spoom 1.3.1 → 1.3.3

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/spoom/cli/deadcode.rb +21 -17
  3. data/lib/spoom/deadcode/index.rb +178 -10
  4. data/lib/spoom/deadcode/indexer.rb +14 -435
  5. data/lib/spoom/deadcode/plugins/action_mailer.rb +3 -3
  6. data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +9 -3
  7. data/lib/spoom/deadcode/plugins/actionpack.rb +12 -9
  8. data/lib/spoom/deadcode/plugins/active_model.rb +8 -8
  9. data/lib/spoom/deadcode/plugins/active_record.rb +5 -5
  10. data/lib/spoom/deadcode/plugins/active_support.rb +4 -4
  11. data/lib/spoom/deadcode/plugins/base.rb +70 -57
  12. data/lib/spoom/deadcode/plugins/graphql.rb +8 -8
  13. data/lib/spoom/deadcode/plugins/minitest.rb +4 -3
  14. data/lib/spoom/deadcode/plugins/namespaces.rb +9 -12
  15. data/lib/spoom/deadcode/plugins/rails.rb +9 -9
  16. data/lib/spoom/deadcode/plugins/rubocop.rb +13 -17
  17. data/lib/spoom/deadcode/plugins/ruby.rb +9 -9
  18. data/lib/spoom/deadcode/plugins/sorbet.rb +15 -18
  19. data/lib/spoom/deadcode/plugins/thor.rb +5 -4
  20. data/lib/spoom/deadcode/plugins.rb +4 -5
  21. data/lib/spoom/deadcode/remover.rb +14 -10
  22. data/lib/spoom/deadcode/send.rb +1 -0
  23. data/lib/spoom/deadcode.rb +4 -73
  24. data/lib/spoom/location.rb +84 -0
  25. data/lib/spoom/model/builder.rb +246 -0
  26. data/lib/spoom/model/model.rb +328 -0
  27. data/lib/spoom/model/namespace_visitor.rb +50 -0
  28. data/lib/spoom/model/reference.rb +49 -0
  29. data/lib/spoom/model/references_visitor.rb +200 -0
  30. data/lib/spoom/model.rb +10 -0
  31. data/lib/spoom/parse.rb +28 -0
  32. data/lib/spoom/poset.rb +197 -0
  33. data/lib/spoom/sorbet/errors.rb +5 -3
  34. data/lib/spoom/sorbet/lsp/errors.rb +1 -1
  35. data/lib/spoom/sorbet.rb +1 -1
  36. data/lib/spoom/version.rb +1 -1
  37. data/lib/spoom/visitor.rb +755 -0
  38. data/lib/spoom.rb +2 -0
  39. metadata +20 -13
  40. data/lib/spoom/deadcode/location.rb +0 -86
  41. data/lib/spoom/deadcode/reference.rb +0 -34
  42. data/lib/spoom/deadcode/visitor.rb +0 -755
@@ -125,6 +125,14 @@ module Spoom
125
125
  end
126
126
  end
127
127
 
128
+ sig { returns(Index) }
129
+ attr_reader :index
130
+
131
+ sig { params(index: Index).void }
132
+ def initialize(index)
133
+ @index = index
134
+ end
135
+
128
136
  # Indexing event methods
129
137
 
130
138
  # Called when an accessor is defined.
@@ -135,20 +143,20 @@ module Spoom
135
143
  #
136
144
  # ~~~rb
137
145
  # class MyPlugin < Spoom::Deadcode::Plugins::Base
138
- # def on_define_accessor(indexer, definition)
139
- # definition.ignored! if definition.name == "foo"
146
+ # def on_define_accessor(definition)
147
+ # @index.ignore(definition) if symbol_def.name == "foo"
140
148
  # end
141
149
  # end
142
150
  # ~~~
143
- sig { params(indexer: Indexer, definition: Definition).void }
144
- def on_define_accessor(indexer, definition)
151
+ sig { params(definition: Model::Attr).void }
152
+ def on_define_accessor(definition)
145
153
  # no-op
