rubocop-kata 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/LICENSE.txt +26 -0
- data/README.md +14 -33
- data/config/default.yml +26 -22
- data/lib/rubocop/cop/kata/class_in_module.rb +36 -0
- data/lib/rubocop/cop/kata/no_redundant_variable.rb +52 -0
- data/lib/rubocop/cop/kata/paired_brackets.rb +70 -0
- data/lib/rubocop/kata/plugin.rb +1 -1
- data/lib/rubocop/kata/variable/boundary.rb +33 -0
- data/lib/rubocop/kata/variable/gap.rb +53 -0
- data/lib/rubocop/kata/variable/inlining.rb +48 -0
- data/lib/rubocop/kata/variable/ledger.rb +50 -0
- data/lib/rubocop/kata/version.rb +1 -1
- data/lib/rubocop-kata.rb +9 -0
- metadata +10 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0d5931b8e8bdf39595150f5f4c4785fd1e7c267c4abfda47d02999913aa4bdd2
|
|
4
|
+
data.tar.gz: 56fdf88f035c7833469f0a290351696b2a1197f1f5cee2282ad60928201b3323
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 122b3da618e42d4137b1c4986bd21ba63374e275168d84181afa87cfae319465fe07270d12d54c3cece22ac6b73585a137d7ac5780f22f5a9c4fb0f667a40a07
|
|
7
|
+
data.tar.gz: b9bfb1dc0d5be32ea85898cfae3fbe3ce3338578f2e57b1e7b9effd2e00b2847c50bedcb5f9d259292de8141fe3579e11b0bb925a9840534047f26577f5dff26
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.0] - 2026-08-30
|
|
4
|
+
|
|
5
|
+
- `Elegant/NoRedundantVariable`, `Elegant/PairedBrackets`, and
|
|
6
|
+
`Elegant/ClassInModule` now ship here, fixed, as `Kata/*` cops derived from
|
|
7
|
+
rubocop-elegant under MIT (`LICENSE.txt`); the originals are
|
|
8
|
+
off. The git-source Gemfile line is no longer needed: the correctors run
|
|
9
|
+
out of the box, and `ClassInModule` clears.
|
|
10
|
+
|
|
3
11
|
## [0.9.0] - 2026-08-27
|
|
4
12
|
|
|
5
13
|
- The shared rule behind the naming cops is now explicit: compound syntax is
|
data/LICENSE.txt
CHANGED
|
@@ -19,3 +19,29 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
Portions of this software are derived from rubocop-elegant
|
|
26
|
+
(https://github.com/yegor256/rubocop-elegant), used under the same MIT
|
|
27
|
+
License, whose notice is reproduced here as it requires:
|
|
28
|
+
|
|
29
|
+
Copyright (c) 2019-2026 Yegor Bugayenko
|
|
30
|
+
|
|
31
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
32
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
33
|
+
in the Software without restriction, including without limitation the rights
|
|
34
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
35
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
36
|
+
furnished to do so, subject to the following conditions:
|
|
37
|
+
|
|
38
|
+
The above copyright notice and this permission notice shall be included in all
|
|
39
|
+
copies or substantial portions of the Software.
|
|
40
|
+
|
|
41
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
42
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
43
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
44
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
45
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
46
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
47
|
+
SOFTWARE.
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
|
|
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 eighteen house cops.
|
|
4
4
|
|
|
5
5
|
`Kata/GoodMethodName` asks for one word per method name. A second word is
|
|
6
6
|
allowed only when it is not hiding a missing object: a role prefix
|
|
@@ -14,7 +14,7 @@ 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
|
-
- **
|
|
17
|
+
- **Eighteen 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
20
|
class PaymentProcessor # Kata/GoodClassName: `PaymentProcessor` names a doer; name the class
|
|
@@ -65,38 +65,16 @@ reports a class nested in a class as global
|
|
|
65
65
|
test-file exclusions never match a Rails or RSpec suite
|
|
66
66
|
([#77](https://github.com/yegor256/rubocop-elegant/issues/77)).
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
Until upstream releases the fixes, the three cops ship here, fixed, as
|
|
69
|
+
`Kata/NoRedundantVariable`, `Kata/PairedBrackets`, and `Kata/ClassInModule`;
|
|
70
|
+
the `Elegant/*` originals are off so each offence is reported once. They are
|
|
71
|
+
derived from rubocop-elegant under its MIT licence, and the notice travels
|
|
72
|
+
with the gem as `LICENSE.txt`. The exclusions are widened.
|
|
73
|
+
Once upstream ships, the copies go and the originals come back on.
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
```ruby
|
|
78
|
-
gem "rubocop-elegant", github: "giacope/rubocop-elegant", branch: "fixes"
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
That branch is the released gem plus the five pull requests, with the version
|
|
82
|
-
pinned so Bundler resolves it. With it in place you can turn the two correctors
|
|
83
|
-
back on:
|
|
84
|
-
|
|
85
|
-
```yaml
|
|
86
|
-
Elegant/NoRedundantVariable:
|
|
87
|
-
AutoCorrect: true
|
|
88
|
-
|
|
89
|
-
Elegant/PairedBrackets:
|
|
90
|
-
AutoCorrect: true
|
|
91
|
-
|
|
92
|
-
Elegant/ClassInModule:
|
|
93
|
-
Enabled: true
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
Drop all three once upstream releases the fixes. On a Rails codebase, consider
|
|
97
|
-
leaving `Elegant/ClassInModule` off for good: it wants every class inside a
|
|
98
|
-
module, and Zeitwerk resolves `class Account` from `app/models/account.rb` as a
|
|
99
|
-
top-level constant by design.
|
|
75
|
+
On a Rails codebase, consider turning `Kata/ClassInModule` off: it wants
|
|
76
|
+
every class inside a module, and Zeitwerk resolves `class Account` from
|
|
77
|
+
`app/models/account.rb` as a top-level constant by design.
|
|
100
78
|
|
|
101
79
|
## The cops
|
|
102
80
|
|
|
@@ -104,6 +82,9 @@ top-level constant by design.
|
|
|
104
82
|
| --- | --- | --- |
|
|
105
83
|
| `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
84
|
| `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`. |
|
|
85
|
+
| `Kata/NoRedundantVariable` | on | A local assigned once and read once is inlined at its read, unless the move would cross a loop, a branch, or a side effect. Autocorrects, and keeps Ruby 3.1 shorthand intact. Fixed copy of `Elegant/NoRedundantVariable`. |
|
|
86
|
+
| `Kata/PairedBrackets` | on | Every bracket pairs on its line, or opens at line end and closes at line start. Autocorrects without drifting the indent. Fixed copy of `Elegant/PairedBrackets`. |
|
|
87
|
+
| `Kata/ClassInModule` | on | A class lives in a module, a class, or a compact namespace, never at the top level. Fixed copy of `Elegant/ClassInModule`. |
|
|
107
88
|
| `Kata/NoComments` | on | No prose comments; say it in the code. Magic comments, linter directives, and licence headers survive. Autocorrects. |
|
|
108
89
|
| `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. |
|
|
109
90
|
| `Kata/ProsePlacement` | off | Sentence-length strings belong in the presentation layer. Enable with an `Include`/`Exclude` matching your layering. |
|
data/config/default.yml
CHANGED
|
@@ -778,17 +778,35 @@ Layout/EmptyLinesAfterModuleInclusion:
|
|
|
778
778
|
Style/DisableCopsWithinSourceCodeDirective:
|
|
779
779
|
Enabled: true
|
|
780
780
|
|
|
781
|
-
# ---
|
|
782
|
-
#
|
|
783
|
-
# corrupts Ruby 3.1 keyword shorthand.
|
|
781
|
+
# --- Three Elegant cops ship here as Kata/*, fixed (see README); the
|
|
782
|
+
# originals are off so an offence is reported once.
|
|
784
783
|
Elegant/NoRedundantVariable:
|
|
785
|
-
|
|
784
|
+
Enabled: false
|
|
786
785
|
|
|
787
|
-
# Inserts a newline beside a bracket without taking the whitespace already
|
|
788
|
-
# there, so every corrected line drifts right and MonotonicIndents then
|
|
789
|
-
# reports the line it just wrote.
|
|
790
786
|
Elegant/PairedBrackets:
|
|
791
|
-
|
|
787
|
+
Enabled: false
|
|
788
|
+
|
|
789
|
+
Elegant/ClassInModule:
|
|
790
|
+
Enabled: false
|
|
791
|
+
|
|
792
|
+
Kata/NoRedundantVariable:
|
|
793
|
+
Description: "A local assigned once and read once is inlined at its read."
|
|
794
|
+
Enabled: true
|
|
795
|
+
VersionAdded: "0.10"
|
|
796
|
+
|
|
797
|
+
Kata/PairedBrackets:
|
|
798
|
+
Description: "A bracket pairs on its line, or opens at the end of one and closes at the start of another."
|
|
799
|
+
Enabled: true
|
|
800
|
+
VersionAdded: "0.10"
|
|
801
|
+
|
|
802
|
+
Kata/ClassInModule:
|
|
803
|
+
Description: "A class lives in a module, a class, or a compact namespace, never at the top level."
|
|
804
|
+
Enabled: true
|
|
805
|
+
VersionAdded: "0.10"
|
|
806
|
+
Exclude:
|
|
807
|
+
- "**/*_test.rb"
|
|
808
|
+
- "test/**/*"
|
|
809
|
+
- "spec/**/*"
|
|
792
810
|
|
|
793
811
|
# Rewrites reopened classes into modules, clashing with the primary
|
|
794
812
|
# class definition at boot.
|
|
@@ -811,20 +829,6 @@ ThreadSafety/DirChdir:
|
|
|
811
829
|
|
|
812
830
|
# --- Elegant's test-file exclusions never match Rails' `foo_test.rb`, nor a
|
|
813
831
|
# suite addressed by directory.
|
|
814
|
-
# Off until upstream ships: it reports a class nested in a class as global, and
|
|
815
|
-
# Elegant/NoClassInModule forbids the module the message asks for, so the pair
|
|
816
|
-
# admits no shape. It also reads a top-level constant as a defect, which is how
|
|
817
|
-
# Rails resolves one. https://github.com/yegor256/rubocop-elegant/issues/75
|
|
818
|
-
Elegant/ClassInModule:
|
|
819
|
-
Enabled: false
|
|
820
|
-
inherit_mode:
|
|
821
|
-
merge:
|
|
822
|
-
- Exclude
|
|
823
|
-
Exclude:
|
|
824
|
-
- "**/*_test.rb"
|
|
825
|
-
- "test/**/*"
|
|
826
|
-
- "spec/**/*"
|
|
827
|
-
|
|
828
832
|
Elegant/NoClassInModule:
|
|
829
833
|
inherit_mode:
|
|
830
834
|
merge:
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
# Derived from rubocop-elegant (https://github.com/yegor256/rubocop-elegant), see LICENSE.txt
|
|
6
|
+
|
|
7
|
+
class RuboCop::Cop::Kata::ClassInModule < RuboCop::Cop::Base
|
|
8
|
+
MSG = "Class %s must be defined inside a module, not globally"
|
|
9
|
+
public_constant :MSG
|
|
10
|
+
|
|
11
|
+
def on_class(node)
|
|
12
|
+
return if namespaced?(node)
|
|
13
|
+
return if scoped?(node)
|
|
14
|
+
add_offense(node, message: format(MSG, name: label(node)))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def namespaced?(node)
|
|
20
|
+
scope = outer(node)
|
|
21
|
+
!scope.nil? && scope.type != :cbase
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def scoped?(node)
|
|
25
|
+
return false unless outer(node).nil?
|
|
26
|
+
node.each_ancestor(:module, :class).any?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def outer(node)
|
|
30
|
+
node.children[0].children[0]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def label(node)
|
|
34
|
+
node.children[0].source
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
# Derived from rubocop-elegant (https://github.com/yegor256/rubocop-elegant), see LICENSE.txt
|
|
6
|
+
|
|
7
|
+
class RuboCop::Cop::Kata::NoRedundantVariable < RuboCop::Cop::Base
|
|
8
|
+
extend RuboCop::Cop::AutoCorrector
|
|
9
|
+
include RuboCop::Cop::RangeHelp
|
|
10
|
+
|
|
11
|
+
MSG = 'Variable "%s" is redundant and must be inlined: it is read only once'
|
|
12
|
+
|
|
13
|
+
def on_def(node) = check(node.body)
|
|
14
|
+
|
|
15
|
+
def on_defs(node) = check(node.body)
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def check(body)
|
|
20
|
+
return if body.nil?
|
|
21
|
+
singles = RuboCop::Kata::Variable::Ledger.new(body).pairs
|
|
22
|
+
movable = singles.select { |assign, _| solo?(assign) && !swallows?(assign, singles) }
|
|
23
|
+
gap = RuboCop::Kata::Variable::Gap.new(movable)
|
|
24
|
+
singles.each { |assign, read| register(assign, read, movable) unless gap.crossed?(assign, read) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def register(assign, read, movable)
|
|
28
|
+
return inline(assign, read) if movable.key?(assign)
|
|
29
|
+
add_offense(assign, message: format(MSG, assign.children.first))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def inline(assign, read)
|
|
33
|
+
inlining = RuboCop::Kata::Variable::Inlining.new(assign.children.last, read)
|
|
34
|
+
add_offense(assign, message: format(MSG, assign.children.first)) do |corrector|
|
|
35
|
+
corrector.replace(inlining.range, inlining.text)
|
|
36
|
+
corrector.remove(range_by_whole_lines(assign.source_range, include_final_newline: true))
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def swallows?(assign, singles)
|
|
41
|
+
range = assign.source_range
|
|
42
|
+
singles.any? do |_, read|
|
|
43
|
+
read.source_range.begin_pos > range.begin_pos && read.source_range.end_pos <= range.end_pos
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def solo?(assign)
|
|
48
|
+
range = assign.source_range
|
|
49
|
+
return false unless range.first_line == range.last_line
|
|
50
|
+
range.source_buffer.source_line(range.first_line).strip == range.source.strip
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
# Derived from rubocop-elegant (https://github.com/yegor256/rubocop-elegant), see LICENSE.txt
|
|
6
|
+
|
|
7
|
+
class RuboCop::Cop::Kata::PairedBrackets < RuboCop::Cop::Base
|
|
8
|
+
extend RuboCop::Cop::AutoCorrector
|
|
9
|
+
|
|
10
|
+
MSG = "Bracket %s must be paired on the same line, or start/end its line"
|
|
11
|
+
|
|
12
|
+
OPENERS = %i[tLPAREN tLPAREN2 tLPAREN_ARG tLBRACK tLBRACK2 tLCURLY tLBRACE tLBRACE_ARG].freeze
|
|
13
|
+
CLOSERS = %i[tRPAREN tRBRACK tRCURLY].freeze
|
|
14
|
+
BLANKS = [" ", "\t"].freeze
|
|
15
|
+
|
|
16
|
+
def on_new_investigation
|
|
17
|
+
super
|
|
18
|
+
pairs.each { |opener, closer| check(opener, closer) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def pairs
|
|
24
|
+
stack = []
|
|
25
|
+
processed_source.tokens.each_with_object([]) do |token, found|
|
|
26
|
+
stack << token if OPENERS.include?(token.type)
|
|
27
|
+
found << [stack.pop, token] if CLOSERS.include?(token.type) && !stack.empty?
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def check(opener, closer)
|
|
32
|
+
return if opener.line == closer.line
|
|
33
|
+
indent = leading(opener)
|
|
34
|
+
after(opener, indent) unless ends?(opener)
|
|
35
|
+
before(closer, indent) unless starts?(closer)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def after(token, indent)
|
|
39
|
+
register(token) { it.replace(trailing(token), "\n#{indent} ") }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def before(token, indent)
|
|
43
|
+
register(token) { it.replace(preceding(token), "\n#{indent}") }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def trailing(token)
|
|
47
|
+
text = processed_source.buffer.source
|
|
48
|
+
stop = token.pos.end_pos
|
|
49
|
+
stop += 1 while BLANKS.include?(text[stop])
|
|
50
|
+
token.pos.end.resize(stop - token.pos.end_pos)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def preceding(token)
|
|
54
|
+
text = processed_source.buffer.source
|
|
55
|
+
from = token.pos.begin_pos
|
|
56
|
+
from -= 1 while from.positive? && BLANKS.include?(text[from - 1])
|
|
57
|
+
token.pos.begin.adjust(begin_pos: from - token.pos.begin_pos)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def starts?(token) = processed_source.lines[token.line - 1][0...token.column].strip.empty?
|
|
61
|
+
|
|
62
|
+
def ends?(token)
|
|
63
|
+
after = processed_source.lines[token.line - 1][(token.column + token.text.length)..].to_s.strip
|
|
64
|
+
after.empty? || after.start_with?("#")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def leading(token) = processed_source.lines[token.line - 1][/\A[ \t]*/]
|
|
68
|
+
|
|
69
|
+
def register(token, &) = add_offense(token.pos, message: format(MSG, token.text), &)
|
|
70
|
+
end
|
data/lib/rubocop/kata/plugin.rb
CHANGED
|
@@ -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
|
|
9
|
+
description: "Practiced forms for Ruby: opinionated defaults and eighteen house cops."
|
|
10
10
|
)
|
|
11
11
|
end
|
|
12
12
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
# Derived from rubocop-elegant (https://github.com/yegor256/rubocop-elegant), see LICENSE.txt
|
|
6
|
+
|
|
7
|
+
class RuboCop::Kata::Variable::Boundary
|
|
8
|
+
LOOPS = %i[block numblock while until while_post until_post for].freeze
|
|
9
|
+
FIRSTS = %i[if case case_match and or].freeze
|
|
10
|
+
HOISTS = %i[rescue resbody ensure].freeze
|
|
11
|
+
|
|
12
|
+
def initialize(assign, read)
|
|
13
|
+
@assign = assign
|
|
14
|
+
@read = read
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def crossed?
|
|
18
|
+
return true unless @read.each_ancestor.any? { it.equal?(@assign.parent) }
|
|
19
|
+
steps.any? { |child, parent| blocks?(child, parent) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def steps
|
|
25
|
+
lineage = @read.each_ancestor.take_while { !it.equal?(@assign.parent) }
|
|
26
|
+
[@read, *lineage].each_cons(2)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def blocks?(child, parent)
|
|
30
|
+
return true if LOOPS.include?(parent.type) || HOISTS.include?(parent.type)
|
|
31
|
+
FIRSTS.include?(parent.type) && !parent.children.first.equal?(child)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
# Derived from rubocop-elegant (https://github.com/yegor256/rubocop-elegant), see LICENSE.txt
|
|
6
|
+
|
|
7
|
+
class RuboCop::Kata::Variable::Gap
|
|
8
|
+
PURE = %i[
|
|
9
|
+
int float rational complex str sym true false nil array hash regexp regopt
|
|
10
|
+
irange erange lvar self pair begin
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
def initialize(movable)
|
|
14
|
+
@movable = movable
|
|
15
|
+
@memo = {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def crossed?(assign, read)
|
|
19
|
+
@memo[assign] = scan?(assign, read) unless @memo.key?(assign)
|
|
20
|
+
@memo[assign]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def scan?(assign, read)
|
|
26
|
+
steps(assign, read).any? do |child, parent|
|
|
27
|
+
before(parent, child, assign).any? { !deferred?(it, read) }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def steps(assign, read)
|
|
32
|
+
lineage = read.each_ancestor.to_a
|
|
33
|
+
[read, *lineage[..(lineage.index { it.equal?(assign.parent) })]].each_cons(2)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def before(parent, child, assign)
|
|
37
|
+
kids = parent.children
|
|
38
|
+
stop = kids.index { it.equal?(child) }
|
|
39
|
+
return [] if stop.nil?
|
|
40
|
+
kids[(parent.equal?(assign.parent) ? kids.index { it.equal?(assign) } + 1 : 0)...stop]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def deferred?(node, read)
|
|
44
|
+
return true if pure?(node)
|
|
45
|
+
twin = @movable[node]
|
|
46
|
+
!twin.nil? && twin.source_range.begin_pos >= read.source_range.begin_pos && !crossed?(node, twin)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def pure?(node)
|
|
50
|
+
return true unless node.is_a?(RuboCop::AST::Node)
|
|
51
|
+
PURE.include?(node.type) && node.each_child_node.all? { pure?(it) }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
# Derived from rubocop-elegant (https://github.com/yegor256/rubocop-elegant), see LICENSE.txt
|
|
6
|
+
|
|
7
|
+
class RuboCop::Kata::Variable::Inlining
|
|
8
|
+
PRIMARY = %i[
|
|
9
|
+
int float str sym dstr dsym xstr true false nil array hash regexp
|
|
10
|
+
lvar ivar cvar gvar const self nth_ref back_ref
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
def initialize(rhs, read)
|
|
14
|
+
@rhs = rhs
|
|
15
|
+
@read = read
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def range = pair.nil? ? @read.source_range : pair.source_range
|
|
19
|
+
|
|
20
|
+
def text = pair.nil? ? value : "#{pair.children.first.source}: #{value}"
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def pair
|
|
25
|
+
parent = @read.parent
|
|
26
|
+
return if parent.nil? || !parent.pair_type?
|
|
27
|
+
key, value = parent.children
|
|
28
|
+
parent if value.equal?(@read) && key.source_range == value.source_range
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def value = wrap? ? "(#{braced})" : braced
|
|
32
|
+
|
|
33
|
+
def braced = @rhs.hash_type? && @rhs.loc.begin.nil? ? "{ #{@rhs.source} }" : @rhs.source
|
|
34
|
+
|
|
35
|
+
def wrap? = !primary? || (@rhs.hash_type? && bare?)
|
|
36
|
+
|
|
37
|
+
def primary?
|
|
38
|
+
return true if PRIMARY.include?(@rhs.type)
|
|
39
|
+
return !@rhs.loc.begin.nil? || @rhs.arguments.empty? if @rhs.send_type? || @rhs.csend_type?
|
|
40
|
+
@rhs.begin_type? && @rhs.children.size == 1
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def bare?
|
|
44
|
+
parent = @read.parent
|
|
45
|
+
return false if parent.nil? || !(parent.send_type? || parent.csend_type?)
|
|
46
|
+
parent.loc.begin.nil? && parent.arguments.include?(@read)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
# Derived from rubocop-elegant (https://github.com/yegor256/rubocop-elegant), see LICENSE.txt
|
|
6
|
+
|
|
7
|
+
class RuboCop::Kata::Variable::Ledger
|
|
8
|
+
STATEMENT_PARENTS = %i[begin kwbegin def defs block numblock].freeze
|
|
9
|
+
|
|
10
|
+
def initialize(body)
|
|
11
|
+
@body = body
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def pairs
|
|
15
|
+
singles.reject { |assign, read| RuboCop::Kata::Variable::Boundary.new(assign, read).crossed? }.to_h
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def singles
|
|
21
|
+
reads = self.reads
|
|
22
|
+
tainted = self.tainted
|
|
23
|
+
assigns.filter_map { |name, found| single(found, reads.fetch(name, [])) unless tainted.include?(name) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def single(found, once) = ([found.first, once.first] if found.size == 1 && once.size == 1)
|
|
27
|
+
|
|
28
|
+
def assigns = nodes.select { it.lvasgn_type? && statement?(it) }.group_by { it.children.first }
|
|
29
|
+
|
|
30
|
+
def reads = nodes.select(&:lvar_type?).group_by { it.children.first }
|
|
31
|
+
|
|
32
|
+
def tainted
|
|
33
|
+
nodes.select { it.op_asgn_type? || it.or_asgn_type? || it.and_asgn_type? }.map { it.children.first }
|
|
34
|
+
.select { it.is_a?(RuboCop::AST::Node) && it.lvasgn_type? }.map { it.children.first }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def nodes(node = @body, found = [])
|
|
38
|
+
return found unless node.is_a?(RuboCop::AST::Node) && !node.def_type? && !node.defs_type?
|
|
39
|
+
found << node
|
|
40
|
+
node.each_child_node { nodes(it, found) }
|
|
41
|
+
found
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def statement?(node)
|
|
45
|
+
return false unless node.children.size == 2
|
|
46
|
+
parent = node.parent
|
|
47
|
+
parent = parent.parent while !parent.nil? && parent.type == :begin && parent.children.size == 1
|
|
48
|
+
!parent.nil? && STATEMENT_PARENTS.include?(parent.type)
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/rubocop/kata/version.rb
CHANGED
data/lib/rubocop-kata.rb
CHANGED
|
@@ -4,6 +4,8 @@ require "rubocop"
|
|
|
4
4
|
|
|
5
5
|
module RuboCop
|
|
6
6
|
module Kata
|
|
7
|
+
module Variable
|
|
8
|
+
end
|
|
7
9
|
end
|
|
8
10
|
|
|
9
11
|
module Cop
|
|
@@ -13,6 +15,7 @@ module RuboCop
|
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
require_relative "rubocop/cop/kata/builder_noun"
|
|
18
|
+
require_relative "rubocop/cop/kata/class_in_module"
|
|
16
19
|
require_relative "rubocop/cop/kata/clock_discipline"
|
|
17
20
|
require_relative "rubocop/cop/kata/constructor_discipline"
|
|
18
21
|
require_relative "rubocop/cop/kata/env_discipline"
|
|
@@ -24,10 +27,16 @@ require_relative "rubocop/cop/kata/good_variable_name"
|
|
|
24
27
|
require_relative "rubocop/cop/kata/io_discipline"
|
|
25
28
|
require_relative "rubocop/cop/kata/no_boolean_flag"
|
|
26
29
|
require_relative "rubocop/cop/kata/no_class_method_logic"
|
|
30
|
+
require_relative "rubocop/cop/kata/no_redundant_variable"
|
|
27
31
|
require_relative "rubocop/cop/kata/no_comments"
|
|
28
32
|
require_relative "rubocop/cop/kata/no_hash_as_object"
|
|
33
|
+
require_relative "rubocop/cop/kata/paired_brackets"
|
|
29
34
|
require_relative "rubocop/cop/kata/prose_placement"
|
|
30
35
|
require_relative "rubocop/cop/kata/real_words"
|
|
31
36
|
require_relative "rubocop/kata/dictionary"
|
|
37
|
+
require_relative "rubocop/kata/variable/boundary"
|
|
38
|
+
require_relative "rubocop/kata/variable/gap"
|
|
39
|
+
require_relative "rubocop/kata/variable/inlining"
|
|
40
|
+
require_relative "rubocop/kata/variable/ledger"
|
|
32
41
|
require_relative "rubocop/kata/plugin"
|
|
33
42
|
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.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Giacomo GK
|
|
@@ -109,7 +109,7 @@ dependencies:
|
|
|
109
109
|
version: '0.7'
|
|
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
|
-
and adds
|
|
112
|
+
and adds eighteen cops of its own — naming (GoodMethodName, GoodVariableName, dictionary-backed
|
|
113
113
|
RealWords, GoodClassName, GoodModuleName, BuilderNoun), discipline (IoDiscipline,
|
|
114
114
|
ClockDiscipline, EnvDiscipline, ConstructorDiscipline, NoClassMethodLogic), and
|
|
115
115
|
shape (NoComments, NoBooleanFlag, NoHashAsObject, ProsePlacement). The rubocop-kata
|
|
@@ -135,6 +135,7 @@ files:
|
|
|
135
135
|
- lib/rubocop-kata.rb
|
|
136
136
|
- lib/rubocop/cop/kata/agent_noun.rb
|
|
137
137
|
- lib/rubocop/cop/kata/builder_noun.rb
|
|
138
|
+
- lib/rubocop/cop/kata/class_in_module.rb
|
|
138
139
|
- lib/rubocop/cop/kata/clock_discipline.rb
|
|
139
140
|
- lib/rubocop/cop/kata/constructor_discipline.rb
|
|
140
141
|
- lib/rubocop/cop/kata/env_discipline.rb
|
|
@@ -147,6 +148,8 @@ files:
|
|
|
147
148
|
- lib/rubocop/cop/kata/no_class_method_logic.rb
|
|
148
149
|
- lib/rubocop/cop/kata/no_comments.rb
|
|
149
150
|
- lib/rubocop/cop/kata/no_hash_as_object.rb
|
|
151
|
+
- lib/rubocop/cop/kata/no_redundant_variable.rb
|
|
152
|
+
- lib/rubocop/cop/kata/paired_brackets.rb
|
|
150
153
|
- lib/rubocop/cop/kata/prose_placement.rb
|
|
151
154
|
- lib/rubocop/cop/kata/real_words.rb
|
|
152
155
|
- lib/rubocop/kata/checkup.rb
|
|
@@ -154,6 +157,10 @@ files:
|
|
|
154
157
|
- lib/rubocop/kata/kit.rb
|
|
155
158
|
- lib/rubocop/kata/plan.rb
|
|
156
159
|
- lib/rubocop/kata/plugin.rb
|
|
160
|
+
- lib/rubocop/kata/variable/boundary.rb
|
|
161
|
+
- lib/rubocop/kata/variable/gap.rb
|
|
162
|
+
- lib/rubocop/kata/variable/inlining.rb
|
|
163
|
+
- lib/rubocop/kata/variable/ledger.rb
|
|
157
164
|
- lib/rubocop/kata/version.rb
|
|
158
165
|
- skills/rubocop-kata/SKILL.md
|
|
159
166
|
homepage: https://github.com/giacope/rubocop-kata
|
|
@@ -182,5 +189,5 @@ requirements: []
|
|
|
182
189
|
rubygems_version: 4.0.17
|
|
183
190
|
specification_version: 4
|
|
184
191
|
summary: 'Practiced forms for Ruby: a RuboCop plugin with opinionated defaults and
|
|
185
|
-
|
|
192
|
+
eighteen house cops'
|
|
186
193
|
test_files: []
|