spoom 1.3.2 → 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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/spoom/cli/deadcode.rb +21 -17
  3. data/lib/spoom/deadcode/index.rb +178 -10
  4. data/lib/spoom/deadcode/indexer.rb +14 -435
  5. data/lib/spoom/deadcode/plugins/action_mailer.rb +3 -3
  6. data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +9 -3
  7. data/lib/spoom/deadcode/plugins/actionpack.rb +12 -9
  8. data/lib/spoom/deadcode/plugins/active_model.rb +8 -8
  9. data/lib/spoom/deadcode/plugins/active_record.rb +5 -5
  10. data/lib/spoom/deadcode/plugins/active_support.rb +4 -4
  11. data/lib/spoom/deadcode/plugins/base.rb +70 -57
  12. data/lib/spoom/deadcode/plugins/graphql.rb +8 -8
  13. data/lib/spoom/deadcode/plugins/minitest.rb +4 -3
  14. data/lib/spoom/deadcode/plugins/namespaces.rb +9 -12
  15. data/lib/spoom/deadcode/plugins/rails.rb +9 -9
  16. data/lib/spoom/deadcode/plugins/rubocop.rb +13 -17
  17. data/lib/spoom/deadcode/plugins/ruby.rb +9 -9
  18. data/lib/spoom/deadcode/plugins/sorbet.rb +15 -18
  19. data/lib/spoom/deadcode/plugins/thor.rb +5 -4
  20. data/lib/spoom/deadcode/plugins.rb +4 -5
  21. data/lib/spoom/deadcode/remover.rb +7 -7
  22. data/lib/spoom/deadcode/send.rb +1 -0
  23. data/lib/spoom/deadcode.rb +4 -73
  24. data/lib/spoom/location.rb +84 -0
  25. data/lib/spoom/model/builder.rb +246 -0
  26. data/lib/spoom/model/model.rb +328 -0
  27. data/lib/spoom/model/namespace_visitor.rb +50 -0
  28. data/lib/spoom/model/reference.rb +49 -0
  29. data/lib/spoom/model/references_visitor.rb +200 -0
  30. data/lib/spoom/model.rb +10 -0
  31. data/lib/spoom/parse.rb +28 -0
  32. data/lib/spoom/poset.rb +197 -0
  33. data/lib/spoom/sorbet/errors.rb +5 -3
  34. data/lib/spoom/sorbet/lsp/errors.rb +1 -1
  35. data/lib/spoom/sorbet.rb +1 -1
  36. data/lib/spoom/version.rb +1 -1
  37. data/lib/spoom/visitor.rb +755 -0
  38. data/lib/spoom.rb +2 -0
  39. metadata +20 -13
  40. data/lib/spoom/deadcode/location.rb +0 -86
  41. data/lib/spoom/deadcode/reference.rb +0 -34
  42. data/lib/spoom/deadcode/visitor.rb +0 -755
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf02e50bf4a9482c7895022115de973e0be60b79cf781760cb8e6ecb48013eaf
4
- data.tar.gz: 73ba081a87afdde5b57672b5a67ca860089506254a1b65fa5309ba28a7014bf3
3
+ metadata.gz: e839292b23e8582e1e7b4317c6431de9b7d59e68b2c4dbb4015f75619a995601
4
+ data.tar.gz: 4853e7e2df4398e635e616df0f7354b8d37c67bef76ed12a0ec6efc16de1edc0
5
5
  SHA512:
6
- metadata.gz: 19c39e380ddc81c38aaaf1b954232a4e068e88b14ad8a4690bb2810b62467578a3e836477c6d0c6db398eeec81a7d212e6b630b070f3b23c9990139a17ca0a60
7
- data.tar.gz: ab920946c6ed779638788550f27b5c66ad1c1c21951d6cedc57b0a80fb58d7be094625d55b177e7c5479bfab9e24b733e10ec6f39de0039d3c80c98f45026514
6
+ metadata.gz: 940d6b9ec69c6eb767a1ccf2fc33767e552080010f94b14a77301f66b57a113f0619efb1e3ca357b04af782b3d9cbe07ff81f349adb814aef8c4d3fc8ea65de1
7
+ data.tar.gz: 7de4600f896698ea578887b0f417fd9a7f31d3689e6b69e514905b671197c62abf5f232c9471b364f0e4c3509c81848a7ca08d09c73c4cba4cba036963e34192
@@ -24,7 +24,7 @@ module Spoom
24
24
  desc: "Allowed mime types"
25
25
  option :exclude,
