rubocop-fourshark 0.6.0 → 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: c4b2d2e8d312ae14c7f582304f4b698783d89dcbaaabb97edbdbf8eb8637d782
4
- data.tar.gz: 2c21cc3d147f2210f38dc5d694b72a3955da359518a037a9270d60c67bf352d3
3
+ metadata.gz: 49c5ac14dc35386b05d887fcd8b53f2ebafd991014046fef3df088a8c4174816
4
+ data.tar.gz: 953643562da8fcdda58cd4482246328baddff15ac4ee881468defaaf669ce218
5
5
  SHA512:
6
- metadata.gz: c314121bb42e4b79fe8e2ab9f6658c47f44042ef9cc0038bdd62a905ac8bbe986ff26f1ee67521e6d73ea2585ddaf0fd3d393cb2b54d853f16e115985cc769a6
7
- data.tar.gz: 861c512174a3a30f7ecbe223467f41899ea8a418f631eb3e44c51c35715538a667be755e5e4bd7f87d7370827347fd75958cfb8cac2296c998ba2e4c1a82c058
6
+ metadata.gz: b0bc1b6c88559d9b780d05bfb1f052a67b99f5eebc0be6db93e6aa934e9c62cf34ecc594c9549887dee3089874c7c733320781e5025dd664aa0642ae8a211cc4
7
+ data.tar.gz: 91995ae5a6cabfccdb2374540e27d6dc3f481cbbc4ac54fc888359a237186400dda1e605de34e5169fb304bf3ec2b22ad78a3e80af83ba7ce1823eec69716cbc
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
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
+
1
14
  ## [0.6.0] - 2026-08-05
2
15
 
3
16
  ### Added
data/README.md CHANGED
@@ -56,7 +56,7 @@ All cops are **enabled by default** the moment the plugin is activated. Cops sco
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
@@ -103,7 +110,7 @@ Where a 4Shark cop supersedes or contradicts a stock cop, `config/default.yml` t
103
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`. |
104
111
  | `RSpec/InverseOfMatcher` | Root models must assert `.inverse_of` in association specs; subclasses must not (it belongs to the parent). Scoped to `spec/models`. |
105
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. |
106
- | `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. |
107
114
  | `RSpec/FactoryBotInBefore` | Object creation belongs in `let`, not `before`. `before` is for actions, not for building the subjects under test. |
108
115
 
109
116
  ### FactoryBot (factories)
data/config/default.yml CHANGED
@@ -22,11 +22,32 @@ Layout/MultilineMethodCallIndentation:
22
22
  Naming/RescuedExceptionsVariableName:
23
23
  PreferredName: exception
24
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
+
25
41
  Style/DisallowSafeNavigation:
26
42
  Description: 'Do not use safe navigation (`&.`). Use explicit conditionals instead.'
27
43
  Enabled: true
28
44
  VersionAdded: '0.1.0'
29
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
+
30
51
  Style/DisallowTry:
31
52
  Description: 'Do not use `try` or `try!`. Use explicit conditionals instead.'
32
53
  Enabled: true
@@ -113,11 +134,27 @@ FactoryBot/AssociationInFactory:
113
134
  VersionAdded: '0.2.0'
114
135
 
115
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`.
116
139
  # `Rails/InverseOf` only flags when AR can't auto-detect the inverse;
117
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.
118
143
  # `Style/SafeNavigation` converts to `&.`, the opposite of `Style/DisallowSafeNavigation`.
144
+ Rails/Delegate:
145
+ Enabled: false
146
+
119
147
  Rails/InverseOf:
120
148
  Enabled: false
121
149
 
150
+ Style/MultilineTernaryOperator:
151
+ Enabled: false
152
+
153
+ Style/NestedTernaryOperator:
154
+ Enabled: false
155
+
122
156
  Style/SafeNavigation:
123
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,24 +47,22 @@ 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
60
  rescue StandardError => exception
62
- source_name =
63
- if processed_source && processed_source.buffer
64
- processed_source.file_path
65
- else
66
- 'unknown'
67
- end
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
67
  warn "Rails/BidirectionalAssociation failed on #{source_name}: #{exception.message}"
70
68
  end
@@ -42,12 +42,11 @@ module RuboCop
42
42
 
43
43
  add_offense(node.loc.selector) if missing_inverse_of
44
44
  rescue StandardError => exception
45
- source_name =
46
- if processed_source && processed_source.buffer
47
- processed_source.file_path
48
- else
49
- 'unknown'
50
- end
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
51
  warn "Rails/MandatoryInverseOf failed on #{source_name}: #{exception.message}"
53
52
  end
@@ -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
@@ -47,12 +47,11 @@ module RuboCop
47
47
  add_offense(node.loc.selector, message: MSG_FORBIDDEN_INVERSE) if chain_has_inverse_of?(node)
48
48
  end
49
49
  rescue StandardError => exception
50
- source_name =
51
- if processed_source && processed_source.buffer
52
- processed_source.file_path
53
- else
54
- 'unknown'
55
- end
50
+ if processed_source && processed_source.buffer
51
+ source_name = processed_source.file_path
52
+ else
53
+ source_name = 'unknown'
54
+ end
56
55
 
57
56
  warn "RSpec/InverseOfMatcher failed on #{source_name}: #{exception.message}"
58
57
  end
@@ -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.6.0'
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.6.0
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