parser 2.6.0.0 → 3.1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/parser/all.rb +3 -0
- data/lib/parser/ast/processor.rb +48 -1
- data/lib/parser/base.rb +30 -6
- data/lib/parser/builders/default.rb +670 -38
- data/lib/parser/context.rb +24 -26
- data/lib/parser/current.rb +36 -9
- data/lib/parser/current_arg_stack.rb +46 -0
- data/lib/parser/diagnostic/engine.rb +1 -2
- data/lib/parser/diagnostic.rb +1 -1
- data/lib/parser/lexer/dedenter.rb +58 -49
- data/lib/parser/lexer/explanation.rb +1 -1
- data/lib/parser/lexer.rb +13837 -11893
- data/lib/parser/macruby.rb +2544 -2489
- data/lib/parser/max_numparam_stack.rb +56 -0
- data/lib/parser/messages.rb +78 -44
- data/lib/parser/meta.rb +13 -3
- data/lib/parser/ruby18.rb +2313 -2259
- data/lib/parser/ruby19.rb +2537 -2488
- data/lib/parser/ruby20.rb +2724 -2673
- data/lib/parser/ruby21.rb +2766 -2727
- data/lib/parser/ruby22.rb +2683 -2628
- data/lib/parser/ruby23.rb +2796 -2755
- data/lib/parser/ruby24.rb +2812 -2771
- data/lib/parser/ruby25.rb +2703 -2670
- data/lib/parser/ruby26.rb +2794 -2747
- data/lib/parser/ruby27.rb +7914 -0
- data/lib/parser/ruby28.rb +8047 -0
- data/lib/parser/ruby30.rb +8096 -0
- data/lib/parser/ruby31.rb +8354 -0
- data/lib/parser/rubymotion.rb +2527 -2485
- data/lib/parser/runner/ruby_parse.rb +2 -2
- data/lib/parser/runner/ruby_rewrite.rb +2 -2
- data/lib/parser/runner.rb +36 -2
- data/lib/parser/source/buffer.rb +53 -28
- data/lib/parser/source/comment/associator.rb +31 -8
- data/lib/parser/source/comment.rb +14 -1
- data/lib/parser/source/map/method_definition.rb +25 -0
- data/lib/parser/source/range.rb +19 -3
- data/lib/parser/source/tree_rewriter/action.rb +137 -28
- data/lib/parser/source/tree_rewriter.rb +144 -14
- data/lib/parser/static_environment.rb +23 -0
- data/lib/parser/tree_rewriter.rb +3 -3
- data/lib/parser/variables_stack.rb +36 -0
- data/lib/parser/version.rb +1 -1
- data/lib/parser.rb +4 -0
- data/parser.gemspec +12 -19
- metadata +34 -99
- data/.gitignore +0 -32
- data/.travis.yml +0 -45
- data/.yardopts +0 -21
- data/CHANGELOG.md +0 -943
- data/CONTRIBUTING.md +0 -17
- data/Gemfile +0 -10
- data/README.md +0 -301
- data/Rakefile +0 -165
- data/ci/run_rubocop_specs +0 -14
- data/doc/AST_FORMAT.md +0 -1735
- data/doc/CUSTOMIZATION.md +0 -37
- data/doc/INTERNALS.md +0 -21
- data/doc/css/.gitkeep +0 -0
- data/doc/css/common.css +0 -68
- data/lib/parser/lexer.rl +0 -2383
- data/lib/parser/macruby.y +0 -2198
- data/lib/parser/ruby18.y +0 -1934
- data/lib/parser/ruby19.y +0 -2175
- data/lib/parser/ruby20.y +0 -2353
- data/lib/parser/ruby21.y +0 -2357
- data/lib/parser/ruby22.y +0 -2364
- data/lib/parser/ruby23.y +0 -2370
- data/lib/parser/ruby24.y +0 -2408
- data/lib/parser/ruby25.y +0 -2405
- data/lib/parser/ruby26.y +0 -2413
- data/lib/parser/rubymotion.y +0 -2182
- data/test/bug_163/fixtures/input.rb +0 -5
- data/test/bug_163/fixtures/output.rb +0 -5
- data/test/bug_163/rewriter.rb +0 -20
- data/test/helper.rb +0 -52
- data/test/parse_helper.rb +0 -315
- data/test/racc_coverage_helper.rb +0 -133
- data/test/test_base.rb +0 -31
- data/test/test_current.rb +0 -27
- data/test/test_diagnostic.rb +0 -96
- data/test/test_diagnostic_engine.rb +0 -62
- data/test/test_encoding.rb +0 -99
- data/test/test_lexer.rb +0 -3543
- data/test/test_lexer_stack_state.rb +0 -78
- data/test/test_parse_helper.rb +0 -80
- data/test/test_parser.rb +0 -7087
- data/test/test_runner_rewrite.rb +0 -47
- data/test/test_source_buffer.rb +0 -162
- data/test/test_source_comment.rb +0 -36
- data/test/test_source_comment_associator.rb +0 -367
- data/test/test_source_map.rb +0 -15
- data/test/test_source_range.rb +0 -172
- data/test/test_source_rewriter.rb +0 -541
- data/test/test_source_rewriter_action.rb +0 -46
- data/test/test_source_tree_rewriter.rb +0 -173
- data/test/test_static_environment.rb +0 -45
- data/test/using_tree_rewriter/fixtures/input.rb +0 -3
- data/test/using_tree_rewriter/fixtures/output.rb +0 -3
- data/test/using_tree_rewriter/using_tree_rewriter.rb +0 -9
| @@ -123,7 +123,7 @@ module Parser | |
| 123 123 | 
             
                  opts.on '--emit-ruby', 'Emit S-expressions as valid Ruby code' do
         | 
| 124 124 | 
             
                    @emit_ruby = true
         | 