146
154
  end
147
155
 
148
156
  # Do not override this method, use `on_define_accessor` instead.
149
- sig { params(indexer: Indexer, definition: Definition).void }
150
- def internal_on_define_accessor(indexer, definition)
151
- on_define_accessor(indexer, definition)
157
+ sig { params(definition: Model::Attr).void }
158
+ def internal_on_define_accessor(definition)
159
+ on_define_accessor(definition)
152
160
  end
153
161
 
154
162
  # Called when a class is defined.
@@ -159,26 +167,26 @@ module Spoom
159
167
  #
160
168
  # ~~~rb
161
169
  # class MyPlugin < Spoom::Deadcode::Plugins::Base
162
- # def on_define_class(indexer, definition)
163
- # definition.ignored! if definition.name == "Foo"
170
+ # def on_define_class(definition)
171
+ # @index.ignore(definition) if definition.name == "Foo"
164
172
  # end
165
173
  # end
166
174
  # ~~~
167
- sig { params(indexer: Indexer, definition: Definition).void }
168
- def on_define_class(indexer, definition)
175
+ sig { params(definition: Model::Class).void }
176
+ def on_define_class(definition)
169
177
  # no-op
170
178
  end
171
179
 
172
180
  # Do not override this method, use `on_define_class` instead.
173
- sig { params(indexer: Indexer, definition: Definition).void }
174
- def internal_on_define_class(indexer, definition)
181
+ sig { params(definition: Model::Class).void }
182
+ def internal_on_define_class(definition)
175
183
  if ignored_class_name?(definition.name)
176
- definition.ignored!
177
- elsif ignored_subclass?(indexer.nesting_class_superclass_name)
178
- definition.ignored!
184
+ @index.ignore(definition)
185
+ elsif ignored_subclass?(definition)
186
+ @index.ignore(definition)
179
187
  end
180
188
 
181
- on_define_class(indexer, definition)
189
+ on_define_class(definition)
182
190
  end
183
191
 
184
192
  # Called when a constant is defined.
@@ -189,22 +197,22 @@ module Spoom
189
197
  #
190
198
  # ~~~rb
191
199
  # class MyPlugin < Spoom::Deadcode::Plugins::Base
192
- # def on_define_constant(indexer, definition)
193
- # definition.ignored! if definition.name == "FOO"
200
+ # def on_define_constant(definition)
201
+ # @index.ignore(definition) if definition.name == "FOO"
194
202
  # end
195
203
  # end
196
204
  # ~~~
197
- sig { params(indexer: Indexer, definition: Definition).void }
198
- def on_define_constant(indexer, definition)
205
+ sig { params(definition: Model::Constant).void }
206
+ def on_define_constant(definition)
199
207
  # no-op
200
208
  end
201
209
 
202
210
  # Do not override this method, use `on_define_constant` instead.
203
- sig { params(indexer: Indexer, definition: Definition).void }
204
- def internal_on_define_constant(indexer, definition)
205
- definition.ignored! if ignored_constant_name?(definition.name)
211
+ sig { params(definition: Model::Constant).void }
212
+ def internal_on_define_constant(definition)
213
+ @index.ignore(definition) if ignored_constant_name?(definition.name)
206
214
 
207
- on_define_constant(indexer, definition)
215
+ on_define_constant(definition)
208
216
  end
209
217
 
210
218
  # Called when a method is defined.
@@ -215,24 +223,22 @@ module Spoom
215
223
  #
216
224
  # ~~~rb
217
225
  # class MyPlugin < Spoom::Deadcode::Plugins::Base
218
- # def on_define_method(indexer, definition)
219
- # super # So the `ignore_method_names` DSL is still applied
220
- #
221
- # definition.ignored! if definition.name == "foo"
226
+ # def on_define_method(definition)
227
+ # @index.ignore(definition) if definition.name == "foo"
222
228
  # end
