leftovers 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![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