spoom 1.3.1 → 1.3.3
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/lib/spoom/cli/deadcode.rb +21 -17
- data/lib/spoom/deadcode/index.rb +178 -10
- data/lib/spoom/deadcode/indexer.rb +14 -435
- data/lib/spoom/deadcode/plugins/action_mailer.rb +3 -3
- data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +9 -3
- data/lib/spoom/deadcode/plugins/actionpack.rb +12 -9
- data/lib/spoom/deadcode/plugins/active_model.rb +8 -8
- data/lib/spoom/deadcode/plugins/active_record.rb +5 -5
- data/lib/spoom/deadcode/plugins/active_support.rb +4 -4
- data/lib/spoom/deadcode/plugins/base.rb +70 -57
- data/lib/spoom/deadcode/plugins/graphql.rb +8 -8
- data/lib/spoom/deadcode/plugins/minitest.rb +4 -3
- data/lib/spoom/deadcode/plugins/namespaces.rb +9 -12
- data/lib/spoom/deadcode/plugins/rails.rb +9 -9
- data/lib/spoom/deadcode/plugins/rubocop.rb +13 -17
- data/lib/spoom/deadcode/plugins/ruby.rb +9 -9
- data/lib/spoom/deadcode/plugins/sorbet.rb +15 -18
- data/lib/spoom/deadcode/plugins/thor.rb +5 -4
- data/lib/spoom/deadcode/plugins.rb +4 -5
- data/lib/spoom/deadcode/remover.rb +14 -10
- data/lib/spoom/deadcode/send.rb +1 -0
- data/lib/spoom/deadcode.rb +4 -73
- data/lib/spoom/location.rb +84 -0
- data/lib/spoom/model/builder.rb +246 -0
- data/lib/spoom/model/model.rb +328 -0
- data/lib/spoom/model/namespace_visitor.rb +50 -0
- data/lib/spoom/model/reference.rb +49 -0
- data/lib/spoom/model/references_visitor.rb +200 -0
- data/lib/spoom/model.rb +10 -0
- data/lib/spoom/parse.rb +28 -0
- data/lib/spoom/poset.rb +197 -0
- data/lib/spoom/sorbet/errors.rb +5 -3
- data/lib/spoom/sorbet/lsp/errors.rb +1 -1
- data/lib/spoom/sorbet.rb +1 -1
- data/lib/spoom/version.rb +1 -1
- data/lib/spoom/visitor.rb +755 -0
- data/lib/spoom.rb +2 -0
- metadata +20 -13
- data/lib/spoom/deadcode/location.rb +0 -86
- data/lib/spoom/deadcode/reference.rb +0 -34
- data/lib/spoom/deadcode/visitor.rb +0 -755
@@ -0,0 +1,328 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
class Model
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
class Error < Spoom::Error; end
|
9
|
+
|
10
|
+
# A Symbol is a uniquely named entity in the Ruby codebase
|
11
|
+
#
|
12
|
+
# A symbol can have multiple definitions, e.g. a class can be reopened.
|
13
|
+
# Sometimes a symbol can have multiple definitions of different types,
|
14
|
+
# e.g. `foo` method can be defined both as a method and as an attribute accessor.
|
15
|
+
class Symbol
|
16
|
+
extend T::Sig
|
17
|
+
|
18
|
+
# The full, unique name of this symbol
|
19
|
+
sig { returns(String) }
|
20
|
+
attr_reader :full_name
|
21
|
+
|
22
|
+
# The definitions of this symbol (where it exists in the code)
|
23
|
+
sig { returns(T::Array[SymbolDef]) }
|
24
|
+
attr_reader :definitions
|
25
|
+
|
26
|
+
sig { params(full_name: String).void }
|
27
|
+
def initialize(full_name)
|
28
|
+
@full_name = full_name
|
29
|
+
@definitions = T.let([], T::Array[SymbolDef])
|
30
|
+
end
|
31
|
+
|
32
|
+
# The short name of this symbol
|
33
|
+
sig { returns(String) }
|
34
|
+
def name
|
35
|
+
T.must(@full_name.split("::").last)
|
36
|
+
end
|
37
|
+
|
38
|
+
sig { returns(String) }
|
39
|
+
def to_s
|
40
|
+
@full_name
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class UnresolvedSymbol < Symbol
|
45
|
+
sig { override.returns(String) }
|
46
|
+
def to_s
|
47
|
+
"<#{@full_name}>"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# A SymbolDef is a definition of a Symbol
|
52
|
+
#
|
53
|
+
# It can be a class, module, constant, method, etc.
|
54
|
+
# A SymbolDef has a location pointing to the actual code that defines the symbol.
|
55
|
+
class SymbolDef
|
56
|
+
extend T::Sig
|
57
|
+
extend T::Helpers
|
58
|
+
|
59
|
+
abstract!
|
60
|
+
|
61
|
+
# The symbol this definition belongs to
|
62
|
+
sig { returns(Symbol) }
|
63
|
+
attr_reader :symbol
|
64
|
+
|
65
|
+
# The enclosing namespace this definition belongs to
|
66
|
+
sig { returns(T.nilable(Namespace)) }
|
67
|
+
attr_reader :owner
|
68
|
+
|
69
|
+
# The actual code location of this definition
|
70
|
+
sig { returns(Location) }
|
71
|
+
attr_reader :location
|
72
|
+
|
73
|
+
sig { params(symbol: Symbol, owner: T.nilable(Namespace), location: Location).void }
|
74
|
+
def initialize(symbol, owner:, location:)
|
75
|
+
@symbol = symbol
|
76
|
+
@owner = owner
|
77
|
+
@location = location
|
78
|
+
|
79
|
+
symbol.definitions << self
|
80
|
+
owner.children << self if owner
|
81
|
+
end
|
82
|
+
|
83
|
+
# The full name of the symbol this definition belongs to
|
84
|
+
sig { returns(String) }
|
85
|
+
def full_name
|
86
|
+
@symbol.full_name
|
87
|
+
end
|
88
|
+
|
89
|
+
# The short name of the symbol this definition belongs to
|
90
|
+
sig { returns(String) }
|
91
|
+
def name
|
92
|
+
@symbol.name
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# A class or module
|
97
|
+
class Namespace < SymbolDef
|
98
|
+
abstract!
|
99
|
+
|
100
|
+
sig { returns(T::Array[SymbolDef]) }
|
101
|
+
attr_reader :children
|
102
|
+
|
103
|
+
sig { returns(T::Array[Mixin]) }
|
104
|
+
attr_reader :mixins
|
105
|
+
|
106
|
+
sig { params(symbol: Symbol, owner: T.nilable(Namespace), location: Location).void }
|
107
|
+
def initialize(symbol, owner:, location:)
|
108
|
+
super(symbol, owner: owner, location: location)
|
109
|
+
|
110
|
+
@children = T.let([], T::Array[SymbolDef])
|
111
|
+
@mixins = T.let([], T::Array[Mixin])
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class SingletonClass < Namespace; end
|
116
|
+
|
117
|
+
class Class < Namespace
|
118
|
+
sig { returns(T.nilable(String)) }
|
119
|
+
attr_accessor :superclass_name
|
120
|
+
|
121
|
+
sig do
|
122
|
+
params(
|
123
|
+
symbol: Symbol,
|
124
|
+
owner: T.nilable(Namespace),
|
125
|
+
location: Location,
|
126
|
+
superclass_name: T.nilable(String),
|
127
|
+
).void
|
128
|
+
end
|
129
|
+
def initialize(symbol, owner:, location:, superclass_name: nil)
|
130
|
+
super(symbol, owner: owner, location: location)
|
131
|
+
|
132
|
+
@superclass_name = superclass_name
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
class Module < Namespace; end
|
137
|
+
|
138
|
+
class Constant < SymbolDef
|
139
|
+
sig { returns(String) }
|
140
|
+
attr_reader :value
|
141
|
+
|
142
|
+
sig { params(symbol: Symbol, owner: T.nilable(Namespace), location: Location, value: String).void }
|
143
|
+
def initialize(symbol, owner:, location:, value:)
|
144
|
+
super(symbol, owner: owner, location: location)
|
145
|
+
|
146
|
+
@value = value
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# A method or an attribute accessor
|
151
|
+
class Property < SymbolDef
|
152
|
+
abstract!
|
153
|
+
|
154
|
+
sig { returns(Visibility) }
|
155
|
+
attr_reader :visibility
|
156
|
+
|
157
|
+
sig { returns(T::Array[Sig]) }
|
158
|
+
attr_reader :sigs
|
159
|
+
|
160
|
+
sig do
|
161
|
+
params(
|
162
|
+
symbol: Symbol,
|
163
|
+
owner: T.nilable(Namespace),
|
164
|
+
location: Location,
|
165
|
+
visibility: Visibility,
|
166
|
+
sigs: T::Array[Sig],
|
167
|
+
).void
|
168
|
+
end
|
169
|
+
def initialize(symbol, owner:, location:, visibility:, sigs: [])
|
170
|
+
super(symbol, owner: owner, location: location)
|
171
|
+
|
172
|
+
@visibility = visibility
|
173
|
+
@sigs = sigs
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
class Method < Property; end
|
178
|
+
|
179
|
+
class Attr < Property
|
180
|
+
abstract!
|
181
|
+
end
|
182
|
+
|
183
|
+
class AttrReader < Attr; end
|
184
|
+
class AttrWriter < Attr; end
|
185
|
+
class AttrAccessor < Attr; end
|
186
|
+
|
187
|
+
class Visibility < T::Enum
|
188
|
+
enums do
|
189
|
+
Public = new("public")
|
190
|
+
Protected = new("protected")
|
191
|
+
Private = new("private")
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# A mixin (include, prepend, extend) to a namespace
|
196
|
+
class Mixin
|
197
|
+
extend T::Sig
|
198
|
+
extend T::Helpers
|
199
|
+
|
200
|
+
abstract!
|
201
|
+
|
202
|
+
sig { returns(String) }
|
203
|
+
attr_reader :name
|
204
|
+
|
205
|
+
sig { params(name: String).void }
|
206
|
+
def initialize(name)
|
207
|
+
@name = name
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
class Include < Mixin; end
|
212
|
+
class Prepend < Mixin; end
|
213
|
+
class Extend < Mixin; end
|
214
|
+
|
215
|
+
# A Sorbet signature (sig block)
|
216
|
+
class Sig
|
217
|
+
extend T::Sig
|
218
|
+
|
219
|
+
sig { returns(String) }
|
220
|
+
attr_reader :string
|
221
|
+
|
222
|
+
sig { params(string: String).void }
|
223
|
+
def initialize(string)
|
224
|
+
@string = string
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Model
|
229
|
+
|
230
|
+
# All the symbols registered in this model
|
231
|
+
sig { returns(T::Hash[String, Symbol]) }
|
232
|
+
attr_reader :symbols
|
233
|
+
|
234
|
+
sig { returns(Poset[Symbol]) }
|
235
|
+
attr_reader :symbols_hierarchy
|
236
|
+
|
237
|
+
sig { void }
|
238
|
+
def initialize
|
239
|
+
@symbols = T.let({}, T::Hash[String, Symbol])
|
240
|
+
@symbols_hierarchy = T.let(Poset[Symbol].new, Poset[Symbol])
|
241
|
+
end
|
242
|
+
|
243
|
+
# Get a symbol by it's full name
|
244
|
+
#
|
245
|
+
# Raises an error if the symbol is not found
|
246
|
+
sig { params(full_name: String).returns(Symbol) }
|
247
|
+
def [](full_name)
|
248
|
+
symbol = @symbols[full_name]
|
249
|
+
raise Error, "Symbol not found: #{full_name}" unless symbol
|
250
|
+
|
251
|
+
symbol
|
252
|
+
end
|
253
|
+
|
254
|
+
# Register a new symbol by it's full name
|
255
|
+
#
|
256
|
+
# If the symbol already exists, it will be returned.
|
257
|
+
sig { params(full_name: String).returns(Symbol) }
|
258
|
+
def register_symbol(full_name)
|
259
|
+
@symbols[full_name] ||= Symbol.new(full_name)
|
260
|
+
end
|
261
|
+
|
262
|
+
sig { params(full_name: String, context: Symbol).returns(Symbol) }
|
263
|
+
def resolve_symbol(full_name, context:)
|
264
|
+
if full_name.start_with?("::")
|
265
|
+
full_name = full_name.delete_prefix("::")
|
266
|
+
return @symbols[full_name] ||= UnresolvedSymbol.new(full_name)
|
267
|
+
end
|
268
|
+
|
269
|
+
target = T.let(@symbols[full_name], T.nilable(Symbol))
|
270
|
+
return target if target
|
271
|
+
|
272
|
+
parts = context.full_name.split("::")
|
273
|
+
until parts.empty?
|
274
|
+
target = @symbols["#{parts.join("::")}::#{full_name}"]
|
275
|
+
return target if target
|
276
|
+
|
277
|
+
parts.pop
|
278
|
+
end
|
279
|
+
|
280
|
+
@symbols[full_name] = UnresolvedSymbol.new(full_name)
|
281
|
+
end
|
282
|
+
|
283
|
+
sig { params(symbol: Symbol).returns(T::Array[Symbol]) }
|
284
|
+
def supertypes(symbol)
|
285
|
+
poe = @symbols_hierarchy[symbol]
|
286
|
+
poe.ancestors
|
287
|
+
end
|
288
|
+
|
289
|
+
sig { params(symbol: Symbol).returns(T::Array[Symbol]) }
|
290
|
+
def subtypes(symbol)
|
291
|
+
poe = @symbols_hierarchy[symbol]
|
292
|
+
poe.descendants
|
293
|
+
end
|
294
|
+
|
295
|
+
sig { void }
|
296
|
+
def finalize!
|
297
|
+
compute_symbols_hierarchy!
|
298
|
+
end
|
299
|
+
|
300
|
+
private
|
301
|
+
|
302
|
+
sig { void }
|
303
|
+
def compute_symbols_hierarchy!
|
304
|
+
@symbols.dup.each do |_full_name, symbol|
|
305
|
+
symbol.definitions.each do |definition|
|
306
|
+
next unless definition.is_a?(Namespace)
|
307
|
+
|
308
|
+
@symbols_hierarchy.add_element(symbol)
|
309
|
+
|
310
|
+
if definition.is_a?(Class)
|
311
|
+
superclass_name = definition.superclass_name
|
312
|
+
if superclass_name
|
313
|
+
superclass = resolve_symbol(superclass_name, context: symbol)
|
314
|
+
@symbols_hierarchy.add_direct_edge(symbol, superclass)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
definition.mixins.each do |mixin|
|
319
|
+
next if mixin.is_a?(Extend)
|
320
|
+
|
321
|
+
target = resolve_symbol(mixin.name, context: symbol)
|
322
|
+
@symbols_hierarchy.add_direct_edge(symbol, target)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
class Model
|
6
|
+
class NamespaceVisitor < Visitor
|
7
|
+
extend T::Helpers
|
8
|
+
|
9
|
+
abstract!
|
10
|
+
|
11
|
+
sig { void }
|
12
|
+
def initialize
|
13
|
+
super()
|
14
|
+
|
15
|
+
@names_nesting = T.let([], T::Array[String])
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { override.params(node: T.nilable(Prism::Node)).void }
|
19
|
+
def visit(node)
|
20
|
+
case node
|
21
|
+
when Prism::ClassNode, Prism::ModuleNode
|
22
|
+
constant_path = node.constant_path.slice
|
23
|
+
|
24
|
+
if constant_path.start_with?("::")
|
25
|
+
full_name = constant_path.delete_prefix("::")
|
26
|
+
|
27
|
+
# We found a top level definition such as `class ::A; end`, we need to reset the name nesting
|
28
|
+
old_nesting = @names_nesting.dup
|
29
|
+
@names_nesting.clear
|
30
|
+
@names_nesting << full_name
|
31
|
+
|
32
|
+
super
|
33
|
+
|
34
|
+
# Restore the name nesting once we finished visited the class
|
35
|
+
@names_nesting.clear
|
36
|
+
@names_nesting = old_nesting
|
37
|
+
else
|
38
|
+
@names_nesting << constant_path
|
39
|
+
|
40
|
+
super
|
41
|
+
|
42
|
+
@names_nesting.pop
|
43
|
+
end
|
44
|
+
else
|
45
|
+
super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
class Model
|
6
|
+
# A reference to something that looks like a constant or a method
|
7
|
+
#
|
8
|
+
# Constants could be classes, modules, or actual constants.
|
9
|
+
# Methods could be accessors, instance or class methods, aliases, etc.
|
10
|
+
class Reference < T::Struct
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
class Kind < T::Enum
|
14
|
+
enums do
|
15
|
+
Constant = new("constant")
|
16
|
+
Method = new("method")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
extend T::Sig
|
22
|
+
|
23
|
+
sig { params(name: String, location: Spoom::Location).returns(Reference) }
|
24
|
+
def constant(name, location)
|
25
|
+
new(name: name, kind: Kind::Constant, location: location)
|
26
|
+
end
|
27
|
+
|
28
|
+
sig { params(name: String, location: Spoom::Location).returns(Reference) }
|
29
|
+
def method(name, location)
|
30
|
+
new(name: name, kind: Kind::Method, location: location)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
const :kind, Kind
|
35
|
+
const :name, String
|
36
|
+
const :location, Spoom::Location
|
37
|
+
|
38
|
+
sig { returns(T::Boolean) }
|
39
|
+
def constant?
|
40
|
+
kind == Kind::Constant
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { returns(T::Boolean) }
|
44
|
+
def method?
|
45
|
+
kind == Kind::Method
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
class Model
|
6
|
+
# Visit a file to collect all the references to constants and methods
|
7
|
+
class ReferencesVisitor < Visitor
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { returns(T::Array[Reference]) }
|
11
|
+
attr_reader :references
|
12
|
+
|
13
|
+
sig { params(file: String).void }
|
14
|
+
def initialize(file)
|
15
|
+
super()
|
16
|
+
|
17
|
+
@file = file
|
18
|
+
@references = T.let([], T::Array[Reference])
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { override.params(node: Prism::AliasMethodNode).void }
|
22
|
+
def visit_alias_method_node(node)
|
23
|
+
reference_method(node.old_name.slice, node)
|
24
|
+
end
|
25
|
+
|
26
|
+
sig { override.params(node: Prism::AndNode).void }
|
27
|
+
def visit_and_node(node)
|
28
|
+
reference_method(node.operator_loc.slice, node)
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { override.params(node: Prism::BlockArgumentNode).void }
|
33
|
+
def visit_block_argument_node(node)
|
34
|
+
expression = node.expression
|
35
|
+
case expression
|
36
|
+
when Prism::SymbolNode
|
37
|
+
reference_method(expression.unescaped, expression)
|
38
|
+
else
|
39
|
+
visit(expression)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { override.params(node: Prism::CallAndWriteNode).void }
|
44
|
+
def visit_call_and_write_node(node)
|
45
|
+
visit(node.receiver)
|
46
|
+
reference_method(node.read_name.to_s, node)
|
47
|
+
reference_method(node.write_name.to_s, node)
|
48
|
+
visit(node.value)
|
49
|
+
end
|
50
|
+
|
51
|
+
sig { override.params(node: Prism::CallOperatorWriteNode).void }
|
52
|
+
def visit_call_operator_write_node(node)
|
53
|
+
visit(node.receiver)
|
54
|
+
reference_method(node.read_name.to_s, node)
|
55
|
+
reference_method(node.write_name.to_s, node)
|
56
|
+
visit(node.value)
|
57
|
+
end
|
58
|
+
|
59
|
+
sig { override.params(node: Prism::CallOrWriteNode).void }
|
60
|
+
def visit_call_or_write_node(node)
|
61
|
+
visit(node.receiver)
|
62
|
+
reference_method(node.read_name.to_s, node)
|
63
|
+
reference_method(node.write_name.to_s, node)
|
64
|
+
visit(node.value)
|
65
|
+
end
|
66
|
+
|
67
|
+
sig { override.params(node: Prism::CallNode).void }
|
68
|
+
def visit_call_node(node)
|
69
|
+
visit(node.receiver)
|
70
|
+
|
71
|
+
name = node.name.to_s
|
72
|
+
reference_method(name, node)
|
73
|
+
|
74
|
+
case name
|
75
|
+
when "<", ">", "<=", ">="
|
76
|
+
# For comparison operators, we also reference the `<=>` method
|
77
|
+
reference_method("<=>", node)
|
78
|
+
end
|
79
|
+
|
80
|
+
visit(node.arguments)
|
81
|
+
visit(node.block)
|
82
|
+
end
|
83
|
+
|
84
|
+
sig { override.params(node: Prism::ClassNode).void }
|
85
|
+
def visit_class_node(node)
|
86
|
+
visit(node.superclass) if node.superclass
|
87
|
+
visit(node.body)
|
88
|
+
end
|
89
|
+
|
90
|
+
sig { override.params(node: Prism::ConstantAndWriteNode).void }
|
91
|
+
def visit_constant_and_write_node(node)
|
92
|
+
reference_constant(node.name.to_s, node)
|
93
|
+
visit(node.value)
|
94
|
+
end
|
95
|
+
|
96
|
+
sig { override.params(node: Prism::ConstantOperatorWriteNode).void }
|
97
|
+
def visit_constant_operator_write_node(node)
|
98
|
+
reference_constant(node.name.to_s, node)
|
99
|
+
visit(node.value)
|
100
|
+
end
|
101
|
+
|
102
|
+
sig { override.params(node: Prism::ConstantOrWriteNode).void }
|
103
|
+
def visit_constant_or_write_node(node)
|
104
|
+
reference_constant(node.name.to_s, node)
|
105
|
+
visit(node.value)
|
106
|
+
end
|
107
|
+
|
108
|
+
sig { override.params(node: Prism::ConstantPathNode).void }
|
109
|
+
def visit_constant_path_node(node)
|
110
|
+
visit(node.parent)
|
111
|
+
reference_constant(node.name.to_s, node)
|
112
|
+
end
|
113
|
+
|
114
|
+
sig { override.params(node: Prism::ConstantPathWriteNode).void }
|
115
|
+
def visit_constant_path_write_node(node)
|
116
|
+
visit(node.target.parent)
|
117
|
+
visit(node.value)
|
118
|
+
end
|
119
|
+
|
120
|
+
sig { override.params(node: Prism::ConstantReadNode).void }
|
121
|
+
def visit_constant_read_node(node)
|
122
|
+
reference_constant(node.name.to_s, node)
|
123
|
+
end
|
124
|
+
|
125
|
+
sig { override.params(node: Prism::ConstantWriteNode).void }
|
126
|
+
def visit_constant_write_node(node)
|
127
|
+
visit(node.value)
|
128
|
+
end
|
129
|
+
|
130
|
+
sig { override.params(node: Prism::LocalVariableAndWriteNode).void }
|
131
|
+
def visit_local_variable_and_write_node(node)
|
132
|
+
name = node.name.to_s
|
133
|
+
reference_method(name, node)
|
134
|
+
reference_method("#{name}=", node)
|
135
|
+
visit(node.value)
|
136
|
+
end
|
137
|
+
|
138
|
+
sig { override.params(node: Prism::LocalVariableOperatorWriteNode).void }
|
139
|
+
def visit_local_variable_operator_write_node(node)
|
140
|
+
name = node.name.to_s
|
141
|
+
reference_method(name, node)
|
142
|
+
reference_method("#{name}=", node)
|
143
|
+
visit(node.value)
|
144
|
+
end
|
145
|
+
|
146
|
+
sig { override.params(node: Prism::LocalVariableOrWriteNode).void }
|
147
|
+
def visit_local_variable_or_write_node(node)
|
148
|
+
name = node.name.to_s
|
149
|
+
reference_method(name, node)
|
150
|
+
reference_method("#{name}=", node)
|
151
|
+
visit(node.value)
|
152
|
+
end
|
153
|
+
|
154
|
+
sig { override.params(node: Prism::LocalVariableWriteNode).void }
|
155
|
+
def visit_local_variable_write_node(node)
|
156
|
+
reference_method("#{node.name}=", node)
|
157
|
+
visit(node.value)
|
158
|
+
end
|
159
|
+
|
160
|
+
sig { override.params(node: Prism::ModuleNode).void }
|
161
|
+
def visit_module_node(node)
|
162
|
+
visit(node.body)
|
163
|
+
end
|
164
|
+
|
165
|
+
sig { override.params(node: Prism::MultiWriteNode).void }
|
166
|
+
def visit_multi_write_node(node)
|
167
|
+
node.lefts.each do |const|
|
168
|
+
case const
|
169
|
+
when Prism::LocalVariableTargetNode
|
170
|
+
reference_method("#{const.name}=", node)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
visit(node.value)
|
174
|
+
end
|
175
|
+
|
176
|
+
sig { override.params(node: Prism::OrNode).void }
|
177
|
+
def visit_or_node(node)
|
178
|
+
reference_method(node.operator_loc.slice, node)
|
179
|
+
super
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
sig { params(name: String, node: Prism::Node).void }
|
185
|
+
def reference_constant(name, node)
|
186
|
+
@references << Reference.constant(name, node_location(node))
|
187
|
+
end
|
188
|
+
|
189
|
+
sig { params(name: String, node: Prism::Node).void }
|
190
|
+
def reference_method(name, node)
|
191
|
+
@references << Reference.method(name, node_location(node))
|
192
|
+
end
|
193
|
+
|
194
|
+
sig { params(node: Prism::Node).returns(Location) }
|
195
|
+
def node_location(node)
|
196
|
+
Location.from_prism(@file, node.location)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
data/lib/spoom/model.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "location"
|
5
|
+
require_relative "parse"
|
6
|
+
require_relative "model/model"
|
7
|
+
require_relative "model/namespace_visitor"
|
8
|
+
require_relative "model/builder"
|
9
|
+
require_relative "model/reference"
|
10
|
+
require_relative "model/references_visitor"
|
data/lib/spoom/parse.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "spoom/visitor"
|
5
|
+
|
6
|
+
module Spoom
|
7
|
+
class ParseError < Error; end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
sig { params(ruby: String, file: String).returns(Prism::Node) }
|
13
|
+
def parse_ruby(ruby, file:)
|
14
|
+
result = Prism.parse(ruby)
|
15
|
+
unless result.success?
|
16
|
+
message = +"Error while parsing #{file}:\n"
|
17
|
+
|
18
|
+
result.errors.each do |e|
|
19
|
+
message << "- #{e.message} (at #{e.location.start_line}:#{e.location.start_column})\n"
|
20
|
+
end
|
21
|
+
|
22
|
+
raise ParseError, message
|
23
|
+
end
|
24
|
+
|
25
|
+
result.value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|