223
229
  # end
224
230
  # ~~~
225
- sig { params(indexer: Indexer, definition: Definition).void }
226
- def on_define_method(indexer, definition)
231
+ sig { params(definition: Model::Method).void }
232
+ def on_define_method(definition)
227
233
  # no-op
228
234
  end
229
235
 
230
236
  # Do not override this method, use `on_define_method` instead.
231
- sig { params(indexer: Indexer, definition: Definition).void }
232
- def internal_on_define_method(indexer, definition)
233
- definition.ignored! if ignored_method_name?(definition.name)
237
+ sig { params(definition: Model::Method).void }
238
+ def internal_on_define_method(definition)
239
+ @index.ignore(definition) if ignored_method_name?(definition.name)
234
240
 
235
- on_define_method(indexer, definition)
241
+ on_define_method(definition)
236
242
  end
237
243
 
238
244
  # Called when a module is defined.
@@ -243,52 +249,54 @@ module Spoom
243
249
  #
244
250
  # ~~~rb
245
251
  # class MyPlugin < Spoom::Deadcode::Plugins::Base
246
- # def on_define_module(indexer, definition)
247
- # definition.ignored! if definition.name == "Foo"
252
+ # def on_define_module(definition)
253
+ # @index.ignore(definition) if definition.name == "Foo"
248
254
  # end
249
255
  # end
250
256
  # ~~~
251
- sig { params(indexer: Indexer, definition: Definition).void }
252
- def on_define_module(indexer, definition)
257
+ sig { params(definition: Model::Module).void }
258
+ def on_define_module(definition)
253
259
  # no-op
254
260
  end
255
261
 
256
262
  # Do not override this method, use `on_define_module` instead.
257
- sig { params(indexer: Indexer, definition: Definition).void }
258
- def internal_on_define_module(indexer, definition)
259
- definition.ignored! if ignored_module_name?(definition.name)
263
+ sig { params(definition: Model::Module).void }
264
+ def internal_on_define_module(definition)
265
+ @index.ignore(definition) if ignored_module_name?(definition.name)
260
266
 
261
- on_define_module(indexer, definition)
267
+ on_define_module(definition)
262
268
  end
263
269
 
264
270
  # Called when a send is being processed
265
271
  #
266
272
  # ~~~rb
267
273
  # class MyPlugin < Spoom::Deadcode::Plugins::Base
268
- # def on_send(indexer, send)
274
+ # def on_send(send)
269
275
  # return unless send.name == "dsl_method"
270
276
  # return if send.args.empty?
271
277
  #
272
278
  # method_name = send.args.first.slice.delete_prefix(":")
273
- # indexer.reference_method(method_name, send.node)
279
+ # @index.reference_method(method_name, send.node, send.loc)
274
280
  # end
275
281
  # end
276
282
  # ~~~
277
- sig { params(indexer: Indexer, send: Send).void }
278
- def on_send(indexer, send)
283
+ sig { params(send: Send).void }
284
+ def on_send(send)
279
285
  # no-op
280
286
  end
281
287
 
282
- # Do not override this method, use `on_send` instead.
283
- sig { params(indexer: Indexer, send: Send).void }
284
- def internal_on_send(indexer, send)
285
- on_send(indexer, send)
286
- end
287
-
288
288
  private
289
289
 
290
290
  # DSL support
291
291
 
292
+ sig { params(definition: Model::Namespace, superclass_name: String).returns(T::Boolean) }
293
+ def subclass_of?(definition, superclass_name)
294
+ superclass_symbol = @index.model.symbols[superclass_name]
295
+ return false unless superclass_symbol
296
+
297
+ @index.model.symbols_hierarchy.edge?(definition.symbol, superclass_symbol)
298
+ end
299
+
292
300
  sig { params(name: T.nilable(String)).returns(T::Boolean) }
293
301
  def ignored_class_name?(name)
294
302
  return false unless name
