├── .github └── workflows │ └── main.yml ├── .gitignore ├── .rubocop.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── bin ├── bench └── parse ├── lib └── parser │ ├── prism.rb │ └── prism │ ├── compare.rb │ ├── compiler.rb │ ├── lexer.rb │ └── rubocop.rb ├── parser-prism.gemspec └── test ├── fixtures ├── LICENSE ├── __ENCODING__.rb ├── __ENCODING___legacy_.rb ├── alias.rb ├── alias_gvar.rb ├── ambiuous_quoted_label_in_ternary_operator.rb ├── and.rb ├── and_asgn.rb ├── and_or_masgn.rb ├── anonymous_blockarg.rb ├── arg.rb ├── arg_duplicate_ignored.rb ├── arg_label.rb ├── arg_scope.rb ├── args.rb ├── args_args_assocs.rb ├── args_args_assocs_comma.rb ├── args_args_comma.rb ├── args_args_star.rb ├── args_assocs.rb ├── args_assocs_comma.rb ├── args_assocs_legacy.rb ├── args_block_pass.rb ├── args_cmd.rb ├── args_star.rb ├── array_assocs.rb ├── array_plain.rb ├── array_splat.rb ├── array_symbols.rb ├── array_symbols_empty.rb ├── array_symbols_interp.rb ├── array_words.rb ├── array_words_empty.rb ├── array_words_interp.rb ├── asgn_cmd.rb ├── asgn_mrhs.rb ├── back_ref.rb ├── bang.rb ├── bang_cmd.rb ├── begin_cmdarg.rb ├── beginless_erange_after_newline.rb ├── beginless_irange_after_newline.rb ├── beginless_range.rb ├── blockarg.rb ├── blockargs.rb ├── break.rb ├── break_block.rb ├── bug_435.rb ├── bug_447.rb ├── bug_452.rb ├── bug_466.rb ├── bug_473.rb ├── bug_480.rb ├── bug_481.rb ├── bug_ascii_8bit_in_literal.rb ├── bug_cmd_string_lookahead.rb ├── bug_cmdarg.rb ├── bug_def_no_paren_eql_begin.rb ├── bug_do_block_in_call_args.rb ├── bug_do_block_in_cmdarg.rb ├── bug_do_block_in_hash_brace.rb ├── bug_heredoc_do.rb ├── bug_interp_single.rb ├── bug_lambda_leakage.rb ├── bug_regex_verification.rb ├── bug_rescue_empty_else.rb ├── bug_while_not_parens_do.rb ├── case_cond.rb ├── case_cond_else.rb ├── case_expr.rb ├── case_expr_else.rb ├── casgn_scoped.rb ├── casgn_toplevel.rb ├── casgn_unscoped.rb ├── character.rb ├── class.rb ├── class_definition_in_while_cond.rb ├── class_super.rb ├── class_super_label.rb ├── comments_before_leading_dot__27.rb ├── complex.rb ├── cond_begin.rb ├── cond_begin_masgn.rb ├── cond_eflipflop.rb ├── cond_iflipflop.rb ├── cond_match_current_line.rb ├── const_op_asgn.rb ├── const_scoped.rb ├── const_toplevel.rb ├── const_unscoped.rb ├── control_meta_escape_chars_in_regexp__since_31.rb ├── cpath.rb ├── cvar.rb ├── cvasgn.rb ├── dedenting_heredoc.rb ├── dedenting_interpolating_heredoc_fake_line_continuation.rb ├── dedenting_non_interpolating_heredoc_line_continuation.rb ├── def.rb ├── defined.rb ├── defs.rb ├── empty_stmt.rb ├── endless_comparison_method.rb ├── endless_method.rb ├── endless_method_command_syntax.rb ├── endless_method_forwarded_args_legacy.rb ├── endless_method_with_rescue_mod.rb ├── endless_method_without_args.rb ├── ensure.rb ├── ensure_empty.rb ├── false.rb ├── float.rb ├── for.rb ├── for_mlhs.rb ├── forward_arg.rb ├── forward_arg_with_open_args.rb ├── forward_args_legacy.rb ├── forwarded_argument_with_kwrestarg.rb ├── forwarded_argument_with_restarg.rb ├── forwarded_kwrestarg.rb ├── forwarded_kwrestarg_with_additional_kwarg.rb ├── forwarded_restarg.rb ├── gvar.rb ├── gvasgn.rb ├── hash_empty.rb ├── hash_hashrocket.rb ├── hash_kwsplat.rb ├── hash_label.rb ├── hash_label_end.rb ├── hash_pair_value_omission.rb ├── heredoc.rb ├── if.rb ├── if_else.rb ├── if_elsif.rb ├── if_masgn__24.rb ├── if_mod.rb ├── if_nl_then.rb ├── if_while_after_class__since_32.rb ├── int.rb ├── int___LINE__.rb ├── interp_digit_var.rb ├── ivar.rb ├── ivasgn.rb ├── keyword_argument_omission.rb ├── kwarg.rb ├── kwbegin_compstmt.rb ├── kwnilarg.rb ├── kwoptarg.rb ├── kwoptarg_with_kwrestarg_and_forwarded_args.rb ├── kwrestarg_named.rb ├── kwrestarg_unnamed.rb ├── lbrace_arg_after_command_args.rb ├── lparenarg_after_lvar__since_25.rb ├── lvar.rb ├── lvar_injecting_match.rb ├── lvasgn.rb ├── masgn.rb ├── masgn_attr.rb ├── masgn_cmd.rb ├── masgn_const.rb ├── masgn_nested.rb ├── masgn_splat.rb ├── method_definition_in_while_cond.rb ├── module.rb ├── multibyte.rb ├── multiple_pattern_matches.rb ├── newline_in_hash_argument.rb ├── next.rb ├── next_block.rb ├── nil.rb ├── nil_expression.rb ├── non_lvar_injecting_match.rb ├── not.rb ├── not_cmd.rb ├── not_masgn__24.rb ├── nth_ref.rb ├── numbered_args_after_27.rb ├── numparam_outside_block.rb ├── op_asgn.rb ├── op_asgn_cmd.rb ├── op_asgn_index.rb ├── op_asgn_index_cmd.rb ├── optarg.rb ├── or.rb ├── or_asgn.rb ├── parser_bug_272.rb ├── parser_bug_490.rb ├── parser_bug_507.rb ├── parser_bug_518.rb ├── parser_bug_525.rb ├── parser_bug_604.rb ├── parser_bug_640.rb ├── parser_bug_645.rb ├── parser_bug_830.rb ├── parser_drops_truncated_parts_of_squiggly_heredoc.rb ├── parser_slash_slash_n_escaping_in_literals.rb ├── pattern_match.rb ├── pattern_matching__FILE__LINE_literals.rb ├── pattern_matching_blank_else.rb ├── pattern_matching_else.rb ├── pattern_matching_single_line.rb ├── pattern_matching_single_line_allowed_omission_of_parentheses.rb ├── postexe.rb ├── preexe.rb ├── procarg0.rb ├── range_endless.rb ├── range_exclusive.rb ├── range_inclusive.rb ├── rational.rb ├── redo.rb ├── regex_interp.rb ├── regex_plain.rb ├── resbody_list.rb ├── resbody_list_mrhs.rb ├── resbody_list_var.rb ├── resbody_var.rb ├── rescue.rb ├── rescue_else.rb ├── rescue_else_ensure.rb ├── rescue_ensure.rb ├── rescue_in_lambda_block.rb ├── rescue_mod.rb ├── rescue_mod_asgn.rb ├── rescue_mod_masgn.rb ├── rescue_mod_op_assign.rb ├── rescue_without_begin_end.rb ├── restarg_named.rb ├── restarg_unnamed.rb ├── retry.rb ├── return.rb ├── return_block.rb ├── ruby_bug_10279.rb ├── ruby_bug_10653.rb ├── ruby_bug_11107.rb ├── ruby_bug_11380.rb ├── ruby_bug_11873.rb ├── ruby_bug_11873_a.rb ├── ruby_bug_11873_b.rb ├── ruby_bug_11989.rb ├── ruby_bug_11990.rb ├── ruby_bug_12073.rb ├── ruby_bug_12402.rb ├── ruby_bug_12669.rb ├── ruby_bug_12686.rb ├── ruby_bug_13547.rb ├── ruby_bug_14690.rb ├── ruby_bug_15789.rb ├── ruby_bug_9669.rb ├── sclass.rb ├── self.rb ├── send_attr_asgn.rb ├── send_attr_asgn_conditional.rb ├── send_binary_op.rb ├── send_block_chain_cmd.rb ├── send_block_conditional.rb ├── send_call.rb ├── send_conditional.rb ├── send_index.rb ├── send_index_asgn.rb ├── send_index_asgn_legacy.rb ├── send_index_cmd.rb ├── send_index_legacy.rb ├── send_lambda.rb ├── send_lambda_args.rb ├── send_lambda_args_noparen.rb ├── send_lambda_args_shadow.rb ├── send_lambda_legacy.rb ├── send_op_asgn_conditional.rb ├── send_plain.rb ├── send_plain_cmd.rb ├── send_self.rb ├── send_self_block.rb ├── send_unary_op.rb ├── slash_newline_in_heredocs.rb ├── space_args_arg.rb ├── space_args_arg_block.rb ├── space_args_arg_call.rb ├── space_args_arg_newline.rb ├── space_args_block.rb ├── space_args_cmd.rb ├── string___FILE__.rb ├── string_concat.rb ├── string_dvar.rb ├── string_interp.rb ├── string_plain.rb ├── super.rb ├── super_block.rb ├── symbol_interp.rb ├── symbol_plain.rb ├── ternary.rb ├── ternary_ambiguous_symbol.rb ├── trailing_forward_arg.rb ├── true.rb ├── unary_num_pow_precedence.rb ├── undef.rb ├── unless.rb ├── unless_else.rb ├── unless_mod.rb ├── until.rb ├── until_mod.rb ├── until_post.rb ├── var_and_asgn.rb ├── var_op_asgn.rb ├── var_op_asgn_cmd.rb ├── var_or_asgn.rb ├── when_multi.rb ├── when_splat.rb ├── when_then.rb ├── while.rb ├── while_mod.rb ├── while_post.rb ├── xstring_interp.rb ├── xstring_plain.rb ├── yield.rb └── zsuper.rb └── prism_test.rb /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | ci: 9 | name: CI 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@master 13 | - uses: ruby/setup-ruby@v1 14 | with: 15 | ruby-version: '3.2' 16 | bundler-cache: true 17 | - name: Test 18 | run: bundle exec rake test 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | test.rb 3 | tmp 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | NewCops: enable 3 | SuggestExtensions: false 4 | TargetRubyVersion: 80_82_73_83_77.33 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "benchmark-ips" 6 | gem "prism" 7 | gem "rake" 8 | gem "rubocop" 9 | gem "test-unit" 10 | 11 | gemspec 12 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | parser-prism (0.1.0) 5 | parser 6 | prism 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | ast (2.4.2) 12 | benchmark-ips (2.12.0) 13 | json (2.6.3) 14 | language_server-protocol (3.17.0.3) 15 | parallel (1.23.0) 16 | parser (3.2.2.4) 17 | ast (~> 2.4.1) 18 | racc 19 | power_assert (2.0.3) 20 | prism (0.19.0) 21 | racc (1.7.3) 22 | rainbow (3.1.1) 23 | rake (13.1.0) 24 | regexp_parser (2.8.2) 25 | rexml (3.2.6) 26 | rubocop (1.57.2) 27 | json (~> 2.3) 28 | language_server-protocol (>= 3.17.0) 29 | parallel (~> 1.10) 30 | parser (>= 3.2.2.4) 31 | rainbow (>= 2.2.2, < 4.0) 32 | regexp_parser (>= 1.8, < 3.0) 33 | rexml (>= 3.2.5, < 4.0) 34 | rubocop-ast (>= 1.28.1, < 2.0) 35 | ruby-progressbar (~> 1.7) 36 | unicode-display_width (>= 2.4.0, < 3.0) 37 | rubocop-ast (1.30.0) 38 | parser (>= 3.2.1.0) 39 | ruby-progressbar (1.13.0) 40 | test-unit (3.6.1) 41 | power_assert 42 | unicode-display_width (2.5.0) 43 | 44 | PLATFORMS 45 | arm64-darwin-22 46 | x86_64-linux 47 | 48 | DEPENDENCIES 49 | benchmark-ips 50 | parser-prism! 51 | prism 52 | rake 53 | rubocop 54 | test-unit 55 | 56 | BUNDLED WITH 57 | 2.4.13 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023-present Kevin Newton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > :warning: Moved: The translation from Prism AST to parser gem AST moved to the [prism](https://github.com/ruby/prism) gem as Prism::Translation::Parser. 2 | 3 | # parser-prism 4 | 5 | This gem provides a new backend for the [whitequark/parser](https://github.com/whitequark/parser) gem's syntax tree that uses the [prism](https://github.com/ruby/prism) parser. 6 | 7 | ## Installation 8 | 9 | Add this line to your application's Gemfile: 10 | 11 | ```ruby 12 | gem "parser-prism" 13 | ``` 14 | 15 | And then execute: 16 | 17 | $ bundle 18 | 19 | Or install it yourself as: 20 | 21 | $ gem install parser-prism 22 | 23 | ## Usage 24 | 25 | The `parser` gem provides multiple parsers to support different versions of the Ruby grammar. This includes all of the Ruby versions going back to 1.8, as well as third-party parsers like MacRuby and RubyMotion. The `parser-prism` gem provides another parser that uses the `prism` parser to build the syntax tree. 26 | 27 | You can use the `prism` parser like you would any other. After requiring the parser, you should be able to call any of the regular `Parser::Base` APIs that you would normally use. 28 | 29 | ```ruby 30 | require "parser/prism" 31 | 32 | Parser::Prism.parse_file("path/to/file.rb") 33 | ``` 34 | 35 | ### RuboCop 36 | 37 | To run RuboCop using the `parser-prism` gem as the parser, you will need to require the `parser/prism/rubocop` file. This file injects `prism` into the known options for both `rubocop` and `rubocop-ast`, such that you can specify it in your `.rubocop.yml` file. Unfortunately `rubocop` doesn't support any direct way to do this, so we have to get a bit hacky. 38 | 39 | First, set the `TargetRubyVersion` in your RuboCop configuration file to `80_82_73_83_77.33`. This is the version of Ruby that `prism` reports itself as. (The leading numbers are the ASCII values for `PRISM`.) 40 | 41 | ```yaml 42 | AllCops: 43 | TargetRubyVersion: 80_82_73_83_77.33 44 | ``` 45 | 46 | Now when you run `rubocop` you will need to require the `parser/prism/rubocop` file before executing so that it can inject the `prism` parser into the known options. 47 | 48 | ``` 49 | bundle exec ruby -rparser/prism/rubocop $(bundle exec which rubocop) 50 | ``` 51 | 52 | This should run RuboCop using the `prism` parser. 53 | 54 | ## Benchmarks 55 | 56 | As a whole, this parser should be significantly faster than the `parser` gem. The `bin/bench` script in this repository compares the performance of `Parser::CurrentRuby` and `Parser::Prism`. Running against a large file like `lib/parser/prism/compiler.rb` yields: 57 | 58 | ``` 59 | Warming up -------------------------------------- 60 | Parser::CurrentRuby 1.000 i/100ms 61 | Parser::Prism 6.000 i/100ms 62 | Calculating ------------------------------------- 63 | Parser::CurrentRuby 16.642 (± 0.0%) i/s - 84.000 in 5.052021s 64 | Parser::Prism 64.951 (± 3.1%) i/s - 330.000 in 5.088147s 65 | 66 | Comparison: 67 | Parser::Prism: 65.0 i/s 68 | Parser::CurrentRuby: 16.6 i/s - 3.90x slower 69 | ``` 70 | 71 | When running with `--yjit`, the comparison is even more stark: 72 | 73 | ``` 74 | Warming up -------------------------------------- 75 | Parser::CurrentRuby 1.000 i/100ms 76 | Parser::Prism 9.000 i/100ms 77 | Calculating ------------------------------------- 78 | Parser::CurrentRuby 20.062 (± 0.0%) i/s - 101.000 in 5.034389s 79 | Parser::Prism 112.823 (± 9.7%) i/s - 558.000 in 5.009460s 80 | 81 | Comparison: 82 | Parser::Prism: 112.8 i/s 83 | Parser::CurrentRuby: 20.1 i/s - 5.62x slower 84 | ``` 85 | 86 | These benchmarks were run on a single laptop without a lot of control for other processes, so take them with a grain of salt. 87 | 88 | ## Development 89 | 90 | Run `bundle exec rake test` to run the tests. This runs tests exported from the `parser` gem into their own fixture files. 91 | 92 | ## Contributing 93 | 94 | Bug reports and pull requests are welcome on GitHub at https://github.com/kddnewton/parser-prism. 95 | 96 | ## License 97 | 98 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 99 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rake/testtask" 5 | 6 | Rake::TestTask.new(:test) do |t| 7 | t.libs << "test" 8 | t.libs << "lib" 9 | t.test_files = FileList["test/**/*_test.rb"] 10 | end 11 | 12 | task default: :test 13 | 14 | # This namespace's purpose is to extract the examples from the whitequark/parser 15 | # gem and generate a test file that we can use to ensure that our parser 16 | # generates equivalent syntax trees when translating. To do this, it runs the 17 | # parser's test suite but overrides the `assert_parses` method to collect the 18 | # examples into a hash. Then, it writes out the hash to a file that we can use 19 | # to generate our own tests. 20 | # 21 | # To run the test suite, it's important to note that we have to mirror both any 22 | # APIs provided to the test suite (for example the ParseHelper module below). 23 | # This is obviously relatively brittle, but it's effective for now. 24 | namespace :whitequark do 25 | desc "Ensure there's a local copy of whitequark/parser" 26 | file "tmp/whitequark" do 27 | sh "git clone --depth=1 https://github.com/whitequark/parser tmp/whitequark" 28 | end 29 | 30 | desc "Ensure we have a fixtures directory for the whitequark/parser tests" 31 | directory "test/fixtures" 32 | 33 | desc "Import the whitequark/parser tests" 34 | task import: ["tmp/whitequark", "test/fixtures"] do 35 | cp "tmp/whitequark/LICENSE.txt", "test/fixtures/LICENSE" 36 | 37 | mkdir_p "tmp/whitequark/scratch" 38 | touch "tmp/whitequark/scratch/helper.rb" 39 | touch "tmp/whitequark/scratch/parse_helper.rb" 40 | $:.unshift("tmp/whitequark/scratch") 41 | 42 | require "ast" 43 | module ParseHelper 44 | include AST::Sexp 45 | 46 | # This object is going to collect all of the examples from the parser gem 47 | # into a hash that we can use to generate our own tests. 48 | COLLECTED = Hash.new { |hash, key| hash[key] = [] } 49 | ALL_VERSIONS = %w[3.1 3.2] 50 | 51 | private 52 | 53 | def assert_context(*) 54 | end 55 | 56 | def assert_diagnoses(*) 57 | end 58 | 59 | def assert_diagnoses_many(*) 60 | end 61 | 62 | def refute_diagnoses(*) 63 | end 64 | 65 | def with_versions(*) 66 | end 67 | 68 | def assert_parses(_ast, code, _source_maps = "", versions = ALL_VERSIONS) 69 | # We're going to skip any examples that are for older Ruby versions 70 | # that we do not support. 71 | return if (versions & %w[3.1 3.2]).empty? 72 | 73 | entry = caller.find { _1.include?("test_parser.rb") } 74 | _, name = *entry.match(/\d+:in `(?:block in )?(?:test_|assert_parses_)?(.+)'/) 75 | 76 | COLLECTED[name] << code 77 | end 78 | end 79 | 80 | require "parser/current" 81 | require "minitest/autorun" 82 | require_relative "tmp/whitequark/test/test_parser" 83 | 84 | Minitest.after_run do 85 | ParseHelper::COLLECTED.each do |(name, codes)| 86 | File.write("test/fixtures/#{name}.rb", "#{codes.sort.join("\n\n")}\n") 87 | end 88 | end 89 | end 90 | 91 | desc "Clean up tmp files related to whitequark/parser" 92 | task :clean do 93 | rm_rf "tmp/whitequark" 94 | rm_rf "test/fixtures" 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /bin/bench: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "benchmark/ips" 6 | require "parser/prism" 7 | require "parser/current" 8 | 9 | filepath = ARGV.fetch(0) { File.expand_path("../lib/parser/prism/compiler.rb", __dir__) } 10 | 11 | Benchmark.ips do |x| 12 | x.report("Parser::CurrentRuby") { Parser::CurrentRuby.parse_file(filepath) } 13 | x.report("Parser::Prism") { Parser::Prism.parse_file(filepath) } 14 | x.compare! 15 | end 16 | -------------------------------------------------------------------------------- /bin/parse: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | $:.unshift(File.expand_path("../lib", __dir__)) 6 | 7 | require "parser/prism" 8 | require "parser/prism/compare" 9 | 10 | if ARGV[0].nil? 11 | filepaths = Dir[File.expand_path("../test/fixtures/*.rb", __dir__)] 12 | failures = 0 13 | 14 | filepaths.each do |filepath| 15 | failures += 1 unless Parser::Prism.compare(filepath) 16 | end 17 | 18 | puts "#{filepaths.size - failures}/#{filepaths.size} tests passed" 19 | elsif ARGV[0] == "-e" 20 | puts "match!" if Parser::Prism.compare("-e", ARGV[1]) 21 | else 22 | puts "match!" if Parser::Prism.compare(ARGV[0]) 23 | end 24 | -------------------------------------------------------------------------------- /lib/parser/prism.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "parser" 4 | require "prism" 5 | 6 | module Parser 7 | class Prism < Base 8 | Racc_debug_parser = false 9 | 10 | def version 11 | 33 12 | end 13 | 14 | def default_encoding 15 | Encoding::UTF_8 16 | end 17 | 18 | def yyerror 19 | end 20 | 21 | ## 22 | # Parses a source buffer and returns the AST. 23 | # 24 | # @param [Parser::Source::Buffer] source_buffer The source buffer to parse. 25 | # @return Parser::AST::Node 26 | # 27 | def parse(source_buffer) 28 | @source_buffer = source_buffer 29 | source = source_buffer.source 30 | 31 | build_ast( 32 | ::Prism.parse(source, filepath: source_buffer.name).value, 33 | build_offset_cache(source) 34 | ) 35 | ensure 36 | @source_buffer = nil 37 | end 38 | 39 | ## 40 | # Parses a source buffer and returns the AST and the source code comments. 41 | # 42 | # @see #parse 43 | # @see Parser::Source::Comment#associate 44 | # @return [Array] 45 | # 46 | def parse_with_comments(source_buffer) 47 | @source_buffer = source_buffer 48 | source = source_buffer.source 49 | 50 | result = ::Prism.parse(source, filepath: source_buffer.name) 51 | 52 | [ 53 | build_ast(result.value, build_offset_cache(source)), 54 | build_comments(result.comments) 55 | ] 56 | ensure 57 | @source_buffer = nil 58 | end 59 | 60 | ## 61 | # Parses a source buffer and returns the AST, the source code comments, 62 | # and the tokens emitted by the lexer. 63 | # 64 | # @param [Parser::Source::Buffer] source_buffer 65 | # @return [Array] 66 | # 67 | def tokenize(source_buffer, _recover = false) 68 | @source_buffer = source_buffer 69 | source = source_buffer.source 70 | 71 | offset_cache = build_offset_cache(source) 72 | result = ::Prism.parse_lex(source, filepath: source_buffer.name) 73 | program, tokens = result.value 74 | 75 | [ 76 | build_ast(program, offset_cache), 77 | build_comments(result.comments, offset_cache), 78 | build_tokens(tokens, offset_cache) 79 | ] 80 | ensure 81 | @source_buffer = nil 82 | end 83 | 84 | # Since prism resolves num params for us, we don't need to support this kind 85 | # of logic here. 86 | def try_declare_numparam(node) 87 | node.children[0].match?(/\A_[1-9]\z/) 88 | end 89 | 90 | private 91 | 92 | # Prism deals with offsets in bytes, while the parser gem deals with offsets 93 | # in characters. We need to handle this conversion in order to build the 94 | # parser gem AST. 95 | # 96 | # If the bytesize of the source is the same as the length, then we can just 97 | # use the offset directly. Otherwise, we build a hash that functions as a 98 | # cache for the conversion. 99 | def build_offset_cache(source) 100 | if source.bytesize == source.length 101 | -> (offset) { offset } 102 | else 103 | Hash.new { |hash, offset| hash[offset] = source.byteslice(0, offset).length } 104 | end 105 | end 106 | 107 | # Build the parser gem AST from the prism AST. 108 | def build_ast(program, offset_cache) 109 | program.accept(Compiler.new(self, offset_cache)) 110 | end 111 | 112 | # Build the parser gem comments from the prism comments. 113 | def build_comments(comments, offset_cache) 114 | comments.map do |comment| 115 | location = comment.location 116 | Source::Comment.new(Source::Range.new(source_buffer, offset_cache[location.start_offset], offset_cache[location.end_offset])) 117 | end 118 | end 119 | 120 | # Build the parser gem tokens from the prism tokens. 121 | def build_tokens(tokens, offset_cache) 122 | Lexer.new(source_buffer, tokens.map(&:first), offset_cache).to_a 123 | end 124 | end 125 | end 126 | 127 | require_relative "prism/compiler" 128 | require_relative "prism/lexer" 129 | -------------------------------------------------------------------------------- /lib/parser/prism/compare.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "parser/current" 4 | 5 | # First, opt in to every AST feature. 6 | Parser::Builders::Default.modernize 7 | 8 | # Modify the source map == check so that it doesn't check against the node 9 | # itself so we don't get into a recursive loop. 10 | Parser::Source::Map.prepend( 11 | Module.new { 12 | def ==(other) 13 | self.class == other.class && 14 | (instance_variables - %i[@node]).map do |ivar| 15 | instance_variable_get(ivar) == other.instance_variable_get(ivar) 16 | end.reduce(:&) 17 | end 18 | } 19 | ) 20 | 21 | # Next, ensure that we're comparing the nodes and also comparing the source 22 | # ranges so that we're getting all of the necessary information. 23 | Parser::AST::Node.prepend( 24 | Module.new { 25 | def ==(other) 26 | super && (location == other.location) 27 | end 28 | } 29 | ) 30 | 31 | module Parser 32 | class Prism 33 | # Compare the ASTs between the translator and the whitequark/parser gem. 34 | def self.compare(filepath, source = nil, compare_tokens: true) 35 | buffer = Source::Buffer.new(filepath, 1) 36 | buffer.source = source || File.read(filepath) 37 | 38 | parser = CurrentRuby.default_parser 39 | parser.diagnostics.consumer = ->(*) {} 40 | parser.diagnostics.all_errors_are_fatal = true 41 | 42 | expected_ast, expected_comments, expected_tokens = 43 | begin 44 | parser.tokenize(buffer) 45 | rescue ArgumentError, SyntaxError 46 | return true 47 | end 48 | 49 | actual_ast, actual_comments, actual_tokens = Prism.new.tokenize(buffer) 50 | 51 | if expected_ast != actual_ast 52 | puts filepath 53 | queue = [[expected_ast, actual_ast]] 54 | 55 | while (left, right = queue.shift) 56 | if left.type != right.type 57 | puts "expected:" 58 | pp left 59 | 60 | puts "actual:" 61 | pp right 62 | 63 | return false 64 | end 65 | 66 | if left.location != right.location 67 | puts "expected:" 68 | pp left 69 | pp left.location 70 | 71 | puts "actual:" 72 | pp right 73 | pp right.location 74 | 75 | return false 76 | end 77 | 78 | if left.type == :str && left.children[0] != right.children[0] 79 | puts "expected:" 80 | pp left 81 | 82 | puts "actual:" 83 | pp right 84 | 85 | return false 86 | end 87 | 88 | left.children.zip(right.children).each do |left_child, right_child| 89 | queue << [left_child, right_child] if left_child.is_a?(Parser::AST::Node) 90 | end 91 | end 92 | 93 | puts "expected:" 94 | pp expected_ast 95 | 96 | puts "actual:" 97 | pp actual_ast 98 | 99 | return false 100 | end 101 | 102 | if compare_tokens && expected_tokens != actual_tokens 103 | expected_index = 0 104 | actual_index = 0 105 | 106 | while expected_index < expected_tokens.length 107 | expected_token = expected_tokens[expected_index] 108 | actual_token = actual_tokens[actual_index] 109 | 110 | expected_index += 1 111 | actual_index += 1 112 | 113 | if expected_token[0] == :tSPACE && actual_token[0] == :tSTRING_END 114 | expected_index += 1 115 | next 116 | end 117 | 118 | case actual_token[0] 119 | when :kDO 120 | actual_token[0] = expected_token[0] if %i[kDO_BLOCK kDO_LAMBDA].include?(expected_token[0]) 121 | when :tLPAREN 122 | actual_token[0] = expected_token[0] if expected_token[0] == :tLPAREN2 123 | when :tLCURLY 124 | actual_token[0] = expected_token[0] if %i[tLBRACE tLBRACE_ARG].include?(expected_token[0]) 125 | when :tPOW 126 | actual_token[0] = expected_token[0] if expected_token[0] == :tDSTAR 127 | end 128 | 129 | if expected_token != actual_token 130 | puts "expected:" 131 | pp expected_token 132 | 133 | puts "actual:" 134 | pp actual_token 135 | 136 | return false 137 | end 138 | end 139 | end 140 | 141 | if expected_comments != actual_comments 142 | puts "expected:" 143 | pp expected_comments 144 | 145 | puts "actual:" 146 | pp actual_comments 147 | 148 | return false 149 | end 150 | 151 | true 152 | end 153 | end 154 | end 155 | -------------------------------------------------------------------------------- /lib/parser/prism/compiler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Prism 4 | class ParserCompiler < Compiler 5 | class CompilationError < StandardError 6 | end 7 | 8 | attr_reader :parser, :builder, :source_buffer, :offset_cache 9 | attr_reader :locals, :in_destructure, :in_pattern 10 | 11 | def initialize(parser, offset_cache, locals: nil, in_destructure: false, in_pattern: false) 12 | @parser = parser 13 | @builder = parser.builder 14 | @source_buffer = parser.source_buffer 15 | @offset_cache = offset_cache 16 | 17 | @locals = locals 18 | @in_destructure = in_destructure 19 | @in_pattern = in_pattern 20 | end 21 | 22 | # alias foo bar 23 | # ^^^^^^^^^^^^^ 24 | def visit_alias_method_node(node) 25 | builder.alias(token(node.keyword_loc), visit(node.new_name), visit(node.old_name)) 26 | end 27 | 28 | # alias $foo $bar 29 | # ^^^^^^^^^^^^^^^ 30 | def visit_alias_global_variable_node(node) 31 | builder.alias(token(node.keyword_loc), visit(node.new_name), visit(node.old_name)) 32 | end 33 | 34 | # foo => bar | baz 35 | # ^^^^^^^^^ 36 | def visit_alternation_pattern_node(node) 37 | builder.match_alt(visit(node.left), token(node.operator_loc), visit(node.right)) 38 | end 39 | 40 | # a and b 41 | # ^^^^^^^ 42 | def visit_and_node(node) 43 | builder.logical_op(:and, visit(node.left), token(node.operator_loc), visit(node.right)) 44 | end 45 | 46 | # [] 47 | # ^^ 48 | def visit_array_node(node) 49 | builder.array(token(node.opening_loc), visit_all(node.elements), token(node.closing_loc)) 50 | end 51 | 52 | # foo => [bar] 53 | # ^^^^^ 54 | def visit_array_pattern_node(node) 55 | if node.constant 56 | builder.const_pattern(visit(node.constant), token(node.opening_loc), builder.array_pattern(nil, visit_all([*node.requireds, *node.rest, *node.posts]), nil), token(node.closing_loc)) 57 | else 58 | builder.array_pattern(token(node.opening_loc), visit_all([*node.requireds, *node.rest, *node.posts]), token(node.closing_loc)) 59 | end 60 | end 61 | 62 | # foo(bar) 63 | # ^^^ 64 | def visit_arguments_node(node) 65 | visit_all(node.arguments) 66 | end 67 | 68 | # { a: 1 } 69 | # ^^^^ 70 | def visit_assoc_node(node) 71 | if node.value.is_a?(ImplicitNode) 72 | builder.pair_label([node.key.slice.chomp(":"), srange(node.key.location)]) 73 | elsif in_pattern && node.value.nil? 74 | if node.key.is_a?(SymbolNode) 75 | builder.match_hash_var([node.key.unescaped, srange(node.key.location)]) 76 | else 77 | builder.match_hash_var_from_str(token(node.key.opening_loc), visit_all(node.key.parts), token(node.key.closing_loc)) 78 | end 79 | elsif node.operator_loc 80 | builder.pair(visit(node.key), token(node.operator_loc), visit(node.value)) 81 | elsif node.key.is_a?(SymbolNode) && node.key.opening_loc.nil? 82 | builder.pair_keyword([node.key.unescaped, srange(node.key.location)], visit(node.value)) 83 | else 84 | parts = 85 | if node.key.is_a?(SymbolNode) 86 | [builder.string_internal([node.key.unescaped, srange(node.key.value_loc)])] 87 | else 88 | visit_all(node.key.parts) 89 | end 90 | 91 | builder.pair_quoted(token(node.key.opening_loc), parts, token(node.key.closing_loc), visit(node.value)) 92 | end 93 | end 94 | 95 | # def foo(**); bar(**); end 96 | # ^^ 97 | # 98 | # { **foo } 99 | # ^^^^^ 100 | def visit_assoc_splat_node(node) 101 | if node.value.nil? && locals.include?(:**) 102 | builder.forwarded_kwrestarg(token(node.operator_loc)) 103 | else 104 | builder.kwsplat(token(node.operator_loc), visit(node.value)) 105 | end 106 | end 107 | 108 | # $+ 109 | # ^^ 110 | def visit_back_reference_read_node(node) 111 | builder.back_ref(token(node.location)) 112 | end 113 | 114 | # begin end 115 | # ^^^^^^^^^ 116 | def visit_begin_node(node) 117 | rescue_bodies = [] 118 | 119 | if (rescue_clause = node.rescue_clause) 120 | begin 121 | find_start_offset = (rescue_clause.reference&.location || rescue_clause.exceptions.last&.location || rescue_clause.keyword_loc).end_offset 122 | find_end_offset = (rescue_clause.statements&.location&.start_offset || rescue_clause.consequent&.location&.start_offset || (find_start_offset + 1)) 123 | 124 | rescue_bodies << builder.rescue_body( 125 | token(rescue_clause.keyword_loc), 126 | rescue_clause.exceptions.any? ? builder.array(nil, visit_all(rescue_clause.exceptions), nil) : nil, 127 | token(rescue_clause.operator_loc), 128 | visit(rescue_clause.reference), 129 | srange_find(find_start_offset, find_end_offset, [";"]), 130 | visit(rescue_clause.statements) 131 | ) 132 | end until (rescue_clause = rescue_clause.consequent).nil? 133 | end 134 | 135 | begin_body = 136 | builder.begin_body( 137 | visit(node.statements), 138 | rescue_bodies, 139 | token(node.else_clause&.else_keyword_loc), 140 | visit(node.else_clause), 141 | token(node.ensure_clause&.ensure_keyword_loc), 142 | visit(node.ensure_clause&.statements) 143 | ) 144 | 145 | if node.begin_keyword_loc 146 | builder.begin_keyword(token(node.begin_keyword_loc), begin_body, token(node.end_keyword_loc)) 147 | else 148 | begin_body 149 | end 150 | end 151 | 152 | # foo(&bar) 153 | # ^^^^ 154 | def visit_block_argument_node(node) 155 | builder.block_pass(token(node.operator_loc), visit(node.expression)) 156 | end 157 | 158 | # foo { |; bar| } 159 | # ^^^ 160 | def visit_block_local_variable_node(node) 161 | builder.shadowarg(token(node.location)) 162 | end 163 | 164 | # A block on a keyword or method call. 165 | def visit_block_node(node) 166 | raise CompilationError, "Cannot directly compile block nodes" 167 | end 168 | 169 | # def foo(&bar); end 170 | # ^^^^ 171 | def visit_block_parameter_node(node) 172 | builder.blockarg(token(node.operator_loc), token(node.name_loc)) 173 | end 174 | 175 | # A block's parameters. 176 | def visit_block_parameters_node(node) 177 | [*visit(node.parameters)].concat(visit_all(node.locals)) 178 | end 179 | 180 | # break 181 | # ^^^^^ 182 | # 183 | # break foo 184 | # ^^^^^^^^^ 185 | def visit_break_node(node) 186 | builder.keyword_cmd(:break, token(node.keyword_loc), nil, visit(node.arguments) || [], nil) 187 | end 188 | 189 | # foo 190 | # ^^^ 191 | # 192 | # foo.bar 193 | # ^^^^^^^ 194 | # 195 | # foo.bar() {} 196 | # ^^^^^^^^^^^^ 197 | def visit_call_node(node) 198 | name = node.name 199 | arguments = node.arguments&.arguments || [] 200 | block = node.block 201 | 202 | if block.is_a?(BlockArgumentNode) 203 | arguments = [*arguments, block] 204 | block = nil 205 | end 206 | 207 | visit_block( 208 | if name == :! 209 | builder.not_op( 210 | token(node.message_loc), 211 | token(node.opening_loc), 212 | visit(node.receiver), 213 | token(node.closing_loc) 214 | ) 215 | elsif name == :[] 216 | builder.index( 217 | visit(node.receiver), 218 | token(node.opening_loc), 219 | visit_all(arguments), 220 | token(node.closing_loc) 221 | ) 222 | elsif name == :[]= && node.message != "[]=" && node.arguments && block.nil? 223 | builder.assign( 224 | builder.index_asgn( 225 | visit(node.receiver), 226 | token(node.opening_loc), 227 | visit_all(node.arguments.arguments[...-1]), 228 | token(node.closing_loc), 229 | ), 230 | srange_find(node.message_loc.end_offset, node.arguments.arguments.last.location.start_offset, ["="]), 231 | visit(node.arguments.arguments.last) 232 | ) 233 | else 234 | message_loc = node.message_loc 235 | call_operator_loc = node.call_operator_loc 236 | call_operator = [{ "." => :dot, "&." => :anddot, "::" => "::" }.fetch(call_operator_loc.slice), srange(call_operator_loc)] if call_operator_loc 237 | 238 | if name.end_with?("=") && !message_loc.slice.end_with?("=") && node.arguments && block.nil? 239 | builder.assign( 240 | builder.attr_asgn(visit(node.receiver), call_operator, token(message_loc)), 241 | srange_find(message_loc.end_offset, node.arguments.location.start_offset, ["="]), 242 | visit(node.arguments.arguments.last) 243 | ) 244 | else 245 | builder.call_method( 246 | visit(node.receiver), 247 | call_operator, 248 | message_loc ? [node.name, srange(message_loc)] : nil, 249 | token(node.opening_loc), 250 | visit_all(arguments), 251 | token(node.closing_loc) 252 | ) 253 | end 254 | end, 255 | block 256 | ) 257 | end 258 | 259 | # foo.bar += baz 260 | # ^^^^^^^^^^^^^^^ 261 | def visit_call_operator_write_node(node) 262 | call_operator_loc = node.call_operator_loc 263 | 264 | builder.op_assign( 265 | builder.call_method( 266 | visit(node.receiver), 267 | call_operator_loc.nil? ? nil : [{ "." => :dot, "&." => :anddot, "::" => "::" }.fetch(call_operator_loc.slice), srange(call_operator_loc)], 268 | node.message_loc ? [node.read_name, srange(node.message_loc)] : nil, 269 | nil, 270 | [], 271 | nil 272 | ), 273 | [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], 274 | visit(node.value) 275 | ) 276 | end 277 | 278 | # foo.bar &&= baz 279 | # ^^^^^^^^^^^^^^^ 280 | alias visit_call_and_write_node visit_call_operator_write_node 281 | 282 | # foo.bar ||= baz 283 | # ^^^^^^^^^^^^^^^ 284 | alias visit_call_or_write_node visit_call_operator_write_node 285 | 286 | # foo.bar, = 1 287 | # ^^^^^^^ 288 | def visit_call_target_node(node) 289 | call_operator_loc = node.call_operator_loc 290 | 291 | builder.attr_asgn( 292 | visit(node.receiver), 293 | call_operator_loc.nil? ? nil : [{ "." => :dot, "&." => :anddot, "::" => "::" }.fetch(call_operator_loc.slice), srange(call_operator_loc)], 294 | token(node.message_loc) 295 | ) 296 | end 297 | 298 | # foo => bar => baz 299 | # ^^^^^^^^^^ 300 | def visit_capture_pattern_node(node) 301 | builder.match_as(visit(node.value), token(node.operator_loc), visit(node.target)) 302 | end 303 | 304 | # case foo; when bar; end 305 | # ^^^^^^^^^^^^^^^^^^^^^^^ 306 | def visit_case_node(node) 307 | builder.case( 308 | token(node.case_keyword_loc), 309 | visit(node.predicate), 310 | visit_all(node.conditions), 311 | token(node.consequent&.else_keyword_loc), 312 | visit(node.consequent), 313 | token(node.end_keyword_loc) 314 | ) 315 | end 316 | 317 | # case foo; in bar; end 318 | # ^^^^^^^^^^^^^^^^^^^^^ 319 | def visit_case_match_node(node) 320 | builder.case_match( 321 | token(node.case_keyword_loc), 322 | visit(node.predicate), 323 | visit_all(node.conditions), 324 | token(node.consequent&.else_keyword_loc), 325 | visit(node.consequent), 326 | token(node.end_keyword_loc) 327 | ) 328 | end 329 | 330 | # class Foo; end 331 | # ^^^^^^^^^^^^^^ 332 | def visit_class_node(node) 333 | builder.def_class( 334 | token(node.class_keyword_loc), 335 | visit(node.constant_path), 336 | token(node.inheritance_operator_loc), 337 | visit(node.superclass), 338 | node.body&.accept(copy_compiler(locals: node.locals)), 339 | token(node.end_keyword_loc) 340 | ) 341 | end 342 | 343 | # @@foo 344 | # ^^^^^ 345 | def visit_class_variable_read_node(node) 346 | builder.cvar(token(node.location)) 347 | end 348 | 349 | # @@foo = 1 350 | # ^^^^^^^^^ 351 | # 352 | # @@foo, @@bar = 1 353 | # ^^^^^ ^^^^^ 354 | def visit_class_variable_write_node(node) 355 | builder.assign( 356 | builder.assignable(builder.cvar(token(node.name_loc))), 357 | token(node.operator_loc), 358 | visit(node.value) 359 | ) 360 | end 361 | 362 | # @@foo += bar 363 | # ^^^^^^^^^^^^ 364 | def visit_class_variable_operator_write_node(node) 365 | builder.op_assign( 366 | builder.assignable(builder.cvar(token(node.name_loc))), 367 | [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], 368 | visit(node.value) 369 | ) 370 | end 371 | 372 | # @@foo &&= bar 373 | # ^^^^^^^^^^^^^ 374 | alias visit_class_variable_and_write_node visit_class_variable_operator_write_node 375 | 376 | # @@foo ||= bar 377 | # ^^^^^^^^^^^^^ 378 | alias visit_class_variable_or_write_node visit_class_variable_operator_write_node 379 | 380 | # @@foo, = bar 381 | # ^^^^^ 382 | def visit_class_variable_target_node(node) 383 | builder.assignable(builder.cvar(token(node.location))) 384 | end 385 | 386 | # Foo 387 | # ^^^ 388 | def visit_constant_read_node(node) 389 | builder.const([node.name, srange(node.location)]) 390 | end 391 | 392 | # Foo = 1 393 | # ^^^^^^^ 394 | # 395 | # Foo, Bar = 1 396 | # ^^^ ^^^ 397 | def visit_constant_write_node(node) 398 | builder.assign(builder.assignable(builder.const([node.name, srange(node.name_loc)])), token(node.operator_loc), visit(node.value)) 399 | end 400 | 401 | # Foo += bar 402 | # ^^^^^^^^^^^ 403 | def visit_constant_operator_write_node(node) 404 | builder.op_assign( 405 | builder.assignable(builder.const([node.name, srange(node.name_loc)])), 406 | [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], 407 | visit(node.value) 408 | ) 409 | end 410 | 411 | # Foo &&= bar 412 | # ^^^^^^^^^^^^ 413 | alias visit_constant_and_write_node visit_constant_operator_write_node 414 | 415 | # Foo ||= bar 416 | # ^^^^^^^^^^^^ 417 | alias visit_constant_or_write_node visit_constant_operator_write_node 418 | 419 | # Foo, = bar 420 | # ^^^ 421 | def visit_constant_target_node(node) 422 | builder.assignable(builder.const([node.name, srange(node.location)])) 423 | end 424 | 425 | # Foo::Bar 426 | # ^^^^^^^^ 427 | def visit_constant_path_node(node) 428 | if node.parent.nil? 429 | builder.const_global( 430 | token(node.delimiter_loc), 431 | [node.child.name, srange(node.child.location)] 432 | ) 433 | else 434 | builder.const_fetch( 435 | visit(node.parent), 436 | token(node.delimiter_loc), 437 | [node.child.name, srange(node.child.location)] 438 | ) 439 | end 440 | end 441 | 442 | # Foo::Bar = 1 443 | # ^^^^^^^^^^^^ 444 | # 445 | # Foo::Foo, Bar::Bar = 1 446 | # ^^^^^^^^ ^^^^^^^^ 447 | def visit_constant_path_write_node(node) 448 | builder.assign( 449 | builder.assignable(visit(node.target)), 450 | token(node.operator_loc), 451 | visit(node.value) 452 | ) 453 | end 454 | 455 | # Foo::Bar += baz 456 | # ^^^^^^^^^^^^^^^ 457 | def visit_constant_path_operator_write_node(node) 458 | builder.op_assign( 459 | builder.assignable(visit(node.target)), 460 | [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], 461 | visit(node.value) 462 | ) 463 | end 464 | 465 | # Foo::Bar &&= baz 466 | # ^^^^^^^^^^^^^^^^ 467 | alias visit_constant_path_and_write_node visit_constant_path_operator_write_node 468 | 469 | # Foo::Bar ||= baz 470 | # ^^^^^^^^^^^^^^^^ 471 | alias visit_constant_path_or_write_node visit_constant_path_operator_write_node 472 | 473 | # Foo::Bar, = baz 474 | # ^^^^^^^^ 475 | def visit_constant_path_target_node(node) 476 | builder.assignable(visit_constant_path_node(node)) 477 | end 478 | 479 | # def foo; end 480 | # ^^^^^^^^^^^^ 481 | # 482 | # def self.foo; end 483 | # ^^^^^^^^^^^^^^^^^ 484 | def visit_def_node(node) 485 | if node.equal_loc 486 | if node.receiver 487 | builder.def_endless_singleton( 488 | token(node.def_keyword_loc), 489 | visit(node.receiver.is_a?(ParenthesesNode) ? node.receiver.body : node.receiver), 490 | token(node.operator_loc), 491 | token(node.name_loc), 492 | builder.args(token(node.lparen_loc), visit(node.parameters) || [], token(node.rparen_loc), false), 493 | token(node.equal_loc), 494 | node.body&.accept(copy_compiler(locals: node.locals)) 495 | ) 496 | else 497 | builder.def_endless_method( 498 | token(node.def_keyword_loc), 499 | token(node.name_loc), 500 | builder.args(token(node.lparen_loc), visit(node.parameters) || [], token(node.rparen_loc), false), 501 | token(node.equal_loc), 502 | node.body&.accept(copy_compiler(locals: node.locals)) 503 | ) 504 | end 505 | elsif node.receiver 506 | builder.def_singleton( 507 | token(node.def_keyword_loc), 508 | visit(node.receiver.is_a?(ParenthesesNode) ? node.receiver.body : node.receiver), 509 | token(node.operator_loc), 510 | token(node.name_loc), 511 | builder.args(token(node.lparen_loc), visit(node.parameters) || [], token(node.rparen_loc), false), 512 | node.body&.accept(copy_compiler(locals: node.locals)), 513 | token(node.end_keyword_loc) 514 | ) 515 | else 516 | builder.def_method( 517 | token(node.def_keyword_loc), 518 | token(node.name_loc), 519 | builder.args(token(node.lparen_loc), visit(node.parameters) || [], token(node.rparen_loc), false), 520 | node.body&.accept(copy_compiler(locals: node.locals)), 521 | token(node.end_keyword_loc) 522 | ) 523 | end 524 | end 525 | 526 | # defined? a 527 | # ^^^^^^^^^^ 528 | # 529 | # defined?(a) 530 | # ^^^^^^^^^^^ 531 | def visit_defined_node(node) 532 | builder.keyword_cmd( 533 | :defined?, 534 | token(node.keyword_loc), 535 | token(node.lparen_loc), 536 | [visit(node.value)], 537 | token(node.rparen_loc) 538 | ) 539 | end 540 | 541 | # if foo then bar else baz end 542 | # ^^^^^^^^^^^^ 543 | def visit_else_node(node) 544 | visit(node.statements) 545 | end 546 | 547 | # "foo #{bar}" 548 | # ^^^^^^ 549 | def visit_embedded_statements_node(node) 550 | builder.begin( 551 | token(node.opening_loc), 552 | visit(node.statements), 553 | token(node.closing_loc) 554 | ) 555 | end 556 | 557 | # "foo #@bar" 558 | # ^^^^^ 559 | def visit_embedded_variable_node(node) 560 | visit(node.variable) 561 | end 562 | 563 | # begin; foo; ensure; bar; end 564 | # ^^^^^^^^^^^^ 565 | def visit_ensure_node(node) 566 | raise CompilationError, "Cannot directly compile ensure nodes" 567 | end 568 | 569 | # false 570 | # ^^^^^ 571 | def visit_false_node(node) 572 | builder.false(token(node.location)) 573 | end 574 | 575 | # foo => [*, bar, *] 576 | # ^^^^^^^^^^^ 577 | def visit_find_pattern_node(node) 578 | if node.constant 579 | builder.const_pattern(visit(node.constant), token(node.opening_loc), builder.find_pattern(nil, visit_all([node.left, *node.requireds, node.right]), nil), token(node.closing_loc)) 580 | else 581 | builder.find_pattern(token(node.opening_loc), visit_all([node.left, *node.requireds, node.right]), token(node.closing_loc)) 582 | end 583 | end 584 | 585 | # 1.0 586 | # ^^^ 587 | def visit_float_node(node) 588 | visit_numeric(node, builder.float([node.value, srange(node.location)])) 589 | end 590 | 591 | # for foo in bar do end 592 | # ^^^^^^^^^^^^^^^^^^^^^ 593 | def visit_for_node(node) 594 | builder.for( 595 | token(node.for_keyword_loc), 596 | visit(node.index), 597 | token(node.in_keyword_loc), 598 | visit(node.collection), 599 | if node.do_keyword_loc 600 | token(node.do_keyword_loc) 601 | else 602 | srange_find(node.collection.location.end_offset, (node.statements&.location || node.end_keyword_loc).start_offset, [";"]) 603 | end, 604 | visit(node.statements), 605 | token(node.end_keyword_loc) 606 | ) 607 | end 608 | 609 | # def foo(...); bar(...); end 610 | # ^^^ 611 | def visit_forwarding_arguments_node(node) 612 | builder.forwarded_args(token(node.location)) 613 | end 614 | 615 | # def foo(...); end 616 | # ^^^ 617 | def visit_forwarding_parameter_node(node) 618 | builder.forward_arg(token(node.location)) 619 | end 620 | 621 | # super 622 | # ^^^^^ 623 | # 624 | # super {} 625 | # ^^^^^^^^ 626 | def visit_forwarding_super_node(node) 627 | visit_block( 628 | builder.keyword_cmd( 629 | :zsuper, 630 | ["super", srange_offsets(node.location.start_offset, node.location.start_offset + 5)] 631 | ), 632 | node.block 633 | ) 634 | end 635 | 636 | # $foo 637 | # ^^^^ 638 | def visit_global_variable_read_node(node) 639 | builder.gvar(token(node.location)) 640 | end 641 | 642 | # $foo = 1 643 | # ^^^^^^^^ 644 | # 645 | # $foo, $bar = 1 646 | # ^^^^ ^^^^ 647 | def visit_global_variable_write_node(node) 648 | builder.assign( 649 | builder.assignable(builder.gvar(token(node.name_loc))), 650 | token(node.operator_loc), 651 | visit(node.value) 652 | ) 653 | end 654 | 655 | # $foo += bar 656 | # ^^^^^^^^^^^ 657 | def visit_global_variable_operator_write_node(node) 658 | builder.op_assign( 659 | builder.assignable(builder.gvar(token(node.name_loc))), 660 | [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], 661 | visit(node.value) 662 | ) 663 | end 664 | 665 | # $foo &&= bar 666 | # ^^^^^^^^^^^^ 667 | alias visit_global_variable_and_write_node visit_global_variable_operator_write_node 668 | 669 | # $foo ||= bar 670 | # ^^^^^^^^^^^^ 671 | alias visit_global_variable_or_write_node visit_global_variable_operator_write_node 672 | 673 | # $foo, = bar 674 | # ^^^^ 675 | def visit_global_variable_target_node(node) 676 | builder.assignable(builder.gvar([node.slice, srange(node.location)])) 677 | end 678 | 679 | # {} 680 | # ^^ 681 | def visit_hash_node(node) 682 | builder.associate( 683 | token(node.opening_loc), 684 | visit_all(node.elements), 685 | token(node.closing_loc) 686 | ) 687 | end 688 | 689 | # foo => {} 690 | # ^^ 691 | def visit_hash_pattern_node(node) 692 | elements = [*node.elements, *node.rest] 693 | 694 | if node.constant 695 | builder.const_pattern(visit(node.constant), token(node.opening_loc), builder.hash_pattern(nil, visit_all(elements), nil), token(node.closing_loc)) 696 | else 697 | builder.hash_pattern(token(node.opening_loc), visit_all(elements), token(node.closing_loc)) 698 | end 699 | end 700 | 701 | # if foo then bar end 702 | # ^^^^^^^^^^^^^^^^^^^ 703 | # 704 | # bar if foo 705 | # ^^^^^^^^^^ 706 | # 707 | # foo ? bar : baz 708 | # ^^^^^^^^^^^^^^^ 709 | def visit_if_node(node) 710 | if !node.if_keyword_loc 711 | builder.ternary( 712 | visit(node.predicate), 713 | token(node.then_keyword_loc), 714 | visit(node.statements), 715 | token(node.consequent.else_keyword_loc), 716 | visit(node.consequent) 717 | ) 718 | elsif node.if_keyword_loc.start_offset == node.location.start_offset 719 | builder.condition( 720 | token(node.if_keyword_loc), 721 | visit(node.predicate), 722 | if node.then_keyword_loc 723 | token(node.then_keyword_loc) 724 | else 725 | srange_find(node.predicate.location.end_offset, (node.statements&.location || node.consequent&.location || node.end_keyword_loc).start_offset, [";"]) 726 | end, 727 | visit(node.statements), 728 | case node.consequent 729 | when IfNode 730 | token(node.consequent.if_keyword_loc) 731 | when ElseNode 732 | token(node.consequent.else_keyword_loc) 733 | end, 734 | visit(node.consequent), 735 | if node.if_keyword != "elsif" 736 | token(node.end_keyword_loc) 737 | end 738 | ) 739 | else 740 | builder.condition_mod( 741 | visit(node.statements), 742 | visit(node.consequent), 743 | token(node.if_keyword_loc), 744 | visit(node.predicate) 745 | ) 746 | end 747 | end 748 | 749 | # 1i 750 | def visit_imaginary_node(node) 751 | visit_numeric(node, builder.complex([node.value, srange(node.location)])) 752 | end 753 | 754 | # { foo: } 755 | # ^^^^ 756 | def visit_implicit_node(node) 757 | raise CompilationError, "Cannot directly compile implicit nodes" 758 | end 759 | 760 | # foo { |bar,| } 761 | # ^ 762 | def visit_implicit_rest_node(node) 763 | raise CompilationError, "Cannot compile implicit rest nodes" 764 | end 765 | 766 | # case foo; in bar; end 767 | # ^^^^^^^^^^^^^^^^^^^^^ 768 | def visit_in_node(node) 769 | pattern = nil 770 | guard = nil 771 | 772 | case node.pattern 773 | when IfNode 774 | pattern = within_pattern { |compiler| node.pattern.statements.accept(compiler) } 775 | guard = builder.if_guard(token(node.pattern.if_keyword_loc), visit(node.pattern.predicate)) 776 | when UnlessNode 777 | pattern = within_pattern { |compiler| node.pattern.statements.accept(compiler) } 778 | guard = builder.unless_guard(token(node.pattern.keyword_loc), visit(node.pattern.predicate)) 779 | else 780 | pattern = within_pattern { |compiler| node.pattern.accept(compiler) } 781 | end 782 | 783 | builder.in_pattern( 784 | token(node.in_loc), 785 | pattern, 786 | guard, 787 | srange_find(node.pattern.location.end_offset, node.statements&.location&.start_offset || node.location.end_offset, [";", "then"]), 788 | visit(node.statements) 789 | ) 790 | end 791 | 792 | # foo[bar] += baz 793 | # ^^^^^^^^^^^^^^^ 794 | def visit_index_operator_write_node(node) 795 | arguments = node.arguments&.arguments || [] 796 | arguments << node.block if node.block 797 | 798 | builder.op_assign( 799 | builder.index( 800 | visit(node.receiver), 801 | token(node.opening_loc), 802 | visit_all(arguments), 803 | token(node.closing_loc) 804 | ), 805 | [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], 806 | visit(node.value) 807 | ) 808 | end 809 | 810 | # foo[bar] &&= baz 811 | # ^^^^^^^^^^^^^^^^ 812 | alias visit_index_and_write_node visit_index_operator_write_node 813 | 814 | # foo[bar] ||= baz 815 | # ^^^^^^^^^^^^^^^^ 816 | alias visit_index_or_write_node visit_index_operator_write_node 817 | 818 | # foo[bar], = 1 819 | # ^^^^^^^^ 820 | def visit_index_target_node(node) 821 | builder.index_asgn( 822 | visit(node.receiver), 823 | token(node.opening_loc), 824 | visit_all(node.arguments.arguments), 825 | token(node.closing_loc), 826 | ) 827 | end 828 | 829 | # @foo 830 | # ^^^^ 831 | def visit_instance_variable_read_node(node) 832 | builder.ivar(token(node.location)) 833 | end 834 | 835 | # @foo = 1 836 | # ^^^^^^^^ 837 | # 838 | # @foo, @bar = 1 839 | # ^^^^ ^^^^ 840 | def visit_instance_variable_write_node(node) 841 | builder.assign( 842 | builder.assignable(builder.ivar(token(node.name_loc))), 843 | token(node.operator_loc), 844 | visit(node.value) 845 | ) 846 | end 847 | 848 | # @foo += bar 849 | # ^^^^^^^^^^^ 850 | def visit_instance_variable_operator_write_node(node) 851 | builder.op_assign( 852 | builder.assignable(builder.ivar(token(node.name_loc))), 853 | [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], 854 | visit(node.value) 855 | ) 856 | end 857 | 858 | # @foo &&= bar 859 | # ^^^^^^^^^^^^ 860 | alias visit_instance_variable_and_write_node visit_instance_variable_operator_write_node 861 | 862 | # @foo ||= bar 863 | # ^^^^^^^^^^^^ 864 | alias visit_instance_variable_or_write_node visit_instance_variable_operator_write_node 865 | 866 | # @foo, = bar 867 | # ^^^^ 868 | def visit_instance_variable_target_node(node) 869 | builder.assignable(builder.ivar(token(node.location))) 870 | end 871 | 872 | # 1 873 | # ^ 874 | def visit_integer_node(node) 875 | visit_numeric(node, builder.integer([node.value, srange(node.location)])) 876 | end 877 | 878 | # /foo #{bar}/ 879 | # ^^^^^^^^^^^^ 880 | def visit_interpolated_regular_expression_node(node) 881 | builder.regexp_compose( 882 | token(node.opening_loc), 883 | visit_all(node.parts), 884 | [node.closing[0], srange_offsets(node.closing_loc.start_offset, node.closing_loc.start_offset + 1)], 885 | builder.regexp_options([node.closing[1..], srange_offsets(node.closing_loc.start_offset + 1, node.closing_loc.end_offset)]) 886 | ) 887 | end 888 | 889 | # if /foo #{bar}/ then end 890 | # ^^^^^^^^^^^^ 891 | alias visit_interpolated_match_last_line_node visit_interpolated_regular_expression_node 892 | 893 | # "foo #{bar}" 894 | # ^^^^^^^^^^^^ 895 | def visit_interpolated_string_node(node) 896 | if node.opening&.start_with?("<<") 897 | children, closing = visit_heredoc(node) 898 | builder.string_compose(token(node.opening_loc), children, closing) 899 | else 900 | builder.string_compose( 901 | token(node.opening_loc), 902 | visit_all(node.parts), 903 | token(node.closing_loc) 904 | ) 905 | end 906 | end 907 | 908 | # :"foo #{bar}" 909 | # ^^^^^^^^^^^^^ 910 | def visit_interpolated_symbol_node(node) 911 | builder.symbol_compose( 912 | token(node.opening_loc), 913 | visit_all(node.parts), 914 | token(node.closing_loc) 915 | ) 916 | end 917 | 918 | # `foo #{bar}` 919 | # ^^^^^^^^^^^^ 920 | def visit_interpolated_x_string_node(node) 921 | if node.opening.start_with?("<<") 922 | children, closing = visit_heredoc(node) 923 | builder.xstring_compose(token(node.opening_loc), children, closing) 924 | else 925 | builder.xstring_compose( 926 | token(node.opening_loc), 927 | visit_all(node.parts), 928 | token(node.closing_loc) 929 | ) 930 | end 931 | end 932 | 933 | # foo(bar: baz) 934 | # ^^^^^^^^ 935 | def visit_keyword_hash_node(node) 936 | builder.associate(nil, visit_all(node.elements), nil) 937 | end 938 | 939 | # def foo(**bar); end 940 | # ^^^^^ 941 | # 942 | # def foo(**); end 943 | # ^^ 944 | def visit_keyword_rest_parameter_node(node) 945 | builder.kwrestarg( 946 | token(node.operator_loc), 947 | node.name ? [node.name, srange(node.name_loc)] : nil 948 | ) 949 | end 950 | 951 | # -> {} 952 | def visit_lambda_node(node) 953 | builder.block( 954 | builder.call_lambda(token(node.operator_loc)), 955 | [node.opening, srange(node.opening_loc)], 956 | if node.parameters 957 | if node.parameters.is_a?(NumberedParametersNode) 958 | visit(node.parameters) 959 | else 960 | builder.args( 961 | token(node.parameters.opening_loc), 962 | visit(node.parameters), 963 | token(node.parameters.closing_loc), 964 | false 965 | ) 966 | end 967 | else 968 | builder.args(nil, [], nil, false) 969 | end, 970 | node.body&.accept(copy_compiler(locals: node.locals)), 971 | [node.closing, srange(node.closing_loc)] 972 | ) 973 | end 974 | 975 | # foo 976 | # ^^^ 977 | def visit_local_variable_read_node(node) 978 | builder.ident([node.name, srange(node.location)]).updated(:lvar) 979 | end 980 | 981 | # foo = 1 982 | # ^^^^^^^ 983 | # 984 | # foo, bar = 1 985 | # ^^^ ^^^ 986 | def visit_local_variable_write_node(node) 987 | builder.assign( 988 | builder.assignable(builder.ident(token(node.name_loc))), 989 | token(node.operator_loc), 990 | visit(node.value) 991 | ) 992 | end 993 | 994 | # foo += bar 995 | # ^^^^^^^^^^ 996 | def visit_local_variable_operator_write_node(node) 997 | builder.op_assign( 998 | builder.assignable(builder.ident(token(node.name_loc))), 999 | [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], 1000 | visit(node.value) 1001 | ) 1002 | end 1003 | 1004 | # foo &&= bar 1005 | # ^^^^^^^^^^^ 1006 | alias visit_local_variable_and_write_node visit_local_variable_operator_write_node 1007 | 1008 | # foo ||= bar 1009 | # ^^^^^^^^^^^ 1010 | alias visit_local_variable_or_write_node visit_local_variable_operator_write_node 1011 | 1012 | # foo, = bar 1013 | # ^^^ 1014 | def visit_local_variable_target_node(node) 1015 | if in_pattern 1016 | builder.assignable(builder.match_var([node.name, srange(node.location)])) 1017 | else 1018 | builder.assignable(builder.ident(token(node.location))) 1019 | end 1020 | end 1021 | 1022 | # foo in bar 1023 | # ^^^^^^^^^^ 1024 | def visit_match_predicate_node(node) 1025 | builder.match_pattern_p( 1026 | visit(node.value), 1027 | token(node.operator_loc), 1028 | within_pattern { |compiler| node.pattern.accept(compiler) } 1029 | ) 1030 | end 1031 | 1032 | # foo => bar 1033 | # ^^^^^^^^^^ 1034 | def visit_match_required_node(node) 1035 | builder.match_pattern( 1036 | visit(node.value), 1037 | token(node.operator_loc), 1038 | within_pattern { |compiler| node.pattern.accept(compiler) } 1039 | ) 1040 | end 1041 | 1042 | # /(?foo)/ =~ bar 1043 | # ^^^^^^^^^^^^^^^^^^^^ 1044 | def visit_match_write_node(node) 1045 | builder.match_op( 1046 | visit(node.call.receiver), 1047 | token(node.call.message_loc), 1048 | visit(node.call.arguments.arguments.first) 1049 | ) 1050 | end 1051 | 1052 | # A node that is missing from the syntax tree. This is only used in the 1053 | # case of a syntax error. The parser gem doesn't have such a concept, so 1054 | # we invent our own here. 1055 | def visit_missing_node(node) 1056 | raise CompilationError, "Cannot compile missing nodes" 1057 | end 1058 | 1059 | # module Foo; end 1060 | # ^^^^^^^^^^^^^^^ 1061 | def visit_module_node(node) 1062 | builder.def_module( 1063 | token(node.module_keyword_loc), 1064 | visit(node.constant_path), 1065 | node.body&.accept(copy_compiler(locals: node.locals)), 1066 | token(node.end_keyword_loc) 1067 | ) 1068 | end 1069 | 1070 | # foo, bar = baz 1071 | # ^^^^^^^^ 1072 | def visit_multi_target_node(node) 1073 | node = node.copy(rest: nil) if node.rest.is_a?(ImplicitRestNode) 1074 | 1075 | builder.multi_lhs( 1076 | token(node.lparen_loc), 1077 | visit_all([*node.lefts, *node.rest, *node.rights]), 1078 | token(node.rparen_loc) 1079 | ) 1080 | end 1081 | 1082 | # foo, bar = baz 1083 | # ^^^^^^^^^^^^^^ 1084 | def visit_multi_write_node(node) 1085 | node = node.copy(rest: nil) if node.rest.is_a?(ImplicitRestNode) 1086 | 1087 | builder.multi_assign( 1088 | builder.multi_lhs( 1089 | token(node.lparen_loc), 1090 | visit_all([*node.lefts, *node.rest, *node.rights]), 1091 | token(node.rparen_loc) 1092 | ), 1093 | token(node.operator_loc), 1094 | visit(node.value) 1095 | ) 1096 | end 1097 | 1098 | # next 1099 | # ^^^^ 1100 | # 1101 | # next foo 1102 | # ^^^^^^^^ 1103 | def visit_next_node(node) 1104 | builder.keyword_cmd( 1105 | :next, 1106 | token(node.keyword_loc), 1107 | nil, 1108 | visit(node.arguments) || [], 1109 | nil 1110 | ) 1111 | end 1112 | 1113 | # nil 1114 | # ^^^ 1115 | def visit_nil_node(node) 1116 | builder.nil(token(node.location)) 1117 | end 1118 | 1119 | # def foo(**nil); end 1120 | # ^^^^^ 1121 | def visit_no_keywords_parameter_node(node) 1122 | if in_pattern 1123 | builder.match_nil_pattern(token(node.operator_loc), token(node.keyword_loc)) 1124 | else 1125 | builder.kwnilarg(token(node.operator_loc), token(node.keyword_loc)) 1126 | end 1127 | end 1128 | 1129 | # -> { _1 + _2 } 1130 | # ^^^^^^^^^^^^^^ 1131 | def visit_numbered_parameters_node(node) 1132 | builder.numargs(node.maximum) 1133 | end 1134 | 1135 | # $1 1136 | # ^^ 1137 | def visit_numbered_reference_read_node(node) 1138 | builder.nth_ref([node.number, srange(node.location)]) 1139 | end 1140 | 1141 | # def foo(bar: baz); end 1142 | # ^^^^^^^^ 1143 | def visit_optional_keyword_parameter_node(node) 1144 | builder.kwoptarg([node.name, srange(node.name_loc)], visit(node.value)) 1145 | end 1146 | 1147 | # def foo(bar = 1); end 1148 | # ^^^^^^^ 1149 | def visit_optional_parameter_node(node) 1150 | builder.optarg(token(node.name_loc), token(node.operator_loc), visit(node.value)) 1151 | end 1152 | 1153 | # a or b 1154 | # ^^^^^^ 1155 | def visit_or_node(node) 1156 | builder.logical_op(:or, visit(node.left), token(node.operator_loc), visit(node.right)) 1157 | end 1158 | 1159 | # def foo(bar, *baz); end 1160 | # ^^^^^^^^^ 1161 | def visit_parameters_node(node) 1162 | params = [] 1163 | 1164 | if node.requireds.any? 1165 | node.requireds.each do |required| 1166 | if required.is_a?(RequiredParameterNode) 1167 | params << visit(required) 1168 | else 1169 | compiler = copy_compiler(in_destructure: true) 1170 | params << required.accept(compiler) 1171 | end 1172 | end 1173 | end 1174 | 1175 | params.concat(visit_all(node.optionals)) if node.optionals.any? 1176 | params << visit(node.rest) if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode) 1177 | 1178 | if node.posts.any? 1179 | node.posts.each do |post| 1180 | if post.is_a?(RequiredParameterNode) 1181 | params << visit(post) 1182 | else 1183 | compiler = copy_compiler(in_destructure: true) 1184 | params << post.accept(compiler) 1185 | end 1186 | end 1187 | end 1188 | 1189 | params.concat(visit_all(node.keywords)) if node.keywords.any? 1190 | params << visit(node.keyword_rest) if !node.keyword_rest.nil? 1191 | params << visit(node.block) if !node.block.nil? 1192 | params 1193 | end 1194 | 1195 | # () 1196 | # ^^ 1197 | # 1198 | # (1) 1199 | # ^^^ 1200 | def visit_parentheses_node(node) 1201 | builder.begin( 1202 | token(node.opening_loc), 1203 | visit(node.body), 1204 | token(node.closing_loc) 1205 | ) 1206 | end 1207 | 1208 | # foo => ^(bar) 1209 | # ^^^^^^ 1210 | def visit_pinned_expression_node(node) 1211 | builder.pin(token(node.operator_loc), visit(node.expression)) 1212 | end 1213 | 1214 | # foo = 1 and bar => ^foo 1215 | # ^^^^ 1216 | def visit_pinned_variable_node(node) 1217 | builder.pin(token(node.operator_loc), visit(node.variable)) 1218 | end 1219 | 1220 | # END {} 1221 | def visit_post_execution_node(node) 1222 | builder.postexe( 1223 | token(node.keyword_loc), 1224 | token(node.opening_loc), 1225 | visit(node.statements), 1226 | token(node.closing_loc) 1227 | ) 1228 | end 1229 | 1230 | # BEGIN {} 1231 | def visit_pre_execution_node(node) 1232 | builder.preexe( 1233 | token(node.keyword_loc), 1234 | token(node.opening_loc), 1235 | visit(node.statements), 1236 | token(node.closing_loc) 1237 | ) 1238 | end 1239 | 1240 | # The top-level program node. 1241 | def visit_program_node(node) 1242 | node.statements.accept(copy_compiler(locals: node.locals)) 1243 | end 1244 | 1245 | # 0..5 1246 | # ^^^^ 1247 | def visit_range_node(node) 1248 | if node.exclude_end? 1249 | builder.range_exclusive( 1250 | visit(node.left), 1251 | token(node.operator_loc), 1252 | visit(node.right) 1253 | ) 1254 | else 1255 | builder.range_inclusive( 1256 | visit(node.left), 1257 | token(node.operator_loc), 1258 | visit(node.right) 1259 | ) 1260 | end 1261 | end 1262 | 1263 | # if foo .. bar; end 1264 | # ^^^^^^^^^^ 1265 | alias visit_flip_flop_node visit_range_node 1266 | 1267 | # 1r 1268 | # ^^ 1269 | def visit_rational_node(node) 1270 | visit_numeric(node, builder.rational([node.value, srange(node.location)])) 1271 | end 1272 | 1273 | # redo 1274 | # ^^^^ 1275 | def visit_redo_node(node) 1276 | builder.keyword_cmd(:redo, token(node.location)) 1277 | end 1278 | 1279 | # /foo/ 1280 | # ^^^^^ 1281 | def visit_regular_expression_node(node) 1282 | builder.regexp_compose( 1283 | token(node.opening_loc), 1284 | [builder.string_internal(token(node.content_loc))], 1285 | [node.closing[0], srange_offsets(node.closing_loc.start_offset, node.closing_loc.start_offset + 1)], 1286 | builder.regexp_options([node.closing[1..], srange_offsets(node.closing_loc.start_offset + 1, node.closing_loc.end_offset)]) 1287 | ) 1288 | end 1289 | 1290 | # if /foo/ then end 1291 | # ^^^^^ 1292 | alias visit_match_last_line_node visit_regular_expression_node 1293 | 1294 | # def foo(bar:); end 1295 | # ^^^^ 1296 | def visit_required_keyword_parameter_node(node) 1297 | builder.kwarg([node.name, srange(node.name_loc)]) 1298 | end 1299 | 1300 | # def foo(bar); end 1301 | # ^^^ 1302 | def visit_required_parameter_node(node) 1303 | builder.arg(token(node.location)) 1304 | end 1305 | 1306 | # foo rescue bar 1307 | # ^^^^^^^^^^^^^^ 1308 | def visit_rescue_modifier_node(node) 1309 | builder.begin_body( 1310 | visit(node.expression), 1311 | [ 1312 | builder.rescue_body( 1313 | token(node.keyword_loc), 1314 | nil, 1315 | nil, 1316 | nil, 1317 | nil, 1318 | visit(node.rescue_expression) 1319 | ) 1320 | ] 1321 | ) 1322 | end 1323 | 1324 | # begin; rescue; end 1325 | # ^^^^^^^ 1326 | def visit_rescue_node(node) 1327 | raise CompilationError, "Cannot directly compile rescue nodes" 1328 | end 1329 | 1330 | # def foo(*bar); end 1331 | # ^^^^ 1332 | # 1333 | # def foo(*); end 1334 | # ^ 1335 | def visit_rest_parameter_node(node) 1336 | builder.restarg(token(node.operator_loc), token(node.name_loc)) 1337 | end 1338 | 1339 | # retry 1340 | # ^^^^^ 1341 | def visit_retry_node(node) 1342 | builder.keyword_cmd(:retry, token(node.location)) 1343 | end 1344 | 1345 | # return 1346 | # ^^^^^^ 1347 | # 1348 | # return 1 1349 | # ^^^^^^^^ 1350 | def visit_return_node(node) 1351 | builder.keyword_cmd( 1352 | :return, 1353 | token(node.keyword_loc), 1354 | nil, 1355 | visit(node.arguments) || [], 1356 | nil 1357 | ) 1358 | end 1359 | 1360 | # self 1361 | # ^^^^ 1362 | def visit_self_node(node) 1363 | builder.self(token(node.location)) 1364 | end 1365 | 1366 | # class << self; end 1367 | # ^^^^^^^^^^^^^^^^^^ 1368 | def visit_singleton_class_node(node) 1369 | builder.def_sclass( 1370 | token(node.class_keyword_loc), 1371 | token(node.operator_loc), 1372 | visit(node.expression), 1373 | node.body&.accept(copy_compiler(locals: node.locals)), 1374 | token(node.end_keyword_loc) 1375 | ) 1376 | end 1377 | 1378 | # __ENCODING__ 1379 | # ^^^^^^^^^^^^ 1380 | def visit_source_encoding_node(node) 1381 | builder.accessible(builder.__ENCODING__(token(node.location))) 1382 | end 1383 | 1384 | # __FILE__ 1385 | # ^^^^^^^^ 1386 | def visit_source_file_node(node) 1387 | builder.accessible(builder.__FILE__(token(node.location))) 1388 | end 1389 | 1390 | # __LINE__ 1391 | # ^^^^^^^^ 1392 | def visit_source_line_node(node) 1393 | builder.accessible(builder.__LINE__(token(node.location))) 1394 | end 1395 | 1396 | # foo(*bar) 1397 | # ^^^^ 1398 | # 1399 | # def foo((bar, *baz)); end 1400 | # ^^^^ 1401 | # 1402 | # def foo(*); bar(*); end 1403 | # ^ 1404 | def visit_splat_node(node) 1405 | if node.expression.nil? && locals.include?(:*) 1406 | builder.forwarded_restarg(token(node.operator_loc)) 1407 | elsif in_destructure 1408 | builder.restarg(token(node.operator_loc), token(node.expression&.location)) 1409 | elsif in_pattern 1410 | builder.match_rest(token(node.operator_loc), token(node.expression&.location)) 1411 | else 1412 | builder.splat(token(node.operator_loc), visit(node.expression)) 1413 | end 1414 | end 1415 | 1416 | # A list of statements. 1417 | def visit_statements_node(node) 1418 | builder.compstmt(visit_all(node.body)) 1419 | end 1420 | 1421 | # "foo" 1422 | # ^^^^^ 1423 | def visit_string_node(node) 1424 | if node.opening&.start_with?("<<") 1425 | children, closing = visit_heredoc(InterpolatedStringNode.new(node.opening_loc, [node.copy(opening_loc: nil, closing_loc: nil, location: node.content_loc)], node.closing_loc, node.location)) 1426 | builder.string_compose(token(node.opening_loc), children, closing) 1427 | elsif node.opening == "?" 1428 | builder.character([node.unescaped, srange(node.location)]) 1429 | else 1430 | builder.string_compose( 1431 | token(node.opening_loc), 1432 | [builder.string_internal([node.unescaped, srange(node.content_loc)])], 1433 | token(node.closing_loc) 1434 | ) 1435 | end 1436 | end 1437 | 1438 | # super(foo) 1439 | # ^^^^^^^^^^ 1440 | def visit_super_node(node) 1441 | arguments = node.arguments&.arguments || [] 1442 | block = node.block 1443 | 1444 | if block.is_a?(BlockArgumentNode) 1445 | arguments = [*arguments, block] 1446 | block = nil 1447 | end 1448 | 1449 | visit_block( 1450 | builder.keyword_cmd( 1451 | :super, 1452 | token(node.keyword_loc), 1453 | token(node.lparen_loc), 1454 | visit_all(arguments), 1455 | token(node.rparen_loc) 1456 | ), 1457 | block 1458 | ) 1459 | end 1460 | 1461 | # :foo 1462 | # ^^^^ 1463 | def visit_symbol_node(node) 1464 | if node.closing_loc.nil? 1465 | if node.opening_loc.nil? 1466 | builder.symbol_internal([node.unescaped, srange(node.location)]) 1467 | else 1468 | builder.symbol([node.unescaped, srange(node.location)]) 1469 | end 1470 | else 1471 | builder.symbol_compose( 1472 | token(node.opening_loc), 1473 | [builder.string_internal([node.unescaped, srange(node.value_loc)])], 1474 | token(node.closing_loc) 1475 | ) 1476 | end 1477 | end 1478 | 1479 | # true 1480 | # ^^^^ 1481 | def visit_true_node(node) 1482 | builder.true(token(node.location)) 1483 | end 1484 | 1485 | # undef foo 1486 | # ^^^^^^^^^ 1487 | def visit_undef_node(node) 1488 | builder.undef_method(token(node.keyword_loc), visit_all(node.names)) 1489 | end 1490 | 1491 | # unless foo; bar end 1492 | # ^^^^^^^^^^^^^^^^^^^ 1493 | # 1494 | # bar unless foo 1495 | # ^^^^^^^^^^^^^^ 1496 | def visit_unless_node(node) 1497 | if node.keyword_loc.start_offset == node.location.start_offset 1498 | builder.condition( 1499 | token(node.keyword_loc), 1500 | visit(node.predicate), 1501 | if node.then_keyword_loc 1502 | token(node.then_keyword_loc) 1503 | else 1504 | srange_find(node.predicate.location.end_offset, (node.statements&.location || node.consequent&.location || node.end_keyword_loc).start_offset, [";"]) 1505 | end, 1506 | visit(node.consequent), 1507 | token(node.consequent&.else_keyword_loc), 1508 | visit(node.statements), 1509 | token(node.end_keyword_loc) 1510 | ) 1511 | else 1512 | builder.condition_mod( 1513 | visit(node.consequent), 1514 | visit(node.statements), 1515 | token(node.keyword_loc), 1516 | visit(node.predicate) 1517 | ) 1518 | end 1519 | end 1520 | 1521 | # until foo; bar end 1522 | # ^^^^^^^^^^^^^^^^^ 1523 | # 1524 | # bar until foo 1525 | # ^^^^^^^^^^^^^ 1526 | def visit_until_node(node) 1527 | if node.location.start_offset == node.keyword_loc.start_offset 1528 | builder.loop( 1529 | :until, 1530 | token(node.keyword_loc), 1531 | visit(node.predicate), 1532 | srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, [";", "do"]), 1533 | visit(node.statements), 1534 | token(node.closing_loc) 1535 | ) 1536 | else 1537 | builder.loop_mod( 1538 | :until, 1539 | visit(node.statements), 1540 | token(node.keyword_loc), 1541 | visit(node.predicate) 1542 | ) 1543 | end 1544 | end 1545 | 1546 | # case foo; when bar; end 1547 | # ^^^^^^^^^^^^^ 1548 | def visit_when_node(node) 1549 | builder.when( 1550 | token(node.keyword_loc), 1551 | visit_all(node.conditions), 1552 | srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset || (node.conditions.last.location.end_offset + 1), [";", "then"]), 1553 | visit(node.statements) 1554 | ) 1555 | end 1556 | 1557 | # while foo; bar end 1558 | # ^^^^^^^^^^^^^^^^^^ 1559 | # 1560 | # bar while foo 1561 | # ^^^^^^^^^^^^^ 1562 | def visit_while_node(node) 1563 | if node.location.start_offset == node.keyword_loc.start_offset 1564 | builder.loop( 1565 | :while, 1566 | token(node.keyword_loc), 1567 | visit(node.predicate), 1568 | srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, [";", "do"]), 1569 | visit(node.statements), 1570 | token(node.closing_loc) 1571 | ) 1572 | else 1573 | builder.loop_mod( 1574 | :while, 1575 | visit(node.statements), 1576 | token(node.keyword_loc), 1577 | visit(node.predicate) 1578 | ) 1579 | end 1580 | end 1581 | 1582 | # `foo` 1583 | # ^^^^^ 1584 | def visit_x_string_node(node) 1585 | if node.opening&.start_with?("<<") 1586 | children, closing = visit_heredoc(InterpolatedXStringNode.new(node.opening_loc, [StringNode.new(0, nil, node.content_loc, nil, node.unescaped, node.content_loc)], node.closing_loc, node.location)) 1587 | builder.xstring_compose(token(node.opening_loc), children, closing) 1588 | else 1589 | builder.xstring_compose( 1590 | token(node.opening_loc), 1591 | [builder.string_internal([node.unescaped, srange(node.content_loc)])], 1592 | token(node.closing_loc) 1593 | ) 1594 | end 1595 | end 1596 | 1597 | # yield 1598 | # ^^^^^ 1599 | # 1600 | # yield 1 1601 | # ^^^^^^^ 1602 | def visit_yield_node(node) 1603 | builder.keyword_cmd( 1604 | :yield, 1605 | token(node.keyword_loc), 1606 | token(node.lparen_loc), 1607 | visit(node.arguments) || [], 1608 | token(node.rparen_loc) 1609 | ) 1610 | end 1611 | 1612 | private 1613 | 1614 | def copy_compiler(locals: self.locals, in_destructure: self.in_destructure, in_pattern: self.in_pattern) 1615 | ParserCompiler.new(parser, offset_cache, locals: locals, in_destructure: in_destructure, in_pattern: in_pattern) 1616 | end 1617 | 1618 | # Blocks can have a special set of parameters that automatically expand 1619 | # when given arrays if they have a single required parameter and no other 1620 | # parameters. 1621 | def procarg0?(parameters) 1622 | parameters && 1623 | parameters.requireds.length == 1 && 1624 | parameters.optionals.empty? && 1625 | parameters.rest.nil? && 1626 | parameters.posts.empty? && 1627 | parameters.keywords.empty? && 1628 | parameters.keyword_rest.nil? && 1629 | parameters.block.nil? 1630 | end 1631 | 1632 | # Locations in the parser gem AST are generated using this class. We store a 1633 | # reference to its constant to make it slightly faster to look up. 1634 | Range = ::Parser::Source::Range 1635 | 1636 | # Constructs a new source range from the given start and end offsets. 1637 | def srange(location) 1638 | Range.new(source_buffer, offset_cache[location.start_offset], offset_cache[location.end_offset]) if location 1639 | end 1640 | 1641 | # Constructs a new source range from the given start and end offsets. 1642 | def srange_offsets(start_offset, end_offset) 1643 | Range.new(source_buffer, offset_cache[start_offset], offset_cache[end_offset]) 1644 | end 1645 | 1646 | # Constructs a new source range by finding the given tokens between the 1647 | # given start offset and end offset. If the needle is not found, it 1648 | # returns nil. 1649 | def srange_find(start_offset, end_offset, tokens) 1650 | tokens.find do |token| 1651 | next unless (index = source_buffer.source.byteslice(start_offset...end_offset).index(token)) 1652 | offset = start_offset + index 1653 | return [token, Range.new(source_buffer, offset_cache[offset], offset_cache[offset + token.length])] 1654 | end 1655 | end 1656 | 1657 | # Transform a location into a token that the parser gem expects. 1658 | def token(location) 1659 | [location.slice, Range.new(source_buffer, offset_cache[location.start_offset], offset_cache[location.end_offset])] if location 1660 | end 1661 | 1662 | # Visit a block node on a call. 1663 | def visit_block(call, block) 1664 | if block 1665 | builder.block( 1666 | call, 1667 | token(block.opening_loc), 1668 | if (parameters = block.parameters) 1669 | if parameters.is_a?(NumberedParametersNode) 1670 | visit(parameters) 1671 | else 1672 | builder.args( 1673 | token(parameters.opening_loc), 1674 | if procarg0?(parameters.parameters) 1675 | parameter = parameters.parameters.requireds.first 1676 | [builder.procarg0(visit(parameter))].concat(visit_all(parameters.locals)) 1677 | else 1678 | visit(parameters) 1679 | end, 1680 | token(parameters.closing_loc), 1681 | false 1682 | ) 1683 | end 1684 | else 1685 | builder.args(nil, [], nil, false) 1686 | end, 1687 | visit(block.body), 1688 | token(block.closing_loc) 1689 | ) 1690 | else 1691 | call 1692 | end 1693 | end 1694 | 1695 | # Visit a heredoc that can be either a string or an xstring. 1696 | def visit_heredoc(node) 1697 | children = [] 1698 | node.parts.each do |part| 1699 | pushing = 1700 | if part.is_a?(StringNode) && part.unescaped.count("\n") > 1 1701 | unescaped = part.unescaped.split("\n") 1702 | escaped = part.content.split("\n") 1703 | 1704 | escaped_lengths = 1705 | if node.opening.end_with?("'") 1706 | escaped.map { |line| line.bytesize + 1 } 1707 | else 1708 | escaped.chunk_while { |before, after| before.match?(/(? "~", "!@" => "!" }.fetch(value, value) 273 | location = Source::Range.new(buffer, offset_cache[next_location.start_offset], offset_cache[next_location.end_offset]) 274 | index += 1 275 | end 276 | when :tFID 277 | if tokens[-1][0] == :kDEF 278 | type = :tIDENTIFIER 279 | end 280 | end 281 | 282 | tokens << [type, [value, location]] 283 | 284 | if token.type == :REGEXP_END 285 | tokens << [:tREGEXP_OPT, [token.value[1..], Source::Range.new(buffer, offset_cache[token.location.start_offset + 1], offset_cache[token.location.end_offset])]] 286 | end 287 | end 288 | 289 | tokens 290 | end 291 | 292 | private 293 | 294 | def parse_complex(value) 295 | value.chomp!("i") 296 | 297 | if value.end_with?("r") 298 | Complex(0, parse_rational(value)) 299 | elsif value.start_with?(/0[BbOoDdXx]/) 300 | Complex(0, Integer(value)) 301 | else 302 | Complex(0, value) 303 | end 304 | end 305 | 306 | def parse_rational(value) 307 | value.chomp!("r") 308 | 309 | if value.start_with?(/0[BbOoDdXx]/) 310 | Rational(Integer(value)) 311 | else 312 | Rational(value) 313 | end 314 | end 315 | end 316 | end 317 | end 318 | -------------------------------------------------------------------------------- /lib/parser/prism/rubocop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "parser" 4 | require "rubocop" 5 | 6 | module Parser 7 | class Prism < Base 8 | VERSION_3_3 = 80_82_73_83_77.33 9 | end 10 | end 11 | 12 | RuboCop::AST::ProcessedSource.prepend( 13 | Module.new do 14 | def parser_class(ruby_version) 15 | if ruby_version == Parser::Prism::VERSION_3_3 16 | require "parser/prism" 17 | Parser::Prism 18 | else 19 | super 20 | end 21 | end 22 | end 23 | ) 24 | 25 | known_rubies = RuboCop::TargetRuby.const_get(:KNOWN_RUBIES) 26 | RuboCop::TargetRuby.send(:remove_const, :KNOWN_RUBIES) 27 | RuboCop::TargetRuby::KNOWN_RUBIES = [*known_rubies, Parser::Prism::VERSION_3_3].freeze 28 | -------------------------------------------------------------------------------- /parser-prism.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = "parser-prism" 5 | spec.version = "0.1.0" 6 | spec.authors = ["Kevin Newton"] 7 | spec.email = ["kddnewton@gmail.com"] 8 | 9 | spec.summary = "A prism parser backend" 10 | spec.homepage = "https://github.com/kddnewton/parser-prism" 11 | spec.license = "MIT" 12 | 13 | spec.files = 14 | Dir.chdir(__dir__) do 15 | `git ls-files -z`.split("\x0") 16 | .reject { |f| f.match(%r{^(test|spec|features)/}) } 17 | end 18 | 19 | spec.bindir = "exe" 20 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 21 | spec.require_paths = ["lib"] 22 | 23 | spec.add_dependency "parser" 24 | spec.add_dependency "prism" 25 | end 26 | -------------------------------------------------------------------------------- /test/fixtures/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2016 whitequark 2 | 3 | Parts of the source are derived from ruby_parser: 4 | Copyright (c) Ryan Davis, seattle.rb 5 | 6 | MIT License 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /test/fixtures/__ENCODING__.rb: -------------------------------------------------------------------------------- 1 | __ENCODING__ 2 | -------------------------------------------------------------------------------- /test/fixtures/__ENCODING___legacy_.rb: -------------------------------------------------------------------------------- 1 | __ENCODING__ 2 | -------------------------------------------------------------------------------- /test/fixtures/alias.rb: -------------------------------------------------------------------------------- 1 | alias :foo bar 2 | -------------------------------------------------------------------------------- /test/fixtures/alias_gvar.rb: -------------------------------------------------------------------------------- 1 | alias $a $+ 2 | 3 | alias $a $b 4 | -------------------------------------------------------------------------------- /test/fixtures/ambiuous_quoted_label_in_ternary_operator.rb: -------------------------------------------------------------------------------- 1 | a ? b & '': nil 2 | -------------------------------------------------------------------------------- /test/fixtures/and.rb: -------------------------------------------------------------------------------- 1 | foo && bar 2 | 3 | foo and bar 4 | -------------------------------------------------------------------------------- /test/fixtures/and_asgn.rb: -------------------------------------------------------------------------------- 1 | foo.a &&= 1 2 | 3 | foo[0, 1] &&= 2 4 | 5 | $foo &&= 1 6 | -------------------------------------------------------------------------------- /test/fixtures/and_or_masgn.rb: -------------------------------------------------------------------------------- 1 | foo && (a, b = bar) 2 | 3 | foo || (a, b = bar) 4 | -------------------------------------------------------------------------------- /test/fixtures/anonymous_blockarg.rb: -------------------------------------------------------------------------------- 1 | def foo(&); bar(&); end 2 | -------------------------------------------------------------------------------- /test/fixtures/arg.rb: -------------------------------------------------------------------------------- 1 | def f(foo); end 2 | 3 | def f(foo, bar); end 4 | -------------------------------------------------------------------------------- /test/fixtures/arg_duplicate_ignored.rb: -------------------------------------------------------------------------------- 1 | def foo(_, _); end 2 | 3 | def foo(_a, _a); end 4 | -------------------------------------------------------------------------------- /test/fixtures/arg_label.rb: -------------------------------------------------------------------------------- 1 | def foo 2 | a:b end 3 | 4 | def foo() a:b end 5 | 6 | f { || a:b } 7 | -------------------------------------------------------------------------------- /test/fixtures/arg_scope.rb: -------------------------------------------------------------------------------- 1 | lambda{|;a|a} 2 | -------------------------------------------------------------------------------- /test/fixtures/args.rb: -------------------------------------------------------------------------------- 1 | def f &b; end 2 | 3 | def f (((a))); end 4 | 5 | def f ((*)); end 6 | 7 | def f ((*, p)); end 8 | 9 | def f ((*r)); end 10 | 11 | def f ((*r, p)); end 12 | 13 | def f ((a, *)); end 14 | 15 | def f ((a, *, p)); end 16 | 17 | def f ((a, *r)); end 18 | 19 | def f ((a, *r, p)); end 20 | 21 | def f ((a, a1)); end 22 | 23 | def f (foo: 1, &b); end 24 | 25 | def f (foo: 1, bar: 2, **baz, &b); end 26 | 27 | def f **baz, &b; end 28 | 29 | def f *, **; end 30 | 31 | def f *r, &b; end 32 | 33 | def f *r, p, &b; end 34 | 35 | def f ; end 36 | 37 | def f a, &b; end 38 | 39 | def f a, *r, &b; end 40 | 41 | def f a, *r, p, &b; end 42 | 43 | def f a, o=1, &b; end 44 | 45 | def f a, o=1, *r, &b; end 46 | 47 | def f a, o=1, *r, p, &b; end 48 | 49 | def f a, o=1, p, &b; end 50 | 51 | def f foo: 52 | ; end 53 | 54 | def f foo: -1 55 | ; end 56 | 57 | def f o=1, &b; end 58 | 59 | def f o=1, *r, &b; end 60 | 61 | def f o=1, *r, p, &b; end 62 | 63 | def f o=1, p, &b; end 64 | -------------------------------------------------------------------------------- /test/fixtures/args_args_assocs.rb: -------------------------------------------------------------------------------- 1 | fun(foo, :foo => 1) 2 | 3 | fun(foo, :foo => 1, &baz) 4 | -------------------------------------------------------------------------------- /test/fixtures/args_args_assocs_comma.rb: -------------------------------------------------------------------------------- 1 | foo[bar, :baz => 1,] 2 | -------------------------------------------------------------------------------- /test/fixtures/args_args_comma.rb: -------------------------------------------------------------------------------- 1 | foo[bar,] 2 | -------------------------------------------------------------------------------- /test/fixtures/args_args_star.rb: -------------------------------------------------------------------------------- 1 | fun(foo, *bar) 2 | 3 | fun(foo, *bar, &baz) 4 | -------------------------------------------------------------------------------- /test/fixtures/args_assocs.rb: -------------------------------------------------------------------------------- 1 | fun(:foo => 1) 2 | 3 | fun(:foo => 1, &baz) 4 | 5 | self.[]= foo, :a => 1 6 | 7 | self[:bar => 1] 8 | 9 | super(:foo => 42) 10 | 11 | yield(:foo => 42) 12 | -------------------------------------------------------------------------------- /test/fixtures/args_assocs_comma.rb: -------------------------------------------------------------------------------- 1 | foo[:baz => 1,] 2 | -------------------------------------------------------------------------------- /test/fixtures/args_assocs_legacy.rb: -------------------------------------------------------------------------------- 1 | fun(:foo => 1) 2 | 3 | fun(:foo => 1, &baz) 4 | 5 | self.[]= foo, :a => 1 6 | 7 | self[:bar => 1] 8 | 9 | super(:foo => 42) 10 | 11 | yield(:foo => 42) 12 | -------------------------------------------------------------------------------- /test/fixtures/args_block_pass.rb: -------------------------------------------------------------------------------- 1 | fun(&bar) 2 | -------------------------------------------------------------------------------- /test/fixtures/args_cmd.rb: -------------------------------------------------------------------------------- 1 | fun(f bar) 2 | -------------------------------------------------------------------------------- /test/fixtures/args_star.rb: -------------------------------------------------------------------------------- 1 | fun(*bar) 2 | 3 | fun(*bar, &baz) 4 | -------------------------------------------------------------------------------- /test/fixtures/array_assocs.rb: -------------------------------------------------------------------------------- 1 | [ 1 => 2 ] 2 | 3 | [ 1, 2 => 3 ] 4 | -------------------------------------------------------------------------------- /test/fixtures/array_plain.rb: -------------------------------------------------------------------------------- 1 | [1, 2] 2 | -------------------------------------------------------------------------------- /test/fixtures/array_splat.rb: -------------------------------------------------------------------------------- 1 | [*foo] 2 | 3 | [1, *foo, 2] 4 | 5 | [1, *foo] 6 | -------------------------------------------------------------------------------- /test/fixtures/array_symbols.rb: -------------------------------------------------------------------------------- 1 | %i[foo bar] 2 | -------------------------------------------------------------------------------- /test/fixtures/array_symbols_empty.rb: -------------------------------------------------------------------------------- 1 | %I() 2 | 3 | %i[] 4 | -------------------------------------------------------------------------------- /test/fixtures/array_symbols_interp.rb: -------------------------------------------------------------------------------- 1 | %I[foo #{bar}] 2 | 3 | %I[foo#{bar}] 4 | -------------------------------------------------------------------------------- /test/fixtures/array_words.rb: -------------------------------------------------------------------------------- 1 | %w[foo bar] 2 | -------------------------------------------------------------------------------- /test/fixtures/array_words_empty.rb: -------------------------------------------------------------------------------- 1 | %W() 2 | 3 | %w[] 4 | -------------------------------------------------------------------------------- /test/fixtures/array_words_interp.rb: -------------------------------------------------------------------------------- 1 | %W[foo #{bar}] 2 | 3 | %W[foo #{bar}foo#@baz] 4 | -------------------------------------------------------------------------------- /test/fixtures/asgn_cmd.rb: -------------------------------------------------------------------------------- 1 | foo = bar = m foo 2 | 3 | foo = m foo 4 | -------------------------------------------------------------------------------- /test/fixtures/asgn_mrhs.rb: -------------------------------------------------------------------------------- 1 | foo = *bar 2 | 3 | foo = bar, 1 4 | 5 | foo = baz, *bar 6 | -------------------------------------------------------------------------------- /test/fixtures/back_ref.rb: -------------------------------------------------------------------------------- 1 | $+ 2 | -------------------------------------------------------------------------------- /test/fixtures/bang.rb: -------------------------------------------------------------------------------- 1 | !foo 2 | -------------------------------------------------------------------------------- /test/fixtures/bang_cmd.rb: -------------------------------------------------------------------------------- 1 | !m foo 2 | -------------------------------------------------------------------------------- /test/fixtures/begin_cmdarg.rb: -------------------------------------------------------------------------------- 1 | p begin 1.times do 1 end end 2 | -------------------------------------------------------------------------------- /test/fixtures/beginless_erange_after_newline.rb: -------------------------------------------------------------------------------- 1 | foo 2 | ...100 3 | -------------------------------------------------------------------------------- /test/fixtures/beginless_irange_after_newline.rb: -------------------------------------------------------------------------------- 1 | foo 2 | ..100 3 | -------------------------------------------------------------------------------- /test/fixtures/beginless_range.rb: -------------------------------------------------------------------------------- 1 | ...100 2 | 3 | ..100 4 | -------------------------------------------------------------------------------- /test/fixtures/blockarg.rb: -------------------------------------------------------------------------------- 1 | def f(&block); end 2 | -------------------------------------------------------------------------------- /test/fixtures/blockargs.rb: -------------------------------------------------------------------------------- 1 | f{ } 2 | 3 | f{ | | } 4 | 5 | f{ |&b| } 6 | 7 | f{ |**baz, &b| } 8 | 9 | f{ |*, &b| } 10 | 11 | f{ |*r, p, &b| } 12 | 13 | f{ |*s, &b| } 14 | 15 | f{ |*s| } 16 | 17 | f{ |*| } 18 | 19 | f{ |; 20 | a 21 | | } 22 | 23 | f{ |;a| } 24 | 25 | f{ |a, &b| } 26 | 27 | f{ |a, *, &b| } 28 | 29 | f{ |a, *r, p, &b| } 30 | 31 | f{ |a, *s, &b| } 32 | 33 | f{ |a, *s| } 34 | 35 | f{ |a, *| } 36 | 37 | f{ |a, b,| } 38 | 39 | f{ |a, c| } 40 | 41 | f{ |a, o=1, &b| } 42 | 43 | f{ |a, o=1, *r, p, &b| } 44 | 45 | f{ |a, o=1, o1=2, *r, &b| } 46 | 47 | f{ |a, o=1, p, &b| } 48 | 49 | f{ |a,| } 50 | 51 | f{ |a| } 52 | 53 | f{ |a| } 54 | 55 | f{ |a| } 56 | 57 | f{ |foo: 1, &b| } 58 | 59 | f{ |foo: 1, bar: 2, **baz, &b| } 60 | 61 | f{ |foo:| } 62 | 63 | f{ |o=1, &b| } 64 | 65 | f{ |o=1, *r, &b| } 66 | 67 | f{ |o=1, *r, p, &b| } 68 | 69 | f{ |o=1, p, &b| } 70 | 71 | f{ || } 72 | -------------------------------------------------------------------------------- /test/fixtures/break.rb: -------------------------------------------------------------------------------- 1 | break 2 | 3 | break foo 4 | 5 | break() 6 | 7 | break(foo) 8 | -------------------------------------------------------------------------------- /test/fixtures/break_block.rb: -------------------------------------------------------------------------------- 1 | break fun foo do end 2 | -------------------------------------------------------------------------------- /test/fixtures/bug_435.rb: -------------------------------------------------------------------------------- 1 | "#{-> foo {}}" 2 | -------------------------------------------------------------------------------- /test/fixtures/bug_447.rb: -------------------------------------------------------------------------------- 1 | m [] do end 2 | 3 | m [], 1 do end 4 | -------------------------------------------------------------------------------- /test/fixtures/bug_452.rb: -------------------------------------------------------------------------------- 1 | td (1_500).toString(); td.num do; end 2 | -------------------------------------------------------------------------------- /test/fixtures/bug_466.rb: -------------------------------------------------------------------------------- 1 | foo "#{(1+1).to_i}" do; end 2 | -------------------------------------------------------------------------------- /test/fixtures/bug_473.rb: -------------------------------------------------------------------------------- 1 | m "#{[]}" 2 | -------------------------------------------------------------------------------- /test/fixtures/bug_480.rb: -------------------------------------------------------------------------------- 1 | m "#{}#{()}" 2 | -------------------------------------------------------------------------------- /test/fixtures/bug_481.rb: -------------------------------------------------------------------------------- 1 | m def x(); end; 1.tap do end 2 | -------------------------------------------------------------------------------- /test/fixtures/bug_ascii_8bit_in_literal.rb: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | "\xD0\xBF\xD1\x80\xD0\xBE\xD0\xB2\xD0\xB5\xD1\x80\xD0\xBA\xD0\xB0" 3 | -------------------------------------------------------------------------------- /test/fixtures/bug_cmd_string_lookahead.rb: -------------------------------------------------------------------------------- 1 | desc "foo" do end 2 | -------------------------------------------------------------------------------- /test/fixtures/bug_cmdarg.rb: -------------------------------------------------------------------------------- 1 | assert do: true 2 | 3 | assert dogs 4 | 5 | f x: -> do meth do end end 6 | -------------------------------------------------------------------------------- /test/fixtures/bug_def_no_paren_eql_begin.rb: -------------------------------------------------------------------------------- 1 | def foo 2 | =begin 3 | =end 4 | end 5 | -------------------------------------------------------------------------------- /test/fixtures/bug_do_block_in_call_args.rb: -------------------------------------------------------------------------------- 1 | bar def foo; self.each do end end 2 | -------------------------------------------------------------------------------- /test/fixtures/bug_do_block_in_cmdarg.rb: -------------------------------------------------------------------------------- 1 | tap (proc do end) 2 | -------------------------------------------------------------------------------- /test/fixtures/bug_do_block_in_hash_brace.rb: -------------------------------------------------------------------------------- 1 | p :foo, {"a": proc do end, b: proc do end} 2 | 3 | p :foo, {** proc do end, b: proc do end} 4 | 5 | p :foo, {:a => proc do end, b: proc do end} 6 | 7 | p :foo, {a: proc do end, b: proc do end} 8 | 9 | p :foo, {proc do end => proc do end, b: proc do end} 10 | -------------------------------------------------------------------------------- /test/fixtures/bug_heredoc_do.rb: -------------------------------------------------------------------------------- 1 | f <<-TABLE do 2 | TABLE 3 | end 4 | -------------------------------------------------------------------------------- /test/fixtures/bug_interp_single.rb: -------------------------------------------------------------------------------- 1 | "#{1}" 2 | 3 | %W"#{1}" 4 | -------------------------------------------------------------------------------- /test/fixtures/bug_lambda_leakage.rb: -------------------------------------------------------------------------------- 1 | ->(scope) {}; scope 2 | -------------------------------------------------------------------------------- /test/fixtures/bug_regex_verification.rb: -------------------------------------------------------------------------------- 1 | /#)/x 2 | -------------------------------------------------------------------------------- /test/fixtures/bug_rescue_empty_else.rb: -------------------------------------------------------------------------------- 1 | begin; rescue LoadError; else; end 2 | -------------------------------------------------------------------------------- /test/fixtures/bug_while_not_parens_do.rb: -------------------------------------------------------------------------------- 1 | while not (true) do end 2 | -------------------------------------------------------------------------------- /test/fixtures/case_cond.rb: -------------------------------------------------------------------------------- 1 | case; when foo; 'foo'; end 2 | -------------------------------------------------------------------------------- /test/fixtures/case_cond_else.rb: -------------------------------------------------------------------------------- 1 | case; when foo; 'foo'; else 'bar'; end 2 | -------------------------------------------------------------------------------- /test/fixtures/case_expr.rb: -------------------------------------------------------------------------------- 1 | case foo; when 'bar'; bar; end 2 | -------------------------------------------------------------------------------- /test/fixtures/case_expr_else.rb: -------------------------------------------------------------------------------- 1 | case foo; when 'bar'; bar; else baz; end 2 | -------------------------------------------------------------------------------- /test/fixtures/casgn_scoped.rb: -------------------------------------------------------------------------------- 1 | Bar::Foo = 10 2 | -------------------------------------------------------------------------------- /test/fixtures/casgn_toplevel.rb: -------------------------------------------------------------------------------- 1 | ::Foo = 10 2 | -------------------------------------------------------------------------------- /test/fixtures/casgn_unscoped.rb: -------------------------------------------------------------------------------- 1 | Foo = 10 2 | -------------------------------------------------------------------------------- /test/fixtures/character.rb: -------------------------------------------------------------------------------- 1 | ?a 2 | -------------------------------------------------------------------------------- /test/fixtures/class.rb: -------------------------------------------------------------------------------- 1 | class Foo end 2 | 3 | class Foo; end 4 | -------------------------------------------------------------------------------- /test/fixtures/class_definition_in_while_cond.rb: -------------------------------------------------------------------------------- 1 | while class << self; a = tap do end; end; break; end 2 | 3 | while class << self; tap do end; end; break; end 4 | 5 | while class Foo a = tap do end; end; break; end 6 | 7 | while class Foo; tap do end; end; break; end 8 | -------------------------------------------------------------------------------- /test/fixtures/class_super.rb: -------------------------------------------------------------------------------- 1 | class Foo < Bar; end 2 | -------------------------------------------------------------------------------- /test/fixtures/class_super_label.rb: -------------------------------------------------------------------------------- 1 | class Foo < a:b; end 2 | -------------------------------------------------------------------------------- /test/fixtures/comments_before_leading_dot__27.rb: -------------------------------------------------------------------------------- 1 | a # 2 | # 3 | &.foo 4 | 5 | 6 | a # 7 | # 8 | .foo 9 | 10 | 11 | a # 12 | # 13 | &.foo 14 | 15 | 16 | a # 17 | # 18 | .foo 19 | 20 | -------------------------------------------------------------------------------- /test/fixtures/complex.rb: -------------------------------------------------------------------------------- 1 | 42.1i 2 | 3 | 42.1ri 4 | 5 | 42i 6 | 7 | 42ri 8 | -------------------------------------------------------------------------------- /test/fixtures/cond_begin.rb: -------------------------------------------------------------------------------- 1 | if (bar); foo; end 2 | -------------------------------------------------------------------------------- /test/fixtures/cond_begin_masgn.rb: -------------------------------------------------------------------------------- 1 | if (bar; a, b = foo); end 2 | -------------------------------------------------------------------------------- /test/fixtures/cond_eflipflop.rb: -------------------------------------------------------------------------------- 1 | !(foo...bar) 2 | 3 | if foo...bar; end 4 | -------------------------------------------------------------------------------- /test/fixtures/cond_iflipflop.rb: -------------------------------------------------------------------------------- 1 | !(foo..bar) 2 | 3 | if foo..bar; end 4 | -------------------------------------------------------------------------------- /test/fixtures/cond_match_current_line.rb: -------------------------------------------------------------------------------- 1 | !/wat/ 2 | 3 | if /wat/; end 4 | -------------------------------------------------------------------------------- /test/fixtures/const_op_asgn.rb: -------------------------------------------------------------------------------- 1 | ::A += 1 2 | 3 | A += 1 4 | 5 | B::A += 1 6 | 7 | def x; ::A ||= 1; end 8 | 9 | def x; self::A ||= 1; end 10 | -------------------------------------------------------------------------------- /test/fixtures/const_scoped.rb: -------------------------------------------------------------------------------- 1 | Bar::Foo 2 | -------------------------------------------------------------------------------- /test/fixtures/const_toplevel.rb: -------------------------------------------------------------------------------- 1 | ::Foo 2 | -------------------------------------------------------------------------------- /test/fixtures/const_unscoped.rb: -------------------------------------------------------------------------------- 1 | Foo 2 | -------------------------------------------------------------------------------- /test/fixtures/control_meta_escape_chars_in_regexp__since_31.rb: -------------------------------------------------------------------------------- 1 | /\C-\M-\xFF/ 2 | 3 | /\C-\xFF/ 4 | 5 | /\M-\C-\xFF/ 6 | 7 | /\M-\c\xFF/ 8 | 9 | /\M-\xFF/ 10 | 11 | /\c\M-\xFF/ 12 | 13 | /\c\xFF/ 14 | -------------------------------------------------------------------------------- /test/fixtures/cpath.rb: -------------------------------------------------------------------------------- 1 | module ::Foo; end 2 | 3 | module Bar::Foo; end 4 | -------------------------------------------------------------------------------- /test/fixtures/cvar.rb: -------------------------------------------------------------------------------- 1 | @@foo 2 | -------------------------------------------------------------------------------- /test/fixtures/cvasgn.rb: -------------------------------------------------------------------------------- 1 | @@var = 10 2 | -------------------------------------------------------------------------------- /test/fixtures/dedenting_heredoc.rb: -------------------------------------------------------------------------------- 1 | p <<~"E" 2 | x 3 | #{" y"} 4 | E 5 | 6 | p <<~"E" 7 | x 8 | #{foo} 9 | E 10 | 11 | p <<~E 12 | x 13 | y 14 | E 15 | 16 | p <<~E 17 | x 18 | y 19 | E 20 | 21 | p <<~E 22 | x 23 | y 24 | E 25 | 26 | p <<~E 27 | x 28 | y 29 | E 30 | 31 | p <<~E 32 | x 33 | \ y 34 | E 35 | 36 | p <<~E 37 | x 38 | \ y 39 | E 40 | 41 | p <<~E 42 | E 43 | 44 | p <<~E 45 | x 46 | 47 | y 48 | E 49 | 50 | p <<~E 51 | x 52 | 53 | y 54 | E 55 | 56 | p <<~E 57 | x 58 | y 59 | E 60 | 61 | p <<~E 62 | x 63 | E 64 | 65 | p <<~E 66 | ð 67 | E 68 | 69 | p <<~E 70 | E 71 | 72 | p <<~`E` 73 | x 74 | #{foo} 75 | E 76 | -------------------------------------------------------------------------------- /test/fixtures/dedenting_interpolating_heredoc_fake_line_continuation.rb: -------------------------------------------------------------------------------- 1 | <<~'FOO' 2 | baz\\ 3 | qux 4 | FOO 5 | -------------------------------------------------------------------------------- /test/fixtures/dedenting_non_interpolating_heredoc_line_continuation.rb: -------------------------------------------------------------------------------- 1 | <<~'FOO' 2 | baz\ 3 | qux 4 | FOO 5 | -------------------------------------------------------------------------------- /test/fixtures/def.rb: -------------------------------------------------------------------------------- 1 | def BEGIN; end 2 | 3 | def END; end 4 | 5 | def String; end 6 | 7 | def String=; end 8 | 9 | def foo; end 10 | 11 | def until; end 12 | 13 | def foo?; end 14 | 15 | def foo!; end 16 | -------------------------------------------------------------------------------- /test/fixtures/defined.rb: -------------------------------------------------------------------------------- 1 | defined? @foo 2 | 3 | defined? foo 4 | 5 | defined?(foo) 6 | -------------------------------------------------------------------------------- /test/fixtures/defs.rb: -------------------------------------------------------------------------------- 1 | def (foo).foo; end 2 | 3 | def String.foo; end 4 | 5 | def String::foo; end 6 | 7 | def self.foo; end 8 | 9 | def self::foo; end 10 | -------------------------------------------------------------------------------- /test/fixtures/empty_stmt.rb: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/fixtures/endless_comparison_method.rb: -------------------------------------------------------------------------------- 1 | def !=(other) = do_something 2 | 3 | def !=(other) = do_something 4 | 5 | def <=(other) = do_something 6 | 7 | def ==(other) = do_something 8 | 9 | def ===(other) = do_something 10 | 11 | def >=(other) = do_something 12 | -------------------------------------------------------------------------------- /test/fixtures/endless_method.rb: -------------------------------------------------------------------------------- 1 | def foo() = 42 2 | 3 | def inc(x) = x + 1 4 | 5 | def obj.foo() = 42 6 | 7 | def obj.inc(x) = x + 1 8 | -------------------------------------------------------------------------------- /test/fixtures/endless_method_command_syntax.rb: -------------------------------------------------------------------------------- 1 | def foo = puts "Hello" 2 | 3 | def foo() = puts "Hello" 4 | 5 | def foo(x) = puts x 6 | 7 | def obj.foo = puts "Hello" 8 | 9 | def obj.foo() = puts "Hello" 10 | 11 | def obj.foo(x) = puts x 12 | 13 | def rescued(x) = raise "to be caught" rescue "instance #{x}" 14 | 15 | def self.rescued(x) = raise "to be caught" rescue "class #{x}" 16 | -------------------------------------------------------------------------------- /test/fixtures/endless_method_forwarded_args_legacy.rb: -------------------------------------------------------------------------------- 1 | def foo(...) = bar(...) 2 | -------------------------------------------------------------------------------- /test/fixtures/endless_method_with_rescue_mod.rb: -------------------------------------------------------------------------------- 1 | def m() = 1 rescue 2 2 | 3 | def self.m() = 1 rescue 2 4 | -------------------------------------------------------------------------------- /test/fixtures/endless_method_without_args.rb: -------------------------------------------------------------------------------- 1 | def foo = 42 2 | 3 | def foo = 42 rescue nil 4 | 5 | def self.foo = 42 6 | 7 | def self.foo = 42 rescue nil 8 | -------------------------------------------------------------------------------- /test/fixtures/ensure.rb: -------------------------------------------------------------------------------- 1 | begin; meth; ensure; bar; end 2 | -------------------------------------------------------------------------------- /test/fixtures/ensure_empty.rb: -------------------------------------------------------------------------------- 1 | begin ensure end 2 | -------------------------------------------------------------------------------- /test/fixtures/false.rb: -------------------------------------------------------------------------------- 1 | false 2 | -------------------------------------------------------------------------------- /test/fixtures/float.rb: -------------------------------------------------------------------------------- 1 | -1.33 2 | 3 | 1.33 4 | -------------------------------------------------------------------------------- /test/fixtures/for.rb: -------------------------------------------------------------------------------- 1 | for a in foo do p a; end 2 | 3 | for a in foo; p a; end 4 | -------------------------------------------------------------------------------- /test/fixtures/for_mlhs.rb: -------------------------------------------------------------------------------- 1 | for a, b in foo; p a, b; end 2 | -------------------------------------------------------------------------------- /test/fixtures/forward_arg.rb: -------------------------------------------------------------------------------- 1 | def foo(...); bar(...); end 2 | -------------------------------------------------------------------------------- /test/fixtures/forward_arg_with_open_args.rb: -------------------------------------------------------------------------------- 1 | (def foo ... 2 | bar(...) 3 | end) 4 | 5 | (def foo ...; bar(...); end) 6 | 7 | def foo ... 8 | end 9 | 10 | def foo ...; bar(...); end 11 | 12 | def foo a, ... 13 | bar(...) 14 | end 15 | 16 | def foo a, ...; bar(...); end 17 | 18 | def foo a, b = 1, ... 19 | end 20 | 21 | def foo b = 1, ... 22 | bar(...) 23 | end 24 | 25 | def foo b = 1, ...; bar(...); end 26 | 27 | def foo(a, ...) bar(...) end 28 | -------------------------------------------------------------------------------- /test/fixtures/forward_args_legacy.rb: -------------------------------------------------------------------------------- 1 | def foo(...); bar(...); end 2 | 3 | def foo(...); end 4 | 5 | def foo(...); super(...); end 6 | -------------------------------------------------------------------------------- /test/fixtures/forwarded_argument_with_kwrestarg.rb: -------------------------------------------------------------------------------- 1 | def foo(argument, **); bar(argument, **); end 2 | -------------------------------------------------------------------------------- /test/fixtures/forwarded_argument_with_restarg.rb: -------------------------------------------------------------------------------- 1 | def foo(argument, *); bar(argument, *); end 2 | -------------------------------------------------------------------------------- /test/fixtures/forwarded_kwrestarg.rb: -------------------------------------------------------------------------------- 1 | def foo(**); bar(**); end 2 | -------------------------------------------------------------------------------- /test/fixtures/forwarded_kwrestarg_with_additional_kwarg.rb: -------------------------------------------------------------------------------- 1 | def foo(**); bar(**, from_foo: true); end 2 | -------------------------------------------------------------------------------- /test/fixtures/forwarded_restarg.rb: -------------------------------------------------------------------------------- 1 | def foo(*); bar(*); end 2 | -------------------------------------------------------------------------------- /test/fixtures/gvar.rb: -------------------------------------------------------------------------------- 1 | $foo 2 | -------------------------------------------------------------------------------- /test/fixtures/gvasgn.rb: -------------------------------------------------------------------------------- 1 | $var = 10 2 | -------------------------------------------------------------------------------- /test/fixtures/hash_empty.rb: -------------------------------------------------------------------------------- 1 | { } 2 | -------------------------------------------------------------------------------- /test/fixtures/hash_hashrocket.rb: -------------------------------------------------------------------------------- 1 | { 1 => 2 } 2 | 3 | { 1 => 2, :foo => "bar" } 4 | -------------------------------------------------------------------------------- /test/fixtures/hash_kwsplat.rb: -------------------------------------------------------------------------------- 1 | { foo: 2, **bar } 2 | -------------------------------------------------------------------------------- /test/fixtures/hash_label.rb: -------------------------------------------------------------------------------- 1 | { foo: 2 } 2 | -------------------------------------------------------------------------------- /test/fixtures/hash_label_end.rb: -------------------------------------------------------------------------------- 1 | f(a ? "a":1) 2 | 3 | { 'foo': 2 } 4 | 5 | { 'foo': 2, 'bar': {}} 6 | -------------------------------------------------------------------------------- /test/fixtures/hash_pair_value_omission.rb: -------------------------------------------------------------------------------- 1 | {BAR:} 2 | 3 | {a:, b:} 4 | 5 | {puts:} 6 | -------------------------------------------------------------------------------- /test/fixtures/heredoc.rb: -------------------------------------------------------------------------------- 1 | <<'HERE' 2 | foo 3 | bar 4 | HERE 5 | 6 | <(**nil) {} 2 | 3 | def f(**nil); end 4 | 5 | m { |**nil| } 6 | -------------------------------------------------------------------------------- /test/fixtures/kwoptarg.rb: -------------------------------------------------------------------------------- 1 | def f(foo: 1); end 2 | -------------------------------------------------------------------------------- /test/fixtures/kwoptarg_with_kwrestarg_and_forwarded_args.rb: -------------------------------------------------------------------------------- 1 | def f(a: nil, **); b(**) end 2 | -------------------------------------------------------------------------------- /test/fixtures/kwrestarg_named.rb: -------------------------------------------------------------------------------- 1 | def f(**foo); end 2 | -------------------------------------------------------------------------------- /test/fixtures/kwrestarg_unnamed.rb: -------------------------------------------------------------------------------- 1 | def f(**); end 2 | -------------------------------------------------------------------------------- /test/fixtures/lbrace_arg_after_command_args.rb: -------------------------------------------------------------------------------- 1 | let (:a) { m do; end } 2 | -------------------------------------------------------------------------------- /test/fixtures/lparenarg_after_lvar__since_25.rb: -------------------------------------------------------------------------------- 1 | foo (-1.3).abs 2 | 3 | meth (-1.3).abs 4 | -------------------------------------------------------------------------------- /test/fixtures/lvar.rb: -------------------------------------------------------------------------------- 1 | foo 2 | -------------------------------------------------------------------------------- /test/fixtures/lvar_injecting_match.rb: -------------------------------------------------------------------------------- 1 | /(?bar)/ =~ 'bar'; match 2 | -------------------------------------------------------------------------------- /test/fixtures/lvasgn.rb: -------------------------------------------------------------------------------- 1 | var = 10; var 2 | -------------------------------------------------------------------------------- /test/fixtures/masgn.rb: -------------------------------------------------------------------------------- 1 | (foo, bar) = 1, 2 2 | 3 | foo, bar = 1, 2 4 | 5 | foo, bar, baz = 1, 2 6 | -------------------------------------------------------------------------------- /test/fixtures/masgn_attr.rb: -------------------------------------------------------------------------------- 1 | self.A, foo = foo 2 | 3 | self.a, self[1, 2] = foo 4 | 5 | self::a, foo = foo 6 | -------------------------------------------------------------------------------- /test/fixtures/masgn_cmd.rb: -------------------------------------------------------------------------------- 1 | foo, bar = m foo 2 | -------------------------------------------------------------------------------- /test/fixtures/masgn_const.rb: -------------------------------------------------------------------------------- 1 | ::A, foo = foo 2 | 3 | self::A, foo = foo 4 | -------------------------------------------------------------------------------- /test/fixtures/masgn_nested.rb: -------------------------------------------------------------------------------- 1 | ((b, )) = foo 2 | 3 | a, (b, c) = foo 4 | -------------------------------------------------------------------------------- /test/fixtures/masgn_splat.rb: -------------------------------------------------------------------------------- 1 | * = bar 2 | 3 | *, c, d = bar 4 | 5 | *b = bar 6 | 7 | *b, c = bar 8 | 9 | @foo, @@bar = *foo 10 | 11 | a, * = bar 12 | 13 | a, *, c = bar 14 | 15 | a, *b = bar 16 | 17 | a, *b, c = bar 18 | 19 | a, b = *foo, bar 20 | -------------------------------------------------------------------------------- /test/fixtures/method_definition_in_while_cond.rb: -------------------------------------------------------------------------------- 1 | while def foo a = tap do end; end; break; end 2 | 3 | while def foo; tap do end; end; break; end 4 | 5 | while def self.foo a = tap do end; end; break; end 6 | 7 | while def self.foo; tap do end; end; break; end 8 | -------------------------------------------------------------------------------- /test/fixtures/module.rb: -------------------------------------------------------------------------------- 1 | module Foo; end 2 | -------------------------------------------------------------------------------- /test/fixtures/multibyte.rb: -------------------------------------------------------------------------------- 1 | # credits to https://github.com/minimaxir/big-list-of-naughty-strings/blob/master/blns.txt 2 | 3 | # 🥲 4 | 5 | a = "😍" 6 | b = "👩🏽" 7 | c = "👨‍🦰 👨🏿‍🦰 👨‍🦱 👨🏿‍🦱 🦹🏿‍♂️" 8 | d = "👾 🙇 💁 🙅 🙆 🙋 🙎 🙍" 9 | e = "🐵 🙈 🙉 🙊" 10 | f = "❤️ 💔 💌 💕 💞 💓 💗 💖 💘 💝 💟 💜 💛 💚 💙" 11 | g = "✋🏿 💪🏿 👐🏿 🙌🏿 👏🏿 🙏🏿" 12 | h = "👨‍👩‍👦 👨‍👩‍👧‍👦 👨‍👨‍👦 👩‍👩‍👧 👨‍👦 👨‍👧‍👦 👩‍👦 👩‍👧‍👦" 13 | i = "🚾 🆒 🆓 🆕 🆖 🆗 🆙 🏧" 14 | j = "0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟" 15 | 16 | 17 | ra = /😍/ 18 | rb = /👩🏽/ 19 | rc = /👨‍🦰 👨🏿‍🦰 👨‍🦱 👨🏿‍🦱 🦹🏿‍♂️/ 20 | rd = /👾 🙇 💁 🙅 🙆 🙋 🙎 🙍/ 21 | re = /🐵 🙈 🙉 🙊/ 22 | rf = /❤️ 💔 💌 💕 💞 💓 💗 💖 💘 💝 💟 💜 💛 💚 💙/ 23 | rg = /✋🏿 💪🏿 👐🏿 🙌🏿 👏🏿 🙏🏿/ 24 | rh = /👨‍👩‍👦 👨‍👩‍👧‍👦 👨‍👨‍👦 👩‍👩‍👧 👨‍👦 👨‍👧‍👦 👩‍👦 👩‍👧‍👦/ 25 | ri = /🚾 🆒 🆓 🆕 🆖 🆗 🆙 🏧/ 26 | rj = /0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟/ 27 | -------------------------------------------------------------------------------- /test/fixtures/multiple_pattern_matches.rb: -------------------------------------------------------------------------------- 1 | {a: 0} => a: 2 | {a: 0} => a: 3 | 4 | {a: 0} in a: 5 | {a: 0} in a: 6 | -------------------------------------------------------------------------------- /test/fixtures/newline_in_hash_argument.rb: -------------------------------------------------------------------------------- 1 | case foo 2 | in a: 3 | 0 4 | true 5 | in "b": 6 | 0 7 | true 8 | end 9 | 10 | obj.set "foo": 11 | 1 12 | 13 | obj.set foo: 14 | 1 15 | -------------------------------------------------------------------------------- /test/fixtures/next.rb: -------------------------------------------------------------------------------- 1 | next 2 | 3 | next foo 4 | 5 | next() 6 | 7 | next(foo) 8 | -------------------------------------------------------------------------------- /test/fixtures/next_block.rb: -------------------------------------------------------------------------------- 1 | next fun foo do end 2 | -------------------------------------------------------------------------------- /test/fixtures/nil.rb: -------------------------------------------------------------------------------- 1 | nil 2 | -------------------------------------------------------------------------------- /test/fixtures/nil_expression.rb: -------------------------------------------------------------------------------- 1 | () 2 | 3 | begin end 4 | -------------------------------------------------------------------------------- /test/fixtures/non_lvar_injecting_match.rb: -------------------------------------------------------------------------------- 1 | /#{1}(?bar)/ =~ 'bar' 2 | -------------------------------------------------------------------------------- /test/fixtures/not.rb: -------------------------------------------------------------------------------- 1 | not foo 2 | 3 | not() 4 | 5 | not(foo) 6 | -------------------------------------------------------------------------------- /test/fixtures/not_cmd.rb: -------------------------------------------------------------------------------- 1 | not m foo 2 | -------------------------------------------------------------------------------- /test/fixtures/not_masgn__24.rb: -------------------------------------------------------------------------------- 1 | !(a, b = foo) 2 | -------------------------------------------------------------------------------- /test/fixtures/nth_ref.rb: -------------------------------------------------------------------------------- 1 | $10 2 | -------------------------------------------------------------------------------- /test/fixtures/numbered_args_after_27.rb: -------------------------------------------------------------------------------- 1 | -> do _1 + _9 end 2 | 3 | -> { _1 + _9} 4 | 5 | m do _1 + _9 end 6 | 7 | m { _1 + _9 } 8 | -------------------------------------------------------------------------------- /test/fixtures/numparam_outside_block.rb: -------------------------------------------------------------------------------- 1 | _1 2 | 3 | class << foo; _1; end 4 | 5 | class A; _1; end 6 | 7 | def self.m; _1; end 8 | 9 | module A; _1; end 10 | -------------------------------------------------------------------------------- /test/fixtures/op_asgn.rb: -------------------------------------------------------------------------------- 1 | foo.A += 1 2 | 3 | foo.a += 1 4 | 5 | foo::a += 1 6 | 7 | $foo += 1 8 | -------------------------------------------------------------------------------- /test/fixtures/op_asgn_cmd.rb: -------------------------------------------------------------------------------- 1 | foo.A += m foo 2 | 3 | foo.a += m foo 4 | 5 | foo::A += m foo 6 | 7 | foo::a += m foo 8 | -------------------------------------------------------------------------------- /test/fixtures/op_asgn_index.rb: -------------------------------------------------------------------------------- 1 | foo[0, 1] += 2 2 | -------------------------------------------------------------------------------- /test/fixtures/op_asgn_index_cmd.rb: -------------------------------------------------------------------------------- 1 | foo[0, 1] += m foo 2 | -------------------------------------------------------------------------------- /test/fixtures/optarg.rb: -------------------------------------------------------------------------------- 1 | def f foo = 1; end 2 | 3 | def f(foo=1, bar=2); end 4 | -------------------------------------------------------------------------------- /test/fixtures/or.rb: -------------------------------------------------------------------------------- 1 | foo or bar 2 | 3 | foo || bar 4 | -------------------------------------------------------------------------------- /test/fixtures/or_asgn.rb: -------------------------------------------------------------------------------- 1 | foo.a ||= 1 2 | 3 | foo[0, 1] ||= 2 4 | 5 | $foo ||= 1 6 | -------------------------------------------------------------------------------- /test/fixtures/parser_bug_272.rb: -------------------------------------------------------------------------------- 1 | a @b do |c|;end 2 | -------------------------------------------------------------------------------- /test/fixtures/parser_bug_490.rb: -------------------------------------------------------------------------------- 1 | def m; class << self; A = nil; end; end 2 | 3 | def m; class << self; class C; end; end; end 4 | 5 | def m; class << self; module M; end; end; end 6 | -------------------------------------------------------------------------------- /test/fixtures/parser_bug_507.rb: -------------------------------------------------------------------------------- 1 | m = -> *args do end 2 | -------------------------------------------------------------------------------- /test/fixtures/parser_bug_518.rb: -------------------------------------------------------------------------------- 1 | class A < B 2 | end 3 | -------------------------------------------------------------------------------- /test/fixtures/parser_bug_525.rb: -------------------------------------------------------------------------------- 1 | m1 :k => m2 do; m3() do end; end 2 | -------------------------------------------------------------------------------- /test/fixtures/parser_bug_604.rb: -------------------------------------------------------------------------------- 1 | m a + b do end 2 | -------------------------------------------------------------------------------- /test/fixtures/parser_bug_640.rb: -------------------------------------------------------------------------------- 1 | <<~FOO 2 | baz\ 3 | qux 4 | FOO 5 | -------------------------------------------------------------------------------- /test/fixtures/parser_bug_645.rb: -------------------------------------------------------------------------------- 1 | -> (arg={}) {} 2 | -------------------------------------------------------------------------------- /test/fixtures/parser_bug_830.rb: -------------------------------------------------------------------------------- 1 | /\(/ 2 | -------------------------------------------------------------------------------- /test/fixtures/parser_drops_truncated_parts_of_squiggly_heredoc.rb: -------------------------------------------------------------------------------- 1 | <<~HERE 2 | #{} 3 | HERE 4 | -------------------------------------------------------------------------------- /test/fixtures/parser_slash_slash_n_escaping_in_literals.rb: -------------------------------------------------------------------------------- 1 | "a\ 2 | b" 3 | 4 | %I{a\ 5 | b} 6 | 7 | %Q{a\ 8 | b} 9 | 10 | %W{a\ 11 | b} 12 | 13 | %i{a\ 14 | b} 15 | 16 | %q{a\ 17 | b} 18 | 19 | %r{a\ 20 | b} 21 | 22 | %s{a\ 23 | b} 24 | 25 | %w{a\ 26 | b} 27 | 28 | %x{a\ 29 | b} 30 | 31 | %{a\ 32 | b} 33 | 34 | 'a\ 35 | b' 36 | 37 | /a\ 38 | b/ 39 | 40 | :"a\ 41 | b" 42 | 43 | :'a\ 44 | b' 45 | 46 | <<-"HERE" 47 | a\ 48 | b 49 | HERE 50 | 51 | <<-'HERE' 52 | a\ 53 | b 54 | HERE 55 | 56 | <<-`HERE` 57 | a\ 58 | b 59 | HERE 60 | 61 | `a\ 62 | b` 63 | -------------------------------------------------------------------------------- /test/fixtures/pattern_match.rb: -------------------------------------------------------------------------------- 1 | case foo; 2 | in a: {b:}, c: 3 | p c 4 | ; end 5 | 6 | case foo; 7 | in {Foo: 42 8 | } 9 | false 10 | ; end 11 | 12 | case foo; 13 | in {a: 14 | 2} 15 | false 16 | ; end 17 | 18 | case foo; 19 | in {a: 20 | } 21 | true 22 | ; end 23 | 24 | case foo; 25 | in {a: 1 26 | } 27 | false 28 | ; end 29 | 30 | case foo; in "#{ %Q{a} }": 1 then true; end 31 | 32 | case foo; in "#{ %Q{a} }": then true; end 33 | 34 | case foo; in "#{ %q{a} }": 1 then true; end 35 | 36 | case foo; in "#{ %q{a} }": then true; end 37 | 38 | case foo; in "#{ 'a' }": 1 then true; end 39 | 40 | case foo; in "#{ 'a' }": then true; end 41 | 42 | case foo; in "a": 1 then true; end 43 | 44 | case foo; in "a": then true; end 45 | 46 | case foo; in (1) then true; end 47 | 48 | case foo; in * then nil; end 49 | 50 | case foo; in ** then true; end 51 | 52 | case foo; in **a then true; end 53 | 54 | case foo; in **nil then true; end 55 | 56 | case foo; in *, 42, * then true; end 57 | 58 | case foo; in *x then nil; end 59 | 60 | case foo; in *x, y, z then nil; end 61 | 62 | case foo; in ->{ 42 } then true; end 63 | 64 | case foo; in ...2 then true; end 65 | 66 | case foo; in ..2 then true; end 67 | 68 | case foo; in 1 => a then true; end 69 | 70 | case foo; in 1 | 2 then true; end 71 | 72 | case foo; in 1, "a", [], {} then nil; end 73 | 74 | case foo; in 1.. then true; end 75 | 76 | case foo; in 1... then true; end 77 | 78 | case foo; in 1...2 then true; end 79 | 80 | case foo; in 1..2 then true; end 81 | 82 | case foo; in 1; end 83 | 84 | case foo; in ::A then true; end 85 | 86 | case foo; in A then true; end 87 | 88 | case foo; in A() then true; end 89 | 90 | case foo; in A(1, 2) then true; end 91 | 92 | case foo; in A(x:) then true; end 93 | 94 | case foo; in A::B then true; end 95 | 96 | case foo; in A[1, 2] then true; end 97 | 98 | case foo; in A[] then true; end 99 | 100 | case foo; in A[x:] then true; end 101 | 102 | case foo; in Array[*, 1, *] then true; end 103 | 104 | case foo; in String(*, 1, *) then true; end 105 | 106 | case foo; in [*, x] then true; end 107 | 108 | case foo; in [*x, 1 => a, *y] then true; end 109 | 110 | case foo; in [*x, y] then true; end 111 | 112 | case foo; in [x, *, y] then true; end 113 | 114 | case foo; in [x, *y, z] then true; end 115 | 116 | case foo; in [x, y, *] then true; end 117 | 118 | case foo; in [x, y, *z] then true; end 119 | 120 | case foo; in [x, y,] then true; end 121 | 122 | case foo; in [x, y] then true; end 123 | 124 | case foo; in [x,] then nil; end 125 | 126 | case foo; in [x] then nil; end 127 | 128 | case foo; in ^$TestPatternMatching; end 129 | 130 | case foo; in ^(0+0) then nil; end 131 | 132 | case foo; in ^(1 133 | ); end 134 | 135 | case foo; in ^(42) then nil; end 136 | 137 | case foo; in ^@@TestPatternMatching; end 138 | 139 | case foo; in ^@a; end 140 | 141 | case foo; in ^foo then nil; end 142 | 143 | case foo; in a: 1 then true; end 144 | 145 | case foo; in a: 1, _a:, ** then true; end 146 | 147 | case foo; in a: 1, b: 2 then true; end 148 | 149 | case foo; in a: then true; end 150 | 151 | case foo; in a:, b: then true; end 152 | 153 | case foo; in self then true; end 154 | 155 | case foo; in x if true; nil; end 156 | 157 | case foo; in x then x; end 158 | 159 | case foo; in x unless true; nil; end 160 | 161 | case foo; in x, *y, z then nil; end 162 | 163 | case foo; in x, then nil; end 164 | 165 | case foo; in x, y then nil; end 166 | 167 | case foo; in x, y, then nil; end 168 | 169 | case foo; in { a: 1 } then true; end 170 | 171 | case foo; in { a: 1, } then true; end 172 | 173 | case foo; in { foo: ^(42) } then nil; end 174 | 175 | case foo; in {} then true; end 176 | -------------------------------------------------------------------------------- /test/fixtures/pattern_matching__FILE__LINE_literals.rb: -------------------------------------------------------------------------------- 1 | case [__FILE__, __LINE__ + 1, __ENCODING__] 2 | in [__FILE__, __LINE__, __ENCODING__] 3 | end 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/pattern_matching_blank_else.rb: -------------------------------------------------------------------------------- 1 | case 1; in 2; 3; else; end 2 | -------------------------------------------------------------------------------- /test/fixtures/pattern_matching_else.rb: -------------------------------------------------------------------------------- 1 | case 1; in 2; 3; else; 4; end 2 | -------------------------------------------------------------------------------- /test/fixtures/pattern_matching_single_line.rb: -------------------------------------------------------------------------------- 1 | 1 => [a]; a 2 | 3 | 1 in [a]; a 4 | -------------------------------------------------------------------------------- /test/fixtures/pattern_matching_single_line_allowed_omission_of_parentheses.rb: -------------------------------------------------------------------------------- 1 | [1, 2] => a, b; a 2 | 3 | [1, 2] in a, b; a 4 | 5 | {a: 1} => a:; a 6 | 7 | {a: 1} in a:; a 8 | 9 | {key: :value} => key: value; value 10 | 11 | {key: :value} in key: value; value 12 | -------------------------------------------------------------------------------- /test/fixtures/postexe.rb: -------------------------------------------------------------------------------- 1 | END { 1 } 2 | -------------------------------------------------------------------------------- /test/fixtures/preexe.rb: -------------------------------------------------------------------------------- 1 | BEGIN { 1 } 2 | -------------------------------------------------------------------------------- /test/fixtures/procarg0.rb: -------------------------------------------------------------------------------- 1 | m { |(foo, bar)| } 2 | 3 | m { |foo| } 4 | -------------------------------------------------------------------------------- /test/fixtures/range_endless.rb: -------------------------------------------------------------------------------- 1 | 1.. 2 | 3 | 1... 4 | -------------------------------------------------------------------------------- /test/fixtures/range_exclusive.rb: -------------------------------------------------------------------------------- 1 | 1...2 2 | -------------------------------------------------------------------------------- /test/fixtures/range_inclusive.rb: -------------------------------------------------------------------------------- 1 | 1..2 2 | -------------------------------------------------------------------------------- /test/fixtures/rational.rb: -------------------------------------------------------------------------------- 1 | 42.1r 2 | 3 | 42r 4 | -------------------------------------------------------------------------------- /test/fixtures/redo.rb: -------------------------------------------------------------------------------- 1 | redo 2 | -------------------------------------------------------------------------------- /test/fixtures/regex_interp.rb: -------------------------------------------------------------------------------- 1 | /foo#{bar}baz/ 2 | -------------------------------------------------------------------------------- /test/fixtures/regex_plain.rb: -------------------------------------------------------------------------------- 1 | /source/im 2 | -------------------------------------------------------------------------------- /test/fixtures/resbody_list.rb: -------------------------------------------------------------------------------- 1 | begin; meth; rescue Exception; bar; end 2 | -------------------------------------------------------------------------------- /test/fixtures/resbody_list_mrhs.rb: -------------------------------------------------------------------------------- 1 | begin; meth; rescue Exception, foo; bar; end 2 | -------------------------------------------------------------------------------- /test/fixtures/resbody_list_var.rb: -------------------------------------------------------------------------------- 1 | begin; meth; rescue foo => ex; bar; end 2 | -------------------------------------------------------------------------------- /test/fixtures/resbody_var.rb: -------------------------------------------------------------------------------- 1 | begin; meth; rescue => @ex; bar; end 2 | 3 | begin; meth; rescue => ex; bar; end 4 | -------------------------------------------------------------------------------- /test/fixtures/rescue.rb: -------------------------------------------------------------------------------- 1 | begin; meth; rescue; foo; end 2 | -------------------------------------------------------------------------------- /test/fixtures/rescue_else.rb: -------------------------------------------------------------------------------- 1 | begin; meth; rescue; foo; else; bar; end 2 | -------------------------------------------------------------------------------- /test/fixtures/rescue_else_ensure.rb: -------------------------------------------------------------------------------- 1 | begin; meth; rescue; baz; else foo; ensure; bar end 2 | -------------------------------------------------------------------------------- /test/fixtures/rescue_ensure.rb: -------------------------------------------------------------------------------- 1 | begin; meth; rescue; baz; ensure; bar; end 2 | -------------------------------------------------------------------------------- /test/fixtures/rescue_in_lambda_block.rb: -------------------------------------------------------------------------------- 1 | -> do rescue; end 2 | -------------------------------------------------------------------------------- /test/fixtures/rescue_mod.rb: -------------------------------------------------------------------------------- 1 | meth rescue bar 2 | -------------------------------------------------------------------------------- /test/fixtures/rescue_mod_asgn.rb: -------------------------------------------------------------------------------- 1 | foo = meth rescue bar 2 | -------------------------------------------------------------------------------- /test/fixtures/rescue_mod_masgn.rb: -------------------------------------------------------------------------------- 1 | foo, bar = meth rescue [1, 2] 2 | -------------------------------------------------------------------------------- /test/fixtures/rescue_mod_op_assign.rb: -------------------------------------------------------------------------------- 1 | foo += meth rescue bar 2 | -------------------------------------------------------------------------------- /test/fixtures/rescue_without_begin_end.rb: -------------------------------------------------------------------------------- 1 | meth do; foo; rescue; bar; end 2 | -------------------------------------------------------------------------------- /test/fixtures/restarg_named.rb: -------------------------------------------------------------------------------- 1 | def f(*foo); end 2 | -------------------------------------------------------------------------------- /test/fixtures/restarg_unnamed.rb: -------------------------------------------------------------------------------- 1 | def f(*); end 2 | -------------------------------------------------------------------------------- /test/fixtures/retry.rb: -------------------------------------------------------------------------------- 1 | retry 2 | -------------------------------------------------------------------------------- /test/fixtures/return.rb: -------------------------------------------------------------------------------- 1 | return 2 | 3 | return foo 4 | 5 | return() 6 | 7 | return(foo) 8 | -------------------------------------------------------------------------------- /test/fixtures/return_block.rb: -------------------------------------------------------------------------------- 1 | return fun foo do end 2 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_10279.rb: -------------------------------------------------------------------------------- 1 | {a: if true then 42 end} 2 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_10653.rb: -------------------------------------------------------------------------------- 1 | false ? raise do end : tap do end 2 | 3 | false ? raise {} : tap {} 4 | 5 | true ? 1.tap do |n| p n end : 0 6 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_11107.rb: -------------------------------------------------------------------------------- 1 | p ->() do a() do end end 2 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_11380.rb: -------------------------------------------------------------------------------- 1 | p -> { :hello }, a: 1 do end 2 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_11873.rb: -------------------------------------------------------------------------------- 1 | a b(c d), "x" do end 2 | 3 | a b(c d), /x/ do end 4 | 5 | a b(c d), /x/m do end 6 | 7 | a b(c(d)), "x" do end 8 | 9 | a b(c(d)), /x/ do end 10 | 11 | a b(c(d)), /x/m do end 12 | 13 | a b{c d}, "x" do end 14 | 15 | a b{c d}, /x/ do end 16 | 17 | a b{c d}, /x/m do end 18 | 19 | a b{c(d)}, "x" do end 20 | 21 | a b{c(d)}, /x/ do end 22 | 23 | a b{c(d)}, /x/m do end 24 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_11873_a.rb: -------------------------------------------------------------------------------- 1 | a b(c d), 1 do end 2 | 3 | a b(c d), 1.0 do end 4 | 5 | a b(c d), 1.0i do end 6 | 7 | a b(c d), 1.0r do end 8 | 9 | a b(c d), :e do end 10 | 11 | a b(c(d)), 1 do end 12 | 13 | a b(c(d)), 1.0 do end 14 | 15 | a b(c(d)), 1.0i do end 16 | 17 | a b(c(d)), 1.0r do end 18 | 19 | a b(c(d)), :e do end 20 | 21 | a b{c d}, 1 do end 22 | 23 | a b{c d}, 1.0 do end 24 | 25 | a b{c d}, 1.0i do end 26 | 27 | a b{c d}, 1.0r do end 28 | 29 | a b{c d}, :e do end 30 | 31 | a b{c(d)}, 1 do end 32 | 33 | a b{c(d)}, 1.0 do end 34 | 35 | a b{c(d)}, 1.0i do end 36 | 37 | a b{c(d)}, 1.0r do end 38 | 39 | a b{c(d)}, :e do end 40 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_11873_b.rb: -------------------------------------------------------------------------------- 1 | p p{p(p);p p}, tap do end 2 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_11989.rb: -------------------------------------------------------------------------------- 1 | p <<~"E" 2 | x\n y 3 | E 4 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_11990.rb: -------------------------------------------------------------------------------- 1 | p <<~E " y" 2 | x 3 | E 4 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_12073.rb: -------------------------------------------------------------------------------- 1 | a = 1; a b: 1 2 | 3 | def foo raise; raise A::B, ''; end 4 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_12402.rb: -------------------------------------------------------------------------------- 1 | foo += raise bar rescue nil 2 | 3 | foo += raise(bar) rescue nil 4 | 5 | foo = raise bar rescue nil 6 | 7 | foo = raise(bar) rescue nil 8 | 9 | foo.C += raise bar rescue nil 10 | 11 | foo.C += raise(bar) rescue nil 12 | 13 | foo.m += raise bar rescue nil 14 | 15 | foo.m += raise(bar) rescue nil 16 | 17 | foo::C ||= raise bar rescue nil 18 | 19 | foo::C ||= raise(bar) rescue nil 20 | 21 | foo::m += raise bar rescue nil 22 | 23 | foo::m += raise(bar) rescue nil 24 | 25 | foo[0] += raise bar rescue nil 26 | 27 | foo[0] += raise(bar) rescue nil 28 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_12669.rb: -------------------------------------------------------------------------------- 1 | a += b += raise :x 2 | 3 | a += b = raise :x 4 | 5 | a = b += raise :x 6 | 7 | a = b = raise :x 8 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_12686.rb: -------------------------------------------------------------------------------- 1 | f (g rescue nil) 2 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_13547.rb: -------------------------------------------------------------------------------- 1 | meth[] {} 2 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_14690.rb: -------------------------------------------------------------------------------- 1 | let () { m(a) do; end } 2 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_15789.rb: -------------------------------------------------------------------------------- 1 | m ->(a = ->{_1}) {a} 2 | 3 | m ->(a: ->{_1}) {a} 4 | -------------------------------------------------------------------------------- /test/fixtures/ruby_bug_9669.rb: -------------------------------------------------------------------------------- 1 | def a b: 2 | return 3 | end 4 | 5 | o = { 6 | a: 7 | 1 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/sclass.rb: -------------------------------------------------------------------------------- 1 | class << foo; nil; end 2 | -------------------------------------------------------------------------------- /test/fixtures/self.rb: -------------------------------------------------------------------------------- 1 | self 2 | -------------------------------------------------------------------------------- /test/fixtures/send_attr_asgn.rb: -------------------------------------------------------------------------------- 1 | foo.A = 1 2 | 3 | foo.a = 1 4 | 5 | foo::A = 1 6 | 7 | foo::a = 1 8 | -------------------------------------------------------------------------------- /test/fixtures/send_attr_asgn_conditional.rb: -------------------------------------------------------------------------------- 1 | a&.b = 1 2 | -------------------------------------------------------------------------------- /test/fixtures/send_binary_op.rb: -------------------------------------------------------------------------------- 1 | foo != 1 2 | 3 | foo !~ 1 4 | 5 | foo % 1 6 | 7 | foo & 1 8 | 9 | foo * 1 10 | 11 | foo ** 1 12 | 13 | foo + 1 14 | 15 | foo - 1 16 | 17 | foo / 1 18 | 19 | foo < 1 20 | 21 | foo << 1 22 | 23 | foo <= 1 24 | 25 | foo <=> 1 26 | 27 | foo == 1 28 | 29 | foo === 1 30 | 31 | foo =~ 1 32 | 33 | foo > 1 34 | 35 | foo >= 1 36 | 37 | foo >> 1 38 | 39 | foo ^ 1 40 | 41 | foo | 1 42 | -------------------------------------------------------------------------------- /test/fixtures/send_block_chain_cmd.rb: -------------------------------------------------------------------------------- 1 | meth 1 do end.fun bar 2 | 3 | meth 1 do end.fun bar do end 4 | 5 | meth 1 do end.fun {} 6 | 7 | meth 1 do end.fun(bar) 8 | 9 | meth 1 do end.fun(bar) {} 10 | 11 | meth 1 do end::fun bar 12 | 13 | meth 1 do end::fun(bar) 14 | -------------------------------------------------------------------------------- /test/fixtures/send_block_conditional.rb: -------------------------------------------------------------------------------- 1 | foo&.bar {} 2 | -------------------------------------------------------------------------------- /test/fixtures/send_call.rb: -------------------------------------------------------------------------------- 1 | foo.(1) 2 | 3 | foo::(1) 4 | -------------------------------------------------------------------------------- /test/fixtures/send_conditional.rb: -------------------------------------------------------------------------------- 1 | a&.b 2 | -------------------------------------------------------------------------------- /test/fixtures/send_index.rb: -------------------------------------------------------------------------------- 1 | foo[1, 2] 2 | -------------------------------------------------------------------------------- /test/fixtures/send_index_asgn.rb: -------------------------------------------------------------------------------- 1 | foo[1, 2] = 3 2 | -------------------------------------------------------------------------------- /test/fixtures/send_index_asgn_legacy.rb: -------------------------------------------------------------------------------- 1 | foo[1, 2] = 3 2 | -------------------------------------------------------------------------------- /test/fixtures/send_index_cmd.rb: -------------------------------------------------------------------------------- 1 | foo[m bar] 2 | -------------------------------------------------------------------------------- /test/fixtures/send_index_legacy.rb: -------------------------------------------------------------------------------- 1 | foo[1, 2] 2 | -------------------------------------------------------------------------------- /test/fixtures/send_lambda.rb: -------------------------------------------------------------------------------- 1 | -> * { } 2 | 3 | -> do end 4 | 5 | ->{ } 6 | -------------------------------------------------------------------------------- /test/fixtures/send_lambda_args.rb: -------------------------------------------------------------------------------- 1 | -> (a) { } 2 | 3 | ->(a) { } 4 | -------------------------------------------------------------------------------- /test/fixtures/send_lambda_args_noparen.rb: -------------------------------------------------------------------------------- 1 | -> a: 1 { } 2 | 3 | -> a: { } 4 | -------------------------------------------------------------------------------- /test/fixtures/send_lambda_args_shadow.rb: -------------------------------------------------------------------------------- 1 | ->(a; foo, bar) { } 2 | -------------------------------------------------------------------------------- /test/fixtures/send_lambda_legacy.rb: -------------------------------------------------------------------------------- 1 | ->{ } 2 | -------------------------------------------------------------------------------- /test/fixtures/send_op_asgn_conditional.rb: -------------------------------------------------------------------------------- 1 | a&.b &&= 1 2 | -------------------------------------------------------------------------------- /test/fixtures/send_plain.rb: -------------------------------------------------------------------------------- 1 | foo.fun 2 | 3 | foo::Fun() 4 | 5 | foo::fun 6 | -------------------------------------------------------------------------------- /test/fixtures/send_plain_cmd.rb: -------------------------------------------------------------------------------- 1 | foo.fun bar 2 | 3 | foo::Fun bar 4 | 5 | foo::fun bar 6 | -------------------------------------------------------------------------------- /test/fixtures/send_self.rb: -------------------------------------------------------------------------------- 1 | fun 2 | 3 | fun! 4 | 5 | fun(1) 6 | -------------------------------------------------------------------------------- /test/fixtures/send_self_block.rb: -------------------------------------------------------------------------------- 1 | fun do end 2 | 3 | fun { } 4 | 5 | fun() { } 6 | 7 | fun(1) { } 8 | -------------------------------------------------------------------------------- /test/fixtures/send_unary_op.rb: -------------------------------------------------------------------------------- 1 | +foo 2 | 3 | -foo 4 | 5 | ~foo 6 | -------------------------------------------------------------------------------- /test/fixtures/slash_newline_in_heredocs.rb: -------------------------------------------------------------------------------- 1 | <<-E 2 | 1 \ 3 | 2 4 | 3 5 | E 6 | 7 | 8 | <<~E 9 | 1 \ 10 | 2 11 | 3 12 | E 13 | 14 | -------------------------------------------------------------------------------- /test/fixtures/space_args_arg.rb: -------------------------------------------------------------------------------- 1 | fun (1) 2 | -------------------------------------------------------------------------------- /test/fixtures/space_args_arg_block.rb: -------------------------------------------------------------------------------- 1 | foo.fun (1) {} 2 | 3 | foo::fun (1) {} 4 | 5 | fun (1) {} 6 | -------------------------------------------------------------------------------- /test/fixtures/space_args_arg_call.rb: -------------------------------------------------------------------------------- 1 | fun (1).to_i 2 | -------------------------------------------------------------------------------- /test/fixtures/space_args_arg_newline.rb: -------------------------------------------------------------------------------- 1 | fun (1 2 | ) 3 | -------------------------------------------------------------------------------- /test/fixtures/space_args_block.rb: -------------------------------------------------------------------------------- 1 | fun () {} 2 | -------------------------------------------------------------------------------- /test/fixtures/space_args_cmd.rb: -------------------------------------------------------------------------------- 1 | fun (f bar) 2 | -------------------------------------------------------------------------------- /test/fixtures/string___FILE__.rb: -------------------------------------------------------------------------------- 1 | __FILE__ 2 | -------------------------------------------------------------------------------- /test/fixtures/string_concat.rb: -------------------------------------------------------------------------------- 1 | "foo#@a" "bar" 2 | 3 | 4 | "one" \ 5 | "two" \ 6 | "three" 7 | -------------------------------------------------------------------------------- /test/fixtures/string_dvar.rb: -------------------------------------------------------------------------------- 1 | "#@a #@@a #$a" 2 | -------------------------------------------------------------------------------- /test/fixtures/string_interp.rb: -------------------------------------------------------------------------------- 1 | "foo#{bar}baz" 2 | -------------------------------------------------------------------------------- /test/fixtures/string_plain.rb: -------------------------------------------------------------------------------- 1 | %q(foobar) 2 | 3 | 'foobar' 4 | -------------------------------------------------------------------------------- /test/fixtures/super.rb: -------------------------------------------------------------------------------- 1 | super foo 2 | 3 | super() 4 | 5 | super(foo) 6 | -------------------------------------------------------------------------------- /test/fixtures/super_block.rb: -------------------------------------------------------------------------------- 1 | super do end 2 | 3 | super foo, bar do end 4 | -------------------------------------------------------------------------------- /test/fixtures/symbol_interp.rb: -------------------------------------------------------------------------------- 1 | :"foo#{bar}baz" 2 | -------------------------------------------------------------------------------- /test/fixtures/symbol_plain.rb: -------------------------------------------------------------------------------- 1 | :'foo' 2 | 3 | :foo 4 | 5 | :foo? 6 | 7 | :$foo 8 | 9 | :@@foo 10 | 11 | :$foo 12 | 13 | :@@foo 14 | 15 | :+ 16 | 17 | :- 18 | 19 | :/ 20 | 21 | :* 22 | -------------------------------------------------------------------------------- /test/fixtures/ternary.rb: -------------------------------------------------------------------------------- 1 | foo ? 1 : 2 2 | -------------------------------------------------------------------------------- /test/fixtures/ternary_ambiguous_symbol.rb: -------------------------------------------------------------------------------- 1 | t=1;(foo)?t:T 2 | -------------------------------------------------------------------------------- /test/fixtures/trailing_forward_arg.rb: -------------------------------------------------------------------------------- 1 | def foo(a, b, ...); bar(a, 42, ...); end 2 | -------------------------------------------------------------------------------- /test/fixtures/true.rb: -------------------------------------------------------------------------------- 1 | true 2 | -------------------------------------------------------------------------------- /test/fixtures/unary_num_pow_precedence.rb: -------------------------------------------------------------------------------- 1 | +2.0 ** 10 2 | 3 | -2 ** 10 4 | 5 | -2.0 ** 10 6 | -------------------------------------------------------------------------------- /test/fixtures/undef.rb: -------------------------------------------------------------------------------- 1 | undef foo, :bar, :"foo#{1}" 2 | -------------------------------------------------------------------------------- /test/fixtures/unless.rb: -------------------------------------------------------------------------------- 1 | unless foo then bar; end 2 | 3 | unless foo; bar; end 4 | -------------------------------------------------------------------------------- /test/fixtures/unless_else.rb: -------------------------------------------------------------------------------- 1 | unless foo then bar; else baz; end 2 | 3 | unless foo; bar; else baz; end 4 | -------------------------------------------------------------------------------- /test/fixtures/unless_mod.rb: -------------------------------------------------------------------------------- 1 | bar unless foo 2 | -------------------------------------------------------------------------------- /test/fixtures/until.rb: -------------------------------------------------------------------------------- 1 | until foo do meth end 2 | 3 | until foo; meth end 4 | -------------------------------------------------------------------------------- /test/fixtures/until_mod.rb: -------------------------------------------------------------------------------- 1 | meth until foo 2 | -------------------------------------------------------------------------------- /test/fixtures/until_post.rb: -------------------------------------------------------------------------------- 1 | begin meth end until foo 2 | -------------------------------------------------------------------------------- /test/fixtures/var_and_asgn.rb: -------------------------------------------------------------------------------- 1 | a &&= 1 2 | -------------------------------------------------------------------------------- /test/fixtures/var_op_asgn.rb: -------------------------------------------------------------------------------- 1 | @@var |= 10 2 | 3 | @a |= 1 4 | 5 | a += 1 6 | 7 | def a; @@var |= 10; end 8 | -------------------------------------------------------------------------------- /test/fixtures/var_op_asgn_cmd.rb: -------------------------------------------------------------------------------- 1 | foo += m foo 2 | -------------------------------------------------------------------------------- /test/fixtures/var_or_asgn.rb: -------------------------------------------------------------------------------- 1 | a ||= 1 2 | -------------------------------------------------------------------------------- /test/fixtures/when_multi.rb: -------------------------------------------------------------------------------- 1 | case foo; when 'bar', 'baz'; bar; end 2 | -------------------------------------------------------------------------------- /test/fixtures/when_splat.rb: -------------------------------------------------------------------------------- 1 | case foo; when 1, *baz; bar; when *foo; end 2 | -------------------------------------------------------------------------------- /test/fixtures/when_then.rb: -------------------------------------------------------------------------------- 1 | case foo; when 'bar' then bar; end 2 | -------------------------------------------------------------------------------- /test/fixtures/while.rb: -------------------------------------------------------------------------------- 1 | while foo do meth end 2 | 3 | while foo; meth end 4 | -------------------------------------------------------------------------------- /test/fixtures/while_mod.rb: -------------------------------------------------------------------------------- 1 | meth while foo 2 | -------------------------------------------------------------------------------- /test/fixtures/while_post.rb: -------------------------------------------------------------------------------- 1 | begin meth end while foo 2 | -------------------------------------------------------------------------------- /test/fixtures/xstring_interp.rb: -------------------------------------------------------------------------------- 1 | `foo#{bar}baz` 2 | -------------------------------------------------------------------------------- /test/fixtures/xstring_plain.rb: -------------------------------------------------------------------------------- 1 | `foobar` 2 | -------------------------------------------------------------------------------- /test/fixtures/yield.rb: -------------------------------------------------------------------------------- 1 | yield 2 | 3 | yield foo 4 | 5 | yield() 6 | 7 | yield(foo) 8 | -------------------------------------------------------------------------------- /test/fixtures/zsuper.rb: -------------------------------------------------------------------------------- 1 | super 2 | -------------------------------------------------------------------------------- /test/prism_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/setup" 4 | $:.unshift(File.expand_path("../lib", __dir__)) 5 | 6 | require "test/unit" 7 | require "parser/prism" 8 | require "parser/prism/compare" 9 | require "pp" 10 | 11 | class PrismTest < Test::Unit::TestCase 12 | skip = [ 13 | # These files contain invalid syntax. We should never try to parse them. 14 | "newline_in_hash_argument.rb", 15 | "unary_num_pow_precedence.rb", 16 | 17 | # These parse differently from MRI with the parser gem, so nothing we can do 18 | # about that. 19 | "dedenting_interpolating_heredoc_fake_line_continuation.rb", 20 | 21 | # This is some kind of difference between when we combine dstr into str 22 | # sexps on escaped newlines. 23 | "parser_slash_slash_n_escaping_in_literals.rb", 24 | "ruby_bug_11989.rb", 25 | 26 | # Some kind of issue with the end location of heredocs including newlines. 27 | "dedenting_heredoc.rb", 28 | "parser_bug_640.rb", 29 | "parser_drops_truncated_parts_of_squiggly_heredoc.rb", 30 | "slash_newline_in_heredocs.rb", 31 | 32 | # Unclear what to do here when you have nesting, it appears that the parser 33 | # gem is just skipping some levels. 34 | "masgn_nested.rb" 35 | ] 36 | 37 | # We haven't fully implemented tokenization properly yet. Most of these are 38 | # heredocs, and a couple are just random bugs. 39 | skip_tokens = [ 40 | "args.rb", 41 | "beginless_erange_after_newline.rb", 42 | "beginless_irange_after_newline.rb", 43 | "beginless_range.rb", 44 | "bug_ascii_8bit_in_literal.rb", 45 | "bug_heredoc_do.rb", 46 | "dedenting_non_interpolating_heredoc_line_continuation.rb", 47 | "forward_arg_with_open_args.rb", 48 | "heredoc.rb", 49 | "interp_digit_var.rb", 50 | "multiple_pattern_matches.rb", 51 | "ruby_bug_11990.rb", 52 | "ruby_bug_9669.rb" 53 | ] 54 | 55 | base = File.expand_path("fixtures", __dir__) 56 | Dir["*.rb", base: base].each do |filename| 57 | next if skip.include?(filename) 58 | 59 | filepath = File.join(base, filename) 60 | compare_tokens = !skip_tokens.include?(filename) 61 | 62 | define_method("test_#{filepath}") do 63 | msg = -> { 64 | buffer = Parser::Source::Buffer.new(filepath) 65 | buffer.source = File.read(filepath) 66 | 67 | <<~MSG 68 | Expected #{filepath} to parse the same as the original parser. 69 | 70 | The original parser produced the following tokens: 71 | #{PP.pp(Parser::CurrentRuby.default_parser.tokenize(buffer)[2], +"")} 72 | 73 | Prism produced the following tokens: 74 | #{PP.pp(Parser::Prism.new.tokenize(buffer)[2], +"")} 75 | MSG 76 | } 77 | 78 | assert(Parser::Prism.compare(filepath, compare_tokens: compare_tokens), msg) 79 | end 80 | end 81 | 82 | def test_visit_methods 83 | expected = Prism.constants.grep(/.Node$/).map(&:name) 84 | actual = 85 | Parser::Prism::Compiler.instance_methods(false).grep(/^visit_/).map do |method| 86 | method.name.delete_prefix("visit_").split("_").map(&:capitalize).join 87 | end 88 | 89 | assert_empty(actual - expected, -> { "Unexpected visit methods for: #{(actual - expected).join(", ")}" }) 90 | assert_empty(expected - actual, -> { "Missing visit methods for: #{(expected - actual).join(", ")}" }) 91 | end 92 | end 93 | --------------------------------------------------------------------------------