rubocop-ast 0.2.0 → 0.5.0

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. data/lib/rubocop/ast.rb +5 -1
  4. data/lib/rubocop/ast/builder.rb +5 -1
  5. data/lib/rubocop/ast/ext/range.rb +28 -0
  6. data/lib/rubocop/ast/ext/set.rb +12 -0
  7. data/lib/rubocop/ast/node.rb +53 -3
  8. data/lib/rubocop/ast/node/block_node.rb +1 -1
  9. data/lib/rubocop/ast/node/break_node.rb +1 -6
  10. data/lib/rubocop/ast/node/case_match_node.rb +2 -4
  11. data/lib/rubocop/ast/node/case_node.rb +12 -4
  12. data/lib/rubocop/ast/node/const_node.rb +65 -0
  13. data/lib/rubocop/ast/node/defined_node.rb +2 -0
  14. data/lib/rubocop/ast/node/float_node.rb +1 -0
  15. data/lib/rubocop/ast/node/hash_node.rb +4 -8
  16. data/lib/rubocop/ast/node/if_node.rb +3 -5
  17. data/lib/rubocop/ast/node/index_node.rb +5 -3
  18. data/lib/rubocop/ast/node/indexasgn_node.rb +5 -3
  19. data/lib/rubocop/ast/node/int_node.rb +1 -0
  20. data/lib/rubocop/ast/node/lambda_node.rb +10 -3
  21. data/lib/rubocop/ast/node/mixin/method_dispatch_node.rb +0 -7
  22. data/lib/rubocop/ast/node/mixin/parameterized_node.rb +55 -0
  23. data/lib/rubocop/ast/node/next_node.rb +12 -0
  24. data/lib/rubocop/ast/node/pair_node.rb +2 -2
  25. data/lib/rubocop/ast/node/resbody_node.rb +21 -0
  26. data/lib/rubocop/ast/node/rescue_node.rb +49 -0
  27. data/lib/rubocop/ast/node/return_node.rb +1 -13
  28. data/lib/rubocop/ast/node/send_node.rb +7 -1
  29. data/lib/rubocop/ast/node/super_node.rb +2 -0
  30. data/lib/rubocop/ast/node/when_node.rb +2 -4
  31. data/lib/rubocop/ast/node/yield_node.rb +2 -0
  32. data/lib/rubocop/ast/node_pattern.rb +5 -4
  33. data/lib/rubocop/ast/processed_source.rb +86 -22
  34. data/lib/rubocop/ast/traversal.rb +1 -1
  35. data/lib/rubocop/ast/version.rb +1 -1
  36. metadata +9 -5
  37. data/lib/rubocop/ast/node/retry_node.rb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d213ca265b42f0b66394057ee01a4a4573a28da98e7fc8caf52b5396fe098381
4
- data.tar.gz: 3a351df6a7f11cfdad47b7844eb0c0d9860e11e7fdd65f5e1405b1f0978081be
3
+ metadata.gz: 329f01e23bc173bdcfec3cec5ef0eaf411f9328dbb7bef472e9ea119a2989427
4
+ data.tar.gz: 9208c40cf5063f525dcdf44eb43d65e05814fa76931f52c3015c391e3e99b77b
5
5
  SHA512:
