Skip to content

Commit

Permalink
Add RegexpNode#each_capture and parsed_tree
Browse files Browse the repository at this point in the history
  • Loading branch information
marcandre authored and mergify[bot] committed Aug 26, 2020
1 parent 50b798d commit 7efae1c
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 1 deletion.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Expand Up @@ -15,7 +15,7 @@
* Add new `Lint/TrailingCommaInAttributeDeclaration` cop. ([@drenmi][])
* [#8578](https://github.com/rubocop-hq/rubocop/pull/8578): Add `:restore_registry` context and `stub_cop_class` helper class. ([@marcandre][])
* [#8579](https://github.com/rubocop-hq/rubocop/pull/8579): Add `Cop.documentation_url`. ([@marcandre][])

* [#8510](https://github.com/rubocop-hq/rubocop/pull/8510): Add `RegexpNode#each_capture` and `parsed_tree`. ([@marcandre][])

### Bug fixes

Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Expand Up @@ -9,6 +9,7 @@
require 'unicode/display_width/no_string_ext'
require 'rubocop-ast'
require_relative 'rubocop/ast_aliases'
require_relative 'rubocop/ext/regexp_node'

require_relative 'rubocop/version'

Expand Down
46 changes: 46 additions & 0 deletions lib/rubocop/ext/regexp_node.rb
@@ -0,0 +1,46 @@
# frozen_string_literal: true

module RuboCop
module Ext
# Extensions to AST::RegexpNode for our cached parsed regexp info
module RegexpNode
ANY = Object.new
def ANY.==(_)
true
end
private_constant :ANY

class << self
attr_reader :parsed_cache
end
@parsed_cache = {}

# @return [Regexp::Expression::Root, nil]
def parsed_tree
return if interpolation?

str = content
Ext::RegexpNode.parsed_cache[str] ||= begin
Regexp::Parser.parse(str)
rescue StandardError
nil
end
end

def each_capture(named: ANY)
return enum_for(__method__, named: named) unless block_given?

parsed_tree&.traverse do |event, exp, _index|
yield(exp) if event == :enter &&
named == exp.respond_to?(:name) &&
exp.respond_to?(:capturing?) &&
exp.capturing?
end

self
end

AST::RegexpNode.include self
end
end
end
35 changes: 35 additions & 0 deletions spec/rubocop/ext/regexp_node_spec.rb
@@ -0,0 +1,35 @@
# frozen_string_literal: true

require 'timeout'

RSpec.describe RuboCop::Ext::RegexpNode do
let(:source) { '/(hello)(?<foo>world)(?:not captured)/' }
let(:processed_source) { parse_source(source) }
let(:ast) { processed_source.ast }
let(:node) { ast }

describe '#each_capture' do
subject(:captures) { node.each_capture(**arg).to_a }

let(:named) { be_instance_of(Regexp::Expression::Group::Named) }
let(:positional) { be_instance_of(Regexp::Expression::Group::Capture) }

context 'when called without argument' do
let(:arg) { {} }

it { is_expected.to match [positional, named] }
end

context 'when called with a `named: false`' do
let(:arg) { { named: false } }

it { is_expected.to match [positional] }
end

context 'when called with a `named: true`' do
let(:arg) { { named: true } }

it { is_expected.to match [named] }
end
end
end

0 comments on commit 7efae1c

Please sign in to comment.