rubocop-ast 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +35 -0
  4. data/lib/rubocop-ast.rb +3 -0
  5. data/lib/rubocop/ast.rb +62 -0
  6. data/lib/rubocop/ast/builder.rb +85 -0
  7. data/lib/rubocop/ast/node.rb +637 -0
  8. data/lib/rubocop/ast/node/alias_node.rb +24 -0
  9. data/lib/rubocop/ast/node/and_node.rb +29 -0
  10. data/lib/rubocop/ast/node/args_node.rb +29 -0
  11. data/lib/rubocop/ast/node/array_node.rb +70 -0
  12. data/lib/rubocop/ast/node/block_node.rb +121 -0
  13. data/lib/rubocop/ast/node/break_node.rb +17 -0
  14. data/lib/rubocop/ast/node/case_match_node.rb +56 -0
  15. data/lib/rubocop/ast/node/case_node.rb +56 -0
  16. data/lib/rubocop/ast/node/class_node.rb +31 -0
  17. data/lib/rubocop/ast/node/def_node.rb +82 -0
  18. data/lib/rubocop/ast/node/defined_node.rb +17 -0
  19. data/lib/rubocop/ast/node/ensure_node.rb +17 -0
  20. data/lib/rubocop/ast/node/float_node.rb +12 -0
  21. data/lib/rubocop/ast/node/for_node.rb +53 -0
  22. data/lib/rubocop/ast/node/forward_args_node.rb +18 -0
  23. data/lib/rubocop/ast/node/hash_node.rb +109 -0
  24. data/lib/rubocop/ast/node/if_node.rb +175 -0
  25. data/lib/rubocop/ast/node/int_node.rb +12 -0
  26. data/lib/rubocop/ast/node/keyword_splat_node.rb +45 -0
  27. data/lib/rubocop/ast/node/mixin/basic_literal_node.rb +16 -0
  28. data/lib/rubocop/ast/node/mixin/binary_operator_node.rb +43 -0
  29. data/lib/rubocop/ast/node/mixin/collection_node.rb +15 -0
  30. data/lib/rubocop/ast/node/mixin/conditional_node.rb +45 -0
  31. data/lib/rubocop/ast/node/mixin/hash_element_node.rb +125 -0
  32. data/lib/rubocop/ast/node/mixin/method_dispatch_node.rb +269 -0
  33. data/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb +114 -0
  34. data/lib/rubocop/ast/node/mixin/modifier_node.rb +17 -0
  35. data/lib/rubocop/ast/node/mixin/numeric_node.rb +21 -0
  36. data/lib/rubocop/ast/node/mixin/parameterized_node.rb +61 -0
  37. data/lib/rubocop/ast/node/mixin/predicate_operator_node.rb +35 -0
  38. data/lib/rubocop/ast/node/module_node.rb +24 -0
  39. data/lib/rubocop/ast/node/or_node.rb +29 -0
  40. data/lib/rubocop/ast/node/pair_node.rb +63 -0
  41. data/lib/rubocop/ast/node/range_node.rb +18 -0
  42. data/lib/rubocop/ast/node/regexp_node.rb +33 -0
  43. data/lib/rubocop/ast/node/resbody_node.rb +24 -0
  44. data/lib/rubocop/ast/node/retry_node.rb +17 -0
  45. data/lib/rubocop/ast/node/return_node.rb +24 -0
  46. data/lib/rubocop/ast/node/self_class_node.rb +24 -0
  47. data/lib/rubocop/ast/node/send_node.rb +17 -0
  48. data/lib/rubocop/ast/node/str_node.rb +16 -0
  49. data/lib/rubocop/ast/node/super_node.rb +21 -0
  50. data/lib/rubocop/ast/node/symbol_node.rb +12 -0
  51. data/lib/rubocop/ast/node/until_node.rb +35 -0
  52. data/lib/rubocop/ast/node/when_node.rb +53 -0
  53. data/lib/rubocop/ast/node/while_node.rb +35 -0
  54. data/lib/rubocop/ast/node/yield_node.rb +21 -0
  55. data/lib/rubocop/ast/sexp.rb +16 -0
  56. data/lib/rubocop/ast/traversal.rb +202 -0
  57. data/lib/rubocop/ast/version.rb +9 -0
  58. data/lib/rubocop/error.rb +34 -0
  59. data/lib/rubocop/node_pattern.rb +881 -0
  60. data/lib/rubocop/processed_source.rb +211 -0
  61. data/lib/rubocop/token.rb +114 -0
  62. metadata +145 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c4c380d9edb3f0bdbdb8d7d9822b25cd96f9d23b7428bf8e8c56e4e19cf5628b
