kaizo 0.8.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 +89 -1
- data/README.md +297 -300
- data/config/default.yml +80 -7
- data/lib/kaizo/version.rb +1 -1
- data/lib/rubocop/cop/kaizo/agent_noun_class_name.rb +23 -0
- data/lib/rubocop/cop/kaizo/argument_counting.rb +17 -7
- data/lib/rubocop/cop/kaizo/explicit_begin.rb +9 -0
- data/lib/rubocop/cop/kaizo/file_utils_inclusion.rb +5 -0
- data/lib/rubocop/cop/kaizo/keyword_arguments.rb +22 -0
- data/lib/rubocop/cop/kaizo/nested_method_calls.rb +12 -0
- data/lib/rubocop/cop/kaizo/next_in_non_void_enumerable.rb +11 -0
- data/lib/rubocop/cop/kaizo/plural_collection_name.rb +30 -2
- 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 +61 -21
- 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 +2 -0
- metadata +4 -2
data/config/default.yml
CHANGED
|
@@ -12,24 +12,50 @@
|
|
|
12
12
|
# syntax or by the value object's members, not by a modeling choice. The defaults
|
|
13
13
|
# are deliberately strict -- at most one positional and one keyword argument -- to
|
|
14
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.
|
|
15
23
|
Kaizo/KeywordArguments:
|
|
16
24
|
Description: 'Checks that a method does not declare too many keyword arguments.'
|
|
17
25
|
Enabled: true
|
|
18
26
|
VersionAdded: '0.1'
|
|
27
|
+
VersionChanged: '0.9.1'
|
|
19
28
|
Max: 1
|
|
29
|
+
AllowedMethods: []
|
|
30
|
+
AllowedPatterns: []
|
|
31
|
+
Exclude:
|
|
32
|
+
- '**/spec/**/*'
|
|
33
|
+
- '**/test/**/*'
|
|
20
34
|
|
|
21
35
|
# `Kaizo/PluralCollectionName` flags a method that hands back an array under a
|
|
22
36
|
# singular name. Ruby has no return types, so this is a heuristic, and it errs
|
|
23
37
|
# toward silence: a method is flagged only when every value it can return is
|
|
24
38
|
# unambiguously an array. `ArrayMethods` is the set whose result is an `Array`
|
|
25
39
|
# whatever the receiver -- `select` and `reject` are absent on purpose, since on
|
|
26
|
-
# a `Hash` they return a `Hash
|
|
27
|
-
#
|
|
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`.
|
|
28
43
|
Kaizo/PluralCollectionName:
|
|
29
44
|
Description: 'Checks that a method returning a collection is named in the plural.'
|
|
30
45
|
Enabled: true
|
|
31
46
|
VersionAdded: '0.8'
|
|
47
|
+
VersionChanged: '0.9'
|
|
32
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
|
|
33
59
|
IrregularPlurals:
|
|
34
60
|
- people
|
|
35
61
|
- children
|
|
@@ -43,13 +69,22 @@ Kaizo/PositionalArguments:
|
|
|
43
69
|
Description: 'Checks that a method does not declare too many positional arguments.'
|
|
44
70
|
Enabled: true
|
|
45
71
|
VersionAdded: '0.1'
|
|
72
|
+
VersionChanged: '0.9'
|
|
46
73
|
Max: 1
|
|
74
|
+
AllowedMethods: []
|
|
75
|
+
AllowedPatterns: []
|
|
47
76
|
|
|
48
77
|
Kaizo/TotalArguments:
|
|
49
78
|
Description: 'Checks that a method does not declare too many arguments in total.'
|
|
50
79
|
Enabled: true
|
|
51
80
|
VersionAdded: '0.1'
|
|
81
|
+
VersionChanged: '0.9.1'
|
|
52
82
|
Max: 2
|
|
83
|
+
AllowedMethods: []
|
|
84
|
+
AllowedPatterns: []
|
|
85
|
+
Exclude:
|
|
86
|
+
- '**/spec/**/*'
|
|
87
|
+
- '**/test/**/*'
|
|
53
88
|
|
|
54
89
|
# `Kaizo/AgentNounClassName` flags `class` definitions and
|
|
55
90
|
# `Struct.new`/`Data.define`/`Class.new` assignments whose name ends in `er`/`or`
|
|
@@ -137,12 +172,18 @@ Kaizo/NestedMethodCalls:
|
|
|
137
172
|
# example. Magic comments, `# rubocop:` directives, and shebangs are exempt;
|
|
138
173
|
# add more exemptions with `AllowedPatterns`. Broaden `Include` (e.g. add
|
|
139
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.
|
|
140
177
|
Kaizo/SpecComment:
|
|
141
178
|
Description: 'Avoid comments in spec files; express the intent as well-structured specs.'
|
|
142
179
|
Enabled: true
|
|
143
180
|
VersionAdded: '0.5'
|
|
181
|
+
VersionChanged: '0.9'
|
|
144
182
|
Include:
|
|
145
183
|
- '**/*_spec.rb'
|
|
184
|
+
Exclude:
|
|
185
|
+
- '**/spec/helpers/**/*'
|
|
186
|
+
- '**/spec/support/**/*'
|
|
146
187
|
AllowedPatterns: []
|
|
147
188
|
|
|
148
189
|
# `Kaizo/ExplicitBegin` requires an explicit `begin`/`end` block when a method
|
|
@@ -185,16 +226,22 @@ Style/HashSyntax:
|
|
|
185
226
|
EnforcedShorthandSyntax: always
|
|
186
227
|
|
|
187
228
|
# `Kaizo/SpecDescriptionProse` requires RSpec `it`/`context` descriptions to
|
|
188
|
-
# read as one-behavior prose: no commas,
|
|
189
|
-
#
|
|
190
|
-
# `
|
|
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.
|
|
191
236
|
Kaizo/SpecDescriptionProse:
|
|
192
237
|
Description: 'Require RSpec it/context descriptions to read as one-behavior prose.'
|
|
193
238
|
Enabled: true
|
|
194
239
|
VersionAdded: '0.6'
|
|
240
|
+
VersionChanged: '0.9'
|
|
195
241
|
Include:
|
|
196
242
|
- '**/*_spec.rb'
|
|
197
|
-
|
|
243
|
+
AllowedPatterns: []
|
|
244
|
+
ForbiddenWords:
|
|
198
245
|
- and
|
|
199
246
|
- but
|
|
200
247
|
- or
|
|
@@ -210,12 +257,28 @@ Kaizo/SpecDescriptionProse:
|
|
|
210
257
|
- because
|
|
211
258
|
- although
|
|
212
259
|
- though
|
|
213
|
-
|
|
260
|
+
RequiredContextPrefixes:
|
|
214
261
|
- when
|
|
215
262
|
- with
|
|
216
263
|
- without
|
|
217
264
|
- after
|
|
218
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
|
+
|
|
219
282
|
# `Kaizo/FileUtilsInclusion` requires `FileUtils` to be mixed in with `include`
|
|
220
283
|
# or `extend` once it is used more than once in a class or module, instead of
|
|
221
284
|
# repeating the `FileUtils.` receiver. The namespace is reported once. A single
|
|
@@ -226,6 +289,16 @@ Kaizo/FileUtilsInclusion:
|
|
|
226
289
|
Enabled: true
|
|
227
290
|
VersionAdded: '0.6'
|
|
228
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
|
+
|
|
229
302
|
# `Kaizo/PreferPathname` flags calls to `File` class methods that have a
|
|
230
303
|
# `Pathname` instance-method equivalent (`File.read`, `File.exist?`, `File.join`,
|
|
231
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
|
|
@@ -10,6 +10,9 @@ module RuboCop
|
|
|
10
10
|
# Operator methods (`[]=`, `[]`, `<=>`, ...) and the `initialize` of a
|
|
11
11
|
# `Struct.new`/`Data.define` block are exempt.
|
|
12
12
|
module ArgumentCounting
|
|
13
|
+
include AllowedMethods
|
|
14
|
+
include AllowedPattern
|
|
15
|
+
|
|
13
16
|
POSITIONAL_TYPES = %i[arg optarg].freeze
|
|
14
17
|
KEYWORD_TYPES = %i[kwarg kwoptarg].freeze
|
|
15
18
|
DEFINE_METHODS = %i[define_method define_singleton_method].freeze
|
|
@@ -34,7 +37,7 @@ module RuboCop
|
|
|
34
37
|
|
|
35
38
|
def on_block(node)
|
|
36
39
|
return unless DEFINE_METHODS.include?(node.method_name)
|
|
37
|
-
return if
|
|
40
|
+
return if exempt_defined_name?(node)
|
|
38
41
|
|
|
39
42
|
check_arity(node)
|
|
40
43
|
end
|
|
@@ -49,15 +52,22 @@ module RuboCop
|
|
|
49
52
|
# modeled away, and a `Struct`/`Data` `initialize` just mirrors the members
|
|
50
53
|
# the value object was declared with.
|
|
51
54
|
def exempt?(node)
|
|
52
|
-
node.operator_method? || allowed_initialize?(node)
|
|
55
|
+
node.operator_method? || allowed_initialize?(node) || allowed_name?(node.method_name)
|
|
53
56
|
end
|
|
54
57
|
|
|
55
|
-
# The same
|
|
56
|
-
# defines with a symbol argument. A computed name is still
|
|
57
|
-
# cannot know what it resolves to.
|
|
58
|
-
def
|
|
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)
|
|
59
62
|
defined_name = node.send_node.first_argument
|
|
60
|
-
defined_name&.
|
|
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)
|
|
61
71
|
end
|
|
62
72
|
|
|
63
73
|
def positional_arity(arguments)
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -16,6 +16,18 @@ module RuboCop
|
|
|
16
16
|
# Depth is measured from each outermost call and reported once. There is no
|
|
17
17
|
# autocorrection: choosing the intermediate name is a design decision.
|
|
18
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
|
+
#
|
|
19
31
|
# @example Max: 1 (default)
|
|
20
32
|
# # bad
|
|
21
33
|
# foo(SomeClass.new(another("bar").chain))
|
|
@@ -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|
|
|
@@ -20,6 +20,30 @@ module RuboCop
|
|
|
20
20
|
# outright. There is no autocorrection: renaming a method is a design
|
|
21
21
|
# decision, and only its author knows the right plural.
|
|
22
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
|
+
#
|
|
23
47
|
# @example
|
|
24
48
|
# # bad
|
|
25
49
|
# def user
|
|
@@ -48,7 +72,7 @@ module RuboCop
|
|
|
48
72
|
# Methods whose result is an `Array` regardless of the receiver. Kept
|
|
49
73
|
# deliberately short: anything whose return type follows its receiver
|
|
50
74
|
# (`select` on a `Hash`) would turn this cop into a false-positive mill.
|
|
51
|
-
|
|
75
|
+
DEFAULT_ARRAY_METHODS = %i[
|
|
52
76
|
map flat_map collect collect_concat to_a entries sort sort_by zip
|
|
53
77
|
].freeze
|
|
54
78
|
|
|
@@ -103,7 +127,11 @@ module RuboCop
|
|
|
103
127
|
return true if node.array_type?
|
|
104
128
|
|
|
105
129
|
call = node.any_block_type? ? node.send_node : node
|
|
106
|
-
call.call_type? &&
|
|
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)
|
|
107
135
|
end
|
|
108
136
|
end
|
|
109
137
|
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)
|
|
@@ -9,12 +9,34 @@ module RuboCop
|
|
|
9
9
|
# `context`/`it` description, a clearer example name, or another example --
|
|
10
10
|
# not prose riding alongside the code.
|
|
11
11
|
#
|
|
12
|
-
# By default only `*_spec.rb` files are inspected
|
|
12
|
+
# By default only `*_spec.rb` files are inspected, and `spec/helpers` and
|
|
13
|
+
# `spec/support` are excluded -- they hold infrastructure, not specs. Magic
|
|
13
14
|
# comments (`# frozen_string_literal: true`, `# encoding: ...`), RuboCop
|
|
14
15
|
# directives (any `# rubocop:` comment), and shebangs are
|
|
15
16
|
# never flagged; add further exemptions with `AllowedPatterns`. There is no
|
|
16
17
|
# autocorrection: turning an explanation into a spec is a design decision.
|
|
17
18
|
#
|
|
19
|
+
# == Configuration
|
|
20
|
+
#
|
|
21
|
+
# [+Include+] Files the cop runs on. Default: <tt>**/*_spec.rb</tt>.
|
|
22
|
+
# Broaden it to cover support files or a Minitest suite.
|
|
23
|
+
# [+Exclude+] Files the cop skips even when included. Default:
|
|
24
|
+
# <tt>**/spec/helpers/**/*</tt> and
|
|
25
|
+
# <tt>**/spec/support/**/*</tt>. Set it to <tt>[]</tt> to
|
|
26
|
+
# inspect those too.
|
|
27
|
+
# [+AllowedPatterns+] Regexps matched against the full comment text,
|
|
28
|
+
# leading +#+ included; a match is exempt.
|
|
29
|
+
# Default: none.
|
|
30
|
+
#
|
|
31
|
+
# Kaizo/SpecComment:
|
|
32
|
+
# inherit_mode:
|
|
33
|
+
# merge:
|
|
34
|
+
# - Include
|
|
35
|
+
# Include:
|
|
36
|
+
# - '**/*_test.rb' # Minitest too
|
|
37
|
+
# AllowedPatterns:
|
|
38
|
+
# - '\A#\s*@rbs' # rbs-inline type annotations
|
|
39
|
+
#
|
|
18
40
|
# @example
|
|
19
41
|
# # bad
|
|
20
42
|
# it 'permits the request' do
|
|
@@ -5,16 +5,19 @@ module RuboCop
|
|
|
5
5
|
# specifications, after the spec-skeleton naming law.
|
|
6
6
|
#
|
|
7
7
|
# An `it`/`specify`/`example` description must not contain a comma (a list
|
|
8
|
-
# is several behaviors), a
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
8
|
+
# is several behaviors), a forbidden word (`ForbiddenWords` -- conjunctions
|
|
9
|
+
# join clauses that are separate examples; a conditional signals a hidden
|
|
10
|
+
# `context`), or code (`_ : # = { } ! [ ]`, a backtick, or a nested quoted
|
|
11
|
+
# literal -- a description is prose, not identifiers or wire values). Each
|
|
12
|
+
# rule is structural: it signals that one example is really more than one,
|
|
13
|
+
# or that the assertion is leaking into the name.
|
|
14
14
|
#
|
|
15
15
|
# A `context` description must not contain code, and must open with one of
|
|
16
|
-
# `
|
|
17
|
-
# name the unit under test and are exempt.
|
|
16
|
+
# `RequiredContextPrefixes` (`when`/`with`/`without`/`after`). `describe`
|
|
17
|
+
# strings name the unit under test and are exempt. An error class name
|
|
18
|
+
# (`Foo::Error`, `Timeout::DeadlineException`) reads as prose, not code:
|
|
19
|
+
# the error is part of the specified behavior and is what the user
|
|
20
|
+
# ultimately sees raised.
|
|
18
21
|
#
|
|
19
22
|
# Wording preferences that do not change the spec's structure (e.g. `should`
|
|
20
23
|
# vs a present-tense verb) are out of scope -- see rubocop-rspec's
|
|
@@ -23,6 +26,32 @@ module RuboCop
|
|
|
23
26
|
# There is no autocorrection: splitting an example, or extracting a
|
|
24
27
|
# condition into a `context`, is a modelling decision for a human.
|
|
25
28
|
#
|
|
29
|
+
# == Configuration
|
|
30
|
+
#
|
|
31
|
+
# [+ForbiddenWords+] Words that force a split when they appear in an
|
|
32
|
+
# example description, matched as whole words, case
|
|
33
|
+
# insensitively. Default: +and+, +but+, +or+, +nor+,
|
|
34
|
+
# +so+, +yet+, +when+, +whenever+, +if+, +unless+,
|
|
35
|
+
# +while+, +until+, +because+, +although+, +though+.
|
|
36
|
+
# [+RequiredContextPrefixes+] Words a `context` description may open
|
|
37
|
+
# with. Default: +when+, +with+, +without+,
|
|
38
|
+
# +after+.
|
|
39
|
+
# [+AllowedPatterns+] Regexps matched against the whole description; a
|
|
40
|
+
# match exempts it from every rule. The escape hatch
|
|
41
|
+
# for a description that must quote something
|
|
42
|
+
# code-shaped. Default: none.
|
|
43
|
+
# [+Include+] Files the cop runs on. Default: <tt>**/*_spec.rb</tt>.
|
|
44
|
+
#
|
|
45
|
+
# Kaizo/SpecDescriptionProse:
|
|
46
|
+
# inherit_mode:
|
|
47
|
+
# merge:
|
|
48
|
+
# - ForbiddenWords
|
|
49
|
+
# - AllowedPatterns
|
|
50
|
+
# ForbiddenWords:
|
|
51
|
+
# - given # flag `given ...` too
|
|
52
|
+
# AllowedPatterns:
|
|
53
|
+
# - 'Foo::Widget' # allow this one identifier
|
|
54
|
+
#
|
|
26
55
|
# @example
|
|
27
56
|
# # bad
|
|
28
57
|
# it "renders the name, image, and flag"
|
|
@@ -39,6 +68,8 @@ module RuboCop
|
|
|
39
68
|
# end
|
|
40
69
|
#
|
|
41
70
|
class SpecDescriptionProse < Base
|
|
71
|
+
include AllowedPattern
|
|
72
|
+
|
|
42
73
|
EXAMPLE_METHODS = %i[
|
|
43
74
|
it specify example fit xit fspecify xspecify fexample xexample
|
|
44
75
|
].freeze
|
|
@@ -46,8 +77,8 @@ module RuboCop
|
|
|
46
77
|
RESTRICT_ON_SEND = (EXAMPLE_METHODS + CONTEXT_METHODS).freeze
|
|
47
78
|
|
|
48
79
|
COMMA_MSG = "Split this example: its description contains a comma.".freeze
|
|
49
|
-
|
|
50
|
-
|
|
80
|
+
FORBIDDEN_WORD_MSG = "Split this example: its description contains `%<word>s`; " \
|
|
81
|
+
"use separate examples or a `context`.".freeze
|
|
51
82
|
CODE_MSG = "Write the description as prose; it contains code, not English.".freeze
|
|
52
83
|
CONTEXT_CODE_MSG = "Write the context description as prose; it contains code, not English.".freeze
|
|
53
84
|
CONTEXT_PREFIX_MSG = "Begin the context description with %<prefixes>s.".freeze
|
|
@@ -57,15 +88,19 @@ module RuboCop
|
|
|
57
88
|
# Homographs like `even`/`given`/`regardless` are deliberately absent --
|
|
58
89
|
# they collide with adjectives/nouns (`even numbers`) -- add them via
|
|
59
90
|
# config if you want them.
|
|
60
|
-
|
|
91
|
+
DEFAULT_FORBIDDEN_WORDS = %w[
|
|
61
92
|
and but or nor so yet
|
|
62
93
|
when whenever if unless while until because although though
|
|
63
94
|
].freeze
|
|
64
|
-
|
|
95
|
+
DEFAULT_REQUIRED_CONTEXT_PREFIXES = %w[when with without after].freeze
|
|
65
96
|
|
|
66
97
|
CODE_CHARS = /[_:#={}!`\[\]]/
|
|
67
98
|
NESTED_QUOTE = /(['"]).+\1/
|
|
68
99
|
|
|
100
|
+
# An error class name is part of the specified behavior -- it is what
|
|
101
|
+
# the user sees raised -- so it is masked to prose before the checks.
|
|
102
|
+
ERROR_CONSTANT = /(?:::)?\b(?:[A-Z]\w*::)*(?:[A-Z]\w*)?(?:Error|Exception)\b/
|
|
103
|
+
|
|
69
104
|
# @!method description(node)
|
|
70
105
|
def_node_matcher :description, <<~PATTERN
|
|
71
106
|
(send nil? _ (str $_) ...)
|
|
@@ -74,8 +109,9 @@ module RuboCop
|
|
|
74
109
|
def on_send(node)
|
|
75
110
|
text = description(node)
|
|
76
111
|
return unless text
|
|
112
|
+
return if matches_allowed_pattern?(text)
|
|
77
113
|
|
|
78
|
-
message = violation(node.method_name, text)
|
|
114
|
+
message = violation(node.method_name, without_error_constants(text))
|
|
79
115
|
return unless message
|
|
80
116
|
|
|
81
117
|
add_offense(node.first_argument, message:)
|
|
@@ -83,6 +119,10 @@ module RuboCop
|
|
|
83
119
|
|
|
84
120
|
private
|
|
85
121
|
|
|
122
|
+
def without_error_constants(text)
|
|
123
|
+
text.gsub(ERROR_CONSTANT, "error")
|
|
124
|
+
end
|
|
125
|
+
|
|
86
126
|
def violation(method, text)
|
|
87
127
|
if EXAMPLE_METHODS.include?(method)
|
|
88
128
|
example_violation(text)
|
|
@@ -94,8 +134,8 @@ module RuboCop
|
|
|
94
134
|
def example_violation(text)
|
|
95
135
|
return COMMA_MSG if text.include?(",")
|
|
96
136
|
|
|
97
|
-
word = forbidden(text,
|
|
98
|
-
return format(
|
|
137
|
+
word = forbidden(text, forbidden_words)
|
|
138
|
+
return format(FORBIDDEN_WORD_MSG, word:) if word
|
|
99
139
|
|
|
100
140
|
CODE_MSG if code?(text)
|
|
101
141
|
end
|
|
@@ -116,19 +156,19 @@ module RuboCop
|
|
|
116
156
|
end
|
|
117
157
|
|
|
118
158
|
def prefix_regexp
|
|
119
|
-
/\A\s*(?:#{
|
|
159
|
+
/\A\s*(?:#{required_context_prefixes.map { |prefix| Regexp.escape(prefix) }.join("|")})\b/i
|
|
120
160
|
end
|
|
121
161
|
|
|
122
162
|
def quoted_prefixes
|
|
123
|
-
|
|
163
|
+
required_context_prefixes.map { |prefix| "`#{prefix}`" }.join("/")
|
|
124
164
|
end
|
|
125
165
|
|
|
126
|
-
def
|
|
127
|
-
cop_config.fetch("
|
|
166
|
+
def forbidden_words
|
|
167
|
+
cop_config.fetch("ForbiddenWords", DEFAULT_FORBIDDEN_WORDS)
|
|
128
168
|
end
|
|
129
169
|
|
|
130
|
-
def
|
|
131
|
-
cop_config.fetch("
|
|
170
|
+
def required_context_prefixes
|
|
171
|
+
cop_config.fetch("RequiredContextPrefixes", DEFAULT_REQUIRED_CONTEXT_PREFIXES)
|
|
132
172
|
end
|
|
133
173
|
end
|
|
134
174
|
end
|