6
- metadata.gz: 1771b173896909ca2e9b68acf312c7b73e2e7308f834fc6d79871bd84b5012622e8727ab3e0cf98ca985cab32a70e65c83b174ff1bf42a59bba942720625b198
7
- data.tar.gz: 57539ef58f3fc0722220759e4875524b43f9cd89a919d47429327f2ab3eaa2cfb0b35d9e275b05ac683f4f860bf5ac1986619fc16aa71c5a6b80ff2797683fa0
6
+ metadata.gz: 487a0aad608835a2a7b25f10d74e5430dded65f3c444dd5bfa3747b553c4811b09768c6966db4815adb1d529e4677930ed19e1a6b57ae7d0844f6d590265da55
7
+ data.tar.gz: c054ab24f0760a913e605fb8754e2c76faf993cc965695bafcba8c9e3b5ed333b20a3a6df7d442be7aa234ff98e881617f824bf9e8b38b8e9d86d72dc8e78ca0
data/README.md CHANGED
@@ -35,8 +35,9 @@ See the [docs site](https://docs.rubocop.org/rubocop-ast) for more details.
35
35
 
36
36
  ### Parser compatibility switches
37
37
 
38
- The main `RuboCop` gem uses [legacy AST output from parser](https://github.com/whitequark/parser/#usage).
39
- This gem is meant to be compatible with all settings. For example, to have `-> { ... }` emitted
38
+ This gem, by default, uses most [legacy AST output from parser](https://github.com/whitequark/parser/#usage), except for `emit_forward_arg` which is set to `true`.
39
+
40
+ The main `RuboCop` gem uses these defaults (and is currently only compatible with these), but this gem can be used separately from `RuboCop` and is meant to be compatible with all settings. For example, to have `-> { ... }` emitted
40
41
  as `LambdaNode` instead of `SendNode`:
41
42
 
42
43
  ```ruby
@@ -4,6 +4,8 @@ require 'parser'
4
4
  require 'forwardable'
5
5
  require 'set'
6
6
 
7
+ require_relative 'ast/ext/range'
8
+ require_relative 'ast/ext/set'
7
9
  require_relative 'ast/node_pattern'
8
10
  require_relative 'ast/sexp'
9
11
  require_relative 'ast/node'
@@ -27,6 +29,7 @@ require_relative 'ast/node/break_node'
27
29
  require_relative 'ast/node/case_match_node'
28
30
  require_relative 'ast/node/case_node'
29
31
  require_relative 'ast/node/class_node'
32
+ require_relative 'ast/node/const_node'
30
33
  require_relative 'ast/node/def_node'
31
34
  require_relative 'ast/node/defined_node'
32
35
  require_relative 'ast/node/ensure_node'
@@ -41,12 +44,13 @@ require_relative 'ast/node/int_node'
41
44
  require_relative 'ast/node/keyword_splat_node'
42
45
  require_relative 'ast/node/lambda_node'
43
46
  require_relative 'ast/node/module_node'
47
+ require_relative 'ast/node/next_node'
44
48
  require_relative 'ast/node/or_node'
45
49
  require_relative 'ast/node/pair_node'
46
50
  require_relative 'ast/node/range_node'
47
51
  require_relative 'ast/node/regexp_node'
52
+ require_relative 'ast/node/rescue_node'
48
53
  require_relative 'ast/node/resbody_node'
49
- require_relative 'ast/node/retry_node'
50
54
  require_relative 'ast/node/return_node'
51
55
  require_relative 'ast/node/self_class_node'
52
56
  require_relative 'ast/node/send_node'
@@ -14,6 +14,8 @@ module RuboCop
14
14
  # parser = Parser::Ruby25.new(builder)
15
15
  # root_node = parser.parse(buffer)
16
16
  class Builder < Parser::Builders::Default
17
+ self.emit_forward_arg = true
18
+
17
19
  NODE_MAP = {
18
20
  and: AndNode,
19
21
  alias: AliasNode,
@@ -25,6 +27,7 @@ module RuboCop
25
27
  case_match: CaseMatchNode,
26
28
  case: CaseNode,
27
29
  class: ClassNode,
30
+ const: ConstNode,
28
31
  def: DefNode,
29
32
  defined?: DefinedNode,
30
33
  defs: DefNode,
@@ -42,11 +45,12 @@ module RuboCop
42
45
  kwsplat: KeywordSplatNode,
43
46
  lambda: LambdaNode,
44
47
  module: ModuleNode,
48
+ next: NextNode,
45
49
  or: OrNode,
46
50
  pair: PairNode,
47
51
  regexp: RegexpNode,
52
+ rescue: RescueNode,
48
53
  resbody: ResbodyNode,
49
- retry: RetryNode,
50
54
  return: ReturnNode,
51
55
  csend: SendNode,
52
56
  send: SendNode,
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module AST
5
+ module Ext
6
+ # Extensions to Parser::AST::Range
7
+ module Range
8
+ # @return [Range] the range of line numbers for the node
9
+ # If `exclude_end` is `true`, then the range will be exclusive.
10
+ #
11
+ # Assume that `node` corresponds to the following array literal:
12
+ #
13
+ # [
14
+ # :foo,
15
+ # :bar
16
+ # ]
17
+ #
18
+ # node.loc.begin.line_span # => 1..1
19
+ # node.loc.expression.line_span(exclude_end: true) # => 1...4
20
+ def line_span(exclude_end: false)
21
+ ::Range.new(first_line, last_line, exclude_end)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ ::Parser::Source::Range.include ::RuboCop::AST::Ext::Range
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ test = :foo
4
+ case test
5
+ when Set[:foo]
6
+ # ok, RUBY_VERSION > 2.4
7
+ else
8
+ # Harmonize `Set#===`
9
+ class Set
10
+ alias === include?
11
+ end
12
+ end
@@ -38,7 +38,7 @@ module RuboCop
38
38
  IMMUTABLE_LITERALS = (LITERALS - MUTABLE_LITERALS).freeze
39
39
 
40
40
  EQUALS_ASSIGNMENTS = %i[lvasgn ivasgn cvasgn gvasgn
41
- casgn masgn].freeze
41
+ casgn masgn rasgn mrasgn].freeze
42
42
  SHORTHAND_ASSIGNMENTS = %i[op_asgn or_asgn and_asgn].freeze
43
43
  ASSIGNMENTS = (EQUALS_ASSIGNMENTS + SHORTHAND_ASSIGNMENTS).freeze
44
44
 
@@ -92,6 +92,16 @@ module RuboCop
92
92
  @mutable_attributes[:parent] = node
93
93
  end
94
94
 
95
+ # @return [Boolean]
96
+ def parent?
97
+ !!parent
98
+ end
99
+
100
+ # @return [Boolean]
101
+ def root?
102
+ !parent
103
+ end
104
+
95
105
  def complete!
96
106
  @mutable_attributes.freeze
97
107
  each_child_node(&:complete!)
@@ -116,12 +126,50 @@ module RuboCop
116
126
 
117
127
  # Returns the index of the receiver node in its siblings. (Sibling index
118
128
  # uses zero based numbering.)
129
+ # Use is discouraged, this is a potentially slow method.
119
130
  #
120
- # @return [Integer] the index of the receiver node in its siblings
131
+ # @return [Integer, nil] the index of the receiver node in its siblings
121
132
  def sibling_index
122
133
  parent&.children&.index { |sibling| sibling.equal?(self) }
123
134
  end
124
135
 
136
+ # Use is discouraged, this is a potentially slow method and can lead
137
+ # to even slower algorithms
138
+ # @return [Node, nil] the right (aka next) sibling
139
+ def right_sibling
140
+ return unless parent
141
+
142
+ parent.children[sibling_index + 1].freeze
143
+ end
144
+
145
+ # Use is discouraged, this is a potentially slow method and can lead
146
+ # to even slower algorithms
147
+ # @return [Node, nil] the left (aka previous) sibling
148
+ def left_sibling
149
+ i = sibling_index
150
+ return if i.nil? || i.zero?
151
+
152
+ parent.children[i - 1].freeze
153
+ end
154
+
155
+ # Use is discouraged, this is a potentially slow method and can lead
156
+ # to even slower algorithms
157
+ # @return [Array<Node>] the left (aka previous) siblings
158
+ def left_siblings
159
+ return [].freeze unless parent
160
+
161
+ parent.children[0...sibling_index].freeze
162
+ end
163
+
164
+ # Use is discouraged, this is a potentially slow method and can lead
165
+ # to even slower algorithms
166
+ # @return [Array<Node>] the right (aka next) siblings
167
+ def right_siblings
168
+ return [].freeze unless parent
169
+
170
+ parent.children[sibling_index + 1..-1].freeze
171
+ end
172
+
125
173
  # Common destructuring method. This can be used to normalize
126
174
  # destructuring for different variations of the node.
127
175
  # Some node types override this with their own custom
@@ -261,8 +309,10 @@ module RuboCop
261
309
  self
262
310
  end
263
311
 
312
+ # Note: Some rare nodes may have no source, like `s(:args)` in `foo {}`
313
+ # @return [String, nil]
264
314
  def source
265
- loc.expression.source
315
+ loc.expression&.source
266
316
  end
267
317
 
268
318
  def source_range
@@ -25,7 +25,7 @@ module RuboCop
25
25
  # @return [Array<Node>]
26
26
  def arguments
27
27
  if numblock_type?
28
- [] # Numbered parameters have no block arguments.
28
+ [].freeze # Numbered parameters have no block arguments.
29
29
  else
30
30
  node_parts[1]
31
31
  end
@@ -6,12 +6,7 @@ module RuboCop
6
6
  # plain node when the builder constructs the AST, making its methods
7
7
  # available to all `break` nodes within RuboCop.
8
8
  class BreakNode < Node
9
- include MethodDispatchNode
10
- include ParameterizedNode
11
-
12
- def arguments
13
- []
14
- end
9
+ include ParameterizedNode::WrappedArguments
15
10
  end
16
11
  end
17
12
  end
@@ -16,12 +16,10 @@ module RuboCop
16
16
  end
17
17
 
18
18
  # @deprecated Use `in_pattern_branches.each`
19
- def each_in_pattern
19
+ def each_in_pattern(&block)
20
20
  return in_pattern_branches.to_enum(__method__) unless block_given?
21
21
 
22
- in_pattern_branches.each do |condition|
23
- yield condition
24
- end
22
+ in_pattern_branches.each(&block)
25
23
 
26
24
  self
27
25
  end
@@ -16,12 +16,10 @@ module RuboCop
16
16
  end
17
17
 
18
18
  # @deprecated Use `when_branches.each`
19
- def each_when
19
+ def each_when(&block)
20
20
  return when_branches.to_enum(__method__) unless block_given?
21
21
 
22
- when_branches.each do |condition|
23
- yield condition
24
- end
22
+ when_branches.each(&block)
25
23
 
26
24
  self
27
25
  end
@@ -33,6 +31,16 @@ module RuboCop
33
31
  node_parts[1...-1]
34
32
  end
35
33
 
34
+ # Returns an array of all the when branches in the `case` statement.
35
+ #
36
+ # @return [Array<Node, nil>] an array of the bodies of the when branches
37
+ # and the else (if any). Note that these bodies could be nil.
38
+ def branches
39
+ bodies = when_branches.map(&:body)
40
+ bodies.push(else_branch) if else?
41
+ bodies
42
+ end
43
+
36
44
  # Returns the else branch of the `case` statement, if any.
37
45
  #
38
46
  # @return [Node] the else branch node of the `case` statement
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module AST
5
+ # A node extension for `const` nodes.
6
+ class ConstNode < Node
7
+ # The `send` node associated with this block.
8
+ #
9
+ # @return [Node, nil] the node associated with the scope (e.g. cbase, const, ...)
10
+ def namespace
11
+ children[0]
12
+ end
13
+
14
+ # @return [Symbol] the demodulized name of the constant: "::Foo::Bar" => :Bar
15
+ def short_name
16
+ children[1]
17
+ end
18
+
19
+ # The body of this block.
20
+ #
21
+ # @return [Boolean] if the constant is a Module / Class, according to the standard convention.
22
+ # Note: some classes might have uppercase in which case this method
23
+ # returns false
24
+ def module_name?
25
+ short_name.match?(/[[:lower:]]/)
26
+ end
27
+ alias class_name? module_name?
28
+
29
+ # @return [Boolean] if the constant starts with `::` (aka s(:cbase))
30
+ def absolute?
31
+ return false unless namespace
32
+
33
+ each_path.first.cbase_type?
34
+ end
35
+
36
+ # @return [Boolean] if the constant does not start with `::` (aka s(:cbase))
37
+ def relative?
38
+ !absolute?
39
+ end
40
+
41
+ # Yield nodes for the namespace
42
+ #
43
+ # For `::Foo::Bar::BAZ` => yields:
44
+ # s(:cbase), then
45
+ # s(:const, :Foo), then
46
+ # s(:const, s(:const, :Foo), :Bar)
47
+ def each_path(&block)
48
+ return to_enum(__method__) unless block_given?
49
+
50
+ descendants = []
51
+ last = self
52
+ loop do
53
+ last = last.children.first
54
+ break if last.nil?
55
+
56
+ descendants << last
57
+ break unless last.const_type?
58
+ end
59
+ descendants.reverse_each(&block)
60
+
61
+ self
62
+ end
63
+ end
64
+ end
65
+ end
@@ -12,6 +12,8 @@ module RuboCop
12
12
  def node_parts
13
13
  [nil, :defined?, *to_a]
14
14
  end
15
+
16
+ alias arguments children
15
17
  end
16
18
  end
17
19
  end
@@ -6,6 +6,7 @@ module RuboCop
6
6
  # node when the builder constructs the AST, making its methods available to
7
7
  # all `float` nodes within RuboCop.
8
8
  class FloatNode < Node
9
+ include BasicLiteralNode
9
10
  include NumericNode
10
11
  end
11
12
  end
@@ -56,12 +56,10 @@ module RuboCop
56
56
  #
57
57
  # @return [self] if a block is given
58
58
  # @return [Enumerator] if no block is given
59
- def each_key
59
+ def each_key(&block)
60
60
  return pairs.map(&:key).to_enum unless block_given?
61
61
 
62
- pairs.map(&:key).each do |key|
63
- yield key
64
- end
62
+ pairs.map(&:key).each(&block)
65
63
 
66
64
  self
67
65
  end
@@ -82,12 +80,10 @@ module RuboCop
82
80
  #
83
81
  # @return [self] if a block is given
84
82
  # @return [Enumerator] if no block is given
85
- def each_value
83
+ def each_value(&block)
86
84
  return pairs.map(&:value).to_enum unless block_given?
87
85
 
88
- pairs.map(&:value).each do |value|
89
- yield value
90
- end
86
+ pairs.map(&:value).each(&block)
91
87
 
92
88
  self
93
89
  end
@@ -147,7 +147,7 @@ module RuboCop
147
147
  def branches
148
148
  branches = [if_branch]
149
149
 
150
- return branches unless else_branch
150
+ return branches unless else?
151
151
 
152
152
  other_branches = if elsif_conditional?
153
153
  else_branch.branches
@@ -158,12 +158,10 @@ module RuboCop
158
158
  end
159
159
 
160
160
  # @deprecated Use `branches.each`
161
- def each_branch
161
+ def each_branch(&block)
162
162
  return branches.to_enum(__method__) unless block_given?
163
163
 
164
- branches.each do |branch|
165
- yield branch
166
- end
164
+ branches.each(&block)
167
165
  end
168
166
  end
169
167
  end
@@ -17,7 +17,7 @@ module RuboCop
17
17
  # The main RuboCop runs in legacy mode; this node is only used
18
18
  # if user `AST::Builder.modernize` or `AST::Builder.emit_index=true`
19
19
  class IndexNode < Node
20
- include ParameterizedNode
20
+ include ParameterizedNode::RestArguments
21
21
  include MethodDispatchNode
22
22
 
23
23
  # For similarity with legacy mode
@@ -35,11 +35,13 @@ module RuboCop
35
35
  :[]
36
36
  end
37
37
 
38
+ private
39
+
38
40
  # An array containing the arguments of the dispatched method.
39
41
  #
40
42
  # @return [Array<Node>] the arguments of the dispatched method
41
- def arguments
42
- node_parts[1..-1]
43
+ def first_argument_index
44
+ 1
43
45
  end
44
46
  end
45
47
  end
@@ -19,7 +19,7 @@ module RuboCop
19
19
  # The main RuboCop runs in legacy mode; this node is only used
20
20
  # if user `AST::Builder.modernize` or `AST::Builder.emit_index=true`
21
21
  class IndexasgnNode < Node
22
- include ParameterizedNode
22
+ include ParameterizedNode::RestArguments
23
23
  include MethodDispatchNode
24
24
 
25
25
  # For similarity with legacy mode
@@ -37,11 +37,13 @@ module RuboCop
37
37
  :[]=
38
38
  end
39
39
 
40
+ private
41
+
40
42
  # An array containing the arguments of the dispatched method.
41
43
  #
42
44
  # @return [Array<Node>] the arguments of the dispatched method
43
- def arguments
44
- node_parts[1..-1]
45
+ def first_argument_index
46
+ 1
45
47
  end
46
48
  end
47
49
  end
@@ -6,6 +6,7 @@ module RuboCop
6
6
  # node when the builder constructs the AST, making its methods available to
7
7
  # all `int` nodes within RuboCop.
8
8
  class IntNode < Node
9
+ include BasicLiteralNode
9
10
  include NumericNode
10
11
  end
11
12
  end
@@ -21,7 +21,7 @@ module RuboCop
21
21
  # The main RuboCop runs in legacy mode; this node is only used
22
22
  # if user `AST::Builder.modernize` or `AST::Builder.emit_lambda=true`
23
23
  class LambdaNode < Node
24
- include ParameterizedNode
24
+ include ParameterizedNode::RestArguments
25
25
  include MethodDispatchNode
26
26
 
27
27
  # For similarity with legacy mode
@@ -44,14 +44,21 @@ module RuboCop
44
44
  false
45
45
  end
46
46
 
47
+ # For similarity with legacy mode
48
+ def receiver
49
+ nil
50
+ end
51
+
47
52
  # For similarity with legacy mode
48
53
  def method_name
49
54
  :lambda
50
55
  end
51
56
 
57
+ private
58
+
52
59
  # For similarity with legacy mode
53
- def arguments
54
- []
60
+ def first_argument_index
61
+ 2
55
62
  end
56
63
  end
57
64
  end
@@ -26,13 +26,6 @@ module RuboCop
26
26
  node_parts[1]
27
27
  end
28
28
 
29
- # An array containing the arguments of the dispatched method.
30
- #
31
- # @return [Array<Node>] the arguments of the dispatched method
32
- def arguments
33
- node_parts[2..-1]
34
- end
35
-
36
29
  # The `block` node associated with this method dispatch, if any.
37
30
  #
38
31
  # @return [BlockNode, nil] the `block` node associated with this method
@@ -2,6 +2,8 @@
2
2
 
3
3
  module RuboCop
4
4
  module AST
5
+ # Requires implementing `arguments`.
6
+ #
5
7
  # Common functionality for nodes that are parameterized:
6
8
  # `send`, `super`, `zsuper`, `def`, `defs`
7
9
  # and (modern only): `index`, `indexasgn`, `lambda`
@@ -57,6 +59,59 @@ module RuboCop
57
59
  arguments? &&
58
60
  (last_argument.block_pass_type? || last_argument.blockarg_type?)
59
61
  end
62
+
63
+ # A specialized `ParameterizedNode` for node that have a single child
64
+ # containing either `nil`, an argument, or a `begin` node with all the
65
+ # arguments
66
+ module WrappedArguments
67
+ include ParameterizedNode
68
+ # @return [Array] The arguments of the node.
69
+ def arguments
70
+ first = children.first
71
+ if first&.begin_type?
72
+ first.children
73
+ else
74
+ children
75
+ end
76
+ end
77
+ end
78
+
79
+ # A specialized `ParameterizedNode`.
80
+ # Requires implementing `first_argument_index`
81
+ # Implements `arguments` as `children[first_argument_index..-1]`
82
+ # and optimizes other calls
83
+ module RestArguments
84
+ include ParameterizedNode
85
+ # @return [Array<Node>] arguments, if any
86
+ def arguments
87
+ children[first_argument_index..-1].freeze
88
+ end
89
+
90
+ # A shorthand for getting the first argument of the node.
91
+ # Equivalent to `arguments.first`.
92
+ #
93
+ # @return [Node, nil] the first argument of the node,
94
+ # or `nil` if there are no arguments
95
+ def first_argument
96
+ children[first_argument_index]
97
+ end
98
+
99
+ # A shorthand for getting the last argument of the node.
100
+ # Equivalent to `arguments.last`.
101
+ #
102
+ # @return [Node, nil] the last argument of the node,
103
+ # or `nil` if there are no arguments
104
+ def last_argument
105
+ children[-1] if arguments?
106
+ end
107
+
108
+ # Checks whether this node has any arguments.
109
+ #
110
+ # @return [Boolean] whether this node has any arguments
111
+ def arguments?
112
+ children.size > first_argument_index
113
+ end
114
+ end
60
115
  end
61
116
  end
62
117
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module AST
5
+ # A node extension for `next` nodes. This will be used in place of a
6
+ # plain node when the builder constructs the AST, making its methods
7
+ # available to all `next` nodes within RuboCop.
8
+ class NextNode < Node
9
+ include ParameterizedNode::WrappedArguments
10
+ end
11
+ end
12
+ end
@@ -32,7 +32,7 @@ module RuboCop
32
32
  #
33
33
  # @param [Boolean] with_spacing whether to include spacing
34
34
  # @return [String] the delimiter of the `pair`
35
- def delimiter(with_spacing = false)
35
+ def delimiter(*deprecated, with_spacing: deprecated.first)
36
36
  if with_spacing
37
37
  hash_rocket? ? SPACED_HASH_ROCKET : SPACED_COLON
38
38
  else
@@ -44,7 +44,7 @@ module RuboCop
44
44
  #
45
45
  # @param [Boolean] with_spacing whether to include spacing
46
46
  # @return [String] the inverse delimiter of the `pair`
47
- def inverse_delimiter(with_spacing = false)
47
+ def inverse_delimiter(*deprecated, with_spacing: deprecated.first)
48
48
  if with_spacing
49
49
  hash_rocket? ? SPACED_COLON : SPACED_HASH_ROCKET
50
50
  else
@@ -13,12 +13,33 @@ module RuboCop
13
13
  node_parts[2]
14
14
  end
15
15
 
16
+ # Returns an array of all the exceptions in the `rescue` clause.
17
+ #
18
+ # @return [Array<Node>] an array of exception nodes
19
+ def exceptions
20
+ exceptions_node = node_parts[0]
21
+ if exceptions_node.nil?
22
+ []
23
+ elsif exceptions_node.array_type?
24
+ exceptions_node.values
25
+ else
26
+ [exceptions_node]
27
+ end
28
+ end
29
+
16
30
  # Returns the exception variable of the `rescue` clause.
17
31
  #
18
32
  # @return [Node, nil] The exception variable of the `resbody`.
19
33
  def exception_variable
20
34
  node_parts[1]
21
35
  end
36
+
37
+ # Returns the index of the `resbody` branch within the exception handling statement.
38
+ #
39
+ # @return [Integer] the index of the `resbody` branch
40
+ def branch_index
41
+ parent.resbody_branches.index(self)
42
+ end
22
43
  end
23
44
  end
24
45
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module AST
5
+ # A node extension for `rescue` nodes. This will be used in place of a
6
+ # plain node when the builder constructs the AST, making its methods
7
+ # available to all `rescue` nodes within RuboCop.
8
+ class RescueNode < Node
9
+ # Returns the body of the rescue node.
10
+ #
11
+ # @return [Node, nil] The body of the rescue node.
12
+ def body
13
+ node_parts[0]
14
+ end
15
+
16
+ # Returns an array of all the rescue branches in the exception handling statement.
17
+ #
18
+ # @return [Array<ResbodyNode>] an array of `resbody` nodes
19
+ def resbody_branches
20
+ node_parts[1...-1]
21
+ end
22
+
23
+ # Returns an array of all the rescue branches in the exception handling statement.
24
+ #
25
+ # @return [Array<Node, nil>] an array of the bodies of the rescue branches
26
+ # and the else (if any). Note that these bodies could be nil.
27
+ def branches
28
+ bodies = resbody_branches.map(&:body)
29
+ bodies.push(else_branch) if else?
30
+ bodies
31
+ end
32
+
33
+ # Returns the else branch of the exception handling statement, if any.
34
+ #
35
+ # @return [Node] the else branch node of the exception handling statement
36
+ # @return [nil] if the exception handling statement does not have an else branch.
37
+ def else_branch
38
+ node_parts[-1]
39
+ end
40
+
41
+ # Checks whether this exception handling statement has an `else` branch.
42
+ #
43
+ # @return [Boolean] whether the exception handling statement has an `else` branch
44
+ def else?
45
+ loc.else
46
+ end
47
+ end
48
+ end
49
+ end
@@ -6,19 +6,7 @@ module RuboCop
6
6
  # plain node when the builder constructs the AST, making its methods
7
7
  # available to all `return` nodes within RuboCop.
8
8
  class ReturnNode < Node
9
- include MethodDispatchNode
10
- include ParameterizedNode
11
-
12
- # Returns the arguments of the `return`.
13
- #
14
- # @return [Array] The arguments of the `return`.
15
- def arguments
16
- if node_parts.one? && node_parts.first.begin_type?
17
- node_parts.first.children
18
- else
19
- node_parts
20
- end
21
- end
9
+ include ParameterizedNode::WrappedArguments
22
10
  end
23
11
  end
24
12
  end
@@ -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
@@ -16,6 +16,8 @@ module RuboCop
16
16
  def node_parts
17
17
  [nil, :super, *to_a]
18
18
  end
19
+
20
+ alias arguments children
19
21
  end
20
22
  end
21
23
  end
@@ -14,12 +14,10 @@ module RuboCop
14
14
  end
15
15
 
16
16
  # @deprecated Use `conditions.each`
17
- def each_condition
17
+ def each_condition(&block)
18
18
  return conditions.to_enum(__method__) unless block_given?
19
19
 
20
- conditions.each do |condition|
21
- yield condition
22
- end
20
+ conditions.each(&block)
23
21
 
24
22
  self
25
23
  end
@@ -16,6 +16,8 @@ module RuboCop
16
16
  def node_parts
17
17
  [nil, :yield, *to_a]
18
18
  end
19
+
20
+ alias arguments children
19
21
  end
20
22
  end
21
23
  end
@@ -99,6 +99,7 @@ module RuboCop
99
99
  # # These arguments can be patterns themselves, in
100
100
  # # which case a matcher responding to === will be
101
101
  # # passed.
102
+ # '# comment' # comments are accepted at the end of lines
102
103
  #
103
104
  # You can nest arbitrarily deep:
104
105
  #
@@ -122,6 +123,8 @@ module RuboCop
122
123
  class Compiler
123
124
  SYMBOL = %r{:(?:[\w+@*/?!<>=~|%^-]+|\[\]=?)}.freeze
124
125
  IDENTIFIER = /[a-zA-Z_][a-zA-Z0-9_-]*/.freeze
126
+ COMMENT = /#\s.*$/.freeze
127
+
125
128
  META = Regexp.union(
126
129
  %w"( ) { } [ ] $< < > $... $ ! ^ ` ... + * ?"
127
130
  ).freeze
@@ -202,7 +205,7 @@ module RuboCop
202
205
 
203
206
  def initialize(str, root = 'node0', node_var = root)
204
207
  @string = str
205
- # For def_node_pattern, root == node_var
208
+ # For def_node_matcher, root == node_var
206
209
  # For def_node_search, root is the root node to search on,
207
210
  # and node_var is the current descendant being searched.
208
211
  @root = root
@@ -455,7 +458,6 @@ module RuboCop
455
458
  [0..Float::INFINITY, 'true']
456
459
  end
457
460
 
458
- # rubocop:disable Metrics/AbcSize
459
461
  # rubocop:disable Metrics/MethodLength
460
462
  def compile_any_order(capture_all = nil)
461
463
  rest = capture_rest = nil
@@ -475,7 +477,6 @@ module RuboCop
475
477
  end
476
478
  end
477
479
  # rubocop:enable Metrics/MethodLength
478
- # rubocop:enable Metrics/AbcSize
479
480
 
480
481
  def insure_same_captures(enum, what)
481
482
  return to_enum __method__, enum, what unless block_given?
@@ -790,7 +791,7 @@ module RuboCop
790
791
  end
791
792
 
792
793
  def self.tokens(pattern)
793
- pattern.scan(TOKEN).grep_v(ONLY_SEPARATOR)
794
+ pattern.gsub(COMMENT, '').scan(TOKEN).grep_v(ONLY_SEPARATOR)
794
795
  end
795
796
 
796
797
  # This method minimizes the closure for our method
@@ -72,23 +72,23 @@ module RuboCop
72
72
  end
73
73
 
74
74
  # @deprecated Use `comments.each`
75
- def each_comment
76
- comments.each { |comment| yield comment }
75
+ def each_comment(&block)
76
+ comments.each(&block)
77
77
  end
78
78
 
79
- # @deprecated Use `comments.find`
80
- def find_comment
81
- comments.find { |comment| yield comment }
79
+ # @deprecated Use `comment_at_line`, `each_comment_in_lines`, or `comments.find`
80
+ def find_comment(&block)
81
+ comments.find(&block)
82
82
  end
83
83
 
84
84
  # @deprecated Use `tokens.each`
85
- def each_token
86
- tokens.each { |token| yield token }
85
+ def each_token(&block)
86
+ tokens.each(&block)
87
87
  end
88
88
 
89
89
  # @deprecated Use `tokens.find`
90
- def find_token
91
- tokens.find { |token| yield token }
90
+ def find_token(&block)
91
+ tokens.find(&block)
92
92
  end
93
93
 
94
94
  def file_path
@@ -99,22 +99,39 @@ module RuboCop
99
99
  ast.nil?
100
100
  end
101
101
 
102
+ # @return [Comment, nil] the comment at that line, if any.
103
+ def comment_at_line(line)
104
+ comment_index[line]
105
+ end
106
+
102
107
  # @return [Boolean] if the given line number has a comment.
103
108
  def line_with_comment?(line)
104
- comment_lines.include?(line)
109
+ comment_index.include?(line)
110
+ end
111
+
112
+ # Enumerates on the comments contained with the given `line_range`
113
+ def each_comment_in_lines(line_range)
114
+ return to_enum(:each_comment_in_lines, line_range) unless block_given?
115
+
116
+ line_range.each do |line|
117
+ if (comment = comment_index[line])
118
+ yield comment
119
+ end
120
+ end
105
121
  end
106
122
 
107
123
  # @return [Boolean] if any of the lines in the given `source_range` has a comment.
124
+ # Consider using `each_comment_in_lines` instead
108
125
  def contains_comment?(source_range)
109
- (source_range.line..source_range.last_line).any? do |line|
110
- line_with_comment?(line)
111
- end
126
+ each_comment_in_lines(source_range.line..source_range.last_line).any?
112
127
  end
113
128
  # @deprecated use contains_comment?
114
129
  alias commented? contains_comment?
115
130
 
131
+ # @deprecated Use `each_comment_in_lines`
132
+ # Should have been called `comments_before_or_at_line`. Doubtful it has of any valid use.
116
133
  def comments_before_line(line)
117
- comments.select { |c| c.location.line <= line }
134
+ each_comment_in_lines(0..line).to_a
118
135
  end
119
136
 
120
137
  def start_with?(string)
@@ -142,10 +159,26 @@ module RuboCop
142
159
  .length
143
160
  end
144
161
 
162
+ def tokens_within(range_or_node)
163
+ begin_index = first_token_index(range_or_node)
164
+ end_index = last_token_index(range_or_node)
165
+ sorted_tokens[begin_index..end_index]
166
+ end
167
+
168
+ def first_token_of(range_or_node)
169
+ sorted_tokens[first_token_index(range_or_node)]
170
+ end
171
+
172
+ def last_token_of(range_or_node)
173
+ sorted_tokens[last_token_index(range_or_node)]
174
+ end
175
+
145
176
  private
146
177
 
147
- def comment_lines
148
- @comment_lines ||= comments.map { |c| c.location.line }
178
+ def comment_index
179
+ @comment_index ||= {}.tap do |hash|
180
+ comments.each { |c| hash[c.location.line] = c }
181
+ end
149
182
  end
150
183
 
151
184
  def parse(source, ruby_version)
@@ -156,6 +189,9 @@ module RuboCop
156
189
  @buffer.source = source
157
190
  rescue EncodingError => e
158
191
  @parser_error = e
192
+ @ast = nil
193
+ @comments = []
194
+ @tokens = []
159
195
  return
160
196
  end
161
197
 
@@ -165,13 +201,15 @@ module RuboCop
165
201
  def tokenize(parser)
166
202
  begin
167
203
  ast, comments, tokens = parser.tokenize(@buffer)
168
-
169
- ast.respond_to?(:complete!) && ast.complete!
204
+ ast ||= nil # force `false` to `nil`, see https://github.com/whitequark/parser/pull/722
170
205
  rescue Parser::SyntaxError
171
206
  # All errors are in diagnostics. No need to handle exception.
207
+ comments = []
208
+ tokens = []
172
209
  end
173
210
 
174
- tokens = tokens.map { |t| Token.from_parser_token(t) } if tokens
211
+ ast&.complete!
212
+ tokens.map! { |t| Token.from_parser_token(t) }
175
213
 
176
214
  [ast, comments, tokens]
177
215
  end
@@ -191,9 +229,9 @@ module RuboCop
191
229
  when 2.7
192
230
  require 'parser/ruby27'
193
231
  Parser::Ruby27
194
- when 2.8
195
- require 'parser/ruby28'
196
- Parser::Ruby28
232
+ when 2.8, 3.0
233
+ require 'parser/ruby30'
234
+ Parser::Ruby30
197
235
  else
198
236
  raise ArgumentError,
199
237
  "RuboCop found unknown Ruby version: #{ruby_version.inspect}"
@@ -216,6 +254,32 @@ module RuboCop
216
254
  end
217
255
  end
218
256
  end
257
+
258
+ def first_token_index(range_or_node)
259
+ begin_pos = source_range(range_or_node).begin_pos
260
+ sorted_tokens.bsearch_index { |token| token.begin_pos >= begin_pos }
261
+ end
262
+
263
+ def last_token_index(range_or_node)
264
+ end_pos = source_range(range_or_node).end_pos
265
+ sorted_tokens.bsearch_index { |token| token.end_pos >= end_pos }
266
+ end
267
+
268
+ # The tokens list is always sorted by token position, except for cases when heredoc
269
+ # is passed as a method argument. In this case tokens are interleaved by
270
+ # heredoc contents' tokens.
271
+ def sorted_tokens
272
+ # Use stable sort.
273
+ @sorted_tokens ||= tokens.sort_by.with_index { |token, i| [token.begin_pos, i] }
274
+ end
275
+
276
+ def source_range(range_or_node)
277
+ if range_or_node.respond_to?(:source_range)
278
+ range_or_node.source_range
279
+ else
280
+ range_or_node
281
+ end
282
+ end
219
283
  end
220
284
  end
221
285
  end
@@ -28,7 +28,7 @@ module RuboCop
28
28
  arg_expr pin match_rest if_guard unless_guard
29
29
  match_with_trailing_comma].freeze
30
30
  MANY_CHILD_NODES = %i[dstr dsym xstr regexp array hash pair
31
- mlhs masgn or_asgn and_asgn
31
+ mlhs masgn or_asgn and_asgn rasgn mrasgn
32
32
  undef alias args super yield or and
33
33
  while_post until_post iflipflop eflipflop
34
34
  match_with_lvasgn begin kwbegin return
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module AST
5
5
  module Version
6
- STRING = '0.2.0'
6
+ STRING = '0.5.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.2.0
4
+ version: 0.5.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-07-19 00:00:00.000000000 Z
13
+ date: 2020-09-24 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: parser
@@ -18,14 +18,14 @@ dependencies:
18
18
  requirements:
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: 2.7.0.1
21
+ version: 2.7.1.5
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
- version: 2.7.0.1
28
+ version: 2.7.1.5
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: bundler
31
31
  requirement: !ruby/object:Gem::Requirement
@@ -59,6 +59,8 @@ files:
59
59
  - lib/rubocop-ast.rb
60
60
  - lib/rubocop/ast.rb
61
61
  - lib/rubocop/ast/builder.rb
62
+ - lib/rubocop/ast/ext/range.rb
63
+ - lib/rubocop/ast/ext/set.rb
62
64
  - lib/rubocop/ast/node.rb
63
65
  - lib/rubocop/ast/node/alias_node.rb
64
66
  - lib/rubocop/ast/node/and_node.rb
@@ -69,6 +71,7 @@ files:
69
71
  - lib/rubocop/ast/node/case_match_node.rb
70
72
  - lib/rubocop/ast/node/case_node.rb
71
73
  - lib/rubocop/ast/node/class_node.rb
74
+ - lib/rubocop/ast/node/const_node.rb
72
75
  - lib/rubocop/ast/node/def_node.rb
73
76
  - lib/rubocop/ast/node/defined_node.rb
74
77
  - lib/rubocop/ast/node/ensure_node.rb
@@ -94,12 +97,13 @@ files:
94
97
  - lib/rubocop/ast/node/mixin/parameterized_node.rb
95
98
  - lib/rubocop/ast/node/mixin/predicate_operator_node.rb
96
99
  - lib/rubocop/ast/node/module_node.rb
100
+ - lib/rubocop/ast/node/next_node.rb
97
101
  - lib/rubocop/ast/node/or_node.rb
98
102
  - lib/rubocop/ast/node/pair_node.rb
99
103
  - lib/rubocop/ast/node/range_node.rb
100
104
  - lib/rubocop/ast/node/regexp_node.rb
101
105
  - lib/rubocop/ast/node/resbody_node.rb
102
- - lib/rubocop/ast/node/retry_node.rb
106
+ - lib/rubocop/ast/node/rescue_node.rb
103
107
  - lib/rubocop/ast/node/return_node.rb
104
108
  - lib/rubocop/ast/node/self_class_node.rb
105
109
  - lib/rubocop/ast/node/send_node.rb
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module AST
5
- # A node extension for `retry` nodes. This will be used in place of a
6
- # plain node when the builder constructs the AST, making its methods
7
- # available to all `retry` nodes within RuboCop.
8
- class RetryNode < Node
9
- include MethodDispatchNode
10
- include ParameterizedNode
11
-
12
- def arguments
13
- []
14
- end
15
- end
16
- end
17
- end