@@ -296,11 +304,16 @@ module Spoom
296
304
  ignored_name?(name, :@ignored_class_names, :@ignored_class_patterns)
297
305
  end
298
306
 
299
- sig { params(superclass_name: T.nilable(String)).returns(T::Boolean) }
300
- def ignored_subclass?(superclass_name)
301
- return false unless superclass_name
307
+ sig { params(definition: Model::Class).returns(T::Boolean) }
308
+ def ignored_subclass?(definition)
309
+ superclass_name = definition.superclass_name
310
+ return true if superclass_name && ignored_name?(
311
+ superclass_name,
312
+ :@ignored_subclasses_of_names,
313
+ :@ignored_subclasses_of_patterns,
314
+ )
302
315
 
303
- ignored_name?(superclass_name, :@ignored_subclasses_of_names, :@ignored_subclasses_of_patterns)
316
+ names(:@ignored_subclasses_of_names).any? { |superclass_name| subclass_of?(definition, superclass_name) }
304
317
  end
305
318
 
306
319
  sig { params(name: String).returns(T::Boolean) }
@@ -8,10 +8,10 @@ module Spoom
8
8
  extend T::Sig
9
9
 
10
10
  ignore_classes_inheriting_from(
11
- /^(::)?GraphQL::Schema::Enum$/,
12
- /^(::)?GraphQL::Schema::Object$/,
13
- /^(::)?GraphQL::Schema::Scalar$/,
14
- /^(::)?GraphQL::Schema::Union$/,
11
+ "GraphQL::Schema::Enum",
12
+ "GraphQL::Schema::Object",
13
+ "GraphQL::Schema::Scalar",
14
+ "GraphQL::Schema::Union",
15
15
  )
16
16
 
17
17
  ignore_methods_named(
@@ -24,21 +24,21 @@ module Spoom
24
24
  "unsubscribed",
25
25
  )
26
26
 
27
- sig { override.params(indexer: Indexer, send: Send).void }
28
- def on_send(indexer, send)
27
+ sig { override.params(send: Send).void }
28
+ def on_send(send)
29
29
  return unless send.recv.nil? && send.name == "field"
30
30
 
31
31
  arg = send.args.first
32
32
  return unless arg.is_a?(Prism::SymbolNode)
33
33
 
34
- indexer.reference_method(arg.unescaped, send.node)
34
+ @index.reference_method(arg.unescaped, send.location)
35
35
 
36
36
  send.each_arg_assoc do |key, value|
37
37
  key = key.slice.delete_suffix(":")
38
38
  next unless key == "resolver_method"
39
39
  next unless value
40
40
 
41
- indexer.reference_method(value.slice.delete_prefix(":"), send.node)
41
+ @index.reference_method(value.slice.delete_prefix(":"), send.location)
42
42
  end
43
43
  end
44
44
  end
@@ -18,9 +18,10 @@ module Spoom
18
18
  "teardown",
19
19
  )
20
20
 
21
- sig { override.params(indexer: Indexer, definition: Definition).void }
22
- def on_define_method(indexer, definition)
23
- definition.ignored! if indexer.path.match?(%r{test/.*test\.rb$}) && definition.name.match?(/^test_/)
21
+ sig { override.params(definition: Model::Method).void }
22
+ def on_define_method(definition)
23
+ file = definition.location.file
24
+ @index.ignore(definition) if file.match?(%r{test/.*test\.rb$}) && definition.name.match?(/^test_/)
24
25
  end
25
26
  end
26
27
  end
@@ -7,24 +7,21 @@ module Spoom
7
7
  class Namespaces < Base
8
8
  extend T::Sig
9
9
 
10
- sig { override.params(indexer: Indexer, definition: Definition).void }
11
- def on_define_class(indexer, definition)
12
- definition.ignored! if used_as_namespace?(indexer)
10
+ sig { override.params(definition: Model::Class).void }
11
+ def on_define_class(definition)
12
+ @index.ignore(definition) if used_as_namespace?(definition)
13
13
  end
