leftovers 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.leftovers.yml +19 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +216 -0
  6. data/.ruby-version +1 -0
  7. data/.spellr.yml +13 -0
  8. data/.spellr_wordlists/english.txt +21 -0
  9. data/.spellr_wordlists/lorem.txt +1 -0
  10. data/.spellr_wordlists/ruby.txt +67 -0
  11. data/.spellr_wordlists/shell.txt +3 -0
  12. data/.travis.yml +10 -0
  13. data/Configuration.md +427 -0
  14. data/Gemfile +6 -0
  15. data/LICENSE.txt +21 -0
  16. data/README.md +172 -0
  17. data/Rakefile +16 -0
  18. data/bin/console +15 -0
  19. data/bin/setup +8 -0
  20. data/exe/leftovers +6 -0
  21. data/leftovers.gemspec +42 -0
  22. data/lib/config/attr_encrypted.yml +16 -0
  23. data/lib/config/builder.yml +3 -0
  24. data/lib/config/capistrano.yml +3 -0
  25. data/lib/config/datagrid.yml +9 -0
  26. data/lib/config/flipper.yml +3 -0
  27. data/lib/config/graphql.yml +20 -0
  28. data/lib/config/guard.yml +3 -0
  29. data/lib/config/haml.yml +2 -0
  30. data/lib/config/jbuilder.yml +2 -0
  31. data/lib/config/okcomputer.yml +3 -0
  32. data/lib/config/parser.yml +91 -0
  33. data/lib/config/pry.yml +3 -0
  34. data/lib/config/rack.yml +2 -0
  35. data/lib/config/rails.yml +387 -0
  36. data/lib/config/rake.yml +5 -0
  37. data/lib/config/redcarpet.yml +38 -0
  38. data/lib/config/rollbar.yml +3 -0
  39. data/lib/config/rspec.yml +56 -0
  40. data/lib/config/ruby.yml +77 -0
  41. data/lib/config/selenium.yml +21 -0
  42. data/lib/config/simplecov.yml +2 -0
  43. data/lib/config/will_paginate.yml +14 -0
  44. data/lib/leftovers/argument_rule.rb +216 -0
  45. data/lib/leftovers/backports.rb +56 -0
  46. data/lib/leftovers/cli.rb +50 -0
  47. data/lib/leftovers/collector.rb +67 -0
  48. data/lib/leftovers/config.rb +53 -0
  49. data/lib/leftovers/core_ext.rb +32 -0
  50. data/lib/leftovers/definition.rb +70 -0
  51. data/lib/leftovers/definition_set.rb +44 -0
  52. data/lib/leftovers/erb.rb +20 -0
  53. data/lib/leftovers/file.rb +30 -0
  54. data/lib/leftovers/file_collector.rb +219 -0
  55. data/lib/leftovers/file_list.rb +24 -0
  56. data/lib/leftovers/haml.rb +24 -0
  57. data/lib/leftovers/hash_rule.rb +40 -0
  58. data/lib/leftovers/merged_config.rb +71 -0
  59. data/lib/leftovers/name_rule.rb +53 -0
  60. data/lib/leftovers/node.rb +182 -0
  61. data/lib/leftovers/rake_task.rb +62 -0
  62. data/lib/leftovers/reporter.rb +11 -0
  63. data/lib/leftovers/rule.rb +74 -0
  64. data/lib/leftovers/transform_rule.rb +171 -0
  65. data/lib/leftovers/value_rule.rb +56 -0
  66. data/lib/leftovers/version.rb +5 -0
  67. data/lib/leftovers.rb +127 -0
  68. metadata +281 -0
