rubocop-kata 0.8.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da7876f7f43029f7dfb90b0fb9527987fe9c50ee40336d0ba4fffdcc2cbad83f
4
- data.tar.gz: 902e0c01d3cc5e71553d3c90c68a707b7f3a554cc3e6ec1228f0640ae8772d6a
3
+ metadata.gz: 6c1ecc1a90d7d43dba2861011f7f9c8c1ecc97e71040908f1018df3199aca660
4
+ data.tar.gz: 216cb124bc0565ab89d5cf3ee52225e24771d6f4d0812e8a272b0fc14517e45e
5
5
  SHA512:
6
- metadata.gz: 99fdeb3a9f112fbc7232f59da504bde048071f20d50e55ef367e5516119a307a68a4f071a371cdbbe7810248268ef526f5bbdec08c5e9068e14d17cee03661ca
7
- data.tar.gz: 9603d8560c96c877a5cb8f614bf10cab2aabf63aa5aace3209cc0596b06dab7a0edd4372919068a5c345b48cff076c1964b453ba38caa1dd3d026a96ad6c8371
6
+ metadata.gz: 5a806b0b657d6fa01fe422361616304f6581e23e041f76a70d3dc33f61814a600a808439282417de3f8ea34d03612927fdfcbced3f18010b90660407ffa165f7
7
+ data.tar.gz: 687dd4b67fb477cd51e4440e79f7d81a020f4712c1802541447eb489f97be8fab728cb26460c947113a2789f1fddf1f0e93ecd9bad70d2eadd232cbe025bf74d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,67 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.0] - 2026-08-27
4
+
5
+ - The shared rule behind the naming cops is now explicit: compound syntax is
6
+ not the smell — multiple semantic concepts are. A name should name one thing,
7
+ not describe where it came from, what it manipulates, or which layer it
8
+ lives in.
9
+ - New `Kata/GoodClassName` replaces `Kata/AgentNoun` and the class half of
10
+ `Kata/NoUtilName`: a class names one object that exists in the model. It
11
+ keeps the doer check and its derived suggestions, adds junk-drawer names
12
+ matched whole or as a trailing segment (`Service` catches `InvoiceService`),
13
+ and flags names packing more than `MaxWords` concepts
14
+ (`CustomerOrderPayment`) unless `Terms` blesses them as one concept
15
+ (`CreditCard`). The default `Terms` ships the established technical
16
+ compounds: `AbstractSyntaxTree`, `RedBlackTree`, `UnitOfWork`,
17
+ `TimeWithZone`, `HashWithIndifferentAccess` and kin.
18
+ - New `Kata/GoodModuleName` replaces the module half of `Kata/NoUtilName`: a
19
+ module names a domain vocabulary, not an implementation category. Layer
20
+ buckets (`Services`, `Helpers`, `Logic` — so `BusinessLogic` too), vague
21
+ shared namespaces (`Common`, `Shared`, `Core`), doer modules, and crowded
22
+ names are flagged; `AccessControl` and `Billing` pass.
23
+ - `Kata/GoodMethodName` flags names chaining actions with a conjunction
24
+ (`validate_and_save`, `find_or_create`): each action wants its own method.
25
+ Traversal names (`customer_address`) were already priced by the word rule;
26
+ the message stays advisory — the cop cannot know your object model.
27
+ - `Kata/GoodMethodName` and `Kata/GoodVariableName` bless grammatical
28
+ suffixes `id` and `ms` (`user_id`, `timeout_ms`) alongside `at`, the role
29
+ prefixes `max`/`min`/`start`/`end` (`max_retries`, `start_time`), and ship
30
+ established lexical compounds and protocol vocabulary as default `Terms`:
31
+ `first_name`, `postal_code`, `time_zone`, `ip_address`, `user_agent`,
32
+ `mime_type`, `status_code`, `access_token` and kin — names that read as one
33
+ concept. Where names must mirror an external schema, `Exclude` the file:
34
+ the file is the boundary.
35
+ - The defaults were then pressure-tested against corpora of real stdlib, gem,
36
+ Rails, and business-domain names, and grew to absorb what any codebase hits:
37
+ role suffixes (`number`, `code`, `price`, `rate`, `date`, `path`, `url`,
38
+ `count`, `size`, `key`, `token`, `params`, `secret`, `level`, `file`, `dir`,
39
+ `period`, `seconds`, `cents`), role prefixes (`current`, `default`, `total`,
40
+ `new`, `other`, `raw`, `set`, `sort`, `expected`, `actual`, `assert`),
41
+ domain terms (`line_item`, `credit_card`, `payment_method`,
42
+ `billing_address`, `date_of_birth`, `dry_run`, `stack_trace` and kin), and
43
+ the contract names of Ruby, Rails, Sidekiq, Devise, Pundit and RSpec
44
+ (`perform_async`, `deconstruct_keys`, `authenticate_user!`, `policy_scope`,
45
+ `failure_message` and kin). Name suffixes that would bless owner+property
46
+ (`name`, `message`, `amount`) stayed out on purpose: `user_name` and
47
+ `error_message` are still priced.
48
+ - `Kata/GoodClassName` `AllowedNames` also covers the class families a
49
+ framework resolves or subclasses by name (`Validator`, `Decorator`,
50
+ `Presenter`, `Helper`, `Job`, `Logger`, `Delegator`, `Enumerator`,
51
+ `Driver`) and more thing-words (`Center`, `Transfer`);
52
+ `Kata/GoodModuleName` mirrors the class cop's allowed list, and its
53
+ `BannedNames` grows the layer plurals (`Jobs`, `Workers`, `Queries`,
54
+ `Policies`, `Forms`, `Decorators`, `Presenters`, `Serializers`,
55
+ `Validators`, `Mixins`, `Extensions`).
56
+ - The doer check no longer flags thing-words that merely end in `-er`/`-or`:
57
+ `User`, `Order`, `Customer`, `Monitor`, `Buffer` and kin ship in
58
+ `Kata/GoodClassName` `AllowedNames`. `Kata/GoodModuleName` ships the
59
+ framework suffixes (`Controller`, `Mailer`, `Serializer`) so reopening
60
+ `ActionController` is not an offense. `Kata/GoodMethodName` exempts the
61
+ names Ruby and Rails resolve by contract: `method_missing`,
62
+ `respond_to_missing?`, `marshal_dump`, `deep_dup`, `table_name`,
63
+ `primary_key` and kin.
64
+
3
65
  ## [0.8.0] - 2026-08-20
