Skip to content

Commit

Permalink
Handle comments and blank interpolations in regexp parsed_tree
Browse files Browse the repository at this point in the history
  • Loading branch information
owst committed Sep 27, 2020
1 parent 14ad258 commit 7693728
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,10 @@

## master (unreleased)

### Changes

* [#8803](https://github.com/rubocop-hq/rubocop/pull/8803): Extend RegexpNode#parsed_tree to handle regexps including interpolaton and comments. ([@owst][])

## 0.92.0 (2020-09-25)

### New features
Expand Down
29 changes: 25 additions & 4 deletions lib/rubocop/ext/regexp_node.rb
Expand Up @@ -16,12 +16,16 @@ class << self
@parsed_cache = {}

# @return [Regexp::Expression::Root, nil]
def parsed_tree
return if interpolation?
def parsed_tree(interpolation: :ignore)
unless %i[ignore blank].include?(interpolation)
raise ArgumentError, 'interpolation must be one of :ignore or :blank'
end

return if interpolation? && interpolation == :ignore

str = content
str = with_interpolations_blanked
Ext::RegexpNode.parsed_cache[str] ||= begin
Regexp::Parser.parse(str)
Regexp::Parser.parse(str, options: options)
rescue StandardError
nil
end
Expand All @@ -40,6 +44,23 @@ def each_capture(named: ANY)
self
end

private

def with_interpolations_blanked
children.reject(&:regopt_type?).map do |child|
source = child.source

# We don't want to consider the contents of interpolations as part of the pattern source,
# but need to preserve their width, to allow offsets to correctly line up with the
# original source: spaces have no effect, and preserve width.
if child.begin_type?
' ' * source.length
else
source
end
end.join
end

AST::RegexpNode.include self
end
end
Expand Down
4 changes: 2 additions & 2 deletions rubocop.gemspec
Expand Up @@ -36,9 +36,9 @@ Gem::Specification.new do |s|
s.add_runtime_dependency('parallel', '~> 1.10')
s.add_runtime_dependency('parser', '>= 2.7.1.5')
s.add_runtime_dependency('rainbow', '>= 2.2.2', '< 4.0')
s.add_runtime_dependency('regexp_parser', '>= 1.7')
s.add_runtime_dependency('regexp_parser', '>= 1.8')
s.add_runtime_dependency('rexml')
s.add_runtime_dependency('rubocop-ast', '>= 0.5.0')
s.add_runtime_dependency('rubocop-ast', '>= 0.6.0')
s.add_runtime_dependency('ruby-progressbar', '~> 1.7')
s.add_runtime_dependency('unicode-display_width', '>= 1.4.0', '< 2.0')

Expand Down
88 changes: 88 additions & 0 deletions spec/rubocop/ext/regexp_node_spec.rb
Expand Up @@ -32,4 +32,92 @@
it { is_expected.to match [named] }
end
end

describe '#parsed_tree' do
let(:source) { '/foo#{bar}baz/' }

context 'with an extended mode regexp with comment' do
let(:source) { '/42 # the answer/x' }

it 'returns the expected tree' do
tree = node.parsed_tree(interpolation: :ignore)

expect(tree.is_a?(Regexp::Expression::Root)).to eq(true)
expect(tree.map(&:token)).to eq(%i[literal whitespace comment])
end
end

context 'with interpolation: :ignore' do
context 'with a regexp containing interpolation' do
it { expect(node.parsed_tree(interpolation: :ignore)).to eq(nil) }
end

context 'with a regexp not containing interpolation' do
let(:source) { '/foobaz/' }

it 'returns the expected tree' do
tree = node.parsed_tree(interpolation: :ignore)

expect(tree.is_a?(Regexp::Expression::Root)).to eq(true)
expect(tree.to_s).to eq('foobaz')
end
end
end

context 'with interpolation: :blank' do
context 'with a regexp containing interpolation' do
it 'returns the expected blanked tree' do
tree = node.parsed_tree(interpolation: :blank)

expect(tree.is_a?(Regexp::Expression::Root)).to eq(true)
expect(tree.to_s).to eq('foo baz')
end
end

context 'with a regexp containing a multi-line interpolation' do
let(:source) do
<<~'REGEXP'
/
foo
#{
bar(
42
)
}
baz
/
REGEXP
end

it 'returns the expected blanked tree' do
tree = node.parsed_tree(interpolation: :blank)

expect(tree.is_a?(Regexp::Expression::Root)).to eq(true)
expect(tree.to_s.split("\n")).to eq(
[
'',
' foo',
' ' * 32,
' baz'
]
)
end
end

context 'with a regexp not containing interpolation' do
let(:source) { '/foobaz/' }

it 'returns the expected tree' do
tree = node.parsed_tree(interpolation: :blank)

expect(tree.is_a?(Regexp::Expression::Root)).to eq(true)
expect(tree.to_s).to eq('foobaz')
end
end
end

context 'with interpolation: :other' do
it { expect { node.parsed_tree(interpolation: :other) }.to raise_error(ArgumentError) }
end
end
end

0 comments on commit 7693728

Please sign in to comment.