data/Configuration.md ADDED
@@ -0,0 +1,427 @@
1
+ # Configuration
2
+
3
+ The configuration is read from `.leftovers.yml` in your project root.
4
+ Its presence is optional and all of these settings are optional:
5
+
6
+ see the [built in config files](https://github.com/robotdana/leftovers/tree/master/lib/config) for examples.
7
+
8
+ - [`include_paths:`](#include_paths)
9
+ - [`exclude_paths:`](#exclude_paths)
10
+ - [`test_paths:`](#test_paths)
11
+ - [`gems:`](#gems)
12
+ - [`rules:`](#rules)
13
+ - [`names:`](#names)
14
+ - [`has_prefix](#has_prefix-has_suffix)
15
+ - [`has_suffix](#has_prefix-has_suffix)
16
+ - [`matches](#matches)
17
+ - [`paths:`](#paths)
18
+ - [`skip:`](#skip)
19
+ - [`calls:`](#calls-defines), [`defines:`](#calls-defines)
20
+ - [`arguments:`](#arguments), [`keys:`](#keys-), [`itself:`](#itself-true)
21
+ - [`transforms:`](#transforms), [`linked_transforms:`](#linked_transforms)
22
+ - `original:`, `add_prefix:`, `add_suffix:`, `delete_prefix:`, `delete_suffix:`, `replace_with:`
23
+ - `delete_before:`, `delete_after:`, `downcase:`, `upcase:`, `capitalize:`, `swapcase:`
24
+ - `pluralize`, `singularize`, `camelize`, `underscore`, `demodulize`, `deconstantize`
25
+ - [`if:`](#if-unless), [`unless:`](#if-unless)
26
+ - [`has_argument:`](#has_argument)
27
+ - `keyword:`
28
+ - [`has_prefix:`](#has_prefix-has_suffix)
29
+ - [`has_suffix:`](#has_prefix-has_suffix)
30
+ - [`matches:`](#matches)
31
+ - `value:`
32
+ - [`has_prefix:`](#has_prefix-has_suffix)
33
+ - [`has_suffix:`](#has_prefix-has_suffix)
34
+ - [`matches:`](#matches)
35
+ - `type:`
36
+
37
+
38
+ ## `include_paths:`
39
+
40
+ List filenames/paths in the gitignore format of files to be checked using a [gitignore-esque format](https://github.com/robotdana/fast_ignore#using-an-includes-list).
41
+
42
+ ```yml
43
+ include_paths:
44
+ - '*.rb'
45
+ - '*.rake'
46
+ - Gemfile
47
+ ```
48
+
49
+ Also it will check files with no extension that have `ruby` in the shebang/hashbang, e.g. `#!/usr/bin/env ruby` or `#!/usr/bin/ruby` etc
50
+
51
+ ## `exclude_paths:`
52
+
53
+ List filenames/paths that match the above that you might want to exclude, using the gitignore format.
54
+ By default it will also read your project's .gitignore file and ignore anything there.
55
+
56
+ ```yml
57
+ exclude_paths:
58
+ - /some/long/irrelevant/generated/file
59
+ ```
60
+
61
+ ## `test_paths:`
62
+
63
+ list filenames/paths of test directories that will be used to determine if a method/etc is only tested but not otherwise used.
64
+ Also in the gitignore format
65
+
66
+ ```yml
67
+ test_paths:
68
+ - /test/
69
+ - /spec/
70
+ ```
71
+
72
+ ## `gems:`
73
+
74
+ By default Leftovers will look at your Gemfile.lock file to find all gem dependencies
75
+
76
+ If you don't use bundler, or don't have a Gemfile.lock for some reason, you can still take advantage of the built in handling for certain gems
77
+ ```yml
78
+ gems:
79
+ - rspec
80
+ - rails
81
+ ```
82
+
83
+ ## `rules:`
84
+
85
+ This is the most complex part of configuration, and is a list of methods that define/call other methods/classes/etc.
86
+ Each must have a list of `names:`. and can optionally be limited to a list of `paths:`.
87
+
88
+ This rule can either `skip:` these names, or describe method/class `calls:` and definitions (`defines:`).
89
+
90
+ ### `names:`
91
+ **required**
92
+
93
+ _alias `name:`_
94
+
95
+ list methods/classnames/etc that this rule applies to.
96
+
97
+ ```yml
98
+ rules:
99
+ - names:
100
+ - initialize
101
+ - ClassName
102
+ skip: true
103
+ - name: respond_to_missing?
104
+ skip: true
105
+ ```
106
+
107
+ #### `has_prefix:`, `has_suffix:`
108
+
109
+ To match names other than exact strings, you can use has_suffix or has_prefix or both if you're feeling fancy.
110
+
111
+ ```yml
112
+ rules:
113
+ - names:
114
+ - has_suffix: Helper # will match Helper, LinkHelper FormHelper, etc
115
+ - has_prefix: be_ # will match be_equal, be_invalid, etc
116
+ - { has_prefix: is_, has_suffix: '?' } # will match is_invalid?, is_equal? etc
117
+ skip: true
118
+ ```
119
+
120
+
121
+ #### `matches:`
122
+
123
+ if `has_suffix:` and `has_prefix:` isn't enough, you can use `matches:` to supply a regexp.
124
+ This string is automatically converted into a ruby regexp and must match the whole method/constant name.
125
+ ```yml
126
+ rules:
127
+ - names:
128
+ - matches: 'column_\d+' # will match column_1, column_2, column_99, but not column_left etc
129
+ skip: true
130
+ ```
131
+
132
+ ### `paths:`
133
+ _alias `path:`_
134
+
135
+ An optional list of paths that limits what paths this method rule can apply to, defined using a .gitignore-esque format
136
+
137
+ ```yml
138
+ rules:
139
+ - name:
140
+ - has_suffix: Helper
141
+ path: /app/helpers
142
+ skip: true
143
+ ```
144
+
145
+ ### `skip:`
146
+
147
+ Skip methods that are called on your behalf by code outside your project, or called dynamically using send with variables.
148
+
149
+ You can also skip method calls and definitions in place using [magic comments](https://github.com/robotdana/leftovers/tree/master/README.md#magic-comments).
150
+
151
+ ```yml
152
+ rules:
153
+ - name: initialize
154
+ skip: true
155
+ ```
156
+ ```ruby
157
+ def initialize
158
+ end
159
+ ```
160
+
161
+ will not report that you didn't directly use the initialize method
162
+
163
+ ### `calls:`, `defines:`
164
+ _aliases `call:`, `define:`_
165
+ Describe implicitly called and defined methods using these keys. they're structured the same way:
166
+
167
+ It must have at least one of `argument:`, `itself: true`, or `keys: '*'` which points to the implied method definition/call.
168
+ This value must be a literal string, symbol, or array of strings or symbols.
169
+
170
+ #### `arguments:`
171
+ _alias `argument:`_
172
+
173
+ the position or keyword of the value or values being implicitly called/defined.
174
+ it can be a single argument or a list.
175
+
176
+ This value must be itself a literal String, Symbol, or Array or Hash whose values are Strings and Symbols or more nested Arrays and Hashes.
177
+ Variables and other method calls returning values will be ignored.
178
+
179
+ `*` means all positional arguments values. `**` means all keyword arguments values.
180
+ Positional arguments start at 1.
181
+
182
+ ```yml
183
+ rules:
184
+ # `send(:my_method, arg)` is equivalent to `my_method(arg)`
185
+ - name: send
186
+ calls:
187
+ argument: 1
188
+ ```
189
+ ```ruby
190
+ user.send(:my_private_method, true)
191
+ ```
192
+ will count as a call to `my_private_method`.
193
+
194
+ ```yml
195
+ - name: attr_reader
196
+ defines:
197
+ arguments: '*'
198
+ ```
199
+ ```ruby
200
+ attr_reader :my_attr, :my_other_attr
201
+ ```
202
+ will count as a definition of `my_attr` and `my_other_attr` and will need to be used elsewhere or they'll be reported as leftovers.
203
+
204
+ ```yml
205
+ rules:
206
+ - name: validate
207
+ calls:
208
+ - arguments: ['*', if, unless]
209
+ ```
210
+ ```ruby
211
+ validate :does_not_match_existing_record, if: :new_record?
212
+ ```
213
+ will count as a call to `does_not_match_existing_record`, and `new_record?`
214
+
215
+ ##### Constant assignment.
216
+
217
+ In addition to method call arguments, this can be used for constant assignment, as often constant assignment plus class_eval/instance_eval/define_method is used to dry up similar methods.
218
+ ```yml
219
+ rules:
220
+ - name: METHOD_NAMES
221
+ defines:
222
+ - arguments: 1
223
+ add_suffix: _attributes
224
+ calls:
225
+ - arguments: 1
226
+ ```
227
+ ```ruby
228
+ METHOD_NAMES = %w{user account}.freeze
229
+ METHOD_NAMES.each do |method_name|
230
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
231
+ def #{method}_attributes
232
+ self.#{method}
233
+ end
234
+ end
235
+ end
236
+ ```
237
+ counts as a definition of `user_attributes` and `account_attributes` and calls to `user` and `account`
238
+
239
+ use `arguments: '**'`, and or `keys: true` for assigning hashes
240
+ ```yml
241
+ rules:
242
+ - name: METHOD_NAMES
243
+ defines:
244
+ - keys: '*'
245
+ add_prefix: setup_
246
+ calls:
247
+ - arguments: '**'
248
+ add_prefix: build_
249
+ ```
250
+ ```ruby
251
+ METHOD_NAMES = { user: :login, account: :profile }
252
+ METHOD_NAMES.each do |method_name|
253
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
254
+ def setup_#{method}
255
+ build_#{method}
256
+ end
257
+ end
258
+ end
259
+ ```
260
+ Would count as defining `setup_user`, and `setup_account` and calling `build_login` and `build_profile`
261
+
262
+ #### `itself: true`
263
+
264
+ Will supply the method/constant name itself as the thing to be transformed.
265
+ The original method/constant name will continue to be defined as normal
266
+ ```yml
267
+ - name:
268
+ has_prefix: be_
269
+ calls:
270
+ itself: true
271
+ delete_prefix: be_
272
+ add_suffix: '?'
273
+ ```
274
+ ```ruby
275
+ expect(value).to be_empty
276
+ ```
277
+ will count `be_empty` as a call to `empty?`
278
+
279
+ #### `keys: '*'`
280
+ When the keyword argument **keywords** are the thing being called.
281
+
282
+ ```yml
283
+ rules:
284
+ - name: validates
285
+ calls:
286
+ - arguments: '*'
287
+ - keys: '*'
288
+ add_suffix: Validator
289
+ activesupport: camelize
290
+ ```
291
+ ```ruby
292
+ validates :first_name, :surname, presence: true
293
+ ```
294
+ will count calls for `validates`, `first_name`, `surname`, and `PresenceValidator`
295
+
296
+ #### `transforms:`
297
+
298
+ Sometimes the method being called is modified from the literal argument, sometimes that's just appending an `=` and sometimes it's more complex:
299
+
300
+ ```yml
301
+ - name: attribute
302
+ defines:
303
+ - argument: 1
304
+ transforms:
305
+ - original # no transformation
306
+ - add_suffix: '?'
307
+ - add_suffix: '='
308
+ ```
309
+ ```ruby
310
+ attribute :first_name
311
+ ```
312
+ will count as a definition of `first_name`, `first_name=` and `first_name?`
313
+
314
+ If there is just one transform for the arguments you can remove the `transforms:` keyword and move everything up a level.
315
+
316
+ ```yml
317
+ - name: attr_writer
318
+ defines:
319
+ - argument: '*'
320
+ add_suffix: '='
321
+ ```
322
+ ```ruby
323
+ attr_writer :first_name, :surname
324
+ ```
325
+ will count as the definition of `first_name=`, and `surname=`
326
+
327
+ | transform | examples | effect |
328
+ | --- | --- | --- |
329
+ |original| `original`, `original: true` | no change. useful when grouped with other transforms |
330
+ |add_prefix| `add_prefix: be_`, `add_prefix: { from_argument: 'to', joiner: '_' }` | adds the prefix string. possibly from another attribute (used for rails' delegate). |
331
+ |add_suffix| `add_suffix: '?'`, `add_suffix: Validator` | adds the suffix string. possibly from another attribute. |
332
+ |delete_prefix| `delete_prefix: be_` | removes the prefix string. |
333
+ |delete_suffix| `delete_suffix: _html` | removes the suffix string. |
334
+ |delete_before| `delete_before: '#'` | removes everything up to and including the string. used for rails' routes. |
335
+ |delete_after| `delete_after: '#'` | removes everything after to and including the string. used for rails' routes. |
336
+ |replace_with| `replace_with: html` | replaces the original string, perhaps because it dynamically calls it |
337
+ |downcase| `downcase`, `downcase: true` | calls ruby's `String#downcase` |
338
+ |upcase| `upcase`, `upcase: true` | calls ruby's `String#upcase` |
339
+ |capitalize| `capitalize`, `capitalize: true` | calls ruby's `String#capitalize` |
340
+ |swapcase| `swapcase`, `swapcase: true` | calls ruby's `String#swapcase` |
341
+ |pluralize| `pluralize`, `pluralize: true` | calls activesupport's `String#pluralize` extension. Will try to load config/initializers/inflections.rb |
342
+ |singularize| `singularize`, `singularize: true` | calls activesupport's `String#singularize` extension. Will try to load config/initializers/inflections.rb |
343
+ |camelize| `camelize`, `camelize: true`, `camelcase`, `camelcase: true` | calls activesupport's `String#camelize` extension. Will try to load config/initializers/inflections.rb |
344
+ |underscore| `underscore`, `underscore: true` | calls activesupport's `String#underscore` extension. |
345
+ |demodulize| `demodulize`, `demodulize: true` | calls activesupport's `String#demodulize` extension. |
346
+ |deconstantize| `deconstantize`, `deconstantize: true` | calls activesupport's `String#deconstantize` extension. |
347
+ |titleize| `titleize`, `titleize: true`, `titlecase`, `titlecase: true` | calls activesupport's `String#titleize` extension. |
348
+ |parameterize| `parameterize`, `parameterize: true` | calls activesupport's `String#parameterize` extension. |
349
+
350
+ #### `linked_transforms:`
351
+
352
+ This is identical to `transforms:` except that a call to one of the defined methods counts as a call to them all.
353
+
354
+ ```yml
355
+ - name: attribute
356
+ defines:
357
+ - argument: 1
358
+ linked_transforms:
359
+ - original # no transformation
360
+ - add_suffix: '?'
361
+ - add_suffix: '='
362
+ ```
363
+ ```ruby
364
+ attribute :first_name
365
+
366
+ def initialize
367
+ self.first_name = 'dana'
368
+ end
369
+ ```
370
+ will count as a definition of `first_name`, `first_name=` and `first_name?`, and because of `linked_transforms` the call to `first_name=` also counts as a call to `first_name?` and `first_name`
371
+
372
+ #### `if:`, `unless:`
373
+
374
+ Sometimes what to do depends on other arguments than the ones looked at:
375
+ e.g. rails' `delegate` method has a `prefix:` argument of its own that is used when defining methods:
376
+
377
+ `if:` and `unless:` work the same way and can be given a list or single value of conditions. For a list, all conditions must be met.
378
+
379
+ ```yml
380
+ rules:
381
+ - name: field
382
+ calls:
383
+ - argument: 1
384
+ unless:
385
+ has_argument: method
386
+ - argument: method
387
+ ```
388
+ ```ruby
389
+ field :first_name
390
+ field :family_name, method: :surname
391
+ ```
392
+ would count calls to `first_name`, and `surname`, but not `family_name`
393
+
394
+ ##### `has_argument:`
395
+
396
+ has_argument can be given a keyword or list of keywords. or a list of patterns like name.
397
+ or to check either the exact value or type of value it can be given `keyword:` and `value:` which also match the patterns like `name:`.
398
+ instead of an exact value `value:` can be given a type or list of type.
399
+
400
+ This all comes together to give the most complex rule, rails delegate method.
401
+ ```yml
402
+ rules:
403
+ - name: delegate
404
+ defines:
405
+ - argument: '*'
406
+ if:
407
+ has_argument:
408
+ keyword: prefix
409
+ value: true # if the value of the prefix keyword argument is literal true value
410
+ add_prefix:
411
+ from_argument: to # use the value of the "to" keyword as the prefix
412
+ joiner: '_' # joining with _ to the original string
413
+ - argument: '*'
414
+ if: # if the method call has a prefix keyword argument that is not a literal true value
415
+ has_argument:
416
+ keyword: prefix
417
+ value:
418
+ type: [String, Symbol]
419
+ add_prefix:
420
+ from_argument: prefix # use the value of the "prefix" keyword as the prefix
421
+ joiner: '_' # joining with _ to the original string
422
+ calls:
423
+ - argument: to # consider the to argument called.
424
+ - argument: '*'
425
+ if:
426
+ has_argument: prefix # if there is a prefix, consider the original method called.
427
+ ```
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in leftovers.gemspec
6
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Dana Sherson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # Leftovers
2
+ [![travis](https://travis-ci.org/robotdana/leftovers.svg?branch=master)](https://travis-ci.org/robotdana/leftovers)
3
+
4
+ Find unused `methods`, `Classes`, `CONSTANTS`, `@instance_variables`, `@@class_variables`, and `$global_variables` in your ruby projects.
5
+
6
+ ## Why?
7
+
8
+ Code that never gets executed is code that you shouldn't need to maintain.
9
+
10
+ - Leftovers from refactoring
11
+ - Partially removed features
12
+ - Typos and THIS NEVER WOULD HAVE WORKED code
13
+ - Code that you only keep around because there are tests of it.
14
+
15
+ Leftovers will use static analysis to find these bits of code for you.
16
+
17
+ It's aware of how some gems call methods for you, including (still somewhat incomplete) support for rails.
18
+
19
+ ## Features
20
+
21
+ - Fully configurable handling of methods that call other methods with literal arguments, and constant assignment
22
+ - magic comments
23
+ - designed to be run as a CI step
24
+ - optimised for speed
25
+ - built in config for some gems. (please make PRs with more gems)
26
+
27
+ ## Installation
28
+
29
+ Add this line to your application's Gemfile:
30
+
31
+ ```ruby
32
+ gem 'leftovers'
33
+ ```
34
+
35
+ And then execute:
36
+
37
+ $ bundle
38
+
39
+ Or install it yourself as:
40
+
41
+ $ gem install leftovers
42
+
43
+ ## Usage
44
+
45
+ Run `leftovers` in your terminal in the root of your project.
46
+ This will output progress as it collects the calls/references and definitions in your project.
47
+ Then it will output any defined methods (or classes etc) which are not called.
48
+
49
+ ```
50
+ $ leftovers
51
+ checked 25 files, collected 2473 calls, 262 definitions
52
+ Only directly called in tests:
53
+ lib/hello_world.rb:10:6 tested_unused_method def tested_unused_method
54
+ lib/hello_world.rb:18:6 another_tested_unused_method def another_tested_unused_method
55
+ Not directly called at all:
56
+ lib/hello_world.rb:6:6 generated_method= attr_accessor :generated_method
57
+ lib/hello_world.rb:6:6 generated_method attr_accessor :generated_method
58
+ ```
59
+
60
+ ## Magic comments
61
+
62
+ ### `# leftovers:keep`
63
+ To mark a method definition as not unused, add the comment `# leftovers:keep` on the same line as the definition
64
+
65
+ ```ruby
66
+ class MyClass
67
+ def my_method # leftovers:keep
68
+ true
69
+ end
70
+ end
71
+ ```
72
+ This would report `MyClass` is unused, but not my_method
73
+ To do this for all definitions of this name, add the name with `skip: true` in the configuration file.
74
+
75
+ ### `# leftovers:test`
76
+
77
+ To mark a definition from a non-test dir, as intentionally only used by tests, use `leftovers:test`
78
+ ```ruby
79
+ # app/my_class.rb
80
+ class MyClass
81
+ def my_method # leftovers:test
82
+ true
83
+ end
84
+ end
85
+ ```
86
+ ```ruby
87
+ # spec/my_class_spec.rb
88
+ describe MyClass do
89
+ it { expect(subject.my_method).to be true }
90
+ end
91
+ ```
92
+
93
+ This would consider `my_method` to be used, even though it is only called by tests.
94
+
95
+ ### `# leftovers:call`
96
+ To mark a dynamic call that doesn't use literal values, use `leftovers:call` with the method name listed
97
+ ```ruby
98
+ method = [:puts, :warn].sample
99
+ send(method, 'text') # leftovers:call puts, warn
100
+ ```
101
+
102
+ This would consider `puts` and `warn` to both have been called
103
+
104
+ ## Configuration
105
+
106
+ The configuration is read from `.leftovers.yml` in your project root.
107
+ Its presence is optional and all of these settings are optional:
108
+
109
+ see the [complete config documentation](https://github.com/robotdana/leftovers/tree/master/Configuration.md) for details.
110
+ see the [built in config files](https://github.com/robotdana/leftovers/tree/master/lib/config) for examples.
111
+
112
+ - [`include_paths:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#include_paths)
113
+ - [`exclude_paths:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#exclude_paths)
114
+ - [`test_paths:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#test_paths)
115
+ - [`gems:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#gems)
116
+ - [`rules:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#rules)
117
+ - [`names:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#names)
118
+ - [`has_prefix:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#has_prefix-has_suffix)
119
+ - [`has_suffix:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#has_prefix-has_suffix)
120
+ - [`matches:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#matches)
121
+ - [`paths:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#paths)
122
+ - [`skip:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#skip)
123
+ - [`calls:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#calls-defines), [`defines:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#calls-defines)
124
+ - [`arguments:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#arguments), [`keys:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#keys-), [`itself:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#itself-true)
125
+ - [`transforms:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#transforms), [`linked_transforms:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#linked_transforms)
126
+ - `original:`, `add_prefix:`, `add_suffix:`, `delete_prefix:`, `delete_suffix:`, `replace_with:`
127
+ - `delete_before:`, `delete_after:`, `downcase:`, `upcase:`, `capitalize:`, `swapcase:`
128
+ - `pluralize`, `singularize`, `camelize`, `underscore`, `demodulize`, `deconstantize`
129
+ - [`if:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#if-unless), [`unless:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#if-unless)
130
+ - [`has_argument:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#has_argument)
131
+ - `keyword:`
132
+ - [`has_prefix:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#has_prefix-has_suffix)
133
+ - [`has_suffix:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#has_prefix-has_suffix)
134
+ - [`matches:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#matches)
135
+ - `value:`
136
+ - [`has_prefix:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#has_prefix-has_suffix)
137
+ - [`has_suffix:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#has_prefix-has_suffix)
138
+ - [`matches:`](https://github.com/robotdana/leftovers/tree/master/Configuration.md#matches)
139
+ - `type:`
140
+
141
+ ## Limitations
142
+
143
+ - Leftovers will report methods/constants you define that are called outside your code (perhaps by gems) as unused
144
+
145
+ Add these names to the `rules:` list with `skip: true` in the `.leftovers.yml` or add an inline comment with `# leftovers:allow my_method_name`
146
+ - Leftovers doesn't execute your code so isn't aware of dynamic calls to `send` (e.g. `send(variable_method_name)`). (it is aware of static calls (e.g. `send(:my_method_name)`), so using send to bypass method privacy is "fine")
147
+
148
+ Add the method/pattern to the `rules:` list with `skip: true` in the `.leftovers.yml`, or add an inline comment with the list of possibilities `# leftovers:call my_method_1, my_method_2`.
149
+ - Leftovers compares by name only, so multiple methods with the same name will count as used even if only one is.
150
+ - haml & erb line and column numbers will be wrong as the files have to be precompiled before checking.
151
+
152
+ ## Other tools
153
+
154
+ - [rubocop](https://github.com/rubocop-hq/rubocop) has a cop that will alert for unused local variables and method arguments, and a cop that will report unreachable code.
155
+ - [coverband](https://github.com/danmayer/coverband) will report which methods are _actually_ called by your _actual_ production code
156
+
157
+ ## Development
158
+
159
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
160
+
161
+ To install this gem onto your local machine, run `bundle exec rake install`.
162
+
163
+ ## Contributing
164
+
165
+ Bug reports and pull requests are welcome on GitHub at https://github.com/robotdana/leftovers.
166
+
167
+ I especially encourage issues and improvements to the default config, whether expanding the existing config/*.yml (rails.yml is particularly incomplete) or adding new gems.
168
+ The file should be named `[rubygems name].yml` and its structure is identical to the [project config](#configuration)
169
+
170
+ ## License
171
+
172
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'bundler/gem_tasks'
5
+ require 'rspec/core/rake_task'
6
+ require 'rubocop/rake_task'
7
+ require 'spellr/rake_task'
8
+ require_relative 'lib/leftovers/rake_task'
9
+
10
+ # RuboCop::RakeTask.new
11
+ RSpec::Core::RakeTask.new(:spec)
12
+ Spellr::RakeTask.generate_task
13
+ Leftovers::RakeTask.generate_task(:leftovers, '--no-progress')
14
+
15
+ # rubocop is misbehaving currently
16
+ task default: %i{spec spellr leftovers}
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'leftovers'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/leftovers ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/leftovers/cli'
5
+
6
+ Leftovers::CLI.new(argv: ARGV, stdout: $stdout, stderr: $stderr)