Skip to content

Commit

Permalink
NodePattern: Support regexp literal
Browse files Browse the repository at this point in the history
  • Loading branch information
marcandre committed Sep 23, 2020
1 parent 23d3125 commit c45b433
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 1 deletion.
7 changes: 7 additions & 0 deletions docs/modules/ROOT/pages/node_pattern.adoc
Expand Up @@ -194,6 +194,13 @@ Branches of the union can contain more than one term:
If all the branches have a single term, you can omit the `|`, so `{int | float}` can be
simplified to `{int float}`.

When checking for symbols or string, you can use regexp literals for a similar effect:

[source,sh]
----
(send _ /to_s|inspect/) # => matches calls to `to_s` or `inspect`
----

== `[]` for "AND"

Imagine you want to check if the number is `odd?` and also positive numbers:
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/ast/node_pattern/compiler/atom_subcompiler.rb
Expand Up @@ -23,6 +23,7 @@ def visit_symbol
end
alias visit_number visit_symbol
alias visit_string visit_symbol
alias visit_regexp visit_symbol

def visit_const
node.child
Expand Down
15 changes: 15 additions & 0 deletions lib/rubocop/ast/node_pattern/lexer.rb
Expand Up @@ -41,6 +41,21 @@ def emit_comment
nil
end

REGEXP_OPTIONS = {
'i' => ::Regexp::IGNORECASE,
'm' => ::Regexp::MULTILINE,
'x' => ::Regexp::EXTENDED,
'o' => 0
}.freeze
private_constant :REGEXP_OPTIONS

def emit_regexp
body, options = ss.captures
flag = options.each_char.map { |c| REGEXP_OPTIONS[c] }.sum

emit(:tREGEXP) { Regexp.new(body, flag) }
end

def do_parse
# Called by the generated `parse` method, do nothing here.
end
Expand Down
3 changes: 3 additions & 0 deletions lib/rubocop/ast/node_pattern/lexer.rex
Expand Up @@ -13,6 +13,8 @@ class RuboCop::AST::NodePattern::LexerRex
macros
SYMBOL_NAME /[\w+@*\/?!<>=~|%^-]+|\[\]=?/
IDENTIFIER /[a-zA-Z_][a-zA-Z0-9_-]*/
REGEXP_BODY /(?:[^\/]|\\\/)*/
REGEXP /\/(#{REGEXP_BODY})(?<!\\)\/([imxo]*)/
rules
/\s+/
/:(#{SYMBOL_NAME})/o { emit :tSYMBOL, &:to_sym }
Expand All @@ -22,6 +24,7 @@ rules
/#{Regexp.union(
%w"( ) { | } [ ] < > $ ! ^ ` ... + * ? ,"
)}/o { emit ss.matched, &:to_sym }
/#{REGEXP}/o { emit_regexp }
/%([A-Z:][a-zA-Z_:]+)/ { emit :tPARAM_CONST }
/%([a-z_]+)/ { emit :tPARAM_NAMED }
/%(\d*)/ { emit(:tPARAM_NUMBER) { |s| s.empty? ? 1 : s.to_i } } # Map `%` to `%1`
Expand Down
2 changes: 2 additions & 0 deletions lib/rubocop/ast/node_pattern/node.rb
Expand Up @@ -10,6 +10,8 @@ class Node < ::Parser::AST::Node

LITERAL_TYPES = %i[symbol number string].to_set.freeze
private_constant :LITERAL_TYPES
# Note: regexp are not added to the LITERAL TYPES
# because they won't function as part of a Set

###
# To be overriden by subclasses
Expand Down
3 changes: 2 additions & 1 deletion lib/rubocop/ast/node_pattern/parser.y
@@ -1,7 +1,7 @@
class RuboCop::AST::NodePattern::Parser
options no_result_var
token tSYMBOL tNUMBER tSTRING tWILDCARD tPARAM_NAMED tPARAM_CONST tPARAM_NUMBER
tFUNCTION_CALL tPREDICATE tNODE_TYPE tARG_LIST tUNIFY
tFUNCTION_CALL tPREDICATE tNODE_TYPE tARG_LIST tUNIFY tREGEXP
rule
node_pattern # @return Node
: node_pattern_no_union
Expand All @@ -28,6 +28,7 @@ rule
| tPARAM_CONST { emit_atom :const, *val }
| tPARAM_NAMED { emit_atom :named_parameter, *val }
| tPARAM_NUMBER { emit_atom :positional_parameter, *val }
| tREGEXP { emit_atom :regexp, *val }
| tWILDCARD { emit_atom :wildcard, *val }
| tUNIFY { emit_atom :unify, *val }
;
Expand Down
24 changes: 24 additions & 0 deletions spec/rubocop/ast/node_pattern/lexer_spec.rb
Expand Up @@ -36,4 +36,28 @@
%i[( array sym $ int + x )]
end
end

[
/test/,
/[abc]+\/()?/x, # rubocop:disable Style/RegexpLiteral
/back\\slash/
].each do |regexp|
context "when given a regexp #{regexp.inspect}" do
let(:source) { regexp.inspect }

it 'round trips' do
token = tokens.first
value = token.last.first
expect(value.inspect).to eq regexp.inspect
end
end
end

context 'when given a regexp ending with a backslash' do
let(:source) { '/tricky\\/' }

it 'does not lexes it properly' do
expect { tokens }.to raise_error(RuboCop::AST::NodePattern::LexerRex::ScanError)
end
end
end
10 changes: 10 additions & 0 deletions spec/rubocop/ast/node_pattern_spec.rb
Expand Up @@ -1896,6 +1896,16 @@ def withargs(foo, bar, qux)
end
end

describe 'regexp' do
it 'matches symbols or strings' do
expect('(_ _ $/abc|def|foo/i ...)').to match_codes(
'Foo(42)', 'foo(42)'
).and not_match_codes(
'bar(42)'
)
end
end

describe 'bad syntax' do
context 'with empty parentheses' do
let(:pattern) { '()' }
Expand Down

0 comments on commit c45b433

Please sign in to comment.