diff --git a/CHANGELOG.md b/CHANGELOG.md index dc5dc7d5..46f47ec9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [4.0.2] - 2022-10-19 + +### Changed + +- [#177](https://github.com/ruby-syntax-tree/syntax_tree/pull/177) - Fix up various other issues with the environment visitor addition. + ## [4.0.1] - 2022-10-18 ### Changed @@ -391,7 +397,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - 🎉 Initial release! 🎉 -[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v4.0.0...HEAD +[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v4.0.2...HEAD +[4.0.2]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v4.0.1...v4.0.2 +[4.0.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v4.0.0...v4.0.1 [4.0.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.6.3...v4.0.0 [3.6.3]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.6.2...v3.6.3 [3.6.2]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.6.1...v3.6.2 diff --git a/Gemfile.lock b/Gemfile.lock index f55c7768..abe983b2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,8 +1,8 @@ PATH remote: . specs: - syntax_tree (4.0.1) - prettier_print (>= 1.0.1) + syntax_tree (4.0.2) + prettier_print (>= 1.0.2) GEM remote: https://rubygems.org/ @@ -14,7 +14,7 @@ GEM parallel (1.22.1) parser (3.1.2.1) ast (~> 2.4.1) - prettier_print (1.0.1) + prettier_print (1.0.2) rainbow (3.1.1) rake (13.0.6) regexp_parser (2.6.0) diff --git a/lib/syntax_tree/version.rb b/lib/syntax_tree/version.rb index 695f4c88..98d461df 100644 --- a/lib/syntax_tree/version.rb +++ b/lib/syntax_tree/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SyntaxTree - VERSION = "4.0.1" + VERSION = "4.0.2" end diff --git a/lib/syntax_tree/visitor/with_environment.rb b/lib/syntax_tree/visitor/with_environment.rb index ad101e0a..043cbd4c 100644 --- a/lib/syntax_tree/visitor/with_environment.rb +++ b/lib/syntax_tree/visitor/with_environment.rb @@ -44,8 +44,12 @@ def visit_module(node) with_new_environment { super } end + # When we find a method invocation with a block, only the code that happens + # inside of the block needs a fresh environment. The method invocation + # itself happens in the same environment def visit_method_add_block(node) - with_new_environment { super } + visit(node.call) + with_new_environment { visit(node.block) } end def visit_def(node) @@ -63,9 +67,7 @@ def visit_def_endless(node) # Visit for keeping track of local arguments, such as method and block # arguments def visit_params(node) - node.requireds.each do |param| - current_environment.add_local_definition(param, :argument) - end + add_argument_definitions(node.requireds) node.posts.each do |param| current_environment.add_local_definition(param, :argument) @@ -117,13 +119,6 @@ def visit_var_field(node) alias visit_pinned_var_ref visit_var_field # Visits for keeping track of variable and argument usages - def visit_aref_field(node) - name = node.collection.value - current_environment.add_local_usage(name, :variable) if name - - super - end - def visit_var_ref(node) value = node.value @@ -137,5 +132,17 @@ def visit_var_ref(node) super end + + private + + def add_argument_definitions(list) + list.each do |param| + if param.is_a?(SyntaxTree::MLHSParen) + add_argument_definitions(param.contents.parts) + else + current_environment.add_local_definition(param, :argument) + end + end + end end end diff --git a/syntax_tree.gemspec b/syntax_tree.gemspec index fe74c7e7..c82a8e98 100644 --- a/syntax_tree.gemspec +++ b/syntax_tree.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = %w[lib] - spec.add_dependency "prettier_print", ">= 1.0.1" + spec.add_dependency "prettier_print", ">= 1.0.2" spec.add_development_dependency "bundler" spec.add_development_dependency "minitest" diff --git a/test/visitor_with_environment_test.rb b/test/visitor_with_environment_test.rb index 302dbfbe..b37bad16 100644 --- a/test/visitor_with_environment_test.rb +++ b/test/visitor_with_environment_test.rb @@ -426,5 +426,194 @@ def test_variables_in_the_top_level assert_equal(1, variable.definitions[0].start_line) assert_equal(2, variable.usages[0].start_line) end + + def test_aref_field + tree = SyntaxTree.parse(<<~RUBY) + object = {} + object["name"] = "something" + RUBY + + visitor = Collector.new + visitor.visit(tree) + + assert_equal(0, visitor.arguments.length) + assert_equal(1, visitor.variables.length) + + variable = visitor.variables["object"] + assert_equal(1, variable.definitions.length) + assert_equal(1, variable.usages.length) + + assert_equal(1, variable.definitions[0].start_line) + assert_equal(2, variable.usages[0].start_line) + end + + def test_aref_on_a_method_call + tree = SyntaxTree.parse(<<~RUBY) + object = MyObject.new + object.attributes["name"] = "something" + RUBY + + visitor = Collector.new + visitor.visit(tree) + + assert_equal(0, visitor.arguments.length) + assert_equal(1, visitor.variables.length) + + variable = visitor.variables["object"] + assert_equal(1, variable.definitions.length) + assert_equal(1, variable.usages.length) + + assert_equal(1, variable.definitions[0].start_line) + assert_equal(2, variable.usages[0].start_line) + end + + def test_aref_with_two_accesses + tree = SyntaxTree.parse(<<~RUBY) + object = MyObject.new + object["first"]["second"] ||= [] + RUBY + + visitor = Collector.new + visitor.visit(tree) + + assert_equal(0, visitor.arguments.length) + assert_equal(1, visitor.variables.length) + + variable = visitor.variables["object"] + assert_equal(1, variable.definitions.length) + assert_equal(1, variable.usages.length) + + assert_equal(1, variable.definitions[0].start_line) + assert_equal(2, variable.usages[0].start_line) + end + + def test_aref_on_a_method_call_with_arguments + tree = SyntaxTree.parse(<<~RUBY) + object = MyObject.new + object.instance_variable_get(:@attributes)[:something] = :other_thing + RUBY + + visitor = Collector.new + visitor.visit(tree) + + assert_equal(0, visitor.arguments.length) + assert_equal(1, visitor.variables.length) + + variable = visitor.variables["object"] + assert_equal(1, variable.definitions.length) + assert_equal(1, variable.usages.length) + + assert_equal(1, variable.definitions[0].start_line) + assert_equal(2, variable.usages[0].start_line) + end + + def test_double_aref_on_method_call + tree = SyntaxTree.parse(<<~RUBY) + object = MyObject.new + object["attributes"].find { |a| a["field"] == "expected" }["value"] = "changed" + RUBY + + visitor = Collector.new + visitor.visit(tree) + + assert_equal(1, visitor.arguments.length) + assert_equal(1, visitor.variables.length) + + variable = visitor.variables["object"] + assert_equal(1, variable.definitions.length) + assert_equal(1, variable.usages.length) + + assert_equal(1, variable.definitions[0].start_line) + assert_equal(2, variable.usages[0].start_line) + + argument = visitor.arguments["a"] + assert_equal(1, argument.definitions.length) + assert_equal(1, argument.usages.length) + + assert_equal(2, argument.definitions[0].start_line) + assert_equal(2, argument.usages[0].start_line) + end + + def test_nested_arguments + tree = SyntaxTree.parse(<<~RUBY) + [[1, [2, 3]]].each do |one, (two, three)| + one + two + three + end + RUBY + + visitor = Collector.new + visitor.visit(tree) + + assert_equal(3, visitor.arguments.length) + assert_equal(0, visitor.variables.length) + + argument = visitor.arguments["one"] + assert_equal(1, argument.definitions.length) + assert_equal(1, argument.usages.length) + + assert_equal(1, argument.definitions[0].start_line) + assert_equal(2, argument.usages[0].start_line) + + argument = visitor.arguments["two"] + assert_equal(1, argument.definitions.length) + assert_equal(1, argument.usages.length) + + assert_equal(1, argument.definitions[0].start_line) + assert_equal(3, argument.usages[0].start_line) + + argument = visitor.arguments["three"] + assert_equal(1, argument.definitions.length) + assert_equal(1, argument.usages.length) + + assert_equal(1, argument.definitions[0].start_line) + assert_equal(4, argument.usages[0].start_line) + end + + def test_double_nested_arguments + tree = SyntaxTree.parse(<<~RUBY) + [[1, [2, 3]]].each do |one, (two, (three, four))| + one + two + three + four + end + RUBY + + visitor = Collector.new + visitor.visit(tree) + + assert_equal(4, visitor.arguments.length) + assert_equal(0, visitor.variables.length) + + argument = visitor.arguments["one"] + assert_equal(1, argument.definitions.length) + assert_equal(1, argument.usages.length) + + assert_equal(1, argument.definitions[0].start_line) + assert_equal(2, argument.usages[0].start_line) + + argument = visitor.arguments["two"] + assert_equal(1, argument.definitions.length) + assert_equal(1, argument.usages.length) + + assert_equal(1, argument.definitions[0].start_line) + assert_equal(3, argument.usages[0].start_line) + + argument = visitor.arguments["three"] + assert_equal(1, argument.definitions.length) + assert_equal(1, argument.usages.length) + + assert_equal(1, argument.definitions[0].start_line) + assert_equal(4, argument.usages[0].start_line) + + argument = visitor.arguments["four"] + assert_equal(1, argument.definitions.length) + assert_equal(1, argument.usages.length) + + assert_equal(1, argument.definitions[0].start_line) + assert_equal(5, argument.usages[0].start_line) + end end end