Want to contribute? There's a few moving parts that you'll need to know about:
- Ripper - The Ruby Parser used by this Project
- Prettier - Provides building blocks for formatting documents in a stylistic way
- Testing - Ensuring that there are no formatting or semantic regressions
Your input Ruby code is parsed by Ripper, and pretty printed using the building blocks provided by Prettier. Jest tests are used to protect against regressions and allow for test driving the development.
Ripper is the built-in CRuby lexer and parser that understands your Ruby code. It is able to read your source code and provide access to the tokens and the abstract syntax tree associated with your code.
You can explore Ripper within an interactive Ruby terminal. The process of identifying the tokens within your source code is called lexing:
$ irb
> require 'ripper'
=> true
> Ripper.tokenize("puts('hello world')")
=> ["puts", "(", "'", "hello world", "'", ")"]
Once the tokens are understood, Ripper constructs an abstract syntax tree:
$ irb
> require 'ripper'
> pp Ripper.sexp("puts('hello world')")
[:program,
[[:method_add_arg,
[:fcall, [:@ident, "puts", [1, 0]]],
[:arg_paren,
[:args_add_block,
[[:string_literal,
[:string_content, [:@tstring_content, "hello world", [1, 6]]]]],
false]]]]]
Sometimes it can be useful to view this visually with Ripper Preview.
The JavaScript Prettier process spawns a Ruby process within parse.js, which interacts with a custom wrapper written around Ripper called RipperJS. By default Ripper discards source code comments from the abstract syntax view. RipperJS attempts to bridge this gap by attaching the original comments to the abstract syntax tree generated by Ripper. Full details are within ripper.rb
If you want to check out the enhanced abstract syntax tree generated by RipperJS:
bin/sexp file.rb
Or to view the output on a code snippet:
bin/sexp "# comment example\nputs('hello world')"
It's worth noting that there are multiple Ruby parsers available, but for this project Ripper has been chosen for the following reasons:
- Ripper is the built in reference implementation for CRuby
- Ripper is stable and always provides access to the latest syntax that Ruby supports
- Ripper is written in C, and it's fast
- Ruby's parser - the Ruby parser that will give you the names of the nodes as well as their structure
- Ripper test parser events - Ripper test file that gives you code examples of each kind of node
- How Ripper parses variables
- Ripper events
- Ripper preview - Visualize Ruby's abstract syntax tree
Prettier provides support for third party plugins by providing access to its document builders. In plugin-ruby the input Ruby file is parsed with Ripper, then printed with Prettier. The printing logic for Ruby can be found within print.js
There are two types of testing performed within this project:
- Visual formatting regressions
- Semantic regressions
Formatting regressions are verified by running the Prettier plugin against a series of pre-determined Ruby test cases and saving the the output to a file for later comparison. If in the future Prettier formats this file differently, the tests will no longer match the stored snapshot and fail. The JavaScript testing framework Jest provides this functionality. Rubocop is additionally ran over these files to ensure that they also match the community standards.
Semantic regressions are verified with with Minitest. After formatting the Ruby test cases the file is ran with Minitest to ensure that it behaves exactly the same way before and after formatting has been applied.
After checking out the repo, run yarn
and bundle
to install dependencies.
Running tests
yarn run test
Updating Jest snapshots
yarn run test -u
Viewing the output of RipperJS on a file
bin/sexp file.rb
Viewing the output of RipperJS on code
bin/sexp 'your_ruby_code(1, 2, 3)'
Viewing the output of Prettier on a file
bin/print file.rb
Viewing the output of Prettier on code
bin/print 'your_ruby_code(1, 2, 3)'