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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.leftovers.yml +19 -0
- data/.rspec +3 -0
- data/.rubocop.yml +216 -0
- data/.ruby-version +1 -0
- data/.spellr.yml +13 -0
- data/.spellr_wordlists/english.txt +21 -0
- data/.spellr_wordlists/lorem.txt +1 -0
- data/.spellr_wordlists/ruby.txt +67 -0
- data/.spellr_wordlists/shell.txt +3 -0
- data/.travis.yml +10 -0
- data/Configuration.md +427 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +172 -0
- data/Rakefile +16 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/leftovers +6 -0
- data/leftovers.gemspec +42 -0
- data/lib/config/attr_encrypted.yml +16 -0
- data/lib/config/builder.yml +3 -0
- data/lib/config/capistrano.yml +3 -0
- data/lib/config/datagrid.yml +9 -0
- data/lib/config/flipper.yml +3 -0
- data/lib/config/graphql.yml +20 -0
- data/lib/config/guard.yml +3 -0
- data/lib/config/haml.yml +2 -0
- data/lib/config/jbuilder.yml +2 -0
- data/lib/config/okcomputer.yml +3 -0
- data/lib/config/parser.yml +91 -0
- data/lib/config/pry.yml +3 -0
- data/lib/config/rack.yml +2 -0
- data/lib/config/rails.yml +387 -0
- data/lib/config/rake.yml +5 -0
- data/lib/config/redcarpet.yml +38 -0
- data/lib/config/rollbar.yml +3 -0
- data/lib/config/rspec.yml +56 -0
- data/lib/config/ruby.yml +77 -0
- data/lib/config/selenium.yml +21 -0
- data/lib/config/simplecov.yml +2 -0
- data/lib/config/will_paginate.yml +14 -0
- data/lib/leftovers/argument_rule.rb +216 -0
- data/lib/leftovers/backports.rb +56 -0
- data/lib/leftovers/cli.rb +50 -0
- data/lib/leftovers/collector.rb +67 -0
- data/lib/leftovers/config.rb +53 -0
- data/lib/leftovers/core_ext.rb +32 -0
- data/lib/leftovers/definition.rb +70 -0
- data/lib/leftovers/definition_set.rb +44 -0
- data/lib/leftovers/erb.rb +20 -0
- data/lib/leftovers/file.rb +30 -0
- data/lib/leftovers/file_collector.rb +219 -0
- data/lib/leftovers/file_list.rb +24 -0
- data/lib/leftovers/haml.rb +24 -0
- data/lib/leftovers/hash_rule.rb +40 -0
- data/lib/leftovers/merged_config.rb +71 -0
- data/lib/leftovers/name_rule.rb +53 -0
- data/lib/leftovers/node.rb +182 -0
- data/lib/leftovers/rake_task.rb +62 -0
- data/lib/leftovers/reporter.rb +11 -0
- data/lib/leftovers/rule.rb +74 -0
- data/lib/leftovers/transform_rule.rb +171 -0
- data/lib/leftovers/value_rule.rb +56 -0
- data/lib/leftovers/version.rb +5 -0
- data/lib/leftovers.rb +127 -0
- 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
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
|
+
[](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