rubocop-ast 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0c196be6c58699fcb84209860508c96e69e8122adb8e14d6641134009802712
4
- data.tar.gz: a2d50c781f612349b4aa7e24a4ea4008c359ce766a6f5e0f7e925ac78d57dbb7
3
+ metadata.gz: 4108494066548a429e972d3f2afcaa9adfd39072a929308cdb20e4a602fef66d
4
+ data.tar.gz: 413b4382539bffed1760d0599bc093ad40ffef64992f821484994150903812da
5
5
  SHA512:
6
- metadata.gz: 2d263c6a0ab978cb1b81dde253ce752c2b64af9e94c59ac6a6a364aef3cec37c03028e4d7c00086c11b8b46abae9373edad636528782f84ad914ea527b644ec0
7
- data.tar.gz: edeefb4a74f678920907e130ea6a7fea86b4c67cf9a9bbaefa21ef231738c0de541d61de5d9c234384e46b3243a74ffaac84288b2c68c49e3178cb39d2d96b37
6
+ metadata.gz: 97af9dd864e8f6867614a79b2aac596e18e147b9a55bcc67b8755e47ef762730ec69612dcc6858ee12017b33beae0b89e008e1267034f1730682015c4efa4255
7
+ data.tar.gz: 807305ab6dc90885fff96d1387d787debf8ce8f27a0d150ad4bf86a76d4afb2d288f0771b2399cf3c3cfb1472c72cd750e4df9e97ad07467da4615a97c062f79
data/README.md CHANGED
@@ -2,12 +2,15 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/rubocop-ast.svg)](https://badge.fury.io/rb/rubocop-ast)
4
4
  [![CI](https://github.com/rubocop-hq/rubocop-ast/workflows/CI/badge.svg)](https://github.com/rubocop-hq/rubocop-ast/actions?query=workflow%3ACI)
5
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/a29666e6373bc41bc0a9/test_coverage)](https://codeclimate.com/github/rubocop-hq/rubocop-ast/test_coverage)
6
+ [![Maintainability](https://api.codeclimate.com/v1/badges/a29666e6373bc41bc0a9/maintainability)](https://codeclimate.com/github/rubocop-hq/rubocop-ast/maintainability)
5
7
 
6
8
  Contains the classes needed by [RuboCop](https://github.com/rubocop-hq/rubocop) to deal with Ruby's AST, in particular:
7
9
  * `RuboCop::AST::Node`
8
- * `RuboCop::AST::NodePattern` ([doc](manual/node_pattern.md))
10
+ * `RuboCop::AST::NodePattern` ([doc](docs/modules/ROOT/pages/node_pattern.adoc))
9
11
 
10
- This gem may be used independently from the main RuboCop gem.
12
+ This gem may be used independently from the main RuboCop gem. It was extracted from RuboCop in version 0.84 and its only
13
+ dependency is the `parser` gem, which `rubocop-ast` extends.
11
14
 
12
15
  ## Installation
13
16
 
@@ -25,7 +28,17 @@ gem 'rubocop-ast'
25
28
 
26
29
  ## Usage
27
30
 
28
- Refer to the documentation of `RuboCop::AST::Node` and [`RuboCop::AST::NodePattern`](manual/node_pattern.md)
31
+ Refer to the documentation of `RuboCop::AST::Node` and [`RuboCop::AST::NodePattern`](docs/modules/ROOT/pages/node_pattern.adoc)
32
+
33
+ ### Parser compatibility switches
34
+
35
+ The main `RuboCop` gem uses [legacy AST output from parser](https://github.com/whitequark/parser/#usage).
36
+ This gem is meant to be compatible with all settings. For example, to have `-> { ... }` emitted
37
+ as `LambdaNode` instead of `SendNode`:
38
+
39
+ ```ruby
40
+ RuboCop::AST::Builder.emit_lambda = true
41
+ ```
29
42
 
30
43
  ## Contributing
31
44
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'parser'
4
4
  require 'forwardable'
5
+ require 'set'
5
6
 
6
7
  require_relative 'ast/node_pattern'
7
8
  require_relative 'ast/sexp'
@@ -34,8 +35,11 @@ require_relative 'ast/node/forward_args_node'
34
35
  require_relative 'ast/node/float_node'
35
36
  require_relative 'ast/node/hash_node'
36
37
  require_relative 'ast/node/if_node'
38
+ require_relative 'ast/node/index_node'
39
+ require_relative 'ast/node/indexasgn_node'
37
40
  require_relative 'ast/node/int_node'
38
41
  require_relative 'ast/node/keyword_splat_node'
42
+ require_relative 'ast/node/lambda_node'
39
43
  require_relative 'ast/node/module_node'
40
44
  require_relative 'ast/node/or_node'
41
45
  require_relative 'ast/node/pair_node'
@@ -35,9 +35,12 @@ module RuboCop
35
35
  hash: HashNode,
36
36
  if: IfNode,
37
37
  int: IntNode,
38
+ index: IndexNode,
39
+ indexasgn: IndexasgnNode,
38
40
  irange: RangeNode,
39
41
  erange: RangeNode,
40
42
  kwsplat: KeywordSplatNode,
43
+ lambda: LambdaNode,
41
44
  module: ModuleNode,
42
45
  or: OrNode,
43
46
  pair: PairNode,
@@ -44,6 +44,8 @@ module RuboCop
44
44
 
45
45
  BASIC_CONDITIONALS = %i[if while until].freeze
46
46
  CONDITIONALS = [*BASIC_CONDITIONALS, :case].freeze
47
+ POST_CONDITION_LOOP_TYPES = %i[while_post until_post].freeze
48
+ LOOP_TYPES = (POST_CONDITION_LOOP_TYPES + %i[while until for]).freeze
47
49
  VARIABLES = %i[ivar gvar cvar lvar].freeze
48
50
  REFERENCES = %i[nth_ref back_ref].freeze
49
51
  KEYWORDS = %i[alias and break case class def defs defined?
@@ -53,6 +55,7 @@ module RuboCop
53
55
  yield].freeze
54
56
  OPERATOR_KEYWORDS = %i[and or].freeze
55
57
  SPECIAL_KEYWORDS = %w[__FILE__ __LINE__ __ENCODING__].freeze
58
+ ARGUMENT_TYPES = %i[arg optarg restarg kwarg kwoptarg kwrestarg blockarg].freeze
56
59
 
57
60
  # @see https://www.rubydoc.info/gems/ast/AST/Node:initialize
58
61
  def initialize(type, children = [], properties = {})
@@ -425,6 +428,15 @@ module RuboCop
425
428
  CONDITIONALS.include?(type)
426
429
  end
427
430
 
431
+ def post_condition_loop?
432
+ POST_CONDITION_LOOP_TYPES.include?(type)
433
+ end
434
+
435
+ # Note: `loop { }` is a normal method call and thus not a loop keyword.
436
+ def loop_keyword?
437
+ LOOP_TYPES.include?(type)
438
+ end
439
+
428
440
  def keyword?
429
441
  return true if special_keyword? || send_type? && prefix_not?
430
442
  return false unless KEYWORDS.include?(type)
@@ -456,6 +468,10 @@ module RuboCop
456
468
  parent&.send_type? && parent.arguments.include?(self)
457
469
  end
458
470
 
471
+ def argument_type?
472
+ ARGUMENT_TYPES.include?(type)
473
+ end
474
+
459
475
  def boolean_type?
460
476
  true_type? || false_type?
461
477
  end
@@ -500,7 +516,7 @@ module RuboCop
500
516
  # So, does the return value of this node matter? If we changed it to
501
517
  # `(...; nil)`, might that affect anything?
502
518
  #
503
- # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
519
+ # rubocop:disable Metrics/MethodLength
504
520
  def value_used?
505
521
  # Be conservative and return true if we're not sure.
506
522
  return false if parent.nil?
@@ -522,7 +538,7 @@ module RuboCop
522
538
  true
523
539
  end
524
540
  end
525
- # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
541
+ # rubocop:enable Metrics/MethodLength
526
542
 
527
543
  # Some expressions are evaluated for their value, some for their side
528
544
  # effects, and some for both.
@@ -24,7 +24,7 @@ module RuboCop
24
24
  #
25
25
  # @return [Boolean] whether the `def` node uses argument forwarding
26
26
  def argument_forwarding?
27
- arguments.any?(&:forward_args_type?)
27
+ arguments.any?(&:forward_args_type?) || arguments.any?(&:forward_arg_type?)
28
28
  end
29
29
 
30
30
  # The name of the defined method as a symbol.
@@ -5,6 +5,21 @@ module RuboCop
5
5
  # A node extension for `forward-args` nodes. This will be used in place
6
6
  # of a plain node when the builder constructs the AST, making its methods
7
7
  # available to all `forward-args` nodes within RuboCop.
8
+ #
9
+ # Not used with modern emitters:
10
+ #
11
+ # $ ruby-parse -e "def foo(...); end"
12
+ # (def :foo
13
+ # (args
14
+ # (forward-arg)) nil)
15
+ # $ ruby-parse --legacy -e "->(foo) { bar }"
16
+ # (def :foo
17
+ # (forward-args) nil)
18
+ #
19
+ # Note the extra 's' with legacy form.
20
+ #
21
+ # The main RuboCop runs in legacy mode; this node is only used
22
+ # if user `AST::Builder.modernize` or `AST::Builder.emit_lambda=true`
8
23
  class ForwardArgsNode < Node
9
24
  include CollectionNode
10
25
 
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module AST
5
+ # Used for modern support only!
6
+ # Not as thoroughly tested as legacy equivalent
7
+ #
8
+ # $ ruby-parse -e "foo[:bar]"
9
+ # (index
10
+ # (send nil :foo)
11
+ # (sym :bar))
12
+ # $ ruby-parse --legacy -e "foo[:bar]"
13
+ # (send
14
+ # (send nil :foo) :[]
15
+ # (sym :bar))
16
+ #
17
+ # The main RuboCop runs in legacy mode; this node is only used
18
+ # if user `AST::Builder.modernize` or `AST::Builder.emit_index=true`
19
+ class IndexNode < Node
20
+ include ParameterizedNode
21
+ include MethodDispatchNode
22
+
23
+ # For similarity with legacy mode
24
+ def attribute_accessor?
25
+ false
26
+ end
27
+
28
+ # For similarity with legacy mode
29
+ def assignment_method?
30
+ false
31
+ end
32
+
33
+ # For similarity with legacy mode
34
+ def method_name
35
+ :[]
36
+ end
37
+
38
+ # An array containing the arguments of the dispatched method.
39
+ #
40
+ # @return [Array<Node>] the arguments of the dispatched method
41
+ def arguments
42
+ node_parts[1..-1]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module AST
5
+ # Used for modern support only!
6
+ # Not as thoroughly tested as legacy equivalent
7
+ #
8
+ # $ ruby-parse -e "foo[:bar] = :baz"
9
+ # (indexasgn
10
+ # (send nil :foo)
11
+ # (sym :bar)
12
+ # (sym :baz))
13
+ # $ ruby-parse --legacy -e "foo[:bar] = :baz"
14
+ # (send
15
+ # (send nil :foo) :[]=
16
+ # (sym :bar)
17
+ # (sym :baz))
18
+ #
19
+ # The main RuboCop runs in legacy mode; this node is only used
20
+ # if user `AST::Builder.modernize` or `AST::Builder.emit_index=true`
21
+ class IndexasgnNode < Node
22
+ include ParameterizedNode
23
+ include MethodDispatchNode
24
+
25
+ # For similarity with legacy mode
26
+ def attribute_accessor?
27
+ false
28
+ end
29
+
30
+ # For similarity with legacy mode
31
+ def assignment_method?
32
+ true
33
+ end
34
+
35
+ # For similarity with legacy mode
36
+ def method_name
37
+ :[]=
38
+ end
39
+
40
+ # An array containing the arguments of the dispatched method.
41
+ #
42
+ # @return [Array<Node>] the arguments of the dispatched method
43
+ def arguments
44
+ node_parts[1..-1]
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module AST
5
+ # Used for modern support only:
6
+ # Not as thoroughly tested as legacy equivalent
7
+ #
8
+ # $ ruby-parse -e "->(foo) { bar }"
9
+ # (block
10
+ # (lambda)
11
+ # (args
12
+ # (arg :foo))
13
+ # (send nil :bar))
14
+ # $ ruby-parse --legacy -e "->(foo) { bar }"
15
+ # (block
16
+ # (send nil :lambda)
17
+ # (args
18
+ # (arg :foo))
19
+ # (send nil :bar))
20
+ #
21
+ # The main RuboCop runs in legacy mode; this node is only used
22
+ # if user `AST::Builder.modernize` or `AST::Builder.emit_lambda=true`
23
+ class LambdaNode < Node
24
+ include ParameterizedNode
25
+ include MethodDispatchNode
26
+
27
+ # For similarity with legacy mode
28
+ def lambda?
29
+ true
30
+ end
31
+
32
+ # For similarity with legacy mode
33
+ def lambda_literal?
34
+ true
35
+ end
36
+
37
+ # For similarity with legacy mode
38
+ def attribute_accessor?
39
+ false
40
+ end
41
+
42
+ # For similarity with legacy mode
43
+ def assignment_method?
44
+ false
45
+ end
46
+
47
+ # For similarity with legacy mode
48
+ def method_name
49
+ :lambda
50
+ end
51
+
52
+ # For similarity with legacy mode
53
+ def arguments
54
+ []
55
+ end
56
+ end
57
+ end
58
+ end
@@ -3,7 +3,8 @@
3
3
  module RuboCop
4
4
  module AST
5
5
  # Common functionality for nodes that are a kind of method dispatch:
6
- # `send`, `csend`, `super`, `zsuper`, `yield`, `defined?`
6
+ # `send`, `csend`, `super`, `zsuper`, `yield`, `defined?`,
7
+ # and (modern only): `index`, `indexasgn`, `lambda`
7
8
  module MethodDispatchNode
8
9
  extend NodePattern::Macros
9
10
  include MethodIdentifierPredicates
@@ -6,15 +6,62 @@ module RuboCop
6
6
  # `send`, `csend`, `def`, `defs`, `super`, `zsuper`
7
7
  #
8
8
  # @note this mixin expects `#method_name` and `#receiver` to be implemented
9
- module MethodIdentifierPredicates
9
+ module MethodIdentifierPredicates # rubocop:disable Metrics/ModuleLength
10
10
  ENUMERATOR_METHODS = %i[collect collect_concat detect downto each
11
11
  find find_all find_index inject loop map!
12
12
  map reduce reject reject! reverse_each select
13
- select! times upto].freeze
13
+ select! times upto].to_set.freeze
14
+
15
+ ENUMERABLE_METHODS = (Enumerable.instance_methods + [:each]).to_set.freeze
14
16
 
15
17
  # http://phrogz.net/programmingruby/language.html#table_18.4
16
18
  OPERATOR_METHODS = %i[| ^ & <=> == === =~ > >= < <= << >> + - * /
17
- % ** ~ +@ -@ !@ ~@ [] []= ! != !~ `].freeze
19
+ % ** ~ +@ -@ !@ ~@ [] []= ! != !~ `].to_set.freeze
20
+
21
+ NONMUTATING_BINARY_OPERATOR_METHODS = %i[* / % + - == === != < > <= >= <=>].to_set.freeze
22
+ NONMUTATING_UNARY_OPERATOR_METHODS = %i[+@ -@ ~ !].to_set.freeze
23
+ NONMUTATING_OPERATOR_METHODS = (NONMUTATING_BINARY_OPERATOR_METHODS +
24
+ NONMUTATING_UNARY_OPERATOR_METHODS).freeze
25
+
26
+ NONMUTATING_ARRAY_METHODS = %i[
27
+ all? any? assoc at bsearch bsearch_index collect
28
+ combination compact count cycle deconstruct difference
29
+ dig drop drop_while each each_index empty? eql?
30
+ fetch filter find_index first flatten hash
31
+ include? index inspect intersection join
32
+ last length map max min minmax none? one? pack
33
+ permutation product rassoc reject
34
+ repeated_combination repeated_permutation reverse
35
+ reverse_each rindex rotate sample select shuffle
36
+ size slice sort sum take take_while
37
+ to_a to_ary to_h to_s transpose union uniq
38
+ values_at zip |
39
+ ].to_set.freeze
40
+
41
+ NONMUTATING_HASH_METHODS = %i[
42
+ any? assoc compact dig each each_key each_pair
43
+ each_value empty? eql? fetch fetch_values filter
44
+ flatten has_key? has_value? hash include? inspect
45
+ invert key key? keys? length member? merge rassoc
46
+ rehash reject select size slice to_a to_h to_hash
47
+ to_proc to_s transform_keys transform_values value?
48
+ values values_at
49
+ ].to_set.freeze
50
+
51
+ NONMUTATING_STRING_METHODS = %i[
52
+ ascii_only? b bytes bytesize byteslice capitalize
53
+ casecmp casecmp? center chars chomp chop chr codepoints
54
+ count crypt delete delete_prefix delete_suffix
55
+ downcase dump each_byte each_char each_codepoint
56
+ each_grapheme_cluster each_line empty? encode encoding
57
+ end_with? eql? getbyte grapheme_clusters gsub hash
58
+ hex include index inspect intern length lines ljust lstrip
59
+ match match? next oct ord partition reverse rindex rjust
60
+ rpartition rstrip scan scrub size slice squeeze start_with?
61
+ strip sub succ sum swapcase to_a to_c to_f to_i to_r to_s
62
+ to_str to_sym tr tr_s unicode_normalize unicode_normalized?
63
+ unpack unpack1 upcase upto valid_encoding?
64
+ ].to_set.freeze
18
65
 
19
66
  # Checks whether the method name matches the argument.
20
67
  #
@@ -31,6 +78,48 @@ module RuboCop
31
78
  OPERATOR_METHODS.include?(method_name)
32
79
  end
33
80
 
81
+ # Checks whether the method is a nonmutating binary operator method.
82
+ #
83
+ # @return [Boolean] whether the method is a nonmutating binary operator method
84
+ def nonmutating_binary_operator_method?
85
+ NONMUTATING_BINARY_OPERATOR_METHODS.include?(method_name)
86
+ end
87
+
88
+ # Checks whether the method is a nonmutating unary operator method.
89
+ #
90
+ # @return [Boolean] whether the method is a nonmutating unary operator method
91
+ def nonmutating_unary_operator_method?
92
+ NONMUTATING_UNARY_OPERATOR_METHODS.include?(method_name)
93
+ end
94
+
95
+ # Checks whether the method is a nonmutating operator method.
96
+ #
97
+ # @return [Boolean] whether the method is a nonmutating operator method
98
+ def nonmutating_operator_method?
99
+ NONMUTATING_OPERATOR_METHODS.include?(method_name)
100
+ end
101
+
102
+ # Checks whether the method is a nonmutating Array method.
103
+ #
104
+ # @return [Boolean] whether the method is a nonmutating Array method
105
+ def nonmutating_array_method?
106
+ NONMUTATING_ARRAY_METHODS.include?(method_name)
107
+ end
108
+
109
+ # Checks whether the method is a nonmutating Hash method.
110
+ #
111
+ # @return [Boolean] whether the method is a nonmutating Hash method
112
+ def nonmutating_hash_method?
113
+ NONMUTATING_HASH_METHODS.include?(method_name)
114
+ end
115
+
116
+ # Checks whether the method is a nonmutating String method.
117
+ #
118
+ # @return [Boolean] whether the method is a nonmutating String method
119
+ def nonmutating_string_method?
120
+ NONMUTATING_STRING_METHODS.include?(method_name)
121
+ end
122
+
34
123
  # Checks whether the method is a comparison method.
35
124
  #
36
125
  # @return [Boolean] whether the method is a comparison
@@ -53,6 +142,13 @@ module RuboCop
53
142
  method_name.to_s.start_with?('each_')
54
143
  end
55
144
 
145
+ # Checks whether the method is an Enumerable method.
146
+ #
147
+ # @return [Boolean] whether the method is an Enumerable method
148
+ def enumerable_method?
149
+ ENUMERABLE_METHODS.include?(method_name)
150
+ end
151
+
56
152
  # Checks whether the method is a predicate method.
57
153
  #
58
154
  # @return [Boolean] whether the method is a predicate method
@@ -4,6 +4,7 @@ module RuboCop
4
4
  module AST
5
5
  # Common functionality for nodes that are parameterized:
6
6
  # `send`, `super`, `zsuper`, `def`, `defs`
7
+ # and (modern only): `index`, `indexasgn`, `lambda`
7
8
  module ParameterizedNode
8
9
  # Checks whether this node's arguments are wrapped in parentheses.
9
10
  #
@@ -31,6 +31,62 @@ module RuboCop
31
31
  def content
32
32
  children.select(&:str_type?).map(&:str_content).join
33
33
  end
34
+
35
+ # @return [Bool] if the regexp is a /.../ literal
36
+ def slash_literal?
37
+ loc.begin.source == '/'
38
+ end
39
+
40
+ # @return [Bool] if the regexp is a %r{...} literal (using any delimiters)
41
+ def percent_r_literal?
42
+ !slash_literal?
43
+ end
44
+
45
+ # @return [String] the regexp delimiters (without %r)
46
+ def delimiters
47
+ [loc.begin.source[-1], loc.end.source[0]]
48
+ end
49
+
50
+ # @return [Bool] if char is one of the delimiters
51
+ def delimiter?(char)
52
+ delimiters.include?(char)
53
+ end
54
+
55
+ # @return [Bool] if regexp contains interpolation
56
+ def interpolation?
57
+ children.any?(&:begin_type?)
58
+ end
59
+
60
+ # @return [Bool] if regexp uses the multiline regopt
61
+ def multiline_mode?
62
+ regopt_include?(:m)
63
+ end
64
+
65
+ # @return [Bool] if regexp uses the extended regopt
66
+ def extended?
67
+ regopt_include?(:x)
68
+ end
69
+
70
+ # @return [Bool] if regexp uses the ignore-case regopt
71
+ def ignore_case?
72
+ regopt_include?(:i)
73
+ end
74
+
75
+ # @return [Bool] if regexp uses the single-interpolation regopt
76
+ def single_interpolation?
77
+ regopt_include?(:o)
78
+ end
79
+
80
+ # @return [Bool] if regexp uses the no-encoding regopt
81
+ def no_encoding?
82
+ regopt_include?(:n)
83
+ end
84
+
85
+ private
86
+
87
+ def regopt_include?(option)
88
+ regopt.children.include?(option)
89
+ end
34
90
  end
35
91
  end
36
92
  end
@@ -10,7 +10,8 @@ module RuboCop
10
10
  include MethodDispatchNode
11
11
 
12
12
  def_node_matcher :attribute_accessor?, <<~PATTERN
13
- (send nil? ${:attr_reader :attr_writer :attr_accessor :attr} $...)
13
+ [(send nil? ${:attr_reader :attr_writer :attr_accessor :attr} $...)
14
+ (_ _ _ _ ...)]
14
15
  PATTERN
15
16
  end
16
17
  end
@@ -70,13 +70,23 @@ module RuboCop
70
70
  # '(send %1 _)' # % stands for a parameter which must be supplied to
71
71
  # # #match at matching time
72
72
  # # it will be compared to the corresponding value in
73
- # # the AST using #==
73
+ # # the AST using #=== so you can pass Procs, Regexp,
74
+ # # etc. in addition to Nodes or literals.
75
+ # # `Array#===` will never match a node element, but
76
+ # # `Set#===` is an alias to `Set#include?` (Ruby 2.5+
77
+ # # only), and so can be very useful to match within
78
+ # # many possible literals / Nodes.
74
79
  # # a bare '%' is the same as '%1'
75
80
  # # the number of extra parameters passed to #match
76
81
  # # must equal the highest % value in the pattern
77
82
  # # for consistency, %0 is the 'root node' which is
78
83
  # # passed as the 1st argument to #match, where the
79
84
  # # matching process starts
85
+ # '(send _ %named)' # arguments can also be passed as named
86
+ # # parameters (see `%1`)
87
+ # # Note that the macros `def_node_pattern` and
88
+ # # `def_node_search` accept default values for these.
89
+ # '(send _ %CONST)' # the named constant will act like `%1` and `%named`.
80
90
  # '^^send' # each ^ ascends one level in the AST
81
91
  # # so this matches against the grandparent node
82
92
  # '`send' # descends any number of level in the AST
@@ -119,11 +129,13 @@ module RuboCop
119
129
  ).freeze
120
130
  NUMBER = /-?\d+(?:\.\d+)?/.freeze
121
131
  STRING = /".+?"/.freeze
122
- METHOD_NAME = /\#?#{IDENTIFIER}[\!\?]?\(?/.freeze
132
+ METHOD_NAME = /\#?#{IDENTIFIER}[!?]?\(?/.freeze
133
+ PARAM_CONST = /%[A-Z:][a-zA-Z_:]+/.freeze
134
+ KEYWORD_NAME = /%[a-z_]+/.freeze
123
135
  PARAM_NUMBER = /%\d*/.freeze
124
136
 
125
- SEPARATORS = /[\s]+/.freeze
126
- TOKENS = Regexp.union(META, PARAM_NUMBER, NUMBER,
137
+ SEPARATORS = /\s+/.freeze
138
+ TOKENS = Regexp.union(META, PARAM_CONST, KEYWORD_NAME, PARAM_NUMBER, NUMBER,
127
139
  METHOD_NAME, SYMBOL, STRING)
128
140
 
129
141
  TOKEN = /\G(?:#{SEPARATORS}|#{TOKENS}|.)/.freeze
@@ -135,6 +147,8 @@ module RuboCop
135
147
  FUNCALL = /\A\##{METHOD_NAME}/.freeze
136
148
  LITERAL = /\A(?:#{SYMBOL}|#{NUMBER}|#{STRING})\Z/.freeze
137
149
  PARAM = /\A#{PARAM_NUMBER}\Z/.freeze
150
+ CONST = /\A#{PARAM_CONST}\Z/.freeze
151
+ KEYWORD = /\A#{KEYWORD_NAME}\Z/.freeze
138
152
  CLOSING = /\A(?:\)|\}|\])\Z/.freeze
139
153
 
140
154
  REST = '...'
@@ -193,6 +207,7 @@ module RuboCop
193
207
  @captures = 0 # number of captures seen
194
208
  @unify = {} # named wildcard -> temp variable
195
209
  @params = 0 # highest % (param) number seen
210
+ @keywords = Set[] # keyword parameters seen
196
211
  run(node_var)
197
212
  end
198
213
 
@@ -232,6 +247,8 @@ module RuboCop
232
247
  when LITERAL then compile_literal(token)
233
248
  when PREDICATE then compile_predicate(token)
234
249
  when NODE then compile_nodetype(token)
250
+ when KEYWORD then compile_keyword(token[1..-1])
251
+ when CONST then compile_const(token[1..-1])
235
252
  when PARAM then compile_param(token[1..-1])
236
253
  when CLOSING then fail_due_to("#{token} in invalid position")
237
254
  when nil then fail_due_to('pattern ended prematurely')
@@ -612,7 +629,15 @@ module RuboCop
612
629
  end
613
630
 
614
631
  def compile_param(number)
615
- "#{CUR_ELEMENT} == #{get_param(number)}"
632
+ "#{get_param(number)} === #{CUR_ELEMENT}"
633
+ end
634
+
635
+ def compile_const(const)
636
+ "#{get_const(const)} === #{CUR_ELEMENT}"
637
+ end
638
+
639
+ def compile_keyword(keyword)
640
+ "#{get_keyword(keyword)} === #{CUR_ELEMENT}"
616
641
  end
617
642
 
618
643
  def compile_args(tokens)
@@ -626,12 +651,14 @@ module RuboCop
626
651
  end
627
652
 
628
653
  def compile_arg(token)
654
+ name = token[1..-1]
629
655
  case token
630
- when WILDCARD then
631
- name = token[1..-1]
656
+ when WILDCARD
632
657
  access_unify(name) || fail_due_to('invalid in arglist: ' + token)
633
658
  when LITERAL then token
634
- when PARAM then get_param(token[1..-1])
659
+ when KEYWORD then get_keyword(name)
660
+ when CONST then get_const(name)
661
+ when PARAM then get_param(name)
635
662
  when CLOSING then fail_due_to("#{token} in invalid position")
636
663
  when nil then fail_due_to('pattern ended prematurely')
637
664
  else fail_due_to("invalid token in arglist: #{token.inspect}")
@@ -650,6 +677,15 @@ module RuboCop
650
677
  number.zero? ? @root : "param#{number}"
651
678
  end
652
679
 
680
+ def get_keyword(name)
681
+ @keywords << name
682
+ name
683
+ end
684
+
685
+ def get_const(const)
686
+ const # Output the constant exactly as given
687
+ end
688
+
653
689
  def emit_yield_capture(when_no_capture = '')
654
690
  yield_val = if @captures.zero?
655
691
  when_no_capture
@@ -675,9 +711,15 @@ module RuboCop
675
711
  (1..@params).map { |n| "param#{n}" }.join(',')
676
712
  end
677
713
 
678
- def emit_trailing_params
714
+ def emit_keyword_list(forwarding: false)
715
+ pattern = "%<keyword>s: #{'%<keyword>s' if forwarding}"
716
+ @keywords.map { |k| format(pattern, keyword: k) }.join(',')
717
+ end
718
+
719
+ def emit_trailing_params(forwarding: false)
679
720
  params = emit_param_list
680
- params.empty? ? '' : ",#{params}"
721
+ keywords = emit_keyword_list(forwarding: forwarding)
722
+ [params, keywords].reject(&:empty?).map { |p| ", #{p}" }.join
681
723
  end
682
724
 
683
725
  def emit_method_code
@@ -753,68 +795,51 @@ module RuboCop
753
795
  def self.tokens(pattern)
754
796
  pattern.scan(TOKEN).reject { |token| token =~ /\A#{SEPARATORS}\Z/ }
755
797
  end
756
- end
757
- private_constant :Compiler
758
-
759
- # Helpers for defining methods based on a pattern string
760
- module Macros
761
- # Define a method which applies a pattern to an AST node
762
- #
763
- # The new method will return nil if the node does not match
764
- # If the node matches, and a block is provided, the new method will
765
- # yield to the block (passing any captures as block arguments).
766
- # If the node matches, and no block is provided, the new method will
767
- # return the captures, or `true` if there were none.
768
- def def_node_matcher(method_name, pattern_str)
769
- compiler = Compiler.new(pattern_str, 'node')
770
- src = "def #{method_name}(node = self" \
771
- "#{compiler.emit_trailing_params});" \
772
- "#{compiler.emit_method_code};end"
773
-
774
- location = caller_locations(1, 1).first
775
- class_eval(src, location.path, location.lineno)
776
- end
777
798
 
778
- # Define a method which recurses over the descendants of an AST node,
779
- # checking whether any of them match the provided pattern
780
- #
781
- # If the method name ends with '?', the new method will return `true`
782
- # as soon as it finds a descendant which matches. Otherwise, it will
783
- # yield all descendants which match.
784
- def def_node_search(method_name, pattern_str)
785
- compiler = Compiler.new(pattern_str, 'node')
786
- called_from = caller(1..1).first.split(':')
787
-
788
- if method_name.to_s.end_with?('?')
789
- node_search_first(method_name, compiler, called_from)
790
- else
791
- node_search_all(method_name, compiler, called_from)
799
+ def def_helper(base, method_name, **defaults)
800
+ location = caller_locations(3, 1).first
801
+ unless defaults.empty?
802
+ base.send :define_method, method_name do |*args, **values|
803
+ send method_name, *args, **defaults, **values
804
+ end
805
+ method_name = :"without_defaults_#{method_name}"
792
806
  end
807
+ src = yield method_name
808
+ base.class_eval(src, location.path, location.lineno)
793
809
  end
794
810
 
795
- def node_search_first(method_name, compiler, called_from)
796
- node_search(method_name, compiler, 'return true', '', called_from)
811
+ def def_node_matcher(base, method_name, **defaults)
812
+ def_helper(base, method_name, **defaults) do |name|
813
+ <<~RUBY
814
+ def #{name}(node = self#{emit_trailing_params})
815
+ #{emit_method_code}
816
+ end
817
+ RUBY
818
+ end
797
819
  end
798
820
 
799
- def node_search_all(method_name, compiler, called_from)
800
- yield_code = compiler.emit_yield_capture('node')
801
- prelude = "return enum_for(:#{method_name}, node0" \
802
- "#{compiler.emit_trailing_params}) unless block_given?"
803
-
804
- node_search(method_name, compiler, yield_code, prelude, called_from)
821
+ def def_node_search(base, method_name, **defaults)
822
+ def_helper(base, method_name, **defaults) do |name|
823
+ emit_node_search(name)
824
+ end
805
825
  end
806
826
 
807
- def node_search(method_name, compiler, on_match, prelude, called_from)
808
- src = node_search_body(method_name, compiler.emit_trailing_params,
809
- prelude, compiler.match_code, on_match)
810
- filename, lineno = *called_from
811
- class_eval(src, filename, lineno.to_i)
827
+ def emit_node_search(method_name)
828
+ if method_name.to_s.end_with?('?')
829
+ on_match = 'return true'
830
+ else
831
+ prelude = <<~RUBY
832
+ return enum_for(:#{method_name},
833
+ node0#{emit_trailing_params(forwarding: true)}) unless block_given?
834
+ RUBY
835
+ on_match = emit_yield_capture('node')
836
+ end
837
+ emit_node_search_body(method_name, prelude: prelude, on_match: on_match)
812
838
  end
813
839
 
814
- def node_search_body(method_name, trailing_params, prelude, match_code,
815
- on_match)
840
+ def emit_node_search_body(method_name, prelude:, on_match:)
816
841
  <<~RUBY
817
- def #{method_name}(node0#{trailing_params})
842
+ def #{method_name}(node0#{emit_trailing_params})
818
843
  #{prelude}
819
844
  node0.each_node do |node|
820
845
  if #{match_code}
@@ -826,6 +851,33 @@ module RuboCop
826
851
  RUBY
827
852
  end
828
853
  end
854
+ private_constant :Compiler
855
+
856
+ # Helpers for defining methods based on a pattern string
857
+ module Macros
858
+ # Define a method which applies a pattern to an AST node
859
+ #
860
+ # The new method will return nil if the node does not match
861
+ # If the node matches, and a block is provided, the new method will
862
+ # yield to the block (passing any captures as block arguments).
863
+ # If the node matches, and no block is provided, the new method will
864
+ # return the captures, or `true` if there were none.
865
+ def def_node_matcher(method_name, pattern_str, **keyword_defaults)
866
+ Compiler.new(pattern_str, 'node')
867
+ .def_node_matcher(self, method_name, **keyword_defaults)
868
+ end
869
+
870
+ # Define a method which recurses over the descendants of an AST node,
871
+ # checking whether any of them match the provided pattern
872
+ #
873
+ # If the method name ends with '?', the new method will return `true`
874
+ # as soon as it finds a descendant which matches. Otherwise, it will
875
+ # yield all descendants which match.
876
+ def def_node_search(method_name, pattern_str, **keyword_defaults)
877
+ Compiler.new(pattern_str, 'node')
878
+ .def_node_search(self, method_name, **keyword_defaults)
879
+ end
880
+ end
829
881
 
830
882
  attr_reader :pattern
831
883
 
@@ -837,11 +889,15 @@ module RuboCop
837
889
  instance_eval(src, __FILE__, __LINE__ + 1)
838
890
  end
839
891
 
840
- def match(*args)
892
+ def match(*args, **rest)
841
893
  # If we're here, it's because the singleton method has not been defined,
842
894
  # either because we've been dup'ed or serialized through YAML
843
895
  initialize(pattern)
844
- match(*args)
896
+ if rest.empty?
897
+ match(*args)
898
+ else
899
+ match(*args, **rest)
900
+ end
845
901
  end
846
902
 
847
903
  def marshal_load(pattern)
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'digest/sha1'
4
4
 
5
+ # rubocop:disable Metrics/ClassLength
5
6
  module RuboCop
6
7
  module AST
7
8
  # ProcessedSource contains objects which are generated by Parser
@@ -176,6 +177,9 @@ module RuboCop
176
177
  when 2.7
177
178
  require 'parser/ruby27'
178
179
  Parser::Ruby27
180
+ when 2.8
181
+ require 'parser/ruby28'
182
+ Parser::Ruby28
179
183
  else
180
184
  raise ArgumentError,
181
185
  "RuboCop found unknown Ruby version: #{ruby_version.inspect}"
@@ -201,3 +205,4 @@ module RuboCop
201
205
  end
202
206
  end
203
207
  end
208
+ # rubocop:enable Metrics/ClassLength
@@ -19,9 +19,10 @@ module RuboCop
19
19
  rational str sym regopt self lvar
20
20
  ivar cvar gvar nth_ref back_ref cbase
21
21
  arg restarg blockarg shadowarg
22
- kwrestarg zsuper lambda redo retry
22
+ kwrestarg zsuper redo retry
23
23
  forward_args forwarded_args
24
- match_var match_nil_pattern empty_else].freeze
24
+ match_var match_nil_pattern empty_else
25
+ forward_arg lambda procarg0 __ENCODING__].freeze
25
26
  ONE_CHILD_NODE = %i[splat kwsplat block_pass not break next
26
27
  preexe postexe match_current_line defined?
27
28
  arg_expr pin match_rest if_guard unless_guard
@@ -33,7 +34,8 @@ module RuboCop
33
34
  match_with_lvasgn begin kwbegin return
34
35
  in_match match_alt
35
36
  match_as array_pattern array_pattern_with_tail
36
- hash_pattern const_pattern].freeze
37
+ hash_pattern const_pattern
38
+ index indexasgn].freeze
37
39
  SECOND_CHILD_ONLY = %i[lvasgn ivasgn cvasgn gvasgn optarg kwarg
38
40
  kwoptarg].freeze
39
41
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module AST
5
5
  module Version
6
- STRING = '0.0.3'
6
+ STRING = '0.1.0'
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-ast
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2020-05-15 00:00:00.000000000 Z
13
+ date: 2020-06-26 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: parser
@@ -77,8 +77,11 @@ files:
77
77
  - lib/rubocop/ast/node/forward_args_node.rb
78
78
  - lib/rubocop/ast/node/hash_node.rb
79
79
  - lib/rubocop/ast/node/if_node.rb
80
+ - lib/rubocop/ast/node/index_node.rb
81
+ - lib/rubocop/ast/node/indexasgn_node.rb
80
82
  - lib/rubocop/ast/node/int_node.rb
81
83
  - lib/rubocop/ast/node/keyword_splat_node.rb
84
+ - lib/rubocop/ast/node/lambda_node.rb
82
85
  - lib/rubocop/ast/node/mixin/basic_literal_node.rb
83
86
  - lib/rubocop/ast/node/mixin/binary_operator_node.rb
84
87
  - lib/rubocop/ast/node/mixin/collection_node.rb
@@ -120,7 +123,7 @@ metadata:
120
123
  homepage_uri: https://www.rubocop.org/
121
124
  changelog_uri: https://github.com/rubocop-hq/rubocop-ast/blob/master/CHANGELOG.md
122
125
  source_code_uri: https://github.com/rubocop-hq/rubocop-ast/
123
- documentation_uri: https://docs.rubocop.org/
126
+ documentation_uri: https://docs.rubocop.org/rubocop-ast/
124
127
  bug_tracker_uri: https://github.com/rubocop-hq/rubocop-ast/issues
125
128
  post_install_message:
126
129
  rdoc_options: []