4
66
 
5
67
  - New `rubocop-kata plan` command: buckets the backlog into `structure`,
data/README.md CHANGED
@@ -14,10 +14,10 @@ that dodge with a shipped dictionary instead of a word list you maintain:
14
14
 
15
15
  - **One dependency.** Bundles rubocop-rspec, rubocop-performance, rubocop-elegant, rubocop-packaging, and rubocop-thread_safety behind a single gem.
16
16
  - **Opinionated defaults.** Methods under 5 lines, classes under 100, 4 parameters max, 120-column lines, double quotes, `NewCops: enable`. See [config/default.yml](config/default.yml).
17
- - **Fifteen house cops.** Naming, dependency discipline, and data honesty in the Elegant Objects spirit — from `Kata/AgentNoun` to `Kata/ClockDiscipline`.
17
+ - **Fifteen house cops.** Naming, dependency discipline, and data honesty in the Elegant Objects spirit — from `Kata/GoodClassName` to `Kata/ClockDiscipline`.
18
18
 
19
19
  ```ruby
20
- class PaymentProcessor # Kata/AgentNoun: `PaymentProcessor` names a doer; name the class
20
+ class PaymentProcessor # Kata/GoodClassName: `PaymentProcessor` names a doer; name the class
21
21
  end # for the thing it is, not the work it does. Try `Payment`.
22
22
 
23
23
  class Payment # OK
@@ -102,13 +102,13 @@ top-level constant by design.
102
102
 
103
103
  | Cop | Default | What it enforces |
104
104
  | --- | --- | --- |
105
- | `Kata/AgentNoun` | on | Classes named for what they are, not `-er`/`-or` doers. Suggests the better name when it can derive one. `AllowedNames` matches a whole name or a trailing segment, so `Error` covers `UsageError`. |
105
+ | `Kata/GoodClassName` | on | A class names one object that exists in the model: no `-er`/`-or` doers (suggests the better name when it can derive one), no junk drawers (`Service`, `Util`, `Data`), no packing several concepts into one name (`CustomerOrderPayment`). Compound syntax is not the smell — `CreditCard` is one concept, and `Terms` holds those. `AllowedNames` and `BannedNames` match a whole name or a trailing segment, so `Error` covers `UsageError` and `Service` catches `InvoiceService`. |
106
+ | `Kata/GoodModuleName` | on | A module names a domain vocabulary (`Billing`, `AccessControl`), not an implementation category: no layer buckets (`Services`, `Helpers`, `Logic`), no vague shared namespaces (`Common`, `Shared`, `Core`), no doers. Tune via `BannedNames`/`Terms`/`AllowedNames`. |
106
107
  | `Kata/NoComments` | on | No prose comments; say it in the code. Magic comments, linter directives, and licence headers survive. Autocorrects. |
107
108
  | `Kata/IoDiscipline` | on | No bare `puts`/`warn`/`pp`/`p` outside the test suite and the boot layer; write through an injected `@io` or an explicit receiver. |
108
109
  | `Kata/ProsePlacement` | off | Sentence-length strings belong in the presentation layer. Enable with an `Include`/`Exclude` matching your layering. |
109
- | `Kata/NoUtilName` | on | No junk-drawer names (`Util`, `Helper`, `Manager`, `Service`, …). Tune via `BannedNames`. |
110
110
  | `Kata/RealWords` | on | Every name segment is a word the shipped dictionary knows — `errorcount` (smash) and `cfg` (abbreviation) both fail, with no word list to maintain. Tune via `Terms`/`BannedWords`/`AllowedNames`. |
111
- | `Kata/GoodMethodName` | on | One word per method name; a second word needs a role prefix (`after_fork`), a role suffix (`file_of`), or a reviewed `Terms` entry. Tune via `MaxWords`/`Prefixes`/`Suffixes`/`Terms`/`AllowedNames`. |
111
+ | `Kata/GoodMethodName` | on | One word per method name; a second word needs a role prefix (`after_fork`), a role suffix (`file_of`), or a reviewed `Terms` entry. A name chaining actions (`validate_and_save`, `find_or_create`) is flagged as two methods in one. Tune via `MaxWords`/`Prefixes`/`Suffixes`/`Terms`/`AllowedNames`. |
112
112
  | `Kata/GoodVariableName` | on | The same rule for locals, parameters, ivars, class variables, and globals; `_name` and `@_name` stay exempt. |
113
113
  | `Kata/BuilderNoun` | on | Builders named for what they return: `total`, not `calculate_total`. Tune via `BannedPrefixes`/`AllowedNames`. |
114
114
  | `Kata/NoBooleanFlag` | on | No positional boolean arguments; split the method or use a keyword. |
@@ -150,7 +150,7 @@ bundle exec rubocop-kata plan # or: plan path/to/project
150
150
 
151
151
  ```