4
+ data.tar.gz: 3568dc3fabb7c2d6b0575c9dc07341b4bb0b5daf6d65e284fdd524000a7de019
5
+ SHA512:
6
+ metadata.gz: 228b11a44b85a2bb3467afad8d28aadd65d7864ee7a5639773b007bcb2b9eef37ef2daff14d923730ec1b50cf3b7bfd96556ba3573e179615b9c7aba7d787480
7
+ data.tar.gz: 061d70e99b00be1f84ab91d18cab96a97829e1ee5a62ef615013eae5994b72f67aa0b6101b64bf0715de6b863ebf60022849b1930ed7e306ff841d86bff39d60
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012-20 Bozhidar Batsov
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,35 @@
1
+ # RuboCop AST
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/rubocop-ast.svg)](https://badge.fury.io/rb/rubocop-ast)
4
+ [![CircleCI](https://circleci.com/gh/rubocop-hq/rubocop-ast.svg?style=svg)](https://circleci.com/gh/rubocop-hq/rubocop-ast)
5
+
6
+ Contains the classes needed by [RuboCop](https://github.com/rubocop-hq/rubocop) to deal with Ruby's AST, in particular:
7
+ * `RuboCop::AST::Node`
8
+ * `RuboCop::NodePattern`
9
+
10
+ ## Installation
11
+
12
+ Just install the `rubocop-ast` gem
13
+
14
+ ```sh
15
+ gem install rubocop-ast
16
+ ```
17
+
18
+ or if you use bundler put this in your `Gemfile`
19
+
20
+ ```ruby
21
+ gem 'rubocop-ast'
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ Refer to the documentation of `RuboCop::AST::Node` and [`RuboCop::NodePattern`](manual/node_pattern.md)
27
+
28
+ ## Contributing
29
+
30
+ Checkout the [contribution guidelines](CONTRIBUTING.md).
31
+
32
+ ## License
33
+
34
+ `rubocop-ast` is MIT licensed. [See the accompanying file](LICENSE.txt) for
35
+ the full text.
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rubocop/ast'
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'parser'
4
+ require 'forwardable'
5
+
6
+ require_relative 'error'
7
+ require_relative 'node_pattern'
8
+
9
+ require_relative 'ast/sexp'
10
+ require_relative 'ast/node'
11
+ require_relative 'ast/node/mixin/method_identifier_predicates'
12
+ require_relative 'ast/node/mixin/binary_operator_node'
13
+ require_relative 'ast/node/mixin/collection_node'
14
+ require_relative 'ast/node/mixin/conditional_node'
15
+ require_relative 'ast/node/mixin/hash_element_node'
16
+ require_relative 'ast/node/mixin/method_dispatch_node'
17
+ require_relative 'ast/node/mixin/modifier_node'
18
+ require_relative 'ast/node/mixin/numeric_node'
19
+ require_relative 'ast/node/mixin/parameterized_node'
20
+ require_relative 'ast/node/mixin/predicate_operator_node'
21
+ require_relative 'ast/node/mixin/basic_literal_node'
22
+ require_relative 'ast/node/alias_node'
23
+ require_relative 'ast/node/and_node'
24
+ require_relative 'ast/node/args_node'
25
+ require_relative 'ast/node/array_node'
26
+ require_relative 'ast/node/block_node'
27
+ require_relative 'ast/node/break_node'
28
+ require_relative 'ast/node/case_match_node'
29
+ require_relative 'ast/node/case_node'
30
+ require_relative 'ast/node/class_node'
31
+ require_relative 'ast/node/def_node'
32
+ require_relative 'ast/node/defined_node'
33
+ require_relative 'ast/node/ensure_node'
34
+ require_relative 'ast/node/for_node'
35
+ require_relative 'ast/node/forward_args_node'
36
+ require_relative 'ast/node/float_node'
37
+ require_relative 'ast/node/hash_node'
38
+ require_relative 'ast/node/if_node'
39
+ require_relative 'ast/node/int_node'
40
+ require_relative 'ast/node/keyword_splat_node'
41
+ require_relative 'ast/node/module_node'
42
+ require_relative 'ast/node/or_node'
43
+ require_relative 'ast/node/pair_node'
44
+ require_relative 'ast/node/range_node'
45
+ require_relative 'ast/node/regexp_node'
46
+ require_relative 'ast/node/resbody_node'
47
+ require_relative 'ast/node/retry_node'
48
+ require_relative 'ast/node/return_node'
49
+ require_relative 'ast/node/self_class_node'
50
+ require_relative 'ast/node/send_node'
51
+ require_relative 'ast/node/str_node'
52
+ require_relative 'ast/node/super_node'
53
+ require_relative 'ast/node/symbol_node'
54
+ require_relative 'ast/node/until_node'
55
+ require_relative 'ast/node/when_node'
56
+ require_relative 'ast/node/while_node'
57
+ require_relative 'ast/node/yield_node'
58
+ require_relative 'ast/builder'
59
+ require_relative 'ast/traversal'
60
+
61
+ require_relative 'token'
62
+ require_relative 'processed_source'
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module AST
5
+ # `RuboCop::AST::Builder` is an AST builder that is utilized to let `Parser`
6
+ # generate ASTs with {RuboCop::AST::Node}.
7
+ #
8
+ # @example
9
+ # buffer = Parser::Source::Buffer.new('(string)')
10
+ # buffer.source = 'puts :foo'
11
+ #
12
+ # builder = RuboCop::AST::Builder.new
13
+ # require 'parser/ruby25'
14
+ # parser = Parser::Ruby25.new(builder)
15
+ # root_node = parser.parse(buffer)
16
+ class Builder < Parser::Builders::Default
17
+ NODE_MAP = {
18
+ and: AndNode,
19
+ alias: AliasNode,
20
+ args: ArgsNode,
21
+ array: ArrayNode,
22
+ block: BlockNode,
23
+ numblock: BlockNode,
24
+ break: BreakNode,
25
+ case_match: CaseMatchNode,
26
+ case: CaseNode,
27
+ class: ClassNode,
28
+ def: DefNode,
29
+ defined?: DefinedNode,
30
+ defs: DefNode,
31
+ ensure: EnsureNode,
32
+ for: ForNode,
33
+ forward_args: ForwardArgsNode,
34
+ float: FloatNode,
35
+ hash: HashNode,
36
+ if: IfNode,
37
+ int: IntNode,
38
+ irange: RangeNode,
39
+ erange: RangeNode,
40
+ kwsplat: KeywordSplatNode,
41
+ module: ModuleNode,
42
+ or: OrNode,
43
+ pair: PairNode,
44
+ regexp: RegexpNode,
45
+ resbody: ResbodyNode,
46
+ retry: RetryNode,
47
+ return: ReturnNode,
48
+ csend: SendNode,
49
+ send: SendNode,
50
+ str: StrNode,
51
+ dstr: StrNode,
52
+ xstr: StrNode,
53
+ sclass: SelfClassNode,
54
+ super: SuperNode,
55
+ zsuper: SuperNode,
56
+ sym: SymbolNode,
57
+ until: UntilNode,
58
+ until_post: UntilNode,
59
+ when: WhenNode,
60
+ while: WhileNode,
61
+ while_post: WhileNode,
62
+ yield: YieldNode
63
+ }.freeze
64
+
65
+ # Generates {Node} from the given information.
66
+ #
67
+ # @return [Node] the generated node
68
+ def n(type, children, source_map)
69
+ node_klass(type).new(type, children, location: source_map)
70
+ end
71
+
72
+ # TODO: Figure out what to do about literal encoding handling...
73
+ # More details here https://github.com/whitequark/parser/issues/283
74
+ def string_value(token)
75
+ value(token)
76
+ end
77
+
78
+ private
79
+
80
+ def node_klass(type)
81
+ NODE_MAP[type] || Node
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,637 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module AST
5
+ # `RuboCop::AST::Node` is a subclass of `Parser::AST::Node`. It provides
6
+ # access to parent nodes and an object-oriented way to traverse an AST with
7
+ # the power of `Enumerable`.
8
+ #
9
+ # It has predicate methods for every node type, like this:
10
+ #
11
+ # @example
12
+ # node.send_type? # Equivalent to: `node.type == :send`
13
+ # node.op_asgn_type? # Equivalent to: `node.type == :op_asgn`
14
+ #
15
+ # # Non-word characters (other than a-zA-Z0-9_) in type names are omitted.
16
+ # node.defined_type? # Equivalent to: `node.type == :defined?`
17
+ #
18
+ # # Find the first lvar node under the receiver node.
19
+ # lvar_node = node.each_descendant.find(&:lvar_type?)
20
+ #
21
+ class Node < Parser::AST::Node # rubocop:disable Metrics/ClassLength
22
+ include RuboCop::AST::Sexp
23
+ extend NodePattern::Macros
24
+
25
+ # <=> isn't included here, because it doesn't return a boolean.
26
+ COMPARISON_OPERATORS = %i[== === != <= >= > <].freeze
27
+
28
+ TRUTHY_LITERALS = %i[str dstr xstr int float sym dsym array
29
+ hash regexp true irange erange complex
30
+ rational regopt].freeze
31
+ FALSEY_LITERALS = %i[false nil].freeze
32
+ LITERALS = (TRUTHY_LITERALS + FALSEY_LITERALS).freeze
33
+ COMPOSITE_LITERALS = %i[dstr xstr dsym array hash irange
34
+ erange regexp].freeze
35
+ BASIC_LITERALS = (LITERALS - COMPOSITE_LITERALS).freeze
36
+ MUTABLE_LITERALS = %i[str dstr xstr array hash
37
+ regexp irange erange].freeze
38
+ IMMUTABLE_LITERALS = (LITERALS - MUTABLE_LITERALS).freeze
39
+
40
+ EQUALS_ASSIGNMENTS = %i[lvasgn ivasgn cvasgn gvasgn
41
+ casgn masgn].freeze
42
+ SHORTHAND_ASSIGNMENTS = %i[op_asgn or_asgn and_asgn].freeze
43
+ ASSIGNMENTS = (EQUALS_ASSIGNMENTS + SHORTHAND_ASSIGNMENTS).freeze
44
+
45
+ BASIC_CONDITIONALS = %i[if while until].freeze
46
+ CONDITIONALS = [*BASIC_CONDITIONALS, :case].freeze
47
+ VARIABLES = %i[ivar gvar cvar lvar].freeze
48
+ REFERENCES = %i[nth_ref back_ref].freeze
49
+ KEYWORDS = %i[alias and break case class def defs defined?
50
+ kwbegin do else ensure for if module next
51
+ not or postexe redo rescue retry return self
52
+ super zsuper then undef until when while
53
+ yield].freeze
54
+ OPERATOR_KEYWORDS = %i[and or].freeze
55
+ SPECIAL_KEYWORDS = %w[__FILE__ __LINE__ __ENCODING__].freeze
56
+
57
+ # @see https://www.rubydoc.info/gems/ast/AST/Node:initialize
58
+ def initialize(type, children = [], properties = {})
59
+ @mutable_attributes = {}
60
+
61
+ # ::AST::Node#initialize freezes itself.
62
+ super
63
+
64
+ # #parent= may be invoked multiple times for a node because there are
65
+ # pending nodes while constructing AST and they are replaced later.
66
+ # For example, `lvar` and `send` type nodes are initially created as an
67
+ # `ident` type node and fixed to the appropriate type later.
68
+ # So, the #parent attribute needs to be mutable.
69
+ each_child_node do |child_node|
70
+ child_node.parent = self unless child_node.complete?
71
+ end
72
+ end
73
+
74
+ Parser::Meta::NODE_TYPES.each do |node_type|
75
+ method_name = "#{node_type.to_s.gsub(/\W/, '')}_type?"
76
+ define_method(method_name) do
77
+ type == node_type
78
+ end
79
+ end
80
+
81
+ # Returns the parent node, or `nil` if the receiver is a root node.
82
+ #
83
+ # @return [Node, nil] the parent node or `nil`
84
+ def parent
85
+ @mutable_attributes[:parent]
86
+ end
87
+
88
+ def parent=(node)
89
+ @mutable_attributes[:parent] = node
90
+ end
91
+
92
+ def complete!
93
+ @mutable_attributes.freeze
94
+ each_child_node(&:complete!)
95
+ end
96
+
97
+ def complete?
98
+ @mutable_attributes.frozen?
99
+ end
100
+
101
+ protected :parent=
102
+
103
+ # Override `AST::Node#updated` so that `AST::Processor` does not try to
104
+ # mutate our ASTs. Since we keep references from children to parents and
105
+ # not just the other way around, we cannot update an AST and share
106
+ # identical subtrees. Rather, the entire AST must be copied any time any
107
+ # part of it is changed.
108
+ def updated(type = nil, children = nil, properties = {})
109
+ properties[:location] ||= @location
110
+ klass = RuboCop::AST::Builder::NODE_MAP[type || @type] || Node
111
+ klass.new(type || @type, children || @children, properties)
112
+ end
113
+
114
+ # Returns the index of the receiver node in its siblings. (Sibling index
115
+ # uses zero based numbering.)
116
+ #
117
+ # @return [Integer] the index of the receiver node in its siblings
118
+ def sibling_index
119
+ parent&.children&.index { |sibling| sibling.equal?(self) }
120
+ end
121
+
122
+ # Common destructuring method. This can be used to normalize
123
+ # destructuring for different variations of the node.
124
+ # Some node types override this with their own custom
125
+ # destructuring method.
126
+ #
127
+ # @return [Array<Node>] the different parts of the ndde
128
+ def node_parts
129
+ to_a
130
+ end
131
+
132
+ # Calls the given block for each ancestor node from parent to root.
133
+ # If no block is given, an `Enumerator` is returned.
134
+ #
135
+ # @overload each_ancestor
136
+ # Yield all nodes.
137
+ # @overload each_ancestor(type)
138
+ # Yield only nodes matching the type.
139
+ # @param [Symbol] type a node type
140
+ # @overload each_ancestor(type_a, type_b, ...)
141
+ # Yield only nodes matching any of the types.
142
+ # @param [Symbol] type_a a node type
143
+ # @param [Symbol] type_b a node type
144
+ # @yieldparam [Node] node each ancestor node
145
+ # @return [self] if a block is given
146
+ # @return [Enumerator] if no block is given
147
+ def each_ancestor(*types, &block)
148
+ return to_enum(__method__, *types) unless block_given?
149
+
150
+ visit_ancestors(types, &block)
151
+
152
+ self
153
+ end
154
+
155
+ # Returns an array of ancestor nodes.
156
+ # This is a shorthand for `node.each_ancestor.to_a`.
157
+ #
158
+ # @return [Array<Node>] an array of ancestor nodes
159
+ def ancestors
160
+ each_ancestor.to_a
161
+ end
162
+
163
+ # Calls the given block for each child node.
164
+ # If no block is given, an `Enumerator` is returned.
165
+ #
166
+ # Note that this is different from `node.children.each { |child| ... }`
167
+ # which yields all children including non-node elements.
168
+ #
169
+ # @overload each_child_node
170
+ # Yield all nodes.
171
+ # @overload each_child_node(type)
172
+ # Yield only nodes matching the type.
173
+ # @param [Symbol] type a node type
174
+ # @overload each_child_node(type_a, type_b, ...)
175
+ # Yield only nodes matching any of the types.
176
+ # @param [Symbol] type_a a node type
177
+ # @param [Symbol] type_b a node type
178
+ # @yieldparam [Node] node each child node
179
+ # @return [self] if a block is given
180
+ # @return [Enumerator] if no block is given
181
+ def each_child_node(*types)
182
+ return to_enum(__method__, *types) unless block_given?
183
+
184
+ children.each do |child|
185
+ next unless child.is_a?(Node)
186
+
187
+ yield child if types.empty? || types.include?(child.type)
188
+ end
189
+
190
+ self
191
+ end
192
+
193
+ # Returns an array of child nodes.
194
+ # This is a shorthand for `node.each_child_node.to_a`.
195
+ #
196
+ # @return [Array<Node>] an array of child nodes
197
+ def child_nodes
198
+ each_child_node.to_a
199
+ end
200
+
201
+ # Calls the given block for each descendant node with depth first order.
202
+ # If no block is given, an `Enumerator` is returned.
203
+ #
204
+ # @overload each_descendant
205
+ # Yield all nodes.
206
+ # @overload each_descendant(type)
207
+ # Yield only nodes matching the type.
208
+ # @param [Symbol] type a node type
209
+ # @overload each_descendant(type_a, type_b, ...)
210
+ # Yield only nodes matching any of the types.
211
+ # @param [Symbol] type_a a node type
212
+ # @param [Symbol] type_b a node type
213
+ # @yieldparam [Node] node each descendant node
214
+ # @return [self] if a block is given
215
+ # @return [Enumerator] if no block is given
216
+ def each_descendant(*types, &block)
217
+ return to_enum(__method__, *types) unless block_given?
218
+
219
+ visit_descendants(types, &block)
220
+
221
+ self
222
+ end
223
+
224
+ # Returns an array of descendant nodes.
225
+ # This is a shorthand for `node.each_descendant.to_a`.
226
+ #
227
+ # @return [Array<Node>] an array of descendant nodes
228
+ def descendants
229
+ each_descendant.to_a
230
+ end
231
+
232
+ # Calls the given block for the receiver and each descendant node in
233
+ # depth-first order.
234
+ # If no block is given, an `Enumerator` is returned.
235
+ #
236
+ # This method would be useful when you treat the receiver node as the root
237
+ # of a tree and want to iterate over all nodes in the tree.
238
+ #
239
+ # @overload each_node
240
+ # Yield all nodes.
241
+ # @overload each_node(type)
242
+ # Yield only nodes matching the type.
243
+ # @param [Symbol] type a node type
244
+ # @overload each_node(type_a, type_b, ...)
245
+ # Yield only nodes matching any of the types.
246
+ # @param [Symbol] type_a a node type
247
+ # @param [Symbol] type_b a node type
248
+ # @yieldparam [Node] node each node
249
+ # @return [self] if a block is given
250
+ # @return [Enumerator] if no block is given
251
+ def each_node(*types, &block)
252
+ return to_enum(__method__, *types) unless block_given?
253
+
254
+ yield self if types.empty? || types.include?(type)
255
+
256
+ visit_descendants(types, &block)
257
+
258
+ self
259
+ end
260
+
261
+ def source
262
+ loc.expression.source
263
+ end
264
+
265
+ def source_range
266
+ loc.expression
267
+ end
268
+
269
+ def first_line
270
+ loc.line
271
+ end
272
+
273
+ def last_line
274
+ loc.last_line
275
+ end
276
+
277
+ def line_count
278
+ return 0 unless source_range
279
+
280
+ source_range.last_line - source_range.first_line + 1
281
+ end
282
+
283
+ def nonempty_line_count
284
+ source.lines.grep(/\S/).size
285
+ end
286
+
287
+ def source_length
288
+ source_range ? source_range.size : 0
289
+ end
290
+
291
+ ## Destructuring
292
+
293
+ def_node_matcher :receiver, <<~PATTERN
294
+ {(send $_ ...) ({block numblock} (send $_ ...) ...)}
295
+ PATTERN
296
+
297
+ def_node_matcher :str_content, '(str $_)'
298
+
299
+ def const_name
300
+ return unless const_type?
301
+
302
+ namespace, name = *self
303
+ if namespace && !namespace.cbase_type?
304
+ "#{namespace.const_name}::#{name}"
305
+ else
306
+ name.to_s
307
+ end
308
+ end
309
+
310
+ def_node_matcher :defined_module0, <<~PATTERN
311
+ {(class (const $_ $_) ...)
312
+ (module (const $_ $_) ...)
313
+ (casgn $_ $_ (send (const nil? {:Class :Module}) :new ...))
314
+ (casgn $_ $_ (block (send (const nil? {:Class :Module}) :new ...) ...))}
315
+ PATTERN
316
+
317
+ private :defined_module0
318
+
319
+ def defined_module
320
+ namespace, name = *defined_module0
321
+ s(:const, namespace, name) if name
322
+ end
323
+
324
+ def defined_module_name
325
+ (const = defined_module) && const.const_name
326
+ end
327
+
328
+ ## Searching the AST
329
+
330
+ def parent_module_name
331
+ # what class or module is this method/constant/etc definition in?
332
+ # returns nil if answer cannot be determined
333
+ ancestors = each_ancestor(:class, :module, :sclass, :casgn, :block)
334
+ result = ancestors.map do |ancestor|
335
+ parent_module_name_part(ancestor) { |full_name| return full_name }
336
+ end.compact.reverse.join('::')
337
+ result.empty? ? 'Object' : result
338
+ end
339
+
340
+ ## Predicates
341
+
342
+ def multiline?
343
+ line_count > 1
344
+ end
345
+
346
+ def single_line?
347
+ line_count == 1
348
+ end
349
+
350
+ def empty_source?
351
+ source_length.zero?
352
+ end
353
+
354
+ # Some cops treat the shovel operator as a kind of assignment.
355
+ def_node_matcher :assignment_or_similar?, <<~PATTERN
356
+ {assignment? (send _recv :<< ...)}
357
+ PATTERN
358
+
359
+ def literal?
360
+ LITERALS.include?(type)
361
+ end
362
+
363
+ def basic_literal?
364
+ BASIC_LITERALS.include?(type)
365
+ end
366
+
367
+ def truthy_literal?
368
+ TRUTHY_LITERALS.include?(type)
369
+ end
370
+
371
+ def falsey_literal?
372
+ FALSEY_LITERALS.include?(type)
373
+ end
374
+
375
+ def mutable_literal?
376
+ MUTABLE_LITERALS.include?(type)
377
+ end
378
+
379
+ def immutable_literal?
380
+ IMMUTABLE_LITERALS.include?(type)
381
+ end
382
+
383
+ %i[literal basic_literal].each do |kind|
384
+ recursive_kind = :"recursive_#{kind}?"
385
+ kind_filter = :"#{kind}?"
386
+ define_method(recursive_kind) do
387
+ case type
388
+ when :send
389
+ [*COMPARISON_OPERATORS, :!, :<=>].include?(method_name) &&
390
+ receiver.send(recursive_kind) &&
391
+ arguments.all?(&recursive_kind)
392
+ when :begin, :pair, *OPERATOR_KEYWORDS, *COMPOSITE_LITERALS
393
+ children.compact.all?(&recursive_kind)
394
+ else
395
+ send(kind_filter)
396
+ end
397
+ end
398
+ end
399
+
400
+ def variable?
401
+ VARIABLES.include?(type)
402
+ end
403
+
404
+ def reference?
405
+ REFERENCES.include?(type)
406
+ end
407
+
408
+ def equals_asgn?
409
+ EQUALS_ASSIGNMENTS.include?(type)
410
+ end
411
+
412
+ def shorthand_asgn?
413
+ SHORTHAND_ASSIGNMENTS.include?(type)
414
+ end
415
+
416
+ def assignment?
417
+ ASSIGNMENTS.include?(type)
418
+ end
419
+
420
+ def basic_conditional?
421
+ BASIC_CONDITIONALS.include?(type)
422
+ end
423
+
424
+ def conditional?
425
+ CONDITIONALS.include?(type)
426
+ end
427
+
428
+ def keyword?
429
+ return true if special_keyword? || send_type? && prefix_not?
430
+ return false unless KEYWORDS.include?(type)
431
+
432
+ !OPERATOR_KEYWORDS.include?(type) || loc.operator.is?(type.to_s)
433
+ end
434
+
435
+ def special_keyword?
436
+ SPECIAL_KEYWORDS.include?(source)
437
+ end
438
+
439
+ def operator_keyword?
440
+ OPERATOR_KEYWORDS.include?(type)
441
+ end
442
+
443
+ def parenthesized_call?
444
+ loc.respond_to?(:begin) && loc.begin && loc.begin.is?('(')
445
+ end
446
+
447
+ def call_type?
448
+ send_type? || csend_type?
449
+ end
450
+
451
+ def chained?
452
+ parent&.call_type? && eql?(parent.receiver)
453
+ end
454
+
455
+ def argument?
456
+ parent&.send_type? && parent.arguments.include?(self)
457
+ end
458
+
459
+ def boolean_type?
460
+ true_type? || false_type?
461
+ end
462
+
463
+ def numeric_type?
464
+ int_type? || float_type?
465
+ end
466
+
467
+ def range_type?
468
+ irange_type? || erange_type?
469
+ end
470
+
471
+ def guard_clause?
472
+ node = and_type? || or_type? ? rhs : self
473
+
474
+ node.match_guard_clause?
475
+ end
476
+
477
+ def_node_matcher :match_guard_clause?, <<~PATTERN
478
+ [${(send nil? {:raise :fail} ...) return break next} single_line?]
479
+ PATTERN
480
+
481
+ def_node_matcher :proc?, <<~PATTERN
482
+ {(block (send nil? :proc) ...)
483
+ (block (send (const nil? :Proc) :new) ...)
484
+ (send (const nil? :Proc) :new)}
485
+ PATTERN
486
+
487
+ def_node_matcher :lambda?, '({block numblock} (send nil? :lambda) ...)'
488
+ def_node_matcher :lambda_or_proc?, '{lambda? proc?}'
489
+
490
+ def_node_matcher :class_constructor?, <<~PATTERN
491
+ { (send (const nil? {:Class :Module}) :new ...)
492
+ (block (send (const nil? {:Class :Module}) :new ...) ...)}
493
+ PATTERN
494
+
495
+ # Some expressions are evaluated for their value, some for their side
496
+ # effects, and some for both
497
+ # If we know that an expression is useful only for its side effects, that
498
+ # means we can transform it in ways which preserve the side effects, but
499
+ # change the return value
500
+ # So, does the return value of this node matter? If we changed it to
501
+ # `(...; nil)`, might that affect anything?
502
+ #
503
+ # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
504
+ def value_used?
505
+ # Be conservative and return true if we're not sure.
506
+ return false if parent.nil?
507
+
508
+ case parent.type
509
+ when :array, :defined?, :dstr, :dsym, :eflipflop, :erange, :float,
510
+ :hash, :iflipflop, :irange, :not, :pair, :regexp, :str, :sym,
511
+ :when, :xstr
512
+ parent.value_used?
513
+ when :begin, :kwbegin
514
+ begin_value_used?
515
+ when :for
516
+ for_value_used?
517
+ when :case, :if
518
+ case_if_value_used?
519
+ when :while, :until, :while_post, :until_post
520
+ while_until_value_used?
521
+ else
522
+ true
523
+ end
524
+ end
525
+ # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
526
+
527
+ # Some expressions are evaluated for their value, some for their side
528
+ # effects, and some for both.
529
+ # If we know that expressions are useful only for their return values,
530
+ # and have no side effects, that means we can reorder them, change the
531
+ # number of times they are evaluated, or replace them with other
532
+ # expressions which are equivalent in value.
533
+ # So, is evaluation of this node free of side effects?
534
+ #
535
+ def pure?
536
+ # Be conservative and return false if we're not sure
537
+ case type
538
+ when :__FILE__, :__LINE__, :const, :cvar, :defined?, :false, :float,
539
+ :gvar, :int, :ivar, :lvar, :nil, :str, :sym, :true, :regopt
540
+ true
541
+ when :and, :array, :begin, :case, :dstr, :dsym, :eflipflop, :ensure,
542
+ :erange, :for, :hash, :if, :iflipflop, :irange, :kwbegin, :not,
543
+ :or, :pair, :regexp, :until, :until_post, :when, :while,
544
+ :while_post
545
+ child_nodes.all?(&:pure?)
546
+ else
547
+ false
548
+ end
549
+ end
550
+
551
+ protected
552
+
553
+ def visit_descendants(types, &block)
554
+ each_child_node do |child|
555
+ yield child if types.empty? || types.include?(child.type)
556
+ child.visit_descendants(types, &block)
557
+ end
558
+ end
559
+
560
+ private
561
+
562
+ def visit_ancestors(types)
563
+ last_node = self
564
+
565
+ while (current_node = last_node.parent)
566
+ yield current_node if types.empty? ||
567
+ types.include?(current_node.type)
568
+ last_node = current_node
569
+ end
570
+ end
571
+
572
+ def begin_value_used?
573
+ # the last child node determines the value of the parent
574
+ sibling_index == parent.children.size - 1 ? parent.value_used? : false
575
+ end
576
+
577
+ def for_value_used?
578
+ # `for var in enum; body; end`
579
+ # (for <var> <enum> <body>)
580
+ sibling_index == 2 ? parent.value_used? : true
581
+ end
582
+
583
+ def case_if_value_used?
584
+ # (case <condition> <when...>)
585
+ # (if <condition> <truebranch> <falsebranch>)
586
+ sibling_index.zero? ? true : parent.value_used?
587
+ end
588
+
589
+ def while_until_value_used?
590
+ # (while <condition> <body>) -> always evaluates to `nil`
591
+ sibling_index.zero?
592
+ end
593
+
594
+ def parent_module_name_part(node)
595
+ case node.type
596
+ when :class, :module, :casgn
597
+ # TODO: if constant name has cbase (leading ::), then we don't need
598
+ # to keep traversing up through nested classes/modules
599
+ node.defined_module_name
600
+ when :sclass
601
+ yield parent_module_name_for_sclass(node)
602
+ else # block
603
+ parent_module_name_for_block(node) { yield nil }
604
+ end
605
+ end
606
+
607
+ def parent_module_name_for_sclass(sclass_node)
608
+ # TODO: look for constant definition and see if it is nested
609
+ # inside a class or module
610
+ subject = sclass_node.children[0]
611
+
612
+ if subject.const_type?
613
+ "#<Class:#{subject.const_name}>"
614
+ elsif subject.self_type?
615
+ "#<Class:#{sclass_node.parent_module_name}>"
616
+ end
617
+ end
618
+
619
+ def parent_module_name_for_block(ancestor)
620
+ if ancestor.method?(:class_eval)
621
+ # `class_eval` with no receiver applies to whatever module or class
622
+ # we are currently in
623
+ return unless (receiver = ancestor.receiver)
624
+
625
+ yield unless receiver.const_type?
626
+ receiver.const_name
627
+ elsif !new_class_or_module_block?(ancestor)
628
+ yield
629
+ end
630
+ end
631
+
632
+ def_node_matcher :new_class_or_module_block?, <<~PATTERN
633
+ ^(casgn _ _ (block (send (const _ {:Class :Module}) :new) ...))
634
+ PATTERN
635
+ end
636
+ end
637
+ end