spoom 1.2.4 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +54 -55
  3. data/lib/spoom/cli/deadcode.rb +172 -0
  4. data/lib/spoom/cli/helper.rb +20 -0
  5. data/lib/spoom/cli/srb/bump.rb +200 -0
  6. data/lib/spoom/cli/srb/coverage.rb +224 -0
  7. data/lib/spoom/cli/srb/lsp.rb +159 -0
  8. data/lib/spoom/cli/srb/tc.rb +150 -0
  9. data/lib/spoom/cli/srb.rb +27 -0
  10. data/lib/spoom/cli.rb +72 -32
  11. data/lib/spoom/context/git.rb +2 -2
  12. data/lib/spoom/context/sorbet.rb +2 -2
  13. data/lib/spoom/deadcode/definition.rb +11 -0
  14. data/lib/spoom/deadcode/indexer.rb +222 -224
  15. data/lib/spoom/deadcode/location.rb +2 -2
  16. data/lib/spoom/deadcode/plugins/action_mailer.rb +2 -2
  17. data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +19 -0
  18. data/lib/spoom/deadcode/plugins/actionpack.rb +4 -6
  19. data/lib/spoom/deadcode/plugins/active_model.rb +8 -8
  20. data/lib/spoom/deadcode/plugins/active_record.rb +9 -12
  21. data/lib/spoom/deadcode/plugins/active_support.rb +11 -0
  22. data/lib/spoom/deadcode/plugins/base.rb +1 -1
  23. data/lib/spoom/deadcode/plugins/graphql.rb +4 -4
  24. data/lib/spoom/deadcode/plugins/namespaces.rb +2 -4
  25. data/lib/spoom/deadcode/plugins/ruby.rb +8 -17
  26. data/lib/spoom/deadcode/plugins/sorbet.rb +4 -10
  27. data/lib/spoom/deadcode/plugins.rb +1 -0
  28. data/lib/spoom/deadcode/remover.rb +209 -174
  29. data/lib/spoom/deadcode/send.rb +9 -10
  30. data/lib/spoom/deadcode/visitor.rb +755 -0
  31. data/lib/spoom/deadcode.rb +40 -10
  32. data/lib/spoom/file_tree.rb +0 -16
  33. data/lib/spoom/sorbet/errors.rb +1 -1
  34. data/lib/spoom/sorbet/lsp/structures.rb +2 -2
  35. data/lib/spoom/version.rb +1 -1
  36. metadata +19 -15
  37. data/lib/spoom/cli/bump.rb +0 -198
  38. data/lib/spoom/cli/coverage.rb +0 -222
  39. data/lib/spoom/cli/lsp.rb +0 -168
  40. data/lib/spoom/cli/run.rb +0 -148
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23df97e815cc01b35dbc960ec1921bbc1c65ae4c6f0588c690a0d9156877a013
4
- data.tar.gz: d82fdb3fb66404b0c2d98ea9206be9d86ec2284bbae2ff24a5653a521ec9155e
3
+ metadata.gz: 1993e4b43cf1c9d97475b654c049e8b14a27189561471b2bfb4bb077ac2f7b9e
4
+ data.tar.gz: ad6a269fe90e77df95564a13e222d58e9993c7ffef389c2be3a0e2fc7a1aed9a
5
5
  SHA512:
6
- metadata.gz: 67b6209ba61fbc285fc89dcf64adcde27cfecf44bb37d644cfb8aef50b7fca6e53a15e8eec5bc45030e4c838a35d3777c50a2d75fb32461792a1bc5957d7b2e3
7
- data.tar.gz: c3e06e5140aac671ff8489243dee9feba3912ec12681332b2abb37ddd5c367246dc180ea80f6207afe28f81f499626f4dcf4b9628baa263854600d19d593f2f5
6
+ metadata.gz: 8eab661cb5d9aa290bea6dba5ab3808ad4df7847213229616c4eb912faf012208da053bcb4019700e390f57ce156ff7852a9ef4b01ab48c819acc1453a62b8eb
7
+ data.tar.gz: 7f74c0655f781be1c9832d33dcc9a1cb088705f201998f431dae80a90dae259fe09a85cb0ffec1bd53369694b842c5bfe10a3b32ff25a9b1c61ee712fd934900
data/README.md CHANGED
@@ -31,13 +31,13 @@ Spoom can create a typing coverage report from Sorbet and Git data:
31
31
  After installing the `spoom` gem, run the `timeline` command to collect the history data:
