Skip to content

Commit ac63bef

Browse files
vinistockkddnewton
andcommitted
Add support for regexp locals
Co-authored-by: Kevin Newton <[email protected]>
1 parent 654ebcb commit ac63bef

File tree

2 files changed

+95
-0
lines changed

2 files changed

+95
-0
lines changed

lib/syntax_tree/with_scope.rb

+57
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,63 @@ def visit_var_ref(node)
217217
super
218218
end
219219

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

222279
def add_argument_definitions(list)

test/with_scope_test.rb

+38
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

@@ -349,6 +356,37 @@ def test_double_nested_arguments
349356
assert_argument(collector, "four", definitions: [1], usages: [5])
350357
end
351358

359+
def test_regex_named_capture_groups
360+
collector = Collector.collect(<<~RUBY)
361+
if /(?<one>\\w+)-(?<two>\\w+)/ =~ "something-else"
362+
one
363+
two
364+
end
365+
RUBY
366+
367+
assert_equal(2, collector.variables.length)
368+
369+
assert_variable(collector, "one", definitions: [1], usages: [2])
370+
assert_variable(collector, "two", definitions: [1], usages: [3])
371+
end
372+
373+
def test_multiline_regex_named_capture_groups
374+
collector = Collector.collect(<<~RUBY)
375+
if %r{
376+
(?<one>\\w+)-
377+
(?<two>\\w+)
378+
} =~ "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: [2], usages: [5])
387+
assert_variable(collector, "two", definitions: [3], usages: [6])
388+
end
389+
352390
class Resolver < Visitor
353391
prepend WithScope
354392

0 commit comments

Comments
 (0)