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
@@ -36,7 +36,7 @@ module RuboCop
|
|
36
36
|
PATTERN
|
37
37
|
|
38
38
|
def on_send(node)
|
39
|
-
return unless node.first_argument
|
39
|
+
return unless node.first_argument&.sym_type?
|
40
40
|
|
41
41
|
root_receiver = root_receiver(node)
|
42
42
|
where_node_and_argument(root_receiver) do |where_node, where_argument|
|
@@ -89,16 +89,20 @@ module RuboCop
|
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
|
+
# rubocop:disable Metrics/AbcSize
|
92
93
|
def remove_where_method(corrector, node, where_node)
|
93
94
|
range = range_between(where_node.loc.selector.begin_pos, where_node.loc.end.end_pos)
|
94
95
|
if node.multiline? && !same_line?(node, where_node)
|
95
96
|
range = range_by_whole_lines(range, include_final_newline: true)
|
96
|
-
|
97
|
+
elsif where_node.receiver
|
97
98
|
corrector.remove(where_node.loc.dot)
|
99
|
+
else
|
100
|
+
corrector.remove(node.loc.dot)
|
98
101
|
end
|
99
102
|
|
100
103
|
corrector.remove(range)
|
101
104
|
end
|
105
|
+
# rubocop:enable Metrics/AbcSize
|
102
106
|
|
103
107
|
def same_line?(left_joins_node, where_node)
|
104
108
|
left_joins_node.loc.selector.line == where_node.loc.selector.line
|
@@ -32,8 +32,8 @@ module RuboCop
|
|
32
32
|
|
33
33
|
def_node_matcher :where_method_call?, <<~PATTERN
|
34
34
|
{
|
35
|
-
(
|
36
|
-
(
|
35
|
+
(call _ :where (array $str_type? $_ ?))
|
36
|
+
(call _ :where $str_type? $_ ?)
|
37
37
|
}
|
38
38
|
PATTERN
|
39
39
|
|
@@ -43,10 +43,10 @@ module RuboCop
|
|
43
43
|
|
44
44
|
range = offense_range(node)
|
45
45
|
|
46
|
-
|
47
|
-
return unless
|
46
|
+
column, value = extract_column_and_value(template_node, value_node)
|
47
|
+
return unless value
|
48
48
|
|
49
|
-
good_method = build_good_method(
|
49
|
+
good_method = build_good_method(node.loc.dot&.source, column, value)
|
50
50
|
message = format(MSG, good_method: good_method)
|
51
51
|
|
52
52
|
add_offense(range, message: message) do |corrector|
|
@@ -54,6 +54,7 @@ module RuboCop
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
57
|
+
alias on_csend on_send
|
57
58
|
|
58
59
|
NOT_EQ_ANONYMOUS_RE = /\A([\w.]+)\s+(?:!=|<>)\s+\?\z/.freeze # column != ?, column <> ?
|
59
60
|
NOT_IN_ANONYMOUS_RE = /\A([\w.]+)\s+NOT\s+IN\s+\(\?\)\z/i.freeze # column NOT IN (?)
|
@@ -67,13 +68,14 @@ module RuboCop
|
|
67
68
|
range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
|
68
69
|
end
|
69
70
|
|
71
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
70
72
|
def extract_column_and_value(template_node, value_node)
|
71
73
|
value =
|
72
74
|
case template_node.value
|
73
75
|
when NOT_EQ_ANONYMOUS_RE, NOT_IN_ANONYMOUS_RE
|
74
|
-
value_node
|
76
|
+
value_node&.source
|
75
77
|
when NOT_EQ_NAMED_RE, NOT_IN_NAMED_RE
|
76
|
-
return unless value_node
|
78
|
+
return unless value_node&.hash_type?
|
77
79
|
|
78
80
|
pair = value_node.pairs.find { |p| p.key.value.to_sym == Regexp.last_match(2).to_sym }
|
79
81
|
pair.value.source
|
@@ -83,16 +85,21 @@ module RuboCop
|
|
83
85
|
return
|
84
86
|
end
|
85
87
|
|
86
|
-
|
88
|
+
column_qualifier = Regexp.last_match(1)
|
89
|
+
return if column_qualifier.count('.') > 1
|
90
|
+
|
91
|
+
[column_qualifier, value]
|
87
92
|
end
|
93
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
88
94
|
|
89
|
-
def build_good_method(column, value)
|
95
|
+
def build_good_method(dot, column, value)
|
96
|
+
dot ||= '.'
|
90
97
|
if column.include?('.')
|
91
98
|
table, column = column.split('.')
|
92
99
|
|
93
|
-
"where
|
100
|
+
"where#{dot}not(#{table}: { #{column}: #{value} })"
|
94
101
|
else
|
95
|
-
"where
|
102
|
+
"where#{dot}not(#{column}: #{value})"
|
96
103
|
end
|
97
104
|
end
|
98
105
|
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# Identifies places where manually constructed SQL
|
7
|
+
# in `where` can be replaced with ranges.
|
8
|
+
#
|
9
|
+
# @safety
|
10
|
+
# This cop's autocorrection is unsafe because it can change the query
|
11
|
+
# by explicitly attaching the column to the wrong table.
|
12
|
+
# For example, `Booking.joins(:events).where('end_at < ?', Time.current)` will correctly
|
13
|
+
# implicitly attach the `end_at` column to the `events` table. But when autocorrected to
|
14
|
+
# `Booking.joins(:events).where(end_at: ...Time.current)`, it will now be incorrectly
|
15
|
+
# explicitly attached to the `bookings` table.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# # bad
|
19
|
+
# User.where('age >= ?', 18)
|
20
|
+
# User.where.not('age >= ?', 18)
|
21
|
+
# User.where('age < ?', 18)
|
22
|
+
# User.where('age >= ? AND age < ?', 18, 21)
|
23
|
+
# User.where('age >= :start', start: 18)
|
24
|
+
# User.where('users.age >= ?', 18)
|
25
|
+
#
|
26
|
+
# # good
|
27
|
+
# User.where(age: 18..)
|
28
|
+
# User.where.not(age: 18..)
|
29
|
+
# User.where(age: ...18)
|
30
|
+
# User.where(age: 18...21)
|
31
|
+
# User.where(users: { age: 18.. })
|
32
|
+
#
|
33
|
+
# # good
|
34
|
+
# # There are no beginless ranges in ruby.
|
35
|
+
# User.where('age > ?', 18)
|
36
|
+
#
|
37
|
+
class WhereRange < Base
|
38
|
+
include RangeHelp
|
39
|
+
extend AutoCorrector
|
40
|
+
extend TargetRubyVersion
|
41
|
+
extend TargetRailsVersion
|
42
|
+
|
43
|
+
MSG = 'Use `%<good_method>s` instead of manually constructing SQL.'
|
44
|
+
|
45
|
+
RESTRICT_ON_SEND = %i[where not].freeze
|
46
|
+
|
47
|
+
# column >= ?
|
48
|
+
GTEQ_ANONYMOUS_RE = /\A\s*([\w.]+)\s+>=\s+\?\s*\z/.freeze
|
49
|
+
# column <[=] ?
|
50
|
+
LTEQ_ANONYMOUS_RE = /\A\s*([\w.]+)\s+(<=?)\s+\?\s*\z/.freeze
|
51
|
+
# column >= ? AND column <[=] ?
|
52
|
+
RANGE_ANONYMOUS_RE = /\A\s*([\w.]+)\s+>=\s+\?\s+AND\s+\1\s+(<=?)\s+\?\s*\z/i.freeze
|
53
|
+
# column >= :value
|
54
|
+
GTEQ_NAMED_RE = /\A\s*([\w.]+)\s+>=\s+:(\w+)\s*\z/.freeze
|
55
|
+
# column <[=] :value
|
56
|
+
LTEQ_NAMED_RE = /\A\s*([\w.]+)\s+(<=?)\s+:(\w+)\s*\z/.freeze
|
57
|
+
# column >= :value1 AND column <[=] :value2
|
58
|
+
RANGE_NAMED_RE = /\A\s*([\w.]+)\s+>=\s+:(\w+)\s+AND\s+\1\s+(<=?)\s+:(\w+)\s*\z/i.freeze
|
59
|
+
|
60
|
+
minimum_target_ruby_version 2.6
|
61
|
+
minimum_target_rails_version 6.0
|
62
|
+
|
63
|
+
def_node_matcher :where_range_call?, <<~PATTERN
|
64
|
+
{
|
65
|
+
(call _ {:where :not} (array $str_type? $_ +))
|
66
|
+
(call _ {:where :not} $str_type? $_ +)
|
67
|
+
}
|
68
|
+
PATTERN
|
69
|
+
|
70
|
+
def on_send(node)
|
71
|
+
return if node.method?(:not) && !where_not?(node)
|
72
|
+
|
73
|
+
where_range_call?(node) do |template_node, values_node|
|
74
|
+
column, value = extract_column_and_value(template_node, values_node)
|
75
|
+
|
76
|
+
return unless column
|
77
|
+
|
78
|
+
range = offense_range(node)
|
79
|
+
good_method = build_good_method(node.method_name, column, value)
|
80
|
+
message = format(MSG, good_method: good_method)
|
81
|
+
|
82
|
+
add_offense(range, message: message) do |corrector|
|
83
|
+
corrector.replace(range, good_method)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def where_not?(node)
|
91
|
+
receiver = node.receiver
|
92
|
+
receiver&.send_type? && receiver.method?(:where)
|
93
|
+
end
|
94
|
+
|
95
|
+
# rubocop:disable Metrics
|
96
|
+
def extract_column_and_value(template_node, values_node)
|
97
|
+
case template_node.value
|
98
|
+
when GTEQ_ANONYMOUS_RE
|
99
|
+
lhs = values_node[0]
|
100
|
+
operator = '..'
|
101
|
+
when LTEQ_ANONYMOUS_RE
|
102
|
+
if target_ruby_version >= 2.7
|
103
|
+
operator = range_operator(Regexp.last_match(2))
|
104
|
+
rhs = values_node[0]
|
105
|
+
end
|
106
|
+
when RANGE_ANONYMOUS_RE
|
107
|
+
if values_node.size >= 2
|
108
|
+
lhs = values_node[0]
|
109
|
+
operator = range_operator(Regexp.last_match(2))
|
110
|
+
rhs = values_node[1]
|
111
|
+
end
|
112
|
+
when GTEQ_NAMED_RE
|
113
|
+
value_node = values_node[0]
|
114
|
+
|
115
|
+
if value_node.hash_type?
|
116
|
+
pair = find_pair(value_node, Regexp.last_match(2))
|
117
|
+
lhs = pair.value
|
118
|
+
operator = '..'
|
119
|
+
end
|
120
|
+
when LTEQ_NAMED_RE
|
121
|
+
value_node = values_node[0]
|
122
|
+
|
123
|
+
if value_node.hash_type?
|
124
|
+
pair = find_pair(value_node, Regexp.last_match(2))
|
125
|
+
if pair && target_ruby_version >= 2.7
|
126
|
+
operator = range_operator(Regexp.last_match(2))
|
127
|
+
rhs = pair.value
|
128
|
+
end
|
129
|
+
end
|
130
|
+
when RANGE_NAMED_RE
|
131
|
+
value_node = values_node[0]
|
132
|
+
|
133
|
+
if value_node.hash_type?
|
134
|
+
pair1 = find_pair(value_node, Regexp.last_match(2))
|
135
|
+
pair2 = find_pair(value_node, Regexp.last_match(4))
|
136
|
+
|
137
|
+
if pair1 && pair2
|
138
|
+
lhs = pair1.value
|
139
|
+
operator = range_operator(Regexp.last_match(3))
|
140
|
+
rhs = pair2.value
|
141
|
+
end
|
142
|
+
end
|
143
|
+
else
|
144
|
+
return
|
145
|
+
end
|
146
|
+
|
147
|
+
if lhs
|
148
|
+
lhs_source = parentheses_needed?(lhs) ? "(#{lhs.source})" : lhs.source
|
149
|
+
end
|
150
|
+
|
151
|
+
if rhs
|
152
|
+
rhs_source = parentheses_needed?(rhs) ? "(#{rhs.source})" : rhs.source
|
153
|
+
end
|
154
|
+
|
155
|
+
column_qualifier = Regexp.last_match(1)
|
156
|
+
return if column_qualifier.count('.') > 1
|
157
|
+
|
158
|
+
[column_qualifier, "#{lhs_source}#{operator}#{rhs_source}"] if operator
|
159
|
+
end
|
160
|
+
# rubocop:enable Metrics
|
161
|
+
|
162
|
+
def range_operator(comparison_operator)
|
163
|
+
comparison_operator == '<' ? '...' : '..'
|
164
|
+
end
|
165
|
+
|
166
|
+
def find_pair(hash_node, value)
|
167
|
+
hash_node.pairs.find { |pair| pair.key.value.to_sym == value.to_sym }
|
168
|
+
end
|
169
|
+
|
170
|
+
def offense_range(node)
|
171
|
+
range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
|
172
|
+
end
|
173
|
+
|
174
|
+
def build_good_method(method_name, column, value)
|
175
|
+
if column.include?('.')
|
176
|
+
table, column = column.split('.')
|
177
|
+
|
178
|
+
"#{method_name}(#{table}: { #{column}: #{value} })"
|
179
|
+
else
|
180
|
+
"#{method_name}(#{column}: #{value})"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def parentheses_needed?(node)
|
185
|
+
!parentheses_not_needed?(node)
|
186
|
+
end
|
187
|
+
|
188
|
+
def parentheses_not_needed?(node)
|
189
|
+
node.variable? ||
|
190
|
+
node.literal? ||
|
191
|
+
node.reference? ||
|
192
|
+
node.const_type? ||
|
193
|
+
node.begin_type? ||
|
194
|
+
parenthesized_call_node?(node)
|
195
|
+
end
|
196
|
+
|
197
|
+
def parenthesized_call_node?(node)
|
198
|
+
node.call_type? && (node.arguments.empty? || node.parenthesized_call?)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -3,12 +3,15 @@
|
|
3
3
|
require_relative 'mixin/active_record_helper'
|
4
4
|
require_relative 'mixin/active_record_migrations_helper'
|
5
5
|
require_relative 'mixin/class_send_node_helper'
|
6
|
+
require_relative 'mixin/database_type_resolvable'
|
6
7
|
require_relative 'mixin/enforce_superclass'
|
7
8
|
require_relative 'mixin/index_method'
|
8
9
|
require_relative 'mixin/migrations_helper'
|
10
|
+
require_relative 'mixin/routes_helper'
|
9
11
|
require_relative 'mixin/target_rails_version'
|
10
12
|
|
11
13
|
require_relative 'rails/action_controller_flash_before_render'
|
14
|
+
require_relative 'rails/strong_parameters_expect'
|
12
15
|
require_relative 'rails/action_controller_test_case'
|
13
16
|
require_relative 'rails/action_filter'
|
14
17
|
require_relative 'rails/action_order'
|
@@ -32,6 +35,7 @@ require_relative 'rails/bulk_change_table'
|
|
32
35
|
require_relative 'rails/compact_blank'
|
33
36
|
require_relative 'rails/content_tag'
|
34
37
|
require_relative 'rails/create_table_with_timestamps'
|
38
|
+
require_relative 'rails/dangerous_column_names'
|
35
39
|
require_relative 'rails/date'
|
36
40
|
require_relative 'rails/default_scope'
|
37
41
|
require_relative 'rails/delegate'
|
@@ -44,7 +48,9 @@ require_relative 'rails/duration_arithmetic'
|
|
44
48
|
require_relative 'rails/dynamic_find_by'
|
45
49
|
require_relative 'rails/eager_evaluation_log_message'
|
46
50
|
require_relative 'rails/enum_hash'
|
51
|
+
require_relative 'rails/enum_syntax'
|
47
52
|
require_relative 'rails/enum_uniqueness'
|
53
|
+
require_relative 'rails/env_local'
|
48
54
|
require_relative 'rails/environment_comparison'
|
49
55
|
require_relative 'rails/environment_variable_access'
|
50
56
|
require_relative 'rails/exit'
|
@@ -73,6 +79,7 @@ require_relative 'rails/link_to_blank'
|
|
73
79
|
require_relative 'rails/mailer_name'
|
74
80
|
require_relative 'rails/match_route'
|
75
81
|
require_relative 'rails/migration_class_name'
|
82
|
+
require_relative 'rails/multiple_route_paths'
|
76
83
|
require_relative 'rails/negate_include'
|
77
84
|
require_relative 'rails/not_null_column'
|
78
85
|
require_relative 'rails/order_by_id'
|
@@ -87,6 +94,7 @@ require_relative 'rails/presence'
|
|
87
94
|
require_relative 'rails/present'
|
88
95
|
require_relative 'rails/rake_environment'
|
89
96
|
require_relative 'rails/read_write_attribute'
|
97
|
+
require_relative 'rails/redundant_active_record_all_method'
|
90
98
|
require_relative 'rails/redundant_allow_nil'
|
91
99
|
require_relative 'rails/redundant_foreign_key'
|
92
100
|
require_relative 'rails/redundant_presence_validation_on_belongs_to'
|
@@ -110,6 +118,7 @@ require_relative 'rails/safe_navigation_with_blank'
|
|
110
118
|
require_relative 'rails/save_bang'
|
111
119
|
require_relative 'rails/schema_comment'
|
112
120
|
require_relative 'rails/scope_args'
|
121
|
+
require_relative 'rails/select_map'
|
113
122
|
require_relative 'rails/short_i18n'
|
114
123
|
require_relative 'rails/skips_model_validations'
|
115
124
|
require_relative 'rails/squished_sql_heredocs'
|
@@ -126,9 +135,11 @@ require_relative 'rails/uniq_before_pluck'
|
|
126
135
|
require_relative 'rails/unique_validation_without_index'
|
127
136
|
require_relative 'rails/unknown_env'
|
128
137
|
require_relative 'rails/unused_ignored_columns'
|
138
|
+
require_relative 'rails/unused_render_content'
|
129
139
|
require_relative 'rails/validation'
|
130
140
|
require_relative 'rails/where_equals'
|
131
141
|
require_relative 'rails/where_exists'
|
132
142
|
require_relative 'rails/where_missing'
|
133
143
|
require_relative 'rails/where_not'
|
134
144
|
require_relative 'rails/where_not_with_multiple_conditions'
|
145
|
+
require_relative 'rails/where_range'
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Rails
|
5
|
+
# This module allows cops to detect and ignore files that have already been migrated
|
6
|
+
# by leveraging the `AllCops: MigratedSchemaVersion` configuration.
|
7
|
+
#
|
8
|
+
# [source,yaml]
|
9
|
+
# -----
|
10
|
+
# AllCops:
|
11
|
+
# MigratedSchemaVersion: '20241225000000'
|
12
|
+
# -----
|
13
|
+
#
|
14
|
+
# When applied to cops, it overrides the `add_global_offense` and `add_offense` methods,
|
15
|
+
# ensuring that cops skip processing if the file is determined to be a migrated file
|
16
|
+
# based on the schema version.
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
module MigrationFileSkippable
|
20
|
+
def add_global_offense(message = nil, severity: nil)
|
21
|
+
return if already_migrated_file?
|
22
|
+
|
23
|
+
super if method(__method__).super_method
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_offense(node_or_range, message: nil, severity: nil, &block)
|
27
|
+
return if already_migrated_file?
|
28
|
+
|
29
|
+
super if method(__method__).super_method
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.apply_to_cops!
|
33
|
+
RuboCop::Cop::Registry.all.each { |cop| cop.prepend(MigrationFileSkippable) }
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def already_migrated_file?
|
39
|
+
return false unless migrated_schema_version
|
40
|
+
|
41
|
+
match_data = File.basename(processed_source.file_path).match(/(?<timestamp>\d{14})/)
|
42
|
+
schema_version = match_data['timestamp'] if match_data
|
43
|
+
|
44
|
+
return false unless schema_version
|
45
|
+
|
46
|
+
schema_version <= migrated_schema_version.to_s # Ignore applied migration files.
|
47
|
+
end
|
48
|
+
|
49
|
+
def migrated_schema_version
|
50
|
+
@migrated_schema_version ||= config.for_all_cops.fetch('MigratedSchemaVersion', nil)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'lint_roller'
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Rails
|
7
|
+
# A plugin that integrates RuboCop Rails with RuboCop's plugin system.
|
8
|
+
class Plugin < LintRoller::Plugin
|
9
|
+
def about
|
10
|
+
LintRoller::About.new(
|
11
|
+
name: 'rubocop-rails',
|
12
|
+
version: Version::STRING,
|
13
|
+
homepage: 'https://github.com/rubocop/rubocop-rails',
|
14
|
+
description: 'A RuboCop extension focused on enforcing Rails best practices and coding conventions.'
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def supported?(context)
|
19
|
+
context.engine == :rubocop
|
20
|
+
end
|
21
|
+
|
22
|
+
def rules(_context)
|
23
|
+
project_root = Pathname.new(__dir__).join('../../..')
|
24
|
+
|
25
|
+
ConfigObsoletion.files << project_root.join('config', 'obsoletion.yml')
|
26
|
+
|
27
|
+
# FIXME: This is a dirty hack relying on a private constant to prevent
|
28
|
+
# "Warning: AllCops does not support TargetRailsVersion parameter".
|
29
|
+
# It should be updated to a better design in the future.
|
30
|
+
without_warnings do
|
31
|
+
ConfigValidator.const_set(:COMMON_PARAMS, ConfigValidator::COMMON_PARAMS.dup << 'TargetRailsVersion')
|
32
|
+
end
|
33
|
+
|
34
|
+
LintRoller::Rules.new(type: :path, config_format: :rubocop, value: project_root.join('config', 'default.yml'))
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def without_warnings
|
40
|
+
original_verbose = $VERBOSE
|
41
|
+
$VERBOSE = nil
|
42
|
+
yield
|
43
|
+
ensure
|
44
|
+
$VERBOSE = original_verbose
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -30,6 +30,7 @@ module RuboCop
|
|
30
30
|
|
31
31
|
def build!(ast)
|
32
32
|
raise "Unexpected type: #{ast.type}" unless ast.block_type?
|
33
|
+
return unless ast.body
|
33
34
|
|
34
35
|
each_table(ast) do |table_def|
|
35
36
|
next unless table_def.method?(:create_table)
|
@@ -83,21 +84,21 @@ module RuboCop
|
|
83
84
|
private
|
84
85
|
|
85
86
|
def build_columns(node)
|
86
|
-
each_content(node).
|
87
|
+
each_content(node).filter_map do |child|
|
87
88
|
next unless child&.send_type?
|
88
89
|
next if child.method?(:index)
|
89
90
|
|
90
91
|
Column.new(child)
|
91
|
-
end
|
92
|
+
end
|
92
93
|
end
|
93
94
|
|
94
95
|
def build_indices(node)
|
95
|
-
each_content(node).
|
96
|
+
each_content(node).filter_map do |child|
|
96
97
|
next unless child&.send_type?
|
97
98
|
next unless child.method?(:index)
|
98
99
|
|
99
100
|
Index.new(child)
|
100
|
-
end
|
101
|
+
end
|
101
102
|
end
|
102
103
|
|
103
104
|
def each_content(node, &block)
|
@@ -127,7 +128,7 @@ module RuboCop
|
|
127
128
|
private
|
128
129
|
|
129
130
|
def analyze_keywords!(node)
|
130
|
-
pairs = node.
|
131
|
+
pairs = node.last_argument
|
131
132
|
return unless pairs.hash_type?
|
132
133
|
|
133
134
|
pairs.each_pair do |k, v|
|
@@ -158,7 +159,7 @@ module RuboCop
|
|
158
159
|
end
|
159
160
|
|
160
161
|
def analyze_keywords!(node)
|
161
|
-
pairs = node.
|
162
|
+
pairs = node.last_argument
|
162
163
|
return unless pairs.hash_type?
|
163
164
|
|
164
165
|
pairs.each_pair do |k, v|
|
@@ -177,7 +178,7 @@ module RuboCop
|
|
177
178
|
attr_reader :table_name
|
178
179
|
|
179
180
|
def initialize(node)
|
180
|
-
super
|
181
|
+
super
|
181
182
|
|
182
183
|
@table_name = node.first_argument.value
|
183
184
|
@columns, @expression = build_columns_or_expr(node.arguments[1])
|
@@ -12,10 +12,10 @@ module RuboCop
|
|
12
12
|
# So a cop that uses the loader should handle `nil` properly.
|
13
13
|
#
|
14
14
|
# @return [Schema, nil]
|
15
|
-
def load(target_ruby_version)
|
15
|
+
def load(target_ruby_version, parser_engine)
|
16
16
|
return @load if defined?(@load)
|
17
17
|
|
18
|
-
@load = load!(target_ruby_version)
|
18
|
+
@load = load!(target_ruby_version, parser_engine)
|
19
19
|
end
|
20
20
|
|
21
21
|
def reset!
|
@@ -38,23 +38,13 @@ module RuboCop
|
|
38
38
|
|
39
39
|
private
|
40
40
|
|
41
|
-
def load!(target_ruby_version)
|
41
|
+
def load!(target_ruby_version, parser_engine)
|
42
42
|
path = db_schema_path
|
43
43
|
return unless path
|
44
44
|
|
45
|
-
ast =
|
46
|
-
Schema.new(ast)
|
47
|
-
end
|
48
|
-
|
49
|
-
def parse(path, target_ruby_version)
|
50
|
-
klass_name = :"Ruby#{target_ruby_version.to_s.sub('.', '')}"
|
51
|
-
klass = ::Parser.const_get(klass_name)
|
52
|
-
parser = klass.new(RuboCop::AST::Builder.new)
|
53
|
-
|
54
|
-
buffer = Parser::Source::Buffer.new(path, 1)
|
55
|
-
buffer.source = path.read
|
45
|
+
ast = RuboCop::ProcessedSource.new(File.read(path), target_ruby_version, path, parser_engine: parser_engine).ast
|
56
46
|
|
57
|
-
|
47
|
+
Schema.new(ast) if ast
|
58
48
|
end
|
59
49
|
end
|
60
50
|
end
|
data/lib/rubocop/rails.rb
CHANGED
@@ -1,14 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module RuboCop
|
4
|
-
# RuboCop Rails project namespace
|
4
|
+
# RuboCop Rails project namespace.
|
5
5
|
module Rails
|
6
|
-
PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
|
7
|
-
CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
|
8
|
-
CONFIG = YAML.safe_load(CONFIG_DEFAULT.read, permitted_classes: [Regexp, Symbol]).freeze
|
9
|
-
|
10
|
-
private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
|
11
|
-
|
12
|
-
::RuboCop::ConfigObsoletion.files << PROJECT_ROOT.join('config', 'obsoletion.yml')
|
13
6
|
end
|
14
7
|
end
|
data/lib/rubocop-rails.rb
CHANGED
@@ -7,16 +7,24 @@ require 'active_support/core_ext/object/blank'
|
|
7
7
|
|
8
8
|
require_relative 'rubocop/rails'
|
9
9
|
require_relative 'rubocop/rails/version'
|
10
|
-
require_relative 'rubocop/rails/inject'
|
11
10
|
require_relative 'rubocop/rails/schema_loader'
|
12
11
|
require_relative 'rubocop/rails/schema_loader/schema'
|
13
|
-
|
14
|
-
RuboCop::Rails::Inject.defaults!
|
15
|
-
|
12
|
+
require_relative 'rubocop/rails/plugin'
|
16
13
|
require_relative 'rubocop/cop/rails_cops'
|
17
14
|
|
15
|
+
require_relative 'rubocop/rails/migration_file_skippable'
|
16
|
+
RuboCop::Rails::MigrationFileSkippable.apply_to_cops!
|
17
|
+
|
18
18
|
RuboCop::Cop::Style::HashExcept.minimum_target_ruby_version(2.0)
|
19
19
|
|
20
|
+
RuboCop::Cop::Style::InverseMethods.singleton_class.prepend(
|
21
|
+
Module.new do
|
22
|
+
def autocorrect_incompatible_with
|
23
|
+
super.push(RuboCop::Cop::Rails::NegateInclude)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
)
|
27
|
+
|
20
28
|
RuboCop::Cop::Style::MethodCallWithArgsParentheses.singleton_class.prepend(
|
21
29
|
Module.new do
|
22
30
|
def autocorrect_incompatible_with
|