32
32
 
33
33
  ```
34
- $ spoom coverage timeline --save
34
+ $ spoom srb coverage timeline --save
35
35
  ```
36
36
 
37
37
  Then create the HTML page with `report`:
38
38
 
39
39
  ```
40
- $ spoom coverage report
40
+ $ spoom srb coverage report
41
41
  ```
42
42
 
43
43
  Your report will be generated under `spoom_report.html`.
@@ -46,66 +46,48 @@ See all the [Typing Coverage](#typing-coverage) CLI commands for more details.
46
46
 
47
47
  ### Command Line Interface
48
48
 
49
- #### Sorbet configuration commands
50
-
51
- Spoom works with your `sorbet/config` file. No additional configuration is needed.
52
-
53
- Show Sorbet config options:
54
-
55
- ```
56
- $ spoom config
57
- ```
58
-
59
- #### Listing files
60
-
61
- List the files (and related strictness) that will be typchecked with the current Sorbet config options:
62
-
63
- ```
64
- $ spoom files
65
- ```
66
-
67
49
  #### Errors sorting and filtering
68
50
 
69
51
  List all typechecking errors sorted by location:
70
52
 
71
53
  ```
72
- $ spoom tc -s loc
54
+ $ spoom srb tc -s loc
73
55
  ```
74
56
 
75
57
  List all typechecking errors sorted by error code first:
76
58
 
77
59
  ```
78
- $ spoom tc -s code
60
+ $ spoom srb tc -s code
79
61
  ```
80
62
 
81
63
  List only typechecking errors from a specific error code:
82
64
 
83
65
  ```
84
- $ spoom tc -c 7004
66
+ $ spoom srb tc -c 7004
85
67
  ```
86
68
 
87
69
  List only the first 10 typechecking errors
88
70
 
89
71
  ```
90
- $ spoom tc -l 10
72
+ $ spoom srb tc -l 10
91
73
  ```
92
74
 
93
75
  These options can be combined:
94
76
 
95
77
  ```
96
- $ spoom tc -s -c 7004 -l 10
78
+ $ spoom srb tc -s -c 7004 -l 10
97
79
  ```
98
80
 
99
81
  Remove duplicated error lines:
100
82
 
101
83
  ```
102
- $ spoom tc -u
84
+ $ spoom srb tc -u
103
85
  ```
104
86
 
105
87
  Format each error line:
106
88
 
107
89
  ```
108
- $ spoom tc -f '%C - %F:%L: %M'
90
+ $ spoom srb tc -f '%C - %F:%L: %M'
109
91
  ```
110
92
 
111
93
  Where:
@@ -118,13 +100,13 @@ Where:
118
100
  Hide the `Errors: X` at the end of the list:
119
101
 
120
102
  ```
121
- $ spoom tc --no-count
103
+ $ spoom srb tc --no-count
122
104
  ```
123
105
 
124
- List only the errors comming from specific directories or files:
106
+ List only the errors coming from specific directories or files:
125
107
 
126
108
  ```
127
- $ spoom tc file1.rb path1/ path2/
109
+ $ spoom srb tc file1.rb path1/ path2/
128
110
  ```
129
111
 
130
112
  #### Typing coverage
@@ -132,61 +114,61 @@ $ spoom tc file1.rb path1/ path2/
132
114
  Show metrics about the project contents and the typing coverage:
133
115
 
134
116
  ```
135
- $ spoom coverage
117
+ $ spoom srb coverage
136
118
  ```
137
119
 
138
120
  Save coverage data under `spoom_data/`:
139
121
 
140
122
  ```
141
- $ spoom coverage --save
123
+ $ spoom srb coverage --save
142
124
  ```
143
125
 
144
126
  Save coverage data under a specific directory:
145
127
 
146
128
  ```
147
- $ spoom coverage --save my_data/
129
+ $ spoom srb coverage --save my_data/
148
130
  ```
149
131
 
150
132
  Show typing coverage evolution based on the commits history:
151
133
 
152
134
  ```
153
- $ spoom coverage timeline
135
+ $ spoom srb coverage timeline
154
136
  ```
155
137
 
156
138
  Show typing coverage evolution based on the commits history between specific dates:
157
139
 
158
140
  ```
159
- $ spoom coverage timeline --from YYYY-MM-DD --to YYYY-MM-DD
141
+ $ spoom srb coverage timeline --from YYYY-MM-DD --to YYYY-MM-DD
160
142
  ```
161
143
 
162
144
  Save the typing coverage evolution as JSON under `spoom_data/`:
163
145
 
164
146
  ```
165
- $ spoom coverage timeline --save
147
+ $ spoom srb coverage timeline --save
166
148
  ```
167
149
 
168
150
  Save the typing coverage evolution as JSON in a specific directory:
169
151
 
170
152
  ```
171
- $ spoom coverage timeline --save my_data/
153
+ $ spoom srb coverage timeline --save my_data/
172
154
  ```
173
155
 
174
156
  Run `bundle install` for each commit of the timeline (may solve errors due to different Sorbet versions):
175
157
 
176
158
  ```
177
- $ spoom coverage timeline --bundle-install
159
+ $ spoom srb coverage timeline --bundle-install
178
160
  ```
179
161
 
180
162
  Generate an HTML typing coverage report:
181
163
 
182
164
  ```
183
- $ spoom coverage report
165
+ $ spoom srb coverage report
184
166
  ```
185
167
 
186
168
  Change the colors used for strictnesses (useful for colorblind folks):
187
169
 
188
170
  ```
189
- $ spoom coverage report \
171
+ $ spoom srb coverage report \
190
172
  --color-true "#648ffe" \
191
173
  --color-false "#fe6002" \
192
174
  --color-ignore "#feb000" \
@@ -197,7 +179,7 @@ $ spoom coverage report \
197
179
  Open the HTML typing coverage report:
198
180
 
199
181
  ```
200
- $ spoom coverage open
182
+ $ spoom srb coverage open
201
183
  ```
202
184
 
203
185
  #### Change the sigil used in files
@@ -205,37 +187,38 @@ $ spoom coverage open
205
187
  Bump the strictness from all files currently at `typed: false` to `typed: true` where it does not create typechecking errors:
206
188
 
207
189
  ```
208
- $ spoom bump --from false --to true
190
+ $ spoom srb bump --from false --to true
209
191
  ```
210
192
 
211
193
  Bump the strictness from all files currently at `typed: false` to `typed: true` even if it creates typechecking errors:
212
194
 
213
195
  ```
214
- $ spoom bump --from false --to true -f
196
+ $ spoom srb bump --from false --to true -f
215
197
  ```
216
198
 
217
199
  Bump the strictness from a list of files (one file by line):
218
200
 
219
201
  ```
220
- $ spoom bump --from false --to true -o list.txt
202
+ $ spoom srb bump --from false --to true -o list.txt
221
203
  ```
222
204
 
223
- Check if files can be bumped without applying any change:
205
+ Check if files can be bumped without applying any change and show the list of files that can be bumped without errors.
206
+ Will exit with a non-zero status if some files can be bumped without errors (useful to check for bumpable files on CI for example):
224
207
 
225
208
  ```
226
- $ spoom bump --from false --to true --dry
209
+ $ spoom srb bump --from false --to true --dry
227
210
  ```
228
211
 
229
212
  Bump files using a custom instance of Sorbet:
230
213
 
231
214
  ```
232
- $ spoom bump --from false --to true --sorbet /path/to/sorbet/bin
215
+ $ spoom srb bump --from false --to true --sorbet /path/to/sorbet/bin
233
216
  ```
234
217
 
235
218
  Count the number of type-checking errors if all files were bumped to true:
236
219
 
237
220
  ```
238
- $ spoom bump --count-errors --dry
221
+ $ spoom srb bump --count-errors --dry
239
222
  ```
240
223
 
241
224
  #### Interact with Sorbet LSP mode
@@ -245,43 +228,43 @@ $ spoom bump --count-errors --dry
245
228
  Find all definitions for `Foo`:
246
229
 
247
230
  ```
248
- $ spoom lsp find Foo
231
+ $ spoom srb lsp find Foo
249
232
  ```
250
233
 
251
234
  List all symbols in a file:
252
235
 
253
236
  ```
254
- $ spoom lsp symbols <file.rb>
237
+ $ spoom srb lsp symbols <file.rb>
255
238
  ```
256
239
 
257
240
  List all definitions for a specific code location:
258
241
 
259
242
  ```
260
- $ spoom lsp defs <file.rb> <line> <column>
243
+ $ spoom srb lsp defs <file.rb> <line> <column>
261
244
  ```
262
245
 
263
246
  List all references for a specific code location:
264
247
 
265
248
  ```
266
- $ spoom lsp refs <file.rb> <line> <column>
249
+ $ spoom srb lsp refs <file.rb> <line> <column>
267
250
  ```
268
251
 
269
252
  Show hover information for a specific code location:
270
253
 
271
254
  ```
272
- $ spoom lsp hover <file.rb> <line> <column>
255
+ $ spoom srb lsp hover <file.rb> <line> <column>
273
256
  ```
274
257
 
275
258
  Show signature information for a specific code location:
276
259
 
277
260
  ```
278
- $ spoom lsp sig <file.rb> <line> <column>
261
+ $ spoom srb lsp sig <file.rb> <line> <column>
279
262
  ```
280
263
 
281
264
  Show type information for a specific code location:
282
265
 
283
266
  ```
284
- $ spoom lsp sig <file.rb> <line> <column>
267
+ $ spoom srb lsp sig <file.rb> <line> <column>
285
268
  ```
286
269
 
287
270
  ### API
@@ -358,6 +341,22 @@ require "spoom/backtrace_filter/minitest"
358
341
  Minitest.backtrace_filter = Spoom::BacktraceFilter::Minitest.new
359
342
  ```
360
343
 
344
+ ### Dead code removal
345
+
346
+ Run dead code detection in your project with:
347
+
348
+ ```
349
+ $ spoom deadcode
350
+ ```
351
+
352
+ This will list all the methods and constants that do not appear to be used in your project.
353
+
354
+ You can remove them with Spoom:
355
+
356
+ ```
357
+ $ spoom deadcode remove path/to/file.rb:42:18-47:23
358
+ ```
359
+
361
360
  ## Development
362
361
 
363
362
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. Don't forget to run `bin/sanity` before pushing your changes.
@@ -0,0 +1,172 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../deadcode"
5
+
6
+ module Spoom
7
+ module Cli
8
+ class Deadcode < Thor
9
+ extend T::Sig
10
+ include Helper
11
+
12
+ default_task :deadcode
13
+
14
+ desc "deadcode PATH...", "Analyze PATHS to find dead code"
15
+ option :allowed_extensions,
16
+ type: :array,
17
+ default: [".rb", ".erb", ".gemspec"],
18
+ aliases: :e,
19
+ desc: "Allowed extensions"
20
+ option :allowed_mime_types,
21
+ type: :array,
22
+ default: ["text/x-ruby", "text/x-ruby-script"],
23
+ aliases: :m,
24
+ desc: "Allowed mime types"
25
+ option :exclude,
26
+ type: :array,
27
+ default: ["vendor/", "sorbet/"],
28
+ aliases: :x,
29
+ desc: "Exclude paths"
30
+ option :show_files,
31
+ type: :boolean,
32
+ default: false,
33
+ desc: "Show the files that will be analyzed"
34
+ option :show_plugins,
35
+ type: :boolean,
36
+ default: false,
37
+ desc: "Show the loaded plugins"
38
+ option :show_defs,
39
+ type: :boolean,
40
+ default: false,
41
+ desc: "Show the indexed definitions"
42
+ option :show_refs,
43
+ type: :boolean,
44
+ default: false,
45
+ desc: "Show the indexed references"
46
+ option :sort,
47
+ type: :string,
48
+ default: "name",
49
+ enum: ["name", "location"],
50
+ desc: "Sort the output by name or location"
51
+ sig { params(paths: String).void }
52
+ def deadcode(*paths)
53
+ context = self.context
54
+
55
+ paths << exec_path if paths.empty?
56
+
57
+ $stderr.puts "Collecting files..."
58
+ collector = FileCollector.new(
59
+ allow_extensions: options[:allowed_extensions],
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 },
62
+ )
63
+ collector.visit_paths(paths)
64
+ files = collector.files.sort
65
+
66
+ if options[:show_files]
67
+ $stderr.puts "\nCollected #{blue(files.size.to_s)} files for analysis\n"
68
+ files.each do |file|
69
+ $stderr.puts " #{gray(file)}"
70
+ end
71
+ $stderr.puts
72
+ end
73
+
74
+ plugins = Spoom::Deadcode.plugins_from_gemfile_lock(context)
75
+ 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
+ end
80
+ $stderr.puts
81
+ end
82
+
83
+ index = Spoom::Deadcode::Index.new
84
+
85
+ $stderr.puts "Indexing #{blue(files.size.to_s)} files..."
86
+ 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
93
+ say_error("Error parsing #{file}: #{e.message}")
94
+ next
95
+ rescue Spoom::Deadcode::IndexerError => e
96
+ say_error("Error indexing #{file}: #{e.message}")
97
+ next
98
+ end
99
+
100
+ if options[:show_defs]
101
+ $stderr.puts "\nDefinitions:"
102
+ index.definitions.each do |name, definitions|
103
+ $stderr.puts " #{blue(name)}"
104
+ definitions.each do |definition|
105
+ $stderr.puts " #{yellow(definition.kind.serialize)} #{gray(definition.location.to_s)}"
106
+ end
107
+ end
108
+ $stderr.puts
109
+ end
110
+
111
+ if options[:show_refs]
112
+ $stderr.puts "\nReferences:"
113
+ index.references.values.flatten.sort_by(&:name).each do |references|
114
+ name = references.name
115
+ kind = references.kind.serialize
116
+ loc = references.location.to_s
117
+ $stderr.puts " #{blue(name)} #{yellow(kind)} #{gray(loc)}"
118
+ end
119
+ $stderr.puts
120
+ end
121
+
122
+ definitions_count = index.definitions.size.to_s
123
+ references_count = index.references.size.to_s
124
+ $stderr.puts "Analyzing #{blue(definitions_count)} definitions against #{blue(references_count)} references..."
125
+
126
+ index.finalize!
127
+ dead = index.definitions.values.flatten.select(&:dead?)
128
+
129
+ if options[:sort] == "name"
130
+ dead.sort_by!(&:name)
131
+ else
132
+ dead.sort_by!(&:location)
133
+ end
134
+
135
+ if dead.empty?
136
+ $stderr.puts "\n#{green("No dead code found!")}"
137
+ else
138
+ $stderr.puts "\nCandidates:"
139
+ dead.each do |definition|
140
+ $stderr.puts " #{red(definition.full_name)} #{gray(definition.location.to_s)}"
141
+ end
142
+ $stderr.puts "\n"
143
+ $stderr.puts red(" Found #{dead.size} dead candidates")
144
+
145
+ exit(1)
146
+ end
147
+ end
148
+
149
+ desc "remove LOCATION", "Remove dead code at LOCATION"
150
+ def remove(location_string)
151
+ location = Spoom::Deadcode::Location.from_string(location_string)
152
+ context = self.context
153
+ remover = Spoom::Deadcode::Remover.new(context)
154
+
155
+ new_source = remover.remove_location(nil, location)
156
+ context.write!("PATCH", new_source)
157
+
158
+ diff = context.exec("diff -u #{location.file} PATCH")
159
+ $stderr.puts T.must(diff.out.lines[2..-1]).join
160
+ context.remove!("PATCH")
161
+
162
+ context.write!(location.file, new_source)
163
+ rescue Spoom::Deadcode::Remover::Error => e
164
+ say_error("Can't remove code at #{location_string}: #{e.message}")
165
+ exit(1)
166
+ rescue Spoom::Deadcode::Location::LocationError => e
167
+ say_error(e.message)
168
+ exit(1)
169
+ end
170
+ end
171
+ end
172
+ end
@@ -46,6 +46,26 @@ module Spoom
46
46
  $stderr.flush
47
47
  end
48
48
 
49
+ # Print `message` on `$stderr`
50
+ #
51
+ # The message is prefixed by a status (default: `Warning`).
52
+ sig do
53
+ params(
54
+ message: String,
55
+ status: T.nilable(String),
56
+ nl: T::Boolean,
57
+ ).void
58
+ end
59
+ def say_warning(message, status: "Warning", nl: true)
60
+ buffer = StringIO.new
61
+ buffer << "#{yellow(status)}: " if status
62
+ buffer << highlight(message)
63
+ buffer << "\n" if nl && !message.end_with?("\n")
64
+
65
+ $stderr.print(buffer.string)
66
+ $stderr.flush
67
+ end
68
+
49
69
  # Returns the context at `--path` (by default the current working directory)
50
70
  sig { returns(Context) }
51
71
  def context