rubocop-fourshark 0.5.1 → 0.7.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: 41cd76136c01c232f0172ee89eed1812e4c9becb8d34f53b164848d82695bad3
4
- data.tar.gz: d5fda1ef5f623c50ff6cad9db4f332b44df2bf851be64c4d604adb70e558bad3
3
+ metadata.gz: 49c5ac14dc35386b05d887fcd8b53f2ebafd991014046fef3df088a8c4174816
4
+ data.tar.gz: 953643562da8fcdda58cd4482246328baddff15ac4ee881468defaaf669ce218
5
5
  SHA512:
6
- metadata.gz: 1950be7fc24c28c1b89dfd0208da4e2ac997b300ee5a01ecee42601e4bda5fe192619f36d1e26d5da00ff5ca164fb19ab087f09a8ff515ae6510547b3d938e7c
7
- data.tar.gz: 78ed7eb5c9dd271902c2c857bdf2fe10f646bca60246813c5a7313e4a886e4e8e7b2ae7e7a516179b13f7bee2f0734f96690b89d2f9276cef3fb146f1eb620a1
6
+ metadata.gz: b0bc1b6c88559d9b780d05bfb1f052a67b99f5eebc0be6db93e6aa934e9c62cf34ecc594c9549887dee3089874c7c733320781e5025dd664aa0642ae8a211cc4
7
+ data.tar.gz: 91995ae5a6cabfccdb2374540e27d6dc3f481cbbc4ac54fc888359a237186400dda1e605de34e5169fb304bf3ec2b22ad78a3e80af83ba7ce1823eec69716cbc
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## [0.7.0] - 2026-08-05
2
+
3
+ ### Added
4
+
5
+ - `Style/DisallowDelegate` — automatic delegation is flagged
6
+ - `Style/DisallowTernary` — the ternary conditional is flagged
7
+
8
+ ### Changed
9
+
10
+ - `Rails/Delegate` disabled — it asks for the macro `Style/DisallowDelegate` forbids
11
+ - `Style/MultilineTernaryOperator`, `Style/NestedTernaryOperator` and `Style/TernaryParentheses` disabled — they shape a construct `Style/DisallowTernary` forbids outright
12
+ - `Style/ConditionalAssignment` configured to `assign_inside_condition` — a conditional that decides a value assigns it in each branch, and ternaries are left to `Style/DisallowTernary`
13
+
14
+ ## [0.6.0] - 2026-08-05
15
+
16
+ ### Added
17
+
18
+ - `Naming/RescuedExceptionsVariableName` configured to `exception` — a rescued exception is no longer bound to a single-letter name
19
+
1
20
  ## [0.5.1] - 2026-06-29
2
21
 
3
22
  ### Fixed
data/README.md CHANGED
@@ -52,11 +52,11 @@ Activating the plugin auto-loads the gem's `config/default.yml`, which enables e
52
52
 
53
53
  All cops are **enabled by default** the moment the plugin is activated. Cops scoped to a path (models, specs, factories) only run on files matching that path. Each cop's source file under [`lib/rubocop/cop`](lib/rubocop/cop) carries runnable `@example` blocks showing the bad/good shapes.
54
54
 
55
- ### Naming
55
+ ### Cop naming
56
56
 
57
57
  Cop names follow RuboCop's empirical convention — a noun phrase describing the construct or smell, no `Disallow*`/`No*` prefixes — with two deliberate exceptions:
58
58
 
59
- - **`Style/DisallowSafeNavigation` / `Style/DisallowTry`** keep the `Disallow*` prefix. RuboCop has no idiom for "forbid a construct it otherwise permits" (it uses `EnforcedStyle` on one cop), and the natural noun name is already taken by a stock cop with the *opposite* intent — `Style/SafeNavigation` *converts to* `&.`. `Disallow*` is unambiguous and collision-free.
59
+ - **`Style/DisallowDelegate` / `Style/DisallowSafeNavigation` / `Style/DisallowTernary` / `Style/DisallowTry`** keep the `Disallow*` prefix. RuboCop has no idiom for "forbid a construct it otherwise permits" (it uses `EnforcedStyle` on one cop), and the natural noun name is already taken by a stock cop with the *opposite* intent — `Style/SafeNavigation` *converts to* `&.`, `Rails/Delegate` *converts to* `delegate`. `Disallow*` is unambiguous and collision-free.
60
60
  - **`Rails/MandatoryInverseOf`** is named to distinguish it from stock `Rails/InverseOf`, which only flags associations where Active Record cannot auto-detect the inverse. Ours mandates `inverse_of` on *every* association — a strict superset — so the gem disables the stock cop (below).
