Skip to content

Commit

Permalink
Add Constant parameters to NodePattern
Browse files Browse the repository at this point in the history
  • Loading branch information
marcandre committed Jun 16, 2020
1 parent 4816c2e commit 805cf3d
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -9,6 +9,7 @@
* [#20](https://github.com/rubocop-hq/rubocop-ast/pull/20): Add option predicates for `RegexpNode`. ([@owst][])
* [#11](https://github.com/rubocop-hq/rubocop-ast/issues/11): Add `argument_type?` method to make it easy to recognize argument nodes. ([@tejasbubane][])
* [#31](https://github.com/rubocop-hq/rubocop-ast/pull/31): NodePattern now uses `param === node` to match params, which allows Regexp, Proc, Set in addition to Nodes and literals. ([@marcandre][])
* [#35](https://github.com/rubocop-hq/rubocop-ast/pull/35): NodePattern now accepts `%named_param` and `%CONST`. The macros `def_node_pattern` and `def_node_search` accept default named parameters. ([@marcandre][])

## 0.0.3 (2020-05-15)

Expand Down
17 changes: 17 additions & 0 deletions docs/modules/ROOT/pages/node_pattern.adoc
Expand Up @@ -436,6 +436,23 @@ interesting_call?(node, method: /^transform/) # match anything starting with 'tr

Named parameters as arguments to custom methods are also supported.

== `%CONST` for constants

Constants can be included in patterns. They will be matched using `===`, so
+Regexp+ / +Set+ / +Proc+ can be used in addition to literals and +Nodes+:

[source,ruby]
----
SOME_CALLS = Set[:transform_values, :transform_keys,
:transform_values!, :transform_keys!,
:to_h].freeze
def_node_matcher :interesting_call?, '(send _ %SOME_CALLS ...)'
----

Constants as arguments to custom methods are also supported.

== `nil` or `nil?`

Take a special attention to nil behavior:
Expand Down
15 changes: 14 additions & 1 deletion lib/rubocop/ast/node_pattern.rb
Expand Up @@ -86,6 +86,7 @@ module AST
# # parameters (see `%1`)
# # Note that the macros `def_node_pattern` and
# # `def_node_search` accept default values for these.
# '(send _ %CONST)' # the named constant will act like `%1` and `%named`.
# '^^send' # each ^ ascends one level in the AST
# # so this matches against the grandparent node
# '`send' # descends any number of level in the AST
Expand Down Expand Up @@ -129,11 +130,12 @@ class Compiler
NUMBER = /-?\d+(?:\.\d+)?/.freeze
STRING = /".+?"/.freeze
METHOD_NAME = /\#?#{IDENTIFIER}[!?]?\(?/.freeze
PARAM_CONST = /%[A-Z:][a-zA-Z_:]+/.freeze
KEYWORD_NAME = /%[a-z_]+/.freeze
PARAM_NUMBER = /%\d*/.freeze

SEPARATORS = /\s+/.freeze
TOKENS = Regexp.union(META, KEYWORD_NAME, PARAM_NUMBER, NUMBER,
TOKENS = Regexp.union(META, PARAM_CONST, KEYWORD_NAME, PARAM_NUMBER, NUMBER,
METHOD_NAME, SYMBOL, STRING)

TOKEN = /\G(?:#{SEPARATORS}|#{TOKENS}|.)/.freeze
Expand All @@ -145,6 +147,7 @@ class Compiler
FUNCALL = /\A\##{METHOD_NAME}/.freeze
LITERAL = /\A(?:#{SYMBOL}|#{NUMBER}|#{STRING})\Z/.freeze
PARAM = /\A#{PARAM_NUMBER}\Z/.freeze
CONST = /\A#{PARAM_CONST}\Z/.freeze
KEYWORD = /\A#{KEYWORD_NAME}\Z/.freeze
CLOSING = /\A(?:\)|\}|\])\Z/.freeze

Expand Down Expand Up @@ -245,6 +248,7 @@ def compile_expr(token = tokens.shift)
when PREDICATE then compile_predicate(token)
when NODE then compile_nodetype(token)
when KEYWORD then compile_keyword(token[1..-1])
when CONST then compile_const(token[1..-1])
when PARAM then compile_param(token[1..-1])
when CLOSING then fail_due_to("#{token} in invalid position")
when nil then fail_due_to('pattern ended prematurely')
Expand Down Expand Up @@ -628,6 +632,10 @@ def compile_param(number)
"#{get_param(number)} === #{CUR_ELEMENT}"
end

def compile_const(const)
"#{get_const(const)} === #{CUR_ELEMENT}"
end

def compile_keyword(keyword)
"#{get_keyword(keyword)} === #{CUR_ELEMENT}"
end
Expand All @@ -649,6 +657,7 @@ def compile_arg(token)
access_unify(name) || fail_due_to('invalid in arglist: ' + token)
when LITERAL then token
when KEYWORD then get_keyword(name)
when CONST then get_const(name)
when PARAM then get_param(name)
when CLOSING then fail_due_to("#{token} in invalid position")
when nil then fail_due_to('pattern ended prematurely')
Expand All @@ -673,6 +682,10 @@ def get_keyword(name)
name
end

def get_const(const)
const # Output the constant exactly as given
end

def emit_yield_capture(when_no_capture = '')
yield_val = if @captures.zero?
when_no_capture
Expand Down
34 changes: 34 additions & 0 deletions spec/rubocop/ast/node_pattern_spec.rb
Expand Up @@ -1162,6 +1162,25 @@
end
end

context 'with a constant argument' do
let(:pattern) { '(send (int equal?(%CONST)) ...)' }
let(:ruby) { '1 + 2' }

before { stub_const 'CONST', const_value }

context 'for which the predicate is true' do
let(:const_value) { 1 }

it_behaves_like 'matching'
end

context 'for which the predicate is false' do
let(:const_value) { 2 }

it_behaves_like 'nonmatching'
end
end

context 'with multiple arguments' do
let(:pattern) { '(str between?(%1, %2))' }
let(:ruby) { '"c"' }
Expand Down Expand Up @@ -2103,5 +2122,20 @@ def withargs(foo, bar, qux)
end
end
end

context 'with a pattern with a constant' do
let(:pattern) { '(sym %TEST)' }
let(:helper_name) { :def_node_matcher }

before { defined_class::TEST = hello_matcher }

it_behaves_like 'matching'

context 'when the value is not in the set' do
let(:ruby) { ':world' }

it_behaves_like 'nonmatching'
end
end
end
end

0 comments on commit 805cf3d

Please sign in to comment.