rubocop-rails 2.26.0 → 2.29.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/LICENSE.txt +1 -1
- data/README.md +51 -1
- data/config/default.yml +30 -6
- data/lib/rubocop/cop/mixin/index_method.rb +12 -5
- data/lib/rubocop/cop/mixin/routes_helper.rb +20 -0
- data/lib/rubocop/cop/mixin/target_rails_version.rb +3 -5
- data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +5 -5
- data/lib/rubocop/cop/rails/action_order.rb +1 -5
- data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +1 -5
- data/lib/rubocop/cop/rails/add_column_index.rb +1 -0
- data/lib/rubocop/cop/rails/application_record.rb +4 -0
- data/lib/rubocop/cop/rails/bulk_change_table.rb +1 -0
- data/lib/rubocop/cop/rails/compact_blank.rb +8 -4
- data/lib/rubocop/cop/rails/dangerous_column_names.rb +2 -0
- data/lib/rubocop/cop/rails/duplicate_association.rb +8 -4
- data/lib/rubocop/cop/rails/enum_syntax.rb +18 -14
- data/lib/rubocop/cop/rails/env_local.rb +26 -3
- data/lib/rubocop/cop/rails/file_path.rb +26 -3
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +7 -0
- data/lib/rubocop/cop/rails/index_by.rb +28 -12
- data/lib/rubocop/cop/rails/index_with.rb +28 -12
- data/lib/rubocop/cop/rails/match_route.rb +1 -9
- data/lib/rubocop/cop/rails/multiple_route_paths.rb +50 -0
- data/lib/rubocop/cop/rails/not_null_column.rb +6 -2
- data/lib/rubocop/cop/rails/pluck.rb +20 -0
- data/lib/rubocop/cop/rails/pluralization_grammar.rb +1 -1
- data/lib/rubocop/cop/rails/present.rb +0 -2
- data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +1 -30
- data/lib/rubocop/cop/rails/redundant_foreign_key.rb +1 -1
- data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +1 -1
- data/lib/rubocop/cop/rails/reflection_class_name.rb +1 -1
- data/lib/rubocop/cop/rails/request_referer.rb +1 -1
- data/lib/rubocop/cop/rails/reversible_migration.rb +3 -1
- data/lib/rubocop/cop/rails/root_pathname_methods.rb +17 -8
- data/lib/rubocop/cop/rails/save_bang.rb +4 -3
- data/lib/rubocop/cop/rails/schema_comment.rb +1 -0
- data/lib/rubocop/cop/rails/select_map.rb +3 -2
- data/lib/rubocop/cop/rails/skips_model_validations.rb +4 -2
- data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +1 -1
- data/lib/rubocop/cop/rails/strong_parameters_expect.rb +104 -0
- data/lib/rubocop/cop/rails/three_state_boolean_column.rb +2 -1
- data/lib/rubocop/cop/rails/time_zone.rb +13 -6
- data/lib/rubocop/cop/rails/transaction_exit_statement.rb +5 -0
- data/lib/rubocop/cop/rails/where_equals.rb +6 -1
- data/lib/rubocop/cop/rails/where_not.rb +6 -1
- data/lib/rubocop/cop/rails/where_range.rb +7 -2
- data/lib/rubocop/cop/rails_cops.rb +3 -0
- data/lib/rubocop/rails/migration_file_skippable.rb +54 -0
- data/lib/rubocop/rails/version.rb +1 -1
- data/lib/rubocop/rails.rb +0 -1
- data/lib/rubocop-rails.rb +3 -0
- metadata +8 -7
@@ -29,18 +29,28 @@ module RuboCop
|
|
29
29
|
PATTERN
|
30
30
|
|
31
31
|
def_node_matcher :on_bad_to_h, <<~PATTERN
|
32
|
-
|
33
|
-
(
|
34
|
-
|
35
|
-
|
32
|
+
{
|
33
|
+
(block
|
34
|
+
(call _ :to_h)
|
35
|
+
(args (arg $_el))
|
36
|
+
(array $_ (lvar _el)))
|
37
|
+
(numblock
|
38
|
+
(call _ :to_h) $1
|
39
|
+
(array $_ (lvar :_1)))
|
40
|
+
}
|
36
41
|
PATTERN
|
37
42
|
|
38
43
|
def_node_matcher :on_bad_map_to_h, <<~PATTERN
|
39
44
|
(call
|
40
|
-
|
41
|
-
(
|
42
|
-
|
43
|
-
|
45
|
+
{
|
46
|
+
(block
|
47
|
+
(call _ {:map :collect})
|
48
|
+
(args (arg $_el))
|
49
|
+
(array $_ (lvar _el)))
|
50
|
+
(numblock
|
51
|
+
(call _ {:map :collect}) $1
|
52
|
+
(array $_ (lvar :_1)))
|
53
|
+
}
|
44
54
|
:to_h)
|
45
55
|
PATTERN
|
46
56
|
|
@@ -48,10 +58,16 @@ module RuboCop
|
|
48
58
|
(send
|
49
59
|
(const {nil? cbase} :Hash)
|
50
60
|
:[]
|
51
|
-
|
52
|
-
(
|
53
|
-
|
54
|
-
|
61
|
+
{
|
62
|
+
(block
|
63
|
+
(call _ {:map :collect})
|
64
|
+
(args (arg $_el))
|
65
|
+
(array $_ (lvar _el)))
|
66
|
+
(numblock
|
67
|
+
(call _ {:map :collect}) $1
|
68
|
+
(array $_ (lvar :_1)))
|
69
|
+
}
|
70
|
+
)
|
55
71
|
PATTERN
|
56
72
|
|
57
73
|
private
|
@@ -32,18 +32,28 @@ module RuboCop
|
|
32
32
|
PATTERN
|
33
33
|
|
34
34
|
def_node_matcher :on_bad_to_h, <<~PATTERN
|
35
|
-
|
36
|
-
(
|
37
|
-
|
38
|
-
|
35
|
+
{
|
36
|
+
(block
|
37
|
+
(call _ :to_h)
|
38
|
+
(args (arg $_el))
|
39
|
+
(array (lvar _el) $_))
|
40
|
+
(numblock
|
41
|
+
(call _ :to_h) $1
|
42
|
+
(array (lvar :_1) $_))
|
43
|
+
}
|
39
44
|
PATTERN
|
40
45
|
|
41
46
|
def_node_matcher :on_bad_map_to_h, <<~PATTERN
|
42
47
|
(call
|
43
|
-
|
44
|
-
(
|
45
|
-
|
46
|
-
|
48
|
+
{
|
49
|
+
(block
|
50
|
+
(call _ {:map :collect})
|
51
|
+
(args (arg $_el))
|
52
|
+
(array (lvar _el) $_))
|
53
|
+
(numblock
|
54
|
+
(call _ {:map :collect}) $1
|
55
|
+
(array (lvar :_1) $_))
|
56
|
+
}
|
47
57
|
:to_h)
|
48
58
|
PATTERN
|
49
59
|
|
@@ -51,10 +61,16 @@ module RuboCop
|
|
51
61
|
(send
|
52
62
|
(const {nil? cbase} :Hash)
|
53
63
|
:[]
|
54
|
-
|
55
|
-
(
|
56
|
-
|
57
|
-
|
64
|
+
{
|
65
|
+
(block
|
66
|
+
(call _ {:map :collect})
|
67
|
+
(args (arg $_el))
|
68
|
+
(array (lvar _el) $_))
|
69
|
+
(numblock
|
70
|
+
(call _ {:map :collect}) $1
|
71
|
+
(array (lvar :_1) $_))
|
72
|
+
}
|
73
|
+
)
|
58
74
|
PATTERN
|
59
75
|
|
60
76
|
private
|
@@ -21,11 +21,11 @@ module RuboCop
|
|
21
21
|
# match 'photos/:id', to: 'photos#show', via: :all
|
22
22
|
#
|
23
23
|
class MatchRoute < Base
|
24
|
+
include RoutesHelper
|
24
25
|
extend AutoCorrector
|
25
26
|
|
26
27
|
MSG = 'Use `%<http_method>s` instead of `match` to define a route.'
|
27
28
|
RESTRICT_ON_SEND = %i[match].freeze
|
28
|
-
HTTP_METHODS = %i[get post put patch delete].freeze
|
29
29
|
|
30
30
|
def_node_matcher :match_method_call?, <<~PATTERN
|
31
31
|
(send nil? :match $_ $(hash ...) ?)
|
@@ -60,14 +60,6 @@ module RuboCop
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
-
def_node_matcher :routes_draw?, <<~PATTERN
|
64
|
-
(send (send _ :routes) :draw)
|
65
|
-
PATTERN
|
66
|
-
|
67
|
-
def within_routes?(node)
|
68
|
-
node.each_ancestor(:block).any? { |a| routes_draw?(a.send_node) }
|
69
|
-
end
|
70
|
-
|
71
63
|
def extract_via(node)
|
72
64
|
via_pair = via_pair(node)
|
73
65
|
return %i[get] unless via_pair
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# Checks for mapping a route with multiple paths, which is deprecated and will be removed in Rails 8.1.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# # bad
|
11
|
+
# get '/users', '/other_path', to: 'users#index'
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# get '/users', to: 'users#index'
|
15
|
+
# get '/other_path', to: 'users#index'
|
16
|
+
#
|
17
|
+
class MultipleRoutePaths < Base
|
18
|
+
include RoutesHelper
|
19
|
+
extend AutoCorrector
|
20
|
+
|
21
|
+
MSG = 'Use separate routes instead of combining multiple route paths in a single route.'
|
22
|
+
RESTRICT_ON_SEND = HTTP_METHODS
|
23
|
+
|
24
|
+
IGNORED_ARGUMENT_TYPES = %i[array hash].freeze
|
25
|
+
|
26
|
+
def on_send(node)
|
27
|
+
return unless within_routes?(node)
|
28
|
+
|
29
|
+
route_paths = node.arguments.reject { |argument| IGNORED_ARGUMENT_TYPES.include?(argument.type) }
|
30
|
+
return if route_paths.count < 2
|
31
|
+
|
32
|
+
add_offense(node) do |corrector|
|
33
|
+
corrector.replace(node, migrate_to_multiple_routes(node, route_paths))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def migrate_to_multiple_routes(node, route_paths)
|
40
|
+
rest = route_paths.last.source_range.end.join(node.source_range.end).source
|
41
|
+
indentation = ' ' * node.source_range.column
|
42
|
+
|
43
|
+
route_paths.map do |route_path|
|
44
|
+
"#{node.method_name} #{route_path.source}#{rest}"
|
45
|
+
end.join("\n#{indentation}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -41,10 +41,14 @@ module RuboCop
|
|
41
41
|
# change_column_null :products, :category_id, false
|
42
42
|
class NotNullColumn < Base
|
43
43
|
include DatabaseTypeResolvable
|
44
|
+
include MigrationsHelper
|
44
45
|
|
45
46
|
MSG = 'Do not add a NOT NULL column without a default value.'
|
46
47
|
RESTRICT_ON_SEND = %i[add_column add_reference].freeze
|
47
48
|
|
49
|
+
VIRTUAL_TYPE_VALUES = [:virtual, 'virtual'].freeze
|
50
|
+
TEXT_TYPE_VALUES = [:text, 'text'].freeze
|
51
|
+
|
48
52
|
def_node_matcher :add_not_null_column?, <<~PATTERN
|
49
53
|
(send nil? :add_column _ _ $_ (hash $...))
|
50
54
|
PATTERN
|
@@ -91,8 +95,8 @@ module RuboCop
|
|
91
95
|
|
92
96
|
def check_column(type, pairs)
|
93
97
|
if type.respond_to?(:value)
|
94
|
-
return if
|
95
|
-
return if (type.value
|
98
|
+
return if VIRTUAL_TYPE_VALUES.include?(type.value)
|
99
|
+
return if TEXT_TYPE_VALUES.include?(type.value) && database == MYSQL
|
96
100
|
end
|
97
101
|
|
98
102
|
check_pairs(pairs)
|
@@ -9,6 +9,24 @@ module RuboCop
|
|
9
9
|
# element in an enumerable. When called on an Active Record relation, it
|
10
10
|
# results in a more efficient query that only selects the necessary key.
|
11
11
|
#
|
12
|
+
# NOTE: If the receiver's relation is not loaded and `pluck` is used inside an iteration,
|
13
|
+
# it may result in N+1 queries because `pluck` queries the database on each iteration.
|
14
|
+
# This cop ignores offenses for `map/collect` when they are suspected to be part of an iteration
|
15
|
+
# to prevent such potential issues.
|
16
|
+
#
|
17
|
+
# [source,ruby]
|
18
|
+
# ----
|
19
|
+
# users = User.all
|
20
|
+
# 5.times do
|
21
|
+
# users.map { |user| user[:foo] } # Only one query is executed
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# users = User.all
|
25
|
+
# 5.times do
|
26
|
+
# users.pluck(:id) # A query is executed on every iteration
|
27
|
+
# end
|
28
|
+
# ----
|
29
|
+
#
|
12
30
|
# @safety
|
13
31
|
# This cop is unsafe because model can use column aliases.
|
14
32
|
#
|
@@ -42,6 +60,8 @@ module RuboCop
|
|
42
60
|
PATTERN
|
43
61
|
|
44
62
|
def on_block(node)
|
63
|
+
return if node.each_ancestor(:block, :numblock).any?
|
64
|
+
|
45
65
|
pluck_candidate?(node) do |argument, key|
|
46
66
|
next if key.regexp_type? || !use_one_block_argument?(argument)
|
47
67
|
|
@@ -3,35 +3,6 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Rails
|
6
|
-
# TODO: In the future, please support only RuboCop 1.52+ and use `RuboCop::Cop::AllowedReceivers`:
|
7
|
-
# https://github.com/rubocop/rubocop/blob/v1.52.0/lib/rubocop/cop/mixin/allowed_receivers.rb
|
8
|
-
# At that time, this duplicated module implementation can be removed.
|
9
|
-
module AllowedReceivers
|
10
|
-
def allowed_receiver?(receiver)
|
11
|
-
receiver_name = receiver_name(receiver)
|
12
|
-
|
13
|
-
allowed_receivers.include?(receiver_name)
|
14
|
-
end
|
15
|
-
|
16
|
-
def receiver_name(receiver)
|
17
|
-
return receiver_name(receiver.receiver) if receiver.receiver && !receiver.receiver.const_type?
|
18
|
-
|
19
|
-
if receiver.send_type?
|
20
|
-
if receiver.receiver
|
21
|
-
"#{receiver_name(receiver.receiver)}.#{receiver.method_name}"
|
22
|
-
else
|
23
|
-
receiver.method_name.to_s
|
24
|
-
end
|
25
|
-
else
|
26
|
-
receiver.source
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def allowed_receivers
|
31
|
-
cop_config.fetch('AllowedReceivers', [])
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
6
|
# Detect redundant `all` used as a receiver for Active Record query methods.
|
36
7
|
#
|
37
8
|
# For the methods `delete_all` and `destroy_all`, this cop will only check cases where the receiver is a model.
|
@@ -203,7 +174,7 @@ module RuboCop
|
|
203
174
|
parent = node.parent
|
204
175
|
return false unless POSSIBLE_ENUMERABLE_BLOCK_METHODS.include?(parent.method_name)
|
205
176
|
|
206
|
-
parent.
|
177
|
+
parent.block_literal? || parent.first_argument&.block_pass_type?
|
207
178
|
end
|
208
179
|
|
209
180
|
def sensitive_association_method?(node)
|
@@ -40,7 +40,7 @@ module RuboCop
|
|
40
40
|
def on_send(node)
|
41
41
|
association_with_foreign_key(node) do |type, name, options, foreign_key_pair, foreign_key|
|
42
42
|
if redundant?(node, type, name, options, foreign_key)
|
43
|
-
add_offense(foreign_key_pair
|
43
|
+
add_offense(foreign_key_pair) do |corrector|
|
44
44
|
range = range_with_surrounding_space(foreign_key_pair.source_range, side: :left)
|
45
45
|
range = range_with_surrounding_comma(range, :left)
|
46
46
|
|
@@ -43,7 +43,7 @@ module RuboCop
|
|
43
43
|
return if reflection_class_name.value.send_type? && reflection_class_name.value.receiver.nil?
|
44
44
|
return if reflection_class_name.value.lvar_type? && str_assigned?(reflection_class_name)
|
45
45
|
|
46
|
-
add_offense(reflection_class_name
|
46
|
+
add_offense(reflection_class_name) do |corrector|
|
47
47
|
autocorrect(corrector, reflection_class_name)
|
48
48
|
end
|
49
49
|
end
|
@@ -215,8 +215,10 @@ module RuboCop
|
|
215
215
|
end
|
216
216
|
|
217
217
|
def check_drop_table_node(node)
|
218
|
+
return unless (last_argument = node.last_argument)
|
219
|
+
|
218
220
|
drop_table_call(node) do
|
219
|
-
unless node.parent.block_type? ||
|
221
|
+
unless node.parent.block_type? || last_argument.block_pass_type?
|
220
222
|
add_offense(node, message: format(MSG, action: 'drop_table(without block)'))
|
221
223
|
end
|
222
224
|
end
|
@@ -23,6 +23,8 @@ module RuboCop
|
|
23
23
|
# File.binread(Rails.root.join('db', 'schema.rb'))
|
24
24
|
# File.write(Rails.root.join('db', 'schema.rb'), content)
|
25
25
|
# File.binwrite(Rails.root.join('db', 'schema.rb'), content)
|
26
|
+
# Dir.glob(Rails.root.join('db', 'schema.rb'))
|
27
|
+
# Dir[Rails.root.join('db', 'schema.rb')]
|
26
28
|
#
|
27
29
|
# # good
|
28
30
|
# Rails.root.join('db', 'schema.rb').open
|
@@ -31,12 +33,13 @@ module RuboCop
|
|
31
33
|
# Rails.root.join('db', 'schema.rb').binread
|
32
34
|
# Rails.root.join('db', 'schema.rb').write(content)
|
33
35
|
# Rails.root.join('db', 'schema.rb').binwrite(content)
|
36
|
+
# Rails.root.glob("db/schema.rb")
|
34
37
|
#
|
35
38
|
class RootPathnameMethods < Base # rubocop:disable Metrics/ClassLength
|
36
39
|
extend AutoCorrector
|
37
40
|
include RangeHelp
|
38
41
|
|
39
|
-
MSG = '`%<rails_root>s` is a `Pathname
|
42
|
+
MSG = '`%<rails_root>s` is a `Pathname`, so you can use `%<replacement>s`.'
|
40
43
|
|
41
44
|
DIR_GLOB_METHODS = %i[[] glob].to_set.freeze
|
42
45
|
|
@@ -188,13 +191,14 @@ module RuboCop
|
|
188
191
|
|
189
192
|
def on_send(node)
|
190
193
|
evidence(node) do |method, path, args, rails_root|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
end
|
194
|
+
replacement = if dir_glob?(node)
|
195
|
+
build_path_glob_replacement(path)
|
196
|
+
else
|
197
|
+
build_path_replacement(path, method, args)
|
198
|
+
end
|
197
199
|
|
200
|
+
message = format(MSG, rails_root: rails_root.source, replacement: replacement)
|
201
|
+
add_offense(node, message: message) do |corrector|
|
198
202
|
corrector.replace(node, replacement)
|
199
203
|
end
|
200
204
|
end
|
@@ -233,7 +237,12 @@ module RuboCop
|
|
233
237
|
end
|
234
238
|
|
235
239
|
replacement = "#{path_replacement}.#{method}"
|
236
|
-
|
240
|
+
|
241
|
+
if args.any?
|
242
|
+
formatted_args = args.map { |arg| arg.array_type? ? "*#{arg.source}" : arg.source }
|
243
|
+
replacement += "(#{formatted_args.join(', ')})"
|
244
|
+
end
|
245
|
+
|
237
246
|
replacement
|
238
247
|
end
|
239
248
|
|
@@ -244,7 +244,7 @@ module RuboCop
|
|
244
244
|
end
|
245
245
|
|
246
246
|
def operator_or_single_negative?(node)
|
247
|
-
node.
|
247
|
+
node.operator_keyword? || single_negative?(node)
|
248
248
|
end
|
249
249
|
|
250
250
|
def conditional?(parent)
|
@@ -328,8 +328,9 @@ module RuboCop
|
|
328
328
|
end
|
329
329
|
|
330
330
|
def return_value_assigned?(node)
|
331
|
-
assignment = assignable_node(node).parent
|
332
|
-
|
331
|
+
return false unless (assignment = assignable_node(node).parent)
|
332
|
+
|
333
|
+
assignment.assignment?
|
333
334
|
end
|
334
335
|
|
335
336
|
def persist_method?(node, methods = RESTRICT_ON_SEND)
|
@@ -40,12 +40,13 @@ module RuboCop
|
|
40
40
|
autocorrect(corrector, select_node, node, preferred_method)
|
41
41
|
end
|
42
42
|
end
|
43
|
+
alias on_csend on_send
|
43
44
|
|
44
45
|
private
|
45
46
|
|
46
47
|
def find_select_node(node, column_name)
|
47
48
|
node.descendants.detect do |select_candidate|
|
48
|
-
next if !select_candidate.
|
49
|
+
next if !select_candidate.call_type? || !select_candidate.method?(:select)
|
49
50
|
|
50
51
|
match_column_name?(select_candidate, column_name)
|
51
52
|
end
|
@@ -53,7 +54,7 @@ module RuboCop
|
|
53
54
|
|
54
55
|
# rubocop:disable Metrics/AbcSize
|
55
56
|
def autocorrect(corrector, select_node, node, preferred_method)
|
56
|
-
corrector.remove(select_node.
|
57
|
+
corrector.remove(select_node.parent.loc.dot)
|
57
58
|
corrector.remove(select_node.loc.selector.begin.join(select_node.source_range.end))
|
58
59
|
corrector.replace(node.loc.selector.begin.join(node.source_range.end), preferred_method)
|
59
60
|
end
|
@@ -100,7 +100,8 @@ module RuboCop
|
|
100
100
|
end
|
101
101
|
|
102
102
|
def forbidden_methods
|
103
|
-
|
103
|
+
# TODO: Remove when RuboCop Rails 3 releases.
|
104
|
+
obsolete_result = cop_config['Blacklist'] # rubocop:disable InternalAffairs/UndefinedConfig
|
104
105
|
if obsolete_result
|
105
106
|
warn '`Blacklist` has been renamed to `ForbiddenMethods`.' unless @displayed_forbidden_warning
|
106
107
|
@displayed_forbidden_warning = true
|
@@ -111,7 +112,8 @@ module RuboCop
|
|
111
112
|
end
|
112
113
|
|
113
114
|
def allowed_methods
|
114
|
-
|
115
|
+
# TODO: Remove when RuboCop Rails 3 releases.
|
116
|
+
obsolete_result = cop_config['Whitelist'] # rubocop:disable InternalAffairs/UndefinedConfig
|
115
117
|
if obsolete_result
|
116
118
|
warn '`Whitelist` has been renamed to `AllowedMethods`.' unless @displayed_allowed_warning
|
117
119
|
@displayed_allowed_warning = true
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# Enforces the use of `ActionController::Parameters#expect` as a method for strong parameter handling.
|
7
|
+
#
|
8
|
+
# @safety
|
9
|
+
# This cop's autocorrection is considered unsafe because there are cases where the HTTP status may change
|
10
|
+
# from 500 to 400 when handling invalid parameters. This change, however, reflects an intentional
|
11
|
+
# incompatibility introduced for valid reasons by the `expect` method, which aligns better with
|
12
|
+
# strong parameter conventions.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
#
|
16
|
+
# # bad
|
17
|
+
# params.require(:user).permit(:name, :age)
|
18
|
+
# params.permit(user: [:name, :age]).require(:user)
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# params.expect(user: [:name, :age])
|
22
|
+
#
|
23
|
+
class StrongParametersExpect < Base
|
24
|
+
extend AutoCorrector
|
25
|
+
extend TargetRailsVersion
|
26
|
+
|
27
|
+
MSG = 'Use `%<prefer>s` instead.'
|
28
|
+
RESTRICT_ON_SEND = %i[require permit].freeze
|
29
|
+
|
30
|
+
minimum_target_rails_version 8.0
|
31
|
+
|
32
|
+
def_node_matcher :params_require_permit, <<~PATTERN
|
33
|
+
$(call
|
34
|
+
$(call
|
35
|
+
(send nil? :params) :require _) :permit _+)
|
36
|
+
PATTERN
|
37
|
+
|
38
|
+
def_node_matcher :params_permit_require, <<~PATTERN
|
39
|
+
$(call
|
40
|
+
$(call
|
41
|
+
(send nil? :params) :permit (hash (pair _require_param_name _ )))
|
42
|
+
:require _require_param_name)
|
43
|
+
PATTERN
|
44
|
+
|
45
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
46
|
+
def on_send(node)
|
47
|
+
return if part_of_ignored_node?(node)
|
48
|
+
|
49
|
+
if (permit_method, require_method = params_require_permit(node))
|
50
|
+
range = offense_range(require_method, node)
|
51
|
+
prefer = expect_method(require_method, permit_method)
|
52
|
+
replace_argument = true
|
53
|
+
elsif (require_method, permit_method = params_permit_require(node))
|
54
|
+
range = offense_range(permit_method, node)
|
55
|
+
prefer = "expect(#{permit_method.arguments.map(&:source).join(', ')})"
|
56
|
+
replace_argument = false
|
57
|
+
else
|
58
|
+
return
|
59
|
+
end
|
60
|
+
|
61
|
+
add_offense(range, message: format(MSG, prefer: prefer)) do |corrector|
|
62
|
+
corrector.remove(require_method.receiver.source_range.end.join(require_method.source_range.end))
|
63
|
+
corrector.replace(permit_method.loc.selector, 'expect')
|
64
|
+
if replace_argument
|
65
|
+
corrector.insert_before(permit_method.first_argument, "#{require_key(require_method)}[")
|
66
|
+
corrector.insert_after(permit_method.last_argument, ']')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
ignore_node(node)
|
71
|
+
end
|
72
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
73
|
+
alias on_csend on_send
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def offense_range(method_node, node)
|
78
|
+
method_node.loc.selector.join(node.source_range.end)
|
79
|
+
end
|
80
|
+
|
81
|
+
def expect_method(require_method, permit_method)
|
82
|
+
require_key = require_key(require_method)
|
83
|
+
permit_args = permit_method.arguments.map(&:source).join(', ')
|
84
|
+
|
85
|
+
arguments = "#{require_key}[#{permit_args}]"
|
86
|
+
|
87
|
+
"expect(#{arguments})"
|
88
|
+
end
|
89
|
+
|
90
|
+
def require_key(require_method)
|
91
|
+
if (first_argument = require_method.first_argument).respond_to?(:value)
|
92
|
+
require_arg = first_argument.value
|
93
|
+
separator = ': '
|
94
|
+
else
|
95
|
+
require_arg = first_argument.source
|
96
|
+
separator = ' => '
|
97
|
+
end
|
98
|
+
|
99
|
+
"#{require_arg}#{separator}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -18,8 +18,9 @@ module RuboCop
|
|
18
18
|
# t.boolean :active, default: true, null: false
|
19
19
|
#
|
20
20
|
class ThreeStateBooleanColumn < Base
|
21
|
-
|
21
|
+
include MigrationsHelper
|
22
22
|
|
23
|
+
MSG = 'Boolean columns should always have a default value and a `NOT NULL` constraint.'
|
23
24
|
RESTRICT_ON_SEND = %i[add_column column boolean].freeze
|
24
25
|
|
25
26
|
def_node_matcher :three_state_boolean?, <<~PATTERN
|