| 125 125 | 
             
                  end
         | 
| 126 | 
            -
             | 
| 126 | 
            +
             | 
| 127 127 | 
             
                  opts.on '--emit-json', 'Emit S-expressions as valid JSON' do
         | 
| 128 128 | 
             
                    @emit_json = true
         | 
| 129 129 | 
             
                  end
         | 
| @@ -146,7 +146,7 @@ module Parser | |
| 146 146 | 
             
                    if @emit_ruby
         | 
| 147 147 | 
             
                      puts ast.inspect
         | 
| 148 148 | 
             
                    elsif @emit_json
         | 
| 149 | 
            -
                      puts JSON.generate(ast.to_sexp_array)
         | 
| 149 | 
            +
                      puts(ast ? JSON.generate(ast.to_sexp_array) : nil)
         | 
| 150 150 | 
             
                    else
         | 
| 151 151 | 
             
                      puts ast.to_s
         | 
| 152 152 | 
             
                    end
         | 
| @@ -55,8 +55,8 @@ module Parser | |
| 55 55 | 
             
                    new_source = rewriter.rewrite(buffer, ast)
         | 
| 56 56 |  | 
| 57 57 | 
             
                    new_buffer = Source::Buffer.new(initial_buffer.name +
         | 
| 58 | 
            -
                                                '|after ' + rewriter_class.name | 
| 59 | 
            -
             | 
| 58 | 
            +
                                                '|after ' + rewriter_class.name,
         | 
| 59 | 
            +
                                                source: new_source)
         | 
| 60 60 |  | 
| 61 61 | 
             
                    @parser.reset
         | 
| 62 62 | 
             
                    new_ast = @parser.parse(new_buffer)
         | 
    
        data/lib/parser/runner.rb
    CHANGED
    
    | @@ -14,9 +14,8 @@ module Parser | |
| 14 14 | 
             
                end
         | 
| 15 15 |  | 
| 16 16 | 
             
                def initialize
         | 
| 17 | 
            -
                  Parser::Builders::Default.modernize
         | 
| 18 | 
            -
             | 
| 19 17 | 
             
                  @option_parser = OptionParser.new { |opts| setup_option_parsing(opts) }
         | 
| 18 | 
            +
                  @legacy = {}
         | 
| 20 19 | 
             
                  @parser_class  = nil
         | 
| 21 20 | 
             
                  @parser        = nil
         | 
| 22 21 | 
             
                  @files         = []
         | 
| @@ -30,6 +29,7 @@ module Parser | |
| 30 29 |  | 
| 31 30 | 
             
                def execute(options)
         | 
| 32 31 | 
             
                  parse_options(options)
         | 
| 32 | 
            +
                  setup_builder_default
         | 
| 33 33 | 
             
                  prepare_parser
         | 
| 34 34 |  | 
| 35 35 | 
             
                  process_all_input
         | 
| @@ -37,6 +37,8 @@ module Parser | |
| 37 37 |  | 
| 38 38 | 
             
                private
         | 
| 39 39 |  | 
| 40 | 
            +
                LEGACY_MODES = %i[lambda procarg0 encoding index arg_inside_procarg0 forward_arg kwargs match_pattern].freeze
         | 
| 41 | 
            +
             | 
| 40 42 | 
             
                def runner_name
         | 
| 41 43 | 
             
                  raise NotImplementedError, "implement #{self.class}##{__callee__}"
         | 
| 42 44 | 
             
                end
         | 
| @@ -106,6 +108,21 @@ module Parser | |
| 106 108 | 
             
                    @parser_class = Parser::Ruby26
         | 
| 107 109 | 
             
                  end
         | 
| 108 110 |  | 
| 111 | 
            +
                  opts.on '--27', 'Parse as Ruby 2.7 would' do
         | 
| 112 | 
            +
                    require 'parser/ruby27'
         | 
| 113 | 
            +
                    @parser_class = Parser::Ruby27
         | 
| 114 | 
            +
                  end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                  opts.on '--30', 'Parse as Ruby 3.0 would' do
         | 
| 117 | 
            +
                    require 'parser/ruby30'
         | 
| 118 | 
            +
                    @parser_class = Parser::Ruby30
         | 
| 119 | 
            +
                  end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                  opts.on '--31', 'Parse as Ruby 3.1 would' do
         | 
| 122 | 
            +
                    require 'parser/ruby31'
         | 
| 123 | 
            +
                    @parser_class = Parser::Ruby31
         | 
| 124 | 
            +
                  end
         | 
| 125 | 
            +
             | 
| 109 126 | 
             
                  opts.on '--mac', 'Parse as MacRuby 0.12 would' do
         | 
| 110 127 | 
             
                    require 'parser/macruby'
         | 
| 111 128 | 
             
                    @parser_class = Parser::MacRuby
         | 
| @@ -116,6 +133,17 @@ module Parser | |
| 116 133 | 
             
                    @parser_class = Parser::RubyMotion
         | 
| 117 134 | 
             
                  end
         | 
| 118 135 |  | 
| 136 | 
            +
                  opts.on '--legacy', "Parse with all legacy modes" do
         | 
| 137 | 
            +
                    @legacy = Hash.new(true)
         | 
| 138 | 
            +
                  end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                  LEGACY_MODES.each do |mode|
         | 
| 141 | 
            +
                    opt_name = "--legacy-#{mode.to_s.gsub('_', '-')}"
         | 
| 142 | 
            +
                    opts.on opt_name, "Parse with legacy mode for emit_#{mode}" do
         | 
| 143 | 
            +
                      @legacy[mode] = true
         | 
| 144 | 
            +
                    end
         | 
| 145 | 
            +
                  end
         | 
| 146 | 
            +
             | 
| 119 147 | 
             
                  opts.on '-w', '--warnings', 'Enable warnings' do |w|
         | 
