spoom 1.2.3 → 1.2.4

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.
@@ -17,6 +17,60 @@ module Spoom
17
17
 
18
18
  # Plugins DSL
19
19
 
20
+ # Mark classes matching `names` as ignored.
21
+ #
22
+ # Names can be either strings or regexps:
23
+ #
24
+ # ~~~rb
25
+ # class MyPlugin < Spoom::Deadcode::Plugins::Base
26
+ # ignore_class_names(
27
+ # "Foo",
28
+ # "Bar",
29
+ # /Baz.*/,
30
+ # )
31
+ # end
32
+ # ~~~
33
+ sig { params(names: T.any(String, Regexp)).void }
34
+ def ignore_classes_named(*names)
35
+ save_names_and_patterns(names, :@ignored_class_names, :@ignored_class_patterns)
36
+ end
37
+
38
+ # Mark classes directly subclassing a class matching `names` as ignored.
39
+ #
40
+ # Names can be either strings or regexps:
41
+ #
42
+ # ~~~rb
43
+ # class MyPlugin < Spoom::Deadcode::Plugins::Base
44
+ # ignore_classes_inheriting_from(
45
+ # "Foo",
46
+ # "Bar",
47
+ # /Baz.*/,
48
+ # )
49
+ # end
50
+ # ~~~
51
+ sig { params(names: T.any(String, Regexp)).void }
52
+ def ignore_classes_inheriting_from(*names)
53
+ save_names_and_patterns(names, :@ignored_subclasses_of_names, :@ignored_subclasses_of_patterns)
54
+ end
55
+
56
+ # Mark constants matching `names` as ignored.
57
+ #
58
+ # Names can be either strings or regexps:
59
+ #
60
+ # ~~~rb
61
+ # class MyPlugin < Spoom::Deadcode::Plugins::Base
62
+ # ignore_class_names(
63
+ # "FOO",
64
+ # "BAR",
65
+ # /BAZ.*/,
66
+ # )
67
+ # end
68
+ # ~~~
69
+ sig { params(names: T.any(String, Regexp)).void }
70
+ def ignore_constants_named(*names)
71
+ save_names_and_patterns(names, :@ignored_constant_names, :@ignored_constant_patterns)
72
+ end
73
+
20
74
  # Mark methods matching `names` as ignored.
21
75
  #
22
76
  # Names can be either strings or regexps:
@@ -31,10 +85,28 @@ module Spoom
31
85
  # end
32
86
  # ~~~
33
87
  sig { params(names: T.any(String, Regexp)).void }
34
- def ignore_method_names(*names)
88
+ def ignore_methods_named(*names)
35
89
  save_names_and_patterns(names, :@ignored_method_names, :@ignored_method_patterns)
36
90
  end
37
91
 
92
+ # Mark modules matching `names` as ignored.
93
+ #
94
+ # Names can be either strings or regexps:
95
+ #
96
+ # ~~~rb
97
+ # class MyPlugin < Spoom::Deadcode::Plugins::Base
98
+ # ignore_class_names(
99
+ # "Foo",
100
+ # "Bar",
101
+ # /Baz.*/,
102
+ # )
103
+ # end
104
+ # ~~~
105
+ sig { params(names: T.any(String, Regexp)).void }
106
+ def ignore_modules_named(*names)
107
+ save_names_and_patterns(names, :@ignored_module_names, :@ignored_module_patterns)
108
+ end
109
+
38
110
  private
39
111
 
40
112
  sig { params(names: T::Array[T.any(String, Regexp)], names_variable: Symbol, patterns_variable: Symbol).void }
@@ -45,7 +117,7 @@ module Spoom
45
117
  names.each do |name|
46
118
  case name
47
119
  when String
48
- ignored_names << name
120
+ ignored_names << name.delete_prefix("::")
49
121
  when Regexp
50
122
  ignored_patterns << name
51
123
  end