152
152
  structure 184 Kata/ConstructorDiscipline 92, Kata/NoHashAsObject 48, Kata/EnvDiscipline 44
153
- naming 263 Kata/RealWords 141, Kata/GoodVariableName 88, Kata/AgentNoun 34
153
+ naming 263 Kata/RealWords 141, Kata/GoodVariableName 88, Kata/GoodClassName 34
154
154
  prose 57 Kata/NoComments 57
155
155
  rest 412 Elegant/PairedBrackets 300, Layout/LineLength 112
156
156
  next: structure — 184 offenses in 61 files; these mint the names `naming` then prices, so take them first
data/config/default.yml CHANGED
@@ -10,20 +10,159 @@ AllCops:
10
10
  Exclude:
11
11
  - "vendor/**/*"
12
12
 
13
- Kata/AgentNoun:
13
+ Kata/GoodClassName:
14
14
  Description: >-
15
- Name classes for the thing they are, not the work they do. `AllowedNames`
16
- entries match a whole name or a trailing segment of one, so `Error` also
17
- exempts `UsageError`. The default list holds the suffixes a framework
18
- resolves a class by, which are contracts rather than naming choices.
15
+ A class names one object that exists in the model. Three shapes hide that
16
+ object: a doer name (`-er`/`-or`), a junk-drawer name (`Service`, `Util`,
17
+ `Data`), and a name packing several concepts (`CustomerOrderPayment`).
18
+ Compound syntax is not the smell `CreditCard` is one concept, and belongs
19
+ in `Terms`. `AllowedNames` and `BannedNames` match a whole name or a
20
+ trailing segment, so `Error` also exempts `UsageError` and `Service` also
21
+ catches `InvoiceService`. The default `AllowedNames` holds the suffixes a
22
+ framework resolves a class by, which are contracts rather than naming
23
+ choices, plus thing-words that merely end in `-er`/`-or` — `User` and
24
+ `Order` are objects, not doers. Absorbs Kata/AgentNoun and the class half
25
+ of Kata/NoUtilName.
19
26
  Enabled: true
20
- VersionAdded: "0.1"
21
- VersionChanged: "0.8"
27
+ VersionAdded: "0.9"
28
+ MaxWords: 2
22
29
  AllowedNames:
30
+ - Actor
31
+ - Author
32
+ - Buffer
33
+ - Center
34
+ - Chapter
35
+ - Character
36
+ - Color
37
+ - Container
38
+ - Controller
39
+ - Cursor
40
+ - Customer
41
+ - Decorator
42
+ - Delegator
43
+ - Driver
44
+ - Enumerator
23
45
  - Error
46
+ - Fiber
47
+ - Folder
48
+ - Helper
49
+ - Job
50
+ - Ledger
51
+ - Logger
52
+ - Mailer
53
+ - Member
54
+ - Monitor
55
+ - Order
56
+ - Owner
57
+ - Presenter
58
+ - Serializer
59
+ - Server
60
+ - Supplier
61
+ - Timer
62
+ - Transfer
63
+ - User
64
+ - Validator
65
+ - Vector
66
+ BannedNames:
67
+ - Util
68
+ - Utils
69
+ - Utility
70
+ - Utilities
71
+ - Service
72
+ - Data
73
+ - Info
74
+ - Misc
75
+ Terms:
76
+ - AbstractSyntaxTree
77
+ - BillOfLading
78
+ - BinarySearchTree
79
+ - HashWithIndifferentAccess
80
+ - JsonWebToken
81
+ - OneTimePassword
82
+ - RedBlackTree
83
+ - StockKeepingUnit
84
+ - TimeWithZone
85
+ - UnitOfWork
86
+
87
+ Kata/GoodModuleName:
88
+ Description: >-
89
+ A module names a domain vocabulary or boundary, not an implementation
90
+ category. Layer buckets (`Services`, `Helpers`, `Logic`), vague shared
91
+ namespaces (`Common`, `Shared`, `Core`), doer names, and names packing
92
+ several concepts are flagged; a genuine compound domain like
93
+ `AccessControl` passes. `BannedNames` matches a whole name or a trailing
94
+ segment, so `Services` also catches `ApplicationServices`. The default
95
+ `AllowedNames` holds the framework suffixes, so reopening
96
+ `ActionController` or `ActionMailer` is not an offense; reopening another
97
+ library's junk drawer (`FileUtils`) is a file-boundary `Exclude`, not a
98
+ naming choice. Absorbs the module half of Kata/NoUtilName.
99
+ Enabled: true
100
+ VersionAdded: "0.9"
101
+ MaxWords: 2
102
+ AllowedNames:
103
+ - Actor
104
+ - Author
105
+ - Buffer
106
+ - Center
107
+ - Chapter
108
+ - Character
109
+ - Color
110
+ - Container
24
111
  - Controller
