spoom 1.2.3 → 1.3.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.
- checksums.yaml +4 -4
- data/README.md +64 -55
- data/lib/spoom/backtrace_filter/minitest.rb +21 -0
- data/lib/spoom/cli/deadcode.rb +172 -0
- data/lib/spoom/cli/helper.rb +20 -0
- data/lib/spoom/cli/srb/bump.rb +200 -0
- data/lib/spoom/cli/srb/coverage.rb +224 -0
- data/lib/spoom/cli/srb/lsp.rb +159 -0
- data/lib/spoom/cli/srb/tc.rb +150 -0
- data/lib/spoom/cli/srb.rb +27 -0
- data/lib/spoom/cli.rb +72 -32
- data/lib/spoom/context/git.rb +2 -2
- data/lib/spoom/context/sorbet.rb +2 -2
- data/lib/spoom/deadcode/definition.rb +11 -0
- data/lib/spoom/deadcode/erb.rb +4 -4
- data/lib/spoom/deadcode/indexer.rb +266 -200
- data/lib/spoom/deadcode/location.rb +30 -2
- data/lib/spoom/deadcode/plugins/action_mailer.rb +21 -0
- data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +19 -0
- data/lib/spoom/deadcode/plugins/actionpack.rb +59 -0
- data/lib/spoom/deadcode/plugins/active_job.rb +13 -0
- data/lib/spoom/deadcode/plugins/active_model.rb +46 -0
- data/lib/spoom/deadcode/plugins/active_record.rb +108 -0
- data/lib/spoom/deadcode/plugins/active_support.rb +32 -0
- data/lib/spoom/deadcode/plugins/base.rb +165 -12
- data/lib/spoom/deadcode/plugins/graphql.rb +47 -0
- data/lib/spoom/deadcode/plugins/minitest.rb +28 -0
- data/lib/spoom/deadcode/plugins/namespaces.rb +32 -0
- data/lib/spoom/deadcode/plugins/rails.rb +31 -0
- data/lib/spoom/deadcode/plugins/rake.rb +12 -0
- data/lib/spoom/deadcode/plugins/rspec.rb +19 -0
- data/lib/spoom/deadcode/plugins/rubocop.rb +41 -0
- data/lib/spoom/deadcode/plugins/ruby.rb +10 -18
- data/lib/spoom/deadcode/plugins/sorbet.rb +40 -0
- data/lib/spoom/deadcode/plugins/thor.rb +21 -0
- data/lib/spoom/deadcode/plugins.rb +91 -0
- data/lib/spoom/deadcode/remover.rb +651 -0
- data/lib/spoom/deadcode/send.rb +27 -6
- data/lib/spoom/deadcode/visitor.rb +755 -0
- data/lib/spoom/deadcode.rb +41 -10
- data/lib/spoom/file_tree.rb +0 -16
- data/lib/spoom/sorbet/errors.rb +1 -1
- data/lib/spoom/sorbet/lsp/structures.rb +2 -2
- data/lib/spoom/version.rb +1 -1
- metadata +36 -15
- data/lib/spoom/cli/bump.rb +0 -198
- data/lib/spoom/cli/coverage.rb +0 -222
- data/lib/spoom/cli/lsp.rb +0 -168
- data/lib/spoom/cli/run.rb +0 -148
@@ -13,8 +13,25 @@ module Spoom
|
|
13
13
|
class << self
|
14
14
|
extend T::Sig
|
15
15
|
|
16
|
-
sig { params(
|
17
|
-
def
|
16
|
+
sig { params(location_string: String).returns(Location) }
|
17
|
+
def from_string(location_string)
|
18
|
+
file, rest = location_string.split(":", 2)
|
19
|
+
raise LocationError, "Invalid location string: #{location_string}" unless file && rest
|
20
|
+
|
21
|
+
start_line, rest = rest.split(":", 2)
|
22
|
+
raise LocationError, "Invalid location string: #{location_string}" unless start_line && rest
|
23
|
+
|
24
|
+
start_column, rest = rest.split("-", 2)
|
25
|
+
raise LocationError, "Invalid location string: #{location_string}" unless start_column && rest
|
26
|
+
|
27
|
+
end_line, end_column = rest.split(":", 2)
|
28
|
+
raise LocationError, "Invalid location string: #{location_string}" unless end_line && end_column
|
29
|
+
|
30
|
+
new(file, start_line.to_i, start_column.to_i, end_line.to_i, end_column.to_i)
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { params(file: String, location: Prism::Location).returns(Location) }
|
34
|
+
def from_prism(file, location)
|
18
35
|
new(file, location.start_line, location.start_column, location.end_line, location.end_column)
|
19
36
|
end
|
20
37
|
end
|
@@ -42,6 +59,17 @@ module Spoom
|
|
42
59
|
@end_column = end_column
|
43
60
|
end
|
44
61
|
|
62
|
+
sig { params(other: Location).returns(T::Boolean) }
|
63
|
+
def include?(other)
|
64
|
+
return false unless @file == other.file
|
65
|
+
return false if @start_line > other.start_line
|
66
|
+
return false if @start_line == other.start_line && @start_column > other.start_column
|
67
|
+
return false if @end_line < other.end_line
|
68
|
+
return false if @end_line == other.end_line && @end_column < other.end_column
|
69
|
+
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
45
73
|
sig { override.params(other: BasicObject).returns(T.nilable(Integer)) }
|
46
74
|
def <=>(other)
|
47
75
|
return unless Location === other
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
module Plugins
|
7
|
+
class ActionMailer < Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { override.params(indexer: Indexer, send: Send).void }
|
11
|
+
def on_send(indexer, send)
|
12
|
+
return unless send.recv.nil? && ActionPack::CALLBACKS.include?(send.name)
|
13
|
+
|
14
|
+
send.each_arg(Prism::SymbolNode) do |arg|
|
15
|
+
indexer.reference_method(arg.unescaped, send.node)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
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 ActionMailerPreview < Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
ignore_classes_inheriting_from("ActionMailer::Preview")
|
11
|
+
|
12
|
+
sig { override.params(indexer: Indexer, definition: Definition).void }
|
13
|
+
def on_define_method(indexer, definition)
|
14
|
+
definition.ignored! if indexer.nesting_class_superclass_name == "ActionMailer::Preview"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
module Plugins
|
7
|
+
class ActionPack < Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
CALLBACKS = T.let(
|
11
|
+
[
|
12
|
+
"after_action",
|
13
|
+
"append_after_action",
|
14
|
+
"append_around_action",
|
15
|
+
"append_before_action",
|
16
|
+
"around_action",
|
17
|
+
"before_action",
|
18
|
+
"prepend_after_action",
|
19
|
+
"prepend_around_action",
|
20
|
+
"prepend_before_action",
|
21
|
+
"skip_after_action",
|
22
|
+
"skip_around_action",
|
23
|
+
"skip_before_action",
|
24
|
+
].freeze,
|
25
|
+
T::Array[String],
|
26
|
+
)
|
27
|
+
|
28
|
+
ignore_classes_named(/Controller$/)
|
29
|
+
|
30
|
+
sig { override.params(indexer: Indexer, definition: Definition).void }
|
31
|
+
def on_define_method(indexer, definition)
|
32
|
+
definition.ignored! if ignored_class_name?(indexer.nesting_class_name)
|
33
|
+
end
|
34
|
+
|
35
|
+
sig { override.params(indexer: Indexer, send: Send).void }
|
36
|
+
def on_send(indexer, send)
|
37
|
+
return unless send.recv.nil? && CALLBACKS.include?(send.name)
|
38
|
+
|
39
|
+
arg = send.args.first
|
40
|
+
case arg
|
41
|
+
when Prism::SymbolNode
|
42
|
+
indexer.reference_method(arg.unescaped, send.node)
|
43
|
+
end
|
44
|
+
|
45
|
+
send.each_arg_assoc do |key, value|
|
46
|
+
key = key.slice.delete_suffix(":")
|
47
|
+
|
48
|
+
case key
|
49
|
+
when "if", "unless"
|
50
|
+
indexer.reference_method(value.slice.delete_prefix(":"), send.node) if value
|
51
|
+
else
|
52
|
+
indexer.reference_constant(camelize(key), send.node)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
module Plugins
|
7
|
+
class ActiveJob < Base
|
8
|
+
ignore_classes_named("ApplicationJob")
|
9
|
+
ignore_methods_named("perform", "build_enumerator", "each_iteration")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
module Plugins
|
7
|
+
class ActiveModel < Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
ignore_classes_inheriting_from(/^(::)?ActiveModel::EachValidator$/)
|
11
|
+
ignore_methods_named("validate_each")
|
12
|
+
|
13
|
+
sig { override.params(indexer: Indexer, send: Send).void }
|
14
|
+
def on_send(indexer, send)
|
15
|
+
return if send.recv
|
16
|
+
|
17
|
+
case send.name
|
18
|
+
when "attribute", "attributes"
|
19
|
+
send.each_arg(Prism::SymbolNode) do |arg|
|
20
|
+
indexer.reference_method(arg.unescaped, send.node)
|
21
|
+
end
|
22
|
+
when "validate", "validates", "validates!", "validates_each"
|
23
|
+
send.each_arg(Prism::SymbolNode) do |arg|
|
24
|
+
indexer.reference_method(arg.unescaped, send.node)
|
25
|
+
end
|
26
|
+
send.each_arg_assoc do |key, value|
|
27
|
+
key = key.slice.delete_suffix(":")
|
28
|
+
|
29
|
+
case key
|
30
|
+
when "if", "unless"
|
31
|
+
indexer.reference_method(value.slice.delete_prefix(":"), send.node) if value
|
32
|
+
else
|
33
|
+
indexer.reference_constant(camelize(key), send.node)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
when "validates_with"
|
37
|
+
arg = send.args.first
|
38
|
+
if arg.is_a?(Prism::SymbolNode)
|
39
|
+
indexer.reference_constant(arg.unescaped, send.node)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
module Plugins
|
7
|
+
class ActiveRecord < Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
ignore_classes_inheriting_from(/^(::)?ActiveRecord::Migration/)
|
11
|
+
|
12
|
+
ignore_methods_named(
|
13
|
+
"change",
|
14
|
+
"down",
|
15
|
+
"up",
|
16
|
+
"table_name_prefix",
|
17
|
+
"to_param",
|
18
|
+
)
|
19
|
+
|
20
|
+
CALLBACKS = T.let(
|
21
|
+
[
|
22
|
+
"after_commit",
|
23
|
+
"after_create_commit",
|
24
|
+
"after_create",
|
25
|
+
"after_destroy_commit",
|
26
|
+
"after_destroy",
|
27
|
+
"after_find",
|
28
|
+
"after_initialize",
|
29
|
+
"after_rollback",
|
30
|
+
"after_save_commit",
|
31
|
+
"after_save",
|
32
|
+
"after_touch",
|
33
|
+
"after_update_commit",
|
34
|
+
"after_update",
|
35
|
+
"after_validation",
|
36
|
+
"around_create",
|
37
|
+
"around_destroy",
|
38
|
+
"around_save",
|
39
|
+
"around_update",
|
40
|
+
"before_create",
|
41
|
+
"before_destroy",
|
42
|
+
"before_save",
|
43
|
+
"before_update",
|
44
|
+
"before_validation",
|
45
|
+
].freeze,
|
46
|
+
T::Array[String],
|
47
|
+
)
|
48
|
+
|
49
|
+
CRUD_METHODS = T.let(
|
50
|
+
[
|
51
|
+
"assign_attributes",
|
52
|
+
"create",
|
53
|
+
"create!",
|
54
|
+
"insert",
|
55
|
+
"insert!",
|
56
|
+
"new",
|
57
|
+
"update",
|
58
|
+
"update!",
|
59
|
+
"upsert",
|
60
|
+
].freeze,
|
61
|
+
T::Array[String],
|
62
|
+
)
|
63
|
+
|
64
|
+
ARRAY_METHODS = T.let(
|
65
|
+
[
|
66
|
+
"insert_all",
|
67
|
+
"insert_all!",
|
68
|
+
"upsert_all",
|
69
|
+
].freeze,
|
70
|
+
T::Array[String],
|
71
|
+
)
|
72
|
+
|
73
|
+
sig { override.params(indexer: Indexer, send: Send).void }
|
74
|
+
def on_send(indexer, send)
|
75
|
+
if send.recv.nil? && CALLBACKS.include?(send.name)
|
76
|
+
send.each_arg(Prism::SymbolNode) do |arg|
|
77
|
+
indexer.reference_method(arg.unescaped, send.node)
|
78
|
+
end
|
79
|
+
return
|
80
|
+
end
|
81
|
+
|
82
|
+
return unless send.recv
|
83
|
+
|
84
|
+
case send.name
|
85
|
+
when *CRUD_METHODS
|
86
|
+
send.each_arg_assoc do |key, _value|
|
87
|
+
key = key.slice.delete_suffix(":")
|
88
|
+
indexer.reference_method("#{key}=", send.node)
|
89
|
+
end
|
90
|
+
when *ARRAY_METHODS
|
91
|
+
send.each_arg(Prism::ArrayNode) do |arg|
|
92
|
+
arg.elements.each do |part|
|
93
|
+
next unless part.is_a?(Prism::HashNode)
|
94
|
+
|
95
|
+
part.elements.each do |assoc|
|
96
|
+
next unless assoc.is_a?(Prism::AssocNode)
|
97
|
+
|
98
|
+
key = assoc.key.slice.delete_suffix(":")
|
99
|
+
indexer.reference_method("#{key}=", send.node)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
module Plugins
|
7
|
+
class ActiveSupport < Base
|
8
|
+
ignore_classes_inheriting_from(/^(::)?ActiveSupport::TestCase$/)
|
9
|
+
|
10
|
+
ignore_methods_named(
|
11
|
+
"after_all",
|
12
|
+
"after_setup",
|
13
|
+
"after_teardown",
|
14
|
+
"before_all",
|
15
|
+
"before_setup",
|
16
|
+
"before_teardown",
|
17
|
+
)
|
18
|
+
|
19
|
+
SETUP_AND_TEARDOWN_METHODS = T.let(["setup", "teardown"], T::Array[String])
|
20
|
+
|
21
|
+
sig { override.params(indexer: Indexer, send: Send).void }
|
22
|
+
def on_send(indexer, send)
|
23
|
+
return unless send.recv.nil? && SETUP_AND_TEARDOWN_METHODS.include?(send.name)
|
24
|
+
|
25
|
+
send.each_arg(Prism::SymbolNode) do |arg|
|
26
|
+
indexer.reference_method(T.must(arg.value), send.node)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -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
|
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
|
@@ -155,7 +269,7 @@ module Spoom
|
|
155
269
|
# return unless send.name == "dsl_method"
|
156
270
|
# return if send.args.empty?
|
157
271
|
#
|
158
|
-
# method_name =
|
272
|
+
# method_name = send.args.first.slice.delete_prefix(":")
|
159
273
|
# indexer.reference_method(method_name, send.node)
|
160
274
|
# end
|
161
275
|
# end
|
@@ -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(
|
176
|
-
def
|
177
|
-
|
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
|
-
|
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
|
-
|
196
|
-
|
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?(Prism::SymbolNode)
|
33
|
+
|
34
|
+
indexer.reference_method(arg.unescaped, send.node)
|
35
|
+
|
36
|
+
send.each_arg_assoc do |key, value|
|
37
|
+
key = key.slice.delete_suffix(":")
|
38
|
+
next unless key == "resolver_method"
|
39
|
+
next unless value
|
40
|
+
|
41
|
+
indexer.reference_method(value.slice.delete_prefix(":"), 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
|