@@ -73,6 +145,12 @@ module Spoom
73
145
  # no-op
74
146
  end
75
147
 
148
+ # 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)
152
+ end
153
+
76
154
  # Called when a class is defined.
77
155
  #
78
156
  # Will be called when the indexer processes a `class` node.
@@ -91,6 +169,18 @@ module Spoom
91
169
  # no-op
92
170
  end
93
171
 
172
+ # 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)
175
+ if ignored_class_name?(definition.name)
176
+ definition.ignored!
177
+ elsif ignored_subclass?(indexer.nesting_class_superclass_name)
178
+ definition.ignored!
179
+ end
180
+
181
+ on_define_class(indexer, definition)
182
+ end
183
+
94
184
  # Called when a constant is defined.
95
185
  #
96
186
  # Will be called when the indexer processes a `CONST =` node.
@@ -109,6 +199,14 @@ module Spoom
109
199
  # no-op
110
200
  end
111
201
 
202
+ # 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)
206
+
207
+ on_define_constant(indexer, definition)
208
+ end
209
+
112
210
  # Called when a method is defined.
113
211
  #
114
212
  # Will be called when the indexer processes a `def` or `defs` node.
@@ -126,7 +224,15 @@ module Spoom
126
224
  # ~~~
127
225
  sig { params(indexer: Indexer, definition: Definition).void }
128
226
  def on_define_method(indexer, definition)
227
+ # no-op
228
+ end
229
+
230
+ # 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)
129
233
  definition.ignored! if ignored_method_name?(definition.name)
234
+
235
+ on_define_method(indexer, definition)
130
236
  end
131
237
 
132
238
  # Called when a module is defined.
@@ -147,6 +253,14 @@ module Spoom
147
253
  # no-op
148
254
  end
149
255
 
256
+ # 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)
260
+
261
+ on_define_module(indexer, definition)
262
+ end
263
+
150
264
  # Called when a send is being processed
151
265
  #
152
266
  # ~~~rb
@@ -165,16 +279,43 @@ module Spoom
165
279
  # no-op
166
280
  end
167
281
 
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
+
168
288
  private
169
289
 
290
+ # DSL support
291
+
292
+ sig { params(name: T.nilable(String)).returns(T::Boolean) }
293
+ def ignored_class_name?(name)
294
+ return false unless name
295
+
296
+ ignored_name?(name, :@ignored_class_names, :@ignored_class_patterns)
297
+ end
298
+
299
+ sig { params(superclass_name: T.nilable(String)).returns(T::Boolean) }
300
+ def ignored_subclass?(superclass_name)
301
+ return false unless superclass_name
302
+
303
+ ignored_name?(superclass_name, :@ignored_subclasses_of_names, :@ignored_subclasses_of_patterns)
304
+ end
305
+
306
+ sig { params(name: String).returns(T::Boolean) }
307
+ def ignored_constant_name?(name)
308
+ ignored_name?(name, :@ignored_constant_names, :@ignored_constant_patterns)
309
+ end
310
+
170
311
  sig { params(name: String).returns(T::Boolean) }
171
312
  def ignored_method_name?(name)
172
313
  ignored_name?(name, :@ignored_method_names, :@ignored_method_patterns)
173
314
  end
174
315
 
175
- sig { params(const: Symbol).returns(T::Set[String]) }
176
- def names(const)
177
- self.class.instance_variable_get(const) || Set.new
316
+ sig { params(name: String).returns(T::Boolean) }
317
+ def ignored_module_name?(name)
318
+ ignored_name?(name, :@ignored_module_names, :@ignored_module_patterns)
178
319
  end
179
320
 
180
321
  sig { params(name: String, names_variable: Symbol, patterns_variable: Symbol).returns(T::Boolean) }
@@ -182,18 +323,30 @@ module Spoom
182
323
  names(names_variable).include?(name) || patterns(patterns_variable).any? { |pattern| pattern.match?(name) }