61
61
 
62
62
  ### Stock cops disabled
@@ -65,14 +65,21 @@ Where a 4Shark cop supersedes or contradicts a stock cop, `config/default.yml` t
65
65
 
66
66
  | Disabled stock cop | Why |
67
67
  |---|---|
68
+ | `Rails/Delegate` | contradicts `Style/DisallowDelegate` — it turns an explicit method into `delegate`, we forbid the macro |
68
69
  | `Rails/InverseOf` | superseded by `Rails/MandatoryInverseOf` (covers its cases and more) |
70
+ | `Style/MultilineTernaryOperator` | superseded by `Style/DisallowTernary` — it shapes a construct we forbid outright |
71
+ | `Style/NestedTernaryOperator` | superseded by `Style/DisallowTernary` — same |
69
72
  | `Style/SafeNavigation` | contradicts `Style/DisallowSafeNavigation` — it pushes `&.`, we forbid it |
73
+ | `Style/TernaryParentheses` | superseded by `Style/DisallowTernary` — same |
70
74
 
71
75
  ### Style
72
76
 
73
77
  | Cop | Intent |
74
78
  |---|---|
79
+ | `Style/ConditionalAssignment` | When a conditional decides a value, each branch assigns the variable. The cop's default (`assign_to_condition`) demands `x = if ... else ... end`, the one form the no-ternary convention rejects — converting a ternary would land straight on it. Ternaries are out of scope (`IncludeTernaryExpressions: false`): `Style/DisallowTernary` bans them, and this cop's ternary autocorrect only produces another ternary. Stock cop, configured via `EnforcedStyle`. |
80
+ | `Style/DisallowDelegate` | Flags automatic delegation (`delegate`, `delegate_missing_to`, `def_delegator(s)`, `DelegateClass`). A `delegate` line is a macro call, not a definition, so `grep 'def foo'` and jump-to-definition find nothing — the community name for the smell is Fowler's Middle Man. Write the method, or let the caller ask the object that knows. |
75
81
  | `Style/DisallowSafeNavigation` | Flags safe navigation (`&.`). 4Shark rejects it — a `&.` chain silently swallows a `nil` that usually signals a real bug. Use an explicit conditional so the `nil` case is handled on purpose. |
82
+ | `Style/DisallowTernary` | Flags the ternary conditional (`cond ? a : b`). `?` and `:` are punctuation that already mean other things in Ruby (`valid?`, `:symbol`), and the ternary hides a branch inside an expression where skimming misses it. Use an explicit `if`/`else`. |
76
83
  | `Style/DisallowTry` | Flags `try` / `try!`. Same rationale — it hides the `nil`/missing-method case instead of handling it explicitly. |
77
84
 
78
85
  ### Layout
@@ -81,6 +88,12 @@ Where a 4Shark cop supersedes or contradicts a stock cop, `config/default.yml` t
81
88
  |---|---|
82
89
  | `Layout/MultilineStatementSpacing` | Requires a blank line between two consecutive statements when either spans multiple lines, so multi-line statements read as distinct units. |
83
90
 
91
+ ### Naming
92
+
93
+ | Cop | Intent |
94
+ |---|---|
95
+ | `Naming/RescuedExceptionsVariableName` | A rescued exception is bound to `exception` (`_exception` when unread), not the cop's default `e` — 4Shark bans single-letter variables, and this is the one slot where a stock cop demanded one. Stock cop, configured via `PreferredName`; autocorrects. |
96
+
84
97
  ### Rails (models)
85
98
 
86
99
  | Cop | Intent |
@@ -97,7 +110,7 @@ Where a 4Shark cop supersedes or contradicts a stock cop, `config/default.yml` t
97
110
  | `RSpec/Dialect` | Use `let`, never `subject` or `let!` — every `subject`/`subject!`/`let!` is flagged in favor of a lazy `let` (force creation in an explicit `before`, not with `let!`). The implicit subject behind `is_expected` is a different method and is left untouched. Stock cop, configured via `PreferredMethods`. |
98
111
  | `RSpec/InverseOfMatcher` | Root models must assert `.inverse_of` in association specs; subclasses must not (it belongs to the parent). Scoped to `spec/models`. |
99
112
  | `RSpec/OverwrittenLet` | A `let`/`let!` must not override one defined in an outer example group — shadowing makes it ambiguous which value applies. Scenario-specific `let`s, and the same name across sibling contexts, are fine. |
