spoom 1.3.1 → 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
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 +14 -10
  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: 3840a06d8381c113e452af8009c2e3c80f8c135c76afe1b1360baf877178d912
4
- data.tar.gz: 3c3941847f63693a3e184d3a24b583ba989769abc18d0bbf8a6b08481ee25ced
3
+ metadata.gz: e839292b23e8582e1e7b4317c6431de9b7d59e68b2c4dbb4015f75619a995601
4
+ data.tar.gz: 4853e7e2df4398e635e616df0f7354b8d37c67bef76ed12a0ec6efc16de1edc0
5
5
  SHA512:
6
- metadata.gz: 23a45ede451ebffe973c1b262a68c667e5b8546c345520615262acef84c195e164b9cf6e7ff4223c569170802c5232366efa92c5525d5ba909606e8f51554cc3
7
- data.tar.gz: d3db42e6aeda62013aced699a86721e37b1fdb72162cd6475d512f77a7c810d3a693d9dfcc0c9b0871a19ef0d67ee7ea87b5188850bf98d441238f3a3aaca87b
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