112
+ - Cursor
113
+ - Customer
114
+ - Decorator
115
+ - Delegator
116
+ - Driver
117
+ - Enumerator
118
+ - Error
119
+ - Fiber
120
+ - Folder
121
+ - Helper
122
+ - Job
123
+ - Ledger
124
+ - Logger
25
125
  - Mailer
126
+ - Member
127
+ - Monitor
128
+ - Order
129
+ - Owner
130
+ - Presenter
26
131
  - Serializer
132
+ - Server
133
+ - Supplier
134
+ - Timer
135
+ - Transfer
136
+ - User
137
+ - Validator
138
+ - Vector
139
+ BannedNames:
140
+ - Common
141
+ - Core
142
+ - Decorators
143
+ - Extensions
144
+ - Forms
145
+ - Helpers
146
+ - Jobs
147
+ - Logic
148
+ - Managers
149
+ - Misc
150
+ - Mixins
151
+ - Models
152
+ - Policies
153
+ - Presenters
154
+ - Queries
155
+ - Serializers
156
+ - Service
157
+ - Services
158
+ - Shared
159
+ - Util
160
+ - Utilities
161
+ - Utility
162
+ - Utils
163
+ - Validators
164
+ - Workers
165
+ Terms: []
27
166
 
28
167
  Kata/NoComments:
29
168
  Description: "Say it in the code; keep only comments the machine or the law reads."
@@ -41,34 +180,52 @@ Kata/GoodMethodName:
41
180
  Description: >-
42
181
  One word per method name. A second word is allowed only when it does not
43
182
  hide a missing object: a role prefix (`after_fork`), a role suffix
44
- (`file_of`), or a reviewed domain term. Do not delete the underscore —
45
- Kata/RealWords catches the smash.
183
+ (`file_of`), or a reviewed domain term. A name that chains actions
184
+ (`validate_and_save`) or spells out another object's property
185
+ (`customer_address`) is a missing method home, not a naming problem.
186
+ Do not delete the underscore — Kata/RealWords catches the smash.
46
187
  Enabled: true
47
188
  VersionAdded: "0.5"
189
+ VersionChanged: "0.9"
48
190
  MaxWords: 2
49
191
  Pattern: "^[a-z][a-z0-9]{0,15}$"
50
192
  Prefixes:
51
193
  - after
52
194
  - all
53
195
  - any
196
+ - actual
54
197
  - around
55
198
  - as
199
+ - assert
56
200
  - at
57
201
  - before
58
202
  - by
203
+ - current
204
+ - default
59
205
  - each
206
+ - end
60
207
  - every
208
+ - expected
61
209
  - fake
62
210
  - for
63
211
  - from
64
212
  - into
213
+ - max
214
+ - min
215
+ - new
65
216
  - "no"
66
217
  - non
67
218
  - "on"
219
+ - other
68
220
  - per
221
+ - raw
222
+ - set
223
+ - sort
224
+ - start
69
225
  - test
70
226
  - the
71
227
  - to
228
+ - total
72
229
  - unless
73
230
  - until
74
231
  - when
@@ -76,54 +233,178 @@ Kata/GoodMethodName:
76
233
  - with
77
234
  - without
78
235
  Suffixes:
236
+ - after
79
237
  - at
80
238
  - by
239
+ - cents
240
+ - code
241
+ - count
242
+ - date
243
+ - dir
244
+ - directory
245
+ - file
81
246
  - for
82
247
  - from
248
+ - id
83
249
  - in
84
250
  - into
251
+ - key
252
+ - level
253
+ - ms
254
+ - number
85
255
  - of
86
256
  - on
87
257
  - over
258
+ - params
259
+ - path
88
260
  - per
261
+ - period
262
+ - price
263
+ - rate
264
+ - seconds
265
+ - secret
266
+ - size
89
267
  - to
268
+ - token
90
269
  - under
270
+ - url
91
271
  - with
92
272
  - without