14
14
 
15
- sig { override.params(indexer: Indexer, definition: Definition).void }
16
- def on_define_module(indexer, definition)
17
- definition.ignored! if used_as_namespace?(indexer)
15
+ sig { override.params(definition: Model::Module).void }
16
+ def on_define_module(definition)
17
+ @index.ignore(definition) if used_as_namespace?(definition)
18
18
  end
19
19
 
20
20
  private
21
21
 
22
- sig { params(indexer: Indexer).returns(T::Boolean) }
23
- def used_as_namespace?(indexer)
24
- node = indexer.current_node
25
- return false unless node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode)
26
-
27
- !!node.body
22
+ sig { params(symbol_def: Model::Namespace).returns(T::Boolean) }
23
+ def used_as_namespace?(symbol_def)
24
+ symbol_def.children.any?
28
25
  end
29
26
  end
30
27
  end
@@ -9,21 +9,21 @@ module Spoom
9
9
 
10
10
  ignore_constants_named("APP_PATH", "ENGINE_PATH", "ENGINE_ROOT")
11
11
 
12
- sig { override.params(indexer: Indexer, definition: Definition).void }
13
- def on_define_class(indexer, definition)
14
- definition.ignored! if file_is_helper?(indexer)
12
+ sig { override.params(definition: Model::Class).void }
13
+ def on_define_class(definition)
14
+ @index.ignore(definition) if file_is_helper?(definition)
15
15
  end
16
16
 
17
- sig { override.params(indexer: Indexer, definition: Definition).void }
18
- def on_define_module(indexer, definition)
19
- definition.ignored! if file_is_helper?(indexer)
17
+ sig { override.params(definition: Model::Module).void }
18
+ def on_define_module(definition)
19
+ @index.ignore(definition) if file_is_helper?(definition)
20
20
  end
21
21
 
22
22
  private
23
23
 
24
- sig { params(indexer: Indexer).returns(T::Boolean) }
25
- def file_is_helper?(indexer)
26
- indexer.path.match?(%r{app/helpers/.*\.rb$})
24
+ sig { params(symbol_def: Model::Namespace).returns(T::Boolean) }
25
+ def file_is_helper?(symbol_def)
26
+ symbol_def.location.file.match?(%r{app/helpers/.*\.rb$})
27
27
  end
28
28
  end
29
29
  end
@@ -10,30 +10,26 @@ module Spoom
10
10
  RUBOCOP_CONSTANTS = T.let(["MSG", "RESTRICT_ON_SEND"].to_set.freeze, T::Set[String])
11
11
 
12
12
  ignore_classes_inheriting_from(
13
- /^(::)?RuboCop::Cop::Cop$/,
14
- /^(::)?RuboCop::Cop::Base$/,
13
+ "RuboCop::Cop::Cop",
14
+ "RuboCop::Cop::Base",
15
15
  )
16
16
 
17
- sig { override.params(indexer: Indexer, definition: Definition).void }
18
- def on_define_constant(indexer, definition)
19
- definition.ignored! if rubocop_constant?(indexer, definition)
20
- end
17
+ sig { override.params(definition: Model::Constant).void }
18
+ def on_define_constant(definition)
19
+ owner = definition.owner
20
+ return false unless owner.is_a?(Model::Class)
21
21
 
22
- sig { override.params(indexer: Indexer, definition: Definition).void }
23
- def on_define_method(indexer, definition)
24
- definition.ignored! if rubocop_method?(indexer, definition)
22
+ @index.ignore(definition) if ignored_subclass?(owner) && RUBOCOP_CONSTANTS.include?(definition.name)
25
23
  end
26
24
 
27
- private
25
+ sig { override.params(definition: Model::Method).void }
26
+ def on_define_method(definition)
27
+ return unless definition.name == "on_send"
28
28
 
