rubocop-fourshark 0.8.4 → 0.8.6

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: 4cdbe5b265ec54b100ac589171adad9038c99fee37dac3008cb56bba16e1a002
4
- data.tar.gz: 8dfc81f74e7af24ad5518bf67ab735e9d75557cf2db03d4401ee46ba1c471f61
3
+ metadata.gz: 1bdc31121f34ef40cca25af1bc804caafecc8b8f58fd2996457cd01d82b98502
4
+ data.tar.gz: 26bbc30201fe22eef3477a20ce1eaf7016ee020a1ed93f53134429654da4a11d
5
5
  SHA512:
6
- metadata.gz: 684f7b5b0f1064064eb545240a7f2f290207c9d0e7f759363e26d2607b2fe0d48fda173e8e48827769fa625f3eefb5ed6591ffdbdc770bf29f63bebbe156c50f
7
- data.tar.gz: 19a91ad805511e8a3f19d903d18eeb007c5a2c1d4e2329733336e12d6b041cad998466f0fe080556830fbf8a90be6736f568b33eaf1d803b3e2e4c94b0dd8555
6
+ metadata.gz: ce23cde85c85d07932802b8bc89aa7c48f047cd274f59ff7d8c32b5e5e490e4bc46f118bb0e6216796cc774f9dc7c687bc464d33ac59361885a8bab2dd865edb
7
+ data.tar.gz: 616ed09b7f636b5cc4931894d86063a62b9e7357cf35802dee67f6d4a2c77ab8523131519ced29806cb6e1bd862ce8771931401411b3b1e0d59ebe6fabc5b575
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [0.8.6] - 2026-08-08
2
+
3
+ ### Fixed
4
+
5
+ - `Style/DisallowDelegate` — a chained receiver carrying an own state argument is no longer flagged
6
+
7
+ ## [0.8.5] - 2026-08-07
8
+
9
+ ### Changed
10
+
11
+ - Convention rationale in the README
12
+
13
+ ### Fixed
14
+
15
+ - `Style/DisallowDelegate` — a macro aimed at the object's own class is no longer flagged
16
+
1
17
  ## [0.8.4] - 2026-08-07
2
18
 
3
19
  ### Fixed
data/README.md CHANGED
@@ -118,20 +118,18 @@ Where a 4Shark cop supersedes or contradicts a stock cop, `config/default.yml` t
118
118
  |---|---|
119
119
  | `FactoryBot/AssociationInFactory` | No associations declared inside a factory — they trigger cascading object creation and callbacks. Set the association manually in the spec. Scoped to `spec/factories`. |
120
120
 
121
- The full rationale behind each convention (the "why this is good 4Shark code") lives in the team's engineering docs; this README states the intent each cop enforces. One convention deviates from a safe Rails default and is worth spelling out — see below.
121
+ This README states the intent each cop enforces. One convention deviates from a safe Rails default and is worth spelling out — see below.
122
122
 
123
123
  ### Why `belongs_to` is `optional: true` by default
124
124
 
125
- 4Shark never exposes internal database IDs across its API and upload boundaries. Clients send their **own** identifiers; each external identifier is mapped to its internal record (a surrogate-key cross-reference), and that lookup is scoped to the client's account so it confirms both that the record **exists** and that it **belongs to the caller**, in a single step.
126
-
127
- By the time an association is assigned, existence and ownership have already been verified — more strictly than Rails would. Rails' default `belongs_to` (`optional: false`) then adds an existence `SELECT` per record on top of that: redundant work, and at high API throughput a measurable per-request cost.
125
+ Rails' default `belongs_to` (`optional: false`) validates that the associated record exists, which costs an existence `SELECT` per record. That validation is redundant in an application whose own request handling already establishes, before the association is assigned, that the record exists and is reachable by the caller and under high throughput the redundant query is a measurable per-request cost.
128
126
 
129
127
  So the convention is:
130
128
 
131
129
  - `belongs_to` is always declared `optional: true` (enforced by `Rails/OptionalBelongsTo`), turning off Rails' automatic existence validation.
132
130
  - Presence is validated manually with `validates :x_id, presence: true` **where the business rule requires it** — case by case, not globally (which is why no cop enforces the presence side).
133
131
 
134
- A deliberate performance trade-off backed by the external-identifier mapping — not an omission.
132
+ A deliberate performance trade-off — not an omission.
135
133
 
136
134
  ## Development
137
135
 