26
26
  type: :array,
27
- default: ["vendor/", "sorbet/"],
27
+ default: ["vendor/", "sorbet/", "tmp/", "log/", "node_modules/"],
28
28
  aliases: :x,
29
29
  desc: "Exclude paths"
30
30
  option :show_files,
@@ -58,7 +58,9 @@ module Spoom
58
58
  collector = FileCollector.new(
59
59
  allow_extensions: options[:allowed_extensions],
60
60
  allow_mime_types: options[:allowed_mime_types],
61
- exclude_patterns: options[:exclude].map { |p| Pathname.new(File.join(exec_path, p, "**")).cleanpath.to_s },
61
+ exclude_patterns: paths.flat_map do |path|
62
+ options[:exclude].map { |excluded| Pathname.new(File.join(path, excluded, "**")).cleanpath.to_s }
63
+ end,
62
64
  )
63
65
  collector.visit_paths(paths)
64
66
  files = collector.files.sort
@@ -71,32 +73,35 @@ module Spoom
71
73
  $stderr.puts
72
74
  end
73
75
 
74
- plugins = Spoom::Deadcode.plugins_from_gemfile_lock(context)
76
+ plugin_classes = Spoom::Deadcode.plugins_from_gemfile_lock(context)
77
+ plugin_classes.merge(Spoom::Deadcode.load_custom_plugins(context))
75
78
  if options[:show_plugins]
76
- $stderr.puts "\nLoaded #{blue(plugins.size.to_s)} plugins\n"
77
- plugins.each do |plugin|
78
- $stderr.puts " #{gray(plugin.class.to_s)}"
79
+ $stderr.puts "\nLoaded #{blue(plugin_classes.size.to_s)} plugins\n"
80
+ plugin_classes.each do |plugin|
81
+ $stderr.puts " #{gray(plugin.to_s)}"
79
82
  end
80
83
  $stderr.puts
81
84
  end
82
85
 
83
- index = Spoom::Deadcode::Index.new
86
+ model = Spoom::Model.new
87
+ index = Spoom::Deadcode::Index.new(model)
88
+ plugins = plugin_classes.map { |plugin| plugin.new(index) }
84
89
 
85
90
  $stderr.puts "Indexing #{blue(files.size.to_s)} files..."
86
91
  files.each do |file|
87
- content = File.read(file)
88
- content = ERB.new(content).src if file.end_with?(".erb")
89
-
90
- tree = Spoom::Deadcode.parse_ruby(content, file: file)
91
- Spoom::Deadcode.index_node(index, tree, content, file: file, plugins: plugins)
92
- rescue Spoom::Deadcode::ParserError => e
92
+ index.index_file(file, plugins: plugins)
93
+ rescue ParseError => e
93
94
  say_error("Error parsing #{file}: #{e.message}")
94
95
  next
95
- rescue Spoom::Deadcode::IndexerError => e
96
+ rescue Spoom::Deadcode::Index::Error => e
96
97
  say_error("Error indexing #{file}: #{e.message}")
97
98
  next
98
99
  end
99
100
 
101
+ model.finalize!
102
+ index.apply_plugins!(plugins)
103
+ index.finalize!
104
+
100
105
  if options[:show_defs]
101
106
  $stderr.puts "\nDefinitions:"
102
107
  index.definitions.each do |name, definitions|
@@ -123,7 +128,6 @@ module Spoom
123
128
  references_count = index.references.size.to_s
124
129
  $stderr.puts "Analyzing #{blue(definitions_count)} definitions against #{blue(references_count)} references..."
125
130
 
126
- index.finalize!
127
131
  dead = index.definitions.values.flatten.select(&:dead?)
128
132
 
129
133
  if options[:sort] == "name"
@@ -148,7 +152,7 @@ module Spoom
148
152
 
149
153
  desc "remove LOCATION", "Remove dead code at LOCATION"
150
154
  def remove(location_string)
151
- location = Spoom::Deadcode::Location.from_string(location_string)
155
+ location = Location.from_string(location_string)
152
156
  context = self.context
153
157
  remover = Spoom::Deadcode::Remover.new(context)
154
158
 
@@ -163,7 +167,7 @@ module Spoom
163
167
  rescue Spoom::Deadcode::Remover::Error => e
164
168
  say_error("Can't remove code at #{location_string}: #{e.message}")
165
169
  exit(1)
166
- rescue Spoom::Deadcode::Location::LocationError => e
170
+ rescue Location::LocationError => e
167
171
  say_error(e.message)
168
172
  exit(1)
