rubocop-fourshark 0.3.0 → 0.5.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: a8b84b9e99c1ff660e70c0dc9b6d600361f175fc7ea47be4c27ceb3ed46c879c
4
- data.tar.gz: b80b447aff3ef40874166bef818eba3d18caff427b39e095c0ad15099804fc88
3
+ metadata.gz: 8f3bb315d43f2566a53923bb26dd2c22024f52f70d85e5cf6e3de77707cd3267
4
+ data.tar.gz: b412c10e92d9d9c68b9d291379b87deea8bf06d60e448be493c3800308908604
5
5
  SHA512:
6
- metadata.gz: abdb30af6129f55a4c37b4f788dbee1528e28129642bcdaaaddc6164d4187d6fb4b0d8177b27b0b523c5749482951af32ebcbd54963651d8c4eb16bb73b4ab19
7
- data.tar.gz: eb6678c1b97f140ef14c222e047b3dbf9c6342281b1a01818f176f9000246de2c95c7d919f1fda3e6cb83507367b88697d049effac922cfd5a3c7f521de323b7
6
+ metadata.gz: 2a779bab95e9568e42c16a69982cb0cb6988f7972297a94953c0865c2491741b07c612fbfa16376a4a92b42be3600929efc6156832b49ccf00c48e5baf853f6b
7
+ data.tar.gz: c5ff6b67765e40271346cb8cc2cbc6675b49931704289e5a3338670730eb8052bc1c336f3b20c71c30baf2d005528f90bfd2c6e3dafafa4638a11ce26da66c1d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [0.5.0] - 2026-06-29
2
+
3
+ ### Changed
4
+
5
+ - Require `rubocop` `>= 1.87` — earlier versions drop the host project's `AllCops/Exclude` when this plugin's config is merged
6
+
7
+ ### Fixed
8
+
9
+ - `Rails/BidirectionalAssociation` no longer crashes on associations with non-literal options (e.g. `inverse_of: nil`); associations that explicitly opt out of an inverse are skipped
10
+
11
+ ## [0.4.0] - 2026-06-29
12
+
13
+ ### Added
14
+
15
+ - `Layout/MultilineMethodCallIndentation` configured to `indented` — multiline method-call chains indent continuation calls one step from the receiver instead of aligning with the first call
16
+
1
17
  ## [0.3.0] - 2026-06-19
2
18
 
3
19
  ### Added
data/README.md CHANGED
@@ -36,6 +36,18 @@ Activating the plugin auto-loads the gem's `config/default.yml`, which enables e
36
36
 
37
37
  > **Note — plugins are not transitively activated.** `rubocop-fourshark` depends on the upstream plugins (`rubocop-rails`, `rubocop-rspec`, `rubocop-rspec_rails`, `rubocop-performance`, `rubocop-factory_bot`), but `lint_roller` does not auto-activate a plugin's dependencies. Each repo still lists the upstream plugins it uses in its own `plugins:` block.
38
38
 
39
+ > **Note — list `rubocop-fourshark` last.** When this gem configures a cop that an upstream plugin also ships (e.g. `RSpec/Dialect`, owned by `rubocop-rspec`), RuboCop merges the plugins' default config in `plugins:` order and the **last** one wins. List `rubocop-fourshark` **after** the upstream plugins so its configuration is the one that takes effect:
40
+ >
41
+ > ```yaml
42
+ > plugins:
43
+ > - rubocop-rspec
44
+ > - rubocop-rspec_rails
45
+ > - rubocop-rails
46
+ > - rubocop-performance
47
+ > - rubocop-factory_bot
48
+ > - rubocop-fourshark
49
+ > ```
50
+
39
51
  ## Cops
40
52
 
41
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.
data/config/default.yml CHANGED
@@ -8,6 +8,12 @@ Layout/MultilineStatementSpacing:
8
8
  Enabled: true
9
9
  VersionAdded: '0.2.0'
10
10
 
11
+ # Stock cop configured (not a 4Shark cop): enforce the `indented` style for
12
+ # multiline method-call chains — continuation calls are indented one step from
13
+ # the receiver instead of aligning with the first call.
14
+ Layout/MultilineMethodCallIndentation:
15
+ EnforcedStyle: indented
16
+
11
17
  Style/DisallowSafeNavigation:
12
18
  Description: 'Do not use safe navigation (`&.`). Use explicit conditionals instead.'
13
19
  Enabled: true
@@ -75,18 +75,12 @@ module RuboCop
75
75
  processed_source.ast.each_descendant(:send).filter_map do |node|
