kaizo 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -0
- data/README.md +79 -3
- data/config/default.yml +34 -3
- data/lib/kaizo/version.rb +1 -1
- data/lib/rubocop/cop/kaizo/agent_noun_class_name.rb +1 -1
- data/lib/rubocop/cop/kaizo/argument_counting.rb +36 -6
- data/lib/rubocop/cop/kaizo/file_utils_inclusion.rb +1 -1
- data/lib/rubocop/cop/kaizo/nested_method_calls.rb +8 -6
- data/lib/rubocop/cop/kaizo/next_in_non_void_enumerable.rb +2 -2
- data/lib/rubocop/cop/kaizo/plural_collection_name.rb +111 -0
- data/lib/rubocop/cop/kaizo/spec_description_prose.rb +2 -2
- data/lib/rubocop/cop/kaizo_cops.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a33b49134e0a0a871e9b6358cf3fcc9ab88d9ebaca5e8928003de08594c01a11
|
|
4
|
+
data.tar.gz: 7ed8c9d6780bc64c233b94135850694da8b2bfa1e314dd7369dddf7560f7d42d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b4c8a8645f32a48c82b52d23966d37d2608c63a0c84e4820e33c15ed6f58bb84c40658eef9de990874e3e0d304c8d6d27abec8ef5f708ff3468f6430663cdd12
|
|
7
|
+
data.tar.gz: f4189d76f5bb36959f71c84f0497b33805e77ae4e8b04941c78e8517a9012f92447dbbb08c8063b5738746018330fba474abcee324656461af7ec83f2946a96a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- Add `Kaizo/PluralCollectionName` cop: flags a method that returns an array
|
|
6
|
+
under a singular name (`def user` handing back `[first, second]`), since a
|
|
7
|
+
plural name documents what the caller gets. Ruby has no return types, so the
|
|
8
|
+
detection is a heuristic and errs toward silence — a method is flagged only
|
|
9
|
+
when every value it can return is unambiguously an array, so one branch
|
|
10
|
+
returning `nil` keeps it quiet. `ArrayMethods` covers calls whose result is an
|
|
11
|
+
`Array` whatever the receiver; `select` and `reject` are excluded on purpose,
|
|
12
|
+
since on a `Hash` they return a `Hash`. Predicate, writer, and operator
|
|
13
|
+
methods are exempt, as is `initialize`. `IrregularPlurals` handles plurals
|
|
14
|
+
without a trailing `s`. No autocorrection. Enabled by default.
|
|
15
|
+
- The argument-counting cops (`Kaizo/PositionalArguments`, `Kaizo/TotalArguments`,
|
|
16
|
+
`Kaizo/KeywordArguments`) no longer flag **operator methods**. The arity of `[]=`
|
|
17
|
+
is fixed by Ruby's syntax — an index (or indices) plus the assigned value — so it
|
|
18
|
+
cannot be modeled away; the same holds for `[]`, `<=>`, `+`, `<<`, `==`, and the
|
|
19
|
+
rest of the operator family. Covers `def`, `def self.`, and the
|
|
20
|
+
`define_method(:[]=)` form; a `define_method` whose name is computed at runtime is
|
|
21
|
+
still checked, since the name cannot be known statically. This joins the existing
|
|
22
|
+
`Struct.new`/`Data.define` `initialize` exemption. Ordinary writers
|
|
23
|
+
(`def name=(value)`) are unaffected.
|
|
24
|
+
- The Public Config now ships `Style/HashSyntax` with
|
|
25
|
+
`EnforcedShorthandSyntax: always`, preferring Ruby 3.1's hash-value shorthand
|
|
26
|
+
(`Session.new(table:)` over `Session.new(table: table)`). Override it in your own
|
|
27
|
+
config if you want the explicit form. *(Landed on `master` after 0.7.0 was
|
|
28
|
+
published, so it has not reached gem users yet and was never recorded here.)*
|
|
29
|
+
- Expanded the `Kaizo/NestedMethodCalls` README guidance to spell out that the
|
|
30
|
+
*name* is the point, not the assignment — extracting to a local called `result`
|
|
31
|
+
or `tmp` satisfies the rule while missing what it asks for. The offense message
|
|
32
|
+
itself is unchanged.
|
|
33
|
+
|
|
3
34
|
## 0.6.0
|
|
4
35
|
|
|
5
36
|
- **Renamed the gem from `rubocop-design` to `kaizo`**, and moved every cop from
|
data/README.md
CHANGED
|
@@ -129,6 +129,24 @@ Data.define(:width, :height, :depth, :weight) do
|
|
|
129
129
|
end
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
+
**Operator methods** are exempt too. The arity of `[]=` is fixed by Ruby's syntax
|
|
133
|
+
— an index (or indices) plus the value being assigned — so there is no object to
|
|
134
|
+
extract and no primitive obsession to correct. The same holds for `[]`, `<=>`,
|
|
135
|
+
`+`, `<<`, `==`, and the rest of the operator family:
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
# not flagged
|
|
139
|
+
def []=(row, column, value)
|
|
140
|
+
@cells[row][column] = value
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
This covers `def`, `def self.`, and `define_method(:[]=)`. A `define_method` whose
|
|
145
|
+
name is computed at runtime is still checked — the cop cannot know what the name
|
|
146
|
+
resolves to. Note the exemption is for *operator* methods, not ordinary writers:
|
|
147
|
+
`def name=(value)` takes a single argument and was never in danger of tripping the
|
|
148
|
+
limits anyway.
|
|
149
|
+
|
|
132
150
|
There is intentionally **no autocorrection**: the fix is a design decision (what
|
|
133
151
|
object should these arguments become?), and that belongs to a human.
|
|
134
152
|
|
|
@@ -214,13 +232,21 @@ intermediate results want names. Reaching for the right name (or extracting a
|
|
|
214
232
|
method) almost always reads better, and is easier to debug, than peeling
|
|
215
233
|
parentheses apart.
|
|
216
234
|
|
|
235
|
+
The point is not the assignment, it is the **name**. A local called `result` or
|
|
236
|
+
`tmp` buys nothing; a name that says what the value *is* turns the step into its
|
|
237
|
+
own documentation.
|
|
238
|
+
|
|
217
239
|
```ruby
|
|
218
240
|
# bad
|
|
219
241
|
wrap(parse(read(io)))
|
|
220
242
|
|
|
221
|
-
#
|
|
222
|
-
|
|
223
|
-
wrap(
|
|
243
|
+
# bad - named, but the name says nothing
|
|
244
|
+
result = parse(read(io))
|
|
245
|
+
wrap(result)
|
|
246
|
+
|
|
247
|
+
# good - the name documents what the value is
|
|
248
|
+
parsed_config = parse(read(io))
|
|
249
|
+
wrap(parsed_config)
|
|
224
250
|
|
|
225
251
|
# good - a single nested call is fine
|
|
226
252
|
puts compute(value)
|
|
@@ -515,6 +541,56 @@ Kaizo/NextInNonVoidEnumerable:
|
|
|
515
541
|
AllowedPatterns: []
|
|
516
542
|
```
|
|
517
543
|
|
|
544
|
+
## Plural names for collections
|
|
545
|
+
|
|
546
|
+
`Kaizo/PluralCollectionName` flags a method that hands back an array under a
|
|
547
|
+
singular name. The plural does the documenting for free — `users` tells the
|
|
548
|
+
caller what they are getting; `user` actively misleads them.
|
|
549
|
+
|
|
550
|
+
```ruby
|
|
551
|
+
# bad
|
|
552
|
+
def user
|
|
553
|
+
[first_match, second_match]
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
# good
|
|
557
|
+
def users
|
|
558
|
+
[first_match, second_match]
|
|
559
|
+
end
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
Ruby has no return types, so "returns an array" is a heuristic — and this cop
|
|
563
|
+
deliberately errs toward silence. A method is flagged only when **every** value
|
|
564
|
+
it can return is unambiguously an array: an array literal, or a call to a method
|
|
565
|
+
in `ArrayMethods` whose result is an `Array` whatever its receiver. A single
|
|
566
|
+
branch returning something else is enough to leave the method alone:
|
|
567
|
+
|
|
568
|
+
```ruby
|
|
569
|
+
# good - not confidently a collection, so not flagged
|
|
570
|
+
def user
|
|
571
|
+
return nil if missing?
|
|
572
|
+
|
|
573
|
+
[first_match, second_match]
|
|
574
|
+
end
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
`select` and `reject` are absent from the default `ArrayMethods` on purpose: on a
|
|
578
|
+
`Hash` they return a `Hash`, and including them would turn this into a
|
|
579
|
+
false-positive mill. A name counts as plural when it ends in `s` or appears in
|
|
580
|
+
`IrregularPlurals`. Predicate (`?`), writer (`=`), and operator methods are
|
|
581
|
+
exempt, as is `initialize`.
|
|
582
|
+
|
|
583
|
+
```yaml
|
|
584
|
+
Kaizo/PluralCollectionName:
|
|
585
|
+
AllowedMethods: []
|
|
586
|
+
IrregularPlurals:
|
|
587
|
+
- people # plural without a trailing `s`
|
|
588
|
+
- children
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
As with the other cops there is **no autocorrection** — only the author knows
|
|
592
|
+
the right plural.
|
|
593
|
+
|
|
518
594
|
## Development
|
|
519
595
|
|
|
520
596
|
```bash
|
data/config/default.yml
CHANGED
|
@@ -7,15 +7,38 @@
|
|
|
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.
|
|
13
15
|
Kaizo/KeywordArguments:
|
|
14
16
|
Description: 'Checks that a method does not declare too many keyword arguments.'
|
|
15
17
|
Enabled: true
|
|
16
18
|
VersionAdded: '0.1'
|
|
17
19
|
Max: 1
|
|
18
20
|
|
|
21
|
+
# `Kaizo/PluralCollectionName` flags a method that hands back an array under a
|
|
22
|
+
# singular name. Ruby has no return types, so this is a heuristic, and it errs
|
|
23
|
+
# toward silence: a method is flagged only when every value it can return is
|
|
24
|
+
# unambiguously an array. `ArrayMethods` is the set whose result is an `Array`
|
|
25
|
+
# whatever the receiver -- `select` and `reject` are absent on purpose, since on
|
|
26
|
+
# a `Hash` they return a `Hash`. A name counts as plural when it ends in `s` or
|
|
27
|
+
# appears in `IrregularPlurals`.
|
|
28
|
+
Kaizo/PluralCollectionName:
|
|
29
|
+
Description: 'Checks that a method returning a collection is named in the plural.'
|
|
30
|
+
Enabled: true
|
|
31
|
+
VersionAdded: '0.8'
|
|
32
|
+
AllowedMethods: []
|
|
33
|
+
IrregularPlurals:
|
|
34
|
+
- people
|
|
35
|
+
- children
|
|
36
|
+
- men
|
|
37
|
+
- women
|
|
38
|
+
- data
|
|
39
|
+
- media
|
|
40
|
+
- criteria
|
|
41
|
+
|
|
19
42
|
Kaizo/PositionalArguments:
|
|
20
43
|
Description: 'Checks that a method does not declare too many positional arguments.'
|
|
21
44
|
Enabled: true
|
|
@@ -153,6 +176,14 @@ Kaizo/NextInNonVoidEnumerable:
|
|
|
153
176
|
# `.rubocop.yml` at your peril.
|
|
154
177
|
Style/RedundantBegin:
|
|
155
178
|
Enabled: false
|
|
179
|
+
|
|
180
|
+
# Prefer Ruby 3.1's hash-value shorthand -- `Session.new(table:)` over
|
|
181
|
+
# `Session.new(table: table)` -- everywhere key and value name match. Shipped on
|
|
182
|
+
# by default because kaizo targets modern Ruby; override in your own config if
|
|
183
|
+
# you want the explicit form.
|
|
184
|
+
Style/HashSyntax:
|
|
185
|
+
EnforcedShorthandSyntax: always
|
|
186
|
+
|
|
156
187
|
# `Kaizo/SpecDescriptionProse` requires RSpec `it`/`context` descriptions to
|
|
157
188
|
# read as one-behavior prose: no commas, conjunctions (`Conjunctions`), or code
|
|
158
189
|
# in an `it` description; `context` descriptions carry no code and open with a
|
data/lib/kaizo/version.rb
CHANGED
|
@@ -6,30 +6,60 @@ 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
|
|
12
13
|
POSITIONAL_TYPES = %i[arg optarg].freeze
|
|
13
14
|
KEYWORD_TYPES = %i[kwarg kwoptarg].freeze
|
|
14
15
|
DEFINE_METHODS = %i[define_method define_singleton_method].freeze
|
|
15
16
|
STRUCT_OR_DATA = { "Struct" => :new, "Data" => :define }.freeze
|
|
17
|
+
|
|
18
|
+
# Ruby's operator method names, mirroring the list behind RuboCop's
|
|
19
|
+
# `operator_method?` (private upstream, so it cannot be reused). Needed
|
|
20
|
+
# only for the `define_method(:[]=)` form, where the name being defined is
|
|
21
|
+
# a symbol argument rather than a `def` node we could ask directly.
|
|
22
|
+
OPERATOR_METHOD_NAMES = [
|
|
23
|
+
:!, :!=, :"!@", :!~, :%, :&, :*, :**, :+, :+@, :-, :-@, :/, :<, :<<, :<=,
|
|
24
|
+
:<=>, :==, :===, :=~, :>, :>=, :>>, :[], :[]=, :^, :`, :|, :~, :"~@"
|
|
25
|
+
].freeze
|
|
16
26
|
MSG = "Method has too many %<kind>s. [%<count>d/%<max>d]".freeze
|
|
17
27
|
|
|
18
28
|
def on_def(node)
|
|
19
|
-
return if
|
|
29
|
+
return if exempt?(node)
|
|
20
30
|
|
|
21
31
|
check_arity(node)
|
|
22
32
|
end
|
|
23
33
|
alias on_defs on_def
|
|
24
34
|
|
|
25
35
|
def on_block(node)
|
|
26
|
-
|
|
36
|
+
return unless DEFINE_METHODS.include?(node.method_name)
|
|
37
|
+
return if defines_operator?(node)
|
|
38
|
+
|
|
39
|
+
check_arity(node)
|
|
27
40
|
end
|
|
28
41
|
alias on_numblock on_block
|
|
29
42
|
alias on_itblock on_block
|
|
30
43
|
|
|
31
44
|
private
|
|
32
45
|
|
|
46
|
+
# A definition whose argument count is not a design choice. An operator
|
|
47
|
+
# method's arity is fixed by Ruby's syntax -- `[]=` takes the indices plus
|
|
48
|
+
# the assigned value, `<=>` takes its right-hand side -- so it cannot be
|
|
49
|
+
# modeled away, and a `Struct`/`Data` `initialize` just mirrors the members
|
|
50
|
+
# the value object was declared with.
|
|
51
|
+
def exempt?(node)
|
|
52
|
+
node.operator_method? || allowed_initialize?(node)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# The same exemption for `define_method(:[]=)`, which names the method it
|
|
56
|
+
# defines with a symbol argument. A computed name is still checked -- we
|
|
57
|
+
# cannot know what it resolves to.
|
|
58
|
+
def defines_operator?(node)
|
|
59
|
+
defined_name = node.send_node.first_argument
|
|
60
|
+
defined_name&.sym_type? && OPERATOR_METHOD_NAMES.include?(defined_name.value)
|
|
61
|
+
end
|
|
62
|
+
|
|
33
63
|
def positional_arity(arguments)
|
|
34
64
|
arguments.count { |argument| POSITIONAL_TYPES.include?(argument.type) }
|
|
35
65
|
end
|
|
@@ -43,8 +73,8 @@ module RuboCop
|
|
|
43
73
|
count = arity(node.arguments)
|
|
44
74
|
return unless max && count > max
|
|
45
75
|
|
|
46
|
-
message = format(MSG, kind: self.class::KIND, count
|
|
47
|
-
add_offense(offense_location(node), message:
|
|
76
|
+
message = format(MSG, kind: self.class::KIND, count:, max:)
|
|
77
|
+
add_offense(offense_location(node), message:) { self.max = count }
|
|
48
78
|
end
|
|
49
79
|
|
|
50
80
|
def offense_location(node)
|
|
@@ -68,7 +68,7 @@ module RuboCop
|
|
|
68
68
|
count = own_sends(node) { |send| file_utils_call?(send) }.size
|
|
69
69
|
return if count < 2
|
|
70
70
|
|
|
71
|
-
add_offense(node.loc.name, message: format(MSG, count
|
|
71
|
+
add_offense(node.loc.name, message: format(MSG, count:, scope:))
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
# Sends in `namespace`'s own body that match the block -- not its
|
|
@@ -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
|
|
@@ -21,9 +23,9 @@ module RuboCop
|
|
|
21
23
|
# # bad
|
|
22
24
|
# wrap(parse(read(io)))
|
|
23
25
|
#
|
|
24
|
-
# # good - name the
|
|
25
|
-
#
|
|
26
|
-
# wrap(
|
|
26
|
+
# # good - a name that documents what the value is
|
|
27
|
+
# parsed_config = parse(read(io))
|
|
28
|
+
# wrap(parsed_config)
|
|
27
29
|
#
|
|
28
30
|
# # good - a single nested call is allowed
|
|
29
31
|
# puts compute(value)
|
|
@@ -48,7 +50,7 @@ module RuboCop
|
|
|
48
50
|
depth = nesting_depth(node)
|
|
49
51
|
return unless max && depth > max
|
|
50
52
|
|
|
51
|
-
add_offense(node, message: format(MSG, depth
|
|
53
|
+
add_offense(node, message: format(MSG, depth:, max:)) do
|
|
52
54
|
self.max = depth
|
|
53
55
|
end
|
|
54
56
|
end
|
|
@@ -113,11 +113,11 @@ module RuboCop
|
|
|
113
113
|
private
|
|
114
114
|
|
|
115
115
|
def flag_block_local_nexts(block_node, method)
|
|
116
|
-
message = format(MSG, method:
|
|
116
|
+
message = format(MSG, method:)
|
|
117
117
|
|
|
118
118
|
block_node.each_child_node do |child|
|
|
119
119
|
each_block_local_next(child) do |next_node|
|
|
120
|
-
add_offense(next_node.loc.keyword, message:
|
|
120
|
+
add_offense(next_node.loc.keyword, message:)
|
|
121
121
|
end
|
|
122
122
|
end
|
|
123
123
|
end
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
# @example
|
|
24
|
+
# # bad
|
|
25
|
+
# def user
|
|
26
|
+
# [first_match, second_match]
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# # good
|
|
30
|
+
# def users
|
|
31
|
+
# [first_match, second_match]
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# @example
|
|
35
|
+
# # good - not confidently an array, so not flagged
|
|
36
|
+
# def user
|
|
37
|
+
# return nil if missing?
|
|
38
|
+
#
|
|
39
|
+
# [first_match, second_match]
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
class PluralCollectionName < Base
|
|
43
|
+
include AllowedMethods
|
|
44
|
+
|
|
45
|
+
MSG = "Name a method that returns a collection in the plural. " \
|
|
46
|
+
"`%<name>s` returns an array.".freeze
|
|
47
|
+
|
|
48
|
+
# Methods whose result is an `Array` regardless of the receiver. Kept
|
|
49
|
+
# deliberately short: anything whose return type follows its receiver
|
|
50
|
+
# (`select` on a `Hash`) would turn this cop into a false-positive mill.
|
|
51
|
+
ARRAY_METHODS = %i[
|
|
52
|
+
map flat_map collect collect_concat to_a entries sort sort_by zip
|
|
53
|
+
].freeze
|
|
54
|
+
|
|
55
|
+
def on_def(node)
|
|
56
|
+
return if exempt?(node)
|
|
57
|
+
return unless returns_array?(node.body)
|
|
58
|
+
|
|
59
|
+
add_offense(node.loc.name, message: format(MSG, name: node.method_name))
|
|
60
|
+
end
|
|
61
|
+
alias on_defs on_def
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def exempt?(node)
|
|
66
|
+
return true if node.predicate_method? || node.assignment_method?
|
|
67
|
+
return true if node.operator_method? || node.method?(:initialize)
|
|
68
|
+
|
|
69
|
+
plural?(node.method_name.to_s) || allowed_method?(node.method_name)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def plural?(name)
|
|
73
|
+
name.end_with?("s") || irregular_plurals.include?(name)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def irregular_plurals
|
|
77
|
+
Array(cop_config["IrregularPlurals"])
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Every value the method can hand back must be an array before it is
|
|
81
|
+
# worth flagging, so that a single `return nil` keeps the cop quiet.
|
|
82
|
+
def returns_array?(body)
|
|
83
|
+
return false unless body
|
|
84
|
+
|
|
85
|
+
results = [final_expression(body), *explicit_returns(body)]
|
|
86
|
+
results.all? { |result| array_result?(result) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def final_expression(body)
|
|
90
|
+
body.begin_type? ? body.children.last : body
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# The value of each `return`, with a bare `return` contributing `nil` --
|
|
94
|
+
# which is exactly what should stop the method being flagged.
|
|
95
|
+
def explicit_returns(body)
|
|
96
|
+
body.each_descendant(:return).map { |node| node.children.first }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# A block-bearing call (`rows.map { ... }`) is a block node wrapping the
|
|
100
|
+
# send, so the method name lives one level down.
|
|
101
|
+
def array_result?(node)
|
|
102
|
+
return false unless node
|
|
103
|
+
return true if node.array_type?
|
|
104
|
+
|
|
105
|
+
call = node.any_block_type? ? node.send_node : node
|
|
106
|
+
call.call_type? && ARRAY_METHODS.include?(call.method_name)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -78,7 +78,7 @@ module RuboCop
|
|
|
78
78
|
message = violation(node.method_name, text)
|
|
79
79
|
return unless message
|
|
80
80
|
|
|
81
|
-
add_offense(node.first_argument, message:
|
|
81
|
+
add_offense(node.first_argument, message:)
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
private
|
|
@@ -95,7 +95,7 @@ module RuboCop
|
|
|
95
95
|
return COMMA_MSG if text.include?(",")
|
|
96
96
|
|
|
97
97
|
word = forbidden(text, conjunctions)
|
|
98
|
-
return format(CONJUNCTION_MSG, word:
|
|
98
|
+
return format(CONJUNCTION_MSG, word:) if word
|
|
99
99
|
|
|
100
100
|
CODE_MSG if code?(text)
|
|
101
101
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require_relative "kaizo/argument_counting"
|
|
2
2
|
require_relative "kaizo/keyword_arguments"
|
|
3
|
+
require_relative "kaizo/plural_collection_name"
|
|
3
4
|
require_relative "kaizo/positional_arguments"
|
|
4
5
|
require_relative "kaizo/total_arguments"
|
|
5
6
|
require_relative "kaizo/agent_noun_class_name"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kaizo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Gillis
|
|
@@ -62,6 +62,7 @@ files:
|
|
|
62
62
|
- lib/rubocop/cop/kaizo/keyword_arguments.rb
|
|
63
63
|
- lib/rubocop/cop/kaizo/nested_method_calls.rb
|
|
64
64
|
- lib/rubocop/cop/kaizo/next_in_non_void_enumerable.rb
|
|
65
|
+
- lib/rubocop/cop/kaizo/plural_collection_name.rb
|
|
65
66
|
- lib/rubocop/cop/kaizo/positional_arguments.rb
|
|
66
67
|
- lib/rubocop/cop/kaizo/prefer_pathname.rb
|
|
67
68
|
- lib/rubocop/cop/kaizo/spec_comment.rb
|
|
@@ -90,7 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
90
91
|
- !ruby/object:Gem::Version
|
|
91
92
|
version: '0'
|
|
92
93
|
requirements: []
|
|
93
|
-
rubygems_version: 4.0.
|
|
94
|
+
rubygems_version: 4.0.17
|
|
94
95
|
specification_version: 4
|
|
95
96
|
summary: A strict, punishing set of RuboCop design cops for AI-agent-authored Ruby.
|
|
96
97
|
test_files: []
|