spoom 1.2.2 → 1.2.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|