93
- Terms: []
94
- AllowedNames: []
273
+ Terms:
274
+ - access_token
275
+ - bank_account
276
+ - billing_address
277
+ - checkout_session
278
+ - content_type
279
+ - credit_card
280
+ - date_of_birth
281
+ - display_name
282
+ - dry_run
283
+ - event_type
284
+ - exit_status
285
+ - first_name
286
+ - full_name
287
+ - gift_card
288
+ - grand_total
289
+ - in_stock
290
+ - ip_address
291
+ - last_name
292
+ - line_item
293
+ - line_items
294
+ - mark_as_read
295
+ - method_name
296
+ - middle_name
297
+ - mime_type
298
+ - out_of_stock
299
+ - page_title
300
+ - password_confirmation
301
+ - payment_intent
302
+ - payment_method
303
+ - query_string
304
+ - rate_limit
305
+ - search_term
306
+ - shipping_address
307
+ - soft_delete
308
+ - stack_trace
309
+ - time_zone
310
+ - user_agent
311
+ AllowedNames:
312
+ - active_for_authentication
313
+ - after_sign_in_path_for
314
+ - after_sign_out_path_for
315
+ - assign_attributes
316
+ - authenticate_user
317
+ - const_missing
318
+ - deconstruct_keys
319
+ - deep_dup
320
+ - default_scope
321
+ - define_method
322
+ - delete_if
323
+ - email_required
324
+ - failure_message
325
+ - failure_message_when_negated
326
+ - find_each
327
+ - force_encoding
328
+ - foreign_key
329
+ - inactive_message
330
+ - initialize_clone
331
+ - initialize_copy
332
+ - initialize_dup
333
+ - instance_method
334
+ - marshal_dump
335
+ - marshal_load
336
+ - method_added
337
+ - method_missing
338
+ - method_removed
339
+ - not_found
340
+ - password_required
341
+ - perform_async
342
+ - perform_later
343
+ - perform_now
344
+ - permitted_attributes
345
+ - policy_scope
346
+ - primary_key
347
+ - pundit_user
348
+ - read_nonblock
349
+ - record_not_found
350
+ - respond_to_missing
351
+ - send_devise_notification
352
+ - serializable_hash
353
+ - singleton_method_added
354
+ - supports_block_expectations
355
+ - table_name
356
+ - write_nonblock
95
357
 
96
358
  Kata/GoodVariableName:
97
359
  Description: >-
98
360
  One word per variable name. A second word means a missing object, not a
99
361
  name to smash together — Kata/RealWords catches the smash. Role prefixes, role
100
- suffixes, and reviewed domain terms are the three exceptions.
362
+ suffixes, and reviewed domain terms are the three exceptions. Grammatical
363
+ suffixes (`created_at`, `user_id`, `timeout_ms`) are representation, not a
364
+ second concept. Where names must mirror an external schema, `Exclude` the
365
+ file: the file is the boundary.
101
366
  Enabled: true
102
367
  VersionAdded: "0.5"
368
+ VersionChanged: "0.9"
103
369
  MaxWords: 2
104
370
  Pattern: "^[a-z][a-z0-9]{0,15}$"
105
371
  Prefixes:
106
372
  - after
107
373
  - all
108
374
  - any
375
+ - actual
109
376
  - around
110
377
  - as
378
+ - assert
111
379
  - at
112
380
  - before
113
381
  - by
382
+ - current
383
+ - default
114
384
  - each
385
+ - end
115
386
  - every
387
+ - expected
116
388
  - fake
117
389
  - for
118
390
  - from
119
391
  - into
392
+ - max
393
+ - min
394
+ - new
120
395
  - "no"
121
396
  - non
122
397
  - "on"
398
+ - other
123
399
  - per
400
+ - raw
401
+ - set
402
+ - sort
403
+ - start
124
404
  - test
125
405
  - the
126
406
  - to
407
+ - total
127
408
  - unless
128
409
  - until
129
410
  - when
@@ -131,23 +412,85 @@ Kata/GoodVariableName:
131
412
  - with
132
413
  - without
133
414
  Suffixes:
415
+ - after
134
416
  - at
135
417
  - by
418
+ - cents
419
+ - code
420
+ - count
421
+ - date
422
+ - dir
423
+ - directory
424
+ - file
136
425
  - for
137
426
  - from
427
+ - id
138
428
  - in
139
429
  - into
430
+ - key
431
+ - level
432
+ - ms
433
+ - number
140
434
  - of
141
435
  - on
142
436
  - over
437
+ - params
438
+ - path
143
439
  - per
440
+ - period
441
+ - price
442
+ - rate
443
+ - seconds
444
+ - secret
445
+ - size
144
446
  - to
447
+ - token
145
448
  - under
449
+ - url
146
450
  - with
147
451
  - without
148
- Terms: []
452
+ Terms:
453
+ - access_token
454
+ - bank_account
455
+ - billing_address
456
+ - checkout_session
457
+ - content_type
458
+ - credit_card
459
+ - date_of_birth
460
+ - display_name
461
+ - dry_run
462
+ - event_type
463
+ - exit_status
464
+ - first_name
465
+ - full_name
466
+ - gift_card
467
+ - grand_total
468
+ - in_stock
469
+ - ip_address
470
+ - last_name
471
+ - line_item
472
+ - line_items
473
+ - mark_as_read
474
+ - method_name
475
+ - middle_name
476
+ - mime_type
477
+ - out_of_stock
478
+ - page_title
479
+ - password_confirmation
480
+ - payment_intent
481
+ - payment_method
482
+ - query_string
483
+ - rate_limit
484
+ - search_term
485
+ - shipping_address
486
+ - soft_delete
487
+ - stack_trace
488
+ - time_zone
489
+ - user_agent
149
490
  AllowedNames:
150
491
  - _include_private
492
+ - include_private
493
+ - resource_or_scope
151
494
 
152
495
  Kata/IoDiscipline:
153
496
  Description: "Write through an injected io, not bare puts/warn/pp/p."