183
324
  end
184
325
 
326
+ sig { params(const: Symbol).returns(T::Set[String]) }
327
+ def names(const)
328
+ self.class.instance_variable_get(const) || Set.new
329
+ end
330
+
185
331
  sig { params(const: Symbol).returns(T::Array[Regexp]) }
186
332
  def patterns(const)
187
333
  self.class.instance_variable_get(const) || []
188
334
  end
189
335
 
190
- sig { params(indexer: Indexer, send: Send).void }
191
- def reference_send_first_symbol_as_method(indexer, send)
192
- first_arg = send.args.first
193
- return unless first_arg.is_a?(SyntaxTree::SymbolLiteral)
336
+ # Plugin utils
194
337
 
195
- name = indexer.node_string(first_arg.value)
196
- indexer.reference_method(name, send.node)
338
+ sig { params(name: String).returns(String) }
339
+ def camelize(name)
340
+ name = T.must(name.split("::").last)
341
+ name = T.must(name.split("/").last)
342
+ name = name.gsub(/[^a-zA-Z0-9_]/, "")
343
+ name = name.sub(/^[a-z\d]*/, &:capitalize)
344
+ name = name.gsub(%r{(?:_|(/))([a-z\d]*)}) do
345
+ s1 = Regexp.last_match(1)
346
+ s2 = Regexp.last_match(2)
347
+ "#{s1}#{s2&.capitalize}"
348
+ end
349
+ name
197
350
  end
198
351
  end
199
352
  end
@@ -0,0 +1,47 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class GraphQL < Base
8
+ extend T::Sig
9
+
10
+ ignore_classes_inheriting_from(
11
+ /^(::)?GraphQL::Schema::Enum$/,
12
+ /^(::)?GraphQL::Schema::Object$/,
13
+ /^(::)?GraphQL::Schema::Scalar$/,
14
+ /^(::)?GraphQL::Schema::Union$/,
15
+ )
16
+
17
+ ignore_methods_named(
18
+ "coerce_input",
19
+ "coerce_result",
20
+ "graphql_name",
21
+ "resolve",
22
+ "resolve_type",
23
+ "subscribed",
24
+ "unsubscribed",
25
+ )
26
+
27
+ sig { override.params(indexer: Indexer, send: Send).void }
28
+ def on_send(indexer, send)
29
+ return unless send.recv.nil? && send.name == "field"
30
+
31
+ arg = send.args.first
32
+ return unless arg.is_a?(SyntaxTree::SymbolLiteral)
33
+
34
+ indexer.reference_method(indexer.node_string(arg.value), send.node)
35
+
36
+ send.each_arg_assoc do |key, value|
37
+ key = indexer.node_string(key).delete_suffix(":")
38
+ next unless key == "resolver_method"
39
+ next unless value
40
+
41
+ indexer.reference_method(indexer.symbol_string(value), send.node)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,28 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class Minitest < Base
8
+ extend T::Sig
9
+
10
+ ignore_classes_named(/Test$/)
11
+
12
+ ignore_methods_named(
13
+ "after_all",
14
+ "around",
15
+ "around_all",
16
+ "before_all",
17
+ "setup",
18
+ "teardown",
19
+ )
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_/)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class Namespaces < Base
8
+ extend T::Sig
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)
13
+ end
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)
18
+ end
19
+
20
+ private
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?(SyntaxTree::ClassDeclaration) || node.is_a?(SyntaxTree::ModuleDeclaration)
26
+
27
+ node.bodystmt.statements.body.any? do |stmt|
28
+ !stmt.is_a?(SyntaxTree::VoidStmt)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class Rails < Base
8
+ extend T::Sig
9
+
10
+ ignore_constants_named("APP_PATH", "ENGINE_PATH", "ENGINE_ROOT")
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)
15
+ end
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)
20
+ end
21
+
22
+ private
23
+
24
+ sig { params(indexer: Indexer).returns(T::Boolean) }
25
+ def file_is_helper?(indexer)
26
+ indexer.path.match?(%r{app/helpers/.*\.rb$})
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class Rake < Base
8
+ ignore_constants_named("APP_RAKEFILE")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class RSpec < Base
8
+ ignore_classes_named(/Spec$/)
9
+
10
+ ignore_methods_named(
11
+ "after_setup",
12
+ "after_teardown",
13
+ "before_setup",
14
+ "before_teardown",
15
+ )
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,41 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class Rubocop < Base
8
+ extend T::Sig
9
+
10
+ RUBOCOP_CONSTANTS = T.let(["MSG", "RESTRICT_ON_SEND"].to_set.freeze, T::Set[String])
11
+
12
+ ignore_classes_inheriting_from(
13
+ /^(::)?RuboCop::Cop::Cop$/,
14
+ /^(::)?RuboCop::Cop::Base$/,
15
+ )
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
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)
25
+ end
26
+
27
+ private
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
33
+
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"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -7,7 +7,7 @@ module Spoom
7
7
  class Ruby < Base