100
- | `RSpec/ConditionalInLet` | A `let` must not contain conditional logic (`if`/`case`) — branch with separate `context`s instead. Ternaries are allowed. |
113
+ | `RSpec/ConditionalInLet` | A `let` must not contain conditional logic (`if`/`case`) — branch with separate `context`s instead. A ternary inside a `let` is flagged by `Style/DisallowTernary`, not by this cop. |
101
114
  | `RSpec/FactoryBotInBefore` | Object creation belongs in `let`, not `before`. `before` is for actions, not for building the subjects under test. |
102
115
 
103
116
  ### FactoryBot (factories)
data/config/default.yml CHANGED
@@ -14,11 +14,40 @@ Layout/MultilineStatementSpacing:
14
14
  Layout/MultilineMethodCallIndentation:
15
15
  EnforcedStyle: indented
16
16
 
17
+ # Stock cop configured (not a 4Shark cop): name the rescued exception variable
18
+ # `exception`. The cop's default is `e`, and a single-letter variable is banned by
19
+ # the 4Shark naming convention — so the two disagree on every rescue in the
20
+ # codebase. `PreferredName` is the option the cop ships to settle exactly that,
21
+ # and the cop autocorrects, so the rename is mechanical.
22
+ Naming/RescuedExceptionsVariableName:
23
+ PreferredName: exception
24
+
25
+ # Stock cop configured (not a 4Shark cop): when a conditional decides a value,
26
+ # each branch assigns the variable. The cop's default (`assign_to_condition`)
27
+ # demands `x = if ... else ... end`, the one form the 4Shark no-ternary
28
+ # convention rejects — converting a ternary would land straight on it.
29
+ # Ternaries are out of scope: `Style/DisallowTernary` bans them outright, and
30
+ # this cop's ternary autocorrect produces another ternary (`cond ? x = a : x = b`),
31
+ # which stays an offense that no autocorrect can clear.
32
+ Style/ConditionalAssignment:
33
+ EnforcedStyle: assign_inside_condition
34
+ IncludeTernaryExpressions: false
35
+
36
+ Style/DisallowDelegate:
37
+ Description: 'Do not use automatic delegation. Write the method explicitly.'
38
+ Enabled: true
39
+ VersionAdded: '0.7.0'
40
+
17
41
  Style/DisallowSafeNavigation:
18
42
  Description: 'Do not use safe navigation (`&.`). Use explicit conditionals instead.'
19
43
  Enabled: true
20
44
  VersionAdded: '0.1.0'
21
45
 
46
+ Style/DisallowTernary:
47
+ Description: 'Do not use the ternary conditional. Use an explicit `if`/`else` instead.'
48
+ Enabled: true
49
+ VersionAdded: '0.7.0'
50
+
22
51
  Style/DisallowTry:
23
52
  Description: 'Do not use `try` or `try!`. Use explicit conditionals instead.'
24
53
  Enabled: true
@@ -105,11 +134,27 @@ FactoryBot/AssociationInFactory:
105
134
  VersionAdded: '0.2.0'
106
135
 
107
136
  # Stock cops disabled because a 4Shark cop supersedes or contradicts them.
137
+ # `Rails/Delegate` flags `def bar; foo.bar; end` and asks for `delegate :bar, to: :foo`,
138
+ # the opposite of `Style/DisallowDelegate`.
108
139
  # `Rails/InverseOf` only flags when AR can't auto-detect the inverse;
109
140
  # `Rails/MandatoryInverseOf` covers it and more.
141
+ # The three ternary-shaping cops are superseded by `Style/DisallowTernary`, which bans
142
+ # every ternary — they could only ever fire on a line that is already an offense.
110
143
  # `Style/SafeNavigation` converts to `&.`, the opposite of `Style/DisallowSafeNavigation`.
144
+ Rails/Delegate:
145
+ Enabled: false
146
+
111
147
  Rails/InverseOf:
112
148
  Enabled: false
113
149
 
150
+ Style/MultilineTernaryOperator:
151
+ Enabled: false
152
+
153
+ Style/NestedTernaryOperator:
154
+ Enabled: false
155
+
114
156
  Style/SafeNavigation:
115
157
  Enabled: false
158
+
159
+ Style/TernaryParentheses:
160
+ Enabled: false
@@ -35,7 +35,11 @@ module RuboCop
35
35
  body = node.body
36
36
  return unless body
37
37
 
38
- statements = body.begin_type? ? body.children : [body]
38
+ if body.begin_type?
39
+ statements = body.children
40
+ else
41
+ statements = [body]
42
+ end
39
43
 
40
44
  statements.each do |statement|
41
45
  add_offense(statement.loc.selector) if association_call?(statement)