29
- sig { params(indexer: Indexer, definition: Definition).returns(T::Boolean) }
30
- def rubocop_constant?(indexer, definition)
31
- ignored_subclass?(indexer.nesting_class_superclass_name) && RUBOCOP_CONSTANTS.include?(definition.name)
32
- end
29
+ owner = definition.owner
30
+ return unless owner.is_a?(Model::Class)
33
31
 
34
- sig { params(indexer: Indexer, definition: Definition).returns(T::Boolean) }
35
- def rubocop_method?(indexer, definition)
36
- ignored_subclass?(indexer.nesting_class_superclass_name) && definition.name == "on_send"
32
+ @index.ignore(definition) if ignored_subclass?(owner)
37
33
  end
38
34
  end
39
35
  end
@@ -20,33 +20,33 @@ module Spoom
20
20
  "to_s",
21
21
  )
22
22
 
23
- sig { override.params(indexer: Indexer, send: Send).void }
24
- def on_send(indexer, send)
23
+ sig { override.params(send: Send).void }
24
+ def on_send(send)
25
25
  case send.name
26
26
  when "const_defined?", "const_get", "const_source_location"
27
- reference_symbol_as_constant(indexer, send, T.must(send.args.first))
27
+ reference_symbol_as_constant(send, T.must(send.args.first))
28
28
  when "send", "__send__", "try"
29
29
  arg = send.args.first
30
- indexer.reference_method(arg.unescaped, send.node) if arg.is_a?(Prism::SymbolNode)
30
+ @index.reference_method(arg.unescaped, send.location) if arg.is_a?(Prism::SymbolNode)
31
31
  when "alias_method"
32
32
  last_arg = send.args.last
33
33
 
34
34
  if last_arg.is_a?(Prism::SymbolNode) || last_arg.is_a?(Prism::StringNode)
35
- indexer.reference_method(last_arg.unescaped, send.node)
35
+ @index.reference_method(last_arg.unescaped, send.location)
36
36
  end
37
37
  end
38
38
  end
39
39
 
40
40
  private
41
41
 
42
- sig { params(indexer: Indexer, send: Send, node: Prism::Node).void }
43
- def reference_symbol_as_constant(indexer, send, node)
42
+ sig { params(send: Send, node: Prism::Node).void }
43
+ def reference_symbol_as_constant(send, node)
44
44
  case node
45
45
  when Prism::SymbolNode
46
- indexer.reference_constant(node.unescaped, send.node)
46
+ @index.reference_constant(node.unescaped, send.location)
47
47
  when Prism::StringNode
48
48
  node.unescaped.split("::").each do |name|
49
- indexer.reference_constant(name, send.node) unless name.empty?
49
+ @index.reference_constant(name, send.location) unless name.empty?
50
50
  end
51
51
  end
52
52
  end
@@ -7,32 +7,29 @@ module Spoom
7
7
  class Sorbet < Base
8
8
  extend T::Sig
9
9
 
10
- sig { override.params(indexer: Indexer, definition: Definition).void }
11
- def on_define_constant(indexer, definition)
12
- definition.ignored! if sorbet_type_member?(indexer, definition) || sorbet_enum_constant?(indexer, definition)
10
+ sig { override.params(definition: Model::Constant).void }
11
+ def on_define_constant(definition)
12
+ @index.ignore(definition) if sorbet_type_member?(definition) || sorbet_enum_constant?(definition)
13
13
  end
14
14
 
15
- sig { override.params(indexer: Indexer, definition: Definition).void }
16
- def on_define_method(indexer, definition)
17
- definition.ignored! if indexer.last_sig =~ /(override|overridable)/
15
+ sig { override.params(definition: Model::Method).void }
16
+ def on_define_method(definition)
17
+ @index.ignore(definition) if definition.sigs.any? { |sig| sig.string =~ /(override|overridable)/ }
18
18
  end
19
19
 
20
20
  private
21
21
 
