rubocop-kata 0.2.0 → 0.3.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: 2aecb4f5acadb8c0a79107ec7eda1e8555464a1f7c00c0fc18d8148238b55eb4
4
- data.tar.gz: cfe52159c9def4a8217a40cde0534aa4d3d68d7ba457800734716b563606442b
3
+ metadata.gz: de0b42a4b03480a8a8fff9e89e502eb9be99320bc348157233cd41c7f554cf39
4
+ data.tar.gz: 9b61887eb55e6b3adcd66f95e366e81e203b72fd9b6a804ea120b9734051d98a
5
5
  SHA512:
6
- metadata.gz: 8dfbdb1a01717c78669d1f4efc9606bd3796778ea1dc7039c6d2b4c76e45066229f1fcec400973879a011f9092425ad2403f35430d0929c3df966eb5d42e4b64
7
- data.tar.gz: 2dbace3f591f7920204c62f8ec94c0153fc91f96f381df1ea73891b93631bdbb0d96782321758bcea984eed0415164334d541c8f4ac64ca1763e490e60820318
6
+ metadata.gz: 915b4e7e20ed0e9cfca066625c8158e6ac2e2d46f3078c52e86f27c46da8bb2b59c65461ebe7bbbf15627acf793d40408852202a657e84d1f83ee56463deae9c
7
+ data.tar.gz: a18141d892a1d2833e9db5a7fd541c885218c8233911f507cce289a0f1705e576278de3c68f40ed17a3b76cf03c3594b8b2e2cb84fd001a2cf766da3258f33e6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.0] - 2026-08-14
4
+
5
+ - Ten new house cops in the Elegant Objects spirit, all on by default:
6
+ `Kata/NoUtilName`, `Kata/NoAbbreviation`, `Kata/BuilderNoun`, `Kata/NoBooleanFlag`,
7
+ `Kata/ConstructorDiscipline`, `Kata/NoClassMethodLogic`, `Kata/NoHashAsObject`,
8
+ `Kata/NoNilReturn`, `Kata/ClockDiscipline`, `Kata/EnvDiscipline`.
9
+
10
+ ## [0.2.1] - 2026-08-13
11
+
12
+ - `Style/MethodCallWithArgsParentheses` disabled, overriding rubocop-elegant.
13
+ - Disabled nine core `Layout/*` cops that autocorrect-loop against `Elegant/PairedBrackets` and `Elegant/NoEmptyLines*`.
14
+ - `AutoCorrect: false` on `Elegant/NoRedundantVariable`, `Style/StaticClass`, and `Lint/NumberConversion` — their correctors rewrite code incorrectly.
15
+ - `Style/RequireOrder` disabled — alphabetizing requires breaks load-order-dependent boot.
16
+
3
17
  ## [0.2.0] - 2026-08-10
4
18
 
5
19
  - Kata cops now run globally instead of only in `lib/**` — code in `app/`, `Rakefile`, etc. is now covered.
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # rubocop-kata
2
2
 
3
- A RuboCop plugin that replaces a pile of linter gems and config with one dependency. Install it and your `.rubocop.yml` shrinks to project-specific overrides. Everything else comes from the gem: a curated stack of RuboCop extensions, opinionated style defaults, and four house cops.
3
+ A RuboCop plugin that replaces a pile of linter gems and config with one dependency. Install it and your `.rubocop.yml` shrinks to project-specific overrides. Everything else comes from the gem: a curated stack of RuboCop extensions, opinionated style defaults, and fourteen house cops.
4
4
 
5
5
  - **One dependency.** Bundles rubocop-rspec, rubocop-performance, rubocop-elegant, rubocop-packaging, and rubocop-thread_safety behind a single gem.
6
6
  - **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).
7
- - **Four house cops.** `Kata/AgentNoun`, `Kata/NoComments` (autocorrecting), `Kata/IoDiscipline`, `Kata/ProsePlacement`.
7
+ - **Fourteen house cops.** Naming, dependency discipline, and data honesty in the Elegant Objects spirit — from `Kata/AgentNoun` to `Kata/ClockDiscipline`.
8
8
 