@@ -10,5 +10,7 @@ require_relative 'rspec/conditional_in_let'
10
10
  require_relative 'rspec/factory_bot_in_before'
11
11
  require_relative 'rspec/inverse_of_matcher'
12
12
  require_relative 'rspec/overwritten_let'
13
+ require_relative 'style/disallow_delegate'
13
14
  require_relative 'style/disallow_safe_navigation'
15
+ require_relative 'style/disallow_ternary'
14
16
  require_relative 'style/disallow_try'
@@ -47,26 +47,24 @@ module RuboCop
47
47
 
48
48
  opposite_content = File.read(opposite_model_path)
49
49
 
50
- expected_inverse =
51
- if inverse_name
52
- inverse_name.to_s
53
- else
54
- camel_to_snake(model_name).split('/').last.pluralize
55
- end
50
+ if inverse_name
51
+ expected_inverse = inverse_name.to_s
52
+ else
53
+ expected_inverse = camel_to_snake(model_name).split('/').last.pluralize
54
+ end
56
55
 
57
56
  unless /(belongs_to|has_many|has_one)\s+:#{expected_inverse}/.match?(opposite_content)
58
57
  add_global_offense("#{target_class} is missing opposite association for #{model_name}")
59
58
  end
60
59
  end
61
- rescue StandardError => e
62
- source_name =
63
- if processed_source && processed_source.buffer
64
- processed_source.file_path
65
- else
66
- 'unknown'
67
- end
60
+ rescue StandardError => exception
61
+ if processed_source && processed_source.buffer
62
+ source_name = processed_source.file_path
63
+ else
64
+ source_name = 'unknown'
65
+ end
68
66
 
69
- warn "Rails/BidirectionalAssociation failed on #{source_name}: #{e.message}"
67
+ warn "Rails/BidirectionalAssociation failed on #{source_name}: #{exception.message}"
70
68
  end
71
69
 
72
70
  private
@@ -41,15 +41,14 @@ module RuboCop
41
41
  !kwargs || !kwargs.hash_type? || kwargs.keys.none? { |k| k.value == :inverse_of }
42
42
 
43
43
  add_offense(node.loc.selector) if missing_inverse_of
44
- rescue StandardError => e
45
- source_name =
46
- if processed_source && processed_source.buffer
47
- processed_source.file_path
48
- else
49
- 'unknown'
50
- end
44
+ rescue StandardError => exception
45
+ if processed_source && processed_source.buffer
46
+ source_name = processed_source.file_path
47
+ else
48
+ source_name = 'unknown'
49
+ end
51
50
 
52
- warn "Rails/MandatoryInverseOf failed on #{source_name}: #{e.message}"
51
+ warn "Rails/MandatoryInverseOf failed on #{source_name}: #{exception.message}"
53
52
  end
54
53
 
55
54
  private
@@ -39,7 +39,11 @@ module RuboCop
39
39
  body = node.body
40
40
  return unless body
41
41
 
42
- statements = body.begin_type? ? body.children : [body]
42
+ if body.begin_type?
43
+ statements = body.children
44
+ else
45
+ statements = [body]
46
+ end
43
47
 
44
48
  MACROS.each do |macro|
45
49
  calls = statements.select { |statement| macro_call?(statement, macro) }
@@ -7,7 +7,7 @@ module RuboCop
7
7
  module RSpec
8
8
  # Forbids conditional logic (`if`/`case`) inside a `let`/`let!` block.
9
9
  # Branching object setup belongs in separate `context` blocks, not in a
10
- # single `let`. Ternaries are allowed.
10
+ # single `let`. A ternary is flagged by `Style/DisallowTernary`, not here.
11
11
  #
12
12
  # @example
13
13
  # # bad
@@ -46,15 +46,14 @@ module RuboCop
46
46
  elsif classification == :subclass
47
47
  add_offense(node.loc.selector, message: MSG_FORBIDDEN_INVERSE) if chain_has_inverse_of?(node)
48
48
  end
49
- rescue StandardError => e
50
- source_name =
51
- if processed_source && processed_source.buffer
52
- processed_source.file_path
53
- else
54
- 'unknown'
55
- end
56
-
57
- warn "RSpec/InverseOfMatcher failed on #{source_name}: #{e.message}"
49
+ rescue StandardError => exception
50
+ if processed_source && processed_source.buffer
51
+ source_name = processed_source.file_path
52
+ else
53
+ source_name = 'unknown'
54
+ end
55
+
56
+ warn "RSpec/InverseOfMatcher failed on #{source_name}: #{exception.message}"
58
57
  end