169
173
  end
@@ -6,28 +6,113 @@ module Spoom
6
6
  class Index
7
7
  extend T::Sig
8
8
 
9
+ class Error < Spoom::Error
10
+ extend T::Sig
11
+
12
+ sig { params(message: String, parent: Exception).void }
13
+ def initialize(message, parent:)
14
+ super(message)
15
+ set_backtrace(parent.backtrace)
16
+ end
17
+ end
18
+
19
+ sig { returns(Model) }
20
+ attr_reader :model
21
+
9
22
  sig { returns(T::Hash[String, T::Array[Definition]]) }
10
23
  attr_reader :definitions
11
24
 
12
- sig { returns(T::Hash[String, T::Array[Reference]]) }
25
+ sig { returns(T::Hash[String, T::Array[Model::Reference]]) }
13
26
  attr_reader :references
14
27
 
15
- sig { void }
16
- def initialize
28
+ sig { params(model: Model).void }
29
+ def initialize(model)
30
+ @model = model
17
31
  @definitions = T.let({}, T::Hash[String, T::Array[Definition]])
18
- @references = T.let({}, T::Hash[String, T::Array[Reference]])
32
+ @references = T.let({}, T::Hash[String, T::Array[Model::Reference]])
33
+ @ignored = T.let(Set.new, T::Set[Model::SymbolDef])
19
34
  end
20
35
 
21
36
  # Indexing
22
37
 
38
+ sig { params(file: String, plugins: T::Array[Plugins::Base]).void }
39
+ def index_file(file, plugins: [])
40
+ if file.end_with?(".erb")
41
+ erb = File.read(file)
42
+ index_erb(erb, file: file, plugins: plugins)
43
+ else
44
+ rb = File.read(file)
45
+ index_ruby(rb, file: file, plugins: plugins)
46
+ end
47
+ end
48
+
49
+ sig { params(erb: String, file: String, plugins: T::Array[Plugins::Base]).void }
50
+ def index_erb(erb, file:, plugins: [])
51
+ index_ruby(Deadcode::ERB.new(erb).src, file: file, plugins: plugins)
52
+ end
53
+
54
+ sig { params(rb: String, file: String, plugins: T::Array[Plugins::Base]).void }
55
+ def index_ruby(rb, file:, plugins: [])
56
+ node = Spoom.parse_ruby(rb, file: file)
57
+
58
+ # Index definitions
59
+ model_builder = Model::Builder.new(@model, file)
60
+ model_builder.visit(node)
61
+
62
+ # Index references
63
+ refs_visitor = Model::ReferencesVisitor.new(file)
64
+ refs_visitor.visit(node)
65
+ refs_visitor.references.each do |ref|
66
+ (@references[ref.name] ||= []) << ref
67
+ end
68
+
69
+ # Index references and sends
70
+ indexer = Indexer.new(file, self, plugins: plugins)
71
+ indexer.visit(node)
72
+ rescue ParseError => e
73
+ raise e
74
+ rescue => e
75
+ raise Error.new("Error while indexing #{file} (#{e.message})", parent: e)
76
+ end
77
+
23
78
  sig { params(definition: Definition).void }
24
79
  def define(definition)
25
80
  (@definitions[definition.name] ||= []) << definition
26
81
  end
27
82
 
28
- sig { params(reference: Reference).void }
29
- def reference(reference)
30
- (@references[reference.name] ||= []) << reference
83
+ sig { params(name: String, location: Location).void }
84
+ def reference_constant(name, location)
85
+ (@references[name] ||= []) << Model::Reference.constant(name, location)
86
+ end
87
+
88
+ sig { params(name: String, location: Location).void }
89
+ def reference_method(name, location)
90
+ (@references[name] ||= []) << Model::Reference.method(name, location)
91
+ end
92
+
93
+ sig { params(symbol_def: Model::SymbolDef).void }
94
+ def ignore(symbol_def)
95
+ @ignored << symbol_def
96
+ end
97
+
98
+ sig { params(plugins: T::Array[Plugins::Base]).void }
99
+ def apply_plugins!(plugins)
100
+ @model.symbols.each do |_full_name, symbol|
101
+ symbol.definitions.each do |symbol_def|
102
+ case symbol_def
103
+ when Model::Class
104
+ plugins.each { |plugin| plugin.internal_on_define_class(symbol_def) }
105
+ when Model::Module
106
+ plugins.each { |plugin| plugin.internal_on_define_module(symbol_def) }
107
+ when Model::Constant
108
+ plugins.each { |plugin| plugin.internal_on_define_constant(symbol_def) }
109
+ when Model::Method
110
+ plugins.each { |plugin| plugin.internal_on_define_method(symbol_def) }
111
+ when Model::Attr
112
+ plugins.each { |plugin| plugin.internal_on_define_accessor(symbol_def) }
113
+ end
114
+ end
115
+ end
31
116
  end