22
- sig { params(indexer: Indexer, definition: Definition).returns(T::Boolean) }
23
- def sorbet_type_member?(indexer, definition)
24
- assign = indexer.nesting_node(Prism::ConstantWriteNode)
25
- return false unless assign
26
-
27
- value = assign.value
28
- return false unless value.is_a?(Prism::CallNode)
29
-
30
- value.name == :type_member || value.name == :type_template
22
+ sig { params(definition: Model::Constant).returns(T::Boolean) }
23
+ def sorbet_type_member?(definition)
24
+ definition.value.match?(/^(type_member|type_template)/)
31
25
  end
32
26
 
33
- sig { params(indexer: Indexer, definition: Definition).returns(T::Boolean) }
34
- def sorbet_enum_constant?(indexer, definition)
35
- /^(::)?T::Enum$/.match?(indexer.nesting_class_superclass_name) && indexer.nesting_call&.name == :enums
27
+ sig { params(definition: Model::Constant).returns(T::Boolean) }
28
+ def sorbet_enum_constant?(definition)
29
+ owner = definition.owner
30
+ return false unless owner.is_a?(Model::Class)
31
+
32
+ subclass_of?(owner, "T::Enum")
36
33
  end
37
34
  end
38
35
  end
@@ -9,11 +9,12 @@ module Spoom
9
9
 
10
10
  ignore_methods_named("exit_on_failure?")
11
11
 
12
- sig { override.params(indexer: Indexer, definition: Definition).void }
13
- def on_define_method(indexer, definition)
14
- return if indexer.nesting_block # method defined in `no_commands do ... end`, we don't want to ignore it
12
+ sig { override.params(definition: Model::Method).void }
13
+ def on_define_method(definition)
14
+ owner = definition.owner
15
+ return unless owner.is_a?(Model::Class)
15
16
 
16
- definition.ignored! if indexer.nesting_class_superclass_name =~ /^(::)?Thor$/
17
+ @index.ignore(definition) if subclass_of?(owner, "Thor")
17
18
  end
18
19
  end
19
20
  end
@@ -57,7 +57,7 @@ module Spoom
57
57
  class << self
58
58
  extend T::Sig
59
59
 
60
- sig { params(context: Context).returns(T::Array[Plugins::Base]) }
60
+ sig { params(context: Context).returns(T::Set[T.class_of(Plugins::Base)]) }
61
61
  def plugins_from_gemfile_lock(context)
62
62
  # These plugins are always loaded
63
63
  plugin_classes = DEFAULT_PLUGINS.dup
@@ -68,16 +68,16 @@ module Spoom
68
68
  plugin_classes << plugin_class if plugin_class
69
69
  end
70
70
 
71
- plugin_classes.map(&:new)
71
+ plugin_classes
72
72
  end
73
73
 
74
- sig { params(context: Context).returns(T::Array[Plugins::Base]) }
74
+ sig { params(context: Context).returns(T::Array[T.class_of(Plugins::Base)]) }
75
75
  def load_custom_plugins(context)
76
76
  context.glob("#{DEFAULT_CUSTOM_PLUGINS_PATH}/*.rb").each do |path|
77
77
  require("#{context.absolute_path}/#{path}")
78
78
  end
79
79
 
80
- ObjectSpace
80
+ T.unsafe(ObjectSpace)
81
81
  .each_object(Class)
82
82
  .select do |klass|
83
83
  next unless T.unsafe(klass).name # skip anonymous classes, we only use them in tests
@@ -89,7 +89,6 @@ module Spoom
89
89
 
90
90
  true
91
91
  end
92
- .map { |klass| T.unsafe(klass).new }
93
92
  end
94
93
  end
95
94
  end
@@ -145,7 +145,7 @@ module Spoom
145
145
  delete_chars(node.location.start_offset, next_node.location.start_offset)
146
146
  else
147
147
  # Should have been removed as a single MLHS node
148
- raise "Unexpected case while removing constant assignment"
148
+ raise Error, "Unexpected case while removing constant assignment"
149
149
  end
150
150
  end
151
151
 
@@ -202,7 +202,7 @@ module Spoom
202
202
  # ~~~
