spoom 1.2.0 → 1.2.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/Gemfile +0 -1
- data/lib/spoom/cli/bump.rb +4 -5
- data/lib/spoom/cli.rb +4 -8
- data/lib/spoom/context/bundle.rb +16 -7
- data/lib/spoom/context/file_system.rb +17 -0
- data/lib/spoom/context/git.rb +17 -1
- data/lib/spoom/context/sorbet.rb +42 -7
- data/lib/spoom/coverage/d3/circle_map.rb +22 -36
- data/lib/spoom/coverage/report.rb +45 -32
- data/lib/spoom/coverage.rb +12 -12
- data/lib/spoom/deadcode/definition.rb +98 -0
- data/lib/spoom/deadcode/erb.rb +103 -0
- data/lib/spoom/deadcode/index.rb +61 -0
- data/lib/spoom/deadcode/indexer.rb +394 -0
- data/lib/spoom/deadcode/location.rb +58 -0
- data/lib/spoom/deadcode/reference.rb +34 -0
- data/lib/spoom/deadcode/send.rb +18 -0
- data/lib/spoom/deadcode.rb +55 -0
- data/lib/spoom/file_collector.rb +102 -0
- data/lib/spoom/file_tree.rb +168 -83
- data/lib/spoom/printer.rb +0 -2
- data/lib/spoom/sorbet/config.rb +3 -1
- data/lib/spoom/sorbet/lsp/base.rb +0 -6
- data/lib/spoom/sorbet/lsp/structures.rb +1 -1
- data/lib/spoom/sorbet/sigils.rb +1 -16
- data/lib/spoom/version.rb +1 -1
- data/lib/spoom.rb +2 -0
- metadata +41 -4
@@ -0,0 +1,61 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
class Index
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { returns(T::Hash[String, T::Array[Definition]]) }
|
10
|
+
attr_reader :definitions
|
11
|
+
|
12
|
+
sig { returns(T::Hash[String, T::Array[Reference]]) }
|
13
|
+
attr_reader :references
|
14
|
+
|
15
|
+
sig { void }
|
16
|
+
def initialize
|
17
|
+
@definitions = T.let({}, T::Hash[String, T::Array[Definition]])
|
18
|
+
@references = T.let({}, T::Hash[String, T::Array[Reference]])
|
19
|
+
end
|
20
|
+
|
21
|
+
# Indexing
|
22
|
+
|
23
|
+
sig { params(definition: Definition).void }
|
24
|
+
def define(definition)
|
25
|
+
(@definitions[definition.name] ||= []) << definition
|
26
|
+
end
|
27
|
+
|
28
|
+
sig { params(reference: Reference).void }
|
29
|
+
def reference(reference)
|
30
|
+
(@references[reference.name] ||= []) << reference
|
31
|
+
end
|
32
|
+
|
33
|
+
# Mark all definitions having a reference of the same name as `alive`
|
34
|
+
#
|
35
|
+
# To be called once all the files have been indexed and all the definitions and references discovered.
|
36
|
+
sig { void }
|
37
|
+
def finalize!
|
38
|
+
@references.keys.each do |name|
|
39
|
+
definitions_for_name(name).each(&:alive!)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Utils
|
44
|
+
|
45
|
+
sig { params(name: String).returns(T::Array[Definition]) }
|
46
|
+
def definitions_for_name(name)
|
47
|
+
@definitions[name] || []
|
48
|
+
end
|
49
|
+
|
50
|
+
sig { returns(T::Array[Definition]) }
|
51
|
+
def all_definitions
|
52
|
+
@definitions.values.flatten
|
53
|
+
end
|
54
|
+
|
55
|
+
sig { returns(T::Array[Reference]) }
|
56
|
+
def all_references
|
57
|
+
@references.values.flatten
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,394 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
class Indexer < SyntaxTree::Visitor
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { returns(String) }
|
10
|
+
attr_reader :path, :file_name
|
11
|
+
|
12
|
+
sig { returns(Index) }
|
13
|
+
attr_reader :index
|
14
|
+
|
15
|
+
sig { params(path: String, source: String, index: Index).void }
|
16
|
+
def initialize(path, source, index)
|
17
|
+
super()
|
18
|
+
|
19
|
+
@path = path
|
20
|
+
@file_name = T.let(File.basename(path), String)
|
21
|
+
@source = source
|
22
|
+
@index = index
|
23
|
+
@previous_node = T.let(nil, T.nilable(SyntaxTree::Node))
|
24
|
+
@names_nesting = T.let([], T::Array[String])
|
25
|
+
@nodes_nesting = T.let([], T::Array[SyntaxTree::Node])
|
26
|
+
@in_const_field = T.let(false, T::Boolean)
|
27
|
+
@in_opassign = T.let(false, T::Boolean)
|
28
|
+
@in_symbol_literal = T.let(false, T::Boolean)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Visit
|
32
|
+
|
33
|
+
sig { override.params(node: T.nilable(SyntaxTree::Node)).void }
|
34
|
+
def visit(node)
|
35
|
+
return unless node
|
36
|
+
|
37
|
+
@nodes_nesting << node
|
38
|
+
super
|
39
|
+
@nodes_nesting.pop
|
40
|
+
@previous_node = node
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { override.params(node: SyntaxTree::AliasNode).void }
|
44
|
+
def visit_alias(node)
|
45
|
+
reference_method(node_string(node.right), node)
|
46
|
+
end
|
47
|
+
|
48
|
+
sig { override.params(node: SyntaxTree::ARef).void }
|
49
|
+
def visit_aref(node)
|
50
|
+
super
|
51
|
+
|
52
|
+
reference_method("[]", node)
|
53
|
+
end
|
54
|
+
|
55
|
+
sig { override.params(node: SyntaxTree::ARefField).void }
|
56
|
+
def visit_aref_field(node)
|
57
|
+
super
|
58
|
+
|
59
|
+
reference_method("[]=", node)
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { override.params(node: SyntaxTree::ArgBlock).void }
|
63
|
+
def visit_arg_block(node)
|
64
|
+
value = node.value
|
65
|
+
|
66
|
+
case value
|
67
|
+
when SyntaxTree::SymbolLiteral
|
68
|
+
# If the block call is something like `x.select(&:foo)`, we need to reference the `foo` method
|
69
|
+
reference_method(symbol_string(value), node)
|
70
|
+
when SyntaxTree::VCall
|
71
|
+
# If the block call is something like `x.select { ... }`, we need to visit the block
|
72
|
+
super
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
sig { override.params(node: SyntaxTree::Binary).void }
|
77
|
+
def visit_binary(node)
|
78
|
+
super
|
79
|
+
|
80
|
+
op = node.operator
|
81
|
+
|
82
|
+
# Reference the operator itself
|
83
|
+
reference_method(op.to_s, node)
|
84
|
+
|
85
|
+
case op
|
86
|
+
when :<, :>, :<=, :>=
|
87
|
+
# For comparison operators, we also reference the `<=>` method
|
88
|
+
reference_method("<=>", node)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
sig { override.params(node: SyntaxTree::CallNode).void }
|
93
|
+
def visit_call(node)
|
94
|
+
visit_send(
|
95
|
+
Send.new(
|
96
|
+
node: node,
|
97
|
+
name: node_string(node.message),
|
98
|
+
recv: node.receiver,
|
99
|
+
args: call_args(node.arguments),
|
100
|
+
),
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
sig { override.params(node: SyntaxTree::ClassDeclaration).void }
|
105
|
+
def visit_class(node)
|
106
|
+
const_name = node_string(node.constant)
|
107
|
+
@names_nesting << const_name
|
108
|
+
define_class(T.must(const_name.split("::").last), @names_nesting.join("::"), node)
|
109
|
+
|
110
|
+
# We do not call `super` here because we don't want to visit the `constant` again
|
111
|
+
visit(node.superclass) if node.superclass
|
112
|
+
visit(node.bodystmt)
|
113
|
+
|
114
|
+
@names_nesting.pop
|
115
|
+
end
|
116
|
+
|
117
|
+
sig { override.params(node: SyntaxTree::Command).void }
|
118
|
+
def visit_command(node)
|
119
|
+
visit_send(
|
120
|
+
Send.new(
|
121
|
+
node: node,
|
122
|
+
name: node_string(node.message),
|
123
|
+
args: call_args(node.arguments),
|
124
|
+
block: node.block,
|
125
|
+
),
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
129
|
+
sig { override.params(node: SyntaxTree::CommandCall).void }
|
130
|
+
def visit_command_call(node)
|
131
|
+
visit_send(
|
132
|
+
Send.new(
|
133
|
+
node: node,
|
134
|
+
name: node_string(node.message),
|
135
|
+
recv: node.receiver,
|
136
|
+
args: call_args(node.arguments),
|
137
|
+
block: node.block,
|
138
|
+
),
|
139
|
+
)
|
140
|
+
end
|
141
|
+
|
142
|
+
sig { override.params(node: SyntaxTree::Const).void }
|
143
|
+
def visit_const(node)
|
144
|
+
reference_constant(node.value, node) unless @in_symbol_literal
|
145
|
+
end
|
146
|
+
|
147
|
+
sig { override.params(node: SyntaxTree::ConstPathField).void }
|
148
|
+
def visit_const_path_field(node)
|
149
|
+
# We do not call `super` here because we don't want to visit the `constant` again
|
150
|
+
visit(node.parent)
|
151
|
+
|
152
|
+
name = node.constant.value
|
153
|
+
full_name = [*@names_nesting, node_string(node.parent), name].join("::")
|
154
|
+
define_constant(name, full_name, node)
|
155
|
+
end
|
156
|
+
|
157
|
+
sig { override.params(node: SyntaxTree::DefNode).void }
|
158
|
+
def visit_def(node)
|
159
|
+
super
|
160
|
+
|
161
|
+
name = node_string(node.name)
|
162
|
+
define_method(name, [*@names_nesting, name].join("::"), node)
|
163
|
+
end
|
164
|
+
|
165
|
+
sig { override.params(node: SyntaxTree::Field).void }
|
166
|
+
def visit_field(node)
|
167
|
+
visit(node.parent)
|
168
|
+
|
169
|
+
name = node.name
|
170
|
+
case name
|
171
|
+
when SyntaxTree::Const
|
172
|
+
name = name.value
|
173
|
+
full_name = [*@names_nesting, node_string(node.parent), name].join("::")
|
174
|
+
define_constant(name, full_name, node)
|
175
|
+
when SyntaxTree::Ident
|
176
|
+
reference_method(name.value, node) if @in_opassign
|
177
|
+
reference_method("#{name.value}=", node)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
sig { override.params(node: SyntaxTree::ModuleDeclaration).void }
|
182
|
+
def visit_module(node)
|
183
|
+
const_name = node_string(node.constant)
|
184
|
+
@names_nesting << const_name
|
185
|
+
define_module(T.must(const_name.split("::").last), @names_nesting.join("::"), node)
|
186
|
+
|
187
|
+
# We do not call `super` here because we don't want to visit the `constant` again
|
188
|
+
visit(node.bodystmt)
|
189
|
+
|
190
|
+
@names_nesting.pop
|
191
|
+
end
|
192
|
+
|
193
|
+
sig { override.params(node: SyntaxTree::OpAssign).void }
|
194
|
+
def visit_opassign(node)
|
195
|
+
# Both `FOO = x` and `FOO += x` yield a VarField node, but the former is a constant definition and the latter is
|
196
|
+
# a constant reference. We need to distinguish between the two cases.
|
197
|
+
@in_opassign = true
|
198
|
+
super
|
199
|
+
@in_opassign = false
|
200
|
+
end
|
201
|
+
|
202
|
+
sig { params(send: Send).void }
|
203
|
+
def visit_send(send)
|
204
|
+
visit(send.recv)
|
205
|
+
|
206
|
+
case send.name
|
207
|
+
when "attr_reader"
|
208
|
+
send.args.each do |arg|
|
209
|
+
next unless arg.is_a?(SyntaxTree::SymbolLiteral)
|
210
|
+
|
211
|
+
name = symbol_string(arg)
|
212
|
+
define_attr_reader(name, [*@names_nesting, name].join("::"), arg)
|
213
|
+
end
|
214
|
+
when "attr_writer"
|
215
|
+
send.args.each do |arg|
|
216
|
+
next unless arg.is_a?(SyntaxTree::SymbolLiteral)
|
217
|
+
|
218
|
+
name = symbol_string(arg)
|
219
|
+
define_attr_writer("#{name}=", "#{[*@names_nesting, name].join("::")}=", arg)
|
220
|
+
end
|
221
|
+
when "attr_accessor"
|
222
|
+
send.args.each do |arg|
|
223
|
+
next unless arg.is_a?(SyntaxTree::SymbolLiteral)
|
224
|
+
|
225
|
+
name = symbol_string(arg)
|
226
|
+
full_name = [*@names_nesting, name].join("::")
|
227
|
+
define_attr_reader(name, full_name, arg)
|
228
|
+
define_attr_writer("#{name}=", "#{full_name}=", arg)
|
229
|
+
end
|
230
|
+
else
|
231
|
+
reference_method(send.name, send.node)
|
232
|
+
visit_all(send.args)
|
233
|
+
visit(send.block)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
sig { override.params(node: SyntaxTree::SymbolLiteral).void }
|
238
|
+
def visit_symbol_literal(node)
|
239
|
+
# Something like `:FOO` will yield a Const node but we do not want to treat it as a constant reference.
|
240
|
+
# So we need to distinguish between the two cases.
|
241
|
+
@in_symbol_literal = true
|
242
|
+
super
|
243
|
+
@in_symbol_literal = false
|
244
|
+
end
|
245
|
+
|
246
|
+
sig { override.params(node: SyntaxTree::TopConstField).void }
|
247
|
+
def visit_top_const_field(node)
|
248
|
+
define_constant(node.constant.value, node.constant.value, node)
|
249
|
+
end
|
250
|
+
|
251
|
+
sig { override.params(node: SyntaxTree::VarField).void }
|
252
|
+
def visit_var_field(node)
|
253
|
+
value = node.value
|
254
|
+
case value
|
255
|
+
when SyntaxTree::Const
|
256
|
+
if @in_opassign
|
257
|
+
reference_constant(value.value, node)
|
258
|
+
else
|
259
|
+
name = value.value
|
260
|
+
define_constant(name, [*@names_nesting, name].join("::"), node)
|
261
|
+
end
|
262
|
+
when SyntaxTree::Ident
|
263
|
+
reference_method(value.value, node) if @in_opassign
|
264
|
+
reference_method("#{value.value}=", node)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
sig { override.params(node: SyntaxTree::VCall).void }
|
269
|
+
def visit_vcall(node)
|
270
|
+
visit_send(Send.new(node: node, name: node_string(node.value)))
|
271
|
+
end
|
272
|
+
|
273
|
+
private
|
274
|
+
|
275
|
+
# Definition indexing
|
276
|
+
|
277
|
+
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
278
|
+
def define_attr_reader(name, full_name, node)
|
279
|
+
definition = Definition.new(
|
280
|
+
kind: Definition::Kind::AttrReader,
|
281
|
+
name: name,
|
282
|
+
full_name: full_name,
|
283
|
+
location: node_location(node),
|
284
|
+
)
|
285
|
+
@index.define(definition)
|
286
|
+
end
|
287
|
+
|
288
|
+
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
289
|
+
def define_attr_writer(name, full_name, node)
|
290
|
+
definition = Definition.new(
|
291
|
+
kind: Definition::Kind::AttrWriter,
|
292
|
+
name: name,
|
293
|
+
full_name: full_name,
|
294
|
+
location: node_location(node),
|
295
|
+
)
|
296
|
+
@index.define(definition)
|
297
|
+
end
|
298
|
+
|
299
|
+
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
300
|
+
def define_class(name, full_name, node)
|
301
|
+
definition = Definition.new(
|
302
|
+
kind: Definition::Kind::Class,
|
303
|
+
name: name,
|
304
|
+
full_name: full_name,
|
305
|
+
location: node_location(node),
|
306
|
+
)
|
307
|
+
@index.define(definition)
|
308
|
+
end
|
309
|
+
|
310
|
+
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
311
|
+
def define_constant(name, full_name, node)
|
312
|
+
definition = Definition.new(
|
313
|
+
kind: Definition::Kind::Constant,
|
314
|
+
name: name,
|
315
|
+
full_name: full_name,
|
316
|
+
location: node_location(node),
|
317
|
+
)
|
318
|
+
@index.define(definition)
|
319
|
+
end
|
320
|
+
|
321
|
+
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
322
|
+
def define_method(name, full_name, node)
|
323
|
+
definition = Definition.new(
|
324
|
+
kind: Definition::Kind::Method,
|
325
|
+
name: name,
|
326
|
+
full_name: full_name,
|
327
|
+
location: node_location(node),
|
328
|
+
)
|
329
|
+
@index.define(definition)
|
330
|
+
end
|
331
|
+
|
332
|
+
sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
|
333
|
+
def define_module(name, full_name, node)
|
334
|
+
definition = Definition.new(
|
335
|
+
kind: Definition::Kind::Module,
|
336
|
+
name: name,
|
337
|
+
full_name: full_name,
|
338
|
+
location: node_location(node),
|
339
|
+
)
|
340
|
+
@index.define(definition)
|
341
|
+
end
|
342
|
+
|
343
|
+
# Reference indexing
|
344
|
+
|
345
|
+
sig { params(name: String, node: SyntaxTree::Node).void }
|
346
|
+
def reference_constant(name, node)
|
347
|
+
@index.reference(Reference.new(name: name, kind: Reference::Kind::Constant, location: node_location(node)))
|
348
|
+
end
|
349
|
+
|
350
|
+
sig { params(name: String, node: SyntaxTree::Node).void }
|
351
|
+
def reference_method(name, node)
|
352
|
+
@index.reference(Reference.new(name: name, kind: Reference::Kind::Method, location: node_location(node)))
|
353
|
+
end
|
354
|
+
|
355
|
+
# Node utils
|
356
|
+
|
357
|
+
sig { params(node: T.any(Symbol, SyntaxTree::Node)).returns(String) }
|
358
|
+
def node_string(node)
|
359
|
+
case node
|
360
|
+
when Symbol
|
361
|
+
node.to_s
|
362
|
+
else
|
363
|
+
T.must(@source[node.location.start_char...node.location.end_char])
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
sig { params(node: SyntaxTree::Node).returns(Location) }
|
368
|
+
def node_location(node)
|
369
|
+
Location.from_syntax_tree(@path, node.location)
|
370
|
+
end
|
371
|
+
|
372
|
+
sig { params(node: SyntaxTree::Node).returns(String) }
|
373
|
+
def symbol_string(node)
|
374
|
+
node_string(node).delete_prefix(":")
|
375
|
+
end
|
376
|
+
|
377
|
+
sig do
|
378
|
+
params(
|
379
|
+
node: T.any(SyntaxTree::Args, SyntaxTree::ArgParen, SyntaxTree::ArgsForward, NilClass),
|
380
|
+
).returns(T::Array[SyntaxTree::Node])
|
381
|
+
end
|
382
|
+
def call_args(node)
|
383
|
+
case node
|
384
|
+
when SyntaxTree::ArgParen
|
385
|
+
call_args(node.arguments)
|
386
|
+
when SyntaxTree::Args
|
387
|
+
node.parts
|
388
|
+
else
|
389
|
+
[]
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
class Location
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
include Comparable
|
10
|
+
|
11
|
+
class LocationError < Spoom::Error; end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
extend T::Sig
|
15
|
+
|
16
|
+
sig { params(file: String, location: SyntaxTree::Location).returns(Location) }
|
17
|
+
def from_syntax_tree(file, location)
|
18
|
+
new(file, location.start_line, location.start_column, location.end_line, location.end_column)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { returns(String) }
|
23
|
+
attr_reader :file
|
24
|
+
|
25
|
+
sig { returns(Integer) }
|
26
|
+
attr_reader :start_line, :start_column, :end_line, :end_column
|
27
|
+
|
28
|
+
sig do
|
29
|
+
params(
|
30
|
+
file: String,
|
31
|
+
start_line: Integer,
|
32
|
+
start_column: Integer,
|
33
|
+
end_line: Integer,
|
34
|
+
end_column: Integer,
|
35
|
+
).void
|
36
|
+
end
|
37
|
+
def initialize(file, start_line, start_column, end_line, end_column)
|
38
|
+
@file = file
|
39
|
+
@start_line = start_line
|
40
|
+
@start_column = start_column
|
41
|
+
@end_line = end_line
|
42
|
+
@end_column = end_column
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { override.params(other: BasicObject).returns(T.nilable(Integer)) }
|
46
|
+
def <=>(other)
|
47
|
+
return nil unless Location === other
|
48
|
+
|
49
|
+
to_s <=> other.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { returns(String) }
|
53
|
+
def to_s
|
54
|
+
"#{@file}:#{@start_line}:#{@start_column}-#{@end_line}:#{@end_column}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
# A reference is a call to a method or a constant
|
7
|
+
class Reference < T::Struct
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
class Kind < T::Enum
|
11
|
+
enums do
|
12
|
+
Constant = new
|
13
|
+
Method = new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
const :kind, Kind
|
18
|
+
const :name, String
|
19
|
+
const :location, Location
|
20
|
+
|
21
|
+
# Kind
|
22
|
+
|
23
|
+
sig { returns(T::Boolean) }
|
24
|
+
def constant?
|
25
|
+
kind == Kind::Constant
|
26
|
+
end
|
27
|
+
|
28
|
+
sig { returns(T::Boolean) }
|
29
|
+
def method?
|
30
|
+
kind == Kind::Method
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
# An abstraction to simplify handling of SyntaxTree::CallNode, SyntaxTree::Command, SyntaxTree::CommandCall and
|
7
|
+
# SyntaxTree::VCall nodes.
|
8
|
+
class Send < T::Struct
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
const :node, SyntaxTree::Node
|
12
|
+
const :name, String
|
13
|
+
const :recv, T.nilable(SyntaxTree::Node), default: nil
|
14
|
+
const :args, T::Array[SyntaxTree::Node], default: []
|
15
|
+
const :block, T.nilable(SyntaxTree::Node), default: nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "erubi"
|
5
|
+
require "syntax_tree"
|
6
|
+
|
7
|
+
require_relative "deadcode/erb"
|
8
|
+
require_relative "deadcode/index"
|
9
|
+
require_relative "deadcode/indexer"
|
10
|
+
|
11
|
+
require_relative "deadcode/location"
|
12
|
+
require_relative "deadcode/definition"
|
13
|
+
require_relative "deadcode/reference"
|
14
|
+
require_relative "deadcode/send"
|
15
|
+
|
16
|
+
module Spoom
|
17
|
+
module Deadcode
|
18
|
+
class Error < Spoom::Error
|
19
|
+
extend T::Sig
|
20
|
+
extend T::Helpers
|
21
|
+
|
22
|
+
abstract!
|
23
|
+
|
24
|
+
sig { params(message: String, parent: Exception).void }
|
25
|
+
def initialize(message, parent:)
|
26
|
+
super(message)
|
27
|
+
set_backtrace(parent.backtrace)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class ParserError < Error; end
|
32
|
+
class IndexerError < Error; end
|
33
|
+
|
34
|
+
class << self
|
35
|
+
extend T::Sig
|
36
|
+
|
37
|
+
sig { params(index: Index, ruby: String, file: String).void }
|
38
|
+
def index_ruby(index, ruby, file:)
|
39
|
+
node = SyntaxTree.parse(ruby)
|
40
|
+
visitor = Spoom::Deadcode::Indexer.new(file, ruby, index)
|
41
|
+
visitor.visit(node)
|
42
|
+
rescue SyntaxTree::Parser::ParseError => e
|
43
|
+
raise ParserError.new("Error while parsing #{file} (#{e.message} at #{e.lineno}:#{e.column})", parent: e)
|
44
|
+
rescue => e
|
45
|
+
raise IndexerError.new("Error while indexing #{file} (#{e.message})", parent: e)
|
46
|
+
end
|
47
|
+
|
48
|
+
sig { params(index: Index, erb: String, file: String).void }
|
49
|
+
def index_erb(index, erb, file:)
|
50
|
+
ruby = ERB.new(erb).src
|
51
|
+
index_ruby(index, ruby, file: file)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|