59
58
 
60
59
  private
@@ -114,7 +113,9 @@ module RuboCop
114
113
 
115
114
  def model_source(model_name)
116
115
  path = File.join(Dir.pwd, 'app/models', "#{camel_to_snake(model_name)}.rb")
117
- File.exist?(path) ? File.read(path) : nil
116
+ return File.read(path) if File.exist?(path)
117
+
118
+ nil
118
119
  end
119
120
 
120
121
  # The declared superclass of the specific class named by the spec — not the
@@ -65,7 +65,11 @@ module RuboCop
65
65
  body = group.body
66
66
  return false unless body
67
67
 
68
- statements = body.begin_type? ? body.children : [body]
68
+ if body.begin_type?
69
+ statements = body.children
70
+ else
71
+ statements = [body]
72
+ end
69
73
 
70
74
  statements.any? { |statement| let_named?(statement, name) }
71
75
  end
@@ -73,7 +77,12 @@ module RuboCop
73
77
  def let_named?(statement, name)
74
78
  return false unless statement.is_a?(::RuboCop::AST::Node)
75
79
 
76
- send = statement.respond_to?(:send_node) ? statement.send_node : statement
80
+ if statement.respond_to?(:send_node)
81
+ send = statement.send_node
82
+ else
83
+ send = statement
84
+ end
85
+
77
86
  return false unless send.send_type?
78
87
  return false unless LET_METHODS.include?(send.method_name) && send.receiver.nil?
79
88
 
@@ -83,7 +92,9 @@ module RuboCop
83
92
 
84
93
  def let_name(node)
85
94
  first = node.first_argument
86
- first && first.sym_type? ? first.value : nil
95
+ return first.value if first && first.sym_type?
96
+
97
+ nil
87
98
  end
88
99
  end
89
100
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Style
8
+ # Forbids automatic delegation — `delegate`, `delegate_missing_to`,
9
+ # `def_delegator`/`def_delegators` (Forwardable) and `DelegateClass`.
10
+ # A delegation macro is a call, not a definition, so `grep 'def foo'` and
11
+ # jump-to-definition find nothing; the community name for the smell is
12
+ # Fowler's Middle Man. Write the method, or delete the wrapper and let the
13
+ # caller ask the object that knows.
14
+ #
15
+ # @example
16
+ # # bad
17
+ # delegate :commission, to: :user_commission
18
+ #
19
+ # # good
20
+ # def commission
21
+ # user_commission.commission
22
+ # end
23
+ #
24
+ class DisallowDelegate < ::RuboCop::Cop::Base
25
+ MSG = 'Do not use automatic delegation. Write the method explicitly instead.'
26
+
27
+ RESTRICT_ON_SEND = %i[delegate delegate_missing_to def_delegator def_delegators DelegateClass].freeze
28
+
29
+ def on_send(node)
30
+ return unless node.receiver.nil?
31
+
32
+ add_offense(node)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Style
8
+ # Forbids the ternary conditional. `?` and `:` are punctuation that already
9
+ # mean other things in Ruby (`valid?`, `:symbol`), so the reader
10
+ # disambiguates characters before seeing the branch — and the ternary hides
11
+ # a branch inside an expression, where skimming misses it. Inside a hash or
12
+ # an argument list, extract a named local and branch on it.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # status = saved ? :applied : :failed
17
+ #
18
+ # # good
19
+ # if saved
20
+ # status = :applied
21
+ # else
22
+ # status = :failed
23
+ # end
24
+ #
25
+ class DisallowTernary < ::RuboCop::Cop::Base
26
+ MSG = 'Do not use the ternary conditional. Use an explicit `if`/`else` instead.'
27
+
28
+ def on_if(node)
29
+ add_offense(node) if node.ternary?
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Fourshark
5
- VERSION = '0.5.1'
5
+ VERSION = '0.7.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-fourshark
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Ribeiro
@@ -147,7 +147,9 @@ files:
147
147
  - lib/rubocop/cop/rspec/factory_bot_in_before.rb
148
148
  - lib/rubocop/cop/rspec/inverse_of_matcher.rb
149
149
  - lib/rubocop/cop/rspec/overwritten_let.rb
150
+ - lib/rubocop/cop/style/disallow_delegate.rb
150
151
  - lib/rubocop/cop/style/disallow_safe_navigation.rb
152
+ - lib/rubocop/cop/style/disallow_ternary.rb
151
153
  - lib/rubocop/cop/style/disallow_try.rb
152
154
  - lib/rubocop/fourshark.rb
153
155
  - lib/rubocop/fourshark/plugin.rb