9
9
  ```ruby
10
10
  class PaymentProcessor # Kata/AgentNoun: `PaymentProcessor` names a doer;
@@ -47,6 +47,16 @@ That's it. The plugin loads the bundled extensions and the shared defaults, so y
47
47
  | `Kata/NoComments` | on | No prose comments; say it in the code. Magic comments, linter directives, and licence headers survive. Autocorrects. |
48
48
  | `Kata/IoDiscipline` | on | No bare `puts`/`warn`/`pp`/`p` outside specs/scripts; write through an injected `@io` or an explicit receiver. |
49
49
  | `Kata/ProsePlacement` | off | Sentence-length strings belong in the presentation layer. Enable with an `Include`/`Exclude` matching your layering. |
50
+ | `Kata/NoUtilName` | on | No junk-drawer names (`Util`, `Helper`, `Manager`, `Service`, …). Tune via `BannedNames`. |
51
+ | `Kata/NoAbbreviation` | on | Spell words out; no `usr`, `cfg`, `res`. Tune via `BannedNames`. |
52
+ | `Kata/BuilderNoun` | on | Builders named for what they return: `total`, not `calculate_total`. Tune via `BannedPrefixes`. |
53
+ | `Kata/NoBooleanFlag` | on | No positional boolean arguments; split the method or use a keyword. |
54
+ | `Kata/ConstructorDiscipline` | on | `initialize` assigns, raises, or freezes — never computes. |
55
+ | `Kata/NoClassMethodLogic` | on | Class methods construct (`build`, `parse`, `of`, `from_*`); instances do the work. |
56
+ | `Kata/NoHashAsObject` | on | A hash with `MaxKeys`+ keys (default 4) wants to be an object. Keyword-argument call sites exempt. |
57
+ | `Kata/NoNilReturn` | on | No `return nil` or trailing `nil`; raise or return a real object. |
58
+ | `Kata/ClockDiscipline` | on | No bare `Time.now`/`Date.today`/`.current`; inject a clock. |
59
+ | `Kata/EnvDiscipline` | on | `ENV` reads only in the boot layer (`config/`, `bin/`, `exe/`). |
50
60
 
51
61
  ## The defaults
52
62
 
data/config/default.yml CHANGED
@@ -41,6 +41,114 @@ Kata/ProsePlacement:
41
41
  Enabled: false
42
42
  VersionAdded: "0.1"
43
43
 
44
+ Kata/NoUtilName:
45
+ Description: "Junk-drawer names hide the concept the code is missing."
46
+ Enabled: true
47
+ VersionAdded: "0.3"
48
+ BannedNames:
49
+ - Util
50
+ - Utils
51
+ - Utility
52
+ - Utilities
53
+ - Helper
54
+ - Helpers
55
+ - Manager
56
+ - Service
57
+ - Common
58
+ - Misc
59
+ - Shared
60
+
61
+ Kata/NoAbbreviation:
62
+ Description: "Spell words out; code reads aloud like prose."
63
+ Enabled: true
64
+ VersionAdded: "0.3"
65
+ BannedNames:
66
+ - usr
67
+ - cfg
68
+ - tmp
69
+ - res
70
+ - val
71
+ - obj
72
+ - arr
73
+ - num
74
+ - buf
75
+ - idx
76
+ - ctx
77
+ - err
78
+
79
+ Kata/BuilderNoun:
80
+ Description: "Builders are named for what they return; verbs are for side effects."
81
+ Enabled: true
82
+ VersionAdded: "0.3"
83
+ BannedPrefixes:
84
+ - get
85
+ - calculate
86
+ - compute
87
+ - retrieve
88
+ - generate
89
+
90
+ Kata/NoBooleanFlag:
91
+ Description: "A boolean flag is two methods trapped in one."
92
+ Enabled: true
93
+ VersionAdded: "0.3"
94
+ Exclude:
95
+ - "spec/**/*"
96
+
97
+ Kata/ConstructorDiscipline:
98
+ Description: "Constructors assign; they do not compute."
99
+ Enabled: true
100
+ VersionAdded: "0.3"
101
+ Exclude:
102
+ - "spec/**/*"
103
+
104
+ Kata/NoClassMethodLogic:
105
+ Description: "Classes construct; instances work."
106
+ Enabled: true
107
+ VersionAdded: "0.3"
108
+ Exclude:
109
+ - "spec/**/*"
110
+ AllowedNames:
111
+ - build
112
+ - parse
113
+ - of
114
+
115
+ Kata/NoHashAsObject:
116
+ Description: "Keys that travel together are an object without a name."
117
+ Enabled: true
118
+ VersionAdded: "0.3"
119
+ MaxKeys: 4
120
+ Exclude:
121
+ - "spec/**/*"
122
+ - "config/**/*"
123
+
124
+ Kata/NoNilReturn:
125
+ Description: "Raise or return a real object; never nil."
126
+ Enabled: true
127
+ VersionAdded: "0.3"
128
+ Exclude:
129
+ - "spec/**/*"
130
+
131
+ Kata/ClockDiscipline:
132
+ Description: "Read time from an injected clock, not the ambient one."
133
+ Enabled: true
134
+ VersionAdded: "0.3"
135
+ Exclude:
136
+ - "spec/**/*"
137
+ - "bin/**/*"
138
+ - "exe/**/*"
139
+
140
+ Kata/EnvDiscipline:
141
+ Description: "ENV belongs in the boot layer; pass configuration in."
142
+ Enabled: true
143
+ VersionAdded: "0.3"
144
+ Exclude:
145
+ - "spec/**/*"
146
+ - "config/**/*"
147
+ - "bin/**/*"
148
+ - "exe/**/*"
149
+ - "Rakefile"
150
+ - "*.gemspec"
151
+
44
152
  Style/Documentation:
45
153
  Enabled: false
46
154
 
@@ -110,6 +218,58 @@ Elegant/NoEmptyLinesInMethods:
110
218
  Elegant/NoComments:
111
219
  Enabled: false
112
220
 
221
+ Style/MethodCallWithArgsParentheses:
222
+ Enabled: false
223
+
224
+ # --- Core layout cops that autocorrect-loop against Elegant/PairedBrackets
225
+ # and Elegant/NoEmptyLines*; the Elegant cops win.
226
+ Layout/BlockAlignment:
227
+ Enabled: false
228
+
229
+ Layout/MultilineBlockLayout:
230
+ Enabled: false
231
+
232
+ Layout/ClosingParenthesisIndentation:
233
+ Enabled: false
234
+
235
+ Layout/FirstArgumentIndentation:
236
+ Enabled: false
237
+
238
+ Layout/MultilineMethodCallBraceLayout:
239
+ Enabled: false
240
+
241
+ Layout/MultilineArrayBraceLayout:
242
+ Enabled: false
243
+
244
+ Layout/MultilineHashBraceLayout:
245
+ Enabled: false
246
+
247
+ Layout/EmptyLinesAroundAccessModifier:
248
+ Enabled: false
249
+
250
+ Layout/EmptyLinesAfterModuleInclusion:
251
+ Enabled: false
252
+
253
+ # --- Correctors known to rewrite code incorrectly; report only.
254
+ # Inlines a variable past side effects (ivar resets, later statements) and
255
+ # corrupts Ruby 3.1 keyword shorthand.
256
+ Elegant/NoRedundantVariable:
257
+ AutoCorrect: false
258
+
259
+ # Rewrites reopened classes into modules, clashing with the primary
260
+ # class definition at boot.
261
+ Style/StaticClass:
262
+ AutoCorrect: false
263
+
264
+ # Turns lenient `.to_i` into strict Integer(x, 10), which raises on
265
+ # nils/Integers call sites legitimately receive.
266
+ Lint/NumberConversion:
267
+ AutoCorrect: false
268
+
269
+ # Alphabetizing requires breaks load-order-dependent boot sequences.
270
+ Style/RequireOrder:
271
+ Enabled: false
272
+
113
273
  ThreadSafety/DirChdir:
114
274
  Exclude:
115
275
  - "spec/**/*"
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RuboCop::Cop::Kata::BuilderNoun < RuboCop::Cop::Base
4
+ MSG = "A builder is named for what it returns: `%s`, not `%s`."
5
+
6
+ def on_def(node) = check(node)
7
+
8
+ def on_defs(node) = check(node)
9
+
10
+ private
11
+
12
+ def check(node)
13
+ name = node.method_name.to_s
14
+ verb = prefix(name)
15
+ return unless verb
16
+ add_offense(node.loc.name, message: format(MSG, name.delete_prefix("#{verb}_"), name))
17
+ end
18
+
19
+ def prefix(name)
20
+ Array(cop_config["BannedPrefixes"]).map(&:to_s).find { name.start_with?("#{it}_") }
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RuboCop::Cop::Kata::ClockDiscipline < RuboCop::Cop::Base
4
+ MSG = "Inject a clock; `%s.%s` reads the ambient time."
5
+
6
+ RESTRICT_ON_SEND = %i[now today current].freeze
7
+
8
+ def_node_matcher :bare_clock?, "(send $(const {nil? cbase} {:Time :Date :DateTime}) ${:now :today :current})"
9
+
10
+ def on_send(node)
11
+ bare_clock?(node) do |const, method|
12
+ add_offense(node, message: format(MSG, const.const_name, method))
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RuboCop::Cop::Kata::ConstructorDiscipline < RuboCop::Cop::Base
4
+ MSG = "A constructor assigns; it does not compute. Move `%s` out of `initialize`."
5
+
6
+ ALLOWED = %i[raise freeze].freeze
7
+
8
+ def on_def(node)
9
+ return unless node.method?(:initialize)
10
+ node.each_descendant(:send) do |send|
11
+ next if ALLOWED.include?(send.method_name)
12
+ add_offense(send, message: format(MSG, send.method_name))
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RuboCop::Cop::Kata::EnvDiscipline < RuboCop::Cop::Base
4
+ MSG = "`ENV` belongs in the boot layer; pass configuration in."
5
+
6
+ def on_const(node)
7
+ return unless node.short_name == :ENV
8
+ return unless node.namespace.nil? || node.namespace.cbase_type?
9
+ add_offense(node, message: MSG)
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RuboCop::Cop::Kata::NoAbbreviation < RuboCop::Cop::Base
4
+ MSG = "`%s` is an abbreviation; spell the word out."
5
+
6
+ def on_lvasgn(node) = check(node)
7
+
8
+ def on_arg(node) = check(node)
9
+
10
+ def on_optarg(node) = check(node)
11
+
12
+ def on_kwarg(node) = check(node)
13
+
14
+ def on_kwoptarg(node) = check(node)
15
+
16
+ private
17
+
18
+ def check(node)
19
+ name = node.name.to_s
20
+ return unless banned?(name)
21
+ add_offense(node.loc.name, message: format(MSG, name))
22
+ end
23
+
24
+ def banned?(name) = Array(cop_config["BannedNames"]).map(&:to_s).include?(name.sub(/\d+\z/, ""))
25
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RuboCop::Cop::Kata::NoBooleanFlag < RuboCop::Cop::Base
4
+ MSG = "A boolean flag is two methods trapped in one; split them or name the argument."
5
+
6
+ def on_send(node)
7
+ return if node.operator_method? || node.assignment_method?
8
+ node.arguments.each { add_offense(it, message: MSG) if it.boolean_type? }
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RuboCop::Cop::Kata::NoClassMethodLogic < RuboCop::Cop::Base
4
+ MSG = "`%s` puts logic on the class; classes construct, instances work."
5
+
6
+ def on_defs(node)
7
+ name = node.method_name.to_s
8
+ return if constructor?(name)
9
+ add_offense(node.loc.name, message: format(MSG, name))
10
+ end
11
+
12
+ private
13
+
14
+ def constructor?(name) = name.start_with?("from_") || allowed?(name)
15
+
16
+ def allowed?(name) = Array(cop_config["AllowedNames"]).map(&:to_s).include?(name)
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RuboCop::Cop::Kata::NoHashAsObject < RuboCop::Cop::Base
4
+ MSG = "%d keys travelling together are an object without a name."
5
+
6
+ def on_hash(node)
7
+ return if node.pairs.size < limit
8
+ return if kwargs?(node)
9
+ add_offense(node, message: format(MSG, node.pairs.size))
10
+ end
11
+
12
+ private
13
+
14
+ def kwargs?(node) = node.parent&.send_type? && node.equal?(node.parent.last_argument)
15
+
16
+ def limit = Integer(cop_config.fetch("MaxKeys", 4))
17
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RuboCop::Cop::Kata::NoNilReturn < RuboCop::Cop::Base
4
+ MSG = "Returning nil hands the caller a bomb; raise or return a real object."
5
+
6
+ def on_return(node)
7
+ add_offense(node, message: MSG) if node.children.first&.nil_type?
8
+ end
9
+
10
+ def on_def(node)
11
+ last = tail(node.body)
12
+ add_offense(last, message: MSG) if last&.nil_type?
13
+ end
14
+ alias on_defs on_def
15
+
16
+ private
17
+
18
+ def tail(body) = body&.begin_type? ? body.children.last : body
19
+ end
@@ -0,0 +1,19 @@
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
@@ -6,7 +6,7 @@ class RuboCop::Kata::Plugin < LintRoller::Plugin
6
6
  def about
7
7
  LintRoller::About.new(
8
8
  name: "rubocop-kata", version: RuboCop::Kata::VERSION, homepage: "https://github.com/giacope/rubocop-kata",
9
- description: "Practiced forms for Ruby: opinionated defaults and four house cops."
9
+ description: "Practiced forms for Ruby: opinionated defaults and fourteen house cops."
10
10
  )
11
11
  end
12
12
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Kata
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
data/lib/rubocop-kata.rb CHANGED
@@ -13,8 +13,18 @@ module RuboCop
13
13
  end
14
14
 
15
15
  require_relative "rubocop/cop/kata/agent_noun"
16
+ require_relative "rubocop/cop/kata/builder_noun"
17
+ require_relative "rubocop/cop/kata/clock_discipline"
18
+ require_relative "rubocop/cop/kata/constructor_discipline"
19
+ require_relative "rubocop/cop/kata/env_discipline"
16
20
  require_relative "rubocop/cop/kata/io_discipline"
21
+ require_relative "rubocop/cop/kata/no_abbreviation"
22
+ require_relative "rubocop/cop/kata/no_boolean_flag"
23
+ require_relative "rubocop/cop/kata/no_class_method_logic"
17
24
  require_relative "rubocop/cop/kata/no_comments"
25
+ require_relative "rubocop/cop/kata/no_hash_as_object"
26
+ require_relative "rubocop/cop/kata/no_nil_return"
27
+ require_relative "rubocop/cop/kata/no_util_name"
18
28
  require_relative "rubocop/cop/kata/prose_placement"
19
29
  require_relative "rubocop/kata/plugin"
20
30
  require_relative "rubocop/kata/version"
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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Giacomo GK
@@ -122,8 +122,18 @@ files:
122
122
  - config/default.yml
123
123
  - lib/rubocop-kata.rb
124
124
  - lib/rubocop/cop/kata/agent_noun.rb
125
+ - lib/rubocop/cop/kata/builder_noun.rb
126
+ - lib/rubocop/cop/kata/clock_discipline.rb
127
+ - lib/rubocop/cop/kata/constructor_discipline.rb
128
+ - lib/rubocop/cop/kata/env_discipline.rb
125
129
  - lib/rubocop/cop/kata/io_discipline.rb
130
+ - lib/rubocop/cop/kata/no_abbreviation.rb
131
+ - lib/rubocop/cop/kata/no_boolean_flag.rb
132
+ - lib/rubocop/cop/kata/no_class_method_logic.rb
126
133
  - lib/rubocop/cop/kata/no_comments.rb
134
+ - lib/rubocop/cop/kata/no_hash_as_object.rb
135
+ - lib/rubocop/cop/kata/no_nil_return.rb
136
+ - lib/rubocop/cop/kata/no_util_name.rb
127
137
  - lib/rubocop/cop/kata/prose_placement.rb
128
138
  - lib/rubocop/kata/plugin.rb
129
139
  - lib/rubocop/kata/version.rb