Skip to content

Commit e89ab8e

Browse files
authored
Merge pull request #1626 from koic/fix_false_positives_for_rails_strong_parameters_expect_with_collection_methods
[Fix #1625] Fix false positives in `Rails/StrongParametersExpect`
2 parents d56c5d7 + e977fb9 commit e89ab8e

3 files changed

Lines changed: 114 additions & 9 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#1625](https://github.com/rubocop/rubocop-rails/issues/1625): Fix false positives in `Rails/StrongParametersExpect` when using collection methods (such as `delete`, `keys`, `merge`, `slice`, `dig`, `fetch`, or `transform_values`) on `params[:key]`, as well as block-style calls such as `params[:key].each { ... }` or `params[:key].map(&:to_s)`. ([@koic][])

lib/rubocop/cop/rails/strong_parameters_expect.rb

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ module Rails
99
# and the cop detects it using the `expect` method.
1010
#
1111
# - Method calls on `params[:key]` without comparison methods, methods that are safe to call
12-
# on `nil` (such as `to_i`, `to_s`, or `is_a?`), or key-check methods such as `key?`
12+
# on `nil` (such as `to_i`, `to_s`, or `is_a?`), key-check methods such as `key?`,
13+
# collection methods such as `keys`, `merge`, or `slice`, or block-style calls such as
14+
# `params[:key].each { ... }` or `params[:key].map(&:to_s)`
1315
# - Passing `params[:key]` as an argument to finder methods that raise on missing records
1416
# - Strong parameter methods using `require` or `permit`
1517
#
@@ -54,9 +56,18 @@ class StrongParametersExpect < Base
5456

5557
MSG = 'Use `%<prefer>s` instead.'
5658
RESTRICT_ON_SEND = %i[[] require permit].freeze
57-
PRESENCE_CHECK_METHODS = %i[nil? blank? present? presence].freeze
58-
NIL_SAFE_METHODS = %i[instance_of? is_a? kind_of? to_a to_f to_h to_i to_s try try!].freeze
59-
KEY_CHECK_METHODS = %i[key? has_key? include? member?].freeze
59+
# Method calls on `params[:key]` that should not be rewritten with `expect(:key)`.
60+
# Covers presence/nil checks, nil-safe conversions and type checks, key-check methods,
61+
# and collection methods that imply `params[:key]` is a Hash/Array.
62+
IGNORED_METHODS = %i[
63+
blank? compact compact! compact_blank compact_blank! deep_merge deep_merge!
64+
delete delete_if dig each except exclude? extract! fetch has_key? has_value?
65+
include? instance_of? is_a? keep_if key? keys kind_of? member? merge merge!
66+
nil? presence present? reverse_merge reverse_merge! slice stringify_keys
67+
to_a to_f to_h to_hash to_i to_s to_unsafe_h to_unsafe_hash
68+
transform_keys transform_keys! transform_values transform_values! try try!
69+
value? values values_at with_defaults with_defaults! without
70+
].freeze
6071
RAISING_FINDER_METHODS = %i[find find_by! find_sole_by].freeze
6172

6273
minimum_target_rails_version 8.0
@@ -135,12 +146,9 @@ def offensive_bracket_access?(node)
135146

136147
if parent.receiver == node
137148
return false if parent.comparison_method? || parent.method?(:[])
149+
return false if block_call?(parent)
138150

139-
method_name = parent.method_name
140-
141-
!PRESENCE_CHECK_METHODS.include?(method_name) &&
142-
!NIL_SAFE_METHODS.include?(method_name) &&
143-
!KEY_CHECK_METHODS.include?(method_name)
151+
!IGNORED_METHODS.include?(parent.method_name)
144152
else
145153
raising_finder_method?(parent)
146154
end
@@ -151,6 +159,10 @@ def raising_finder_method?(node)
151159
RAISING_FINDER_METHODS.include?(node.method_name)
152160
end
153161

162+
def block_call?(send_node)
163+
send_node.block_literal? || send_node.last_argument&.block_pass_type?
164+
end
165+
154166
def offense_range(method_node, node)
155167
method_node.loc.selector.join(node.source_range.end)
156168
end

spec/rubocop/cop/rails/strong_parameters_expect_spec.rb

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,98 @@
109109
RUBY
110110
end
111111

112+
it 'does not register an offense when using `params[:key].delete(:inner)`' do
113+
expect_no_offenses(<<~RUBY)
114+
params[:key].delete(:inner)
115+
RUBY
116+
end
117+
118+
it 'does not register an offense when using `params[:key].each do |_index, subparams|`' do
119+
expect_no_offenses(<<~RUBY)
120+
params[:key].each do |_index, subparams|
121+
do_something(subparams)
122+
end
123+
RUBY
124+
end
125+
126+
it 'does not register an offense when using `params[:key].map { ... }`' do
127+
expect_no_offenses(<<~RUBY)
128+
params[:key].map { |item| item[:id] }
129+
RUBY
130+
end
131+
132+
it 'does not register an offense when using `params[:key].select(&:present?)`' do
133+
expect_no_offenses(<<~RUBY)
134+
params[:key].select(&:present?)
135+
RUBY
136+
end
137+
138+
it 'does not register an offense when using `params[:key].reject { |k, v| v.blank? }`' do
139+
expect_no_offenses(<<~RUBY)
140+
params[:key].reject { |_k, v| v.blank? }
141+
RUBY
142+
end
143+
144+
it 'does not register an offense when using `params[:key].keys`' do
145+
expect_no_offenses(<<~RUBY)
146+
params[:key].keys
147+
RUBY
148+
end
149+
150+
it 'does not register an offense when using `params[:key].values`' do
151+
expect_no_offenses(<<~RUBY)
152+
params[:key].values
153+
RUBY
154+
end
155+
156+
it 'does not register an offense when using `params[:key].slice(:a, :b)`' do
157+
expect_no_offenses(<<~RUBY)
158+
params[:key].slice(:a, :b)
159+
RUBY
160+
end
161+
162+
it 'does not register an offense when using `params[:key].except(:a)`' do
163+
expect_no_offenses(<<~RUBY)
164+
params[:key].except(:a)
165+
RUBY
166+
end
167+
168+
it 'does not register an offense when using `params[:key].merge(other)`' do
169+
expect_no_offenses(<<~RUBY)
170+
params[:key].merge(other)
171+
RUBY
172+
end
173+
174+
it 'does not register an offense when using `params[:key].dig(:a, :b)`' do
175+
expect_no_offenses(<<~RUBY)
176+
params[:key].dig(:a, :b)
177+
RUBY
178+
end
179+
180+
it 'does not register an offense when using `params[:key].fetch(:inner)`' do
181+
expect_no_offenses(<<~RUBY)
182+
params[:key].fetch(:inner)
183+
RUBY
184+
end
185+
186+
it 'does not register an offense when using `params[:key].compact`' do
187+
expect_no_offenses(<<~RUBY)
188+
params[:key].compact
189+
RUBY
190+
end
191+
192+
it 'does not register an offense when using `params[:key].transform_values(&:to_s)`' do
193+
expect_no_offenses(<<~RUBY)
194+
params[:key].transform_values(&:to_s)
195+
RUBY
196+
end
197+
198+
it 'does not register an offense when using `params[:key].to_unsafe_h`' do
199+
expect_no_offenses(<<~RUBY)
200+
params[:key].to_unsafe_h
201+
RUBY
202+
end
203+
112204
it "does not register an offense when using `params[:key] == 'value'`" do
113205
expect_no_offenses(<<~RUBY)
114206
params[:key] == 'value'

0 commit comments

Comments
 (0)