| 120 148 | 
             
                    @warnings = w
         | 
| 121 149 | 
             
                  end
         | 
| @@ -154,6 +182,12 @@ module Parser | |
| 154 182 | 
             
                  end
         | 
| 155 183 | 
             
                end
         | 
| 156 184 |  | 
| 185 | 
            +
                def setup_builder_default
         | 
| 186 | 
            +
                  LEGACY_MODES.each do |mode|
         | 
| 187 | 
            +
                    Parser::Builders::Default.send(:"emit_#{mode}=", !@legacy[mode])
         | 
| 188 | 
            +
                  end
         | 
| 189 | 
            +
                end
         | 
| 190 | 
            +
             | 
| 157 191 | 
             
                def prepare_parser
         | 
| 158 192 | 
             
                  @parser = @parser_class.new
         | 
| 159 193 |  | 
    
        data/lib/parser/source/buffer.rb
    CHANGED
    
    | @@ -102,7 +102,7 @@ module Parser | |
| 102 102 | 
             
                    end
         | 
| 103 103 | 
             
                  end
         | 
| 104 104 |  | 
| 105 | 
            -
                  def initialize(name, first_line = 1)
         | 
| 105 | 
            +
                  def initialize(name, first_line = 1, source: nil)
         | 
| 106 106 | 
             
                    @name        = name.to_s
         | 
| 107 107 | 
             
                    @source      = nil
         | 
| 108 108 | 
             
                    @first_line  = first_line
         | 
| @@ -114,8 +114,9 @@ module Parser | |
| 114 114 | 
             
                    @slice_source = nil
         | 
| 115 115 |  | 
| 116 116 | 
             
                    # Cache for fast lookup
         | 
| 117 | 
            -
                    @ | 
| 118 | 
            -
             | 
| 117 | 
            +
                    @line_index_for_position = {}
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                    self.source = source if source
         | 
| 119 120 | 
             
                  end
         | 
| 120 121 |  | 
| 121 122 | 
             
                  ##
         | 
| @@ -205,9 +206,10 @@ module Parser | |
| 205 206 | 
             
                  # @return [[Integer, Integer]] `[line, column]`
         | 
| 206 207 | 
             
                  #
         | 
| 207 208 | 
             
                  def decompose_position(position)
         | 
| 208 | 
            -
                     | 
| 209 | 
            +
                    line_index = line_index_for_position(position)
         | 
| 210 | 
            +
                    line_begin = line_begins[line_index]
         | 
