leftovers 0.1.0

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 (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)