rubocop-fourshark 0.6.0 → 0.7.1
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 +19 -0
- data/README.md +8 -2
- data/config/default.yml +26 -0
- data/lib/rubocop/cop/factory_bot/association_in_factory.rb +5 -1
- data/lib/rubocop/cop/fourshark_cops.rb +2 -0
- data/lib/rubocop/cop/rails/bidirectional_association.rb +10 -12
- data/lib/rubocop/cop/rails/mandatory_inverse_of.rb +5 -6
- data/lib/rubocop/cop/rails/ordered_macros.rb +5 -1
- data/lib/rubocop/cop/rspec/conditional_in_let.rb +1 -1
- data/lib/rubocop/cop/rspec/inverse_of_matcher.rb +8 -7
- data/lib/rubocop/cop/rspec/overwritten_let.rb +14 -3
- data/lib/rubocop/cop/style/disallow_delegate.rb +37 -0
- data/lib/rubocop/cop/style/disallow_ternary.rb +34 -0
- data/lib/rubocop/fourshark/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 582aea0059be3aad96e3b1017d419db000bb72f5a424684b4f90347de8359bc9
|
|
4
|
+
data.tar.gz: 685cb43ef7bbc16988a954b3821cc7a9a813bb98bf7777432da0960d4808d149
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1469cf2ff8d928ec8af532f2c09b0ca3fd35414c02105820ca519760d54d01ec177c172c79cf710a79e3314cafe4784c8c62f099bceb7b59753c51dc7ed5bb72
|
|
7
|
+
data.tar.gz: cf5dbfa5a4762d24715bb18b73224c92cb7251bced71e7003fa66d48d4d24be38bdcca4eb19e33e2d124e87dad74ec9a2cf53c890c69621dcefb2875aed33a07
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## [0.7.1] - 2026-08-05
|
|
2
|
+
|
|
3
|
+
### Removed
|
|
4
|
+
|
|
5
|
+
- `Style/ConditionalAssignment` configuration — the stock default governs assignment from a conditional again
|
|
6
|
+
|
|
7
|
+
## [0.7.0] - 2026-08-05
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `Style/DisallowDelegate` — automatic delegation is flagged
|
|
12
|
+
- `Style/DisallowTernary` — the ternary conditional is flagged
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- `Rails/Delegate` disabled — it asks for the macro `Style/DisallowDelegate` forbids
|
|
17
|
+
- `Style/MultilineTernaryOperator`, `Style/NestedTernaryOperator` and `Style/TernaryParentheses` disabled — they shape a construct `Style/DisallowTernary` forbids outright
|
|
18
|
+
- `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`
|
|
19
|
+
|
|
1
20
|
## [0.6.0] - 2026-08-05
|
|
2
21
|
|
|
3
22
|
### 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*
|
|
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,20 @@ 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/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
80
|
| `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. |
|
|
81
|
+
| `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
82
|
| `Style/DisallowTry` | Flags `try` / `try!`. Same rationale — it hides the `nil`/missing-method case instead of handling it explicitly. |
|
|
77
83
|
|
|
78
84
|
### Layout
|
|
@@ -103,7 +109,7 @@ Where a 4Shark cop supersedes or contradicts a stock cop, `config/default.yml` t
|
|
|
103
109
|
| `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
110
|
| `RSpec/InverseOfMatcher` | Root models must assert `.inverse_of` in association specs; subclasses must not (it belongs to the parent). Scoped to `spec/models`. |
|
|
105
111
|
| `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.
|
|
112
|
+
| `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
113
|
| `RSpec/FactoryBotInBefore` | Object creation belongs in `let`, not `before`. `before` is for actions, not for building the subjects under test. |
|
|
108
114
|
|
|
109
115
|
### FactoryBot (factories)
|
data/config/default.yml
CHANGED
|
@@ -22,11 +22,21 @@ Layout/MultilineMethodCallIndentation:
|
|
|
22
22
|
Naming/RescuedExceptionsVariableName:
|
|
23
23
|
PreferredName: exception
|
|
24
24
|
|
|
25
|
+
Style/DisallowDelegate:
|
|
26
|
+
Description: 'Do not use automatic delegation. Write the method explicitly.'
|
|
27
|
+
Enabled: true
|
|
28
|
+
VersionAdded: '0.7.0'
|
|
29
|
+
|
|
25
30
|
Style/DisallowSafeNavigation:
|
|
26
31
|
Description: 'Do not use safe navigation (`&.`). Use explicit conditionals instead.'
|
|
27
32
|
Enabled: true
|
|
28
33
|
VersionAdded: '0.1.0'
|
|
29
34
|
|
|
35
|
+
Style/DisallowTernary:
|
|
36
|
+
Description: 'Do not use the ternary conditional. Use an explicit `if`/`else` instead.'
|
|
37
|
+
Enabled: true
|
|
38
|
+
VersionAdded: '0.7.0'
|
|
39
|
+
|
|
30
40
|
Style/DisallowTry:
|
|
31
41
|
Description: 'Do not use `try` or `try!`. Use explicit conditionals instead.'
|
|
32
42
|
Enabled: true
|
|
@@ -113,11 +123,27 @@ FactoryBot/AssociationInFactory:
|
|
|
113
123
|
VersionAdded: '0.2.0'
|
|
114
124
|
|
|
115
125
|
# Stock cops disabled because a 4Shark cop supersedes or contradicts them.
|
|
126
|
+
# `Rails/Delegate` flags `def bar; foo.bar; end` and asks for `delegate :bar, to: :foo`,
|
|
127
|
+
# the opposite of `Style/DisallowDelegate`.
|
|
116
128
|
# `Rails/InverseOf` only flags when AR can't auto-detect the inverse;
|
|
117
129
|
# `Rails/MandatoryInverseOf` covers it and more.
|
|
130
|
+
# The three ternary-shaping cops are superseded by `Style/DisallowTernary`, which bans
|
|
131
|
+
# every ternary — they could only ever fire on a line that is already an offense.
|
|
118
132
|
# `Style/SafeNavigation` converts to `&.`, the opposite of `Style/DisallowSafeNavigation`.
|
|
133
|
+
Rails/Delegate:
|
|
134
|
+
Enabled: false
|
|
135
|
+
|
|
119
136
|
Rails/InverseOf:
|
|
120
137
|
Enabled: false
|
|
121
138
|
|
|
139
|
+
Style/MultilineTernaryOperator:
|
|
140
|
+
Enabled: false
|
|
141
|
+
|
|
142
|
+
Style/NestedTernaryOperator:
|
|
143
|
+
Enabled: false
|
|
144
|
+
|
|
122
145
|
Style/SafeNavigation:
|
|
123
146
|
Enabled: false
|
|
147
|
+
|
|
148
|
+
Style/TernaryParentheses:
|
|
149
|
+
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?
|
|
38
|
+
statements = if body.begin_type?
|
|
39
|
+
body.children
|
|
40
|
+
else
|
|
41
|
+
[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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
end
|
|
50
|
+
expected_inverse = if inverse_name
|
|
51
|
+
inverse_name.to_s
|
|
52
|
+
else
|
|
53
|
+
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
end
|
|
61
|
+
source_name = if processed_source && processed_source.buffer
|
|
62
|
+
processed_source.file_path
|
|
63
|
+
else
|
|
64
|
+
'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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
end
|
|
45
|
+
source_name = if processed_source && processed_source.buffer
|
|
46
|
+
processed_source.file_path
|
|
47
|
+
else
|
|
48
|
+
'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?
|
|
42
|
+
statements = if body.begin_type?
|
|
43
|
+
body.children
|
|
44
|
+
else
|
|
45
|
+
[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`.
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
end
|
|
50
|
+
source_name = if processed_source && processed_source.buffer
|
|
51
|
+
processed_source.file_path
|
|
52
|
+
else
|
|
53
|
+
'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.
|
|
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?
|
|
68
|
+
statements = if body.begin_type?
|
|
69
|
+
body.children
|
|
70
|
+
else
|
|
71
|
+
[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)
|
|
80
|
+
send = if statement.respond_to?(:send_node)
|
|
81
|
+
statement.send_node
|
|
82
|
+
else
|
|
83
|
+
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?
|
|
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
|
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.
|
|
4
|
+
version: 0.7.1
|
|
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
|