From ac63bef6bd59dbe93632b5acff5acc57bca6db26 Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Thu, 2 Mar 2023 16:06:30 -0500 Subject: [PATCH 1/3] Add support for regexp locals Co-authored-by: Kevin Newton --- lib/syntax_tree/with_scope.rb | 57 +++++++++++++++++++++++++++++++++++ test/with_scope_test.rb | 38 +++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/lib/syntax_tree/with_scope.rb b/lib/syntax_tree/with_scope.rb index 7fcef067..2f691927 100644 --- a/lib/syntax_tree/with_scope.rb +++ b/lib/syntax_tree/with_scope.rb @@ -217,6 +217,63 @@ def visit_var_ref(node) super end + # When using regex named capture groups, vcalls might actually be a variable + def visit_vcall(node) + value = node.value + definition = current_scope.find_local(value.value) + current_scope.add_local_usage(value, definition.type) if definition + + super + end + + # Visit for capturing local variables defined in regex named capture groups + def visit_binary(node) + if node.operator == :=~ + left = node.left + + if left.is_a?(RegexpLiteral) && left.parts.length == 1 && + left.parts.first.is_a?(TStringContent) + content = left.parts.first + + value = content.value + location = content.location + start_line = location.start_line + + Regexp + .new(value, Regexp::FIXEDENCODING) + .names + .each do |name| + offset = value.index(/\(\?<#{Regexp.escape(name)}>/) + line = start_line + value[0...offset].count("\n") + + # We need to add 3 to account for these three characters + # prefixing a named capture (?< + column = location.start_column + offset + 3 + if value[0...offset].include?("\n") + column = + value[0...offset].length - value[0...offset].rindex("\n") + + 3 - 1 + end + + ident_location = + Location.new( + start_line: line, + start_char: location.start_char + offset, + start_column: column, + end_line: line, + end_char: location.start_char + offset + name.length, + end_column: column + name.length + ) + + identifier = Ident.new(value: name, location: ident_location) + current_scope.add_local_definition(identifier, :variable) + end + end + end + + super + end + private def add_argument_definitions(list) diff --git a/test/with_scope_test.rb b/test/with_scope_test.rb index 9675e811..bb804df5 100644 --- a/test/with_scope_test.rb +++ b/test/with_scope_test.rb @@ -39,6 +39,13 @@ def visit_label(node) arguments[[current_scope.id, value]] = node end end + + def visit_vcall(node) + local = current_scope.find_local(node.value) + variables[[current_scope.id, value]] = local if local + + super + end end end @@ -349,6 +356,37 @@ def test_double_nested_arguments assert_argument(collector, "four", definitions: [1], usages: [5]) end + def test_regex_named_capture_groups + collector = Collector.collect(<<~RUBY) + if /(?\\w+)-(?\\w+)/ =~ "something-else" + one + two + end + RUBY + + assert_equal(2, collector.variables.length) + + assert_variable(collector, "one", definitions: [1], usages: [2]) + assert_variable(collector, "two", definitions: [1], usages: [3]) + end + + def test_multiline_regex_named_capture_groups + collector = Collector.collect(<<~RUBY) + if %r{ + (?\\w+)- + (?\\w+) + } =~ "something-else" + one + two + end + RUBY + + assert_equal(2, collector.variables.length) + + assert_variable(collector, "one", definitions: [2], usages: [5]) + assert_variable(collector, "two", definitions: [3], usages: [6]) + end + class Resolver < Visitor prepend WithScope From 039c0874e08316e3f9a9c80f7838177ccad1cd6c Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Thu, 2 Mar 2023 17:04:42 -0500 Subject: [PATCH 2/3] Add support for lambda and block locals Co-authored-by: Kevin Newton --- lib/syntax_tree/with_scope.rb | 9 +++++++++ test/with_scope_test.rb | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/syntax_tree/with_scope.rb b/lib/syntax_tree/with_scope.rb index 2f691927..f7c3a203 100644 --- a/lib/syntax_tree/with_scope.rb +++ b/lib/syntax_tree/with_scope.rb @@ -189,6 +189,15 @@ def visit_blockarg(node) super end + def visit_block_var(node) + node.locals.each do |local| + current_scope.add_local_definition(local, :variable) + end + + super + end + alias visit_lambda_var visit_block_var + # Visit for keeping track of local variable definitions def visit_var_field(node) value = node.value diff --git a/test/with_scope_test.rb b/test/with_scope_test.rb index bb804df5..6928a322 100644 --- a/test/with_scope_test.rb +++ b/test/with_scope_test.rb @@ -356,6 +356,27 @@ def test_double_nested_arguments assert_argument(collector, "four", definitions: [1], usages: [5]) end + def test_block_locals + collector = Collector.collect(<<~RUBY) + [].each do |; a| + end + RUBY + + assert_equal(1, collector.variables.length) + + assert_variable(collector, "a", definitions: [1]) + end + + def test_lambda_locals + collector = Collector.collect(<<~RUBY) + ->(;a) { } + RUBY + + assert_equal(1, collector.variables.length) + + assert_variable(collector, "a", definitions: [1]) + end + def test_regex_named_capture_groups collector = Collector.collect(<<~RUBY) if /(?\\w+)-(?\\w+)/ =~ "something-else" From 48a630ac84cb5faa3734a5a2560d0a562d54951d Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Thu, 2 Mar 2023 17:14:00 -0500 Subject: [PATCH 3/3] Fix test for pinned variable Co-authored-by: Kevin Newton --- test/with_scope_test.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/with_scope_test.rb b/test/with_scope_test.rb index 6928a322..eb88576a 100644 --- a/test/with_scope_test.rb +++ b/test/with_scope_test.rb @@ -117,11 +117,7 @@ def foo assert_equal(2, collector.variables.length) assert_variable(collector, "a", definitions: [2], usages: [4, 5]) - assert_variable(collector, "rest", definitions: [4]) - - # Rest is considered a vcall by the parser instead of a var_ref - # assert_equal(1, variable_rest.usages.length) - # assert_equal(6, variable_rest.usages[0].start_line) + assert_variable(collector, "rest", definitions: [4], usages: [6]) end if RUBY_VERSION >= "3.1"