@@ -172,23 +515,6 @@ Kata/ProsePlacement:
172
515
  Enabled: false
173
516
  VersionAdded: "0.1"
174
517
 
175
- Kata/NoUtilName:
176
- Description: "Junk-drawer names hide the concept the code is missing."
177
- Enabled: true
178
- VersionAdded: "0.3"
179
- BannedNames:
180
- - Util
181
- - Utils
182
- - Utility
183
- - Utilities
184
- - Helper
185
- - Helpers
186
- - Manager
187
- - Service
188
- - Common
189
- - Misc
190
- - Shared
191
-
192
518
  Kata/RealWords:
193
519
  Description: >-
194
520
  Every name segment is a dictionary word: `errorcount` is a smash, `cfg`
@@ -1,28 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class RuboCop::Cop::Kata::AgentNoun < RuboCop::Cop::Base
4
- MSG = "`%s` names a doer; name the class for the thing it is, not the work it does."
5
- HINT = "#{MSG} Try `%s`.".freeze
6
- SUFFIX = /(?:er|or)\z/
3
+ module RuboCop::Cop::Kata::AgentNoun
4
+ DOER_MSG = "`%s` names a doer; name the %s for the thing it is, not the work it does."
5
+ DOER_HINT = "#{DOER_MSG} Try `%s`.".freeze
6
+ DOER = /(?:er|or)\z/
7
7
  SEGMENT = /[A-Z]+(?=[A-Z][a-z]|\z)|[A-Z][a-z0-9]*/
8
8
  DERIVATIONS = [[/ator\z/, "ation"], [/ector\z/, "ection"], [/isor\z/, "ision"], [/i[zs]er\z/, "is"]].freeze
9
9
 
10
- def on_class(node) = check(node)
11
-
12
- def on_module(node) = check(node)
13
-
14
10
  private
15
11
 
16
- def check(node)
17
- name = node.identifier.short_name.to_s
18
- return unless SUFFIX.match?(name)
19
- return if allowed?(name)
20
- add_offense(node.identifier, message: message(name))
21
- end
12
+ def doer?(name) = DOER.match?(name)
22
13
 
23
- def message(name)
14
+ def doer(name)
24
15
  hint = suggestion(name)
25
- hint ? format(HINT, name, hint) : format(MSG, name)
16
+ hint ? format(DOER_HINT, name, kind, hint) : format(DOER_MSG, name, kind)
26
17
  end
27
18
 
28
19
  def suggestion(name)
@@ -36,14 +27,10 @@ class RuboCop::Cop::Kata::AgentNoun < RuboCop::Cop::Base
36
27
  end
37
28
 
38
29
  def proposal(name)
39
- return if name.empty? || SUFFIX.match?(name)
30
+ return if name.empty? || DOER.match?(name)
40
31
  return unless known?(name)
41
32
  name
42
33
  end
43
34
 
44
35
  def known?(name) = RuboCop::Kata::Dictionary::ENTRIES.include?(name.scan(SEGMENT).last.to_s.downcase)
45
-
46
- def allowed?(name) = list.any? { name.end_with?(it) }
47
-
48
- def list = Array(cop_config["AllowedNames"]).map(&:to_s).reject(&:empty?)
49
36
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RuboCop::Cop::Kata::GoodClassName < RuboCop::Cop::Base
4
+ include RuboCop::Cop::Kata::AgentNoun
5
+
6
+ BUCKET_MSG = "`%s` is a junk drawer; it hides the concept the code is missing."
7
+ CROWD_MSG = "`%s` packs %d concepts into one name; a class names one object that exists " \
8
+ "in the model — or, if `%s` reads as one concept here, add it to `Terms`."
9
+
10
+ def on_class(node) = check(node)
11
+
12
+ private
13
+
14
+ def check(node)
15
+ name = node.identifier.short_name.to_s
16
+ return if allowed?(name)
17
+ complaint = complaint(name)
18
+ add_offense(node.identifier, message: complaint) if complaint
19
+ end
20
+
21
+ def complaint(name)
22
+ return format(BUCKET_MSG, name) if banned?(name)
23
+ return doer(name) if doer?(name)
24
+ crowd(name)
25
+ end
26
+
27
+ def crowd(name)
28
+ count = name.scan(SEGMENT).size
29
+ format(CROWD_MSG, name, count, name) if count > max
30
+ end
31
+
32
+ def kind = "class"
33
+
34
+ def banned?(name) = list("BannedNames").any? { name.end_with?(it) }
35
+
36
+ def allowed?(name) = list("AllowedNames").any? { name.end_with?(it) } || list("Terms").include?(name)
37
+
38
+ def list(key) = Array(cop_config[key]).map(&:to_s).reject(&:empty?)
39
+
40
+ def max = Integer(cop_config.fetch("MaxWords", 2))
41
+ end
@@ -4,6 +4,8 @@ class RuboCop::Cop::Kata::GoodMethodName < RuboCop::Cop::Base
4
4
  MSG = "`%s` needs two words: either a concept is missing, or the method belongs on the object " \
5
5
  "the second word names. Say it in one word, move it, give the role a `Prefixes`/`Suffixes` " \
6
6
  "word — or, if `%s` is one domain concept here, add it to `Terms`."
