rubocop-rails 2.19.1 → 2.30.3
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 +70 -16
- data/config/default.yml +173 -28
- data/lib/rubocop/cop/mixin/active_record_helper.rb +16 -4
- data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +2 -2
- data/lib/rubocop/cop/mixin/database_type_resolvable.rb +66 -0
- data/lib/rubocop/cop/mixin/index_method.rb +68 -61
- data/lib/rubocop/cop/mixin/routes_helper.rb +20 -0
- data/lib/rubocop/cop/mixin/target_rails_version.rb +27 -2
- data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +3 -1
- data/lib/rubocop/cop/rails/action_controller_test_case.rb +2 -2
- data/lib/rubocop/cop/rails/action_filter.rb +3 -0
- data/lib/rubocop/cop/rails/action_order.rb +1 -5
- data/lib/rubocop/cop/rails/active_record_aliases.rb +2 -2
- data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +1 -5
- data/lib/rubocop/cop/rails/active_support_aliases.rb +6 -5
- data/lib/rubocop/cop/rails/active_support_on_load.rb +21 -1
- data/lib/rubocop/cop/rails/add_column_index.rb +1 -0
- data/lib/rubocop/cop/rails/after_commit_override.rb +1 -1
- data/lib/rubocop/cop/rails/application_record.rb +4 -0
- data/lib/rubocop/cop/rails/assert_not.rb +0 -1
- data/lib/rubocop/cop/rails/belongs_to.rb +1 -1
- data/lib/rubocop/cop/rails/blank.rb +1 -1
- data/lib/rubocop/cop/rails/bulk_change_table.rb +19 -45
- data/lib/rubocop/cop/rails/compact_blank.rb +29 -8
- data/lib/rubocop/cop/rails/content_tag.rb +2 -2
- data/lib/rubocop/cop/rails/dangerous_column_names.rb +448 -0
- data/lib/rubocop/cop/rails/date.rb +14 -5
- data/lib/rubocop/cop/rails/delegate.rb +53 -7
- data/lib/rubocop/cop/rails/duplicate_association.rb +71 -10
- data/lib/rubocop/cop/rails/dynamic_find_by.rb +3 -3
- data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +2 -2
- data/lib/rubocop/cop/rails/enum_hash.rb +31 -8
- data/lib/rubocop/cop/rails/enum_syntax.rb +130 -0
- data/lib/rubocop/cop/rails/enum_uniqueness.rb +29 -7
- data/lib/rubocop/cop/rails/env_local.rb +69 -0
- data/lib/rubocop/cop/rails/expanded_date_range.rb +1 -1
- data/lib/rubocop/cop/rails/file_path.rb +186 -18
- data/lib/rubocop/cop/rails/find_by.rb +3 -3
- data/lib/rubocop/cop/rails/find_by_id.rb +9 -23
- data/lib/rubocop/cop/rails/find_each.rb +1 -1
- data/lib/rubocop/cop/rails/freeze_time.rb +1 -1
- data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +1 -1
- data/lib/rubocop/cop/rails/helper_instance_variable.rb +1 -1
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +7 -0
- data/lib/rubocop/cop/rails/http_status.rb +16 -5
- data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +63 -13
- data/lib/rubocop/cop/rails/i18n_locale_texts.rb +5 -1
- data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +23 -3
- 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/inquiry.rb +2 -1
- data/lib/rubocop/cop/rails/inverse_of.rb +1 -1
- data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +19 -10
- data/lib/rubocop/cop/rails/link_to_blank.rb +2 -2
- 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 +100 -6
- data/lib/rubocop/cop/rails/output.rb +3 -2
- data/lib/rubocop/cop/rails/pick.rb +10 -5
- data/lib/rubocop/cop/rails/pluck.rb +21 -1
- data/lib/rubocop/cop/rails/pluck_id.rb +2 -1
- data/lib/rubocop/cop/rails/pluck_in_where.rb +35 -13
- data/lib/rubocop/cop/rails/pluralization_grammar.rb +30 -16
- data/lib/rubocop/cop/rails/presence.rb +1 -1
- data/lib/rubocop/cop/rails/present.rb +1 -3
- data/lib/rubocop/cop/rails/rake_environment.rb +22 -6
- data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +190 -0
- data/lib/rubocop/cop/rails/redundant_foreign_key.rb +1 -1
- data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +16 -0
- data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +2 -2
- data/lib/rubocop/cop/rails/reflection_class_name.rb +2 -2
- data/lib/rubocop/cop/rails/refute_methods.rb +0 -1
- data/lib/rubocop/cop/rails/relative_date_constant.rb +1 -1
- data/lib/rubocop/cop/rails/render_plain_text.rb +6 -3
- data/lib/rubocop/cop/rails/request_referer.rb +1 -1
- data/lib/rubocop/cop/rails/response_parsed_body.rb +52 -10
- data/lib/rubocop/cop/rails/reversible_migration.rb +7 -5
- data/lib/rubocop/cop/rails/root_pathname_methods.rb +58 -15
- data/lib/rubocop/cop/rails/save_bang.rb +22 -14
- data/lib/rubocop/cop/rails/schema_comment.rb +17 -10
- data/lib/rubocop/cop/rails/select_map.rb +79 -0
- data/lib/rubocop/cop/rails/skips_model_validations.rb +9 -4
- data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +1 -2
- data/lib/rubocop/cop/rails/strip_heredoc.rb +1 -1
- data/lib/rubocop/cop/rails/strong_parameters_expect.rb +104 -0
- data/lib/rubocop/cop/rails/three_state_boolean_column.rb +4 -5
- data/lib/rubocop/cop/rails/time_zone.rb +26 -11
- data/lib/rubocop/cop/rails/transaction_exit_statement.rb +40 -9
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +11 -26
- data/lib/rubocop/cop/rails/unique_validation_without_index.rb +17 -21
- data/lib/rubocop/cop/rails/unknown_env.rb +5 -1
- data/lib/rubocop/cop/rails/unused_ignored_columns.rb +6 -0
- data/lib/rubocop/cop/rails/unused_render_content.rb +67 -0
- data/lib/rubocop/cop/rails/validation.rb +9 -4
- data/lib/rubocop/cop/rails/where_equals.rb +29 -12
- data/lib/rubocop/cop/rails/where_exists.rb +9 -9
- data/lib/rubocop/cop/rails/where_missing.rb +6 -2
- data/lib/rubocop/cop/rails/where_not.rb +18 -11
- data/lib/rubocop/cop/rails/where_range.rb +203 -0
- data/lib/rubocop/cop/rails_cops.rb +11 -0
- data/lib/rubocop/rails/migration_file_skippable.rb +54 -0
- data/lib/rubocop/rails/plugin.rb +48 -0
- data/lib/rubocop/rails/schema_loader/schema.rb +8 -7
- data/lib/rubocop/rails/schema_loader.rb +5 -15
- data/lib/rubocop/rails/version.rb +1 -1
- data/lib/rubocop/rails.rb +1 -8
- data/lib/rubocop-rails.rb +12 -4
- metadata +55 -11
- data/lib/rubocop/rails/inject.rb +0 -18
@@ -21,12 +21,15 @@ module RuboCop
|
|
21
21
|
# # bad
|
22
22
|
# Time.now
|
23
23
|
# Time.parse('2015-03-02T19:05:37')
|
24
|
+
# '2015-03-02T19:05:37'.to_time
|
24
25
|
#
|
25
26
|
# # good
|
26
27
|
# Time.current
|
27
28
|
# Time.zone.now
|
28
29
|
# Time.zone.parse('2015-03-02T19:05:37')
|
29
30
|
# Time.zone.parse('2015-03-02T19:05:37Z') # Respect ISO 8601 format with timezone specifier.
|
31
|
+
# Time.parse('2015-03-02T19:05:37Z') # Also respects ISO 8601
|
32
|
+
# '2015-03-02T19:05:37Z'.to_time # Also respects ISO 8601
|
30
33
|
#
|
31
34
|
# @example EnforcedStyle: flexible (default)
|
32
35
|
# # `flexible` allows usage of `in_time_zone` instead of `zone`.
|
@@ -44,18 +47,16 @@ module RuboCop
|
|
44
47
|
extend AutoCorrector
|
45
48
|
|
46
49
|
MSG = 'Do not use `%<current>s` without zone. Use `%<prefer>s` instead.'
|
47
|
-
|
48
50
|
MSG_ACCEPTABLE = 'Do not use `%<current>s` without zone. Use one of %<prefer>s instead.'
|
49
|
-
|
50
51
|
MSG_LOCALTIME = 'Do not use `Time.localtime` without offset or zone.'
|
52
|
+
MSG_STRING_TO_TIME = 'Do not use `String#to_time` without zone. Use `Time.zone.parse` instead.'
|
51
53
|
|
52
54
|
GOOD_METHODS = %i[zone zone_default find_zone find_zone!].freeze
|
53
|
-
|
54
55
|
DANGEROUS_METHODS = %i[now local new parse at].freeze
|
55
|
-
|
56
56
|
ACCEPTED_METHODS = %i[in_time_zone utc getlocal xmlschema iso8601 jisx0301 rfc3339 httpdate to_i to_f].freeze
|
57
|
+
TIMEZONE_SPECIFIER = /([A-Za-z]|[+-]\d{2}:?\d{2})\z/.freeze
|
57
58
|
|
58
|
-
|
59
|
+
RESTRICT_ON_SEND = %i[to_time].freeze
|
59
60
|
|
60
61
|
def on_const(node)
|
61
62
|
mod, klass = *node
|
@@ -66,6 +67,16 @@ module RuboCop
|
|
66
67
|
check_time_node(klass, node.parent) if klass == :Time
|
67
68
|
end
|
68
69
|
|
70
|
+
def on_send(node)
|
71
|
+
return if !node.receiver&.str_type? || !node.method?(:to_time)
|
72
|
+
return if attach_timezone_specifier?(node.receiver)
|
73
|
+
|
74
|
+
add_offense(node.loc.selector, message: MSG_STRING_TO_TIME) do |corrector|
|
75
|
+
corrector.replace(node, "Time.zone.parse(#{node.receiver.source})") unless node.csend_type?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
alias on_csend on_send
|
79
|
+
|
69
80
|
private
|
70
81
|
|
71
82
|
def autocorrect(corrector, node)
|
@@ -86,11 +97,9 @@ module RuboCop
|
|
86
97
|
end
|
87
98
|
|
88
99
|
def autocorrect_time_new(node, corrector)
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
corrector.replace(node.loc.selector, 'now')
|
93
|
-
end
|
100
|
+
replacement = replacement(node)
|
101
|
+
|
102
|
+
corrector.replace(node.loc.selector, replacement)
|
94
103
|
end
|
95
104
|
|
96
105
|
# remove redundant `.in_time_zone` from `Time.zone.now.in_time_zone`
|
@@ -172,7 +181,7 @@ module RuboCop
|
|
172
181
|
|
173
182
|
def safe_method(method_name, node)
|
174
183
|
if %w[new current].include?(method_name)
|
175
|
-
node
|
184
|
+
replacement(node)
|
176
185
|
else
|
177
186
|
method_name
|
178
187
|
end
|
@@ -248,6 +257,12 @@ module RuboCop
|
|
248
257
|
pair.key.sym_type? && pair.key.value == :in && !pair.value.nil_type?
|
249
258
|
end
|
250
259
|
end
|
260
|
+
|
261
|
+
def replacement(node)
|
262
|
+
return 'now' unless node.arguments?
|
263
|
+
|
264
|
+
node.first_argument.str_type? ? 'parse' : 'local'
|
265
|
+
end
|
251
266
|
end
|
252
267
|
end
|
253
268
|
end
|
@@ -13,6 +13,12 @@ module RuboCop
|
|
13
13
|
# error when rollback is desired, and to use `next` when commit is
|
14
14
|
# desired.
|
15
15
|
#
|
16
|
+
# If you are defining custom transaction methods, you can configure it with `TransactionMethods`.
|
17
|
+
#
|
18
|
+
# NOTE: This cop is disabled on Rails >= 7.2 because transactions were restored
|
19
|
+
# to their historical behavior. In Rails 7.1, the behavior is controlled with
|
20
|
+
# the config `active_record.commit_transaction_on_non_local_return`.
|
21
|
+
#
|
16
22
|
# @example
|
17
23
|
# # bad
|
18
24
|
# ApplicationRecord.transaction do
|
@@ -34,6 +40,11 @@ module RuboCop
|
|
34
40
|
# throw if user.active?
|
35
41
|
# end
|
36
42
|
#
|
43
|
+
# # bad, as `with_lock` implicitly opens a transaction too
|
44
|
+
# ApplicationRecord.with_lock do
|
45
|
+
# break if user.active?
|
46
|
+
# end
|
47
|
+
#
|
37
48
|
# # good
|
38
49
|
# ApplicationRecord.transaction do
|
39
50
|
# # Rollback
|
@@ -45,12 +56,16 @@ module RuboCop
|
|
45
56
|
# # Commit
|
46
57
|
# next if user.active?
|
47
58
|
# end
|
59
|
+
#
|
60
|
+
# @example TransactionMethods: ["custom_transaction"]
|
61
|
+
# # bad
|
62
|
+
# CustomModel.custom_transaction do
|
63
|
+
# return if user.active?
|
64
|
+
# end
|
65
|
+
#
|
48
66
|
class TransactionExitStatement < Base
|
49
|
-
MSG =
|
50
|
-
|
51
|
-
MSG
|
52
|
-
|
53
|
-
RESTRICT_ON_SEND = %i[transaction with_lock].freeze
|
67
|
+
MSG = 'Exit statement `%<statement>s` is not allowed. Use `raise` (rollback) or `next` (commit).'
|
68
|
+
BUILT_IN_TRANSACTION_METHODS = %i[transaction with_lock].freeze
|
54
69
|
|
55
70
|
def_node_search :exit_statements, <<~PATTERN
|
56
71
|
({return | break | send nil? :throw} ...)
|
@@ -65,10 +80,10 @@ module RuboCop
|
|
65
80
|
PATTERN
|
66
81
|
|
67
82
|
def on_send(node)
|
68
|
-
return
|
69
|
-
return unless
|
83
|
+
return if target_rails_version >= 7.2
|
84
|
+
return unless in_transaction_block?(node)
|
70
85
|
|
71
|
-
exit_statements(parent.body).each do |statement_node|
|
86
|
+
exit_statements(node.parent.body).each do |statement_node|
|
72
87
|
next if statement_node.break_type? && nested_block?(statement_node)
|
73
88
|
|
74
89
|
statement = statement(statement_node)
|
@@ -80,6 +95,13 @@ module RuboCop
|
|
80
95
|
|
81
96
|
private
|
82
97
|
|
98
|
+
def in_transaction_block?(node)
|
99
|
+
return false unless transaction_method_name?(node.method_name)
|
100
|
+
return false unless (parent = node.parent)
|
101
|
+
|
102
|
+
parent.any_block_type? && parent.body
|
103
|
+
end
|
104
|
+
|
83
105
|
def statement(statement_node)
|
84
106
|
if statement_node.return_type?
|
85
107
|
'return'
|
@@ -91,7 +113,16 @@ module RuboCop
|
|
91
113
|
end
|
92
114
|
|
93
115
|
def nested_block?(statement_node)
|
94
|
-
|
116
|
+
name = statement_node.ancestors.find(&:any_block_type?).children.first.method_name
|
117
|
+
!transaction_method_name?(name)
|
118
|
+
end
|
119
|
+
|
120
|
+
def transaction_method_name?(method_name)
|
121
|
+
BUILT_IN_TRANSACTION_METHODS.include?(method_name) || transaction_method?(method_name)
|
122
|
+
end
|
123
|
+
|
124
|
+
def transaction_method?(method_name)
|
125
|
+
cop_config.fetch('TransactionMethods', []).include?(method_name.to_s)
|
95
126
|
end
|
96
127
|
end
|
97
128
|
end
|
@@ -51,43 +51,28 @@ module RuboCop
|
|
51
51
|
|
52
52
|
MSG = 'Use `distinct` before `pluck`.'
|
53
53
|
RESTRICT_ON_SEND = %i[uniq].freeze
|
54
|
-
NEWLINE = "\n"
|
55
|
-
PATTERN = '[!^block (send (send %<type>s :pluck ...) :uniq ...)]'
|
56
54
|
|
57
|
-
def_node_matcher :
|
58
|
-
|
59
|
-
def_node_matcher :aggressive_node_match, format(PATTERN, type: '_')
|
55
|
+
def_node_matcher :uniq_before_pluck, '[!^any_block $(send $(send _ :pluck ...) :uniq ...)]'
|
60
56
|
|
61
57
|
def on_send(node)
|
62
|
-
|
63
|
-
|
64
|
-
else
|
65
|
-
aggressive_node_match(node)
|
66
|
-
end
|
67
|
-
|
68
|
-
return unless uniq
|
69
|
-
|
70
|
-
add_offense(node.loc.selector) do |corrector|
|
71
|
-
method = node.method_name
|
58
|
+
uniq_before_pluck(node) do |uniq_node, pluck_node|
|
59
|
+
next if style == :conservative && !pluck_node.receiver&.const_type?
|
72
60
|
|
73
|
-
|
74
|
-
|
61
|
+
add_offense(uniq_node.loc.selector) do |corrector|
|
62
|
+
autocorrect(corrector, uniq_node, pluck_node)
|
63
|
+
end
|
75
64
|
end
|
76
65
|
end
|
77
66
|
|
78
67
|
private
|
79
68
|
|
80
|
-
def
|
81
|
-
range_between(
|
82
|
-
end
|
83
|
-
|
84
|
-
def dot_method_begin_pos(method, node)
|
85
|
-
lines = node.source.split(NEWLINE)
|
69
|
+
def autocorrect(corrector, uniq_node, pluck_node)
|
70
|
+
corrector.remove(range_between(pluck_node.loc.end.end_pos, uniq_node.loc.selector.end_pos))
|
86
71
|
|
87
|
-
if
|
88
|
-
|
72
|
+
if (dot = pluck_node.loc.dot)
|
73
|
+
corrector.insert_before(dot.begin, '.distinct')
|
89
74
|
else
|
90
|
-
|
75
|
+
corrector.insert_before(pluck_node, 'distinct.')
|
91
76
|
end
|
92
77
|
end
|
93
78
|
end
|
@@ -31,11 +31,11 @@ module RuboCop
|
|
31
31
|
RESTRICT_ON_SEND = %i[validates].freeze
|
32
32
|
|
33
33
|
def on_send(node)
|
34
|
-
return unless uniqueness_part(node)
|
35
|
-
return if condition_part?(node)
|
36
34
|
return unless schema
|
35
|
+
return unless (uniqueness_part = uniqueness_part(node))
|
36
|
+
return if uniqueness_part.falsey_literal? || condition_part?(node, uniqueness_part)
|
37
37
|
|
38
|
-
klass, table, names = find_schema_information(node)
|
38
|
+
klass, table, names = find_schema_information(node, uniqueness_part)
|
39
39
|
return unless names
|
40
40
|
return if with_index?(klass, table, names)
|
41
41
|
|
@@ -44,12 +44,12 @@ module RuboCop
|
|
44
44
|
|
45
45
|
private
|
46
46
|
|
47
|
-
def find_schema_information(node)
|
47
|
+
def find_schema_information(node, uniqueness_part)
|
48
48
|
klass = class_node(node)
|
49
49
|
return unless klass
|
50
50
|
|
51
51
|
table = schema.table_by(name: table_name(klass))
|
52
|
-
names = column_names(node)
|
52
|
+
names = column_names(node, uniqueness_part)
|
53
53
|
|
54
54
|
[klass, table, names]
|
55
55
|
end
|
@@ -71,12 +71,12 @@ module RuboCop
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
-
def column_names(node)
|
74
|
+
def column_names(node, uniqueness_part)
|
75
75
|
arg = node.first_argument
|
76
|
-
return unless arg.
|
76
|
+
return unless arg.type?(:str, :sym)
|
77
77
|
|
78
78
|
ret = [arg.value]
|
79
|
-
names_from_scope = column_names_from_scope(
|
79
|
+
names_from_scope = column_names_from_scope(uniqueness_part)
|
80
80
|
ret.concat(names_from_scope) if names_from_scope
|
81
81
|
|
82
82
|
ret = ret.flat_map do |name|
|
@@ -90,11 +90,10 @@ module RuboCop
|
|
90
90
|
ret.include?(nil) ? nil : ret.to_set
|
91
91
|
end
|
92
92
|
|
93
|
-
def column_names_from_scope(
|
94
|
-
|
95
|
-
return unless uniq.hash_type?
|
93
|
+
def column_names_from_scope(uniqueness_part)
|
94
|
+
return unless uniqueness_part.hash_type?
|
96
95
|
|
97
|
-
scope = find_scope(
|
96
|
+
scope = find_scope(uniqueness_part)
|
98
97
|
return unless scope
|
99
98
|
|
100
99
|
scope = unfreeze_scope(scope)
|
@@ -125,8 +124,8 @@ module RuboCop
|
|
125
124
|
end
|
126
125
|
|
127
126
|
def uniqueness_part(node)
|
128
|
-
pairs = node.
|
129
|
-
return unless pairs
|
127
|
+
pairs = node.last_argument
|
128
|
+
return unless pairs&.hash_type?
|
130
129
|
|
131
130
|
pairs.each_pair.find do |pair|
|
132
131
|
next unless pair.key.sym_type? && pair.key.value == :uniqueness
|
@@ -135,14 +134,11 @@ module RuboCop
|
|
135
134
|
end
|
136
135
|
end
|
137
136
|
|
138
|
-
def condition_part?(node)
|
139
|
-
pairs = node.
|
140
|
-
return unless pairs.hash_type?
|
141
|
-
|
137
|
+
def condition_part?(node, uniqueness_node)
|
138
|
+
pairs = node.last_argument
|
139
|
+
return false unless pairs.hash_type?
|
142
140
|
return true if condition_hash_part?(pairs, keys: %i[if unless])
|
143
|
-
|
144
|
-
uniqueness_node = uniqueness_part(node)
|
145
|
-
return unless uniqueness_node&.hash_type?
|
141
|
+
return false unless uniqueness_node.hash_type?
|
146
142
|
|
147
143
|
condition_hash_part?(uniqueness_node, keys: %i[if unless conditions])
|
148
144
|
end
|
@@ -86,7 +86,11 @@ module RuboCop
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def environments
|
89
|
-
|
89
|
+
@environments ||= begin
|
90
|
+
environments = cop_config['Environments'].dup || []
|
91
|
+
environments << 'local' if target_rails_version >= 7.1
|
92
|
+
environments
|
93
|
+
end
|
90
94
|
end
|
91
95
|
end
|
92
96
|
end
|
@@ -7,6 +7,12 @@ module RuboCop
|
|
7
7
|
# `ignored_columns` is necessary to drop a column from RDBMS, but you don't need it after the migration
|
8
8
|
# to drop the column. You avoid forgetting to remove `ignored_columns` by this cop.
|
9
9
|
#
|
10
|
+
# IMPORTANT: This cop can't be used to effectively check for unused columns because the development
|
11
|
+
# and production schema can be out of sync until the migration has been run on production. As such,
|
12
|
+
# this cop can cause `ignored_columns` to be removed even though the production schema still contains
|
13
|
+
# the column, which can lead to downtime when the migration is actually executed. Only enable this cop
|
14
|
+
# if you know your migrations will be run before any of your Rails applications boot with the modified code.
|
15
|
+
#
|
10
16
|
# @example
|
11
17
|
# # bad
|
12
18
|
# class User < ApplicationRecord
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# If you try to render content along with a non-content status code (100-199, 204, 205, or 304),
|
7
|
+
# it will be dropped from the response.
|
8
|
+
#
|
9
|
+
# This cop checks for uses of `render` which specify both body content and a non-content status.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# # bad
|
13
|
+
# render 'foo', status: :continue
|
14
|
+
# render status: 100, plain: 'Ruby!'
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# head :continue
|
18
|
+
# head 100
|
19
|
+
class UnusedRenderContent < Base
|
20
|
+
include RangeHelp
|
21
|
+
|
22
|
+
MSG = 'Do not specify body content for a response with a non-content status code'
|
23
|
+
RESTRICT_ON_SEND = %i[render].freeze
|
24
|
+
NON_CONTENT_STATUS_CODES = Set[*100..199, 204, 205, 304] & ::Rack::Utils::SYMBOL_TO_STATUS_CODE.values
|
25
|
+
NON_CONTENT_STATUSES = Set[
|
26
|
+
*::Rack::Utils::SYMBOL_TO_STATUS_CODE.invert.fetch_values(*NON_CONTENT_STATUS_CODES)
|
27
|
+
]
|
28
|
+
BODY_OPTIONS = Set[
|
29
|
+
:action,
|
30
|
+
:body,
|
31
|
+
:content_type,
|
32
|
+
:file,
|
33
|
+
:html,
|
34
|
+
:inline,
|
35
|
+
:json,
|
36
|
+
:js,
|
37
|
+
:layout,
|
38
|
+
:plain,
|
39
|
+
:raw,
|
40
|
+
:template,
|
41
|
+
:text,
|
42
|
+
:xml
|
43
|
+
]
|
44
|
+
|
45
|
+
def_node_matcher :non_content_status?, <<~PATTERN
|
46
|
+
(pair
|
47
|
+
(sym :status)
|
48
|
+
{(sym NON_CONTENT_STATUSES) (int NON_CONTENT_STATUS_CODES)}
|
49
|
+
)
|
50
|
+
PATTERN
|
51
|
+
|
52
|
+
def_node_matcher :unused_render_content?, <<~PATTERN
|
53
|
+
(send nil? :render {
|
54
|
+
(hash <#non_content_status? $(pair (sym BODY_OPTIONS) _) ...>) |
|
55
|
+
$({str sym} _) (hash <#non_content_status? ...>)
|
56
|
+
})
|
57
|
+
PATTERN
|
58
|
+
|
59
|
+
def on_send(node)
|
60
|
+
unused_render_content?(node) do |unused_content_node|
|
61
|
+
add_offense(unused_content_node)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -8,6 +8,7 @@ module RuboCop
|
|
8
8
|
# @example
|
9
9
|
# # bad
|
10
10
|
# validates_acceptance_of :foo
|
11
|
+
# validates_comparison_of :foo
|
11
12
|
# validates_confirmation_of :foo
|
12
13
|
# validates_exclusion_of :foo
|
13
14
|
# validates_format_of :foo
|
@@ -22,6 +23,7 @@ module RuboCop
|
|
22
23
|
# # good
|
23
24
|
# validates :foo, acceptance: true
|
24
25
|
# validates :foo, confirmation: true
|
26
|
+
# validates :foo, comparison: true
|
25
27
|
# validates :foo, exclusion: true
|
26
28
|
# validates :foo, format: true
|
27
29
|
# validates :foo, inclusion: true
|
@@ -29,7 +31,7 @@ module RuboCop
|
|
29
31
|
# validates :foo, numericality: true
|
30
32
|
# validates :foo, presence: true
|
31
33
|
# validates :foo, absence: true
|
32
|
-
# validates :foo,
|
34
|
+
# validates :foo, length: true
|
33
35
|
# validates :foo, uniqueness: true
|
34
36
|
#
|
35
37
|
class Validation < Base
|
@@ -39,6 +41,7 @@ module RuboCop
|
|
39
41
|
|
40
42
|
TYPES = %w[
|
41
43
|
acceptance
|
44
|
+
comparison
|
42
45
|
confirmation
|
43
46
|
exclusion
|
44
47
|
format
|
@@ -51,16 +54,16 @@ module RuboCop
|
|
51
54
|
uniqueness
|
52
55
|
].freeze
|
53
56
|
|
54
|
-
RESTRICT_ON_SEND = TYPES.map { |p| "validates_#{p}_of"
|
57
|
+
RESTRICT_ON_SEND = TYPES.map { |p| :"validates_#{p}_of" }.freeze
|
55
58
|
ALLOWLIST = TYPES.map { |p| "validates :column, #{p}: value" }.freeze
|
56
59
|
|
57
60
|
def on_send(node)
|
58
61
|
return if node.receiver
|
62
|
+
return unless (last_argument = node.last_argument)
|
59
63
|
|
60
64
|
range = node.loc.selector
|
61
65
|
|
62
66
|
add_offense(range, message: message(node)) do |corrector|
|
63
|
-
last_argument = node.arguments.last
|
64
67
|
return if !last_argument.literal? && !last_argument.splat_type? && !frozen_array_argument?(last_argument)
|
65
68
|
|
66
69
|
corrector.replace(range, 'validates')
|
@@ -120,7 +123,9 @@ module RuboCop
|
|
120
123
|
end
|
121
124
|
|
122
125
|
def validate_type(node)
|
123
|
-
node.method_name.to_s.split('_')[1]
|
126
|
+
type = node.method_name.to_s.split('_')[1]
|
127
|
+
|
128
|
+
type == 'size' ? 'length' : type
|
124
129
|
end
|
125
130
|
|
126
131
|
def frozen_array_argument?(argument)
|
@@ -4,7 +4,8 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
module Rails
|
6
6
|
# Identifies places where manually constructed SQL
|
7
|
-
# in `where` can be replaced with
|
7
|
+
# in `where` and `where.not` can be replaced with
|
8
|
+
# `where(attribute: value)` and `where.not(attribute: value)`.
|
8
9
|
#
|
9
10
|
# @safety
|
10
11
|
# This cop's autocorrection is unsafe because is may change SQL.
|
@@ -13,6 +14,7 @@ module RuboCop
|
|
13
14
|
# @example
|
14
15
|
# # bad
|
15
16
|
# User.where('name = ?', 'Gabe')
|
17
|
+
# User.where.not('name = ?', 'Gabe')
|
16
18
|
# User.where('name = :name', name: 'Gabe')
|
17
19
|
# User.where('name IS NULL')
|
18
20
|
# User.where('name IN (?)', ['john', 'jane'])
|
@@ -21,6 +23,7 @@ module RuboCop
|
|
21
23
|
#
|
22
24
|
# # good
|
23
25
|
# User.where(name: 'Gabe')
|
26
|
+
# User.where.not(name: 'Gabe')
|
24
27
|
# User.where(name: nil)
|
25
28
|
# User.where(name: ['john', 'jane'])
|
26
29
|
# User.where(users: { name: 'Gabe' })
|
@@ -29,25 +32,27 @@ module RuboCop
|
|
29
32
|
extend AutoCorrector
|
30
33
|
|
31
34
|
MSG = 'Use `%<good_method>s` instead of manually constructing SQL.'
|
32
|
-
RESTRICT_ON_SEND = %i[where].freeze
|
35
|
+
RESTRICT_ON_SEND = %i[where not].freeze
|
33
36
|
|
34
37
|
def_node_matcher :where_method_call?, <<~PATTERN
|
35
38
|
{
|
36
|
-
(
|
37
|
-
(
|
39
|
+
(call _ {:where :not} (array $str_type? $_ ?))
|
40
|
+
(call _ {:where :not} $str_type? $_ ?)
|
38
41
|
}
|
39
42
|
PATTERN
|
40
43
|
|
41
44
|
def on_send(node)
|
45
|
+
return if node.method?(:not) && !where_not?(node)
|
46
|
+
|
42
47
|
where_method_call?(node) do |template_node, value_node|
|
43
48
|
value_node = value_node.first
|
44
49
|
|
45
50
|
range = offense_range(node)
|
46
51
|
|
47
|
-
|
48
|
-
return unless
|
52
|
+
column, value = extract_column_and_value(template_node, value_node)
|
53
|
+
return unless value
|
49
54
|
|
50
|
-
good_method = build_good_method(
|
55
|
+
good_method = build_good_method(node.method_name, column, value)
|
51
56
|
message = format(MSG, good_method: good_method)
|
52
57
|
|
53
58
|
add_offense(range, message: message) do |corrector|
|
@@ -55,6 +60,7 @@ module RuboCop
|
|
55
60
|
end
|
56
61
|
end
|
57
62
|
end
|
63
|
+
alias on_csend on_send
|
58
64
|
|
59
65
|
EQ_ANONYMOUS_RE = /\A([\w.]+)\s+=\s+\?\z/.freeze # column = ?
|
60
66
|
IN_ANONYMOUS_RE = /\A([\w.]+)\s+IN\s+\(\?\)\z/i.freeze # column IN (?)
|
@@ -68,11 +74,12 @@ module RuboCop
|
|
68
74
|
range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
|
69
75
|
end
|
70
76
|
|
77
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
71
78
|
def extract_column_and_value(template_node, value_node)
|
72
79
|
value =
|
73
80
|
case template_node.value
|
74
81
|
when EQ_ANONYMOUS_RE, IN_ANONYMOUS_RE
|
75
|
-
value_node
|
82
|
+
value_node&.source
|
76
83
|
when EQ_NAMED_RE, IN_NAMED_RE
|
77
84
|
return unless value_node&.hash_type?
|
78
85
|
|
@@ -84,18 +91,28 @@ module RuboCop
|
|
84
91
|
return
|
85
92
|
end
|
86
93
|
|
87
|
-
|
94
|
+
column_qualifier = Regexp.last_match(1)
|
95
|
+
return if column_qualifier.count('.') > 1
|
96
|
+
|
97
|
+
[column_qualifier, value]
|
88
98
|
end
|
99
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
89
100
|
|
90
|
-
def build_good_method(column, value)
|
101
|
+
def build_good_method(method_name, column, value)
|
91
102
|
if column.include?('.')
|
92
103
|
table, column = column.split('.')
|
93
104
|
|
94
|
-
"
|
105
|
+
"#{method_name}(#{table}: { #{column}: #{value} })"
|
95
106
|
else
|
96
|
-
"
|
107
|
+
"#{method_name}(#{column}: #{value})"
|
97
108
|
end
|
98
109
|
end
|
110
|
+
|
111
|
+
def where_not?(node)
|
112
|
+
return false unless (receiver = node.receiver)
|
113
|
+
|
114
|
+
receiver.send_type? && receiver.method?(:where)
|
115
|
+
end
|
99
116
|
end
|
100
117
|
end
|
101
118
|
end
|
@@ -39,7 +39,6 @@ module RuboCop
|
|
39
39
|
# # bad
|
40
40
|
# User.exists?(name: 'john')
|
41
41
|
# User.exists?(['name = ?', 'john'])
|
42
|
-
# User.exists?('name = ?', 'john')
|
43
42
|
# user.posts.exists?(published: true)
|
44
43
|
#
|
45
44
|
# # good
|
@@ -56,11 +55,11 @@ module RuboCop
|
|
56
55
|
RESTRICT_ON_SEND = %i[exists?].freeze
|
57
56
|
|
58
57
|
def_node_matcher :where_exists_call?, <<~PATTERN
|
59
|
-
(
|
58
|
+
(call (call _ :where $...) :exists?)
|
60
59
|
PATTERN
|
61
60
|
|
62
61
|
def_node_matcher :exists_with_args?, <<~PATTERN
|
63
|
-
(
|
62
|
+
(call _ :exists? $...)
|
64
63
|
PATTERN
|
65
64
|
|
66
65
|
def on_send(node)
|
@@ -68,7 +67,7 @@ module RuboCop
|
|
68
67
|
return unless convertable_args?(args)
|
69
68
|
|
70
69
|
range = correction_range(node)
|
71
|
-
good_method = build_good_method(args)
|
70
|
+
good_method = build_good_method(args, dot: node.loc.dot)
|
72
71
|
message = format(MSG, good_method: good_method, bad_method: range.source)
|
73
72
|
|
74
73
|
add_offense(range, message: message) do |corrector|
|
@@ -76,6 +75,7 @@ module RuboCop
|
|
76
75
|
end
|
77
76
|
end
|
78
77
|
end
|
78
|
+
alias on_csend on_send
|
79
79
|
|
80
80
|
private
|
81
81
|
|
@@ -109,11 +109,11 @@ module RuboCop
|
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
112
|
-
def build_good_method(args)
|
112
|
+
def build_good_method(args, dot:)
|
113
113
|
if exists_style?
|
114
114
|
build_good_method_exists(args)
|
115
115
|
elsif where_style?
|
116
|
-
build_good_method_where(args)
|
116
|
+
build_good_method_where(args, dot&.source || '.')
|
117
117
|
end
|
118
118
|
end
|
119
119
|
|
@@ -125,11 +125,11 @@ module RuboCop
|
|
125
125
|
end
|
126
126
|
end
|
127
127
|
|
128
|
-
def build_good_method_where(args)
|
128
|
+
def build_good_method_where(args, dot_source)
|
129
129
|
if args.size > 1
|
130
|
-
"where(#{args.map(&:source).join(', ')})
|
130
|
+
"where(#{args.map(&:source).join(', ')})#{dot_source}exists?"
|
131
131
|
else
|
132
|
-
"where(#{args[0].source})
|
132
|
+
"where(#{args[0].source})#{dot_source}exists?"
|
133
133
|
end
|
134
134
|
end
|
135
135
|
end
|