32
117
 
33
118
  # Mark all definitions having a reference of the same name as `alive`
@@ -35,8 +120,91 @@ module Spoom
35
120
  # To be called once all the files have been indexed and all the definitions and references discovered.
36
121
  sig { void }
37
122
  def finalize!
38
- @references.keys.each do |name|
39
- definitions_for_name(name).each(&:alive!)
123
+ @model.symbols.each do |_full_name, symbol|
124
+ symbol.definitions.each do |symbol_def|
125
+ case symbol_def
126
+ when Model::Class
127
+ definition = Definition.new(
128
+ kind: Definition::Kind::Class,
129
+ name: symbol.name,
130
+ full_name: symbol.full_name,
131
+ location: symbol_def.location,
132
+ )
133
+ definition.ignored! if @ignored.include?(symbol_def)
134
+ definition.alive! if @references.key?(symbol.name)
135
+ define(definition)
136
+ when Model::Module
137
+ definition = Definition.new(
138
+ kind: Definition::Kind::Module,
139
+ name: symbol.name,
140
+ full_name: symbol.full_name,
141
+ location: symbol_def.location,
142
+ )
143
+ definition.ignored! if @ignored.include?(symbol_def)
144
+ definition.alive! if @references.key?(symbol.name)
145
+ define(definition)
146
+ when Model::Constant
147
+ definition = Definition.new(
148
+ kind: Definition::Kind::Constant,
149
+ name: symbol.name,
150
+ full_name: symbol.full_name,
151
+ location: symbol_def.location,
152
+ )
153
+ definition.ignored! if @ignored.include?(symbol_def)
154
+ definition.alive! if @references.key?(symbol.name)
155
+ define(definition)
156
+ when Model::Method
157
+ definition = Definition.new(
158
+ kind: Definition::Kind::Method,
159
+ name: symbol.name,
160
+ full_name: symbol.full_name,
161
+ location: symbol_def.location,
162
+ )
163
+ definition.ignored! if @ignored.include?(symbol_def)
164
+ definition.alive! if @references.key?(symbol.name)
165
+ define(definition)
166
+ when Model::AttrAccessor
167
+ definition = Definition.new(
168
+ kind: Definition::Kind::AttrReader,
169
+ name: symbol.name,
170
+ full_name: symbol.full_name,
171
+ location: symbol_def.location,
172
+ )
173
+ definition.ignored! if @ignored.include?(symbol_def)
174
+ definition.alive! if @references.key?(symbol.name)
175
+ define(definition)
176
+
177
+ definition = Definition.new(
178
+ kind: Definition::Kind::AttrWriter,
179
+ name: "#{symbol.name}=",
180
+ full_name: "#{symbol.full_name}=",
181
+ location: symbol_def.location,
182
+ )
183
+ definition.ignored! if @ignored.include?(symbol_def)
184
+ definition.alive! if @references.key?(symbol.name)
185
+ define(definition)
186
+ when Model::AttrReader
187
+ definition = Definition.new(
188
+ kind: Definition::Kind::AttrReader,
189
+ name: symbol.name,
190
+ full_name: symbol.full_name,
191
+ location: symbol_def.location,
192
+ )
193
+ definition.ignored! if @ignored.include?(symbol_def)
194
+ definition.alive! if @references.key?(symbol.name)
195
+ define(definition)
196
+ when Model::AttrWriter
197
+ definition = Definition.new(
198
+ kind: Definition::Kind::AttrWriter,
199
+ name: "#{symbol.name}=",
200
+ full_name: "#{symbol.full_name}=",
201
+ location: symbol_def.location,
202
+ )
203
+ definition.ignored! if @ignored.include?(symbol_def)
204
+ definition.alive! if @references.key?(symbol.name)
205
+ define(definition)
206
+ end
207
+ end
40
208
  end
41
209
  end
42
210
 
@@ -52,7 +220,7 @@ module Spoom
52
220
  @definitions.values.flatten
53
221
  end
54
222
 
55
- sig { returns(T::Array[Reference]) }
223
+ sig { returns(T::Array[Model::Reference]) }
56
224
  def all_references
57
225
  @references.values.flatten
58
226
  end