7
+ CHAIN_MSG = "`%s` chains actions with `%s`; each action wants its own method."
8
+ JOINTS = %w[and or then].freeze
7
9
 
8
10
  def on_def(node) = check(node, node.method_name)
9
11
 
@@ -15,8 +17,13 @@ class RuboCop::Cop::Kata::GoodMethodName < RuboCop::Cop::Base
15
17
  return unless name.match?(/\A[a-z_]/)
16
18
  stem = name.to_s.sub(/[?!=]\z/, "")
17
19
  return if allowed?(name.to_s) || allowed?(stem)
18
- return if good?(stem)
19
- add_offense(node.loc.name, message: format(MSG, name, name))
20
+ complaint = chain(name, stem) || (format(MSG, name, name) unless good?(stem))
21
+ add_offense(node.loc.name, message: complaint) if complaint
22
+ end
23
+
24
+ def chain(name, stem)
25
+ joint = (stem.split("_") & JOINTS).first
26
+ format(CHAIN_MSG, name, joint) if joint
20
27
  end
21
28
 
22
29
  def good?(stem)
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RuboCop::Cop::Kata::GoodModuleName < RuboCop::Cop::Base
4
+ include RuboCop::Cop::Kata::AgentNoun
5
+
6
+ LAYER_MSG = "`%s` names a layer or a junk drawer; a module names a domain vocabulary."
7
+ CROWD_MSG = "`%s` packs %d concepts into one name; a module names one domain — " \
8
+ "or, if `%s` reads as one concept here, add it to `Terms`."
9
+
10
+ def on_module(node) = check(node)
11
+
12
+ private
13
+
14
+ def check(node)
15
+ name = node.identifier.short_name.to_s
16
+ return if allowed?(name)
17
+ complaint = complaint(name)
18
+ add_offense(node.identifier, message: complaint) if complaint
19
+ end
20
+
21
+ def complaint(name)
22
+ return format(LAYER_MSG, name) if banned?(name)
23
+ return doer(name) if doer?(name)
24
+ crowd(name)
25
+ end
26
+
27
+ def crowd(name)
28
+ count = name.scan(SEGMENT).size
29
+ format(CROWD_MSG, name, count, name) if count > max
30
+ end
31
+
32
+ def kind = "module"
33
+
34
+ def banned?(name) = list("BannedNames").any? { name.end_with?(it) }
35
+
36
+ def allowed?(name) = list("AllowedNames").any? { name.end_with?(it) } || list("Terms").include?(name)
37
+
38
+ def list(key) = Array(cop_config[key]).map(&:to_s).reject(&:empty?)
39
+
40
+ def max = Integer(cop_config.fetch("MaxWords", 2))
41
+ end
@@ -10,8 +10,8 @@ class RuboCop::Kata::Plan
10
10
  Kata/NoBooleanFlag Kata/IoDiscipline Kata/ClockDiscipline Kata/EnvDiscipline
11
11
  ],
12
12
  "naming" => %w[
13
- Kata/RealWords Kata/GoodMethodName Kata/GoodVariableName Kata/AgentNoun
14
- Kata/BuilderNoun Kata/NoUtilName
13
+ Kata/RealWords Kata/GoodMethodName Kata/GoodVariableName Kata/GoodClassName
14
+ Kata/GoodModuleName Kata/BuilderNoun
15
15
  ],
16
16
  "prose" => %w[Kata/NoComments Kata/ProsePlacement]
17
17
  }.freeze
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Kata
5
- VERSION = "0.8.0"
5
+ VERSION = "0.9.0"
6
6
  end
7
7
  end
data/lib/rubocop-kata.rb CHANGED
@@ -12,19 +12,20 @@ module RuboCop
12
12
  end
13
13
  end
14
14
 
15
- require_relative "rubocop/cop/kata/agent_noun"
16
15
  require_relative "rubocop/cop/kata/builder_noun"
17
16
  require_relative "rubocop/cop/kata/clock_discipline"
18
17
  require_relative "rubocop/cop/kata/constructor_discipline"
19
18
  require_relative "rubocop/cop/kata/env_discipline"
19
+ require_relative "rubocop/cop/kata/agent_noun"
20
+ require_relative "rubocop/cop/kata/good_class_name"
20
21
  require_relative "rubocop/cop/kata/good_method_name"
22
+ require_relative "rubocop/cop/kata/good_module_name"
21
23
  require_relative "rubocop/cop/kata/good_variable_name"
22
24
  require_relative "rubocop/cop/kata/io_discipline"
23
25
  require_relative "rubocop/cop/kata/no_boolean_flag"
24
26
  require_relative "rubocop/cop/kata/no_class_method_logic"
25
27
  require_relative "rubocop/cop/kata/no_comments"
26
28
  require_relative "rubocop/cop/kata/no_hash_as_object"
27
- require_relative "rubocop/cop/kata/no_util_name"
28
29
  require_relative "rubocop/cop/kata/prose_placement"
29
30
  require_relative "rubocop/cop/kata/real_words"
30
31
  require_relative "rubocop/kata/dictionary"
@@ -4,7 +4,7 @@ description: >-
4
4
  Adopt rubocop-kata's rules on an existing codebase without the offense count going
