Skip to content

Commit 40ce90e

Browse files
marcandrebbatsov
authored andcommitted
Add Regexp::Expression#loc and #expression to replace parsed_tree_expr_loc
1 parent 58c9474 commit 40ce90e

File tree

6 files changed

+99
-27
lines changed

6 files changed

+99
-27
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#8960](https://github.com/rubocop-hq/rubocop/pull/8960): Add `Regexp::Expression#loc` and `#expression` to replace `parsed_tree_expr_loc`. ([@marcandre][])

lib/rubocop.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
require_relative 'rubocop/ast_aliases'
1717
require_relative 'rubocop/ext/regexp_node'
18+
require_relative 'rubocop/ext/regexp_parser'
1819

1920
require_relative 'rubocop/core_ext/string'
2021
require_relative 'rubocop/ext/processed_source'

lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def each_repeated_character_class_element_loc(node)
4343

4444
child_source = child.to_s
4545

46-
yield node.parsed_tree_expr_loc(child) if seen.include?(child_source)
46+
yield child.expression if seen.include?(child_source)
4747

4848
seen << child_source
4949
end
@@ -56,7 +56,7 @@ def each_repeated_character_class_element_loc(node)
5656
# mark every space (except the first) as duplicate if we do not skip regexp_parser nodes
5757
# that are within an interpolation.
5858
def within_interpolation?(node, child)
59-
parse_tree_child_loc = node.parsed_tree_expr_loc(child)
59+
parse_tree_child_loc = child.expression
6060

6161
interpolation_locs(node).any? { |il| il.overlaps?(parse_tree_child_loc) }
6262
end

lib/rubocop/ext/regexp_node.rb

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ def ANY.==(_)
1111
private_constant :ANY
1212

1313
# @return [Regexp::Expression::Root, nil]
14+
# Note: we extend Regexp nodes to provide `loc` and `expression`
15+
# see `ext/regexp_parser`.
1416
attr_reader :parsed_tree
1517

1618
def assign_properties(*)
@@ -22,6 +24,8 @@ def assign_properties(*)
2224
rescue StandardError
2325
nil
2426
end
27+
origin = loc.begin.end
28+
@parsed_tree&.each_expression(true) { |e| e.origin = origin }
2529
end
2630

2731
def each_capture(named: ANY)
@@ -37,11 +41,6 @@ def each_capture(named: ANY)
3741
self
3842
end
3943

40-
# @return [Parser::Source::Range] the range of the parse-tree expression
41-
def parsed_tree_expr_loc(expr)
42-
loc.begin.end.adjust(begin_pos: expr.ts, end_pos: expr.ts).resize(expr.full_length)
43-
end
44-
4544
private
4645

4746
def with_interpolations_blanked

lib/rubocop/ext/regexp_parser.rb

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Ext
5+
# Extensions for `regexp_parser` gem
6+
module RegexpParser
7+
# Source map for RegexpParser nodes
8+
class Map < ::Parser::Source::Map
9+
attr_reader :body, :quantifier, :begin, :end
10+
11+
def initialize(expression, body:, quantifier: nil, begin_l: nil, end_l: nil)
12+
@begin = begin_l
13+
@end = end_l
14+
@body = body
15+
@quantifier = quantifier
16+
super(expression)
17+
end
18+
end
19+
20+
module Expression
21+
# Add `expression` and `loc` to all `regexp_parser` nodes
22+
module Base
23+
attr_accessor :origin
24+
25+
# Shortcut to `loc.expression`
26+
def expression
27+
@expression ||= origin.adjust(begin_pos: ts, end_pos: ts + full_length)
28+
end
29+
30+
# @returns a location map like `parser` does, with:
31+
# - expression: complete expression
32+
# - quantifier: for `+`, `{1,2}`, etc.
33+
# - begin/end: for `[` and `]` (only CharacterSet for now)
34+
#
35+
# E.g.
36+
# [a-z]{2,}
37+
# ^^^^^^^^^ expression
38+
# ^^^^ quantifier
39+
# ^^^^^ body
40+
# ^ begin
41+
# ^ end
42+
#
43+
# Please open issue if you need other locations
44+
def loc
45+
@loc ||= begin
46+
Map.new(expression, **build_location)
47+
end
48+
end
49+
50+
private
51+
52+
def build_location
53+
return { body: expression } unless (q = quantifier)
54+
55+
body = expression.adjust(end_pos: -q.text.length)
56+
q_loc = expression.with(begin_pos: body.end_pos)
57+
{ body: body, quantifier: q_loc }
58+
end
59+
end
60+
61+
# Provide `CharacterSet` with `begin` and `end` locations.
62+
module CharacterSet
63+
def build_location
64+
h = super
65+
body = h[:body]
66+
h.merge!(
67+
begin_l: body.with(end_pos: body.begin_pos + 1),
68+
end_l: body.with(begin_pos: body.end_pos - 1)
69+
)
70+
end
71+
end
72+
end
73+
::Regexp::Expression::Base.include Expression::Base
74+
::Regexp::Expression::CharacterSet.include Expression::CharacterSet
75+
end
76+
end
77+
end

spec/rubocop/ext/regexp_node_spec.rb

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -96,29 +96,23 @@
9696
expect(tree.to_s).to eq('foobaz')
9797
end
9898
end
99-
end
10099

101-
describe '#parsed_node_loc' do
102-
let(:source) { '/([a-z]+)\d*\s?(?:foo)/' }
100+
context 'with a regexp with subexpressions' do
101+
let(:source) { '/([a-z]+)\d*\s?(?:foo)/' }
103102

104-
it 'returns the correct loc for each node in the parsed_tree' do
105-
loc_sources = node.parsed_tree.each_expression.map do |regexp_node|
106-
node.parsed_tree_expr_loc(regexp_node).source
107-
end
103+
it 'has location information' do
104+
nodes = node.parsed_tree.each_expression.map { |exp, _index| exp }
105+
106+
sources = nodes.map { |n| n.loc.expression.source }
108107

109-
expect(loc_sources).to eq(
110-
[
111-
'([a-z]+)',
112-
'[a-z]+',
113-
'a-z',
114-
'a',
115-
'z',
116-
'\d*',
117-
'\s?',
118-
'(?:foo)',
119-
'foo'
120-
]
121-
)
108+
expect(sources).to eq %w{
109+
([a-z]+) [a-z]+ a-z a z \d* \s? (?:foo) foo
110+
}
111+
112+
loc = nodes[1].loc
113+
delim = loc.begin, loc.body, loc.end, loc.quantifier
114+
expect(delim.map(&:source)).to eq %w{[ [a-z] ] +}
115+
end
122116
end
123117
end
124118
end

0 commit comments

Comments
 (0)