8
8
  extend T::Sig
9
9
 
10
- ignore_method_names(
10
+ ignore_methods_named(
11
11
  "==",
12
12
  "extended",
13
13
  "included",
@@ -26,7 +26,8 @@ module Spoom
26
26
  when "const_defined?", "const_get", "const_source_location"
27
27
  reference_symbol_as_constant(indexer, send, T.must(send.args.first))
28
28
  when "send", "__send__", "try"
29
- reference_send_first_symbol_as_method(indexer, send)
29
+ arg = send.args.first
30
+ indexer.reference_method(indexer.node_string(arg.value), send.node) if arg.is_a?(SyntaxTree::SymbolLiteral)
30
31
  when "alias_method"
31
32
  last_arg = send.args.last
32
33
 
@@ -0,0 +1,46 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class Sorbet < Base
8
+ extend T::Sig
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)
13
+ end
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)/
18
+ end
19
+
20
+ private
21
+
22
+ sig { params(indexer: Indexer, definition: Definition).returns(T::Boolean) }
23
+ def sorbet_type_member?(indexer, definition)
24
+ assign = indexer.nesting_node(SyntaxTree::Assign)
25
+ return false unless assign
26
+
27
+ value = assign.value
28
+
29
+ case value
30
+ when SyntaxTree::MethodAddBlock
31
+ indexer.node_string(value.call).match?(/^(type_member|type_template)/)
32
+ when SyntaxTree::VCall
33
+ indexer.node_string(value.value).match?(/^(type_member|type_template)/)
34
+ else
35
+ false
36
+ end
37
+ end
38
+
39
+ sig { params(indexer: Indexer, definition: Definition).returns(T::Boolean) }
40
+ def sorbet_enum_constant?(indexer, definition)
41
+ /^(::)?T::Enum$/.match?(indexer.nesting_class_superclass_name) && indexer.nesting_block_call_name == "enums"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,21 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class Thor < Base
8
+ extend T::Sig
9
+
10
+ ignore_methods_named("exit_on_failure?")
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
15
+
16
+ definition.ignored! if indexer.nesting_class_superclass_name =~ /^(::)?Thor$/
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -2,4 +2,94 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require_relative "plugins/base"
5
+
6
+ require_relative "plugins/actionpack"
7
+ require_relative "plugins/active_job"
8
+ require_relative "plugins/action_mailer"
9
+ require_relative "plugins/active_model"
10
+ require_relative "plugins/active_record"
11
+ require_relative "plugins/active_support"
12
+ require_relative "plugins/graphql"
13
+ require_relative "plugins/minitest"
14
+ require_relative "plugins/namespaces"
15
+ require_relative "plugins/rails"
16
+ require_relative "plugins/rake"
17
+ require_relative "plugins/rspec"
18
+ require_relative "plugins/rubocop"
5
19
  require_relative "plugins/ruby"