76
76
  next unless %i[belongs_to has_many has_one].include?(node.method_name)
77
77
 
78
- # Skip polymorphic associations and `as:` options
78
+ # Skip polymorphic associations, `as:` options, and explicit `inverse_of: nil/false`
79
79
  next if polymorphic_or_as?(node)
80
+ next if inverse_disabled?(node)
80
81
 
81
82
  model_name = class_name_from_ast
82
-
83
- assoc_name =
84
- begin
85
- node.first_argument.value if node.first_argument
86
- rescue StandardError
87
- nil
88
- end
89
-
83
+ assoc_name = literal_value(node.first_argument)
90
84
  inverse_name = extract_inverse_of(node)
91
85
  target_class = extract_class_name(node, assoc_name)
92
86
 
@@ -95,45 +89,40 @@ module RuboCop
95
89
  end
96
90
 
97
91
  def extract_inverse_of(node)
98
- kwargs = node.last_argument
99
- return nil unless kwargs && kwargs.hash_type?
100
-
101
- pair =
102
- begin
103
- kwargs.pairs.find { |p| p.key.value == :inverse_of }
104
- rescue StandardError
105
- nil
106
- end
107
-
108
- pair.value.value if pair && pair.value
92
+ literal_value(option_value(node, :inverse_of))
109
93
  end
110
94
 
111
95
  def extract_class_name(node, assoc_name)
112
- kwargs = node.last_argument
113
- return assoc_name.to_s.camelize unless kwargs && kwargs.hash_type?
114
-
115
- class_name_pair =
116
- begin
117
- kwargs.pairs.find { |p| p.key.value == :class_name }
118
- rescue StandardError
119
- nil
120
- end
96
+ literal_value(option_value(node, :class_name)) || assoc_name.to_s.camelize
97
+ end
121
98
 
122
- if class_name_pair
123
- class_name_pair.value.value
124
- else
125
- assoc_name.to_s.camelize
126
- end
99
+ def inverse_disabled?(node)
100
+ value = option_value(node, :inverse_of)
101
+ value.falsey_literal? if value
127
102
  end
128
103
 
129
104
  def polymorphic_or_as?(node)
105
+ %i[polymorphic as].any? { |key| option_pair(node, key) }
106
+ end
107
+
108
+ # The keyword options hash is the last argument; pairs whose key is not a
109
+ # plain symbol (string keys, double-splats) are skipped rather than read.
110
+ def option_pair(node, key)
130
111
  kwargs = node.last_argument
131
- return false unless kwargs && kwargs.hash_type?
112
+ return nil unless kwargs.respond_to?(:hash_type?) && kwargs.hash_type?
132
113
 
133
- kwargs.pairs.any? do |pair|
134
- key = pair.key.value
135
- %i[polymorphic as].include?(key)
136
- end
114
+ kwargs.pairs.find { |pair| pair.key.sym_type? && pair.key.value == key }
115
+ end
116
+
117
+ def option_value(node, key)
118
+ pair = option_pair(node, key)
119
+ pair.value if pair
120
+ end
121
+
122
+ # A node's literal value, only when it actually carries one — guards
123
+ # against non-literal options like `inverse_of: nil` or `class_name: Foo`.
124
+ def literal_value(node)
125
+ node.value if node.respond_to?(:value)
137
126
  end
138
127
 
139
128
  def class_name_from_ast
@@ -159,9 +148,9 @@ module RuboCop
159
148
  return '' if name.nil?
160
149
 
161
150
  name.gsub('::', '/')
162
- .gsub(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
163
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
164
- .downcase
151
+ .gsub(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
152
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
153
+ .downcase
165
154
  end
166
155
  end
167
156
  end
@@ -129,9 +129,9 @@ module RuboCop
129
129
  # "UserAccount" → "user_account"; "Plan::Statement" → "plan/statement"
130
130
  def camel_to_snake(name)
131
131
  name.gsub('::', '/')
132
- .gsub(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
133
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
134
- .downcase
132
+ .gsub(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
133
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
134
+ .downcase
135
135
  end
136
136
  end
137
137
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Fourshark
5
- VERSION = '0.3.0'
5
+ VERSION = '0.5.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.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Ribeiro
@@ -29,14 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 1.72.2
32
+ version: '1.87'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 1.72.2
39
+ version: '1.87'
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: rubocop-factory_bot
42
42
  requirement: !ruby/object:Gem::Requirement