Skip to content

Commit 384a0d2

Browse files
authored
Merge pull request #329 from vinistock/vs/add_support_for_more_locals
Add support for more locals in WithScope
2 parents 8a51e7c + 48a630a commit 384a0d2

File tree

2 files changed

+126
-5
lines changed

2 files changed

+126
-5
lines changed

lib/syntax_tree/with_scope.rb

+66
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,15 @@ def visit_blockarg(node)
189189
super
190190
end
191191

192+
def visit_block_var(node)
193+
node.locals.each do |local|
194+
current_scope.add_local_definition(local, :variable)
195+
end
196+
197+
super
198+
end
199+
alias visit_lambda_var visit_block_var
200+
192201
# Visit for keeping track of local variable definitions
193202
def visit_var_field(node)
194203
value = node.value
@@ -217,6 +226,63 @@ def visit_var_ref(node)
217226
super
218227
end
219228

229+
# When using regex named capture groups, vcalls might actually be a variable
230+
def visit_vcall(node)
231+
value = node.value
232+
definition = current_scope.find_local(value.value)
233+
current_scope.add_local_usage(value, definition.type) if definition
234+
235+
super
236+
end
237+
238+
# Visit for capturing local variables defined in regex named capture groups
239+
def visit_binary(node)
240+
if node.operator == :=~
241+
left = node.left
242+
243+
if left.is_a?(RegexpLiteral) && left.parts.length == 1 &&
244+
left.parts.first.is_a?(TStringContent)
245+
content = left.parts.first
246+
247+
value = content.value
248+
location = content.location
249+
start_line = location.start_line
250+
251+
Regexp
252+
.new(value, Regexp::FIXEDENCODING)
253+
.names
254+
.each do |name|
255+
offset = value.index(/\(\?<#{Regexp.escape(name)}>/)
256+
line = start_line + value[0...offset].count("\n")
257+
258+
# We need to add 3 to account for these three characters
259+
# prefixing a named capture (?<
260+
column = location.start_column + offset + 3
261+
if value[0...offset].include?("\n")
262+
column =
263+
value[0...offset].length - value[0...offset].rindex("\n") +
264+
3 - 1
265+
end
266+
267+
ident_location =
268+
Location.new(
269+
start_line: line,
270+
start_char: location.start_char + offset,
271+
start_column: column,
272+
end_line: line,
273+
end_char: location.start_char + offset + name.length,
274+
end_column: column + name.length
275+
)
276+
277+
identifier = Ident.new(value: name, location: ident_location)
278+
current_scope.add_local_definition(identifier, :variable)
279+
end
280+
end
281+
end
282+
283+
super
284+
end
285+
220286
private
221287

222288
def add_argument_definitions(list)

test/with_scope_test.rb

+60-5
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ def visit_label(node)
3939
arguments[[current_scope.id, value]] = node
4040
end
4141
end
42+
43+
def visit_vcall(node)
44+
local = current_scope.find_local(node.value)
45+
variables[[current_scope.id, value]] = local if local
46+
47+
super
48+
end
4249
end
4350
end
4451

@@ -110,11 +117,7 @@ def foo
110117

111118
assert_equal(2, collector.variables.length)
112119
assert_variable(collector, "a", definitions: [2], usages: [4, 5])
113-
assert_variable(collector, "rest", definitions: [4])
114-
115-
# Rest is considered a vcall by the parser instead of a var_ref
116-
# assert_equal(1, variable_rest.usages.length)
117-
# assert_equal(6, variable_rest.usages[0].start_line)
120+
assert_variable(collector, "rest", definitions: [4], usages: [6])
118121
end
119122

120123
if RUBY_VERSION >= "3.1"
@@ -349,6 +352,58 @@ def test_double_nested_arguments
349352
assert_argument(collector, "four", definitions: [1], usages: [5])
350353
end
351354

355+
def test_block_locals
356+
collector = Collector.collect(<<~RUBY)
357+
[].each do |; a|
358+
end
359+
RUBY
360+
361+
assert_equal(1, collector.variables.length)
362+
363+
assert_variable(collector, "a", definitions: [1])
364+
end
365+
366+
def test_lambda_locals
367+
collector = Collector.collect(<<~RUBY)
368+
->(;a) { }
369+
RUBY
370+
371+
assert_equal(1, collector.variables.length)
372+
373+
assert_variable(collector, "a", definitions: [1])
374+
end
375+
376+
def test_regex_named_capture_groups
377+
collector = Collector.collect(<<~RUBY)
378+
if /(?<one>\\w+)-(?<two>\\w+)/ =~ "something-else"
379+
one
380+
two
381+
end
382+
RUBY
383+
384+
assert_equal(2, collector.variables.length)
385+
386+
assert_variable(collector, "one", definitions: [1], usages: [2])
387+
assert_variable(collector, "two", definitions: [1], usages: [3])
388+
end
389+
390+
def test_multiline_regex_named_capture_groups
391+
collector = Collector.collect(<<~RUBY)
392+
if %r{
393+
(?<one>\\w+)-
394+
(?<two>\\w+)
395+
} =~ "something-else"
396+
one
397+
two
398+
end
399+
RUBY
400+
401+
assert_equal(2, collector.variables.length)
402+
403+
assert_variable(collector, "one", definitions: [2], usages: [5])
404+
assert_variable(collector, "two", definitions: [3], usages: [6])
405+
end
406+
352407
class Resolver < Visitor
353408
prepend WithScope
354409

0 commit comments

Comments
 (0)