rubocop-kata 0.1.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: afc68d8fc2ae4c367777585d33f35a3b33606f0e228a20b258f2bb3d0626b93c
4
+ data.tar.gz: 7ef9e559ce1dc181a714f648450ee1f8a7e38933310242020a918f37deb26101
5
+ SHA512:
6
+ metadata.gz: fb1e23f94f65585d432ccae75b64637b3d4731c8296bb80e04fe6ffe6780e381f3e9d81a5523c131f8ed0c2e970e3d3022b247c224bd13aeb82fba757111d230
7
+ data.tar.gz: c26e89e367edbe8b36deb696232aafe1f30739abb91aa1a163fb4c56eb8b25a8b3e13a9baf83155bcb986535ffe09528eec8f57fbda23469b201b246b71494b9
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - 2026-08-10
4
+
5
+ - Initial release.
6
+ - Cops: `Kata/AgentNoun`, `Kata/NoComments` (autocorrecting), `Kata/IoDiscipline`, `Kata/ProsePlacement`.
7
+ - Shared defaults bundling rubocop-rspec, rubocop-performance, rubocop-elegant, rubocop-packaging, and rubocop-thread_safety.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Giacomo GK
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # rubocop-kata
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.
4
+
5
+ - **One dependency.** Bundles rubocop-rspec, rubocop-performance, rubocop-elegant, rubocop-packaging, and rubocop-thread_safety behind a single gem.
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`.
8
+
9
+ ```ruby
10
+ class PaymentProcessor # Kata/AgentNoun: `PaymentProcessor` names a doer;
11
+ end # name the class for the thing it is, not the work it does.
12
+
13
+ class Payment # OK
14
+ end
15
+ ```
16
+
17
+ ## Getting started
18
+
19
+ Add the gem to your `Gemfile`:
20
+
21
+ ```ruby
22
+ gem "rubocop-kata", group: :development, require: false
23
+ ```
24
+
25
+ Install it and point `.rubocop.yml` at the plugin:
26
+
27
+ ```yaml
28
+ plugins:
29
+ - rubocop-kata
30
+
31
+ AllCops:
32
+ TargetRubyVersion: 3.4
33
+ ```
34
+
35
+ ```sh
36
+ bundle install
37
+ bundle exec rubocop
38
+ ```
39
+
40
+ That's it. The plugin loads the bundled extensions and the shared defaults, so your `.rubocop.yml` keeps only what's specific to your project. Requires Ruby >= 3.4 and RuboCop ~> 1.75.
41
+
42
+ ## The cops
43
+
44
+ | Cop | Default | What it enforces |
45
+ | --- | --- | --- |
46
+ | `Kata/AgentNoun` | on | Classes named for what they are, not `-er`/`-or` doers. Allow exceptions via `AllowedNames`. |
47
+ | `Kata/NoComments` | on | No prose comments; say it in the code. Magic comments, linter directives, and licence headers survive. Autocorrects. |
48
+ | `Kata/IoDiscipline` | off | No bare `puts`/`warn`/`pp`/`p`; write through an injected `@io`. Enable with an `Include` on your output layer. |
49
+ | `Kata/ProsePlacement` | off | Sentence-length strings belong in the presentation layer. Enable with an `Include`/`Exclude` matching your layering. |
50
+
51
+ ## The defaults
52
+
53
+ Double-quoted strings, `Metrics/MethodLength: 5`, `Metrics/ClassLength: 100`,
54
+ `Metrics/ParameterLists: 4`, 120-column lines, endless methods on one line,
55
+ `rescue => error`, `NewCops: enable`, and heredocs counted as one line in
56
+ spec examples. See
57
+ [config/default.yml](config/default.yml).
58
+
59
+ ## License
60
+
61
+ MIT.
@@ -0,0 +1,106 @@
1
+ plugins:
2
+ - rubocop-elegant
3
+ - rubocop-rspec
4
+ - rubocop-performance
5
+ - rubocop-packaging
6
+ - rubocop-thread_safety
7
+
8
+ AllCops:
9
+ NewCops: enable
10
+ Exclude:
11
+ - "vendor/**/*"
12
+
13
+ Kata/AgentNoun:
14
+ Description: "Name classes for the thing they are, not the work they do."
15
+ Enabled: true
16
+ VersionAdded: "0.1"
17
+ Include:
18
+ - "lib/**/*"
19
+ AllowedNames:
20
+ - Error
21
+
22
+ Kata/NoComments:
23
+ Description: "Say it in the code; keep only comments the machine or the law reads."
24
+ Enabled: true
25
+ VersionAdded: "0.1"
26
+ Include:
27
+ - "lib/**/*"
28
+
29
+ Kata/IoDiscipline:
30
+ Description: "Write through an injected io, not bare puts/warn/pp/p."
31
+ Enabled: false
32
+ VersionAdded: "0.1"
33
+
34
+ Kata/ProsePlacement:
35
+ Description: "Keep sentence-length strings in the presentation layer."
36
+ Enabled: false
37
+ VersionAdded: "0.1"
38
+
39
+ Style/Documentation:
40
+ Enabled: false
41
+
42
+ Style/StringLiterals:
43
+ EnforcedStyle: double_quotes
44
+
45
+ Style/StringLiteralsInInterpolation:
46
+ EnforcedStyle: double_quotes
47
+
48
+ Style/ItBlockParameter:
49
+ EnforcedStyle: only_numbered_parameters
50
+
51
+ Style/FormatStringToken:
52
+ EnforcedStyle: unannotated
53
+
54
+ Style/EndlessMethod:
55
+ EnforcedStyle: allow_single_line
56
+
57
+ Naming/RescuedExceptionsVariableName:
58
+ PreferredName: error
59
+
60
+ Layout/LineLength:
61
+ Max: 120
62
+
63
+ Layout/LineEndStringConcatenationIndentation:
64
+ EnforcedStyle: indented
65
+
66
+ Metrics/BlockLength:
67
+ Exclude:
68
+ - "spec/**/*"
69
+ - "*.gemspec"
70
+
71
+ Metrics/ClassLength:
72
+ Max: 100
73
+
74
+ Metrics/ModuleLength:
75
+ Max: 100
76
+ Exclude:
77
+ - "spec/**/*"
78
+
79
+ Metrics/MethodLength:
80
+ Max: 5
81
+ Exclude:
82
+ - "spec/**/*"
83
+
84
+ Metrics/ParameterLists:
85
+ Max: 4
86
+ CountKeywordArgs: true
87
+ Exclude:
88
+ - "spec/**/*"
89
+
90
+ RSpec/ExampleLength:
91
+ CountAsOne:
92
+ - heredoc
93
+
94
+ RSpec/VerifiedDoubles:
95
+ Enabled: true
96
+
97
+ Elegant/NoEmptyLinesInBlocks:
98
+ Exclude:
99
+ - "spec/**/*"
100
+
101
+ Elegant/NoComments:
102
+ Enabled: false
103
+
104
+ ThreadSafety/DirChdir:
105
+ Exclude:
106
+ - "spec/**/*"
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
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
+
6
+ SUFFIX = /(?:er|or)\z/
7
+
8
+ def on_class(node) = check(node)
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 unless SUFFIX.match?(name)
17
+ return if allowed?(name)
18
+ add_offense(node.identifier, message: format(MSG, name))
19
+ end
20
+
21
+ def allowed?(name) = Array(cop_config["AllowedNames"]).map(&:to_s).include?(name)
22
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RuboCop::Cop::Kata::IoDiscipline < RuboCop::Cop::Base
4
+ MSG = "Write through the injected `@io`, not bare `%s`."
5
+
6
+ RESTRICT_ON_SEND = %i[puts warn pp p].freeze
7
+
8
+ def_node_matcher :bare_output?, "(send nil? ${:puts :warn :pp :p} ...)"
9
+
10
+ def on_send(node)
11
+ bare_output?(node) do |method|
12
+ add_offense(node, message: format(MSG, method))
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RuboCop::Cop::Kata::NoComments < RuboCop::Cop::Base
4
+ extend RuboCop::Cop::AutoCorrector
5
+
6
+ MSG = "Say it in the code: rename it, extract it, or name the constant."
7
+
8
+ MAGIC = /\A#\s*(frozen_string_literal|encoding|coding|warn_indent|shareable_constant_value):/
9
+ DIRECTIVE = /\A#\s*(rubocop|simplecov|rbs|steep|sorbet|typed|:nocov:)/
10
+ NOTICE = /^#\s*(copyright\b|\(c\)\s*\d|spdx-|licen[sc]ed under\b|licen[sc]e:|all rights reserved)/i
11
+ SHEBANG = /\A#!/
12
+
13
+ def on_new_investigation
14
+ header = notice
15
+ processed_source.comments.each { register(it) unless exempt?(it) || header.include?(it) }
16
+ end
17
+
18
+ private
19
+
20
+ def exempt?(comment)
21
+ text = comment.text
22
+ MAGIC.match?(text) || DIRECTIVE.match?(text) || NOTICE.match?(text) || SHEBANG.match?(text)
23
+ end
24
+
25
+ def notice
26
+ header = preamble
27
+ NOTICE.match?(header.map(&:text).join("\n")) ? header : []
28
+ end
29
+
30
+ def preamble
31
+ processed_source.comments.take_while { it.source_range.line < (processed_source.ast&.first_line || Float::INFINITY) }
32
+ end
33
+
34
+ def register(comment)
35
+ add_offense(comment) { |corrector| corrector.remove(removal(comment)) }
36
+ end
37
+
38
+ def removal(comment)
39
+ range = comment.source_range
40
+ alone?(range) ? line(range) : trailing(range)
41
+ end
42
+
43
+ def alone?(range) = range.source_line[0, range.column].strip.empty?
44
+
45
+ def line(range)
46
+ finish = range.end_pos
47
+ finish += 1 if processed_source.buffer.source[finish] == "\n"
48
+ range.with(begin_pos: range.begin_pos - range.column, end_pos: finish)
49
+ end
50
+
51
+ def trailing(range)
52
+ range.with(begin_pos: range.begin_pos - range.source_line[0, range.column][/\s*\z/].length)
53
+ end
54
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RuboCop::Cop::Kata::ProsePlacement < RuboCop::Cop::Base
4
+ MSG = "Prose belongs to the presentation layer — pass data and phrase it there."
5
+
6
+ WORDS = 5
7
+
8
+ def on_str(node)
9
+ add_offense(node) if prose?(node.value) && !exempt?(node)
10
+ end
11
+
12
+ def on_dstr(node)
13
+ add_offense(node) if prose?(node.each_child_node(:str).map(&:value).join(" ")) && !exempt?(node)
14
+ end
15
+
16
+ private
17
+
18
+ def prose?(text) = text.split(/\s+/).count { it.match?(/\A[A-Za-z][a-z]*[,.:;]?\z/) } >= WORDS
19
+
20
+ def exempt?(node)
21
+ node.each_ancestor(:send).any? { it.method?(:raise) } || node.each_ancestor(:dstr).any?
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lint_roller"
4
+
5
+ class RuboCop::Kata::Plugin < LintRoller::Plugin
6
+ def about
7
+ LintRoller::About.new(
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."
10
+ )
11
+ end
12
+
13
+ def supported?(context) = context.engine == :rubocop
14
+
15
+ def rules(_context)
16
+ LintRoller::Rules.new(
17
+ type: :path,
18
+ config_format: :rubocop,
19
+ value: Pathname.new(__dir__).join("../../../config/default.yml")
20
+ )
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Kata
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop"
4
+
5
+ module RuboCop
6
+ module Kata
7
+ end
8
+
9
+ module Cop
10
+ module Kata
11
+ end
12
+ end
13
+ end
14
+
15
+ require_relative "rubocop/cop/kata/agent_noun"
16
+ require_relative "rubocop/cop/kata/io_discipline"
17
+ require_relative "rubocop/cop/kata/no_comments"
18
+ require_relative "rubocop/cop/kata/prose_placement"
19
+ require_relative "rubocop/kata/plugin"
20
+ require_relative "rubocop/kata/version"
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubocop-kata
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Giacomo GK
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: lint_roller
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rubocop
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.75'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.75'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rubocop-elegant
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.7'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.7'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rubocop-packaging
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.6'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.6'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rubocop-performance
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.25'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.25'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rubocop-rspec
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.6'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '3.6'
96
+ - !ruby/object:Gem::Dependency
97
+ name: rubocop-thread_safety
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.7'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.7'
110
+ description: 'Bundles a curated RuboCop stack (rspec, performance, elegant, packaging,
111
+ thread_safety) behind one dependency, layers opinionated style defaults on top,
112
+ and adds four cops of its own: AgentNoun, NoComments, IoDiscipline, and ProsePlacement.'
113
+ email:
114
+ - giaco@hey.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - CHANGELOG.md
120
+ - LICENSE.txt
121
+ - README.md
122
+ - config/default.yml
123
+ - lib/rubocop-kata.rb
124
+ - lib/rubocop/cop/kata/agent_noun.rb
125
+ - lib/rubocop/cop/kata/io_discipline.rb
126
+ - lib/rubocop/cop/kata/no_comments.rb
127
+ - lib/rubocop/cop/kata/prose_placement.rb
128
+ - lib/rubocop/kata/plugin.rb
129
+ - lib/rubocop/kata/version.rb
130
+ homepage: https://github.com/giacope/rubocop-kata
131
+ licenses:
132
+ - MIT
133
+ metadata:
134
+ source_code_uri: https://github.com/giacope/rubocop-kata
135
+ changelog_uri: https://github.com/giacope/rubocop-kata/blob/main/CHANGELOG.md
136
+ bug_tracker_uri: https://github.com/giacope/rubocop-kata/issues
137
+ default_lint_roller_plugin: RuboCop::Kata::Plugin
138
+ rubygems_mfa_required: 'true'
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '3.4'
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubygems_version: 4.0.17
154
+ specification_version: 4
155
+ summary: 'Practiced forms for Ruby: a RuboCop plugin with opinionated defaults and
156
+ four house cops'
157
+ test_files: []