kaizo 0.7.0 → 0.9.2
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 +4 -4
- data/CHANGELOG.md +119 -0
- data/README.md +332 -259
- data/config/default.yml +112 -8
- data/lib/kaizo/version.rb +1 -1
- data/lib/rubocop/cop/kaizo/agent_noun_class_name.rb +24 -1
- data/lib/rubocop/cop/kaizo/argument_counting.rb +46 -6
- data/lib/rubocop/cop/kaizo/explicit_begin.rb +9 -0
- data/lib/rubocop/cop/kaizo/file_utils_inclusion.rb +6 -1
- data/lib/rubocop/cop/kaizo/keyword_arguments.rb +22 -0
- data/lib/rubocop/cop/kaizo/nested_method_calls.rb +20 -6
- data/lib/rubocop/cop/kaizo/next_in_non_void_enumerable.rb +13 -2
- data/lib/rubocop/cop/kaizo/plural_collection_name.rb +139 -0
- data/lib/rubocop/cop/kaizo/positional_arguments.rb +16 -0
- data/lib/rubocop/cop/kaizo/prefer_pathname.rb +13 -0
- data/lib/rubocop/cop/kaizo/spec_comment.rb +23 -1
- data/lib/rubocop/cop/kaizo/spec_description_prose.rb +62 -22
- data/lib/rubocop/cop/kaizo/spec_subject.rb +117 -0
- data/lib/rubocop/cop/kaizo/tempfile_create.rb +71 -0
- data/lib/rubocop/cop/kaizo/total_arguments.rb +18 -0
- data/lib/rubocop/cop/kaizo_cops.rb +3 -0
- metadata +5 -2
data/config/default.yml
CHANGED
|
@@ -7,26 +7,84 @@
|
|
|
7
7
|
|
|
8
8
|
# The `Kaizo/*` cops check `def`, `def self.`, `define_method`, and
|
|
9
9
|
# `define_singleton_method`. `*rest`, `**keyword-rest`, and `&block` parameters
|
|
10
|
-
# are not counted.
|
|
11
|
-
#
|
|
12
|
-
#
|
|
10
|
+
# are not counted. Operator methods (`[]=`, `[]`, `<=>`, ...) and the `initialize`
|
|
11
|
+
# of a `Struct.new`/`Data.define` block are exempt -- their arity is fixed by Ruby
|
|
12
|
+
# syntax or by the value object's members, not by a modeling choice. The defaults
|
|
13
|
+
# are deliberately strict -- at most one positional and one keyword argument -- to
|
|
14
|
+
# apply maximum pressure; loosen them if that is too aggressive for your codebase.
|
|
15
|
+
# Exempt individual methods by name (`AllowedMethods`, e.g. `initialize`) or by
|
|
16
|
+
# regexp (`AllowedPatterns`).
|
|
17
|
+
#
|
|
18
|
+
# The keyword-counting cops (`KeywordArguments`, `TotalArguments`) skip `spec/`
|
|
19
|
+
# and `test/` trees: wide keyword interfaces are the testing idiom there
|
|
20
|
+
# (FactoryBot-style `create`/`build` helpers), and the total bound would
|
|
21
|
+
# re-police the keywords the exemption frees. `PositionalArguments` runs
|
|
22
|
+
# everywhere -- positional arguments communicate nothing unless they are solo.
|
|
13
23
|
Kaizo/KeywordArguments:
|
|
14
24
|
Description: 'Checks that a method does not declare too many keyword arguments.'
|
|
15
25
|
Enabled: true
|
|
16
26
|
VersionAdded: '0.1'
|
|
27
|
+
VersionChanged: '0.9.1'
|
|
17
28
|
Max: 1
|
|
29
|
+
AllowedMethods: []
|
|
30
|
+
AllowedPatterns: []
|
|
31
|
+
Exclude:
|
|
32
|
+
- '**/spec/**/*'
|
|
33
|
+
- '**/test/**/*'
|
|
34
|
+
|
|
35
|
+
# `Kaizo/PluralCollectionName` flags a method that hands back an array under a
|
|
36
|
+
# singular name. Ruby has no return types, so this is a heuristic, and it errs
|
|
37
|
+
# toward silence: a method is flagged only when every value it can return is
|
|
38
|
+
# unambiguously an array. `ArrayMethods` is the set whose result is an `Array`
|
|
39
|
+
# whatever the receiver -- `select` and `reject` are absent on purpose, since on
|
|
40
|
+
# a `Hash` they return a `Hash`; extend it (with `inherit_mode: merge`) to
|
|
41
|
+
# cover your own collection-returning helpers. A name counts as plural when it
|
|
42
|
+
# ends in `s` or appears in `IrregularPlurals`.
|
|
43
|
+
Kaizo/PluralCollectionName:
|
|
44
|
+
Description: 'Checks that a method returning a collection is named in the plural.'
|
|
45
|
+
Enabled: true
|
|
46
|
+
VersionAdded: '0.8'
|
|
47
|
+
VersionChanged: '0.9'
|
|
48
|
+
AllowedMethods: []
|
|
49
|
+
ArrayMethods:
|
|
50
|
+
- map
|
|
51
|
+
- flat_map
|
|
52
|
+
- collect
|
|
53
|
+
- collect_concat
|
|
54
|
+
- to_a
|
|
55
|
+
- entries
|
|
56
|
+
- sort
|
|
57
|
+
- sort_by
|
|
58
|
+
- zip
|
|
59
|
+
IrregularPlurals:
|
|
60
|
+
- people
|
|
61
|
+
- children
|
|
62
|
+
- men
|
|
63
|
+
- women
|
|
64
|
+
- data
|
|
65
|
+
- media
|
|
66
|
+
- criteria
|
|
18
67
|
|
|
19
68
|
Kaizo/PositionalArguments:
|
|
20
69
|
Description: 'Checks that a method does not declare too many positional arguments.'
|
|
21
70
|
Enabled: true
|
|
22
71
|
VersionAdded: '0.1'
|
|
72
|
+
VersionChanged: '0.9'
|
|
23
73
|
Max: 1
|
|
74
|
+
AllowedMethods: []
|
|
75
|
+
AllowedPatterns: []
|
|
24
76
|
|
|
25
77
|
Kaizo/TotalArguments:
|
|
26
78
|
Description: 'Checks that a method does not declare too many arguments in total.'
|
|
27
79
|
Enabled: true
|
|
28
80
|
VersionAdded: '0.1'
|
|
81
|
+
VersionChanged: '0.9.1'
|
|
29
82
|
Max: 2
|
|
83
|
+
AllowedMethods: []
|
|
84
|
+
AllowedPatterns: []
|
|
85
|
+
Exclude:
|
|
86
|
+
- '**/spec/**/*'
|
|
87
|
+
- '**/test/**/*'
|
|
30
88
|
|
|
31
89
|
# `Kaizo/AgentNounClassName` flags `class` definitions and
|
|
32
90
|
# `Struct.new`/`Data.define`/`Class.new` assignments whose name ends in `er`/`or`
|
|
@@ -114,12 +172,18 @@ Kaizo/NestedMethodCalls:
|
|
|
114
172
|
# example. Magic comments, `# rubocop:` directives, and shebangs are exempt;
|
|
115
173
|
# add more exemptions with `AllowedPatterns`. Broaden `Include` (e.g. add
|
|
116
174
|
# `**/spec/**/*` or `**/*_test.rb`) to cover support files or Minitest.
|
|
175
|
+
# `spec/helpers` and `spec/support` hold infrastructure, not specs, so they
|
|
176
|
+
# are excluded by default; override `Exclude` to change that.
|
|
117
177
|
Kaizo/SpecComment:
|
|
118
178
|
Description: 'Avoid comments in spec files; express the intent as well-structured specs.'
|
|
119
179
|
Enabled: true
|
|
120
180
|
VersionAdded: '0.5'
|
|
181
|
+
VersionChanged: '0.9'
|
|
121
182
|
Include:
|
|
122
183
|
- '**/*_spec.rb'
|
|
184
|
+
Exclude:
|
|
185
|
+
- '**/spec/helpers/**/*'
|
|
186
|
+
- '**/spec/support/**/*'
|
|
123
187
|
AllowedPatterns: []
|
|
124
188
|
|
|
125
189
|
# `Kaizo/ExplicitBegin` requires an explicit `begin`/`end` block when a method
|
|
@@ -153,17 +217,31 @@ Kaizo/NextInNonVoidEnumerable:
|
|
|
153
217
|
# `.rubocop.yml` at your peril.
|
|
154
218
|
Style/RedundantBegin:
|
|
155
219
|
Enabled: false
|
|
220
|
+
|
|
221
|
+
# Prefer Ruby 3.1's hash-value shorthand -- `Session.new(table:)` over
|
|
222
|
+
# `Session.new(table: table)` -- everywhere key and value name match. Shipped on
|
|
223
|
+
# by default because kaizo targets modern Ruby; override in your own config if
|
|
224
|
+
# you want the explicit form.
|
|
225
|
+
Style/HashSyntax:
|
|
226
|
+
EnforcedShorthandSyntax: always
|
|
227
|
+
|
|
156
228
|
# `Kaizo/SpecDescriptionProse` requires RSpec `it`/`context` descriptions to
|
|
157
|
-
# read as one-behavior prose: no commas,
|
|
158
|
-
#
|
|
159
|
-
# `
|
|
229
|
+
# read as one-behavior prose: no commas, none of the `ForbiddenWords`
|
|
230
|
+
# (conjunctions and conditionals whose presence signals a hidden split), and no
|
|
231
|
+
# code in an `it` description; `context` descriptions carry no code and must
|
|
232
|
+
# open with one of the `RequiredContextPrefixes`. Error class names
|
|
233
|
+
# (`Foo::Error`) read as prose, since the error is part of the specified
|
|
234
|
+
# behavior. Exempt whole descriptions with `AllowedPatterns`. `describe`
|
|
235
|
+
# strings are exempt. No autocorrection.
|
|
160
236
|
Kaizo/SpecDescriptionProse:
|
|
161
237
|
Description: 'Require RSpec it/context descriptions to read as one-behavior prose.'
|
|
162
238
|
Enabled: true
|
|
163
239
|
VersionAdded: '0.6'
|
|
240
|
+
VersionChanged: '0.9'
|
|
164
241
|
Include:
|
|
165
242
|
- '**/*_spec.rb'
|
|
166
|
-
|
|
243
|
+
AllowedPatterns: []
|
|
244
|
+
ForbiddenWords:
|
|
167
245
|
- and
|
|
168
246
|
- but
|
|
169
247
|
- or
|
|
@@ -179,12 +257,28 @@ Kaizo/SpecDescriptionProse:
|
|
|
179
257
|
- because
|
|
180
258
|
- although
|
|
181
259
|
- though
|
|
182
|
-
|
|
260
|
+
RequiredContextPrefixes:
|
|
183
261
|
- when
|
|
184
262
|
- with
|
|
185
263
|
- without
|
|
186
264
|
- after
|
|
187
265
|
|
|
266
|
+
# `Kaizo/SpecSubject` requires the unit under test to be declared with
|
|
267
|
+
# `subject`, not `let`. A `let`/`let!` is flagged when its block confidently
|
|
268
|
+
# builds the class under test: a `.new` on `described_class`, on the constant
|
|
269
|
+
# an enclosing `describe`/`context` names (full or short name), or on a
|
|
270
|
+
# constant matching the spec's file name. Deliberate second instances (an
|
|
271
|
+
# `other` in an equality spec) are exempted with `AllowedMethods` /
|
|
272
|
+
# `AllowedPatterns`, matched against the `let` name. No autocorrection.
|
|
273
|
+
Kaizo/SpecSubject:
|
|
274
|
+
Description: 'Declare the unit under test with subject, not let.'
|
|
275
|
+
Enabled: true
|
|
276
|
+
VersionAdded: '0.9'
|
|
277
|
+
Include:
|
|
278
|
+
- '**/*_spec.rb'
|
|
279
|
+
AllowedMethods: []
|
|
280
|
+
AllowedPatterns: []
|
|
281
|
+
|
|
188
282
|
# `Kaizo/FileUtilsInclusion` requires `FileUtils` to be mixed in with `include`
|
|
189
283
|
# or `extend` once it is used more than once in a class or module, instead of
|
|
190
284
|
# repeating the `FileUtils.` receiver. The namespace is reported once. A single
|
|
@@ -195,6 +289,16 @@ Kaizo/FileUtilsInclusion:
|
|
|
195
289
|
Enabled: true
|
|
196
290
|
VersionAdded: '0.6'
|
|
197
291
|
|
|
292
|
+
# `Kaizo/TempfileCreate` requires block-form `Tempfile.create`, the only
|
|
293
|
+
# temp-file API that closes and removes the file deterministically.
|
|
294
|
+
# `Tempfile.new`/`Tempfile.open` rely on a GC finalizer that may run late or
|
|
295
|
+
# never; blockless `Tempfile.create` returns a plain `File` that is never
|
|
296
|
+
# removed automatically. No autocorrection.
|
|
297
|
+
Kaizo/TempfileCreate:
|
|
298
|
+
Description: 'Use block-form Tempfile.create; Tempfile.new/open clean up unpredictably.'
|
|
299
|
+
Enabled: true
|
|
300
|
+
VersionAdded: '0.9'
|
|
301
|
+
|
|
198
302
|
# `Kaizo/PreferPathname` flags calls to `File` class methods that have a
|
|
199
303
|
# `Pathname` instance-method equivalent (`File.read`, `File.exist?`, `File.join`,
|
|
200
304
|
# ...), preferring `Pathname`. Runs on `**/*.rb`; `exe/**/*` and `bin/**/*` are
|
data/lib/kaizo/version.rb
CHANGED
|
@@ -16,6 +16,29 @@ module RuboCop
|
|
|
16
16
|
# assignments are both checked. There is no autocorrection: renaming a
|
|
17
17
|
# class is a design decision.
|
|
18
18
|
#
|
|
19
|
+
# == Configuration
|
|
20
|
+
#
|
|
21
|
+
# [+AllowedSuffixes+] Suffixes exempt from the `er`/`or` rule, matched
|
|
22
|
+
# against the end of the name (+Controller+ clears
|
|
23
|
+
# +UsersController+ too). Default: a broad list of
|
|
24
|
+
# legitimate `-er`/`-or` domain nouns and framework
|
|
25
|
+
# terms (+Adapter+, +Controller+, +Error+, +User+,
|
|
26
|
+
# ...) -- see +config/default.yml+ for all of them.
|
|
27
|
+
# [+ForbiddenSuffixes+] Suffixes always flagged, even when matched by
|
|
28
|
+
# +AllowedSuffixes+ -- which is how a default
|
|
29
|
+
# exemption is dropped. Default: +Service+, +Util+,
|
|
30
|
+
# +Utils+.
|
|
31
|
+
#
|
|
32
|
+
# Extend either list without restating it via RuboCop's
|
|
33
|
+
# <tt>inherit_mode: merge</tt>:
|
|
34
|
+
#
|
|
35
|
+
# Kaizo/AgentNounClassName:
|
|
36
|
+
# inherit_mode:
|
|
37
|
+
# merge:
|
|
38
|
+
# - AllowedSuffixes
|
|
39
|
+
# AllowedSuffixes:
|
|
40
|
+
# - Voucher # PaymentVoucher now passes
|
|
41
|
+
#
|
|
19
42
|
# @example
|
|
20
43
|
# # bad
|
|
21
44
|
# class PaymentProcessor
|
|
@@ -58,7 +81,7 @@ module RuboCop
|
|
|
58
81
|
def check_name(name, location)
|
|
59
82
|
return unless offending?(name)
|
|
60
83
|
|
|
61
|
-
add_offense(location, message: format(MSG, name:
|
|
84
|
+
add_offense(location, message: format(MSG, name:))
|
|
62
85
|
end
|
|
63
86
|
|
|
64
87
|
def offending?(name)
|
|
@@ -6,30 +6,70 @@ module RuboCop
|
|
|
6
6
|
# Walks method definitions written with `def`, `def self.`, `define_method`,
|
|
7
7
|
# and `define_singleton_method`, and reports when the argument count
|
|
8
8
|
# produced by the including cop exceeds the configured `Max`. Including cops
|
|
9
|
-
# must define a private `arity(arguments)` method and a `KIND` constant.
|
|
10
|
-
# `
|
|
9
|
+
# must define a private `arity(arguments)` method and a `KIND` constant.
|
|
10
|
+
# Operator methods (`[]=`, `[]`, `<=>`, ...) and the `initialize` of a
|
|
11
|
+
# `Struct.new`/`Data.define` block are exempt.
|
|
11
12
|
module ArgumentCounting
|
|
13
|
+
include AllowedMethods
|
|
14
|
+
include AllowedPattern
|
|
15
|
+
|
|
12
16
|
POSITIONAL_TYPES = %i[arg optarg].freeze
|
|
13
17
|
KEYWORD_TYPES = %i[kwarg kwoptarg].freeze
|
|
14
18
|
DEFINE_METHODS = %i[define_method define_singleton_method].freeze
|
|
15
19
|
STRUCT_OR_DATA = { "Struct" => :new, "Data" => :define }.freeze
|
|
20
|
+
|
|
21
|
+
# Ruby's operator method names, mirroring the list behind RuboCop's
|
|
22
|
+
# `operator_method?` (private upstream, so it cannot be reused). Needed
|
|
23
|
+
# only for the `define_method(:[]=)` form, where the name being defined is
|
|
24
|
+
# a symbol argument rather than a `def` node we could ask directly.
|
|
25
|
+
OPERATOR_METHOD_NAMES = [
|
|
26
|
+
:!, :!=, :"!@", :!~, :%, :&, :*, :**, :+, :+@, :-, :-@, :/, :<, :<<, :<=,
|
|
27
|
+
:<=>, :==, :===, :=~, :>, :>=, :>>, :[], :[]=, :^, :`, :|, :~, :"~@"
|
|
28
|
+
].freeze
|
|
16
29
|
MSG = "Method has too many %<kind>s. [%<count>d/%<max>d]".freeze
|
|
17
30
|
|
|
18
31
|
def on_def(node)
|
|
19
|
-
return if
|
|
32
|
+
return if exempt?(node)
|
|
20
33
|
|
|
21
34
|
check_arity(node)
|
|
22
35
|
end
|
|
23
36
|
alias on_defs on_def
|
|
24
37
|
|
|
25
38
|
def on_block(node)
|
|
26
|
-
|
|
39
|
+
return unless DEFINE_METHODS.include?(node.method_name)
|
|
40
|
+
return if exempt_defined_name?(node)
|
|
41
|
+
|
|
42
|
+
check_arity(node)
|
|
27
43
|
end
|
|
28
44
|
alias on_numblock on_block
|
|
29
45
|
alias on_itblock on_block
|
|
30
46
|
|
|
31
47
|
private
|
|
32
48
|
|
|
49
|
+
# A definition whose argument count is not a design choice. An operator
|
|
50
|
+
# method's arity is fixed by Ruby's syntax -- `[]=` takes the indices plus
|
|
51
|
+
# the assigned value, `<=>` takes its right-hand side -- so it cannot be
|
|
52
|
+
# modeled away, and a `Struct`/`Data` `initialize` just mirrors the members
|
|
53
|
+
# the value object was declared with.
|
|
54
|
+
def exempt?(node)
|
|
55
|
+
node.operator_method? || allowed_initialize?(node) || allowed_name?(node.method_name)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The same exemptions for `define_method(:[]=)`, which names the method it
|
|
59
|
+
# defines with a symbol (or string) argument. A computed name is still
|
|
60
|
+
# checked -- we cannot know what it resolves to.
|
|
61
|
+
def exempt_defined_name?(node)
|
|
62
|
+
defined_name = node.send_node.first_argument
|
|
63
|
+
return false unless defined_name&.type?(:sym, :str)
|
|
64
|
+
|
|
65
|
+
name = defined_name.value.to_sym
|
|
66
|
+
OPERATOR_METHOD_NAMES.include?(name) || allowed_name?(name)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def allowed_name?(name)
|
|
70
|
+
allowed_method?(name) || matches_allowed_pattern?(name.to_s)
|
|
71
|
+
end
|
|
72
|
+
|
|
33
73
|
def positional_arity(arguments)
|
|
34
74
|
arguments.count { |argument| POSITIONAL_TYPES.include?(argument.type) }
|
|
35
75
|
end
|
|
@@ -43,8 +83,8 @@ module RuboCop
|
|
|
43
83
|
count = arity(node.arguments)
|
|
44
84
|
return unless max && count > max
|
|
45
85
|
|
|
46
|
-
message = format(MSG, kind: self.class::KIND, count
|
|
47
|
-
add_offense(offense_location(node), message:
|
|
86
|
+
message = format(MSG, kind: self.class::KIND, count:, max:)
|
|
87
|
+
add_offense(offense_location(node), message:) { self.max = count }
|
|
48
88
|
end
|
|
49
89
|
|
|
50
90
|
def offense_location(node)
|
|
@@ -15,6 +15,15 @@ module RuboCop
|
|
|
15
15
|
# Modifier `rescue` expressions (`foo rescue nil`) and endless method
|
|
16
16
|
# definitions are not flagged.
|
|
17
17
|
#
|
|
18
|
+
# == Configuration
|
|
19
|
+
#
|
|
20
|
+
# No cop-specific options; the standard per-cop settings (+Enabled+,
|
|
21
|
+
# +Severity+, +AutoCorrect+, +Include+/+Exclude+) apply. Note that
|
|
22
|
+
# loading the kaizo plugin disables `Style/RedundantBegin`, this cop's
|
|
23
|
+
# exact inverse. To opt out of explicit begins, disable this cop;
|
|
24
|
+
# re-enable `Style/RedundantBegin` only alongside that, since with both
|
|
25
|
+
# enabled each flags the form the other mandates.
|
|
26
|
+
#
|
|
18
27
|
# @safety
|
|
19
28
|
# Autocorrection is skipped when the body does not sit on its own lines
|
|
20
29
|
# between `def` and `end` (a single-line definition, for example), and
|
|
@@ -19,6 +19,11 @@ module RuboCop
|
|
|
19
19
|
# There is no autocorrection: whether to `include` or `extend`, and where
|
|
20
20
|
# the mixin belongs, is a judgment call for a human.
|
|
21
21
|
#
|
|
22
|
+
# == Configuration
|
|
23
|
+
#
|
|
24
|
+
# No cop-specific options; the standard per-cop settings (+Enabled+,
|
|
25
|
+
# +Severity+, +Include+/+Exclude+) apply.
|
|
26
|
+
#
|
|
22
27
|
# @example
|
|
23
28
|
# # bad - the `FileUtils.` receiver is repeated
|
|
24
29
|
# class Backup
|
|
@@ -68,7 +73,7 @@ module RuboCop
|
|
|
68
73
|
count = own_sends(node) { |send| file_utils_call?(send) }.size
|
|
69
74
|
return if count < 2
|
|
70
75
|
|
|
71
|
-
add_offense(node.loc.name, message: format(MSG, count
|
|
76
|
+
add_offense(node.loc.name, message: format(MSG, count:, scope:))
|
|
72
77
|
end
|
|
73
78
|
|
|
74
79
|
# Sends in `namespace`'s own body that match the block -- not its
|
|
@@ -8,6 +8,23 @@ module RuboCop
|
|
|
8
8
|
# (`kwarg`) and optional (`kwoptarg`) parameters are counted; `**rest` is
|
|
9
9
|
# not.
|
|
10
10
|
#
|
|
11
|
+
# == Configuration
|
|
12
|
+
#
|
|
13
|
+
# [+Max+] Most keyword arguments a method may declare. Default: +1+.
|
|
14
|
+
# [+AllowedMethods+] Method names exempt from the limit. Default: none.
|
|
15
|
+
# [+AllowedPatterns+] Regexps matched against the method name; a match is
|
|
16
|
+
# exempt. Default: none.
|
|
17
|
+
# [+Exclude+] Paths the cop skips. Default: <tt>**/spec/**/*</tt> and
|
|
18
|
+
# <tt>**/test/**/*</tt> -- wide keyword interfaces are the
|
|
19
|
+
# testing idiom (FactoryBot-style builders), so keyword
|
|
20
|
+
# counts are not policed there. Set it to <tt>[]</tt> to
|
|
21
|
+
# police tests too.
|
|
22
|
+
#
|
|
23
|
+
# Kaizo/KeywordArguments:
|
|
24
|
+
# Max: 2
|
|
25
|
+
# AllowedMethods:
|
|
26
|
+
# - initialize
|
|
27
|
+
#
|
|
11
28
|
# @example Max: 2
|
|
12
29
|
# # bad
|
|
13
30
|
# def calculate_volume(width:, length:, height:)
|
|
@@ -17,6 +34,11 @@ module RuboCop
|
|
|
17
34
|
# def calculate_volume(shape)
|
|
18
35
|
# end
|
|
19
36
|
#
|
|
37
|
+
# @example AllowedMethods: ['initialize'] (default: [])
|
|
38
|
+
# # good - exempt by name
|
|
39
|
+
# def initialize(host:, port:, ssl: true)
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
20
42
|
class KeywordArguments < Base
|
|
21
43
|
include ArgumentCounting
|
|
22
44
|
|
|
@@ -5,8 +5,10 @@ module RuboCop
|
|
|
5
5
|
#
|
|
6
6
|
# A call whose arguments are themselves the results of other calls --
|
|
7
7
|
# `foo(SomeClass.new(another("bar").chain))` -- packs several steps into one
|
|
8
|
-
# expression.
|
|
9
|
-
# always reads better and is easier to debug than peeling
|
|
8
|
+
# expression. Giving the intermediate results descriptive names (or extracting
|
|
9
|
+
# a method) almost always reads better and is easier to debug than peeling
|
|
10
|
+
# parentheses apart. The point is not the assignment but the name: it should
|
|
11
|
+
# say what the value is, so the step documents itself.
|
|
10
12
|
#
|
|
11
13
|
# Only nesting through *argument* positions is counted; a receiver chain such
|
|
12
14
|
# as `user.account.owner.name` is a separate concern. Operator methods
|
|
@@ -14,6 +16,18 @@ module RuboCop
|
|
|
14
16
|
# Depth is measured from each outermost call and reported once. There is no
|
|
15
17
|
# autocorrection: choosing the intermediate name is a design decision.
|
|
16
18
|
#
|
|
19
|
+
# == Configuration
|
|
20
|
+
#
|
|
21
|
+
# [+Max+] Deepest allowed nesting of calls in argument positions; +1+
|
|
22
|
+
# permits one nested call. Default: +1+.
|
|
23
|
+
# [+AllowedMethods+] Calls to these methods neither count toward depth
|
|
24
|
+
# nor get flagged. Default: none.
|
|
25
|
+
#
|
|
26
|
+
# Kaizo/NestedMethodCalls:
|
|
27
|
+
# Max: 2
|
|
28
|
+
# AllowedMethods:
|
|
29
|
+
# - expect # don't count RSpec's expect(...) wrapper
|
|
30
|
+
#
|
|
17
31
|
# @example Max: 1 (default)
|
|
18
32
|
# # bad
|
|
19
33
|
# foo(SomeClass.new(another("bar").chain))
|
|
@@ -21,9 +35,9 @@ module RuboCop
|
|
|
21
35
|
# # bad
|
|
22
36
|
# wrap(parse(read(io)))
|
|
23
37
|
#
|
|
24
|
-
# # good - name the
|
|
25
|
-
#
|
|
26
|
-
# wrap(
|
|
38
|
+
# # good - a name that documents what the value is
|
|
39
|
+
# parsed_config = parse(read(io))
|
|
40
|
+
# wrap(parsed_config)
|
|
27
41
|
#
|
|
28
42
|
# # good - a single nested call is allowed
|
|
29
43
|
# puts compute(value)
|
|
@@ -48,7 +62,7 @@ module RuboCop
|
|
|
48
62
|
depth = nesting_depth(node)
|
|
49
63
|
return unless max && depth > max
|
|
50
64
|
|
|
51
|
-
add_offense(node, message: format(MSG, depth
|
|
65
|
+
add_offense(node, message: format(MSG, depth:, max:)) do
|
|
52
66
|
self.max = depth
|
|
53
67
|
end
|
|
54
68
|
end
|
|
@@ -21,6 +21,17 @@ module RuboCop
|
|
|
21
21
|
# clause might become a ternary, a `select`/`reject`, a `filter_map`, or a
|
|
22
22
|
# restructured block.
|
|
23
23
|
#
|
|
24
|
+
# == Configuration
|
|
25
|
+
#
|
|
26
|
+
# [+AllowedMethods+] Enumerable methods whose blocks may use `next`.
|
|
27
|
+
# Default: none.
|
|
28
|
+
# [+AllowedPatterns+] Regexps matched against the enumerable method's
|
|
29
|
+
# name; a match is exempt. Default: none.
|
|
30
|
+
#
|
|
31
|
+
# Kaizo/NextInNonVoidEnumerable:
|
|
32
|
+
# AllowedMethods:
|
|
33
|
+
# - reduce # allow `next acc` guards in reduce/inject
|
|
34
|
+
#
|
|
24
35
|
# @example
|
|
25
36
|
# # bad
|
|
26
37
|
# array.map do |item|
|
|
@@ -113,11 +124,11 @@ module RuboCop
|
|
|
113
124
|
private
|
|
114
125
|
|
|
115
126
|
def flag_block_local_nexts(block_node, method)
|
|
116
|
-
message = format(MSG, method:
|
|
127
|
+
message = format(MSG, method:)
|
|
117
128
|
|
|
118
129
|
block_node.each_child_node do |child|
|
|
119
130
|
each_block_local_next(child) do |next_node|
|
|
120
|
-
add_offense(next_node.loc.keyword, message:
|
|
131
|
+
add_offense(next_node.loc.keyword, message:)
|
|
121
132
|
end
|
|
122
133
|
end
|
|
123
134
|
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module Kaizo
|
|
4
|
+
# Checks that a method returning a collection is named in the plural. A
|
|
5
|
+
# singular name on a method handing back an array (`def user` returning
|
|
6
|
+
# `[first, second]`) misdescribes what the caller gets; the plural does the
|
|
7
|
+
# documenting for free.
|
|
8
|
+
#
|
|
9
|
+
# Ruby has no return types, so "returns an array" is a heuristic, and this
|
|
10
|
+
# cop deliberately errs toward silence. A method is only flagged when
|
|
11
|
+
# *every* value it can return is unambiguously an array: an array literal,
|
|
12
|
+
# or a call to a method that returns an `Array` whatever its receiver
|
|
13
|
+
# (`ArrayMethods`, e.g. `map`, `to_a`, `sort`). One branch returning `nil`
|
|
14
|
+
# is enough to leave the method alone. Methods like `select` and `reject`
|
|
15
|
+
# are absent by design -- on a `Hash` they hand back a `Hash`.
|
|
16
|
+
#
|
|
17
|
+
# A name counts as plural when it ends in `s` or appears in
|
|
18
|
+
# `IrregularPlurals`. Predicate (`?`), writer (`=`), and operator methods
|
|
19
|
+
# are exempt, as is `initialize`, and `AllowedMethods` exempts names
|
|
20
|
+
# outright. There is no autocorrection: renaming a method is a design
|
|
21
|
+
# decision, and only its author knows the right plural.
|
|
22
|
+
#
|
|
23
|
+
# == Configuration
|
|
24
|
+
#
|
|
25
|
+
# [+ArrayMethods+] Calls treated as returning an +Array+ whatever their
|
|
26
|
+
# receiver. Default: +map+, +flat_map+, +collect+,
|
|
27
|
+
# +collect_concat+, +to_a+, +entries+, +sort+, +sort_by+,
|
|
28
|
+
# +zip+.
|
|
29
|
+
# [+IrregularPlurals+] Names accepted as plural despite not ending in
|
|
30
|
+
# +s+. Default: +people+, +children+, +men+,
|
|
31
|
+
# +women+, +data+, +media+, +criteria+.
|
|
32
|
+
# [+AllowedMethods+] Method names never flagged. Default: none.
|
|
33
|
+
#
|
|
34
|
+
# Extend a list without restating its default via RuboCop's
|
|
35
|
+
# <tt>inherit_mode: merge</tt>:
|
|
36
|
+
#
|
|
37
|
+
# Kaizo/PluralCollectionName:
|
|
38
|
+
# inherit_mode:
|
|
39
|
+
# merge:
|
|
40
|
+
# - ArrayMethods
|
|
41
|
+
# - IrregularPlurals
|
|
42
|
+
# ArrayMethods:
|
|
43
|
+
# - fetch_all
|
|
44
|
+
# IrregularPlurals:
|
|
45
|
+
# - alumni
|
|
46
|
+
#
|
|
47
|
+
# @example
|
|
48
|
+
# # bad
|
|
49
|
+
# def user
|
|
50
|
+
# [first_match, second_match]
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
# # good
|
|
54
|
+
# def users
|
|
55
|
+
# [first_match, second_match]
|
|
56
|
+
# end
|
|
57
|
+
#
|
|
58
|
+
# @example
|
|
59
|
+
# # good - not confidently an array, so not flagged
|
|
60
|
+
# def user
|
|
61
|
+
# return nil if missing?
|
|
62
|
+
#
|
|
63
|
+
# [first_match, second_match]
|
|
64
|
+
# end
|
|
65
|
+
#
|
|
66
|
+
class PluralCollectionName < Base
|
|
67
|
+
include AllowedMethods
|
|
68
|
+
|
|
69
|
+
MSG = "Name a method that returns a collection in the plural. " \
|
|
70
|
+
"`%<name>s` returns an array.".freeze
|
|
71
|
+
|
|
72
|
+
# Methods whose result is an `Array` regardless of the receiver. Kept
|
|
73
|
+
# deliberately short: anything whose return type follows its receiver
|
|
74
|
+
# (`select` on a `Hash`) would turn this cop into a false-positive mill.
|
|
75
|
+
DEFAULT_ARRAY_METHODS = %i[
|
|
76
|
+
map flat_map collect collect_concat to_a entries sort sort_by zip
|
|
77
|
+
].freeze
|
|
78
|
+
|
|
79
|
+
def on_def(node)
|
|
80
|
+
return if exempt?(node)
|
|
81
|
+
return unless returns_array?(node.body)
|
|
82
|
+
|
|
83
|
+
add_offense(node.loc.name, message: format(MSG, name: node.method_name))
|
|
84
|
+
end
|
|
85
|
+
alias on_defs on_def
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def exempt?(node)
|
|
90
|
+
return true if node.predicate_method? || node.assignment_method?
|
|
91
|
+
return true if node.operator_method? || node.method?(:initialize)
|
|
92
|
+
|
|
93
|
+
plural?(node.method_name.to_s) || allowed_method?(node.method_name)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def plural?(name)
|
|
97
|
+
name.end_with?("s") || irregular_plurals.include?(name)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def irregular_plurals
|
|
101
|
+
Array(cop_config["IrregularPlurals"])
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Every value the method can hand back must be an array before it is
|
|
105
|
+
# worth flagging, so that a single `return nil` keeps the cop quiet.
|
|
106
|
+
def returns_array?(body)
|
|
107
|
+
return false unless body
|
|
108
|
+
|
|
109
|
+
results = [final_expression(body), *explicit_returns(body)]
|
|
110
|
+
results.all? { |result| array_result?(result) }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def final_expression(body)
|
|
114
|
+
body.begin_type? ? body.children.last : body
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# The value of each `return`, with a bare `return` contributing `nil` --
|
|
118
|
+
# which is exactly what should stop the method being flagged.
|
|
119
|
+
def explicit_returns(body)
|
|
120
|
+
body.each_descendant(:return).map { |node| node.children.first }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# A block-bearing call (`rows.map { ... }`) is a block node wrapping the
|
|
124
|
+
# send, so the method name lives one level down.
|
|
125
|
+
def array_result?(node)
|
|
126
|
+
return false unless node
|
|
127
|
+
return true if node.array_type?
|
|
128
|
+
|
|
129
|
+
call = node.any_block_type? ? node.send_node : node
|
|
130
|
+
call.call_type? && array_methods.include?(call.method_name)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def array_methods
|
|
134
|
+
@array_methods ||= Array(cop_config.fetch("ArrayMethods", DEFAULT_ARRAY_METHODS)).map(&:to_sym)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -8,6 +8,22 @@ module RuboCop
|
|
|
8
8
|
# and optional (`optarg`) parameters are counted; `*rest` and `&block` are
|
|
9
9
|
# not.
|
|
10
10
|
#
|
|
11
|
+
# Unlike the keyword-counting cops, this one runs everywhere, spec and
|
|
12
|
+
# test trees included: positional arguments communicate nothing unless
|
|
13
|
+
# they are solo, in test code as much as anywhere else.
|
|
14
|
+
#
|
|
15
|
+
# == Configuration
|
|
16
|
+
#
|
|
17
|
+
# [+Max+] Most positional arguments a method may declare. Default: +1+.
|
|
18
|
+
# [+AllowedMethods+] Method names exempt from the limit. Default: none.
|
|
19
|
+
# [+AllowedPatterns+] Regexps matched against the method name; a match is
|
|
20
|
+
# exempt. Default: none.
|
|
21
|
+
#
|
|
22
|
+
# Kaizo/PositionalArguments:
|
|
23
|
+
# Max: 0 # force every argument to be a keyword
|
|
24
|
+
# AllowedMethods:
|
|
25
|
+
# - initialize
|
|
26
|
+
#
|
|
11
27
|
# @example Max: 2
|
|
12
28
|
# # bad
|
|
13
29
|
# def move(x, y, z)
|
|
@@ -15,6 +15,19 @@ module RuboCop
|
|
|
15
15
|
# There is no autocorrection: rewriting `File.read(path)` as
|
|
16
16
|
# `Pathname(path).read` changes the receiver and is left to a human.
|
|
17
17
|
#
|
|
18
|
+
# == Configuration
|
|
19
|
+
#
|
|
20
|
+
# [+Include+] Files the cop runs on. Default: <tt>**/*.rb</tt>.
|
|
21
|
+
# [+Exclude+] Files the cop skips even when included. Default:
|
|
22
|
+
# <tt>exe/**/*</tt> and <tt>bin/**/*</tt>.
|
|
23
|
+
#
|
|
24
|
+
# Kaizo/PreferPathname:
|
|
25
|
+
# inherit_mode:
|
|
26
|
+
# merge:
|
|
27
|
+
# - Exclude
|
|
28
|
+
# Exclude:
|
|
29
|
+
# - 'db/**/*' # add your own
|
|
30
|
+
#
|
|
18
31
|
# @example
|
|
19
32
|
# # bad
|
|
20
33
|
# File.read(path)
|