rubocop-ast 0.1.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +9 -5
- data/lib/rubocop/ast.rb +5 -1
- data/lib/rubocop/ast/builder.rb +5 -1
- data/lib/rubocop/ast/ext/range.rb +28 -0
- data/lib/rubocop/ast/ext/set.rb +12 -0
- data/lib/rubocop/ast/node.rb +63 -8
- data/lib/rubocop/ast/node/array_node.rb +2 -8
- data/lib/rubocop/ast/node/block_node.rb +1 -1
- data/lib/rubocop/ast/node/break_node.rb +1 -6
- data/lib/rubocop/ast/node/case_match_node.rb +3 -9
- data/lib/rubocop/ast/node/case_node.rb +13 -9
- data/lib/rubocop/ast/node/const_node.rb +65 -0
- data/lib/rubocop/ast/node/def_node.rb +4 -23
- data/lib/rubocop/ast/node/defined_node.rb +2 -0
- data/lib/rubocop/ast/node/float_node.rb +1 -0
- data/lib/rubocop/ast/node/hash_node.rb +21 -8
- data/lib/rubocop/ast/node/if_node.rb +7 -14
- data/lib/rubocop/ast/node/index_node.rb +5 -3
- data/lib/rubocop/ast/node/indexasgn_node.rb +5 -3
- data/lib/rubocop/ast/node/int_node.rb +1 -0
- data/lib/rubocop/ast/node/lambda_node.rb +10 -3
- data/lib/rubocop/ast/node/mixin/method_dispatch_node.rb +0 -7
- data/lib/rubocop/ast/node/mixin/parameterized_node.rb +55 -0
- data/lib/rubocop/ast/node/next_node.rb +12 -0
- data/lib/rubocop/ast/node/pair_node.rb +2 -2
- data/lib/rubocop/ast/node/resbody_node.rb +21 -0
- data/lib/rubocop/ast/node/rescue_node.rb +49 -0
- data/lib/rubocop/ast/node/return_node.rb +1 -13
- data/lib/rubocop/ast/node/send_node.rb +7 -1
- data/lib/rubocop/ast/node/super_node.rb +2 -0
- data/lib/rubocop/ast/node/when_node.rb +3 -9
- data/lib/rubocop/ast/node/yield_node.rb +2 -0
- data/lib/rubocop/ast/node_pattern.rb +99 -86
- data/lib/rubocop/ast/processed_source.rb +94 -16
- data/lib/rubocop/ast/traversal.rb +2 -2
- data/lib/rubocop/ast/version.rb +1 -1
- metadata +12 -8
- data/lib/rubocop/ast/node/retry_node.rb +0 -17
@@ -6,13 +6,19 @@ module RuboCop
|
|
6
6
|
# node when the builder constructs the AST, making its methods available
|
7
7
|
# to all `send` nodes within RuboCop.
|
8
8
|
class SendNode < Node
|
9
|
-
include ParameterizedNode
|
9
|
+
include ParameterizedNode::RestArguments
|
10
10
|
include MethodDispatchNode
|
11
11
|
|
12
12
|
def_node_matcher :attribute_accessor?, <<~PATTERN
|
13
13
|
[(send nil? ${:attr_reader :attr_writer :attr_accessor :attr} $...)
|
14
14
|
(_ _ _ _ ...)]
|
15
15
|
PATTERN
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def first_argument_index
|
20
|
+
2
|
21
|
+
end
|
16
22
|
end
|
17
23
|
end
|
18
24
|
end
|
@@ -13,17 +13,11 @@ module RuboCop
|
|
13
13
|
node_parts[0...-1]
|
14
14
|
end
|
15
15
|
|
16
|
-
#
|
17
|
-
|
18
|
-
#
|
19
|
-
# @return [self] if a block is given
|
20
|
-
# @return [Enumerator] if no block is given
|
21
|
-
def each_condition
|
16
|
+
# @deprecated Use `conditions.each`
|
17
|
+
def each_condition(&block)
|
22
18
|
return conditions.to_enum(__method__) unless block_given?
|
23
19
|
|
24
|
-
conditions.each
|
25
|
-
yield condition
|
26
|
-
end
|
20
|
+
conditions.each(&block)
|
27
21
|
|
28
22
|
self
|
29
23
|
end
|
@@ -84,7 +84,7 @@ module RuboCop
|
|
84
84
|
# # matching process starts
|
85
85
|
# '(send _ %named)' # arguments can also be passed as named
|
86
86
|
# # parameters (see `%1`)
|
87
|
-
# # Note that the macros `
|
87
|
+
# # Note that the macros `def_node_matcher` and
|
88
88
|
# # `def_node_search` accept default values for these.
|
89
89
|
# '(send _ %CONST)' # the named constant will act like `%1` and `%named`.
|
90
90
|
# '^^send' # each ^ ascends one level in the AST
|
@@ -96,6 +96,10 @@ module RuboCop
|
|
96
96
|
# # if that returns a truthy value, the match succeeds
|
97
97
|
# 'equal?(%1)' # predicates can be given 1 or more extra args
|
98
98
|
# '#method(%0, 1)' # funcalls can also be given 1 or more extra args
|
99
|
+
# # These arguments can be patterns themselves, in
|
100
|
+
# # which case a matcher responding to === will be
|
101
|
+
# # passed.
|
102
|
+
# '# comment' # comments are accepted at the end of lines
|
99
103
|
#
|
100
104
|
# You can nest arbitrarily deep:
|
101
105
|
#
|
@@ -110,11 +114,6 @@ module RuboCop
|
|
110
114
|
# and so on. Therefore, if you add methods which are named like
|
111
115
|
# `#prefix_type?` to the AST node class, then 'prefix' will become usable as
|
112
116
|
# a pattern.
|
113
|
-
#
|
114
|
-
# Also note that if you need a "guard clause" to protect against possible nils
|
115
|
-
# in a certain place in the AST, you can do it like this: `[!nil <pattern>]`
|
116
|
-
#
|
117
|
-
# The compiler code is very simple; don't be afraid to read through it!
|
118
117
|
class NodePattern
|
119
118
|
# @private
|
120
119
|
Invalid = Class.new(StandardError)
|
@@ -124,6 +123,8 @@ module RuboCop
|
|
124
123
|
class Compiler
|
125
124
|
SYMBOL = %r{:(?:[\w+@*/?!<>=~|%^-]+|\[\]=?)}.freeze
|
126
125
|
IDENTIFIER = /[a-zA-Z_][a-zA-Z0-9_-]*/.freeze
|
126
|
+
COMMENT = /#\s.*$/.freeze
|
127
|
+
|
127
128
|
META = Regexp.union(
|
128
129
|
%w"( ) { } [ ] $< < > $... $ ! ^ ` ... + * ?"
|
129
130
|
).freeze
|
@@ -135,8 +136,10 @@ module RuboCop
|
|
135
136
|
PARAM_NUMBER = /%\d*/.freeze
|
136
137
|
|
137
138
|
SEPARATORS = /\s+/.freeze
|
138
|
-
|
139
|
-
|
139
|
+
ONLY_SEPARATOR = /\A#{SEPARATORS}\Z/.freeze
|
140
|
+
|
141
|
+
TOKENS = Regexp.union(META, PARAM_CONST, KEYWORD_NAME, PARAM_NUMBER, NUMBER,
|
142
|
+
METHOD_NAME, SYMBOL, STRING)
|
140
143
|
|
141
144
|
TOKEN = /\G(?:#{SEPARATORS}|#{TOKENS}|.)/.freeze
|
142
145
|
|
@@ -163,6 +166,7 @@ module RuboCop
|
|
163
166
|
CUR_NODE = "#{CUR_PLACEHOLDER} node@@@"
|
164
167
|
CUR_ELEMENT = "#{CUR_PLACEHOLDER} element@@@"
|
165
168
|
SEQ_HEAD_GUARD = '@@@seq guard head@@@'
|
169
|
+
MULTIPLE_CUR_PLACEHOLDER = /#{CUR_PLACEHOLDER}.*#{CUR_PLACEHOLDER}/.freeze
|
166
170
|
|
167
171
|
line = __LINE__
|
168
172
|
ANY_ORDER_TEMPLATE = ERB.new <<~RUBY.gsub("-%>\n", '%>')
|
@@ -199,22 +203,26 @@ module RuboCop
|
|
199
203
|
RUBY
|
200
204
|
REPEATED_TEMPLATE.location = [__FILE__, line + 1]
|
201
205
|
|
202
|
-
def initialize(str,
|
206
|
+
def initialize(str, root = 'node0', node_var = root)
|
203
207
|
@string = str
|
204
|
-
|
208
|
+
# For def_node_matcher, root == node_var
|
209
|
+
# For def_node_search, root is the root node to search on,
|
210
|
+
# and node_var is the current descendant being searched.
|
211
|
+
@root = root
|
212
|
+
@node_var = node_var
|
205
213
|
|
206
214
|
@temps = 0 # avoid name clashes between temp variables
|
207
215
|
@captures = 0 # number of captures seen
|
208
216
|
@unify = {} # named wildcard -> temp variable
|
209
217
|
@params = 0 # highest % (param) number seen
|
210
218
|
@keywords = Set[] # keyword parameters seen
|
211
|
-
run
|
219
|
+
run
|
212
220
|
end
|
213
221
|
|
214
|
-
def run
|
222
|
+
def run
|
215
223
|
@tokens = Compiler.tokens(@string)
|
216
224
|
|
217
|
-
@match_code = with_context(compile_expr, node_var, use_temp_node: false)
|
225
|
+
@match_code = with_context(compile_expr, @node_var, use_temp_node: false)
|
218
226
|
@match_code.prepend("(captures = Array.new(#{@captures})) && ") \
|
219
227
|
if @captures.positive?
|
220
228
|
|
@@ -234,6 +242,10 @@ module RuboCop
|
|
234
242
|
# CUR_NODE: Ruby code that evaluates to an AST node
|
235
243
|
# CUR_ELEMENT: Either the node or the type if in first element of
|
236
244
|
# a sequence (aka seq_head, e.g. "(seq_head first_node_arg ...")
|
245
|
+
if (atom = compile_atom(token))
|
246
|
+
return atom_to_expr(atom)
|
247
|
+
end
|
248
|
+
|
237
249
|
case token
|
238
250
|
when '(' then compile_seq
|
239
251
|
when '{' then compile_union
|
@@ -242,16 +254,10 @@ module RuboCop
|
|
242
254
|
when '$' then compile_capture
|
243
255
|
when '^' then compile_ascend
|
244
256
|
when '`' then compile_descend
|
245
|
-
when WILDCARD then
|
257
|
+
when WILDCARD then compile_new_wildcard(token[1..-1])
|
246
258
|
when FUNCALL then compile_funcall(token)
|
247
|
-
when LITERAL then compile_literal(token)
|
248
259
|
when PREDICATE then compile_predicate(token)
|
249
260
|
when NODE then compile_nodetype(token)
|
250
|
-
when KEYWORD then compile_keyword(token[1..-1])
|
251
|
-
when CONST then compile_const(token[1..-1])
|
252
|
-
when PARAM then compile_param(token[1..-1])
|
253
|
-
when CLOSING then fail_due_to("#{token} in invalid position")
|
254
|
-
when nil then fail_due_to('pattern ended prematurely')
|
255
261
|
else fail_due_to("invalid token #{token.inspect}")
|
256
262
|
end
|
257
263
|
end
|
@@ -260,7 +266,7 @@ module RuboCop
|
|
260
266
|
def tokens_until(stop, what)
|
261
267
|
return to_enum __method__, stop, what unless block_given?
|
262
268
|
|
263
|
-
fail_due_to("empty #{what}") if tokens.first == stop
|
269
|
+
fail_due_to("empty #{what}") if tokens.first == stop
|
264
270
|
yield until tokens.first == stop
|
265
271
|
tokens.shift
|
266
272
|
end
|
@@ -324,11 +330,15 @@ module RuboCop
|
|
324
330
|
# @private
|
325
331
|
# Builds Ruby code for a sequence
|
326
332
|
# (head *first_terms variadic_term *last_terms)
|
327
|
-
class Sequence
|
333
|
+
class Sequence
|
334
|
+
extend Forwardable
|
335
|
+
def_delegators :@compiler, :compile_guard_clause, :with_seq_head_context,
|
336
|
+
:with_child_context, :fail_due_to
|
337
|
+
|
328
338
|
def initialize(compiler, *arity_term_list)
|
329
339
|
@arities, @terms = arity_term_list.transpose
|
330
340
|
|
331
|
-
|
341
|
+
@compiler = compiler
|
332
342
|
@variadic_index = @arities.find_index { |a| a.is_a?(Range) }
|
333
343
|
fail_due_to 'multiple variable patterns in same sequence' \
|
334
344
|
if @variadic_index && !@arities.one? { |a| a.is_a?(Range) }
|
@@ -448,7 +458,6 @@ module RuboCop
|
|
448
458
|
[0..Float::INFINITY, 'true']
|
449
459
|
end
|
450
460
|
|
451
|
-
# rubocop:disable Metrics/AbcSize
|
452
461
|
# rubocop:disable Metrics/MethodLength
|
453
462
|
def compile_any_order(capture_all = nil)
|
454
463
|
rest = capture_rest = nil
|
@@ -468,7 +477,6 @@ module RuboCop
|
|
468
477
|
end
|
469
478
|
end
|
470
479
|
# rubocop:enable Metrics/MethodLength
|
471
|
-
# rubocop:enable Metrics/AbcSize
|
472
480
|
|
473
481
|
def insure_same_captures(enum, what)
|
474
482
|
return to_enum __method__, enum, what unless block_given?
|
@@ -581,29 +589,18 @@ module RuboCop
|
|
581
589
|
end
|
582
590
|
end
|
583
591
|
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
elsif @unify.key?(name)
|
588
|
-
# we have already seen a wildcard with this name before
|
589
|
-
# so the value it matched the first time will already be stored
|
590
|
-
# in a temp. check if this value matches the one stored in the temp
|
591
|
-
"#{CUR_ELEMENT} == #{access_unify(name)}"
|
592
|
-
else
|
593
|
-
n = @unify[name] = "unify_#{name.gsub('-', '__')}"
|
594
|
-
# double assign to avoid "assigned but unused variable"
|
595
|
-
"(#{n} = #{CUR_ELEMENT}; " \
|
596
|
-
"#{n} = #{n}; true)"
|
597
|
-
end
|
598
|
-
end
|
592
|
+
# Known wildcards are considered atoms, see `compile_atom`
|
593
|
+
def compile_new_wildcard(name)
|
594
|
+
return 'true' if name.empty?
|
599
595
|
|
600
|
-
|
601
|
-
|
596
|
+
n = @unify[name] = "unify_#{name.gsub('-', '__')}"
|
597
|
+
# double assign to avoid "assigned but unused variable"
|
598
|
+
"(#{n} = #{CUR_ELEMENT}; #{n} = #{n}; true)"
|
602
599
|
end
|
603
600
|
|
604
601
|
def compile_predicate(predicate)
|
605
602
|
if predicate.end_with?('(') # is there an arglist?
|
606
|
-
args = compile_args
|
603
|
+
args = compile_args
|
607
604
|
predicate = predicate[0..-2] # drop the trailing (
|
608
605
|
"#{CUR_ELEMENT}.#{predicate}(#{args.join(',')})"
|
609
606
|
else
|
@@ -616,7 +613,7 @@ module RuboCop
|
|
616
613
|
# code is used in. pass target value as an argument
|
617
614
|
method = method[1..-1] # drop the leading #
|
618
615
|
if method.end_with?('(') # is there an arglist?
|
619
|
-
args = compile_args
|
616
|
+
args = compile_args
|
620
617
|
method = method[0..-2] # drop the trailing (
|
621
618
|
"#{method}(#{CUR_ELEMENT},#{args.join(',')})"
|
622
619
|
else
|
@@ -628,43 +625,44 @@ module RuboCop
|
|
628
625
|
"#{compile_guard_clause} && #{CUR_NODE}.#{type.tr('-', '_')}_type?"
|
629
626
|
end
|
630
627
|
|
631
|
-
def
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
628
|
+
def compile_args
|
629
|
+
tokens_until(')', 'call arguments').map do
|
630
|
+
arg = compile_arg
|
631
|
+
tokens.shift if tokens.first == ','
|
632
|
+
arg
|
633
|
+
end
|
637
634
|
end
|
638
635
|
|
639
|
-
def
|
640
|
-
"#{
|
636
|
+
def atom_to_expr(atom)
|
637
|
+
"#{atom} === #{CUR_ELEMENT}"
|
641
638
|
end
|
642
639
|
|
643
|
-
def
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
next if [')', ','].include?(token)
|
648
|
-
|
649
|
-
args << compile_arg(token)
|
640
|
+
def expr_to_atom(expr)
|
641
|
+
with_temp_variables do |compare|
|
642
|
+
in_context = with_context(expr, compare, use_temp_node: false)
|
643
|
+
"::RuboCop::AST::NodePattern::Matcher.new{|#{compare}| #{in_context}}"
|
650
644
|
end
|
651
645
|
end
|
652
646
|
|
653
|
-
|
654
|
-
|
647
|
+
# @return compiled atom (e.g. ":literal" or "SOME_CONST")
|
648
|
+
# or nil if not a simple atom (unknown wildcard, other tokens)
|
649
|
+
def compile_atom(token)
|
655
650
|
case token
|
656
|
-
when WILDCARD
|
657
|
-
access_unify(name) || fail_due_to('invalid in arglist: ' + token)
|
651
|
+
when WILDCARD then access_unify(token[1..-1]) # could be nil
|
658
652
|
when LITERAL then token
|
659
|
-
when KEYWORD then get_keyword(
|
660
|
-
when CONST then get_const(
|
661
|
-
when PARAM then get_param(
|
653
|
+
when KEYWORD then get_keyword(token[1..-1])
|
654
|
+
when CONST then get_const(token[1..-1])
|
655
|
+
when PARAM then get_param(token[1..-1])
|
662
656
|
when CLOSING then fail_due_to("#{token} in invalid position")
|
663
657
|
when nil then fail_due_to('pattern ended prematurely')
|
664
|
-
else fail_due_to("invalid token in arglist: #{token.inspect}")
|
665
658
|
end
|
666
659
|
end
|
667
660
|
|
661
|
+
def compile_arg
|
662
|
+
token = tokens.shift
|
663
|
+
compile_atom(token) || expr_to_atom(compile_expr(token))
|
664
|
+
end
|
665
|
+
|
668
666
|
def next_capture
|
669
667
|
index = @captures
|
670
668
|
@captures += 1
|
@@ -716,10 +714,10 @@ module RuboCop
|
|
716
714
|
@keywords.map { |k| format(pattern, keyword: k) }.join(',')
|
717
715
|
end
|
718
716
|
|
719
|
-
def
|
717
|
+
def emit_params(*first, forwarding: false)
|
720
718
|
params = emit_param_list
|
721
719
|
keywords = emit_keyword_list(forwarding: forwarding)
|
722
|
-
[params, keywords].reject(&:empty?).
|
720
|
+
[*first, params, keywords].reject(&:empty?).join(',')
|
723
721
|
end
|
724
722
|
|
725
723
|
def emit_method_code
|
@@ -754,7 +752,7 @@ module RuboCop
|
|
754
752
|
end
|
755
753
|
|
756
754
|
def auto_use_temp_node?(code)
|
757
|
-
code.
|
755
|
+
code.match?(MULTIPLE_CUR_PLACEHOLDER)
|
758
756
|
end
|
759
757
|
|
760
758
|
# with_<...>_context methods are used whenever the context,
|
@@ -793,16 +791,22 @@ module RuboCop
|
|
793
791
|
end
|
794
792
|
|
795
793
|
def self.tokens(pattern)
|
796
|
-
pattern.scan(TOKEN).
|
794
|
+
pattern.gsub(COMMENT, '').scan(TOKEN).grep_v(ONLY_SEPARATOR)
|
795
|
+
end
|
796
|
+
|
797
|
+
# This method minimizes the closure for our method
|
798
|
+
def wrapping_block(method_name, **defaults)
|
799
|
+
proc do |*args, **values|
|
800
|
+
send method_name, *args, **defaults, **values
|
801
|
+
end
|
797
802
|
end
|
798
803
|
|
799
804
|
def def_helper(base, method_name, **defaults)
|
800
805
|
location = caller_locations(3, 1).first
|
801
806
|
unless defaults.empty?
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
method_name = :"without_defaults_#{method_name}"
|
807
|
+
call = :"without_defaults_#{method_name}"
|
808
|
+
base.send :define_method, method_name, &wrapping_block(call, **defaults)
|
809
|
+
method_name = call
|
806
810
|
end
|
807
811
|
src = yield method_name
|
808
812
|
base.class_eval(src, location.path, location.lineno)
|
@@ -811,7 +815,7 @@ module RuboCop
|
|
811
815
|
def def_node_matcher(base, method_name, **defaults)
|
812
816
|
def_helper(base, method_name, **defaults) do |name|
|
813
817
|
<<~RUBY
|
814
|
-
def #{name}(node = self
|
818
|
+
def #{name}(#{emit_params('node = self')})
|
815
819
|
#{emit_method_code}
|
816
820
|
end
|
817
821
|
RUBY
|
@@ -828,20 +832,18 @@ module RuboCop
|
|
828
832
|
if method_name.to_s.end_with?('?')
|
829
833
|
on_match = 'return true'
|
830
834
|
else
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
RUBY
|
835
|
-
on_match = emit_yield_capture('node')
|
835
|
+
args = emit_params(":#{method_name}", @root, forwarding: true)
|
836
|
+
prelude = "return enum_for(#{args}) unless block_given?\n"
|
837
|
+
on_match = emit_yield_capture(@node_var)
|
836
838
|
end
|
837
839
|
emit_node_search_body(method_name, prelude: prelude, on_match: on_match)
|
838
840
|
end
|
839
841
|
|
840
842
|
def emit_node_search_body(method_name, prelude:, on_match:)
|
841
843
|
<<~RUBY
|
842
|
-
def #{method_name}(
|
844
|
+
def #{method_name}(#{emit_params(@root)})
|
843
845
|
#{prelude}
|
844
|
-
|
846
|
+
#{@root}.each_node do |#{@node_var}|
|
845
847
|
if #{match_code}
|
846
848
|
#{on_match}
|
847
849
|
end
|
@@ -874,7 +876,7 @@ module RuboCop
|
|
874
876
|
# as soon as it finds a descendant which matches. Otherwise, it will
|
875
877
|
# yield all descendants which match.
|
876
878
|
def def_node_search(method_name, pattern_str, **keyword_defaults)
|
877
|
-
Compiler.new(pattern_str, 'node')
|
879
|
+
Compiler.new(pattern_str, 'node0', 'node')
|
878
880
|
.def_node_search(self, method_name, **keyword_defaults)
|
879
881
|
end
|
880
882
|
end
|
@@ -883,8 +885,8 @@ module RuboCop
|
|
883
885
|
|
884
886
|
def initialize(str)
|
885
887
|
@pattern = str
|
886
|
-
compiler = Compiler.new(str)
|
887
|
-
src = "def match(
|
888
|
+
compiler = Compiler.new(str, 'node0')
|
889
|
+
src = "def match(#{compiler.emit_params('node0')});" \
|
888
890
|
"#{compiler.emit_method_code}end"
|
889
891
|
instance_eval(src, __FILE__, __LINE__ + 1)
|
890
892
|
end
|
@@ -933,6 +935,17 @@ module RuboCop
|
|
933
935
|
|
934
936
|
nil
|
935
937
|
end
|
938
|
+
|
939
|
+
# @api private
|
940
|
+
class Matcher
|
941
|
+
def initialize(&block)
|
942
|
+
@block = block
|
943
|
+
end
|
944
|
+
|
945
|
+
def ===(compare)
|
946
|
+
@block.call(compare)
|
947
|
+
end
|
948
|
+
end
|
936
949
|
end
|
937
950
|
end
|
938
951
|
end
|