kaizo 0.7.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7a167afcda94e477c2fd351f0f88c4c4f69b4adb987e0e74a59f50c516aa6080
4
+ data.tar.gz: f002c0b1464262edc8eb988f046b935fb9347d2258585c143acc6dd46911f86b
5
+ SHA512:
6
+ metadata.gz: 41ec7ca0e3f99dc13c761540b9ac7c06bd66f1cbcdecf776db8f1dd4dcd0c412008ae70f6ac73e6d9295662a27d70b3c2b2973930674ea7229fc1d173b4bb33e
7
+ data.tar.gz: f3ea240a663c70959adf03f95c5b49452ae3ae71a93818b8dcdedcf8bf1b72f10acd9d1db95494e12f7ee592a3162b8590a5d99603caca710126c810147bdaca
data/CHANGELOG.md ADDED
@@ -0,0 +1,92 @@
1
+ # Changelog
2
+
3
+ ## 0.6.0
4
+
5
+ - **Renamed the gem from `rubocop-design` to `kaizo`**, and moved every cop from
6
+ the `Design/` department into `Kaizo/`: `Design/PositionalArguments` is now
7
+ `Kaizo/PositionalArguments`, `Design/SpecComment` is now `Kaizo/SpecComment`,
8
+ and so on for every cop. This is a pure rename — no cop logic, thresholds, or
9
+ messages changed. Update your `Gemfile` (`gem 'kaizo'`), your `.rubocop.yml`
10
+ `plugins:` entry (`- kaizo`), and any cop names in your configuration.
11
+ - Add `Kaizo/SpecDescriptionProse` cop: requires RSpec `it`/`context`
12
+ descriptions to read as one-behavior prose. An `it` description may not contain
13
+ a comma, a conjunction (`Conjunctions`), or code; a `context` description
14
+ carries no code and must open with a `ContextPrefixes` word. `describe` strings
15
+ are exempt. No autocorrection. Enabled by default.
16
+ - Add `Kaizo/FileUtilsInclusion` cop: requires `FileUtils` to be mixed in with
17
+ `include`/`extend` once it is used more than once in a class or module, instead
18
+ of repeating the `FileUtils.` receiver. A single qualified call, and a
19
+ namespace that already mixes it in, are left alone; nested classes/modules are
20
+ counted on their own. No autocorrection. Enabled by default.
21
+ - Add `Kaizo/PreferPathname` cop: flags calls to `File` class methods that have a
22
+ `Pathname` instance-method equivalent (`File.read`, `File.exist?`, `File.join`,
23
+ ...), preferring `Pathname`. Runs on `**/*.rb`; `exe/**/*` and `bin/**/*` are
24
+ exempt by default (override with the standard `Include`/`Exclude`). No
25
+ autocorrection. Enabled by default.
26
+ - Add `Kaizo/ExplicitBegin` cop: requires an explicit `begin`/`end` block when
27
+ a method body attaches a `rescue` or `ensure` directly to the `def` (an
28
+ "implicit begin") -- the inverse of `Style/RedundantBegin`. Modifier `rescue`
29
+ (`foo rescue nil`) and endless method definitions are not flagged.
30
+ Autocorrection wraps the body in `begin`/`end`, and is skipped only for
31
+ single-line definitions and bodies holding heredocs or other multiline string
32
+ literals, where re-indenting could change string contents. Enabled by default.
33
+ - Add `Kaizo/NextInNonVoidEnumerable` cop: flags `next` inside the block of a
34
+ value-returning `Enumerable` method (`map`, `select`, `reduce`, and friends),
35
+ where `next` is being used as control-flow-as-value. Void iteration methods
36
+ (`each`, `each_with_object`, ...) and non-`Enumerable` loops (`loop`, `while`,
37
+ `Integer#times`) are never flagged, and a `next` bound to a nested block or
38
+ loop is attributed to that inner scope. Exempt methods with `AllowedMethods` /
39
+ `AllowedPatterns`. No autocorrection. Enabled by default.
40
+ - Loading the plugin now disables core's `Style/RedundantBegin`, which enforces
41
+ the opposite style; leaving both on would make their autocorrections loop.
42
+ Re-enable it explicitly in your `.rubocop.yml` if you do not want
43
+ `Kaizo/ExplicitBegin`.
44
+
45
+ ## 0.5.0
46
+
47
+ - Add `Design/SpecComment` cop: flags comments in spec files (`**/*_spec.rb`),
48
+ on the principle that a comment in a spec usually wants to be a `context`/`it`
49
+ description or a better-structured example. Magic comments, `# rubocop:`
50
+ directives, and shebangs are exempt; add more with `AllowedPatterns`. No
51
+ autocorrection. Enabled by default.
52
+
53
+ ## 0.4.0
54
+
55
+ - Add `Design/NestedMethodCalls` cop: flags method calls nested too deeply in
56
+ argument positions -- e.g. `foo(SomeClass.new(another("bar").chain))` -- on the
57
+ principle that the intermediate results want names. Only argument nesting is
58
+ counted (receiver chains are a separate concern); operator methods never count,
59
+ and `AllowedMethods` exempts named methods. Bounded by `Max` (default 1). No
60
+ autocorrection. Enabled by default.
61
+
62
+ ## 0.3.0
63
+
64
+ - **Renamed the gem from `rubocop-arity` to `rubocop-design`**, and moved every
65
+ cop into a single `Design/` department: `Arity/PositionalArguments`,
66
+ `Arity/KeywordArguments`, and `Arity/TotalArguments` are now
67
+ `Design/PositionalArguments`, `Design/KeywordArguments`, and
68
+ `Design/TotalArguments`. Update your `Gemfile`, your `.rubocop.yml`
69
+ `plugins:` entry, and any cop names in your configuration.
70
+
71
+ ## 0.2.0
72
+
73
+ - Add `Design/AgentNounClassName` cop: flags classes named as agent nouns
74
+ ("doers") — names ending in `er`/`or`, or in a configured `ForbiddenSuffixes`
75
+ entry (default `Service`/`Util`/`Utils`) — unless they end in an
76
+ `AllowedSuffixes` entry. Checks `class` definitions and
77
+ `Struct.new`/`Data.define`/`Class.new` assignments. No autocorrection.
78
+
79
+ ## 0.1.0
80
+
81
+ Initial release.
82
+
83
+ - Add `Arity/PositionalArguments` cop: bounds the number of positional
84
+ arguments (`Max`).
85
+ - Add `Arity/KeywordArguments` cop: bounds the number of keyword arguments
86
+ (`Max`).
87
+ - Add `Arity/TotalArguments` cop: bounds the combined number of positional and
88
+ keyword arguments (`Max`).
89
+
90
+ All three cops check `def`, `def self.`, `define_method`, and
91
+ `define_singleton_method`, ignore `*rest`/`**kwargs`/`&block`, and exempt the
92
+ `initialize` of a `Struct.new`/`Data.define` block.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Flipmine
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,529 @@
1
+ # Kaizo
2
+
3
+ A strict, punishing [RuboCop](https://rubocop.org) extension aimed at
4
+ AI-agent-authored Ruby — holding generated code to a demanding design bar by
5
+ bounding how many arguments a method declares, flagging class names that
6
+ describe an action rather than the concept they model, flagging method calls
7
+ nested too deeply in other calls' arguments, and treating comments and loose
8
+ descriptions in specs as prose that should become structure.
9
+
10
+ _Kaizo_ (改造) — "remodeling", "modification": the ruleset keeps applying
11
+ pressure until the code is remade into something better.
12
+
13
+ A long argument list is a smell: it usually means a method is juggling loose
14
+ primitives that want to be modeled as an object. By putting a ceiling on the
15
+ number of arguments, these cops apply steady pressure toward naming the
16
+ abstraction — an entity, a value object, a parameter object — instead of
17
+ threading five primitives through a signature.
18
+
19
+ ```ruby
20
+ # bad
21
+ def calculate_volume(width, length, height, shape_type)
22
+ end
23
+
24
+ # good
25
+ def calculate_volume(shape)
26
+ end
27
+ ```
28
+
29
+ ## Cops
30
+
31
+ | Cop | Bounds | Counts |
32
+ |-----|--------|--------|
33
+ | `Kaizo/PositionalArguments` | positional params | `arg`, `optarg` |
34
+ | `Kaizo/KeywordArguments` | keyword params | `kwarg`, `kwoptarg` |
35
+ | `Kaizo/TotalArguments` | positional + keyword | all of the above |
36
+
37
+ Each cop has a `Max` option. All three check `def`, `def self.`,
38
+ `define_method`, and `define_singleton_method`.
39
+
40
+ The three cops are independent and complementary — enable whichever dimensions
41
+ you want to bound. `TotalArguments` alone is a single global cap; pairing
42
+ `PositionalArguments` with `KeywordArguments` bounds each kind separately (so you
43
+ can, for instance, forbid positional arguments while allowing a couple of keyword
44
+ ones); running `TotalArguments` alongside them also catches methods that stay
45
+ under each per-kind limit but exceed the total. A method that breaks more than
46
+ one bound is reported once per cop it violates — by design — so enable the
47
+ smallest set that expresses your rule.
48
+
49
+ Beyond argument counts, the gem ships **`Kaizo/AgentNounClassName`**, which
50
+ flags classes named after what they *do* (see [Class naming](#class-naming)),
51
+ **`Kaizo/NestedMethodCalls`**, which flags calls buried too deeply in other
52
+ calls' arguments (see [Nested method calls](#nested-method-calls)),
53
+ **`Kaizo/SpecComment`**, which flags comments in spec files (see
54
+ [Comments in specs](#comments-in-specs)), **`Kaizo/SpecDescriptionProse`**,
55
+ which requires `it`/`context` descriptions to read as one-behavior prose (see
56
+ [Spec description prose](#spec-description-prose)),
57
+ **`Kaizo/FileUtilsInclusion`**, which asks you to `include`/`extend` `FileUtils`
58
+ once its methods are used more than once (see
59
+ [Including FileUtils](#including-fileutils)), **`Kaizo/PreferPathname`**, which
60
+ prefers `Pathname` over `File` for the operations `Pathname` provides (see
61
+ [Prefer Pathname](#prefer-pathname)), **`Kaizo/ExplicitBegin`**, which requires
62
+ an explicit `begin` for method bodies that `rescue` or `ensure` (see
63
+ [Explicit begin](#explicit-begin)), and **`Kaizo/NextInNonVoidEnumerable`**,
64
+ which flags `next` used as control-flow-as-value inside `map`/`select`/`reduce`
65
+ blocks (see [Next in value-returning blocks](#next-in-value-returning-blocks)).
66
+
67
+ ## Installation
68
+
69
+ Add to your `Gemfile`:
70
+
71
+ ```ruby
72
+ gem 'kaizo', require: false
73
+ ```
74
+
75
+ Enable the plugin in `.rubocop.yml`:
76
+
77
+ ```yaml
78
+ plugins:
79
+ - kaizo
80
+ ```
81
+
82
+ (Requires RuboCop 1.72.2+ for the `lint_roller` plugin API.)
83
+
84
+ ## Configuration
85
+
86
+ The defaults are deliberately strict — **at most one positional and one keyword
87
+ argument** — to apply maximum pressure toward modeling. Loosen them if that is
88
+ too aggressive for your codebase:
89
+
90
+ ```yaml
91
+ Kaizo/PositionalArguments:
92
+ Max: 1 # default
93
+ Kaizo/KeywordArguments:
94
+ Max: 1 # default
95
+ Kaizo/TotalArguments:
96
+ Max: 2 # default (one positional + one keyword)
97
+ ```
98
+
99
+ Setting `Max: 0` forbids a kind of argument entirely — for example, banning
100
+ positional arguments so that every parameter must be passed by keyword:
101
+
102
+ ```yaml
103
+ Kaizo/PositionalArguments:
104
+ Max: 0 # every argument must be passed by keyword
105
+ ```
106
+
107
+ ### What is counted
108
+
109
+ Only **named, individual** parameters count toward the limits:
110
+
111
+ - Positional: required (`a`) and optional (`a = 1`).
112
+ - Keyword: required (`a:`) and optional (`a: 1`).
113
+
114
+ The variadic collectors `*rest`, `**keyword_rest`, `&block`, and `...` are **not**
115
+ counted — they are single tokens, not a list of primitives.
116
+
117
+ ### Exemptions
118
+
119
+ The `initialize` of a `Struct.new` or `Data.define` block is exempt, because its
120
+ parameters mirror the value object's attributes — which is exactly the modeling
121
+ these cops are meant to encourage:
122
+
123
+ ```ruby
124
+ # not flagged
125
+ Data.define(:width, :height, :depth, :weight) do
126
+ def initialize(width:, height:, depth:, weight:)
127
+ super
128
+ end
129
+ end
130
+ ```
131
+
132
+ There is intentionally **no autocorrection**: the fix is a design decision (what
133
+ object should these arguments become?), and that belongs to a human.
134
+
135
+ ## Relationship to `Metrics/ParameterLists`
136
+
137
+ Core RuboCop's `Metrics/ParameterLists` enforces a single maximum on the whole
138
+ parameter list. `kaizo` is more granular: it bounds positional and
139
+ keyword arguments separately (and together), and is framed around domain
140
+ modeling rather than method complexity. Use whichever fits; they can coexist.
141
+
142
+ For the full rationale — why the counting is implemented here rather than reusing
143
+ or configuring `Metrics/ParameterLists`, with reproducible evidence — see
144
+ [docs/why-not-metrics-parameterlists.md](docs/why-not-metrics-parameterlists.md).
145
+
146
+ ## Known limitations
147
+
148
+ - **The proc/lambda form of `define_method` is not inspected.** Only the block
149
+ form (`define_method(:foo) { |a, b| }`) is checked. When the body is supplied
150
+ as a callable — `define_method(:foo, ->(a, b) {})` or
151
+ `define_method(:foo, captured_method)` — it is left alone, because the argument
152
+ may be any object that responds to `call` and is not statically countable in
153
+ the general case.
154
+ - **Numbered and `it` block parameters count as zero.**
155
+ `define_method(:squared) { _1 * _1 }` is treated as taking no arguments:
156
+ implicit block parameters are not part of a declared signature, which is what
157
+ these cops measure. Spell the parameters out if you want them counted.
158
+
159
+ ## Class naming
160
+
161
+ `Kaizo/AgentNounClassName` flags classes named as agent nouns — "doers" —
162
+ rather than the domain concepts they model. A class whose name ends in `er`/`or`
163
+ (`OrderManager`, `PaymentProcessor`, `RequestHandler`), or in a configured
164
+ forbidden suffix like `Service`, usually means procedural behavior that wants a
165
+ clearer name or a different home.
166
+
167
+ ```ruby
168
+ # bad
169
+ class PaymentProcessor
170
+ end
171
+
172
+ # good
173
+ class Payment
174
+ end
175
+ ```
176
+
177
+ It checks `class` definitions and `Struct.new` / `Data.define` / `Class.new`
178
+ constant assignments. Like the argument-count cops, there is **no autocorrection** — a
179
+ rename is a design decision.
180
+
181
+ ### Tuning the lists
182
+
183
+ Two suffix lists drive it, both fully configurable and matched against the last
184
+ segment of a namespaced name (`Billing::InvoiceBuilder` is checked as
185
+ `InvoiceBuilder`):
186
+
187
+ - **`AllowedSuffixes`** — exempt these. Matched as a suffix, so `Controller`
188
+ clears `Controller` and `UsersController` alike. Ships with a broad default of
189
+ legitimate `-er`/`-or` words — domain nouns (`Order`, `User`, `Number`,
190
+ `Error`) and framework terms (`Controller`, `Serializer`, `Adapter`).
191
+ - **`ForbiddenSuffixes`** — always flag these, even when they don't end in
192
+ `-er`/`-or` (default: `Service`, `Util`, `Utils`). This list **wins** over
193
+ `AllowedSuffixes`, so it doubles as the way to drop a default exemption.
194
+
195
+ Extend either list without restating the default using RuboCop's `inherit_mode`:
196
+
197
+ ```yaml
198
+ Kaizo/AgentNounClassName:
199
+ inherit_mode:
200
+ merge:
201
+ - AllowedSuffixes
202
+ - ForbiddenSuffixes
203
+ AllowedSuffixes:
204
+ - Ledger # OrderLedger now passes
205
+ ForbiddenSuffixes:
206
+ - Server # ApiServer now flagged, despite the default allowance
207
+ ```
208
+
209
+ ## Nested method calls
210
+
211
+ `Kaizo/NestedMethodCalls` flags method calls nested too deeply in **argument**
212
+ positions — `foo(SomeClass.new(another("bar").chain))` — on the principle that the
213
+ intermediate results want names. Reaching for the right name (or extracting a
214
+ method) almost always reads better, and is easier to debug, than peeling
215
+ parentheses apart.
216
+
217
+ ```ruby
218
+ # bad
219
+ wrap(parse(read(io)))
220
+
221
+ # good - name the intermediate result
222
+ parsed = parse(read(io))
223
+ wrap(parsed)
224
+
225
+ # good - a single nested call is fine
226
+ puts compute(value)
227
+ ```
228
+
229
+ Depth is bounded by `Max` (default `1` — one nested call is allowed). Only nesting
230
+ through **arguments** is counted; a *receiver chain* such as
231
+ `user.account.owner.name` is a separate concern (a dedicated chaining cop is
232
+ planned). Operator methods (`a + b`, `arr[i]`) never count, block bodies are not
233
+ traversed, and `AllowedMethods` exempts calls to named methods:
234
+
235
+ ```yaml
236
+ Kaizo/NestedMethodCalls:
237
+ Max: 1 # default; raise to allow deeper nesting
238
+ AllowedMethods:
239
+ - expect # e.g. don't count RSpec's expect(...) wrapper
240
+ ```
241
+
242
+ Like the other cops, there is **no autocorrection** — choosing the intermediate
243
+ name is a design decision.
244
+
245
+ ## Comments in specs
246
+
247
+ `Kaizo/SpecComment` flags comments in spec files. A comment in a spec is almost
248
+ always a sign that the spec is doing the job of its own description: if you need a
249
+ sentence to explain what an example sets up or asserts, that sentence usually
250
+ wants to be a `context`/`it` description, a clearer example name, or another
251
+ example — not prose riding alongside the code.
252
+
253
+ ```ruby
254
+ # bad
255
+ it 'permits the request' do
256
+ # an admin can see everything
257
+ user = create(:user, admin: true)
258
+ expect(policy).to permit(user)
259
+ end
260
+
261
+ # good
262
+ it 'permits an admin to see everything' do
263
+ admin = create(:user, admin: true)
264
+ expect(policy).to permit(admin)
265
+ end
266
+ ```
267
+
268
+ By default only `*_spec.rb` files are inspected. Magic comments
269
+ (`# frozen_string_literal: true`, `# encoding: …`), RuboCop directives
270
+ (any `# rubocop:` comment), and shebangs are never flagged. Like
271
+ the other cops, there is **no autocorrection** — turning an explanation into a
272
+ spec is a design decision.
273
+
274
+ ### Scope and escape hatches
275
+
276
+ The cop is scoped through its `Include`, so broaden it to cover support files or a
277
+ Minitest suite (using `inherit_mode: merge` to add to the default rather than
278
+ replace it):
279
+
280
+ ```yaml
281
+ Kaizo/SpecComment:
282
+ inherit_mode:
283
+ merge:
284
+ - Include
285
+ Include:
286
+ - '**/spec/**/*' # spec_helper, support/, factories
287
+ - '**/*_test.rb' # Minitest / Test::Unit
288
+ ```
289
+
290
+ Permit specific comments with `AllowedPatterns` — regexps matched against the
291
+ full comment text, leading `#` included:
292
+
293
+ ```yaml
294
+ Kaizo/SpecComment:
295
+ AllowedPatterns:
296
+ - '\A#\s*@rbs' # rbs-inline type annotations
297
+ - 'noqa'
298
+ ```
299
+
300
+ ## Spec description prose
301
+
302
+ `Kaizo/SpecDescriptionProse` requires RSpec `it`/`context` descriptions to read
303
+ as one-behavior prose specifications. Every rule is **structural** — it fires
304
+ only when the wording signals that one example is really more than one, or that
305
+ the assertion is leaking into the name.
306
+
307
+ An `it`/`specify`/`example` description must not contain:
308
+
309
+ - a **comma** — a list is several behaviors;
310
+ - a **conjunction** (`and`, `or`, `so`, `when`, `if`, `unless`, … — the
311
+ `Conjunctions` list) — joined clauses are separate examples, and a condition
312
+ belongs in a `context`;
313
+ - **code** — `_ : # = { } ! [ ]`, a backtick, or a nested quoted literal;
314
+ a description is prose, not identifiers or wire values.
315
+
316
+ A `context` description must not contain code, and must open with a word from
317
+ `ContextPrefixes` (`when`/`with`/`without`/`after`). `describe` strings name the
318
+ unit under test and are exempt.
319
+
320
+ ```ruby
321
+ # bad
322
+ it "renders the name, image, and flag"
323
+ it "omits the key when the role is unset"
324
+ it "renders the :cpu member"
325
+ context "the role is unset" do
326
+ end
327
+
328
+ # good
329
+ it "renders the name"
330
+ it "renders the cpu member"
331
+ context "when the role is unset" do
332
+ it "omits the key"
333
+ end
334
+ ```
335
+
336
+ The defaults are deliberately curated, not exhaustive: `for` is dropped from the
337
+ conjunctions (it is a preposition in nearly every description), and homographs
338
+ like `even`/`given`/`regardless` are left out (they collide with `even numbers`
339
+ and the like) — add them via `Conjunctions` if you want them. Pure **wording**
340
+ preferences that don't change structure (e.g. `should` vs a present-tense verb)
341
+ are out of scope — rubocop-rspec's `RSpec/ExampleWording` already covers those.
342
+ There is no autocorrection: splitting an example, or extracting a condition into
343
+ a `context`, is a modelling decision for a human.
344
+
345
+ ## Including FileUtils
346
+
347
+ `Kaizo/FileUtilsInclusion` flags repeated qualified `FileUtils.` calls within a
348
+ class or module: once you reach for `FileUtils` more than once, `include` it (for
349
+ instance-level use) or `extend` it (for class/singleton-level use) and call its
350
+ methods unqualified.
351
+
352
+ ```ruby
353
+ # bad
354
+ class Backup
355
+ def run
356
+ FileUtils.mkdir_p(dir)
357
+ FileUtils.cp(src, dir)
358
+ end
359
+ end
360
+
361
+ # good
362
+ class Backup
363
+ include FileUtils
364
+
365
+ def run
366
+ mkdir_p(dir)
367
+ cp(src, dir)
368
+ end
369
+ end
370
+ ```
371
+
372
+ The class or module is reported once. A single qualified call is left alone, a
373
+ namespace that already mixes `FileUtils` in is not flagged, and nested classes
374
+ and modules are counted on their own (one call in an outer class and one in a
375
+ nested class do not add up). As with most of the cops here, there is **no
376
+ autocorrection** — whether to `include` or `extend`, and where the mixin belongs,
377
+ is a design decision.
378
+ ## Prefer Pathname
379
+
380
+ `Kaizo/PreferPathname` flags calls to `File` class methods that have a `Pathname`
381
+ instance-method equivalent — `File.read`, `File.exist?`, `File.join`,
382
+ `File.expand_path`, and the like. Once a path is a `Pathname`, calling the method
383
+ on it reads better than threading a string through `File`.
384
+
385
+ ```ruby
386
+ # bad
387
+ File.read(path)
388
+ File.exist?(path)
389
+ File.join(dir, name)
390
+
391
+ # good
392
+ path.read
393
+ path.exist?
394
+ dir.join(name)
395
+ ```
396
+
397
+ The banned set is the intersection of `File`'s class methods and `Pathname`'s own
398
+ public instance methods (so `File.new`, and `File.path` whose `Pathname#path`
399
+ equivalent is protected, are left alone).
400
+ The cop runs on `**/*.rb` and skips `exe/**/*` and `bin/**/*` by default —
401
+ executables often work with raw path strings — which you can adjust with the
402
+ standard `Include`/`Exclude` options:
403
+
404
+ ```yaml
405
+ Kaizo/PreferPathname:
406
+ Exclude:
407
+ - 'exe/**/*'
408
+ - 'bin/**/*'
409
+ - 'db/**/*' # add your own
410
+ ```
411
+
412
+ Because the ban is broad, a few equivalents are not drop-in replacements:
413
+ `Pathname#join` treats an absolute segment as a reset (`Pathname("a").join("/b")`
414
+ is `/b`, where `File.join("a", "/b")` is `a/b`), `Pathname#chmod`/`chown`/`utime`
415
+ act on the single receiver (where `File.chmod` is variadic over many paths), and
416
+ `Pathname#split`/`rename` differ in return type and arity. The cop only points;
417
+ mind those differences when you rewrite — part of why it does not autocorrect.
418
+
419
+ As with most of the cops here, there is **no autocorrection** — rewriting
420
+ `File.read(path)` as `Pathname(path).read` changes the receiver and is a call for
421
+ a human.
422
+
423
+ ## Explicit begin
424
+
425
+ `Kaizo/ExplicitBegin` requires an explicit `begin`/`end` block when a method
426
+ body attaches a `rescue` or `ensure` directly to the `def` — Ruby's "implicit
427
+ begin". It is the inverse of core's `Style/RedundantBegin`. An explicit `begin`
428
+ names the guarded region and keeps it bounded: it marks exactly what the
429
+ `rescue`/`ensure` covers, so the method can grow other statements without
430
+ silently widening what is rescued.
431
+
432
+ ```ruby
433
+ # bad
434
+ def foo
435
+ do_something
436
+ ensure
437
+ cleanup
438
+ end
439
+
440
+ # good
441
+ def foo
442
+ begin
443
+ do_something
444
+ ensure
445
+ cleanup
446
+ end
447
+ end
448
+ ```
449
+
450
+ Modifier rescues (`foo rescue nil`) and endless method definitions are not
451
+ flagged. Unlike the other cops, this one **does autocorrect** (`rubocop -a`) —
452
+ wrapping a body in `begin`/`end` is a mechanical fix, not a design decision. The
453
+ correction is skipped when the body does not sit on its own lines between `def`
454
+ and `end` (a single-line definition, say), or contains a heredoc or other
455
+ multiline string, symbol, or regexp literal, where re-indenting could change
456
+ their contents.
457
+
458
+ Because `Style/RedundantBegin` enforces the exact opposite style, loading this
459
+ plugin **disables it** by default — otherwise the two autocorrections would loop
460
+ forever, each undoing the other. Re-enable it explicitly in your `.rubocop.yml`
461
+ if you would rather not require explicit begins:
462
+
463
+ ```yaml
464
+ Style/RedundantBegin:
465
+ Enabled: true # opt back out of Kaizo/ExplicitBegin
466
+ ```
467
+
468
+ ## Next in value-returning blocks
469
+
470
+ `Kaizo/NextInNonVoidEnumerable` flags `next` inside the block of a
471
+ value-returning `Enumerable` method — `map`, `select`, `filter_map`, `reduce`,
472
+ `sum`, `group_by`, the `*_by` methods, the `any?`/`all?`/`none?`/`one?`
473
+ predicates, and so on — where `next` is being used as control-flow-as-value.
474
+
475
+ ```ruby
476
+ # bad
477
+ array.map do |item|
478
+ next if skip?(item)
479
+ transform(item)
480
+ end
481
+
482
+ # bad - `next <value>` counts too
483
+ array.reduce(0) do |sum, item|
484
+ next sum if skip?(item)
485
+ sum + item
486
+ end
487
+
488
+ # good - void iteration method; `next` just skips the iteration
489
+ array.each do |item|
490
+ next if skip?(item)
491
+ process(item)
492
+ end
493
+
494
+ # good - say what you mean
495
+ array.filter_map { |item| transform(item) unless skip?(item) }
496
+ ```
497
+
498
+ Only *void* iteration methods — those whose block return value is ignored
499
+ (`each`, `each_with_index`, `each_slice`, `each_with_object`, `reverse_each`,
500
+ `cycle`, …) — are meant to use `next`, which is why they are absent from the
501
+ flagged set. Non-`Enumerable` looping constructs (`loop`, `Integer#times`,
502
+ `while`) are likewise never flagged. A `next` that binds to a nested block or
503
+ loop is attributed to that inner scope, so an inner `each { next }` or
504
+ `while … next … end` does not flag an outer `map`.
505
+
506
+ As with most of the cops here, there is **no autocorrection** — the right fix
507
+ depends on intent (a guard clause might become a ternary, a `select`/`reject`, a
508
+ `filter_map`, or a restructured block). Exempt specific methods with
509
+ `AllowedMethods` / `AllowedPatterns`:
510
+
511
+ ```yaml
512
+ Kaizo/NextInNonVoidEnumerable:
513
+ AllowedMethods:
514
+ - reduce # allow `next <acc>` guards in reduce/inject
515
+ AllowedPatterns: []
516
+ ```
517
+
518
+ ## Development
519
+
520
+ ```bash
521
+ bin/setup # install dependencies
522
+ bundle exec rake # run specs + self-lint
523
+ bundle exec rake spec # specs only
524
+ ```
525
+
526
+ ## License
527
+
528
+ Available as open source under the terms of the
529
+ [MIT License](https://opensource.org/licenses/MIT).