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,6 +36,10 @@ module RuboCop
|
|
36
36
|
# if: -> { trusted_origin? && action_name != "admin" }
|
37
37
|
# end
|
38
38
|
class IgnoredSkipActionFilterOption < Base
|
39
|
+
extend AutoCorrector
|
40
|
+
|
41
|
+
include RangeHelp
|
42
|
+
|
39
43
|
MSG = <<~MSG.chomp.freeze
|
40
44
|
`%<ignore>s` option will be ignored when `%<prefer>s` and `%<ignore>s` are used together.
|
41
45
|
MSG
|
@@ -48,7 +52,7 @@ module RuboCop
|
|
48
52
|
(send
|
49
53
|
nil?
|
50
54
|
{#{FILTERS.join(' ')}}
|
51
|
-
|
55
|
+
...
|
52
56
|
$_)
|
53
57
|
PATTERN
|
54
58
|
|
@@ -60,9 +64,13 @@ module RuboCop
|
|
60
64
|
options = options_hash(options)
|
61
65
|
|
62
66
|
if if_and_only?(options)
|
63
|
-
add_offense(options[:if], message: format(MSG, prefer: :only, ignore: :if))
|
67
|
+
add_offense(options[:if], message: format(MSG, prefer: :only, ignore: :if)) do |corrector|
|
68
|
+
remove_node_with_left_space_and_comma(corrector, options[:if])
|
69
|
+
end
|
64
70
|
elsif if_and_except?(options)
|
65
|
-
add_offense(options[:except], message: format(MSG, prefer: :if, ignore: :except))
|
71
|
+
add_offense(options[:except], message: format(MSG, prefer: :if, ignore: :except)) do |corrector|
|
72
|
+
remove_node_with_left_space_and_comma(corrector, options[:except])
|
73
|
+
end
|
66
74
|
end
|
67
75
|
end
|
68
76
|
|
@@ -81,6 +89,18 @@ module RuboCop
|
|
81
89
|
def if_and_except?(options)
|
82
90
|
options.key?(:if) && options.key?(:except)
|
83
91
|
end
|
92
|
+
|
93
|
+
def remove_node_with_left_space_and_comma(corrector, node)
|
94
|
+
corrector.remove(
|
95
|
+
range_with_surrounding_comma(
|
96
|
+
range_with_surrounding_space(
|
97
|
+
node.source_range,
|
98
|
+
side: :left
|
99
|
+
),
|
100
|
+
:left
|
101
|
+
)
|
102
|
+
)
|
103
|
+
end
|
84
104
|
end
|
85
105
|
end
|
86
106
|
end
|
@@ -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
|
@@ -29,10 +29,11 @@ module RuboCop
|
|
29
29
|
def on_send(node)
|
30
30
|
return unless node.arguments.empty?
|
31
31
|
return unless (receiver = node.receiver)
|
32
|
-
return
|
32
|
+
return unless receiver.type?(:str, :array)
|
33
33
|
|
34
34
|
add_offense(node.loc.selector)
|
35
35
|
end
|
36
|
+
alias on_csend on_send
|
36
37
|
end
|
37
38
|
end
|
38
39
|
end
|
@@ -222,7 +222,7 @@ module RuboCop
|
|
222
222
|
|
223
223
|
def with_options_arguments(recv, node)
|
224
224
|
blocks = node.each_ancestor(:block).select do |block|
|
225
|
-
block.send_node.command?(:with_options) && same_context_in_with_options?(block.
|
225
|
+
block.send_node.command?(:with_options) && same_context_in_with_options?(block.first_argument, recv)
|
226
226
|
end
|
227
227
|
blocks.flat_map { |n| n.send_node.arguments }
|
228
228
|
end
|
@@ -115,6 +115,10 @@ module RuboCop
|
|
115
115
|
$_)))
|
116
116
|
PATTERN
|
117
117
|
|
118
|
+
def_node_matcher :delegated_methods, <<~PATTERN
|
119
|
+
(send nil? :delegate (sym $_)+ (hash <(pair (sym :to) _) ...>))
|
120
|
+
PATTERN
|
121
|
+
|
118
122
|
def on_send(node)
|
119
123
|
methods_node = only_or_except_filter_methods(node)
|
120
124
|
return unless methods_node
|
@@ -122,25 +126,30 @@ module RuboCop
|
|
122
126
|
parent = node.each_ancestor(:class, :module).first
|
123
127
|
return unless parent
|
124
128
|
|
129
|
+
# NOTE: a `:begin` node may not exist if the class/module consists of a single statement
|
125
130
|
block = parent.each_child_node(:begin).first
|
126
|
-
return unless block
|
127
|
-
|
128
131
|
defined_action_methods = defined_action_methods(block)
|
129
132
|
|
130
|
-
|
131
|
-
|
132
|
-
end
|
133
|
+
unmatched_methods = array_values(methods_node) - defined_action_methods
|
134
|
+
return if unmatched_methods.empty?
|
133
135
|
|
134
|
-
message = message(
|
135
|
-
add_offense(node, message: message)
|
136
|
+
message = message(unmatched_methods, parent)
|
137
|
+
add_offense(node, message: message)
|
136
138
|
end
|
137
139
|
|
138
140
|
private
|
139
141
|
|
140
142
|
def defined_action_methods(block)
|
143
|
+
return [] unless block
|
144
|
+
|
141
145
|
defined_methods = block.each_child_node(:def).map(&:method_name)
|
146
|
+
defined_methods + delegated_action_methods(block) + aliased_action_methods(block, defined_methods)
|
147
|
+
end
|
142
148
|
|
143
|
-
|
149
|
+
def delegated_action_methods(node)
|
150
|
+
node.each_child_node(:send).flat_map do |child_node|
|
151
|
+
delegated_methods(child_node) || []
|
152
|
+
end
|
144
153
|
end
|
145
154
|
|
146
155
|
def aliased_action_methods(node, defined_methods)
|
@@ -176,14 +185,14 @@ module RuboCop
|
|
176
185
|
when :sym
|
177
186
|
[node.value]
|
178
187
|
when :array
|
179
|
-
node.values.
|
188
|
+
node.values.filter_map do |v|
|
180
189
|
case v.type
|
181
190
|
when :str
|
182
191
|
v.str_content.to_sym
|
183
192
|
when :sym
|
184
193
|
v.value
|
185
194
|
end
|
186
|
-
end
|
195
|
+
end
|
187
196
|
else
|
188
197
|
[]
|
189
198
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Rails
|
6
|
-
# Checks for calls to `link_to` that contain a
|
6
|
+
# Checks for calls to `link_to`, `link_to_if`, and `link_to_unless` methods that contain a
|
7
7
|
# `target: '_blank'` but no `rel: 'noopener'`. This can be a security
|
8
8
|
# risk as the loaded page will have control over the previous page
|
9
9
|
# and could change its location for phishing purposes.
|
@@ -24,7 +24,7 @@ module RuboCop
|
|
24
24
|
extend AutoCorrector
|
25
25
|
|
26
26
|
MSG = 'Specify a `:rel` option containing noopener.'
|
27
|
-
RESTRICT_ON_SEND = %i[link_to].freeze
|
27
|
+
RESTRICT_ON_SEND = %i[link_to link_to_if link_to_unless].freeze
|
28
28
|
|
29
29
|
def_node_matcher :blank_target?, <<~PATTERN
|
30
30
|
(pair {(sym :target) (str "target")} {(str "_blank") (sym :_blank)})
|
@@ -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
|
@@ -3,23 +3,52 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Rails
|
6
|
-
# Checks for add_column
|
7
|
-
#
|
6
|
+
# Checks for add_column calls with a NOT NULL constraint without a default
|
7
|
+
# value.
|
8
|
+
#
|
9
|
+
# This cop only applies when adding a column to an existing table, since
|
10
|
+
# existing records will not have a value for the new column. New tables
|
11
|
+
# can freely use NOT NULL columns without defaults, since there are no
|
12
|
+
# records that could violate the constraint.
|
13
|
+
#
|
14
|
+
# If you need to add a NOT NULL column to an existing table, you must add
|
15
|
+
# it as nullable first, back-fill the data, and then use
|
16
|
+
# `change_column_null`. Alternatively, you could add the column with a
|
17
|
+
# default first to have the database automatically backfill existing rows,
|
18
|
+
# and then use `change_column_default` to remove the default.
|
19
|
+
#
|
20
|
+
# `TEXT` cannot have a default value in MySQL.
|
21
|
+
# The cop will automatically detect an adapter from `development`
|
22
|
+
# environment in `config/database.yml` or the environment variable
|
23
|
+
# `DATABASE_URL` when the `Database` option is not set. If the database
|
24
|
+
# is MySQL, this cop ignores offenses for `TEXT` columns.
|
8
25
|
#
|
9
26
|
# @example
|
10
27
|
# # bad
|
11
28
|
# add_column :users, :name, :string, null: false
|
12
29
|
# add_reference :products, :category, null: false
|
30
|
+
# change_table :users do |t|
|
31
|
+
# t.string :name, null: false
|
32
|
+
# end
|
13
33
|
#
|
14
34
|
# # good
|
15
35
|
# add_column :users, :name, :string, null: true
|
16
36
|
# add_column :users, :name, :string, null: false, default: ''
|
37
|
+
# change_table :users do |t|
|
38
|
+
# t.string :name, null: false, default: ''
|
39
|
+
# end
|
17
40
|
# add_reference :products, :category
|
18
|
-
#
|
41
|
+
# change_column_null :products, :category_id, false
|
19
42
|
class NotNullColumn < Base
|
43
|
+
include DatabaseTypeResolvable
|
44
|
+
include MigrationsHelper
|
45
|
+
|
20
46
|
MSG = 'Do not add a NOT NULL column without a default value.'
|
21
47
|
RESTRICT_ON_SEND = %i[add_column add_reference].freeze
|
22
48
|
|
49
|
+
VIRTUAL_TYPE_VALUES = [:virtual, 'virtual'].freeze
|
50
|
+
TEXT_TYPE_VALUES = [:text, 'text'].freeze
|
51
|
+
|
23
52
|
def_node_matcher :add_not_null_column?, <<~PATTERN
|
24
53
|
(send nil? :add_column _ _ $_ (hash $...))
|
25
54
|
PATTERN
|
@@ -28,6 +57,22 @@ module RuboCop
|
|
28
57
|
(send nil? :add_reference _ _ (hash $...))
|
29
58
|
PATTERN
|
30
59
|
|
60
|
+
def_node_matcher :change_table?, <<~PATTERN
|
61
|
+
(block (send nil? :change_table ...) (args (arg $_)) _)
|
62
|
+
PATTERN
|
63
|
+
|
64
|
+
def_node_matcher :add_not_null_column_in_change_table?, <<~PATTERN
|
65
|
+
(send (lvar $_) :column _ $_ (hash $...))
|
66
|
+
PATTERN
|
67
|
+
|
68
|
+
def_node_matcher :add_not_null_column_via_shortcut_in_change_table?, <<~PATTERN
|
69
|
+
(send (lvar $_) $_ _ (hash $...))
|
70
|
+
PATTERN
|
71
|
+
|
72
|
+
def_node_matcher :add_not_null_reference_in_change_table?, <<~PATTERN
|
73
|
+
(send (lvar $_) :add_reference _ _ (hash $...))
|
74
|
+
PATTERN
|
75
|
+
|
31
76
|
def_node_matcher :null_false?, <<~PATTERN
|
32
77
|
(pair (sym :null) (false))
|
33
78
|
PATTERN
|
@@ -41,13 +86,25 @@ module RuboCop
|
|
41
86
|
check_add_reference(node)
|
42
87
|
end
|
43
88
|
|
89
|
+
def on_block(node)
|
90
|
+
check_change_table(node)
|
91
|
+
end
|
92
|
+
alias on_numblock on_block
|
93
|
+
|
44
94
|
private
|
45
95
|
|
96
|
+
def check_column(type, pairs)
|
97
|
+
if type.respond_to?(:value)
|
98
|
+
return if VIRTUAL_TYPE_VALUES.include?(type.value)
|
99
|
+
return if TEXT_TYPE_VALUES.include?(type.value) && database == MYSQL
|
100
|
+
end
|
101
|
+
|
102
|
+
check_pairs(pairs)
|
103
|
+
end
|
104
|
+
|
46
105
|
def check_add_column(node)
|
47
106
|
add_not_null_column?(node) do |type, pairs|
|
48
|
-
|
49
|
-
|
50
|
-
check_pairs(pairs)
|
107
|
+
check_column(type, pairs)
|
51
108
|
end
|
52
109
|
end
|
53
110
|
|
@@ -57,6 +114,43 @@ module RuboCop
|
|
57
114
|
end
|
58
115
|
end
|
59
116
|
|
117
|
+
def check_add_column_in_change_table(node, table)
|
118
|
+
add_not_null_column_in_change_table?(node) do |receiver, type, pairs|
|
119
|
+
next unless receiver == table
|
120
|
+
|
121
|
+
check_column(type, pairs)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def check_add_column_via_shortcut_in_change_table(node, table)
|
126
|
+
add_not_null_column_via_shortcut_in_change_table?(node) do |receiver, type, pairs|
|
127
|
+
next unless receiver == table
|
128
|
+
|
129
|
+
check_column(type, pairs)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def check_add_reference_in_change_table(node, table)
|
134
|
+
add_not_null_reference_in_change_table?(node) do |receiver, pairs|
|
135
|
+
next unless receiver == table
|
136
|
+
|
137
|
+
check_pairs(pairs)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def check_change_table(node)
|
142
|
+
change_table?(node) do |table|
|
143
|
+
next unless node.body
|
144
|
+
|
145
|
+
children = node.body.begin_type? ? node.body.children : [node.body]
|
146
|
+
children.each do |child|
|
147
|
+
check_add_column_in_change_table(child, table)
|
148
|
+
check_add_column_via_shortcut_in_change_table(child, table)
|
149
|
+
check_add_reference_in_change_table(child, table)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
60
154
|
def check_pairs(pairs)
|
61
155
|
return if pairs.any? { |pair| default_option?(pair) }
|
62
156
|
|
@@ -23,6 +23,7 @@ module RuboCop
|
|
23
23
|
|
24
24
|
MSG = "Do not write to stdout. Use Rails's logger if you want to log."
|
25
25
|
RESTRICT_ON_SEND = %i[ap p pp pretty_print print puts binwrite syswrite write write_nonblock].freeze
|
26
|
+
ALLOWED_TYPES = %i[send csend block numblock].freeze
|
26
27
|
|
27
28
|
def_node_matcher :output?, <<~PATTERN
|
28
29
|
(send nil? {:ap :p :pp :pretty_print :print :puts} ...)
|
@@ -39,8 +40,8 @@ module RuboCop
|
|
39
40
|
PATTERN
|
40
41
|
|
41
42
|
def on_send(node)
|
42
|
-
return if node.parent&.
|
43
|
-
return
|
43
|
+
return if ALLOWED_TYPES.include?(node.parent&.type)
|
44
|
+
return if !output?(node) && !io_output?(node)
|
44
45
|
|
45
46
|
range = offense_range(node)
|
46
47
|
|
@@ -9,6 +9,10 @@ module RuboCop
|
|
9
9
|
# `pick` avoids. When called on an Active Record relation, `pick` adds a
|
10
10
|
# limit to the query so that only one value is fetched from the database.
|
11
11
|
#
|
12
|
+
# Note that when `pick` is added to a relation with an existing limit, it
|
13
|
+
# causes a subquery to be added. In most cases this is undesirable, and
|
14
|
+
# care should be taken while resolving this violation.
|
15
|
+
#
|
12
16
|
# @safety
|
13
17
|
# This cop is unsafe because `pluck` is defined on both `ActiveRecord::Relation` and `Enumerable`,
|
14
18
|
# whereas `pick` is only defined on `ActiveRecord::Relation` in Rails 6.0. This was addressed
|
@@ -28,13 +32,13 @@ module RuboCop
|
|
28
32
|
extend AutoCorrector
|
29
33
|
extend TargetRailsVersion
|
30
34
|
|
31
|
-
MSG = 'Prefer `pick(%<args>s)` over
|
35
|
+
MSG = 'Prefer `pick(%<args>s)` over `%<current>s`.'
|
32
36
|
RESTRICT_ON_SEND = %i[first].freeze
|
33
37
|
|
34
38
|
minimum_target_rails_version 6.0
|
35
39
|
|
36
40
|
def_node_matcher :pick_candidate?, <<~PATTERN
|
37
|
-
(
|
41
|
+
(call (call _ :pluck ...) :first)
|
38
42
|
PATTERN
|
39
43
|
|
40
44
|
def on_send(node)
|
@@ -44,7 +48,7 @@ module RuboCop
|
|
44
48
|
node_selector = node.loc.selector
|
45
49
|
range = receiver_selector.join(node_selector)
|
46
50
|
|
47
|
-
add_offense(range, message: message(receiver)) do |corrector|
|
51
|
+
add_offense(range, message: message(receiver, range)) do |corrector|
|
48
52
|
first_range = receiver.source_range.end.join(node_selector)
|
49
53
|
|
50
54
|
corrector.remove(first_range)
|
@@ -52,11 +56,12 @@ module RuboCop
|
|
52
56
|
end
|
53
57
|
end
|
54
58
|
end
|
59
|
+
alias on_csend on_send
|
55
60
|
|
56
61
|
private
|
57
62
|
|
58
|
-
def message(receiver)
|
59
|
-
format(MSG, args: receiver.arguments.map(&:source).join(', '))
|
63
|
+
def message(receiver, current)
|
64
|
+
format(MSG, args: receiver.arguments.map(&:source).join(', '), current: current.source)
|
60
65
|
end
|
61
66
|
end
|
62
67
|
end
|
@@ -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
|
#
|
@@ -38,10 +56,12 @@ module RuboCop
|
|
38
56
|
minimum_target_rails_version 5.0
|
39
57
|
|
40
58
|
def_node_matcher :pluck_candidate?, <<~PATTERN
|
41
|
-
(
|
59
|
+
(any_block (call _ {:map :collect}) $_argument (send lvar :[] $_key))
|
42
60
|
PATTERN
|
43
61
|
|
44
62
|
def on_block(node)
|
63
|
+
return if node.each_ancestor(:any_block).any?
|
64
|
+
|
45
65
|
pluck_candidate?(node) do |argument, key|
|
46
66
|
next if key.regexp_type? || !use_one_block_argument?(argument)
|
47
67
|
|
@@ -34,7 +34,7 @@ module RuboCop
|
|
34
34
|
RESTRICT_ON_SEND = %i[pluck].freeze
|
35
35
|
|
36
36
|
def_node_matcher :pluck_id_call?, <<~PATTERN
|
37
|
-
(
|
37
|
+
(call _ :pluck {(sym :id) (send nil? :primary_key)})
|
38
38
|
PATTERN
|
39
39
|
|
40
40
|
def on_send(node)
|
@@ -47,6 +47,7 @@ module RuboCop
|
|
47
47
|
corrector.replace(offense_range(node), 'ids')
|
48
48
|
end
|
49
49
|
end
|
50
|
+
alias on_csend on_send
|
50
51
|
|
51
52
|
private
|
52
53
|
|