| 209 211 |  | 
| 210 | 
            -
                    [ @first_line +  | 
| 212 | 
            +
                    [ @first_line + line_index , position - line_begin ]
         | 
| 211 213 | 
             
                  end
         | 
| 212 214 |  | 
| 213 215 | 
             
                  ##
         | 
| @@ -218,10 +220,7 @@ module Parser | |
| 218 220 | 
             
                  # @api private
         | 
| 219 221 | 
             
                  #
         | 
| 220 222 | 
             
                  def line_for_position(position)
         | 
| 221 | 
            -
                     | 
| 222 | 
            -
                      line_no, _ = line_for(position)
         | 
| 223 | 
            -
                      @first_line + line_no
         | 
| 224 | 
            -
                    end
         | 
| 223 | 
            +
                    line_index_for_position(position) + @first_line
         | 
| 225 224 | 
             
                  end
         | 
| 226 225 |  | 
| 227 226 | 
             
                  ##
         | 
| @@ -232,10 +231,8 @@ module Parser | |
| 232 231 | 
             
                  # @api private
         | 
| 233 232 | 
             
                  #
         | 
| 234 233 | 
             
                  def column_for_position(position)
         | 
| 235 | 
            -
                     | 
| 236 | 
            -
             | 
| 237 | 
            -
                      position - line_begin
         | 
| 238 | 
            -
                    end
         | 
| 234 | 
            +
                    line_index = line_index_for_position(position)
         | 
| 235 | 
            +
                    position - line_begins[line_index]
         | 
| 239 236 | 
             
                  end
         | 
| 240 237 |  | 
| 241 238 | 
             
                  ##
         | 
| @@ -276,15 +273,13 @@ module Parser | |
| 276 273 | 
             
                  # @raise  [IndexError] if `lineno` is out of bounds
         | 
| 277 274 | 
             
                  #
         | 
| 278 275 | 
             
                  def line_range(lineno)
         | 
| 279 | 
            -
                    index = lineno - @first_line | 
| 280 | 
            -
                    if index  | 
| 276 | 
            +
                    index = lineno - @first_line
         | 
| 277 | 
            +
                    if index < 0 || index + 1 >= line_begins.size
         | 
| 281 278 | 
             
                      raise IndexError, 'Parser::Source::Buffer: range for line ' \
         | 
| 282 279 | 
             
                        "#{lineno} requested, valid line numbers are #{@first_line}.." \
         | 
| 283 | 
            -
                        "#{@first_line + line_begins.size -  | 
| 284 | 
            -
                    elsif index == line_begins.size
         | 
| 285 | 
            -
                      Range.new(self, line_begins[-index][1], @source.size)
         | 
| 280 | 
            +
                        "#{@first_line + line_begins.size - 2}"
         | 
| 286 281 | 
             
                    else
         | 
| 287 | 
            -
                      Range.new(self, line_begins[ | 
| 282 | 
            +
                      Range.new(self, line_begins[index], line_begins[index + 1] - 1)
         | 
| 288 283 | 
             
                    end
         | 
| 289 284 | 
             
                  end
         | 
| 290 285 |  | 
| @@ -301,27 +296,57 @@ module Parser | |
| 301 296 | 
             
                  # @return [Integer]
         | 
| 302 297 | 
             
                  #
         | 
| 303 298 | 
             
                  def last_line
         | 
| 304 | 
            -
                    line_begins.size + @first_line -  | 
| 299 | 
            +
                    line_begins.size + @first_line - 2
         | 
| 300 | 
            +
                  end
         | 
| 301 | 
            +
             | 
| 302 | 
            +
                  # :nodoc:
         | 
| 303 | 
            +
                  def freeze
         | 
| 304 | 
            +
                    source_lines; line_begins; source_range # build cache
         | 
| 305 | 
            +
                    super
         | 
| 306 | 
            +
                  end
         | 
| 307 | 
            +
             | 
| 308 | 
            +
                  # :nodoc:
         | 
| 309 | 
            +
                  def inspect
         | 
| 310 | 
            +
                    "#<#{self.class} #{name}>"
         | 
| 305 311 | 
             
                  end
         | 
| 306 312 |  | 
| 307 313 | 
             
                  private
         | 
| 308 314 |  | 
| 315 | 
            +
                  # @returns [0, line_begin_of_line_1, ..., source.size + 1]
         | 
| 309 316 | 
             
                  def line_begins
         | 
| 310 | 
            -
                     | 
| 311 | 
            -
                       | 
| 312 | 
            -
             | 
| 317 | 
            +
                    @line_begins ||= begin
         | 
| 318 | 
            +
                      begins = [0]
         | 
| 319 | 
            +
                      index = 0
         | 
| 313 320 | 
             
                      while index = @source.index("\n".freeze, index)
         | 
| 314 321 | 
             
                        index += 1
         | 
| 315 | 
            -
                         | 
| 322 | 
            +
                        begins << index
         | 
| 316 323 | 
             
                      end
         | 
| 324 | 
            +
                      begins << @source.size + 1
         | 
| 325 | 
            +
                      begins
         | 
| 317 326 | 
             
                    end
         | 
| 327 | 
            +
                  end
         | 
| 318 328 |  | 
| 319 | 
            -
             | 
| 329 | 
            +
                  # @returns 0-based line index of position
         | 
| 330 | 
            +
                  def line_index_for_position(position)
         | 
| 331 | 
            +
                    @line_index_for_position[position] || begin
         | 
| 332 | 
            +
                      index = bsearch(line_begins, position) - 1
         | 
| 333 | 
            +
                      @line_index_for_position[position] = index unless @line_index_for_position.frozen?
         | 
| 334 | 
            +
                      index
         | 
| 335 | 
            +
                    end
         | 
| 320 336 | 
             
                  end
         | 
| 321 337 |  | 
| 322 | 
            -
                   | 
| 323 | 
            -
                    line_begins | 
| 324 | 
            -
                       | 
| 338 | 
            +
                  if Array.method_defined?(:bsearch_index) # RUBY_VERSION >= 2.3
         | 
| 339 | 
            +
                    def bsearch(line_begins, position)
         | 
| 340 | 
            +
                      line_begins.bsearch_index do |line_begin|
         | 
| 341 | 
            +
                        position < line_begin
         | 
| 342 | 
            +
                      end || line_begins.size - 1 # || only for out of bound values
         | 
| 343 | 
            +
                    end
         | 
| 344 | 
            +
                  else
         | 
| 345 | 
            +
                    def bsearch(line_begins, position)
         | 
| 346 | 
            +
                      @line_range ||= 0...line_begins.size
         | 
| 347 | 
            +
                      @line_range.bsearch do |i|
         | 
| 348 | 
            +
                        position < line_begins[i]
         | 
| 349 | 
            +
                      end || line_begins.size - 1 # || only for out of bound values
         | 
| 325 350 | 
             
                    end
         | 
| 326 351 | 
             
                  end
         | 
| 327 352 | 
             
                end
         | 
| @@ -84,12 +84,24 @@ module Parser | |
| 84 84 | 
             
                  #
         | 
| 85 85 | 
             
                  # Note that {associate} produces unexpected result for nodes which are
         | 
| 86 86 | 
             
                  # equal but have distinct locations; comments for these nodes are merged.
         | 
| 87 | 
            +
                  # You may prefer using {associate_by_identity} or {associate_locations}.
         | 
| 87 88 | 
             
                  #
         | 
| 88 89 | 
             
                  # @return [Hash<Parser::AST::Node, Array<Parser::Source::Comment>>]
         | 
| 89 90 | 
             
                  # @deprecated Use {associate_locations}.
         | 
| 90 91 | 
             
                  #
         | 
| 91 92 | 
             
                  def associate
         | 
| 92 | 
            -
                    @ | 
| 93 | 
            +
                    @map_using = :eql
         | 
| 94 | 
            +
                    do_associate
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  ##
         | 
| 98 | 
            +
                  # Same as {associate}, but compares by identity, thus producing an unambiguous
         | 
| 99 | 
            +
                  # result even in presence of equal nodes.
         | 
| 100 | 
            +
                  #
         | 
| 101 | 
            +
                  # @return [Hash<Parser::Source::Node, Array<Parser::Source::Comment>>]
         | 
| 102 | 
            +
                  #
         | 
| 103 | 
            +
                  def associate_locations
         | 
| 104 | 
            +
                    @map_using = :location
         | 
| 93 105 | 
             
                    do_associate
         | 
| 94 106 | 
             
                  end
         | 
| 95 107 |  | 
| @@ -100,15 +112,29 @@ module Parser | |
| 100 112 | 
             
                  #
         | 
| 101 113 | 
             
                  # @return [Hash<Parser::Source::Map, Array<Parser::Source::Comment>>]
         | 
| 102 114 | 
             
                  #
         | 
| 103 | 
            -
                  def  | 
| 104 | 
            -
                    @ | 
| 115 | 
            +
                  def associate_by_identity
         | 
| 116 | 
            +
                    @map_using = :identity
         | 
| 105 117 | 
             
                    do_associate
         | 
| 106 118 | 
             
                  end
         | 
| 107 119 |  | 
| 108 120 | 
             
                  private
         | 
| 109 121 |  | 
| 122 | 
            +
                  POSTFIX_TYPES = Set[:if, :while, :while_post, :until, :until_post, :masgn].freeze
         | 
| 123 | 
            +
                  def children_in_source_order(node)
         | 
| 124 | 
            +
                    if POSTFIX_TYPES.include?(node.type)
         | 
| 125 | 
            +
                      # All these types have either nodes with expressions, or `nil`
         | 
| 126 | 
            +
                      # so a compact will do, but they need to be sorted.
         | 
| 127 | 
            +
                      node.children.compact.sort_by { |child| child.loc.expression.begin_pos }
         | 
| 128 | 
            +
                    else
         | 
| 129 | 
            +
                      node.children.select do |child|
         | 
| 130 | 
            +
                        child.is_a?(AST::Node) && child.loc && child.loc.expression
         | 
| 131 | 
            +
                      end
         | 
| 132 | 
            +
                    end
         | 
| 133 | 
            +
                  end
         | 
| 134 | 
            +
             | 
| 110 135 | 
             
                  def do_associate
         | 
| 111 136 | 
             
                    @mapping     = Hash.new { |h, k| h[k] = [] }
         | 
| 137 | 
            +
                    @mapping.compare_by_identity if @map_using == :identity
         | 
| 112 138 | 
             
                    @comment_num = -1
         | 
| 113 139 | 
             
                    advance_comment
         | 
| 114 140 |  | 
| @@ -131,10 +157,7 @@ module Parser | |
| 131 157 | 
             
                    node_loc = node.location
         | 
| 132 158 | 
             
                    if @current_comment.location.line <= node_loc.last_line ||
         | 
| 133 159 | 
             
                       node_loc.is_a?(Map::Heredoc)
         | 
| 134 | 
            -
                      node. | 
| 135 | 
            -
                        next unless child.is_a?(AST::Node) && child.loc && child.loc.expression
         | 
| 136 | 
            -
                        visit(child)
         | 
| 137 | 
            -
                      end
         | 
| 160 | 
            +
                      children_in_source_order(node).each { |child| visit(child) }
         | 
| 138 161 |  | 
| 139 162 | 
             
                      process_trailing_comments(node)
         | 
| 140 163 | 
             
                    end
         | 
| @@ -181,7 +204,7 @@ module Parser | |
| 181 204 | 
             
                  end
         | 
| 182 205 |  | 
| 183 206 | 
             
                  def associate_and_advance_comment(node)
         | 
| 184 | 
            -
                    key = @ | 
| 207 | 
            +
                    key = @map_using == :location ? node.location : node
         | 
| 185 208 | 
             
                    @mapping[key] << @current_comment
         | 
| 186 209 | 
             
                    advance_comment
         | 
| 187 210 | 
             
                  end
         | 
| @@ -10,7 +10,7 @@ module Parser | |
| 10 10 | 
             
                #  @return [String]
         | 
| 11 11 | 
             
                #
         | 
| 12 12 | 
             
                # @!attribute [r] location
         | 
| 13 | 
            -
                #  @return [Parser::Source:: | 
| 13 | 
            +
                #  @return [Parser::Source::Range]
         | 
| 14 14 | 
             
                #
         | 
| 15 15 | 
             
                # @api public
         | 
| 16 16 | 
             
                #
         | 
| @@ -48,6 +48,19 @@ module Parser | |
| 48 48 | 
             
                    associator.associate_locations
         | 
| 49 49 | 
             
                  end
         | 
| 50 50 |  | 
| 51 | 
            +
                  ##
         | 
| 52 | 
            +
                  # Associate `comments` with `ast` nodes using identity.
         | 
| 53 | 
            +
                  #
         | 
| 54 | 
            +
                  # @param [Parser::AST::Node] ast
         | 
| 55 | 
            +
                  # @param [Array<Comment>]    comments
         | 
| 56 | 
            +
                  # @return [Hash<Parser::Source::Node, Array<Comment>>]
         | 
| 57 | 
            +
                  # @see Parser::Source::Comment::Associator#associate_by_identity
         | 
| 58 | 
            +
                  #
         | 
| 59 | 
            +
                  def self.associate_by_identity(ast, comments)
         | 
| 60 | 
            +
                    associator = Associator.new(ast, comments)
         | 
| 61 | 
            +
                    associator.associate_by_identity
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 51 64 | 
             
                  ##
         | 
| 52 65 | 
             
                  # @param [Parser::Source::Range] range
         | 
| 53 66 | 
             
                  #
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Parser
         | 
| 4 | 
            +
              module Source
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                class Map::MethodDefinition < Map
         | 
| 7 | 
            +
                  attr_reader :keyword
         | 
| 8 | 
            +
                  attr_reader :operator
         | 
| 9 | 
            +
                  attr_reader :name
         | 
| 10 | 
            +
                  attr_reader :end
         | 
| 11 | 
            +
                  attr_reader :assignment
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def initialize(keyword_l, operator_l, name_l, end_l, assignment_l, body_l)
         | 
| 14 | 
            +
                    @keyword  = keyword_l
         | 
| 15 | 
            +
                    @operator = operator_l
         | 
| 16 | 
            +
                    @name     = name_l
         | 
| 17 | 
            +
                    @end      = end_l
         | 
| 18 | 
            +
                    @assignment = assignment_l
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    super(@keyword.join(end_l || body_l))
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
    
        data/lib/parser/source/range.rb
    CHANGED
    
    | @@ -13,7 +13,7 @@ module Parser | |
| 13 13 | 
             
                #        ^^
         | 
| 14 14 | 
             
                #
         | 
| 15 15 | 
             
                # @!attribute [r] source_buffer
         | 
| 16 | 
            -
                #  @return [Parser:: | 
| 16 | 
            +
                #  @return [Parser::Source::Buffer]
         | 
| 17 17 | 
             
                #
         | 
| 18 18 | 
             
                # @!attribute [r] begin_pos
         | 
| 19 19 | 
             
                #  @return [Integer] index of the first character in the range
         | 
| @@ -112,11 +112,11 @@ module Parser | |
| 112 112 | 
             
                  # @raise RangeError
         | 
| 113 113 | 
             
                  #
         | 
| 114 114 | 
             
                  def column_range
         | 
| 115 | 
            -
                    if  | 
| 115 | 
            +
                    if line != last_line
         | 
| 116 116 | 
             
                      raise RangeError, "#{self.inspect} spans more than one line"
         | 
| 117 117 | 
             
                    end
         | 
| 118 118 |  | 
| 119 | 
            -
                     | 
| 119 | 
            +
                    column...last_column
         | 
| 120 120 | 
             
                  end
         | 
| 121 121 |  | 
| 122 122 | 
             
                  ##
         | 
| @@ -149,6 +149,13 @@ module Parser | |
| 149 149 | 
             
                    (@begin_pos...@end_pos).to_a
         | 
| 150 150 | 
             
                  end
         | 
| 151 151 |  | 
| 152 | 
            +
                  ##
         | 
| 153 | 
            +
                  # @return [Range] a Ruby range with the same `begin_pos` and `end_pos`
         | 
| 154 | 
            +
                  #
         | 
| 155 | 
            +
                  def to_range
         | 
| 156 | 
            +
                    self.begin_pos...self.end_pos
         | 
| 157 | 
            +
                  end
         | 
| 158 | 
            +
             | 
| 152 159 | 
             
                  ##
         | 
| 153 160 | 
             
                  # Composes a GNU/Clang-style string representation of the beginning of this
         | 
| 154 161 | 
             
                  # range.
         | 
| @@ -298,6 +305,15 @@ module Parser | |
| 298 305 | 
             
                    (@end_pos <=> other.end_pos)
         | 
| 299 306 | 
             
                  end
         | 
| 300 307 |  | 
| 308 | 
            +
                  alias_method :eql?, :==
         | 
| 309 | 
            +
             | 
| 310 | 
            +
                  ##
         | 
| 311 | 
            +
                  # Support for Ranges be used in as Hash indices and in Sets.
         | 
| 312 | 
            +
                  #
         | 
| 313 | 
            +
                  def hash
         | 
| 314 | 
            +
                    [@source_buffer, @begin_pos, @end_pos].hash
         | 
| 315 | 
            +
                  end
         | 
| 316 | 
            +
             | 
| 301 317 | 
             
                  ##
         | 
| 302 318 | 
             
                  # @return [String] a human-readable representation of this range.
         | 
| 303 319 | 
             
                  #
         | 
| @@ -7,7 +7,7 @@ module Parser | |
| 7 7 | 
             
                #
         | 
| 8 8 | 
             
                # Actions are arranged in a tree and get combined so that:
         | 
| 9 9 | 
             
                #   children are strictly contained by their parent
         | 
| 10 | 
            -
                #   sibblings all disjoint from one another
         | 
| 10 | 
            +
                #   sibblings all disjoint from one another and ordered
         | 
| 11 11 | 
             
                #   only actions with replacement==nil may have children
         | 
| 12 12 | 
             
                #
         | 
| 13 13 | 
             
                class TreeRewriter::Action
         | 
| @@ -25,30 +25,77 @@ module Parser | |
| 25 25 | 
             
                    freeze
         | 
| 26 26 | 
             
                  end
         | 
| 27 27 |  | 
| 28 | 
            -
                  # Assumes action.children.empty?
         | 
| 29 28 | 
             
                  def combine(action)
         | 
| 30 | 
            -
                    return self  | 
| 29 | 
            +
                    return self if action.empty? # Ignore empty action
         | 
| 31 30 | 
             
                    do_combine(action)
         | 
| 32 31 | 
             
                  end
         | 
| 33 32 |  | 
| 33 | 
            +
                  def empty?
         | 
| 34 | 
            +
                    @insert_before.empty? &&
         | 
| 35 | 
            +
                      @insert_after.empty? &&
         | 
| 36 | 
            +
                      @children.empty? &&
         | 
| 37 | 
            +
                      (@replacement == nil || (@replacement.empty? && @range.empty?))
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 34 40 | 
             
                  def ordered_replacements
         | 
| 35 41 | 
             
                    reps = []
         | 
| 36 42 | 
             
                    reps << [@range.begin, @insert_before] unless @insert_before.empty?
         | 
| 37 43 | 
             
                    reps << [@range, @replacement] if @replacement
         | 
| 38 | 
            -
                    reps.concat(@children. | 
| 44 | 
            +
                    reps.concat(@children.flat_map(&:ordered_replacements))
         | 
| 39 45 | 
             
                    reps << [@range.end, @insert_after] unless @insert_after.empty?
         | 
| 40 46 | 
             
                    reps
         | 
| 41 47 | 
             
                  end
         | 
| 42 48 |  | 
| 49 | 
            +
                  def nested_actions
         | 
| 50 | 
            +
                    actions = []
         | 
| 51 | 
            +
                    actions << [:wrap, @range, @insert_before, @insert_after] if !@insert_before.empty? ||
         | 
| 52 | 
            +
                                                                                 !@insert_after.empty?
         | 
| 53 | 
            +
                    actions << [:replace, @range, @replacement] if @replacement
         | 
| 54 | 
            +
                    actions.concat(@children.flat_map(&:nested_actions))
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 43 57 | 
             
                  def insertion?
         | 
| 44 58 | 
             
                    !insert_before.empty? || !insert_after.empty? || (replacement && !replacement.empty?)
         | 
| 45 59 | 
             
                  end
         | 
| 46 60 |  | 
| 61 | 
            +
                  ##
         | 
| 62 | 
            +
                  # A root action has its range set to the whole source range, even
         | 
| 63 | 
            +
                  # though it typically do not act on that range.
         | 
| 64 | 
            +
                  # This method returns the action as if it was a child action with
         | 
| 65 | 
            +
                  # its range contracted.
         | 
| 66 | 
            +
                  # @return [Action]
         | 
| 67 | 
            +
                  def contract
         | 
| 68 | 
            +
                    raise 'Empty actions can not be contracted' if empty?
         | 
| 69 | 
            +
                    return self if insertion?
         | 
| 70 | 
            +
                    range = @range.with(
         | 
| 71 | 
            +
                      begin_pos: children.first.range.begin_pos,
         | 
| 72 | 
            +
                      end_pos: children.last.range.end_pos,
         | 
| 73 | 
            +
                    )
         | 
| 74 | 
            +
                    with(range: range)
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                  ##
         | 
| 78 | 
            +
                  # @return [Action] that has been moved to the given source_buffer and with the given offset
         | 
| 79 | 
            +
                  # No check is done on validity of resulting range.
         | 
| 80 | 
            +
                  def moved(source_buffer, offset)
         | 
| 81 | 
            +
                    moved_range = ::Parser::Source::Range.new(
         | 
| 82 | 
            +
                      source_buffer,
         | 
| 83 | 
            +
                      @range.begin_pos + offset,
         | 
| 84 | 
            +
                      @range.end_pos + offset
         | 
| 85 | 
            +
                    )
         | 
| 86 | 
            +
                    with(
         | 
| 87 | 
            +
                      range: moved_range,
         | 
| 88 | 
            +
                      children: children.map { |child| child.moved(source_buffer, offset) }
         | 
| 89 | 
            +
                    )
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
             | 
| 47 92 | 
             
                  protected
         | 
| 48 93 |  | 
| 49 | 
            -
                   | 
| 94 | 
            +
                  attr_reader :children
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                  def with(range: @range, enforcer: @enforcer, children: @children, insert_before: @insert_before, replacement: @replacement, insert_after: @insert_after)
         | 
| 50 97 | 
             
                    children = swallow(children) if replacement
         | 
| 51 | 
            -
                    self.class.new(range,  | 
| 98 | 
            +
                    self.class.new(range, enforcer, children: children, insert_before: insert_before, replacement: replacement, insert_after: insert_after)
         | 
| 52 99 | 
             
                  end
         | 
| 53 100 |  | 
| 54 101 | 
             
                  # Assumes range.contains?(action.range) && action.children.empty?
         | 
| @@ -56,24 +103,32 @@ module Parser | |
| 56 103 | 
             
                    if action.range == @range
         | 
| 57 104 | 
             
                      merge(action)
         | 
| 58 105 | 
             
                    else
         | 
| 59 | 
            -
                       | 
| 106 | 
            +
                      place_in_hierarchy(action)
         | 
| 60 107 | 
             
                    end
         | 
| 61 108 | 
             
                  end
         | 
| 62 109 |  | 
| 63 | 
            -
                  def  | 
| 64 | 
            -
                    family =  | 
| 110 | 
            +
                  def place_in_hierarchy(action)
         | 
| 111 | 
            +
                    family = analyse_hierarchy(action)
         | 
| 65 112 |  | 
| 66 113 | 
             
                    if family[:fusible]
         | 
| 67 | 
            -
                      fuse_deletions(action, family[:fusible], [*family[: | 
| 114 | 
            +
                      fuse_deletions(action, family[:fusible], [*family[:sibbling_left], *family[:child], *family[:sibbling_right]])
         | 
| 68 115 | 
             
                    else
         | 
| 69 116 | 
             
                      extra_sibbling = if family[:parent]  # action should be a descendant of one of the children
         | 
| 70 | 
            -
                        family[:parent] | 
| 117 | 
            +
                        family[:parent].do_combine(action)
         | 
| 71 118 | 
             
                      elsif family[:child]                 # or it should become the parent of some of the children,
         | 
| 72 | 
            -
                        action.with(children: family[:child])
         | 
| 119 | 
            +
                        action.with(children: family[:child], enforcer: @enforcer)
         | 
| 120 | 
            +
                          .combine_children(action.children)
         | 
| 73 121 | 
             
                      else                                 # or else it should become an additional child
         | 
| 74 122 | 
             
                        action
         | 
| 75 123 | 
             
                      end
         | 
| 76 | 
            -
                      with(children: [*family[: | 
| 124 | 
            +
                      with(children: [*family[:sibbling_left], extra_sibbling, *family[:sibbling_right]])
         | 
| 125 | 
            +
                    end
         | 
| 126 | 
            +
                  end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                  # Assumes `more_children` all contained within `@range`
         | 
| 129 | 
            +
                  def combine_children(more_children)
         | 
| 130 | 
            +
                    more_children.inject(self) do |parent, new_child|
         | 
| 131 | 
            +
                      parent.place_in_hierarchy(new_child)
         | 
| 77 132 | 
             
                    end
         | 
| 78 133 | 
             
                  end
         | 
| 79 134 |  | 
| @@ -84,22 +139,76 @@ module Parser | |
| 84 139 | 
             
                    without_fusible.do_combine(fused_deletion)
         | 
| 85 140 | 
             
                  end
         | 
| 86 141 |  | 
| 87 | 
            -
                  #  | 
| 88 | 
            -
                  # | 
| 89 | 
            -
                  #  | 
| 90 | 
            -
                  def  | 
| 91 | 
            -
                     | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 142 | 
            +
                  # Similar to @children.bsearch_index || size
         | 
| 143 | 
            +
                  # except allows for a starting point
         | 
| 144 | 
            +
                  # and `bsearch_index` is only Ruby 2.3+
         | 
| 145 | 
            +
                  def bsearch_child_index(from = 0)
         | 
| 146 | 
            +
                    size = @children.size
         | 
| 147 | 
            +
                    (from...size).bsearch { |i| yield @children[i] } || size
         | 
| 148 | 
            +
                  end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                  # Returns the children in a hierarchy with respect to `action`:
         | 
| 151 | 
            +
                  #   :sibbling_left, sibbling_right (for those that are disjoint from `action`)
         | 
| 152 | 
            +
                  #   :parent (in case one of our children contains `action`)
         | 
| 153 | 
            +
                  #   :child (in case `action` strictly contains some of our children)
         | 
| 154 | 
            +
                  #   :fusible (in case `action` overlaps some children but they can be fused in one deletion)
         | 
| 155 | 
            +
                  #   or raises a `CloberingError`
         | 
| 156 | 
            +
                  # In case a child has equal range to `action`, it is returned as `:parent`
         | 
| 157 | 
            +
                  # Reminder: an empty range 1...1 is considered disjoint from 1...10
         | 
| 158 | 
            +
                  def analyse_hierarchy(action)
         | 
| 159 | 
            +
                    r = action.range
         | 
| 160 | 
            +
                    # left_index is the index of the first child that isn't completely to the left of action
         | 
| 161 | 
            +
                    left_index = bsearch_child_index { |child| child.range.end_pos > r.begin_pos }
         | 
| 162 | 
            +
                    # right_index is the index of the first child that is completely on the right of action
         | 
| 163 | 
            +
                    start = left_index == 0 ? 0 : left_index - 1  # See "corner case" below for reason of -1
         | 
| 164 | 
            +
                    right_index = bsearch_child_index(start) { |child| child.range.begin_pos >= r.end_pos }
         | 
| 165 | 
            +
                    center = right_index - left_index
         | 
| 166 | 
            +
                    case center
         | 
| 167 | 
            +
                    when 0
         | 
| 168 | 
            +
                      # All children are disjoint from action, nothing else to do
         | 
| 169 | 
            +
                    when -1
         | 
| 170 | 
            +
                      # Corner case: if a child has empty range == action's range
         | 
| 171 | 
            +
                      # then it will appear to be both disjoint and to the left of action,
         | 
| 172 | 
            +
                      # as well as disjoint and to the right of action.
         | 
| 173 | 
            +
                      # Since ranges are equal, we return it as parent
         | 
| 174 | 
            +
                      left_index -= 1  # Fix indices, as otherwise this child would be
         | 
| 175 | 
            +
                      right_index += 1 # considered as a sibbling (both left and right!)
         | 
| 176 | 
            +
                      parent = @children[left_index]
         | 
| 100 177 | 
             
                    else
         | 
| 101 | 
            -
                      @ | 
| 178 | 
            +
                      overlap_left = @children[left_index].range.begin_pos <=> r.begin_pos
         | 
| 179 | 
            +
                      overlap_right = @children[right_index-1].range.end_pos <=> r.end_pos
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                      # For one child to be the parent of action, we must have:
         | 
| 182 | 
            +
                      if center == 1 && overlap_left <= 0 && overlap_right >= 0
         | 
| 183 | 
            +
                        parent = @children[left_index]
         | 
| 184 | 
            +
                      else
         | 
| 185 | 
            +
                        # Otherwise consider all non disjoint elements (center) to be contained...
         | 
| 186 | 
            +
                        contained = @children[left_index...right_index]
         | 
| 187 | 
            +
                        fusible = check_fusible(action,
         | 
| 188 | 
            +
                          (contained.shift if overlap_left < 0),  # ... but check first and last one
         | 
| 189 | 
            +
                          (contained.pop if overlap_right > 0)    # ... for overlaps
         | 
| 190 | 
            +
                        )
         | 
| 191 | 
            +
                      end
         | 
| 192 | 
            +
                    end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                    {
         | 
| 195 | 
            +
                      parent: parent,
         | 
| 196 | 
            +
                      sibbling_left: @children[0...left_index],
         | 
| 197 | 
            +
                      sibbling_right: @children[right_index...@children.size],
         | 
| 198 | 
            +
                      fusible: fusible,
         | 
| 199 | 
            +
                      child: contained,
         | 
| 200 | 
            +
                    }
         | 
| 201 | 
            +
                  end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                  # @param [Array(Action | nil)] fusible
         | 
| 204 | 
            +
                  def check_fusible(action, *fusible)
         | 
| 205 | 
            +
                    fusible.compact!
         | 
| 206 | 
            +
                    return if fusible.empty?
         | 
| 207 | 
            +
                    fusible.each do |child|
         | 
| 208 | 
            +
                      kind = action.insertion? || child.insertion? ? :crossing_insertions : :crossing_deletions
         | 
| 209 | 
            +
                      @enforcer.call(kind) { {range: action.range, conflict: child.range} }
         | 
| 102 210 | 
             
                    end
         | 
| 211 | 
            +
                    fusible
         | 
| 103 212 | 
             
                  end
         | 
| 104 213 |  | 
| 105 214 | 
             
                  # Assumes action.range == range && action.children.empty?
         | 
| @@ -109,7 +218,7 @@ module Parser | |
| 109 218 | 
             
                      insert_before: "#{action.insert_before}#{insert_before}",
         | 
| 110 219 | 
             
                      replacement: action.replacement || @replacement,
         | 
| 111 220 | 
             
                      insert_after: "#{insert_after}#{action.insert_after}",
         | 
| 112 | 
            -
                    )
         | 
| 221 | 
            +
                    ).combine_children(action.children)
         | 
| 113 222 | 
             
                  end
         | 
| 114 223 |  | 
| 115 224 | 
             
                  def call_enforcer_for_merge(action)
         |