5
5
  the wrong way. Kata's structural cops (ConstructorDiscipline, NoHashAsObject,
6
6
  Io/Clock/EnvDiscipline, NoClassMethodLogic, NoBooleanFlag) create names, and its naming
7
- cops (RealWords, GoodMethodName, GoodVariableName, AgentNoun, BuilderNoun, NoUtilName)
7
+ cops (RealWords, GoodMethodName, GoodVariableName, GoodClassName, GoodModuleName, BuilderNoun)
8
8
  charge for every name created — so a structural refactor removes offenses and adds more,
9
9
  and a single total cannot tell progress from regression. This skill sequences the work by
10
10
  stage, picks names that are already clean by checking them against the dictionary the cops
@@ -79,8 +79,9 @@ This is the step that keeps the count honest. Kata's naming rules, condensed:
79
79
  | a second word only via a role prefix (`after_fork`), a role suffix (`file_of`), or a reviewed `Terms` entry | same |
80
80
  | never smash the underscore out — `errorcount` fails too | `RealWords` |
81
81
  | every segment is a real word; no `cfg`, `ctx`, `msg`, `tmp`, … | `RealWords` |
82
- | classes are not `-er`/`-or` doers | `AgentNoun` |
83
- | no `Util`, `Helper`, `Manager`, `Service`, `Common`, `Shared` | `NoUtilName` |
82
+ | a class is one object, not a `-er`/`-or` doer, a junk drawer (`Service`, `Util`, `Data`), or three concepts packed together | `GoodClassName` |
83
+ | a module is a domain vocabulary, not a layer (`Services`, `Helpers`, `Common`, `Shared`, `Core`) | `GoodModuleName` |
84
+ | a method is one action — no `_and_`/`_or_` chains | `GoodMethodName` |
84
85
  | a method that returns something is named for what it returns, not `get_`/`calculate_`/`compute_` | `BuilderNoun` |
85
86
  | memoization ivars are `@_name` | `Naming/MemoizedInstanceVariableName` |
86
87
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-kata
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Giacomo GK
@@ -110,11 +110,11 @@ dependencies:
110
110
  description: Bundles a curated RuboCop stack (rspec, performance, elegant, packaging,
111
111
  thread_safety) behind one dependency, layers opinionated style defaults on top,
112
112
  and adds fifteen cops of its own — naming (GoodMethodName, GoodVariableName, dictionary-backed
113
- RealWords, AgentNoun, BuilderNoun, NoUtilName), discipline (IoDiscipline, ClockDiscipline,
114
- EnvDiscipline, ConstructorDiscipline, NoClassMethodLogic), and shape (NoComments,
115
- NoBooleanFlag, NoHashAsObject, ProsePlacement). The rubocop-kata command reports
116
- dead configuration (doctor), buckets a backlog into the stages that cause each other
117
- (plan), and prints an agent skill for working it (skill).
113
+ RealWords, GoodClassName, GoodModuleName, BuilderNoun), discipline (IoDiscipline,
114
+ ClockDiscipline, EnvDiscipline, ConstructorDiscipline, NoClassMethodLogic), and
115
+ shape (NoComments, NoBooleanFlag, NoHashAsObject, ProsePlacement). The rubocop-kata
116
+ command reports dead configuration (doctor), buckets a backlog into the stages that
117
+ cause each other (plan), and prints an agent skill for working it (skill).
118
118
  email:
119
119
  - giaco@hey.com
120
120
  executables:
@@ -138,14 +138,15 @@ files:
138
138
  - lib/rubocop/cop/kata/clock_discipline.rb
139
139
  - lib/rubocop/cop/kata/constructor_discipline.rb
140
140
  - lib/rubocop/cop/kata/env_discipline.rb
141
+ - lib/rubocop/cop/kata/good_class_name.rb
141
142
  - lib/rubocop/cop/kata/good_method_name.rb
143
+ - lib/rubocop/cop/kata/good_module_name.rb
142
144
  - lib/rubocop/cop/kata/good_variable_name.rb
143
145
  - lib/rubocop/cop/kata/io_discipline.rb
144
146
  - lib/rubocop/cop/kata/no_boolean_flag.rb
145
147
  - lib/rubocop/cop/kata/no_class_method_logic.rb
146
148
  - lib/rubocop/cop/kata/no_comments.rb
147
149
  - lib/rubocop/cop/kata/no_hash_as_object.rb
148
- - lib/rubocop/cop/kata/no_util_name.rb
149
150
  - lib/rubocop/cop/kata/prose_placement.rb
150
151
  - lib/rubocop/cop/kata/real_words.rb
151
152
  - lib/rubocop/kata/checkup.rb
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class RuboCop::Cop::Kata::NoUtilName < RuboCop::Cop::Base
4
- MSG = "`%s` is a junk drawer; it hides the concept the code is missing."
5
-
6
- def on_class(node) = check(node)
7
-
8
- def on_module(node) = check(node)
9
-
10
- private
11
-
12
- def check(node)
13
- name = node.identifier.short_name.to_s
14
- return unless banned?(name)
15
- add_offense(node.identifier, message: format(MSG, name))
16
- end
17
-
18
- def banned?(name) = Array(cop_config["BannedNames"]).map(&:to_s).include?(name)
19
- end