Skip to content

Commit bf9cadb

Browse files
committed
[Fix rubocop#12600] Support Prism as a Ruby parser
Resolves rubocop#12600. This is the initial basic implementation to support multiple parser engines. It is still in an experimental phase. There are still incompatibilities with Prism compared to the Parser gem, but as the RuboCop core, it is possible to begin providing support for Prism independently within the RuboCop core. > [!IMPORTANT] > To work this feature, the following patch for RuboCop AST needs to be released: > rubocop/rubocop-ast#277 ## Setting the parser engine RuboCop allows switching the backend parser by specifying either `parser_whitequark` or `parser_prism` for the `ParserEngine`. Here are the parsers used as backends for each value: - `ParserEngine: parser_whitequark` ... https://github.com/whitequark/parser - `ParserEngine: parser_prism` ... https://github.com/ruby/prism (`Prism::Translation::Parser`) By default, `parser_whitequark` is implicitly used. `parser_whitequark` can analyze source code from Ruby 2.0 and above: ```yaml AllCops: ParserEngine: parser_whitequark ``` `parser_prism` can analyze source code from Ruby 3.3 and above: ```yaml AllCops: ParserEngine: parser_prism TargetRubyVersion: 3.3 ``` `parser_prism` tends to perform analysis faster than `parser_whitequark`. However, there are some incompatibilities with `parser_whitequark`, making it still considered experimental. For known issues, please refer to the Prism issues: https://github.com/ruby/prism/issues?q=is%3Aissue+is%3Aopen+label%3Arubocop ## Incompatibility At the time of opening this PR, the following cops have incompatibility with Prism: - `Gemspec/RequiredRubyVersion` - `Internal_affairs/location_line_equality_comparison` - `Layout/class_structure` - `Layout/empty_lines` - `Layout/end_of_line` - `Layout/heredoc_indentation` - `Layout/indentation_style` - `Layout/line_length` - `Layout/space_around_operators` - `Layout/space_inside_hash_literal_braces` - `Layout/trailing_whitespace` - `Lint/ambiguous_operator` - `Lint/ambiguous_regexp_literal` - `Lint/circular_argument_reference` - `Lint/deprecated_class_methods` - `Lint/deprecated_constants` - `Lint/erb_new_arguments` - `Lint/non_deterministic_require_order` - `Lint/numbered_parameter_assignment` - `Lint/parentheses_as_grouped_expression` - `Lint/redundant_dir_glob_sort` - `Lint/redundant_require_statement` - `Lint/refinement_import_methods` - `Lint/require_parentheses` - `Lint/syntax` - `Lint/unified_integer` - `Lint/useless_else_without_rescue` - `Naming/block_forwarding` - `Naming/heredoc_delimiter_case` - `Naming/heredoc_delimiter_naming` - `Naming/variable_number` - `Security/yaml_load` - `Style/arguments_forwarding` - `Style/array_intersect` - `Style/block_delimiters` - `Style/collection_compact` - `Style/command_literal` - `Style/comparable_clamp` - `Style/conditional_assignment_assign_in_condition` - `Style/conditional_assignment_assign_to_condition` - `Style/data_inheritance` - `Style/dir_empty` - `Style/file_empty` - `Style/frozen_string_literal_comment` - `Style/guard_clause` - `Style/hash_except` - `Style/hash_syntax` - `Style/hash_transform_keys` - `Style/hash_transform_values` - `Style/if_with_boolean_literal_branches` - `Style/multiline_ternary_operator` - `Style/multiline_when_then` - `Style/mutable_constant` - `Style/nested_file_dirname` - `Style/numeric_literals` - `Style/numeric_predicate` - `Style/object_then` - `Style/quoted_symbols` - `Style/redundant_begin` - `Style/redundant_freeze` - `Style/redundant_heredoc_delimiter_quotes` - `Style/redundant_parentheses` - `Style/rescue_modifier` - `Style/safe_navigation` - `Style/select_by_regexp` - `Style/single_line_methods` - `Style/slicing_with_range` - `Style/string_literals` - `Style/yaml_file_read` - `Style/yoda_condition` Some cop incompatibilities have been resolved in the Prism development line. For known issues, please refer to the Prism issues: https://github.com/ruby/prism/issues?q=is%3Aissue+is%3Aopen+label%3Arubocop ### `Lint/Syntax` cop The messages generated by Lint/Syntax depend on the parser engine used. Parser gem: ```console $ ruby -rparser/ruby33 -ve 'p Parser::Ruby33.parse("(")' ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-darwin22] (string):1:2: error: unexpected token $end ``` Displays `unexpected token $end`. Prism: ```console $ ruby -rprism -rprism/translation/parser33 -ve 'p Prism::Translation::Parser33.parse("(")' ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-darwin22] (string):1:2: error: expected a matching `)` ``` Displays `expected a matching )`. There are differences in the messages between Parser gem and Prism, but since Prism can provide clearer messages in some cases, this incompatibility is accepted. In other words, the messages may vary depending on the parser engine. ## Test To run tests with Prism, the command `bundle exec rake prism_spec` is provided. This task is also executed in GitHub Actions. To run tests with Prism specifying files, set the environment variable `PARSER_ENGINE=parser_prism`: ```console $ PARSER_ENGINE=parser_prism path/to/test_spec.rb ``` In the context of testing with Prism, two options for test cases are provided: `broken_on: :prism` and `unsupported_on: :prism`. Both options are utilized to skip tests specifically for Prism. ### `broken_on: :prism` Test cases failing due to Prism incompatibilities are marked with `broken_on: :prism`. This indicates an expectation for the issue to be resolved within Prism. ### `unsupported_on: :prism` Prism is designed to parse Ruby versions 3.3+, which means that features unique to Ruby versions 2.0 through 3.2 are not supported. Test cases falling into this category are marked with `unsupported_on: :prism`. This marker is used for cases that are testable with the Parser gem but not with Prism. > [!NOTE] > With `bundle exec rake`, `prism_spec` will be run instead of `ascii_spec`. > The `ascii_spec` task has not been failing for a while, so it will not be run by default. > However, `ascii_spec` task will continue to be checked in CI. If there are any failures > originating from `ascii_spec` in CI, please run `bundle exec ascii_spec` to investigate.
1 parent 8acce28 commit bf9cadb

File tree

95 files changed

+506
-259
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+506
-259
lines changed

.github/workflows/rubocop.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,22 @@ jobs:
7676
- name: internal_investigation
7777
run: bundle exec rake internal_investigation
7878

79+
prism:
80+
runs-on: ubuntu-latest
81+
name: Prism
82+
steps:
83+
- uses: actions/checkout@v4
84+
- name: set up Ruby
85+
uses: ruby/setup-ruby@v1
86+
with:
87+
# Specify the minimum Ruby version 2.7 required for Prism to run.
88+
ruby-version: 2.7
89+
bundler-cache: true
90+
- name: spec
91+
env:
92+
PARSER_ENGINE: parser_prism
93+
run: bundle exec rake prism_spec
94+
7995
rspec4:
8096
runs-on: ubuntu-latest
8197
name: RSpec 4

.rubocop_todo.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ InternalAffairs/NodeDestructuring:
1313
# Offense count: 55
1414
# Configuration parameters: CountComments, CountAsOne.
1515
Metrics/ClassLength:
16-
Max: 191
16+
Max: 192
1717

1818
# Offense count: 235
1919
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.

Rakefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ Dir['tasks/**/*.rake'].each { |t| load t }
2424
desc 'Run RuboCop over itself'
2525
RuboCop::RakeTask.new(:internal_investigation)
2626

27-
task default: %i[documentation_syntax_check spec ascii_spec internal_investigation]
27+
# The `ascii_spec` task has not been failing for a while, so it will not be run by default.
28+
# However, `ascii_spec` task will continue to be checked in CI. If there are any failures
29+
# originating from `ascii_spec` in CI, please run `bundle exec ascii_spec` to investigate.
30+
task default: %i[documentation_syntax_check spec prism_spec internal_investigation]
2831

2932
require 'yard'
3033
YARD::Rake::YardocTask.new

changelog/new_support_prism.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#12600](https://github.com/rubocop/rubocop/issues/12600): Support Prism as a Ruby parser (experimental). ([@koic][])

config/default.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,12 @@ AllCops:
144144
# Ruby version is still unresolved, RuboCop will use the oldest officially
145145
# supported Ruby version (currently Ruby 2.7).
146146
TargetRubyVersion: ~
147+
# You can specify the parser engine. There are two options available:
148+
# - `parser_whitequark` ... https://github.com/whitequark/parser
149+
# - `parser_prism` ... https://github.com/ruby/prism (`Prism::Translation::Parser`)
150+
# By default, `parser` is used. For the `TargetRubyVersion` value, `parser` can be specified for versions `2.0` and above.
151+
# `parser_prism` can be specified for versions `3.3` and above. `parser_prism` is faster but still considered experimental.
152+
ParserEngine: parser_whitequark
147153
# Determines if a notification for extension libraries should be shown when
148154
# rubocop is run. Keys are the name of the extension, and values are an array
149155
# of gems in the Gemfile that the extension is suggested for, if not already

docs/modules/ROOT/pages/compatibility.adoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ The following table is the runtime support matrix.
3737
| 3.3 | -
3838
|===
3939

40-
RuboCop targets Ruby 2.0+ code analysis since RuboCop 1.30. It restored code analysis support that had been removed earlier by mistake, together with dropping runtime support for unsupported Ruby versions.
40+
RuboCop targets Ruby 2.0+ code analysis with Parser gem as a parser since RuboCop 1.30. It restored code analysis support that had been removed earlier by mistake, together with dropping runtime support for unsupported Ruby versions.
41+
42+
Starting from RuboCop 1.62, support for Prism's `Prism::Translation::parser` will enable analysis of Ruby 3.3+. For more details, please refer to the xref:configuration.adoc#setting-the-parser-engine[setting `ParserEngine`].
4143

4244
NOTE: The compatibility xref:configuration.adoc#setting-the-target-ruby-version[setting `TargetRubyVersion`] is about code analysis (what RuboCop can analyze), not runtime (is RuboCop capable of running on some Ruby or not).
4345

docs/modules/ROOT/pages/configuration.adoc

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,12 +645,50 @@ AllCops:
645645
TargetRubyVersion: 2.5
646646
----
647647

648+
NOTE: When `ParserEngine: parser_prism` is specified, the values that can be assigned to `TargetRubyVersion` must be `3.3` or higher.
649+
648650
Otherwise, RuboCop will then check your project for a series of files where
649651
the version may be specified already. The files that will be looked for are
650652
`*.gemspec`, `.ruby-version`, `.tool-versions`, and `Gemfile.lock`.
651653
If Gemspec file has an array for `required_ruby_version`, the lowest version will be used.
652654
If none of the files are found a default version value will be used.
653655

656+
== Setting the parser engine
657+
658+
NOTE: The parser engine configuration was introduced in RuboCop 1.62. This experimental feature has been under consideration for a while.
659+
660+
RuboCop allows switching the backend parser by specifying either `parser_whitequark` or `parser_prism` for the `ParserEngine`.
661+
662+
Here are the parsers used as backends for each value:
663+
664+
- `ParserEngine: parser_whitequark` ... https://github.com/whitequark/parser
665+
- `ParserEngine: parser_prism` ... https://github.com/ruby/prism (`Prism::Translation::Parser`)
666+
667+
By default, `parser_whitequark` is implicitly used.
668+
669+
`parser_whitequark` can analyze source code from Ruby 2.0 and above:
670+
671+
[source,yaml]
672+
----
673+
AllCops:
674+
ParserEngine: parser_whitequark
675+
----
676+
677+
`parser_prism` can analyze source code from Ruby 3.3 and above:
678+
679+
[source,yaml]
680+
----
681+
AllCops:
682+
ParserEngine: parser_prism
683+
TargetRubyVersion: 3.3
684+
----
685+
686+
`parser_prism` tends to perform analysis faster than `parser_whitequark`.
687+
688+
However, there are some incompatibilities with `parser_whitequark`, making it still considered experimental.
689+
For known issues, please refer to the Prism issues:
690+
https://github.com/ruby/prism/issues?q=is%3Aissue+is%3Aopen+label%3Arubocop
691+
654692
== Automatically Generated Configuration
655693

656694
If you have a code base with an overwhelming amount of offenses, it can

docs/modules/ROOT/pages/development.adoc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,26 @@ This works because the correcting a file is implemented by repeating investigati
372372

373373
Note that `expect_correction` in `Cop` specs only asserts the result after one pass.
374374

375+
=== Run tests
376+
377+
RuboCop supports two parser engines: the Parser gem and Prism. By default, tests are executed with the Parser:
378+
379+
```console
380+
$ bundle exec rake spec
381+
```
382+
383+
To run all tests with the experimental support for Prism, use `bundle exec prism_spec`, and to execute tests for individual files,
384+
specify the environment variable `PARSER_ENGINE=parser_prism`.
385+
386+
e.g., `PARSER_ENGINE=parser_prism spec/rubocop/cop/style/hash_syntax_spec.rb`
387+
388+
`bundle exec rake` runs tests for both Parser gem and Prism parsers.
389+
390+
But `ascii_spec` rake task does not run by default. Because `ascii_spec` task has not been failing for a while.
391+
However, `ascii_spec` task will continue to be checked in CI.
392+
393+
In CI, all tests required for merging are executed. Please investigate if anything fails.
394+
375395
=== Configuration
376396

377397
Each cop can hold a configuration and you can refer to `cop_config` in the

lib/rubocop/config.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def validate_after_resolution
6262

6363
def_delegators :@hash, :[], :[]=, :delete, :dig, :each, :key?, :keys, :each_key,
6464
:fetch, :map, :merge, :replace, :to_h, :to_hash, :transform_values
65-
def_delegators :@validator, :validate, :target_ruby_version
65+
def_delegators :@validator, :validate, :target_ruby_version, :parser_engine
6666

6767
def to_s
6868
@to_s ||= @hash.to_s

lib/rubocop/config_validator.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ def target_ruby_version
6363
target_ruby.version
6464
end
6565

66+
def parser_engine
67+
for_all_cops.fetch('ParserEngine', :parser_whitequark).to_sym
68+
end
69+
6670
def validate_section_presence(name)
6771
return unless @config.key?(name) && @config[name].nil?
6872

0 commit comments

Comments
 (0)