@@ -147,7 +145,7 @@ Each new cop ships with a spec (`expect_offense` / `expect_no_offenses`) and a `
147
145
 
148
146
  ## Releasing
149
147
 
150
- This project does **not** use HubFlow. Releases are cut from `main`: feature branches merge into `main` via PR, the version is bumped, a `vX.Y.Z` tag is created from `main`, and the gem is published to RubyGems. Consuming repos depend on the published version in their `Gemfile`.
148
+ Releases are cut from `main`: feature branches merge into `main` via PR, the version is bumped, a `vX.Y.Z` tag is created from `main`, and the gem is published to RubyGems.
151
149
 
152
150
  ## License
153
151
 
data/config/default.yml CHANGED
@@ -16,8 +16,8 @@ Layout/MultilineMethodCallIndentation:
16
16
 
17
17
  # Stock cop configured (not a 4Shark cop): name the rescued exception variable
18
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,
19
+ # the 4Shark naming convention — so the two disagree wherever a rescue binds its
20
+ # exception. `PreferredName` is the option the cop ships to settle exactly that,
21
21
  # and the cop autocorrects, so the rename is mechanical.
22
22
  Naming/RescuedExceptionsVariableName:
23
23
  PreferredName: exception
@@ -22,6 +22,13 @@ module RuboCop
22
22
  # method answers for a domain it owns, so it is not delegation and is not
23
23
  # flagged.
24
24
  #
25
+ # A macro aimed at `:class` reaches the same place and is exempt for the
26
+ # same reason. Remove Middle Man has nothing to prescribe here: there is no
27
+ # third object for the caller to navigate to, so the only alternatives are
28
+ # the macro and a hand-written body that says exactly what the macro says.
29
+ # Between those two the macro is the better spelling, and flagging it while
30
+ # exempting the body would push every author toward the longer one.
31
+ #
25
32
  # `each` in a class that includes or prepends `Enumerable` is the module's
26
33
  # required contract, not delegation. The class has no interface without it
27
34
  # — every method `Enumerable` provides is built on `each` — so the class is
@@ -79,6 +86,10 @@ module RuboCop
79
86
  # self.class.lock_key(owner_id: owner_id)
80
87
  # end
81
88
  #
89
+ # # good — aimed at the object's own class, where there is no third
90
+ # # object to navigate to
91
+ # delegate :model, to: :class
92
+ #
82
93
  # # good — `each` is the Enumerable contract this class implements
83
94
  # class SearchResult
84
95
  # include Enumerable
@@ -124,8 +135,23 @@ module RuboCop
124
135
  (send nil? {:include :prepend} (const {nil? cbase} :Enumerable))
125
136
  PATTERN
126
137
 
138
+ # `delegate` names its target in a `to:` option, wherever that option
139
+ # sits among the forwarded method names.
140
+ # @!method to_option(node)
141
+ def_node_matcher :to_option, <<~PATTERN
142
+ (send nil? :delegate ... (hash <(pair (sym :to) $_) ...>))
143
+ PATTERN
144
+
145
+ # The Forwardable macros and `delegate_missing_to` take their target as
146
+ # the first argument. `DelegateClass` names none and never matches.
147
+ # @!method leading_target(node)
148
+ def_node_matcher :leading_target, <<~PATTERN
149
+ (send nil? {:delegate_missing_to :def_delegator :def_delegators} $_ ...)
150
+ PATTERN
151
+
127
152
  def on_send(node)
128
153
  return unless node.receiver.nil?
154
+ return if own_class_target?(node)
129
155
 
130
156
  add_offense(node, message: MACRO_MSG)
131
157
  end
@@ -140,6 +166,16 @@ module RuboCop
140
166
 
141
167
  private
142
168
 
169
+ # The target is written as a bare name, so a symbol and a string say the
170
+ # same thing and both are read as one.
171
+ def own_class_target?(node)
172
+ target = to_option(node) || leading_target(node)
173
+ return false if target.nil?
174
+ return false unless target.type?(:sym, :str)
175
+
176
+ target.value.to_sym == :class
177
+ end
178
+
143
179
  def forwards_own_message?(body, method_name)
144
180
  return false if body.nil?
145
181
  return false unless body.send_type?
@@ -176,11 +212,14 @@ module RuboCop
176
212
  mixes_in_enumerable?(body)
177
213
  end
178
214
 
179
- # Remove Middle Man on a message chain IS the caller navigating, so an
180
- # argument riding along does not earn the exemption.
215
+ # A body that passes the object's own state is composing, not forwarding:
216
+ # it collapses `owner.collaborator.message(owner.state)` into
217
+ # `owner.message`, so the method hides the passing of a parameter the
218
+ # object itself holds. How far the collaborator sits is not part of that
219
+ # judgement — depth changes the path to the collaborator, never who the
220
+ # answer is about. A body that passes nothing, or passes a parameter the
221
+ # caller handed in, composes nothing and stays an offense at any depth.
181
222
  def composes_own_state?(body)
182
- return false if call_with_receiver?(body.receiver)
183
-
184
223
  body.arguments.any? { |argument| own_state?(argument, body.receiver) }
185
224
  end
186
225
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Fourshark
5
- VERSION = '0.8.4'
5
+ VERSION = '0.8.6'
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.8.4
4
+ version: 0.8.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Ribeiro