spoom 1.2.2 → 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.
- checksums.yaml +4 -4
- data/README.md +10 -0
- data/lib/spoom/backtrace_filter/minitest.rb +21 -0
- data/lib/spoom/cli/coverage.rb +1 -1
- data/lib/spoom/context/git.rb +4 -4
- data/lib/spoom/context/sorbet.rb +6 -6
- data/lib/spoom/deadcode/erb.rb +4 -4
- data/lib/spoom/deadcode/indexer.rb +83 -6
- data/lib/spoom/deadcode/location.rb +29 -1
- data/lib/spoom/deadcode/plugins/action_mailer.rb +21 -0
- data/lib/spoom/deadcode/plugins/actionpack.rb +61 -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 +111 -0
- data/lib/spoom/deadcode/plugins/active_support.rb +21 -0
- data/lib/spoom/deadcode/plugins/base.rb +354 -0
- 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 +34 -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 +65 -0
- data/lib/spoom/deadcode/plugins/sorbet.rb +46 -0
- data/lib/spoom/deadcode/plugins/thor.rb +21 -0
- data/lib/spoom/deadcode/plugins.rb +95 -0
- data/lib/spoom/deadcode/remover.rb +616 -0
- data/lib/spoom/deadcode/send.rb +22 -0
- data/lib/spoom/deadcode.rb +8 -6
- data/lib/spoom/sorbet/lsp.rb +2 -2
- data/lib/spoom/sorbet/sigils.rb +2 -2
- data/lib/spoom/sorbet.rb +1 -0
- data/lib/spoom/version.rb +1 -1
- metadata +24 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 23df97e815cc01b35dbc960ec1921bbc1c65ae4c6f0588c690a0d9156877a013
|
4
|
+
data.tar.gz: d82fdb3fb66404b0c2d98ea9206be9d86ec2284bbae2ff24a5653a521ec9155e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 67b6209ba61fbc285fc89dcf64adcde27cfecf44bb37d644cfb8aef50b7fca6e53a15e8eec5bc45030e4c838a35d3777c50a2d75fb32461792a1bc5957d7b2e3
|
7
|
+
data.tar.gz: c3e06e5140aac671ff8489243dee9feba3912ec12681332b2abb37ddd5c367246dc180ea80f6207afe28f81f499626f4dcf4b9628baa263854600d19d593f2f5
|
data/README.md
CHANGED
@@ -348,6 +348,16 @@ Find all the symbols for a file:
|
|
348
348
|
puts client.document_symbols("file://path/to/my/file.rb")
|
349
349
|
```
|
350
350
|
|
351
|
+
### Backtrace Filtering
|
352
|
+
|
353
|
+
Spoom provides a backtrace filter for Minitest to remove the Sorbet frames from test failures, giving a more readable output. To enable it:
|
354
|
+
|
355
|
+
```ruby
|
356
|
+
# test/test_helper.rb
|
357
|
+
require "spoom/backtrace_filter/minitest"
|
358
|
+
Minitest.backtrace_filter = Spoom::BacktraceFilter::Minitest.new
|
359
|
+
```
|
360
|
+
|
351
361
|
## Development
|
352
362
|
|
353
363
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. Don't forget to run `bin/sanity` before pushing your changes.
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "minitest"
|
5
|
+
|
6
|
+
module Spoom
|
7
|
+
module BacktraceFilter
|
8
|
+
class Minitest < ::Minitest::BacktraceFilter
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
SORBET_PATHS = T.let(Gem.loaded_specs["sorbet-runtime"].full_require_paths.freeze, T::Array[String])
|
12
|
+
|
13
|
+
sig { override.params(bt: T.nilable(T::Array[String])).returns(T::Array[String]) }
|
14
|
+
def filter(bt)
|
15
|
+
super.select do |line|
|
16
|
+
SORBET_PATHS.none? { |path| line.include?(path) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/spoom/cli/coverage.rb
CHANGED
data/lib/spoom/context/git.rb
CHANGED
@@ -13,7 +13,7 @@ module Spoom
|
|
13
13
|
sig { params(string: String).returns(T.nilable(Commit)) }
|
14
14
|
def parse_line(string)
|
15
15
|
sha, epoch = string.split(" ", 2)
|
16
|
-
return
|
16
|
+
return unless sha && epoch
|
17
17
|
|
18
18
|
time = Time.strptime(epoch, "%s")
|
19
19
|
Commit.new(sha: sha, time: time)
|
@@ -88,7 +88,7 @@ module Spoom
|
|
88
88
|
sig { returns(T.nilable(String)) }
|
89
89
|
def git_current_branch
|
90
90
|
res = git("branch --show-current")
|
91
|
-
return
|
91
|
+
return unless res.status
|
92
92
|
|
93
93
|
res.out.strip
|
94
94
|
end
|
@@ -103,10 +103,10 @@ module Spoom
|
|
103
103
|
sig { params(short_sha: T::Boolean).returns(T.nilable(Spoom::Git::Commit)) }
|
104
104
|
def git_last_commit(short_sha: true)
|
105
105
|
res = git_log("HEAD --format='%#{short_sha ? "h" : "H"} %at' -1")
|
106
|
-
return
|
106
|
+
return unless res.status
|
107
107
|
|
108
108
|
out = res.out.strip
|
109
|
-
return
|
109
|
+
return if out.empty?
|
110
110
|
|
111
111
|
Spoom::Git::Commit.parse_line(out)
|
112
112
|
end
|
data/lib/spoom/context/sorbet.rb
CHANGED
@@ -52,7 +52,7 @@ module Spoom
|
|
52
52
|
sorbet_bin: sorbet_bin,
|
53
53
|
capture_err: capture_err,
|
54
54
|
)
|
55
|
-
return
|
55
|
+
return unless file?(metrics_file)
|
56
56
|
|
57
57
|
metrics_path = absolute_path_to(metrics_file)
|
58
58
|
metrics = Spoom::Sorbet::MetricsParser.parse_file(metrics_path)
|
@@ -109,7 +109,7 @@ module Spoom
|
|
109
109
|
sig { params(arg: String, sorbet_bin: T.nilable(String), capture_err: T::Boolean).returns(T.nilable(String)) }
|
110
110
|
def srb_version(*arg, sorbet_bin: nil, capture_err: true)
|
111
111
|
res = T.unsafe(self).srb_tc("--no-config", "--version", *arg, sorbet_bin: sorbet_bin, capture_err: capture_err)
|
112
|
-
return
|
112
|
+
return unless res.status
|
113
113
|
|
114
114
|
res.out.split(" ")[2]
|
115
115
|
end
|
@@ -147,10 +147,10 @@ module Spoom
|
|
147
147
|
sig { returns(T.nilable(Spoom::Git::Commit)) }
|
148
148
|
def sorbet_intro_commit
|
149
149
|
res = git_log("--diff-filter=A --format='%h %at' -1 -- sorbet/config")
|
150
|
-
return
|
150
|
+
return unless res.status
|
151
151
|
|
152
152
|
out = res.out.strip
|
153
|
-
return
|
153
|
+
return if out.empty?
|
154
154
|
|
155
155
|
Spoom::Git::Commit.parse_line(out)
|
156
156
|
end
|
@@ -159,10 +159,10 @@ module Spoom
|
|
159
159
|
sig { returns(T.nilable(Spoom::Git::Commit)) }
|
160
160
|
def sorbet_removal_commit
|
161
161
|
res = git_log("--diff-filter=D --format='%h %at' -1 -- sorbet/config")
|
162
|
-
return
|
162
|
+
return unless res.status
|
163
163
|
|
164
164
|
out = res.out.strip
|
165
|
-
return
|
165
|
+
return if out.empty?
|
166
166
|
|
167
167
|
Spoom::Git::Commit.parse_line(out)
|
168
168
|
end
|
data/lib/spoom/deadcode/erb.rb
CHANGED
@@ -44,7 +44,7 @@ module Spoom
|
|
44
44
|
|
45
45
|
private
|
46
46
|
|
47
|
-
sig { params(text: T.untyped).void }
|
47
|
+
sig { override.params(text: T.untyped).void }
|
48
48
|
def add_text(text)
|
49
49
|
return if text.empty?
|
50
50
|
|
@@ -62,7 +62,7 @@ module Spoom
|
|
62
62
|
|
63
63
|
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
64
64
|
|
65
|
-
sig { params(indicator: T.untyped, code: T.untyped).void }
|
65
|
+
sig { override.params(indicator: T.untyped, code: T.untyped).void }
|
66
66
|
def add_expression(indicator, code)
|
67
67
|
flush_newline_if_pending(src)
|
68
68
|
|
@@ -79,13 +79,13 @@ module Spoom
|
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
82
|
-
sig { params(code: T.untyped).void }
|
82
|
+
sig { override.params(code: T.untyped).void }
|
83
83
|
def add_code(code)
|
84
84
|
flush_newline_if_pending(src)
|
85
85
|
super
|
86
86
|
end
|
87
87
|
|
88
|
-
sig { params(_: T.untyped).void }
|
88
|
+
sig { override.params(_: T.untyped).void }
|
89
89
|
def add_postamble(_)
|
90
90
|
flush_newline_if_pending(src)
|
91
91
|
super
|
@@ -12,14 +12,15 @@ module Spoom
|
|
12
12
|
sig { returns(Index) }
|
13
13
|
attr_reader :index
|
14
14
|
|
15
|
-
sig { params(path: String, source: String, index: Index).void }
|
16
|
-
def initialize(path, source, index)
|
15
|
+
sig { params(path: String, source: String, index: Index, plugins: T::Array[Plugins::Base]).void }
|
16
|
+
def initialize(path, source, index, plugins: [])
|
17
17
|
super()
|
18
18
|
|
19
19
|
@path = path
|
20
20
|
@file_name = T.let(File.basename(path), String)
|
21
21
|
@source = source
|
22
22
|
@index = index
|
23
|
+
@plugins = plugins
|
23
24
|
@previous_node = T.let(nil, T.nilable(SyntaxTree::Node))
|
24
25
|
@names_nesting = T.let([], T::Array[String])
|
25
26
|
@nodes_nesting = T.let([], T::Array[SyntaxTree::Node])
|
@@ -156,10 +157,10 @@ module Spoom
|
|
156
157
|
|
157
158
|
sig { override.params(node: SyntaxTree::DefNode).void }
|
158
159
|
def visit_def(node)
|
159
|
-
super
|
160
|
-
|
161
160
|
name = node_string(node.name)
|
162
161
|
define_method(name, [*@names_nesting, name].join("::"), node)
|
162
|
+
|
163
|
+
super
|
163
164
|
end
|
164
165
|
|
165
166
|
sig { override.params(node: SyntaxTree::Field).void }
|
@@ -228,6 +229,10 @@ module Spoom
|
|
228
229
|
define_attr_writer("#{name}=", "#{full_name}=", arg)
|
229
230
|
end
|
230
231
|
else
|
232
|
+
@plugins.each do |plugin|
|
233
|
+
plugin.internal_on_send(self, send)
|
234
|
+
end
|
235
|
+
|
231
236
|
reference_method(send.name, send.node)
|
232
237
|
visit_all(send.args)
|
233
238
|
visit(send.block)
|
@@ -270,8 +275,6 @@ module Spoom
|
|
270
275
|
visit_send(Send.new(node: node, name: node_string(node.value)))
|
271
276
|
end
|
272
277
|
|
273
|
-
private
|
274
|
-
|
275
278
|
# Definition indexing
|
276
279
|
|
277
280
|
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
@@ -283,6 +286,7 @@ module Spoom
|
|
283
286
|
location: node_location(node),
|
284
287
|
)
|
285
288
|
@index.define(definition)
|
289
|
+
@plugins.each { |plugin| plugin.internal_on_define_accessor(self, definition) }
|
286
290
|
end
|
287
291
|
|
288
292
|
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
@@ -294,6 +298,7 @@ module Spoom
|
|
294
298
|
location: node_location(node),
|
295
299
|
)
|
296
300
|
@index.define(definition)
|
301
|
+
@plugins.each { |plugin| plugin.internal_on_define_accessor(self, definition) }
|
297
302
|
end
|
298
303
|
|
299
304
|
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
@@ -305,6 +310,7 @@ module Spoom
|
|
305
310
|
location: node_location(node),
|
306
311
|
)
|
307
312
|
@index.define(definition)
|
313
|
+
@plugins.each { |plugin| plugin.internal_on_define_class(self, definition) }
|
308
314
|
end
|
309
315
|
|
310
316
|
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
@@ -316,6 +322,7 @@ module Spoom
|
|
316
322
|
location: node_location(node),
|
317
323
|
)
|
318
324
|
@index.define(definition)
|
325
|
+
@plugins.each { |plugin| plugin.internal_on_define_constant(self, definition) }
|
319
326
|
end
|
320
327
|
|
321
328
|
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
@@ -327,6 +334,7 @@ module Spoom
|
|
327
334
|
location: node_location(node),
|
328
335
|
)
|
329
336
|
@index.define(definition)
|
337
|
+
@plugins.each { |plugin| plugin.internal_on_define_method(self, definition) }
|
330
338
|
end
|
331
339
|
|
332
340
|
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
@@ -338,6 +346,7 @@ module Spoom
|
|
338
346
|
location: node_location(node),
|
339
347
|
)
|
340
348
|
@index.define(definition)
|
349
|
+
@plugins.each { |plugin| plugin.internal_on_define_module(self, definition) }
|
341
350
|
end
|
342
351
|
|
343
352
|
# Reference indexing
|
@@ -352,6 +361,74 @@ module Spoom
|
|
352
361
|
@index.reference(Reference.new(name: name, kind: Reference::Kind::Method, location: node_location(node)))
|
353
362
|
end
|
354
363
|
|
364
|
+
# Context
|
365
|
+
|
366
|
+
sig { returns(SyntaxTree::Node) }
|
367
|
+
def current_node
|
368
|
+
T.must(@nodes_nesting.last)
|
369
|
+
end
|
370
|
+
|
371
|
+
sig { type_parameters(:N).params(type: T::Class[T.type_parameter(:N)]).returns(T.nilable(T.type_parameter(:N))) }
|
372
|
+
def nesting_node(type)
|
373
|
+
@nodes_nesting.reverse_each do |node|
|
374
|
+
return T.unsafe(node) if node.is_a?(type)
|
375
|
+
end
|
376
|
+
|
377
|
+
nil
|
378
|
+
end
|
379
|
+
|
380
|
+
sig { returns(T.nilable(SyntaxTree::ClassDeclaration)) }
|
381
|
+
def nesting_class
|
382
|
+
nesting_node(SyntaxTree::ClassDeclaration)
|
383
|
+
end
|
384
|
+
|
385
|
+
sig { returns(T.nilable(SyntaxTree::BlockNode)) }
|
386
|
+
def nesting_block
|
387
|
+
nesting_node(SyntaxTree::BlockNode)
|
388
|
+
end
|
389
|
+
|
390
|
+
sig { returns(T.nilable(SyntaxTree::MethodAddBlock)) }
|
391
|
+
def nesting_block_call
|
392
|
+
nesting_node(SyntaxTree::MethodAddBlock)
|
393
|
+
end
|
394
|
+
|
395
|
+
sig { returns(T.nilable(String)) }
|
396
|
+
def nesting_block_call_name
|
397
|
+
block = nesting_block_call
|
398
|
+
return unless block.is_a?(SyntaxTree::MethodAddBlock)
|
399
|
+
|
400
|
+
call = block.call
|
401
|
+
case call
|
402
|
+
when SyntaxTree::ARef
|
403
|
+
node_string(call.collection)
|
404
|
+
when SyntaxTree::CallNode, SyntaxTree::Command, SyntaxTree::CommandCall
|
405
|
+
node_string(call.message)
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
sig { returns(T.nilable(String)) }
|
410
|
+
def nesting_class_name
|
411
|
+
nesting_class = self.nesting_class
|
412
|
+
return unless nesting_class
|
413
|
+
|
414
|
+
node_string(nesting_class.constant)
|
415
|
+
end
|
416
|
+
|
417
|
+
sig { returns(T.nilable(String)) }
|
418
|
+
def nesting_class_superclass_name
|
419
|
+
nesting_class_superclass = nesting_class&.superclass
|
420
|
+
return unless nesting_class_superclass
|
421
|
+
|
422
|
+
node_string(nesting_class_superclass).delete_prefix("::")
|
423
|
+
end
|
424
|
+
|
425
|
+
sig { returns(T.nilable(String)) }
|
426
|
+
def last_sig
|
427
|
+
return unless @previous_node.is_a?(SyntaxTree::MethodAddBlock)
|
428
|
+
|
429
|
+
node_string(@previous_node)
|
430
|
+
end
|
431
|
+
|
355
432
|
# Node utils
|
356
433
|
|
357
434
|
sig { params(node: T.any(Symbol, SyntaxTree::Node)).returns(String) }
|
@@ -13,6 +13,23 @@ module Spoom
|
|
13
13
|
class << self
|
14
14
|
extend T::Sig
|
15
15
|
|
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
|
+
|
16
33
|
sig { params(file: String, location: SyntaxTree::Location).returns(Location) }
|
17
34
|
def from_syntax_tree(file, location)
|
18
35
|
new(file, location.start_line, location.start_column, location.end_line, location.end_column)
|
@@ -42,9 +59,20 @@ 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
|
-
return
|
75
|
+
return unless Location === other
|
48
76
|
|
49
77
|
to_s <=> other.to_s
|
50
78
|
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 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(SyntaxTree::SymbolLiteral) do |arg|
|
15
|
+
indexer.reference_method(indexer.node_string(arg.value), send.node)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,61 @@
|
|
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 SyntaxTree::SymbolLiteral
|
42
|
+
indexer.reference_method(indexer.node_string(arg.value), send.node)
|
43
|
+
when SyntaxTree::VarRef
|
44
|
+
indexer.reference_constant(indexer.node_string(arg), send.node)
|
45
|
+
end
|
46
|
+
|
47
|
+
send.each_arg_assoc do |key, value|
|
48
|
+
key = indexer.node_string(key).delete_suffix(":")
|
49
|
+
|
50
|
+
case key
|
51
|
+
when "if", "unless"
|
52
|
+
indexer.reference_method(indexer.symbol_string(value), send.node) if value
|
53
|
+
else
|
54
|
+
indexer.reference_constant(camelize(key), send.node)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
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(SyntaxTree::SymbolLiteral) do |arg|
|
20
|
+
indexer.reference_method(indexer.node_string(arg.value), send.node)
|
21
|
+
end
|
22
|
+
when "validate", "validates", "validates!", "validates_each"
|
23
|
+
send.each_arg(SyntaxTree::SymbolLiteral) do |arg|
|
24
|
+
indexer.reference_method(indexer.node_string(arg.value), send.node)
|
25
|
+
end
|
26
|
+
send.each_arg_assoc do |key, value|
|
27
|
+
key = indexer.node_string(key).delete_suffix(":")
|
28
|
+
|
29
|
+
case key
|
30
|
+
when "if", "unless"
|
31
|
+
indexer.reference_method(indexer.symbol_string(value), 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?(SyntaxTree::SymbolLiteral)
|
39
|
+
indexer.reference_constant(indexer.node_string(arg.value), send.node)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,111 @@
|
|
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(SyntaxTree::SymbolLiteral) do |arg|
|
77
|
+
indexer.reference_method(indexer.node_string(arg.value), 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 = indexer.symbol_string(key).delete_suffix(":")
|
88
|
+
indexer.reference_method("#{key}=", send.node)
|
89
|
+
end
|
90
|
+
when *ARRAY_METHODS
|
91
|
+
send.each_arg(SyntaxTree::ArrayLiteral) do |arg|
|
92
|
+
args = arg.contents
|
93
|
+
next unless args.is_a?(SyntaxTree::Args)
|
94
|
+
|
95
|
+
args.parts.each do |part|
|
96
|
+
next unless part.is_a?(SyntaxTree::HashLiteral)
|
97
|
+
|
98
|
+
part.assocs.each do |assoc|
|
99
|
+
next unless assoc.is_a?(SyntaxTree::Assoc)
|
100
|
+
|
101
|
+
key = indexer.symbol_string(assoc.key).delete_suffix(":")
|
102
|
+
indexer.reference_method("#{key}=", send.node)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
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 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
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|