spoom 1.2.4 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +54 -55
- data/lib/spoom/cli/deadcode.rb +172 -0
- data/lib/spoom/cli/helper.rb +20 -0
- data/lib/spoom/cli/srb/bump.rb +200 -0
- data/lib/spoom/cli/srb/coverage.rb +224 -0
- data/lib/spoom/cli/srb/lsp.rb +159 -0
- data/lib/spoom/cli/srb/tc.rb +150 -0
- data/lib/spoom/cli/srb.rb +27 -0
- data/lib/spoom/cli.rb +72 -32
- data/lib/spoom/context/git.rb +2 -2
- data/lib/spoom/context/sorbet.rb +2 -2
- data/lib/spoom/deadcode/definition.rb +11 -0
- data/lib/spoom/deadcode/indexer.rb +222 -224
- data/lib/spoom/deadcode/location.rb +2 -2
- data/lib/spoom/deadcode/plugins/action_mailer.rb +2 -2
- data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +19 -0
- data/lib/spoom/deadcode/plugins/actionpack.rb +4 -6
- data/lib/spoom/deadcode/plugins/active_model.rb +8 -8
- data/lib/spoom/deadcode/plugins/active_record.rb +9 -12
- data/lib/spoom/deadcode/plugins/active_support.rb +11 -0
- data/lib/spoom/deadcode/plugins/base.rb +1 -1
- data/lib/spoom/deadcode/plugins/graphql.rb +4 -4
- data/lib/spoom/deadcode/plugins/namespaces.rb +2 -4
- data/lib/spoom/deadcode/plugins/ruby.rb +8 -17
- data/lib/spoom/deadcode/plugins/sorbet.rb +4 -10
- data/lib/spoom/deadcode/plugins.rb +1 -0
- data/lib/spoom/deadcode/remover.rb +209 -174
- data/lib/spoom/deadcode/send.rb +9 -10
- data/lib/spoom/deadcode/visitor.rb +755 -0
- data/lib/spoom/deadcode.rb +40 -10
- data/lib/spoom/file_tree.rb +0 -16
- data/lib/spoom/sorbet/errors.rb +1 -1
- data/lib/spoom/sorbet/lsp/structures.rb +2 -2
- data/lib/spoom/version.rb +1 -1
- metadata +19 -15
- data/lib/spoom/cli/bump.rb +0 -198
- data/lib/spoom/cli/coverage.rb +0 -222
- data/lib/spoom/cli/lsp.rb +0 -168
- data/lib/spoom/cli/run.rb +0 -148
@@ -13,7 +13,7 @@ module Spoom
|
|
13
13
|
@context = context
|
14
14
|
end
|
15
15
|
|
16
|
-
sig { params(kind: Definition::Kind, location: Location).
|
16
|
+
sig { params(kind: T.nilable(Definition::Kind), location: Location).returns(String) }
|
17
17
|
def remove_location(kind, location)
|
18
18
|
file = location.file
|
19
19
|
|
@@ -23,7 +23,7 @@ module Spoom
|
|
23
23
|
|
24
24
|
node_remover = NodeRemover.new(@context.read(file), kind, location)
|
25
25
|
node_remover.apply_edit
|
26
|
-
|
26
|
+
node_remover.new_source
|
27
27
|
end
|
28
28
|
|
29
29
|
class NodeRemover
|
@@ -32,7 +32,7 @@ module Spoom
|
|
32
32
|
sig { returns(String) }
|
33
33
|
attr_reader :new_source
|
34
34
|
|
35
|
-
sig { params(source: String, kind: Definition::Kind, location: Location).void }
|
35
|
+
sig { params(source: String, kind: T.nilable(Definition::Kind), location: Location).void }
|
36
36
|
def initialize(source, kind, location)
|
37
37
|
@old_source = source
|
38
38
|
@new_source = T.let(source.dup, String)
|
@@ -52,11 +52,15 @@ module Spoom
|
|
52
52
|
|
53
53
|
node = @node_context.node
|
54
54
|
case node
|
55
|
-
when
|
55
|
+
when Prism::ClassNode, Prism::ModuleNode, Prism::DefNode
|
56
56
|
delete_node_and_comments_and_sigs(@node_context)
|
57
|
-
when
|
57
|
+
when Prism::ConstantWriteNode, Prism::ConstantOperatorWriteNode,
|
58
|
+
Prism::ConstantAndWriteNode, Prism::ConstantOrWriteNode,
|
59
|
+
Prism::ConstantPathWriteNode, Prism::ConstantPathOperatorWriteNode,
|
60
|
+
Prism::ConstantPathAndWriteNode, Prism::ConstantPathOrWriteNode,
|
61
|
+
Prism::ConstantTargetNode
|
58
62
|
delete_constant_assignment(@node_context)
|
59
|
-
when
|
63
|
+
when Prism::SymbolNode # for attr accessors
|
60
64
|
delete_attr_accessor(@node_context)
|
61
65
|
else
|
62
66
|
raise Error, "Unsupported node type: #{node.class}"
|
@@ -67,19 +71,26 @@ module Spoom
|
|
67
71
|
|
68
72
|
sig { params(context: NodeContext).void }
|
69
73
|
def delete_constant_assignment(context)
|
70
|
-
|
71
|
-
|
72
|
-
|
74
|
+
case context.node
|
75
|
+
when Prism::ConstantWriteNode, Prism::ConstantOperatorWriteNode,
|
76
|
+
Prism::ConstantAndWriteNode, Prism::ConstantOrWriteNode,
|
77
|
+
Prism::ConstantPathWriteNode, Prism::ConstantPathOperatorWriteNode,
|
78
|
+
Prism::ConstantPathAndWriteNode, Prism::ConstantPathOrWriteNode
|
79
|
+
# Nesting node is an assign, it means only one constant is assigned on the line
|
80
|
+
# so we can remove the whole assign
|
81
|
+
delete_node_and_comments_and_sigs(context)
|
82
|
+
return
|
73
83
|
end
|
74
84
|
|
85
|
+
# We're assigning multiple constants, we need to remove only the useless node
|
75
86
|
parent_context = context.parent_context
|
76
87
|
parent_node = parent_context.node
|
77
|
-
if parent_node.is_a?(
|
88
|
+
if parent_node.is_a?(Prism::ConstantWriteNode)
|
78
89
|
# Nesting node is an assign, it means only one constant is assigned on the line
|
79
90
|
# so we can remove the whole assign
|
80
91
|
delete_node_and_comments_and_sigs(parent_context)
|
81
92
|
return
|
82
|
-
elsif parent_node.is_a?(
|
93
|
+
elsif parent_node.is_a?(Prism::MultiWriteNode) && parent_node.lefts.size == 1
|
83
94
|
# Nesting node is a single left hand side, it means only one constant is assigned
|
84
95
|
# so we can remove the whole line
|
85
96
|
delete_node_and_comments_and_sigs(parent_context.parent_context)
|
@@ -102,36 +113,36 @@ module Spoom
|
|
102
113
|
# BAZ = 42
|
103
114
|
# ~~~
|
104
115
|
delete_lines(node.location.start_line, node.location.end_line)
|
105
|
-
elsif prev_node && next_node
|
116
|
+
elsif prev_node && next_node.is_a?(Prism::ConstantTargetNode)
|
106
117
|
# We have a node before and after one the same line, just remove the part of the line
|
107
118
|
#
|
108
119
|
# ~~~
|
109
120
|
# FOO, BAR, BAZ = 42 # we need to remove BAR
|
110
121
|
# ~~~
|
111
|
-
replace_chars(prev_node.location.
|
122
|
+
replace_chars(prev_node.location.end_offset, next_node.location.start_offset, ", ")
|
112
123
|
elsif prev_node
|
113
124
|
# We have a node before, on the same line, but no node after, just remove the part of the line
|
114
125
|
#
|
115
126
|
# ~~~
|
116
127
|
# FOO, BAR = 42 # we need to remove BAR
|
117
128
|
# ~~~
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
replace_chars(prev_node.location.
|
129
|
+
nesting_assign = T.cast(parent_context.node, Prism::MultiWriteNode)
|
130
|
+
|
131
|
+
rparen_loc = nesting_assign.rparen_loc
|
132
|
+
if rparen_loc
|
133
|
+
# We have an assign with parenthesis, we need to remove the part of the line until the closing parenthesis
|
134
|
+
delete_chars(prev_node.location.end_offset, rparen_loc.start_offset)
|
135
|
+
else
|
136
|
+
# We don't have a parenthesis, we need to remove the part of the line until the operator
|
137
|
+
replace_chars(prev_node.location.end_offset, nesting_assign.operator_loc.start_offset, " ")
|
127
138
|
end
|
128
|
-
elsif next_node
|
139
|
+
elsif next_node.is_a?(Prism::ConstantTargetNode)
|
129
140
|
# We don't have a node before but a node after on the same line, just remove the part of the line
|
130
141
|
#
|
131
142
|
# ~~~
|
132
143
|
# FOO, BAR = 42 # we need to remove FOO
|
133
144
|
# ~~~
|
134
|
-
delete_chars(node.location.
|
145
|
+
delete_chars(node.location.start_offset, next_node.location.start_offset)
|
135
146
|
else
|
136
147
|
# Should have been removed as a single MLHS node
|
137
148
|
raise "Unexpected case while removing constant assignment"
|
@@ -142,10 +153,10 @@ module Spoom
|
|
142
153
|
def delete_attr_accessor(context)
|
143
154
|
args_context = context.parent_context
|
144
155
|
send_context = args_context.parent_context
|
145
|
-
send_context = send_context.parent_context if send_context.node.is_a?(
|
156
|
+
send_context = send_context.parent_context if send_context.node.is_a?(Prism::ArgumentsNode)
|
146
157
|
|
147
|
-
send_node = T.cast(send_context.node,
|
148
|
-
need_accessor =
|
158
|
+
send_node = T.cast(send_context.node, Prism::CallNode)
|
159
|
+
need_accessor = send_node.name == :attr_accessor
|
149
160
|
|
150
161
|
if args_context.node.child_nodes.size == 1
|
151
162
|
# Only one accessor is defined, we can remove the whole node
|
@@ -175,21 +186,21 @@ module Spoom
|
|
175
186
|
# ~~~
|
176
187
|
# attr_reader :foo, :bar, :baz # we need to remove bar
|
177
188
|
# ~~~
|
178
|
-
replace_chars(prev_node.location.
|
189
|
+
replace_chars(prev_node.location.end_offset, next_node.location.start_offset, ", ")
|
179
190
|
elsif prev_node
|
180
191
|
# We have a node before, on the same line, but no node after, just remove the part of the line
|
181
192
|
#
|
182
193
|
# ~~~
|
183
194
|
# attr_reader :foo, :bar, :baz # we need to remove baz
|
184
195
|
# ~~~
|
185
|
-
delete_chars(prev_node.location.
|
196
|
+
delete_chars(prev_node.location.end_offset, context.node.location.end_offset)
|
186
197
|
elsif next_node
|
187
198
|
# We don't have a node before but a node after on the same line, just remove the part of the line
|
188
199
|
#
|
189
200
|
# ~~~
|
190
201
|
# attr_reader :foo, :bar, :baz # we need to remove foo
|
191
202
|
# ~~~
|
192
|
-
delete_chars(context.node.location.
|
203
|
+
delete_chars(context.node.location.start_offset, next_node.location.start_offset)
|
193
204
|
else
|
194
205
|
raise "Unexpected case while removing attr_accessor"
|
195
206
|
end
|
@@ -199,13 +210,13 @@ module Spoom
|
|
199
210
|
|
200
211
|
sig do
|
201
212
|
params(
|
202
|
-
node:
|
213
|
+
node: Prism::Node,
|
203
214
|
send_context: NodeContext,
|
204
215
|
was_removed: T::Boolean,
|
205
216
|
).void
|
206
217
|
end
|
207
218
|
def insert_accessor(node, send_context, was_removed:)
|
208
|
-
name =
|
219
|
+
name = node.slice
|
209
220
|
code = case @kind
|
210
221
|
when Definition::Kind::AttrReader
|
211
222
|
"attr_writer #{name}"
|
@@ -221,10 +232,10 @@ module Spoom
|
|
221
232
|
node_after = send_context.next_node
|
222
233
|
|
223
234
|
if was_removed
|
224
|
-
first_node = send_context.
|
235
|
+
first_node = send_context.attached_sigs.first || send_context.node
|
225
236
|
at_line = first_node.location.start_line - 1
|
226
237
|
|
227
|
-
prev_context = NodeContext.new(@old_source, first_node, send_context.nesting)
|
238
|
+
prev_context = NodeContext.new(@old_source, @node_context.comments, first_node, send_context.nesting)
|
228
239
|
node_before = prev_context.previous_node
|
229
240
|
|
230
241
|
new_line_before = node_before && send_context.node.location.start_line - node_before.location.end_line < 2
|
@@ -251,21 +262,51 @@ module Spoom
|
|
251
262
|
start_line = context.node.location.start_line
|
252
263
|
end_line = context.node.location.end_line
|
253
264
|
|
254
|
-
#
|
255
|
-
|
256
|
-
|
257
|
-
|
265
|
+
# TODO: remove once Prism location are fixed
|
266
|
+
node = context.node
|
267
|
+
case node
|
268
|
+
when Prism::ConstantWriteNode, Prism::ConstantOperatorWriteNode,
|
269
|
+
Prism::ConstantAndWriteNode, Prism::ConstantOrWriteNode,
|
270
|
+
Prism::ConstantPathWriteNode, Prism::ConstantPathOperatorWriteNode,
|
271
|
+
Prism::ConstantPathAndWriteNode, Prism::ConstantPathOrWriteNode
|
272
|
+
value = node.value
|
273
|
+
if value.is_a?(Prism::StringNode)
|
274
|
+
end_line = value.closing_loc&.start_line || value.location.end_line
|
275
|
+
end
|
258
276
|
end
|
259
277
|
|
278
|
+
# Adjust the lines to remove to include sigs attached to the node
|
279
|
+
first_node = context.attached_sigs.first || context.node
|
280
|
+
start_line = first_node.location.start_line if first_node
|
281
|
+
|
282
|
+
# Adjust the lines to remove to include comments attached to the node
|
283
|
+
first_comment = context.attached_comments(first_node).first
|
284
|
+
start_line = first_comment.location.start_line if first_comment
|
285
|
+
|
260
286
|
# Adjust the lines to remove to include previous blank lines
|
261
|
-
prev_context = NodeContext.new(@old_source,
|
262
|
-
before = prev_context.previous_node
|
287
|
+
prev_context = NodeContext.new(@old_source, @node_context.comments, first_node, context.nesting)
|
288
|
+
before = T.let(prev_context.previous_node, T.nilable(T.any(Prism::Node, Prism::Comment)))
|
289
|
+
|
290
|
+
# There may be an unrelated comment between the current node and the one before
|
291
|
+
# if there is, we only want to delete lines up to the last comment found
|
292
|
+
if before
|
293
|
+
to_node = first_comment || node
|
294
|
+
comment = @node_context.comments_between_lines(before.location.end_line, to_node.location.start_line).last
|
295
|
+
before = comment if comment
|
296
|
+
end
|
297
|
+
|
263
298
|
if before && before.location.end_line < start_line - 1
|
264
299
|
# There is a node before and a blank line
|
265
300
|
start_line = before.location.end_line + 1
|
266
|
-
elsif before.nil?
|
267
|
-
# There is no node before,
|
268
|
-
|
301
|
+
elsif before.nil?
|
302
|
+
# There is no node before, check if there is a blank line
|
303
|
+
parent_context = context.parent_context
|
304
|
+
# With Prism the StatementsNode location starts at the first line of the first node
|
305
|
+
parent_context = parent_context.parent_context if parent_context.node.is_a?(Prism::StatementsNode)
|
306
|
+
if parent_context.node.location.start_line < start_line - 1
|
307
|
+
# There is a blank line before the node
|
308
|
+
start_line = parent_context.node.location.start_line + 1
|
309
|
+
end
|
269
310
|
end
|
270
311
|
|
271
312
|
# Adjust the lines to remove to include following blank lines
|
@@ -296,41 +337,24 @@ module Spoom
|
|
296
337
|
@new_source[start_char...end_char] = replacement
|
297
338
|
end
|
298
339
|
|
299
|
-
sig { params(
|
300
|
-
def delete_line_part(line_number, start_column, end_column)
|
301
|
-
lines = []
|
302
|
-
@new_source.lines.each_with_index do |line, index|
|
303
|
-
current_line = index + 1
|
304
|
-
|
305
|
-
lines << if line_number == current_line
|
306
|
-
T.must(line[0...start_column]) + T.must(line[end_column..-1])
|
307
|
-
else
|
308
|
-
line
|
309
|
-
end
|
310
|
-
end
|
311
|
-
@new_source = lines.join
|
312
|
-
end
|
313
|
-
|
314
|
-
sig { params(node: SyntaxTree::MethodAddBlock, name: String, kind: Definition::Kind).returns(String) }
|
340
|
+
sig { params(node: Prism::CallNode, name: String, kind: T.nilable(Definition::Kind)).returns(String) }
|
315
341
|
def transform_sig(node, name:, kind:)
|
316
342
|
type = T.let(nil, T.nilable(String))
|
317
343
|
|
318
|
-
|
319
|
-
statements =
|
344
|
+
block = T.cast(node.block, Prism::BlockNode)
|
345
|
+
statements = T.cast(block.body, Prism::StatementsNode)
|
320
346
|
|
321
347
|
statements.body.each do |call|
|
322
|
-
next unless call.is_a?(
|
323
|
-
next unless
|
348
|
+
next unless call.is_a?(Prism::CallNode)
|
349
|
+
next unless call.name == :returns
|
324
350
|
|
325
351
|
args = call.arguments
|
326
|
-
|
327
|
-
|
328
|
-
next unless args.is_a?(SyntaxTree::Args)
|
352
|
+
next unless args
|
329
353
|
|
330
|
-
first = args.
|
354
|
+
first = args.arguments.first
|
331
355
|
next unless first
|
332
356
|
|
333
|
-
type =
|
357
|
+
type = first.slice
|
334
358
|
end
|
335
359
|
|
336
360
|
name = name.delete_prefix(":")
|
@@ -348,20 +372,31 @@ module Spoom
|
|
348
372
|
class NodeContext
|
349
373
|
extend T::Sig
|
350
374
|
|
351
|
-
sig { returns(
|
375
|
+
sig { returns(T::Hash[Integer, Prism::Comment]) }
|
376
|
+
attr_reader :comments
|
377
|
+
|
378
|
+
sig { returns(Prism::Node) }
|
352
379
|
attr_reader :node
|
353
380
|
|
354
|
-
sig { returns(T::Array[
|
381
|
+
sig { returns(T::Array[Prism::Node]) }
|
355
382
|
attr_accessor :nesting
|
356
383
|
|
357
|
-
sig
|
358
|
-
|
384
|
+
sig do
|
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
|
392
|
+
def initialize(source, comments, node, nesting)
|
359
393
|
@source = source
|
394
|
+
@comments = comments
|
360
395
|
@node = node
|
361
396
|
@nesting = nesting
|
362
397
|
end
|
363
398
|
|
364
|
-
sig { returns(
|
399
|
+
sig { returns(Prism::Node) }
|
365
400
|
def parent_node
|
366
401
|
parent = @nesting.last
|
367
402
|
raise "No parent for node #{node}" unless parent
|
@@ -375,100 +410,119 @@ module Spoom
|
|
375
410
|
parent = nesting.pop
|
376
411
|
raise "No parent context for node #{@node}" unless parent
|
377
412
|
|
378
|
-
NodeContext.new(@source, parent, nesting)
|
413
|
+
NodeContext.new(@source, @comments, parent, nesting)
|
379
414
|
end
|
380
415
|
|
381
|
-
sig { returns(T::Array[
|
416
|
+
sig { returns(T::Array[Prism::Node]) }
|
382
417
|
def previous_nodes
|
383
418
|
parent = parent_node
|
419
|
+
child_nodes = parent.child_nodes.compact
|
384
420
|
|
385
|
-
index =
|
421
|
+
index = child_nodes.index(@node)
|
386
422
|
raise "Node #{@node} not found in parent #{parent}" unless index
|
387
423
|
|
388
|
-
|
424
|
+
T.must(child_nodes[0...index])
|
389
425
|
end
|
390
426
|
|
391
|
-
sig { returns(T.nilable(
|
427
|
+
sig { returns(T.nilable(Prism::Node)) }
|
392
428
|
def previous_node
|
393
429
|
previous_nodes.last
|
394
430
|
end
|
395
431
|
|
396
|
-
sig { returns(T::Array[
|
432
|
+
sig { returns(T::Array[Prism::Node]) }
|
397
433
|
def next_nodes
|
398
434
|
parent = parent_node
|
435
|
+
child_nodes = parent.child_nodes.compact
|
399
436
|
|
400
|
-
index =
|
437
|
+
index = child_nodes.index(node)
|
401
438
|
raise "Node #{@node} not found in nesting node #{parent}" unless index
|
402
439
|
|
403
|
-
|
440
|
+
T.must(child_nodes.compact[(index + 1)..-1])
|
404
441
|
end
|
405
442
|
|
406
|
-
sig { returns(T.nilable(
|
443
|
+
sig { returns(T.nilable(Prism::Node)) }
|
407
444
|
def next_node
|
408
445
|
next_nodes.first
|
409
446
|
end
|
410
447
|
|
411
448
|
sig { returns(T.nilable(NodeContext)) }
|
412
449
|
def sclass_context
|
413
|
-
sclass = T.let(nil, T.nilable(
|
450
|
+
sclass = T.let(nil, T.nilable(Prism::SingletonClassNode))
|
414
451
|
|
415
452
|
nesting = @nesting.dup
|
416
453
|
until nesting.empty? || sclass
|
417
454
|
node = nesting.pop
|
418
|
-
next unless node.is_a?(
|
455
|
+
next unless node.is_a?(Prism::SingletonClassNode)
|
419
456
|
|
420
457
|
sclass = node
|
421
458
|
end
|
422
459
|
|
423
|
-
return unless sclass.is_a?(
|
460
|
+
return unless sclass.is_a?(Prism::SingletonClassNode)
|
424
461
|
|
425
|
-
|
426
|
-
|
427
|
-
|
462
|
+
body = sclass.body
|
463
|
+
return NodeContext.new(@source, @comments, sclass, nesting) unless body.is_a?(Prism::StatementsNode)
|
464
|
+
|
465
|
+
nodes = body.child_nodes.reject do |node|
|
466
|
+
sorbet_signature?(node) || sorbet_extend_sig?(node)
|
428
467
|
end
|
429
468
|
|
430
469
|
if nodes.size <= 1
|
431
|
-
return NodeContext.new(@source, sclass, nesting)
|
470
|
+
return NodeContext.new(@source, @comments, sclass, nesting)
|
432
471
|
end
|
433
472
|
|
434
473
|
nil
|
435
474
|
end
|
436
475
|
|
437
|
-
sig { params(node: T.nilable(
|
476
|
+
sig { params(node: T.nilable(Prism::Node)).returns(T::Boolean) }
|
438
477
|
def sorbet_signature?(node)
|
439
|
-
|
478
|
+
node.is_a?(Prism::CallNode) && node.name == :sig
|
479
|
+
end
|
440
480
|
|
441
|
-
|
442
|
-
|
481
|
+
sig { params(node: T.nilable(Prism::Node)).returns(T::Boolean) }
|
482
|
+
def sorbet_extend_sig?(node)
|
483
|
+
return false unless node.is_a?(Prism::CallNode)
|
484
|
+
return false unless node.name == :extend
|
443
485
|
|
444
|
-
|
445
|
-
return false unless
|
486
|
+
args = node.arguments
|
487
|
+
return false unless args
|
488
|
+
return false unless args.arguments.size == 1
|
446
489
|
|
447
|
-
|
490
|
+
args.arguments.first&.slice == "T::Sig"
|
448
491
|
end
|
449
492
|
|
450
|
-
sig { params(
|
451
|
-
def
|
452
|
-
|
453
|
-
return false unless node_string(node.message) == "extend"
|
454
|
-
return false unless node.arguments.parts.size == 1
|
493
|
+
sig { params(start_line: Integer, end_line: Integer).returns(T::Array[Prism::Comment]) }
|
494
|
+
def comments_between_lines(start_line, end_line)
|
495
|
+
comments = T.let([], T::Array[Prism::Comment])
|
455
496
|
|
456
|
-
|
497
|
+
(start_line + 1).upto(end_line - 1) do |line|
|
498
|
+
comment = @comments[line]
|
499
|
+
comments << comment if comment
|
500
|
+
end
|
501
|
+
|
502
|
+
comments
|
457
503
|
end
|
458
504
|
|
459
|
-
sig { params(
|
460
|
-
def
|
461
|
-
|
505
|
+
sig { params(node: Prism::Node).returns(T::Array[Prism::Comment]) }
|
506
|
+
def attached_comments(node)
|
507
|
+
comments = T.let([], T::Array[Prism::Comment])
|
508
|
+
|
509
|
+
start_line = node.location.start_line - 1
|
510
|
+
start_line.downto(1) do |line|
|
511
|
+
comment = @comments[line]
|
512
|
+
break unless comment
|
462
513
|
|
463
|
-
|
514
|
+
comments << comment
|
515
|
+
end
|
516
|
+
|
517
|
+
comments.reverse
|
464
518
|
end
|
465
519
|
|
466
|
-
sig { returns(T::Array[
|
467
|
-
def
|
468
|
-
nodes = T.let([], T::Array[
|
520
|
+
sig { returns(T::Array[Prism::Node]) }
|
521
|
+
def attached_sigs
|
522
|
+
nodes = T.let([], T::Array[Prism::Node])
|
469
523
|
|
470
524
|
previous_nodes.reverse_each do |prev_node|
|
471
|
-
break unless
|
525
|
+
break unless sorbet_signature?(prev_node)
|
472
526
|
|
473
527
|
nodes << prev_node
|
474
528
|
end
|
@@ -476,13 +530,13 @@ module Spoom
|
|
476
530
|
nodes.reverse
|
477
531
|
end
|
478
532
|
|
479
|
-
sig { returns(T.nilable(
|
533
|
+
sig { returns(T.nilable(Prism::CallNode)) }
|
480
534
|
def attached_sig
|
481
535
|
previous_nodes.reverse_each do |node|
|
482
|
-
if node.is_a?(
|
536
|
+
if node.is_a?(Prism::Comment)
|
483
537
|
next
|
484
538
|
elsif sorbet_signature?(node)
|
485
|
-
return T.cast(node,
|
539
|
+
return T.cast(node, Prism::CallNode)
|
486
540
|
else
|
487
541
|
break
|
488
542
|
end
|
@@ -490,79 +544,92 @@ module Spoom
|
|
490
544
|
|
491
545
|
nil
|
492
546
|
end
|
493
|
-
|
494
|
-
sig { params(node: T.any(Symbol, SyntaxTree::Node)).returns(String) }
|
495
|
-
def node_string(node)
|
496
|
-
case node
|
497
|
-
when Symbol
|
498
|
-
node.to_s
|
499
|
-
else
|
500
|
-
T.must(@source[node.location.start_char...node.location.end_char])
|
501
|
-
end
|
502
|
-
end
|
503
547
|
end
|
504
548
|
|
505
|
-
class NodeFinder <
|
549
|
+
class NodeFinder < Visitor
|
506
550
|
extend T::Sig
|
507
551
|
|
508
552
|
class << self
|
509
553
|
extend T::Sig
|
510
554
|
|
511
|
-
sig { params(source: String, location: Location, kind: Definition::Kind).returns(NodeContext) }
|
555
|
+
sig { params(source: String, location: Location, kind: T.nilable(Definition::Kind)).returns(NodeContext) }
|
512
556
|
def find(source, location, kind)
|
513
|
-
|
557
|
+
result = Prism.parse(source)
|
558
|
+
|
559
|
+
unless result.success?
|
560
|
+
message = result.errors.map do |e|
|
561
|
+
"#{e.message} (at #{e.location.start_line}:#{e.location.start_column})."
|
562
|
+
end.join(" ")
|
563
|
+
|
564
|
+
raise ParserError, "Error while parsing #{location.file}: #{message}"
|
565
|
+
end
|
514
566
|
|
515
567
|
visitor = new(location)
|
516
|
-
visitor.visit(
|
568
|
+
visitor.visit(result.value)
|
517
569
|
|
518
570
|
node = visitor.node
|
519
571
|
unless node
|
520
572
|
raise Error, "Can't find node at #{location}"
|
521
573
|
end
|
522
574
|
|
523
|
-
|
575
|
+
if kind && !node_match_kind?(node, kind)
|
524
576
|
raise Error, "Can't find node at #{location}, expected #{kind} but got #{node.class}"
|
525
577
|
end
|
526
578
|
|
527
|
-
|
579
|
+
comments_by_line = T.let(
|
580
|
+
result.comments.to_h do |comment|
|
581
|
+
[comment.location.start_line, comment]
|
582
|
+
end,
|
583
|
+
T::Hash[Integer, Prism::Comment],
|
584
|
+
)
|
585
|
+
|
586
|
+
NodeContext.new(source, comments_by_line, node, visitor.nodes_nesting)
|
528
587
|
end
|
529
588
|
|
530
|
-
sig { params(node:
|
589
|
+
sig { params(node: Prism::Node, kind: Definition::Kind).returns(T::Boolean) }
|
531
590
|
def node_match_kind?(node, kind)
|
532
591
|
case kind
|
533
592
|
when Definition::Kind::AttrReader, Definition::Kind::AttrWriter
|
534
|
-
node.is_a?(
|
593
|
+
node.is_a?(Prism::SymbolNode)
|
535
594
|
when Definition::Kind::Class
|
536
|
-
node.is_a?(
|
595
|
+
node.is_a?(Prism::ClassNode)
|
537
596
|
when Definition::Kind::Constant
|
538
|
-
node.is_a?(
|
597
|
+
node.is_a?(Prism::ConstantWriteNode) ||
|
598
|
+
node.is_a?(Prism::ConstantAndWriteNode) ||
|
599
|
+
node.is_a?(Prism::ConstantOrWriteNode) ||
|
600
|
+
node.is_a?(Prism::ConstantOperatorWriteNode) ||
|
601
|
+
node.is_a?(Prism::ConstantPathWriteNode) ||
|
602
|
+
node.is_a?(Prism::ConstantPathAndWriteNode) ||
|
603
|
+
node.is_a?(Prism::ConstantPathOrWriteNode) ||
|
604
|
+
node.is_a?(Prism::ConstantPathOperatorWriteNode) ||
|
605
|
+
node.is_a?(Prism::ConstantTargetNode)
|
539
606
|
when Definition::Kind::Method
|
540
|
-
node.is_a?(
|
607
|
+
node.is_a?(Prism::DefNode)
|
541
608
|
when Definition::Kind::Module
|
542
|
-
node.is_a?(
|
609
|
+
node.is_a?(Prism::ModuleNode)
|
543
610
|
end
|
544
611
|
end
|
545
612
|
end
|
546
613
|
|
547
|
-
sig { returns(T.nilable(
|
614
|
+
sig { returns(T.nilable(Prism::Node)) }
|
548
615
|
attr_reader :node
|
549
616
|
|
550
|
-
sig { returns(T::Array[
|
551
|
-
|
617
|
+
sig { returns(T::Array[Prism::Node]) }
|
618
|
+
attr_reader :nodes_nesting
|
552
619
|
|
553
620
|
sig { params(location: Location).void }
|
554
621
|
def initialize(location)
|
555
622
|
super()
|
556
623
|
@location = location
|
557
|
-
@node = T.let(nil, T.nilable(
|
558
|
-
@nodes_nesting = T.let([], T::Array[
|
624
|
+
@node = T.let(nil, T.nilable(Prism::Node))
|
625
|
+
@nodes_nesting = T.let([], T::Array[Prism::Node])
|
559
626
|
end
|
560
627
|
|
561
|
-
sig { override.params(node: T.nilable(
|
628
|
+
sig { override.params(node: T.nilable(Prism::Node)).void }
|
562
629
|
def visit(node)
|
563
630
|
return unless node
|
564
631
|
|
565
|
-
location =
|
632
|
+
location = Location.from_prism(@location.file, node.location)
|
566
633
|
|
567
634
|
if location == @location
|
568
635
|
# We found the node we're looking for at `@location`
|
@@ -578,38 +645,6 @@ module Spoom
|
|
578
645
|
super(node)
|
579
646
|
end
|
580
647
|
end
|
581
|
-
|
582
|
-
private
|
583
|
-
|
584
|
-
# TODO: remove once SyntaxTree location are fixed
|
585
|
-
sig { params(node: SyntaxTree::Node).returns(Location) }
|
586
|
-
def location_from_node(node)
|
587
|
-
case node
|
588
|
-
when SyntaxTree::Program, SyntaxTree::BodyStmt
|
589
|
-
# Patch SyntaxTree node locations to use the one of their children
|
590
|
-
location_from_children(node, node.statements.body)
|
591
|
-
when SyntaxTree::Statements
|
592
|
-
# Patch SyntaxTree node locations to use the one of their children
|
593
|
-
location_from_children(node, node.body)
|
594
|
-
else
|
595
|
-
Location.from_syntax_tree(@location.file, node.location)
|
596
|
-
end
|
597
|
-
end
|
598
|
-
|
599
|
-
# TODO: remove once SyntaxTree location are fixed
|
600
|
-
sig { params(node: SyntaxTree::Node, nodes: T::Array[SyntaxTree::Node]).returns(Location) }
|
601
|
-
def location_from_children(node, nodes)
|
602
|
-
first = T.must(nodes.first)
|
603
|
-
last = T.must(nodes.last)
|
604
|
-
|
605
|
-
Location.new(
|
606
|
-
@location.file,
|
607
|
-
first.location.start_line,
|
608
|
-
first.location.start_column,
|
609
|
-
last.location.end_line,
|
610
|
-
last.location.end_column,
|
611
|
-
)
|
612
|
-
end
|
613
648
|
end
|
614
649
|
end
|
615
650
|
end
|