203
203
  delete_chars(context.node.location.start_offset, next_node.location.start_offset)
204
204
  else
205
- raise "Unexpected case while removing attr_accessor"
205
+ raise Error, "Unexpected case while removing attr_accessor"
206
206
  end
207
207
 
208
208
  insert_accessor(context.node, send_context, was_removed: false) if need_accessor
@@ -399,7 +399,7 @@ module Spoom
399
399
  sig { returns(Prism::Node) }
400
400
  def parent_node
401
401
  parent = @nesting.last
402
- raise "No parent for node #{node}" unless parent
402
+ raise Error, "No parent for node #{node}" unless parent
403
403
 
404
404
  parent
405
405
  end
@@ -408,7 +408,7 @@ module Spoom
408
408
  def parent_context
409
409
  nesting = @nesting.dup
410
410
  parent = nesting.pop
411
- raise "No parent context for node #{@node}" unless parent
411
+ raise Error, "No parent context for node #{@node}" unless parent
412
412
 
413
413
  NodeContext.new(@source, @comments, parent, nesting)
414
414
  end
@@ -419,7 +419,7 @@ module Spoom
419
419
  child_nodes = parent.child_nodes.compact
420
420
 
421
421
  index = child_nodes.index(@node)
422
- raise "Node #{@node} not found in parent #{parent}" unless index
422
+ raise Error, "Node #{@node} not found in parent #{parent}" unless index
423
423
 
424
424
  T.must(child_nodes[0...index])
425
425
  end
@@ -435,7 +435,7 @@ module Spoom
435
435
  child_nodes = parent.child_nodes.compact
436
436
 
437
437
  index = child_nodes.index(node)
438
- raise "Node #{@node} not found in nesting node #{parent}" unless index
438
+ raise Error, "Node #{@node} not found in nesting node #{parent}" unless index
439
439
 
440
440
  T.must(child_nodes.compact[(index + 1)..-1])
441
441
  end
@@ -561,10 +561,10 @@ module Spoom
561
561
  "#{e.message} (at #{e.location.start_line}:#{e.location.start_column})."
562
562
  end.join(" ")
563
563
 
564
- raise ParserError, "Error while parsing #{location.file}: #{message}"
564
+ raise ParseError, "Error while parsing #{location.file}: #{message}"
565
565
  end
566
566
 
567
- visitor = new(location)
567
+ visitor = new(location, kind)
568
568
  visitor.visit(result.value)
569
569
 
570
570
  node = visitor.node
@@ -617,10 +617,11 @@ module Spoom
617
617
  sig { returns(T::Array[Prism::Node]) }
618
618
  attr_reader :nodes_nesting
619
619
 
620
- sig { params(location: Location).void }
621
- def initialize(location)
620
+ sig { params(location: Location, kind: T.nilable(Definition::Kind)).void }
621
+ def initialize(location, kind)
622
622
  super()
623
623
  @location = location
624
+ @kind = kind
624
625
  @node = T.let(nil, T.nilable(Prism::Node))
625
626
  @nodes_nesting = T.let([], T::Array[Prism::Node])
626
627
  end
@@ -635,6 +636,9 @@ module Spoom
635
636
  # We found the node we're looking for at `@location`
636
637
  @node = node
637
638
 
639
+ # The node we found matches the kind we're looking for, we can stop here
640
+ return if @kind && self.class.node_match_kind?(node, @kind)
641
+
638
642
  # There may be a more precise child inside the node that also matches `@location`, let's visit them
639
643
  @nodes_nesting << node
640
644
  super(node)
@@ -12,6 +12,7 @@ module Spoom
12
12
  const :recv, T.nilable(Prism::Node), default: nil
13
13
  const :args, T::Array[Prism::Node], default: []
14
14
  const :block, T.nilable(Prism::Node), default: nil
15
+ const :location, Location
15
16
 
16
17
  sig do
17
18
  type_parameters(:T)