spoom 1.5.0 → 1.7.2
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 +14 -0
- data/lib/spoom/backtrace_filter/minitest.rb +3 -4
- data/lib/spoom/cli/deadcode.rb +1 -2
- data/lib/spoom/cli/helper.rb +41 -31
- data/lib/spoom/cli/srb/assertions.rb +48 -0
- data/lib/spoom/cli/srb/bump.rb +1 -2
- data/lib/spoom/cli/srb/coverage.rb +1 -1
- data/lib/spoom/cli/srb/metrics.rb +68 -0
- data/lib/spoom/cli/srb/sigs.rb +209 -0
- data/lib/spoom/cli/srb/tc.rb +16 -1
- data/lib/spoom/cli/srb.rb +16 -4
- data/lib/spoom/cli.rb +1 -2
- data/lib/spoom/colors.rb +2 -6
- data/lib/spoom/context/bundle.rb +8 -9
- data/lib/spoom/context/exec.rb +3 -6
- data/lib/spoom/context/file_system.rb +12 -19
- data/lib/spoom/context/git.rb +14 -19
- data/lib/spoom/context/sorbet.rb +14 -27
- data/lib/spoom/context.rb +4 -8
- data/lib/spoom/counters.rb +22 -0
- data/lib/spoom/coverage/d3/base.rb +6 -8
- data/lib/spoom/coverage/d3/circle_map.rb +6 -16
- data/lib/spoom/coverage/d3/pie.rb +14 -19
- data/lib/spoom/coverage/d3/timeline.rb +46 -47
- data/lib/spoom/coverage/d3.rb +2 -4
- data/lib/spoom/coverage/report.rb +41 -79
- data/lib/spoom/coverage/snapshot.rb +8 -14
- data/lib/spoom/coverage.rb +3 -5
- data/lib/spoom/deadcode/definition.rb +12 -14
- data/lib/spoom/deadcode/erb.rb +10 -8
- data/lib/spoom/deadcode/index.rb +21 -25
- data/lib/spoom/deadcode/indexer.rb +5 -6
- data/lib/spoom/deadcode/plugins/action_mailer.rb +2 -3
- data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +2 -3
- data/lib/spoom/deadcode/plugins/actionpack.rb +19 -22
- data/lib/spoom/deadcode/plugins/active_model.rb +2 -3
- data/lib/spoom/deadcode/plugins/active_record.rb +62 -53
- data/lib/spoom/deadcode/plugins/active_support.rb +3 -2
- data/lib/spoom/deadcode/plugins/base.rb +29 -32
- data/lib/spoom/deadcode/plugins/graphql.rb +2 -3
- data/lib/spoom/deadcode/plugins/minitest.rb +4 -4
- data/lib/spoom/deadcode/plugins/namespaces.rb +5 -5
- data/lib/spoom/deadcode/plugins/rails.rb +5 -5
- data/lib/spoom/deadcode/plugins/rubocop.rb +5 -5
- data/lib/spoom/deadcode/plugins/ruby.rb +3 -4
- data/lib/spoom/deadcode/plugins/sorbet.rb +12 -6
- data/lib/spoom/deadcode/plugins/thor.rb +2 -3
- data/lib/spoom/deadcode/plugins.rb +23 -31
- data/lib/spoom/deadcode/remover.rb +58 -79
- data/lib/spoom/deadcode/send.rb +2 -8
- data/lib/spoom/file_collector.rb +11 -19
- data/lib/spoom/file_tree.rb +36 -51
- data/lib/spoom/location.rb +9 -20
- data/lib/spoom/model/builder.rb +54 -17
- data/lib/spoom/model/model.rb +71 -74
- data/lib/spoom/model/namespace_visitor.rb +4 -3
- data/lib/spoom/model/reference.rb +4 -8
- data/lib/spoom/model/references_visitor.rb +50 -30
- data/lib/spoom/parse.rb +4 -4
- data/lib/spoom/poset.rb +22 -24
- data/lib/spoom/printer.rb +10 -13
- data/lib/spoom/rbs.rb +77 -0
- data/lib/spoom/sorbet/config.rb +17 -24
- data/lib/spoom/sorbet/errors.rb +87 -45
- data/lib/spoom/sorbet/lsp/base.rb +10 -16
- data/lib/spoom/sorbet/lsp/errors.rb +8 -16
- data/lib/spoom/sorbet/lsp/structures.rb +65 -91
- data/lib/spoom/sorbet/lsp.rb +20 -22
- data/lib/spoom/sorbet/metrics/code_metrics_visitor.rb +236 -0
- data/lib/spoom/sorbet/metrics/metrics_file_parser.rb +34 -0
- data/lib/spoom/sorbet/metrics.rb +2 -32
- data/lib/spoom/sorbet/sigils.rb +16 -23
- data/lib/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs.rb +242 -0
- data/lib/spoom/sorbet/translate/sorbet_assertions_to_rbs_comments.rb +123 -0
- data/lib/spoom/sorbet/translate/sorbet_sigs_to_rbs_comments.rb +293 -0
- data/lib/spoom/sorbet/translate/strip_sorbet_sigs.rb +23 -0
- data/lib/spoom/sorbet/translate/translator.rb +71 -0
- data/lib/spoom/sorbet/translate.rb +49 -0
- data/lib/spoom/sorbet.rb +6 -12
- data/lib/spoom/source/rewriter.rb +167 -0
- data/lib/spoom/source.rb +4 -0
- data/lib/spoom/timeline.rb +4 -6
- data/lib/spoom/version.rb +1 -1
- data/lib/spoom/visitor.rb +298 -151
- data/lib/spoom.rb +4 -3
- data/rbi/spoom.rbi +3567 -0
- metadata +62 -8
@@ -5,8 +5,6 @@ module Spoom
|
|
5
5
|
module Deadcode
|
6
6
|
module Plugins
|
7
7
|
class Ruby < Base
|
8
|
-
extend T::Sig
|
9
|
-
|
10
8
|
ignore_methods_named(
|
11
9
|
"==",
|
12
10
|
"extended",
|
@@ -20,7 +18,8 @@ module Spoom
|
|
20
18
|
"to_s",
|
21
19
|
)
|
22
20
|
|
23
|
-
|
21
|
+
# @override
|
22
|
+
#: (Send send) -> void
|
24
23
|
def on_send(send)
|
25
24
|
case send.name
|
26
25
|
when "const_defined?", "const_get", "const_source_location"
|
@@ -42,7 +41,7 @@ module Spoom
|
|
42
41
|
|
43
42
|
private
|
44
43
|
|
45
|
-
|
44
|
+
#: (Send send, Prism::Node node) -> void
|
46
45
|
def reference_symbol_as_constant(send, node)
|
47
46
|
case node
|
48
47
|
when Prism::SymbolNode
|
@@ -5,26 +5,32 @@ module Spoom
|
|
5
5
|
module Deadcode
|
6
6
|
module Plugins
|
7
7
|
class Sorbet < Base
|
8
|
-
|
9
|
-
|
10
|
-
sig { override.params(definition: Model::Constant).void }
|
8
|
+
# @override
|
9
|
+
#: (Model::Constant definition) -> void
|
11
10
|
def on_define_constant(definition)
|
12
11
|
@index.ignore(definition) if sorbet_type_member?(definition) || sorbet_enum_constant?(definition)
|
13
12
|
end
|
14
13
|
|
15
|
-
|
14
|
+
# @override
|
15
|
+
#: (Model::Method definition) -> void
|
16
16
|
def on_define_method(definition)
|
17
|
+
# Ignore signatures containing `override` or `overridable`, like `sig { override.void }`
|
17
18
|
@index.ignore(definition) if definition.sigs.any? { |sig| sig.string =~ /(override|overridable)/ }
|
19
|
+
|
20
|
+
# Ignore comments `@override` and `@overridable`
|
21
|
+
@index.ignore(definition) if definition.comments.any? do |comment|
|
22
|
+
comment.string == "@override" || comment.string == "@overridable"
|
23
|
+
end
|
18
24
|
end
|
19
25
|
|
20
26
|
private
|
21
27
|
|
22
|
-
|
28
|
+
#: (Model::Constant definition) -> bool
|
23
29
|
def sorbet_type_member?(definition)
|
24
30
|
definition.value.match?(/^(type_member|type_template)/)
|
25
31
|
end
|
26
32
|
|
27
|
-
|
33
|
+
#: (Model::Constant definition) -> bool
|
28
34
|
def sorbet_enum_constant?(definition)
|
29
35
|
owner = definition.owner
|
30
36
|
return false unless owner.is_a?(Model::Class)
|
@@ -5,11 +5,10 @@ module Spoom
|
|
5
5
|
module Deadcode
|
6
6
|
module Plugins
|
7
7
|
class Thor < Base
|
8
|
-
extend T::Sig
|
9
|
-
|
10
8
|
ignore_methods_named("exit_on_failure?")
|
11
9
|
|
12
|
-
|
10
|
+
# @override
|
11
|
+
#: (Model::Method definition) -> void
|
13
12
|
def on_define_method(definition)
|
14
13
|
owner = definition.owner
|
15
14
|
return unless owner.is_a?(Model::Class)
|
@@ -25,39 +25,31 @@ module Spoom
|
|
25
25
|
module Deadcode
|
26
26
|
DEFAULT_CUSTOM_PLUGINS_PATH = ".spoom/deadcode/plugins"
|
27
27
|
|
28
|
-
DEFAULT_PLUGINS =
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
]).freeze,
|
33
|
-
T::Set[T.class_of(Plugins::Base)],
|
34
|
-
)
|
28
|
+
DEFAULT_PLUGINS = Set.new([
|
29
|
+
Spoom::Deadcode::Plugins::Namespaces,
|
30
|
+
Spoom::Deadcode::Plugins::Ruby,
|
31
|
+
]).freeze #: Set[singleton(Plugins::Base)]
|
35
32
|
|
36
|
-
PLUGINS_FOR_GEM =
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
}.freeze,
|
54
|
-
T::Hash[String, T.class_of(Plugins::Base)],
|
55
|
-
)
|
33
|
+
PLUGINS_FOR_GEM = {
|
34
|
+
"actionmailer" => Spoom::Deadcode::Plugins::ActionMailer,
|
35
|
+
"actionpack" => Spoom::Deadcode::Plugins::ActionPack,
|
36
|
+
"activejob" => Spoom::Deadcode::Plugins::ActiveJob,
|
37
|
+
"activemodel" => Spoom::Deadcode::Plugins::ActiveModel,
|
38
|
+
"activerecord" => Spoom::Deadcode::Plugins::ActiveRecord,
|
39
|
+
"activesupport" => Spoom::Deadcode::Plugins::ActiveSupport,
|
40
|
+
"graphql" => Spoom::Deadcode::Plugins::GraphQL,
|
41
|
+
"minitest" => Spoom::Deadcode::Plugins::Minitest,
|
42
|
+
"rails" => Spoom::Deadcode::Plugins::Rails,
|
43
|
+
"rake" => Spoom::Deadcode::Plugins::Rake,
|
44
|
+
"rspec" => Spoom::Deadcode::Plugins::RSpec,
|
45
|
+
"rubocop" => Spoom::Deadcode::Plugins::Rubocop,
|
46
|
+
"sorbet-runtime" => Spoom::Deadcode::Plugins::Sorbet,
|
47
|
+
"sorbet-static" => Spoom::Deadcode::Plugins::Sorbet,
|
48
|
+
"thor" => Spoom::Deadcode::Plugins::Thor,
|
49
|
+
}.freeze #: Hash[String, singleton(Plugins::Base)]
|
56
50
|
|
57
51
|
class << self
|
58
|
-
|
59
|
-
|
60
|
-
sig { params(context: Context).returns(T::Set[T.class_of(Plugins::Base)]) }
|
52
|
+
#: (Context context) -> Set[singleton(Plugins::Base)]
|
61
53
|
def plugins_from_gemfile_lock(context)
|
62
54
|
# These plugins are always loaded
|
63
55
|
plugin_classes = DEFAULT_PLUGINS.dup
|
@@ -71,7 +63,7 @@ module Spoom
|
|
71
63
|
plugin_classes
|
72
64
|
end
|
73
65
|
|
74
|
-
|
66
|
+
#: (Context context) -> Array[singleton(Plugins::Base)]
|
75
67
|
def load_custom_plugins(context)
|
76
68
|
context.glob("#{DEFAULT_CUSTOM_PLUGINS_PATH}/*.rb").each do |path|
|
77
69
|
require("#{context.absolute_path}/#{path}")
|
@@ -4,16 +4,14 @@
|
|
4
4
|
module Spoom
|
5
5
|
module Deadcode
|
6
6
|
class Remover
|
7
|
-
extend T::Sig
|
8
|
-
|
9
7
|
class Error < Spoom::Error; end
|
10
8
|
|
11
|
-
|
9
|
+
#: (Context context) -> void
|
12
10
|
def initialize(context)
|
13
11
|
@context = context
|
14
12
|
end
|
15
13
|
|
16
|
-
|
14
|
+
#: (Definition::Kind? kind, Location location) -> String
|
17
15
|
def remove_location(kind, location)
|
18
16
|
file = location.file
|
19
17
|
|
@@ -27,22 +25,20 @@ module Spoom
|
|
27
25
|
end
|
28
26
|
|
29
27
|
class NodeRemover
|
30
|
-
|
31
|
-
|
32
|
-
sig { returns(String) }
|
28
|
+
#: String
|
33
29
|
attr_reader :new_source
|
34
30
|
|
35
|
-
|
31
|
+
#: (String source, Definition::Kind? kind, Location location) -> void
|
36
32
|
def initialize(source, kind, location)
|
37
33
|
@old_source = source
|
38
|
-
@new_source =
|
34
|
+
@new_source = source.dup #: String
|
39
35
|
@kind = kind
|
40
36
|
@location = location
|
41
37
|
|
42
|
-
@node_context =
|
38
|
+
@node_context = NodeFinder.find(source, location, kind) #: NodeContext
|
43
39
|
end
|
44
40
|
|
45
|
-
|
41
|
+
#: -> void
|
46
42
|
def apply_edit
|
47
43
|
sclass_context = @node_context.sclass_context
|
48
44
|
if sclass_context
|
@@ -69,7 +65,7 @@ module Spoom
|
|
69
65
|
|
70
66
|
private
|
71
67
|
|
72
|
-
|
68
|
+
#: (NodeContext context) -> void
|
73
69
|
def delete_constant_assignment(context)
|
74
70
|
case context.node
|
75
71
|
when Prism::ConstantWriteNode, Prism::ConstantOperatorWriteNode,
|
@@ -103,8 +99,10 @@ module Spoom
|
|
103
99
|
prev_node = context.previous_node
|
104
100
|
next_node = context.next_node
|
105
101
|
|
106
|
-
|
107
|
-
|
102
|
+
has_prev_node_on_different_line = prev_node && prev_node.location.end_line != node.location.start_line
|
103
|
+
has_next_node_on_different_line = next_node && next_node.location.start_line != node.location.end_line
|
104
|
+
|
105
|
+
if has_prev_node_on_different_line && has_next_node_on_different_line
|
108
106
|
# We have a node before and after, but on different lines, we need to remove the whole line
|
109
107
|
#
|
110
108
|
# ~~~
|
@@ -149,7 +147,7 @@ module Spoom
|
|
149
147
|
end
|
150
148
|
end
|
151
149
|
|
152
|
-
|
150
|
+
#: (NodeContext context) -> void
|
153
151
|
def delete_attr_accessor(context)
|
154
152
|
args_context = context.parent_context
|
155
153
|
send_context = args_context.parent_context
|
@@ -168,8 +166,10 @@ module Spoom
|
|
168
166
|
prev_node = context.previous_node
|
169
167
|
next_node = context.next_node
|
170
168
|
|
171
|
-
|
172
|
-
|
169
|
+
has_prev_node_on_different_line = prev_node && prev_node.location.end_line != context.node.location.start_line
|
170
|
+
has_next_node_on_different_line = next_node && next_node.location.start_line != context.node.location.end_line
|
171
|
+
|
172
|
+
if has_prev_node_on_different_line && has_next_node_on_different_line
|
173
173
|
# We have a node before and after, but on different lines, we need to remove the whole line
|
174
174
|
#
|
175
175
|
# ~~~
|
@@ -208,13 +208,7 @@ module Spoom
|
|
208
208
|
insert_accessor(context.node, send_context, was_removed: false) if need_accessor
|
209
209
|
end
|
210
210
|
|
211
|
-
|
212
|
-
params(
|
213
|
-
node: Prism::Node,
|
214
|
-
send_context: NodeContext,
|
215
|
-
was_removed: T::Boolean,
|
216
|
-
).void
|
217
|
-
end
|
211
|
+
#: (Prism::Node node, NodeContext send_context, was_removed: bool) -> void
|
218
212
|
def insert_accessor(node, send_context, was_removed:)
|
219
213
|
name = node.slice
|
220
214
|
code = case @kind
|
@@ -257,7 +251,7 @@ module Spoom
|
|
257
251
|
@new_source = lines.join
|
258
252
|
end
|
259
253
|
|
260
|
-
|
254
|
+
#: (NodeContext context) -> void
|
261
255
|
def delete_node_and_comments_and_sigs(context)
|
262
256
|
start_line = context.node.location.start_line
|
263
257
|
end_line = context.node.location.end_line
|
@@ -285,7 +279,7 @@ module Spoom
|
|
285
279
|
|
286
280
|
# Adjust the lines to remove to include previous blank lines
|
287
281
|
prev_context = NodeContext.new(@old_source, @node_context.comments, first_node, context.nesting)
|
288
|
-
before =
|
282
|
+
before = prev_context.previous_node #: (Prism::Node | Prism::Comment)?
|
289
283
|
|
290
284
|
# There may be an unrelated comment between the current node and the one before
|
291
285
|
# if there is, we only want to delete lines up to the last comment found
|
@@ -320,26 +314,26 @@ module Spoom
|
|
320
314
|
delete_lines(start_line, end_line)
|
321
315
|
end
|
322
316
|
|
323
|
-
|
317
|
+
#: (Integer start_line, Integer end_line) -> void
|
324
318
|
def delete_lines(start_line, end_line)
|
325
319
|
lines = @new_source.lines
|
326
320
|
lines[start_line - 1...end_line] = []
|
327
321
|
@new_source = lines.join
|
328
322
|
end
|
329
323
|
|
330
|
-
|
324
|
+
#: (Integer start_char, Integer end_char) -> void
|
331
325
|
def delete_chars(start_char, end_char)
|
332
326
|
@new_source[start_char...end_char] = ""
|
333
327
|
end
|
334
328
|
|
335
|
-
|
329
|
+
#: (Integer start_char, Integer end_char, String replacement) -> void
|
336
330
|
def replace_chars(start_char, end_char, replacement)
|
337
331
|
@new_source[start_char...end_char] = replacement
|
338
332
|
end
|
339
333
|
|
340
|
-
|
334
|
+
#: (Prism::CallNode node, name: String, kind: Definition::Kind?) -> String
|
341
335
|
def transform_sig(node, name:, kind:)
|
342
|
-
type =
|
336
|
+
type = nil #: String?
|
343
337
|
|
344
338
|
block = T.cast(node.block, Prism::BlockNode)
|
345
339
|
statements = T.cast(block.body, Prism::StatementsNode)
|
@@ -370,25 +364,16 @@ module Spoom
|
|
370
364
|
end
|
371
365
|
|
372
366
|
class NodeContext
|
373
|
-
|
374
|
-
|
375
|
-
sig { returns(T::Hash[Integer, Prism::Comment]) }
|
367
|
+
#: Hash[Integer, Prism::Comment]
|
376
368
|
attr_reader :comments
|
377
369
|
|
378
|
-
|
370
|
+
#: Prism::Node
|
379
371
|
attr_reader :node
|
380
372
|
|
381
|
-
|
373
|
+
#: Array[Prism::Node]
|
382
374
|
attr_accessor :nesting
|
383
375
|
|
384
|
-
|
385
|
-
params(
|
386
|
-
source: String,
|
387
|
-
comments: T::Hash[Integer, Prism::Comment],
|
388
|
-
node: Prism::Node,
|
389
|
-
nesting: T::Array[Prism::Node],
|
390
|
-
).void
|
391
|
-
end
|
376
|
+
#: (String source, Hash[Integer, Prism::Comment] comments, Prism::Node node, Array[Prism::Node] nesting) -> void
|
392
377
|
def initialize(source, comments, node, nesting)
|
393
378
|
@source = source
|
394
379
|
@comments = comments
|
@@ -396,7 +381,7 @@ module Spoom
|
|
396
381
|
@nesting = nesting
|
397
382
|
end
|
398
383
|
|
399
|
-
|
384
|
+
#: -> Prism::Node
|
400
385
|
def parent_node
|
401
386
|
parent = @nesting.last
|
402
387
|
raise Error, "No parent for node #{node}" unless parent
|
@@ -404,7 +389,7 @@ module Spoom
|
|
404
389
|
parent
|
405
390
|
end
|
406
391
|
|
407
|
-
|
392
|
+
#: -> NodeContext
|
408
393
|
def parent_context
|
409
394
|
nesting = @nesting.dup
|
410
395
|
parent = nesting.pop
|
@@ -413,7 +398,7 @@ module Spoom
|
|
413
398
|
NodeContext.new(@source, @comments, parent, nesting)
|
414
399
|
end
|
415
400
|
|
416
|
-
|
401
|
+
#: -> Array[Prism::Node]
|
417
402
|
def previous_nodes
|
418
403
|
parent = parent_node
|
419
404
|
child_nodes = parent.child_nodes.compact
|
@@ -424,12 +409,12 @@ module Spoom
|
|
424
409
|
T.must(child_nodes[0...index])
|
425
410
|
end
|
426
411
|
|
427
|
-
|
412
|
+
#: -> Prism::Node?
|
428
413
|
def previous_node
|
429
414
|
previous_nodes.last
|
430
415
|
end
|
431
416
|
|
432
|
-
|
417
|
+
#: -> Array[Prism::Node]
|
433
418
|
def next_nodes
|
434
419
|
parent = parent_node
|
435
420
|
child_nodes = parent.child_nodes.compact
|
@@ -440,14 +425,14 @@ module Spoom
|
|
440
425
|
T.must(child_nodes.compact[(index + 1)..-1])
|
441
426
|
end
|
442
427
|
|
443
|
-
|
428
|
+
#: -> Prism::Node?
|
444
429
|
def next_node
|
445
430
|
next_nodes.first
|
446
431
|
end
|
447
432
|
|
448
|
-
|
433
|
+
#: -> NodeContext?
|
449
434
|
def sclass_context
|
450
|
-
sclass =
|
435
|
+
sclass = nil #: Prism::SingletonClassNode?
|
451
436
|
|
452
437
|
nesting = @nesting.dup
|
453
438
|
until nesting.empty? || sclass
|
@@ -473,12 +458,12 @@ module Spoom
|
|
473
458
|
nil
|
474
459
|
end
|
475
460
|
|
476
|
-
|
461
|
+
#: (Prism::Node? node) -> bool
|
477
462
|
def sorbet_signature?(node)
|
478
463
|
node.is_a?(Prism::CallNode) && node.name == :sig
|
479
464
|
end
|
480
465
|
|
481
|
-
|
466
|
+
#: (Prism::Node? node) -> bool
|
482
467
|
def sorbet_extend_sig?(node)
|
483
468
|
return false unless node.is_a?(Prism::CallNode)
|
484
469
|
return false unless node.name == :extend
|
@@ -490,9 +475,9 @@ module Spoom
|
|
490
475
|
args.arguments.first&.slice == "T::Sig"
|
491
476
|
end
|
492
477
|
|
493
|
-
|
478
|
+
#: (Integer start_line, Integer end_line) -> Array[Prism::Comment]
|
494
479
|
def comments_between_lines(start_line, end_line)
|
495
|
-
comments =
|
480
|
+
comments = [] #: Array[Prism::Comment]
|
496
481
|
|
497
482
|
(start_line + 1).upto(end_line - 1) do |line|
|
498
483
|
comment = @comments[line]
|
@@ -502,9 +487,9 @@ module Spoom
|
|
502
487
|
comments
|
503
488
|
end
|
504
489
|
|
505
|
-
|
490
|
+
#: (Prism::Node node) -> Array[Prism::Comment]
|
506
491
|
def attached_comments(node)
|
507
|
-
comments =
|
492
|
+
comments = [] #: Array[Prism::Comment]
|
508
493
|
|
509
494
|
start_line = node.location.start_line - 1
|
510
495
|
start_line.downto(1) do |line|
|
@@ -517,9 +502,9 @@ module Spoom
|
|
517
502
|
comments.reverse
|
518
503
|
end
|
519
504
|
|
520
|
-
|
505
|
+
#: -> Array[Prism::Node]
|
521
506
|
def attached_sigs
|
522
|
-
nodes =
|
507
|
+
nodes = [] #: Array[Prism::Node]
|
523
508
|
|
524
509
|
previous_nodes.reverse_each do |prev_node|
|
525
510
|
break unless sorbet_signature?(prev_node)
|
@@ -530,7 +515,7 @@ module Spoom
|
|
530
515
|
nodes.reverse
|
531
516
|
end
|
532
517
|
|
533
|
-
|
518
|
+
#: -> Prism::CallNode?
|
534
519
|
def attached_sig
|
535
520
|
previous_nodes.reverse_each do |node|
|
536
521
|
if node.is_a?(Prism::Comment)
|
@@ -547,12 +532,8 @@ module Spoom
|
|
547
532
|
end
|
548
533
|
|
549
534
|
class NodeFinder < Visitor
|
550
|
-
extend T::Sig
|
551
|
-
|
552
535
|
class << self
|
553
|
-
|
554
|
-
|
555
|
-
sig { params(source: String, location: Location, kind: T.nilable(Definition::Kind)).returns(NodeContext) }
|
536
|
+
#: (String source, Location location, Definition::Kind? kind) -> NodeContext
|
556
537
|
def find(source, location, kind)
|
557
538
|
result = Prism.parse(source)
|
558
539
|
|
@@ -576,17 +557,14 @@ module Spoom
|
|
576
557
|
raise Error, "Can't find node at #{location}, expected #{kind} but got #{node.class}"
|
577
558
|
end
|
578
559
|
|
579
|
-
comments_by_line =
|
580
|
-
|
581
|
-
|
582
|
-
end,
|
583
|
-
T::Hash[Integer, Prism::Comment],
|
584
|
-
)
|
560
|
+
comments_by_line = result.comments.to_h do |comment|
|
561
|
+
[comment.location.start_line, comment]
|
562
|
+
end #: Hash[Integer, Prism::Comment]
|
585
563
|
|
586
564
|
NodeContext.new(source, comments_by_line, node, visitor.nodes_nesting)
|
587
565
|
end
|
588
566
|
|
589
|
-
|
567
|
+
#: (Prism::Node node, Definition::Kind kind) -> bool
|
590
568
|
def node_match_kind?(node, kind)
|
591
569
|
case kind
|
592
570
|
when Definition::Kind::AttrReader, Definition::Kind::AttrWriter
|
@@ -611,22 +589,23 @@ module Spoom
|
|
611
589
|
end
|
612
590
|
end
|
613
591
|
|
614
|
-
|
592
|
+
#: Prism::Node?
|
615
593
|
attr_reader :node
|
616
594
|
|
617
|
-
|
595
|
+
#: Array[Prism::Node]
|
618
596
|
attr_reader :nodes_nesting
|
619
597
|
|
620
|
-
|
598
|
+
#: (Location location, Definition::Kind? kind) -> void
|
621
599
|
def initialize(location, kind)
|
622
600
|
super()
|
623
601
|
@location = location
|
624
602
|
@kind = kind
|
625
|
-
@node =
|
626
|
-
@nodes_nesting =
|
603
|
+
@node = nil #: Prism::Node?
|
604
|
+
@nodes_nesting = [] #: Array[Prism::Node]
|
627
605
|
end
|
628
606
|
|
629
|
-
|
607
|
+
# @override
|
608
|
+
#: (Prism::Node? node) -> void
|
630
609
|
def visit(node)
|
631
610
|
return unless node
|
632
611
|
|
data/lib/spoom/deadcode/send.rb
CHANGED
@@ -5,8 +5,6 @@ module Spoom
|
|
5
5
|
module Deadcode
|
6
6
|
# An abstraction to simplify handling of Prism::CallNode nodes.
|
7
7
|
class Send < T::Struct
|
8
|
-
extend T::Sig
|
9
|
-
|
10
8
|
const :node, Prism::CallNode
|
11
9
|
const :name, String
|
12
10
|
const :recv, T.nilable(Prism::Node), default: nil
|
@@ -14,18 +12,14 @@ module Spoom
|
|
14
12
|
const :block, T.nilable(Prism::Node), default: nil
|
15
13
|
const :location, Location
|
16
14
|
|
17
|
-
|
18
|
-
type_parameters(:T)
|
19
|
-
.params(arg_type: T::Class[T.type_parameter(:T)], block: T.proc.params(arg: T.type_parameter(:T)).void)
|
20
|
-
.void
|
21
|
-
end
|
15
|
+
#: [T] (Class[T] arg_type) { (T arg) -> void } -> void
|
22
16
|
def each_arg(arg_type, &block)
|
23
17
|
args.each do |arg|
|
24
18
|
yield(T.unsafe(arg)) if arg.is_a?(arg_type)
|
25
19
|
end
|
26
20
|
end
|
27
21
|
|
28
|
-
|
22
|
+
#: { (Prism::Node key, Prism::Node? value) -> void } -> void
|
29
23
|
def each_arg_assoc(&block)
|
30
24
|
args.each do |arg|
|
31
25
|
next unless arg.is_a?(Prism::KeywordHashNode) || arg.is_a?(Prism::HashNode)
|
data/lib/spoom/file_collector.rb
CHANGED
@@ -3,9 +3,7 @@
|
|
3
3
|
|
4
4
|
module Spoom
|
5
5
|
class FileCollector
|
6
|
-
|
7
|
-
|
8
|
-
sig { returns(T::Array[String]) }
|
6
|
+
#: Array[String]
|
9
7
|
attr_reader :files
|
10
8
|
|
11
9
|
# Initialize a new file collector
|
@@ -16,26 +14,20 @@ module Spoom
|
|
16
14
|
# If `allow_mime_types` is empty, all files are collected.
|
17
15
|
# If `allow_mime_types` is an array of mimetypes, files without an extension are collected if their mimetype is in
|
18
16
|
# the list.
|
19
|
-
|
20
|
-
params(
|
21
|
-
allow_extensions: T::Array[String],
|
22
|
-
allow_mime_types: T::Array[String],
|
23
|
-
exclude_patterns: T::Array[String],
|
24
|
-
).void
|
25
|
-
end
|
17
|
+
#: (?allow_extensions: Array[String], ?allow_mime_types: Array[String], ?exclude_patterns: Array[String]) -> void
|
26
18
|
def initialize(allow_extensions: [], allow_mime_types: [], exclude_patterns: [])
|
27
|
-
@files =
|
19
|
+
@files = [] #: Array[String]
|
28
20
|
@allow_extensions = allow_extensions
|
29
21
|
@allow_mime_types = allow_mime_types
|
30
22
|
@exclude_patterns = exclude_patterns
|
31
23
|
end
|
32
24
|
|
33
|
-
|
25
|
+
#: (Array[String] paths) -> void
|
34
26
|
def visit_paths(paths)
|
35
27
|
paths.each { |path| visit_path(path) }
|
36
28
|
end
|
37
29
|
|
38
|
-
|
30
|
+
#: (String path) -> void
|
39
31
|
def visit_path(path)
|
40
32
|
path = clean_path(path)
|
41
33
|
|
@@ -52,24 +44,24 @@ module Spoom
|
|
52
44
|
|
53
45
|
private
|
54
46
|
|
55
|
-
|
47
|
+
#: (String path) -> String
|
56
48
|
def clean_path(path)
|
57
49
|
Pathname.new(path).cleanpath.to_s
|
58
50
|
end
|
59
51
|
|
60
|
-
|
52
|
+
#: (String path) -> void
|
61
53
|
def visit_file(path)
|
62
54
|
return if excluded_file?(path)
|
63
55
|
|
64
56
|
@files << path
|
65
57
|
end
|
66
58
|
|
67
|
-
|
59
|
+
#: (String path) -> void
|
68
60
|
def visit_directory(path)
|
69
61
|
visit_paths(Dir.glob("#{path}/*"))
|
70
62
|
end
|
71
63
|
|
72
|
-
|
64
|
+
#: (String path) -> bool
|
73
65
|
def excluded_file?(path)
|
74
66
|
return false if @allow_extensions.empty?
|
75
67
|
|
@@ -84,7 +76,7 @@ module Spoom
|
|
84
76
|
end
|
85
77
|
end
|
86
78
|
|
87
|
-
|
79
|
+
#: (String path) -> bool
|
88
80
|
def excluded_path?(path)
|
89
81
|
@exclude_patterns.any? do |pattern|
|
90
82
|
# Use `FNM_PATHNAME` so patterns do not match directory separators
|
@@ -93,7 +85,7 @@ module Spoom
|
|
93
85
|
end
|
94
86
|
end
|
95
87
|
|
96
|
-
|
88
|
+
#: (String path) -> String?
|
97
89
|
def mime_type_for(path)
|
98
90
|
# The `file` command appears to be hanging on MacOS for some files so we timeout after 1s.
|
99
91
|
%x{timeout 1s file --mime-type -b '#{path}'}.split("; ").first&.strip
|