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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c1ecc1a90d7d43dba2861011f7f9c8c1ecc97e71040908f1018df3199aca660
4
- data.tar.gz: 216cb124bc0565ab89d5cf3ee52225e24771d6f4d0812e8a272b0fc14517e45e
3
+ metadata.gz: 0d5931b8e8bdf39595150f5f4c4785fd1e7c267c4abfda47d02999913aa4bdd2
4
+ data.tar.gz: 56fdf88f035c7833469f0a290351696b2a1197f1f5cee2282ad60928201b3323
5
5
  SHA512:
6
- metadata.gz: 5a806b0b657d6fa01fe422361616304f6581e23e041f76a70d3dc33f61814a600a808439282417de3f8ea34d03612927fdfcbced3f18010b90660407ffa165f7
7
- data.tar.gz: 687dd4b67fb477cd51e4440e79f7d81a020f4712c1802541447eb489f97be8fab728cb26460c947113a2789f1fddf1f0e93ecd9bad70d2eadd232cbe025bf74d
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 fifteen 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 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
- - **Fifteen house cops.** Naming, dependency discipline, and data honesty in the Elegant Objects spirit — from `Kata/GoodClassName` to `Kata/ClockDiscipline`.
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
- The defaults here neutralise all four. `Elegant/ClassInModule` is off, because
69
- its offences cannot be cleared: the cop it ships beside forbids the module it
70
- asks for. The two broken correctors are set to report only, because their
71
- offences can be cleared, by hand. The exclusions are widened. Nothing corrupts
72
- your code out of the box, and nothing reports a defect you cannot fix.
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
- A gemspec cannot name a git source, so to take the fixes themselves rather than
75
- the workarounds, add this to your own `Gemfile`:
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
- # --- Correctors known to rewrite code incorrectly; report only.
782
- # Inlines a variable past side effects (ivar resets, later statements) and
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
- AutoCorrect: false
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
- AutoCorrect: false
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
@@ -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 fourteen house cops."
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Kata
5
- VERSION = "0.9.0"
5
+ VERSION = "0.10.0"
6
6
  end
7
7
  end
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.9.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 fifteen cops of its own — naming (GoodMethodName, GoodVariableName, dictionary-backed
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
- fifteen house cops'
192
+ eighteen house cops'
186
193
  test_files: []