rubocop/rubocop-ast

`ProcessedSource#ast_with_comments` doesn't differentiate between identical nodes

dvandersluis opened this issue · 4 comments

This spec passes but is strange behaviour:

RSpec.describe RuboCop::AST::ProcessedSource do
  describe '#ast_with_comments' do
    include RuboCop::AST::Sexp

    let(:source) do
      <<~RUBY
        # comment 1
        do_a
        # comment 2
        do_b
        # comment 3
        do_a
      RUBY
    end

    it do
      expect(processed_source.ast_with_comments).to include(
        s(:send, nil, :do_a) => [instance_of(Parser::Source::Comment), instance_of(Parser::Source::Comment)],
        s(:send, nil, :do_b) => [instance_of(Parser::Source::Comment)]
      )
    end
  end
end

Here is the actual hash created:

{
  s(:send, nil, :do_a) => [
    #<Parser::Source::Comment ast/and_node_spec.rb:1:1 "# comment 1">,
    #<Parser::Source::Comment ast/and_node_spec.rb:5:1 "# comment 3">
  ],
  s(:send, nil, :do_b) => [
    #<Parser::Source::Comment ast/and_node_spec.rb:3:1 "# comment 2">
  ]
}

Because there are two s(:send, nil, :do_a) nodes, both comments are associated with the same node in the hash, and then when this is used in rubocop (for instance CommentsHelp.begin_pos_with_comment), it returns potentially the wrong comment node.

I don't know that there is a good way to fix this necessarily without changing how the keys are constructed; this might also not be a RuboCop::AST bug directly either.

Indeed, this is surprising 😅

This was noted in whitequark/parser#184: "Note that {associate} produces unexpected result for nodes which are equal but have distinct locations; comments for these nodes are merged."

The proposed fix was associate_locations that does the same but uses the location as key.

IMO, a better method than either of them would be associate_by_identity, which does not use eql? but equal?. I'm proposing a PR for this: whitequark/parser#798

I believe that we could modify our ast_with_comments to use that without any incompatibility, but I didn't double check yet.

As I expected, tests pass on master.
Released in 1.4.2 👍

Released 1.5.0...

Thanks for the quick work @marcandre! 🎉