20
+ require_relative "plugins/sorbet"
21
+ require_relative "plugins/thor"
22
+
23
+ module Spoom
24
+ module Deadcode
25
+ DEFAULT_CUSTOM_PLUGINS_PATH = ".spoom/deadcode/plugins"
26
+
27
+ DEFAULT_PLUGINS = T.let(
28
+ Set.new([
29
+ Spoom::Deadcode::Plugins::Namespaces,
30
+ Spoom::Deadcode::Plugins::Ruby,
31
+ ]).freeze,
32
+ T::Set[T.class_of(Plugins::Base)],
33
+ )
34
+
35
+ PLUGINS_FOR_GEM = T.let(
36
+ {
37
+ "actionmailer" => Spoom::Deadcode::Plugins::ActionMailer,
38
+ "actionpack" => Spoom::Deadcode::Plugins::ActionPack,
39
+ "activejob" => Spoom::Deadcode::Plugins::ActiveJob,
40
+ "activemodel" => Spoom::Deadcode::Plugins::ActiveModel,
41
+ "activerecord" => Spoom::Deadcode::Plugins::ActiveRecord,
42
+ "activesupport" => Spoom::Deadcode::Plugins::ActiveSupport,
43
+ "graphql" => Spoom::Deadcode::Plugins::GraphQL,
44
+ "minitest" => Spoom::Deadcode::Plugins::Minitest,
45
+ "rails" => Spoom::Deadcode::Plugins::Rails,
46
+ "rake" => Spoom::Deadcode::Plugins::Rake,
47
+ "rspec" => Spoom::Deadcode::Plugins::RSpec,
48
+ "rubocop" => Spoom::Deadcode::Plugins::Rubocop,
49
+ "sorbet-runtime" => Spoom::Deadcode::Plugins::Sorbet,
50
+ "sorbet-static" => Spoom::Deadcode::Plugins::Sorbet,
51
+ "thor" => Spoom::Deadcode::Plugins::Thor,
52
+ }.freeze,
53
+ T::Hash[String, T.class_of(Plugins::Base)],
54
+ )
55
+
56
+ class << self
57
+ extend T::Sig
58
+
59
+ sig { params(context: Context).returns(T::Array[Plugins::Base]) }
60
+ def plugins_from_gemfile_lock(context)
61
+ # These plugins are always loaded
62
+ plugin_classes = DEFAULT_PLUGINS.dup
63
+
64
+ # These plugins depends on the gems used by the project
65
+ context.gemfile_lock_specs.keys.each do |name|
66
+ plugin_class = PLUGINS_FOR_GEM[name]
67
+ plugin_classes << plugin_class if plugin_class
68
+ end
69
+
70
+ plugin_classes.map(&:new)
71
+ end
72
+
73
+ sig { params(context: Context).returns(T::Array[Plugins::Base]) }
74
+ def load_custom_plugins(context)
75
+ context.glob("#{DEFAULT_CUSTOM_PLUGINS_PATH}/*.rb").each do |path|
76
+ require("#{context.absolute_path}/#{path}")
77
+ end
78
+
79
+ ObjectSpace
80
+ .each_object(Class)
81
+ .select do |klass|
82
+ next unless T.unsafe(klass).name # skip anonymous classes, we only use them in tests
83
+ next unless T.unsafe(klass) < Plugins::Base
84
+
85
+ location = Object.const_source_location(T.unsafe(klass).to_s)&.first
86
+ next unless location
87
+ next unless location.start_with?("#{context.absolute_path}/#{DEFAULT_CUSTOM_PLUGINS_PATH}")
88
+
89
+ true
90
+ end
91
+ .map { |klass| T.unsafe(klass).new }
92
+ end
93
+ end
94
+ end
95
+ end