rubocop-rails 2.20.2 → 2.24.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +9 -7
- data/config/default.yml +72 -10
- data/lib/rubocop/cop/mixin/active_record_helper.rb +15 -3
- data/lib/rubocop/cop/mixin/database_type_resolvable.rb +66 -0
- data/lib/rubocop/cop/mixin/index_method.rb +2 -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/active_record_aliases.rb +2 -2
- 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/after_commit_override.rb +1 -1
- data/lib/rubocop/cop/rails/bulk_change_table.rb +8 -41
- data/lib/rubocop/cop/rails/content_tag.rb +1 -1
- data/lib/rubocop/cop/rails/dangerous_column_names.rb +446 -0
- data/lib/rubocop/cop/rails/date.rb +1 -1
- data/lib/rubocop/cop/rails/duplicate_association.rb +69 -12
- 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/env_local.rb +46 -0
- data/lib/rubocop/cop/rails/expanded_date_range.rb +1 -1
- data/lib/rubocop/cop/rails/file_path.rb +9 -6
- 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/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_status.rb +4 -3
- data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +63 -13
- data/lib/rubocop/cop/rails/inquiry.rb +1 -0
- data/lib/rubocop/cop/rails/inverse_of.rb +1 -1
- data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +7 -8
- data/lib/rubocop/cop/rails/not_null_column.rb +13 -3
- data/lib/rubocop/cop/rails/output.rb +3 -2
- data/lib/rubocop/cop/rails/pick.rb +6 -5
- data/lib/rubocop/cop/rails/pluck.rb +1 -1
- data/lib/rubocop/cop/rails/pluck_id.rb +2 -1
- data/lib/rubocop/cop/rails/pluck_in_where.rb +18 -5
- data/lib/rubocop/cop/rails/rake_environment.rb +22 -6
- data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +219 -0
- data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +7 -0
- data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +1 -1
- data/lib/rubocop/cop/rails/response_parsed_body.rb +52 -10
- data/lib/rubocop/cop/rails/reversible_migration.rb +4 -4
- data/lib/rubocop/cop/rails/root_pathname_methods.rb +38 -4
- data/lib/rubocop/cop/rails/save_bang.rb +15 -8
- data/lib/rubocop/cop/rails/schema_comment.rb +16 -10
- data/lib/rubocop/cop/rails/select_map.rb +78 -0
- data/lib/rubocop/cop/rails/time_zone.rb +13 -5
- data/lib/rubocop/cop/rails/transaction_exit_statement.rb +29 -10
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +12 -4
- data/lib/rubocop/cop/rails/unique_validation_without_index.rb +2 -2
- data/lib/rubocop/cop/rails/unknown_env.rb +5 -1
- data/lib/rubocop/cop/rails/unused_render_content.rb +67 -0
- data/lib/rubocop/cop/rails/validation.rb +2 -2
- data/lib/rubocop/cop/rails/where_equals.rb +3 -2
- 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 +8 -6
- data/lib/rubocop/cop/rails_cops.rb +6 -0
- data/lib/rubocop/rails/schema_loader/schema.rb +3 -2
- data/lib/rubocop/rails/schema_loader.rb +5 -15
- data/lib/rubocop/rails/version.rb +1 -1
- data/lib/rubocop-rails.rb +8 -0
- metadata +30 -4
@@ -0,0 +1,219 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
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
|
+
# Detect redundant `all` used as a receiver for Active Record query methods.
|
36
|
+
#
|
37
|
+
# For the methods `delete_all` and `destroy_all`, this cop will only check cases where the receiver is a model.
|
38
|
+
# It will ignore cases where the receiver is an association (e.g., `user.articles.all.delete_all`).
|
39
|
+
# This is because omitting `all` from an association changes the methods
|
40
|
+
# from `ActiveRecord::Relation` to `ActiveRecord::Associations::CollectionProxy`,
|
41
|
+
# which can affect their behavior.
|
42
|
+
#
|
43
|
+
# @safety
|
44
|
+
# This cop is unsafe for autocorrection if the receiver for `all` is not an Active Record object.
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# # bad
|
48
|
+
# User.all.find(id)
|
49
|
+
# User.all.order(:created_at)
|
50
|
+
# users.all.where(id: ids)
|
51
|
+
# user.articles.all.order(:created_at)
|
52
|
+
#
|
53
|
+
# # good
|
54
|
+
# User.find(id)
|
55
|
+
# User.order(:created_at)
|
56
|
+
# users.where(id: ids)
|
57
|
+
# user.articles.order(:created_at)
|
58
|
+
#
|
59
|
+
# @example AllowedReceivers: ['ActionMailer::Preview', 'ActiveSupport::TimeZone'] (default)
|
60
|
+
# # good
|
61
|
+
# ActionMailer::Preview.all.first
|
62
|
+
# ActiveSupport::TimeZone.all.first
|
63
|
+
class RedundantActiveRecordAllMethod < Base
|
64
|
+
include ActiveRecordHelper
|
65
|
+
include AllowedReceivers
|
66
|
+
include RangeHelp
|
67
|
+
extend AutoCorrector
|
68
|
+
|
69
|
+
MSG = 'Redundant `all` detected.'
|
70
|
+
|
71
|
+
RESTRICT_ON_SEND = [:all].freeze
|
72
|
+
|
73
|
+
# Defined methods in `ActiveRecord::Querying::QUERYING_METHODS` on activerecord 7.1.0.
|
74
|
+
QUERYING_METHODS = %i[
|
75
|
+
and
|
76
|
+
annotate
|
77
|
+
any?
|
78
|
+
async_average
|
79
|
+
async_count
|
80
|
+
async_ids
|
81
|
+
async_maximum
|
82
|
+
async_minimum
|
83
|
+
async_pick
|
84
|
+
async_pluck
|
85
|
+
async_sum
|
86
|
+
average
|
87
|
+
calculate
|
88
|
+
count
|
89
|
+
create_or_find_by
|
90
|
+
create_or_find_by!
|
91
|
+
create_with
|
92
|
+
delete_all
|
93
|
+
delete_by
|
94
|
+
destroy_all
|
95
|
+
destroy_by
|
96
|
+
distinct
|
97
|
+
eager_load
|
98
|
+
except
|
99
|
+
excluding
|
100
|
+
exists?
|
101
|
+
extending
|
102
|
+
extract_associated
|
103
|
+
fifth
|
104
|
+
fifth!
|
105
|
+
find
|
106
|
+
find_by
|
107
|
+
find_by!
|
108
|
+
find_each
|
109
|
+
find_in_batches
|
110
|
+
find_or_create_by
|
111
|
+
find_or_create_by!
|
112
|
+
find_or_initialize_by
|
113
|
+
find_sole_by
|
114
|
+
first
|
115
|
+
first!
|
116
|
+
first_or_create
|
117
|
+
first_or_create!
|
118
|
+
first_or_initialize
|
119
|
+
forty_two
|
120
|
+
forty_two!
|
121
|
+
fourth
|
122
|
+
fourth!
|
123
|
+
from
|
124
|
+
group
|
125
|
+
having
|
126
|
+
ids
|
127
|
+
in_batches
|
128
|
+
in_order_of
|
129
|
+
includes
|
130
|
+
invert_where
|
131
|
+
joins
|
132
|
+
last
|
133
|
+
last!
|
134
|
+
left_joins
|
135
|
+
left_outer_joins
|
136
|
+
limit
|
137
|
+
lock
|
138
|
+
many?
|
139
|
+
maximum
|
140
|
+
merge
|
141
|
+
minimum
|
142
|
+
none
|
143
|
+
none?
|
144
|
+
offset
|
145
|
+
one?
|
146
|
+
only
|
147
|
+
optimizer_hints
|
148
|
+
or
|
149
|
+
order
|
150
|
+
pick
|
151
|
+
pluck
|
152
|
+
preload
|
153
|
+
readonly
|
154
|
+
references
|
155
|
+
regroup
|
156
|
+
reorder
|
157
|
+
reselect
|
158
|
+
rewhere
|
159
|
+
second
|
160
|
+
second!
|
161
|
+
second_to_last
|
162
|
+
second_to_last!
|
163
|
+
select
|
164
|
+
sole
|
165
|
+
strict_loading
|
166
|
+
sum
|
167
|
+
take
|
168
|
+
take!
|
169
|
+
third
|
170
|
+
third!
|
171
|
+
third_to_last
|
172
|
+
third_to_last!
|
173
|
+
touch_all
|
174
|
+
unscope
|
175
|
+
update_all
|
176
|
+
where
|
177
|
+
with
|
178
|
+
without
|
179
|
+
].to_set.freeze
|
180
|
+
|
181
|
+
POSSIBLE_ENUMERABLE_BLOCK_METHODS = %i[any? count find none? one? select sum].freeze
|
182
|
+
SENSITIVE_METHODS_ON_ASSOCIATION = %i[delete_all destroy_all].freeze
|
183
|
+
|
184
|
+
def_node_matcher :followed_by_query_method?, <<~PATTERN
|
185
|
+
(send (send _ :all) QUERYING_METHODS ...)
|
186
|
+
PATTERN
|
187
|
+
|
188
|
+
def on_send(node)
|
189
|
+
return unless followed_by_query_method?(node.parent)
|
190
|
+
return if possible_enumerable_block_method?(node) || sensitive_association_method?(node)
|
191
|
+
return if node.receiver ? allowed_receiver?(node.receiver) : !inherit_active_record_base?(node)
|
192
|
+
|
193
|
+
range_of_all_method = offense_range(node)
|
194
|
+
add_offense(range_of_all_method) do |collector|
|
195
|
+
collector.remove(range_of_all_method)
|
196
|
+
collector.remove(node.parent.loc.dot)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
def possible_enumerable_block_method?(node)
|
203
|
+
parent = node.parent
|
204
|
+
return false unless POSSIBLE_ENUMERABLE_BLOCK_METHODS.include?(parent.method_name)
|
205
|
+
|
206
|
+
parent.parent&.block_type? || parent.parent&.numblock_type? || parent.first_argument&.block_pass_type?
|
207
|
+
end
|
208
|
+
|
209
|
+
def sensitive_association_method?(node)
|
210
|
+
!node.receiver&.const_type? && SENSITIVE_METHODS_ON_ASSOCIATION.include?(node.parent.method_name)
|
211
|
+
end
|
212
|
+
|
213
|
+
def offense_range(node)
|
214
|
+
range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -53,6 +53,12 @@ module RuboCop
|
|
53
53
|
# @example source that matches - by a foreign key
|
54
54
|
# validates :user_id, presence: true
|
55
55
|
#
|
56
|
+
# @example source that DOES NOT match - if condition
|
57
|
+
# validates :user_id, presence: true, if: condition
|
58
|
+
#
|
59
|
+
# @example source that DOES NOT match - unless condition
|
60
|
+
# validates :user_id, presence: true, unless: condition
|
61
|
+
#
|
56
62
|
# @example source that DOES NOT match - strict validation
|
57
63
|
# validates :user_id, presence: true, strict: true
|
58
64
|
#
|
@@ -65,6 +71,7 @@ module RuboCop
|
|
65
71
|
$[
|
66
72
|
(hash <$(pair (sym :presence) true) ...>) # presence: true
|
67
73
|
!(hash <$(pair (sym :strict) {true const}) ...>) # strict: true
|
74
|
+
!(hash <$(pair (sym {:if :unless}) _) ...>) # if: some_condition or unless: some_condition
|
68
75
|
]
|
69
76
|
)
|
70
77
|
PATTERN
|
@@ -3,25 +3,30 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Rails
|
6
|
-
# Prefer `response.parsed_body` to `
|
6
|
+
# Prefer `response.parsed_body` to custom parsing logic for `response.body`.
|
7
7
|
#
|
8
8
|
# @safety
|
9
|
-
# This cop is unsafe because Content-Type may not be `application/json
|
10
|
-
# Content-Type provided by corporate entities such as
|
11
|
-
# `
|
9
|
+
# This cop is unsafe because Content-Type may not be `application/json` or `text/html`.
|
10
|
+
# For example, the proprietary Content-Type provided by corporate entities such as
|
11
|
+
# `application/vnd.github+json` is not supported at `response.parsed_body` by default,
|
12
|
+
# so you still have to use `JSON.parse(response.body)` there.
|
12
13
|
#
|
13
14
|
# @example
|
14
15
|
# # bad
|
15
16
|
# JSON.parse(response.body)
|
16
17
|
#
|
18
|
+
# # bad
|
19
|
+
# Nokogiri::HTML.parse(response.body)
|
20
|
+
#
|
21
|
+
# # bad
|
22
|
+
# Nokogiri::HTML5.parse(response.body)
|
23
|
+
#
|
17
24
|
# # good
|
18
25
|
# response.parsed_body
|
19
26
|
class ResponseParsedBody < Base
|
20
27
|
extend AutoCorrector
|
21
28
|
extend TargetRailsVersion
|
22
29
|
|
23
|
-
MSG = 'Prefer `response.parsed_body` to `JSON.parse(response.body)`.'
|
24
|
-
|
25
30
|
RESTRICT_ON_SEND = %i[parse].freeze
|
26
31
|
|
27
32
|
minimum_target_rails_version 5.0
|
@@ -38,12 +43,27 @@ module RuboCop
|
|
38
43
|
)
|
39
44
|
PATTERN
|
40
45
|
|
46
|
+
# @!method nokogiri_html_parse_response_body(node)
|
47
|
+
def_node_matcher :nokogiri_html_parse_response_body, <<~PATTERN
|
48
|
+
(send
|
49
|
+
(const
|
50
|
+
(const {nil? cbase} :Nokogiri)
|
51
|
+
${:HTML :HTML5}
|
52
|
+
)
|
53
|
+
:parse
|
54
|
+
(send
|
55
|
+
(send nil? :response)
|
56
|
+
:body
|
57
|
+
)
|
58
|
+
)
|
59
|
+
PATTERN
|
60
|
+
|
41
61
|
def on_send(node)
|
42
|
-
|
62
|
+
check_json_parse_response_body(node)
|
43
63
|
|
44
|
-
|
45
|
-
|
46
|
-
|
64
|
+
return unless target_rails_version >= 7.1
|
65
|
+
|
66
|
+
check_nokogiri_html_parse_response_body(node)
|
47
67
|
end
|
48
68
|
|
49
69
|
private
|
@@ -51,6 +71,28 @@ module RuboCop
|
|
51
71
|
def autocorrect(corrector, node)
|
52
72
|
corrector.replace(node, 'response.parsed_body')
|
53
73
|
end
|
74
|
+
|
75
|
+
def check_json_parse_response_body(node)
|
76
|
+
return unless json_parse_response_body?(node)
|
77
|
+
|
78
|
+
add_offense(
|
79
|
+
node,
|
80
|
+
message: 'Prefer `response.parsed_body` to `JSON.parse(response.body)`.'
|
81
|
+
) do |corrector|
|
82
|
+
autocorrect(corrector, node)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def check_nokogiri_html_parse_response_body(node)
|
87
|
+
return unless (const = nokogiri_html_parse_response_body(node))
|
88
|
+
|
89
|
+
add_offense(
|
90
|
+
node,
|
91
|
+
message: "Prefer `response.parsed_body` to `Nokogiri::#{const}.parse(response.body)`."
|
92
|
+
) do |corrector|
|
93
|
+
autocorrect(corrector, node)
|
94
|
+
end
|
95
|
+
end
|
54
96
|
end
|
55
97
|
end
|
56
98
|
end
|
@@ -17,7 +17,7 @@ module RuboCop
|
|
17
17
|
# # good
|
18
18
|
# def change
|
19
19
|
# change_table :users do |t|
|
20
|
-
# t.remove :name, :string
|
20
|
+
# t.remove :name, type: :string
|
21
21
|
# end
|
22
22
|
# end
|
23
23
|
#
|
@@ -290,10 +290,10 @@ module RuboCop
|
|
290
290
|
when :change
|
291
291
|
false
|
292
292
|
when :remove
|
293
|
-
target_rails_version >= 6.1 && all_hash_key?(node.
|
293
|
+
target_rails_version >= 6.1 && all_hash_key?(node.last_argument, :type)
|
294
294
|
when :change_default, :change_column_default, :change_table_comment,
|
295
295
|
:change_column_comment
|
296
|
-
all_hash_key?(node.
|
296
|
+
all_hash_key?(node.last_argument, :from, :to)
|
297
297
|
else
|
298
298
|
true
|
299
299
|
end
|
@@ -307,7 +307,7 @@ module RuboCop
|
|
307
307
|
|
308
308
|
def within_reversible_or_up_only_block?(node)
|
309
309
|
node.each_ancestor(:block).any? do |ancestor|
|
310
|
-
(ancestor.block_type? && ancestor.
|
310
|
+
(ancestor.block_type? && ancestor.method?(:reversible)) || ancestor.method?(:up_only)
|
311
311
|
end
|
312
312
|
end
|
313
313
|
|
@@ -12,7 +12,7 @@ module RuboCop
|
|
12
12
|
# `Style/FileRead`, `Style/FileWrite` and `Rails/RootJoinChain`.
|
13
13
|
#
|
14
14
|
# @safety
|
15
|
-
# This cop is unsafe for autocorrection because
|
15
|
+
# This cop is unsafe for autocorrection because ``Dir``'s `children`, `each_child`, `entries`, and `glob`
|
16
16
|
# methods return string element, but these methods of `Pathname` return `Pathname` element.
|
17
17
|
#
|
18
18
|
# @example
|
@@ -32,13 +32,28 @@ module RuboCop
|
|
32
32
|
# Rails.root.join('db', 'schema.rb').write(content)
|
33
33
|
# Rails.root.join('db', 'schema.rb').binwrite(content)
|
34
34
|
#
|
35
|
-
class RootPathnameMethods < Base
|
35
|
+
class RootPathnameMethods < Base # rubocop:disable Metrics/ClassLength
|
36
36
|
extend AutoCorrector
|
37
37
|
include RangeHelp
|
38
38
|
|
39
39
|
MSG = '`%<rails_root>s` is a `Pathname` so you can just append `#%<method>s`.'
|
40
40
|
|
41
|
-
|
41
|
+
DIR_GLOB_METHODS = %i[glob].to_set.freeze
|
42
|
+
|
43
|
+
DIR_NON_GLOB_METHODS = %i[
|
44
|
+
children
|
45
|
+
delete
|
46
|
+
each_child
|
47
|
+
empty?
|
48
|
+
entries
|
49
|
+
exist?
|
50
|
+
mkdir
|
51
|
+
open
|
52
|
+
rmdir
|
53
|
+
unlink
|
54
|
+
].to_set.freeze
|
55
|
+
|
56
|
+
DIR_METHODS = (DIR_GLOB_METHODS + DIR_NON_GLOB_METHODS).freeze
|
42
57
|
|
43
58
|
FILE_METHODS = %i[
|
44
59
|
atime
|
@@ -134,7 +149,8 @@ module RuboCop
|
|
134
149
|
|
135
150
|
RESTRICT_ON_SEND = (DIR_METHODS + FILE_METHODS + FILE_TEST_METHODS + FILE_UTILS_METHODS).to_set.freeze
|
136
151
|
|
137
|
-
|
152
|
+
# @!method pathname_method_for_ruby_2_5_or_higher(node)
|
153
|
+
def_node_matcher :pathname_method_for_ruby_2_5_or_higher, <<~PATTERN
|
138
154
|
{
|
139
155
|
(send (const {nil? cbase} :Dir) $DIR_METHODS $_ $...)
|
140
156
|
(send (const {nil? cbase} {:IO :File}) $FILE_METHODS $_ $...)
|
@@ -143,6 +159,16 @@ module RuboCop
|
|
143
159
|
}
|
144
160
|
PATTERN
|
145
161
|
|
162
|
+
# @!method pathname_method_for_ruby_2_4_or_lower(node)
|
163
|
+
def_node_matcher :pathname_method_for_ruby_2_4_or_lower, <<~PATTERN
|
164
|
+
{
|
165
|
+
(send (const {nil? cbase} :Dir) $DIR_NON_GLOB_METHODS $_ $...)
|
166
|
+
(send (const {nil? cbase} {:IO :File}) $FILE_METHODS $_ $...)
|
167
|
+
(send (const {nil? cbase} :FileTest) $FILE_TEST_METHODS $_ $...)
|
168
|
+
(send (const {nil? cbase} :FileUtils) $FILE_UTILS_METHODS $_ $...)
|
169
|
+
}
|
170
|
+
PATTERN
|
171
|
+
|
146
172
|
def_node_matcher :dir_glob?, <<~PATTERN
|
147
173
|
(send
|
148
174
|
(const {cbase nil?} :Dir) :glob ...)
|
@@ -183,6 +209,14 @@ module RuboCop
|
|
183
209
|
yield(method, path, args, rails_root)
|
184
210
|
end
|
185
211
|
|
212
|
+
def pathname_method(node)
|
213
|
+
if target_ruby_version >= 2.5
|
214
|
+
pathname_method_for_ruby_2_5_or_higher(node)
|
215
|
+
else
|
216
|
+
pathname_method_for_ruby_2_4_or_lower(node)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
186
220
|
def build_path_glob_replacement(path, method)
|
187
221
|
receiver = range_between(path.source_range.begin_pos, path.children.first.loc.selector.end_pos).source
|
188
222
|
|
@@ -188,7 +188,7 @@ module RuboCop
|
|
188
188
|
end
|
189
189
|
|
190
190
|
def persisted_referenced?(assignment)
|
191
|
-
return unless assignment.referenced?
|
191
|
+
return false unless assignment.referenced?
|
192
192
|
|
193
193
|
assignment.variable.references.any? do |reference|
|
194
194
|
call_to_persisted?(reference.node.parent)
|
@@ -196,6 +196,8 @@ module RuboCop
|
|
196
196
|
end
|
197
197
|
|
198
198
|
def call_to_persisted?(node)
|
199
|
+
node = node.parent.condition if node.parenthesized_call? && node.parent.if_type?
|
200
|
+
|
199
201
|
node.send_type? && node.method?(:persisted?)
|
200
202
|
end
|
201
203
|
|
@@ -235,10 +237,10 @@ module RuboCop
|
|
235
237
|
|
236
238
|
def in_condition_or_compound_boolean?(node)
|
237
239
|
node = node.block_node || node
|
238
|
-
parent = node.
|
240
|
+
parent = node.each_ancestor.find { |ancestor| !ancestor.begin_type? }
|
239
241
|
return false unless parent
|
240
242
|
|
241
|
-
operator_or_single_negative?(parent) || (conditional?(parent) && node == parent.condition)
|
243
|
+
operator_or_single_negative?(parent) || (conditional?(parent) && node == deparenthesize(parent.condition))
|
242
244
|
end
|
243
245
|
|
244
246
|
def operator_or_single_negative?(node)
|
@@ -249,6 +251,11 @@ module RuboCop
|
|
249
251
|
parent.if_type? || parent.case_type?
|
250
252
|
end
|
251
253
|
|
254
|
+
def deparenthesize(node)
|
255
|
+
node = node.children.last while node.begin_type?
|
256
|
+
node
|
257
|
+
end
|
258
|
+
|
252
259
|
def checked_immediately?(node)
|
253
260
|
node.parent && call_to_persisted?(node.parent)
|
254
261
|
end
|
@@ -298,7 +305,7 @@ module RuboCop
|
|
298
305
|
|
299
306
|
node = assignable_node(node)
|
300
307
|
method, sibling_index = find_method_with_sibling_index(node.parent)
|
301
|
-
return unless method && (method.def_type? || method.block_type?)
|
308
|
+
return false unless method && (method.def_type? || method.block_type?)
|
302
309
|
|
303
310
|
method.children.size == node.sibling_index + sibling_index
|
304
311
|
end
|
@@ -331,10 +338,10 @@ module RuboCop
|
|
331
338
|
|
332
339
|
# Check argument signature as no arguments or one hash
|
333
340
|
def expected_signature?(node)
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
341
|
+
return true unless node.arguments?
|
342
|
+
return false if !node.arguments.one? || node.method?(:destroy)
|
343
|
+
|
344
|
+
node.first_argument.hash_type? || !node.first_argument.literal?
|
338
345
|
end
|
339
346
|
end
|
340
347
|
end
|
@@ -74,17 +74,25 @@ module RuboCop
|
|
74
74
|
def on_send(node)
|
75
75
|
if add_column_without_comment?(node)
|
76
76
|
add_offense(node, message: COLUMN_MSG)
|
77
|
-
elsif
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
add_offense(node.parent.body, message: COLUMN_MSG)
|
82
|
-
end
|
77
|
+
elsif create_table_without_comment?(node)
|
78
|
+
add_offense(node, message: TABLE_MSG)
|
79
|
+
elsif create_table_with_block?(node.parent)
|
80
|
+
check_column_within_create_table_block(node.parent.body)
|
83
81
|
end
|
84
82
|
end
|
85
83
|
|
86
84
|
private
|
87
85
|
|
86
|
+
def check_column_within_create_table_block(node)
|
87
|
+
if node.begin_type?
|
88
|
+
node.child_nodes.each do |child_node|
|
89
|
+
add_offense(child_node, message: COLUMN_MSG) if t_column_without_comment?(child_node)
|
90
|
+
end
|
91
|
+
elsif t_column_without_comment?(node)
|
92
|
+
add_offense(node, message: COLUMN_MSG)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
88
96
|
def add_column_without_comment?(node)
|
89
97
|
add_column?(node) && !add_column_with_comment?(node)
|
90
98
|
end
|
@@ -93,10 +101,8 @@ module RuboCop
|
|
93
101
|
create_table?(node) && !create_table_with_comment?(node)
|
94
102
|
end
|
95
103
|
|
96
|
-
def
|
97
|
-
|
98
|
-
t_column?(node.parent.body) &&
|
99
|
-
!t_column_with_comment?(node.parent.body)
|
104
|
+
def t_column_without_comment?(node)
|
105
|
+
t_column?(node) && !t_column_with_comment?(node)
|
100
106
|
end
|
101
107
|
end
|
102
108
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# Checks for uses of `select(:column_name)` with `map(&:column_name)`.
|
7
|
+
# These can be replaced with `pluck(:column_name)`.
|
8
|
+
#
|
9
|
+
# There also should be some performance improvement since it skips instantiating the model class for matches.
|
10
|
+
#
|
11
|
+
# @safety
|
12
|
+
# This cop is unsafe because the model might override the attribute getter.
|
13
|
+
# Additionally, the model's `after_initialize` hooks are skipped when using `pluck`.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# # bad
|
17
|
+
# Model.select(:column_name).map(&:column_name)
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# Model.pluck(:column_name)
|
21
|
+
#
|
22
|
+
class SelectMap < Base
|
23
|
+
extend AutoCorrector
|
24
|
+
|
25
|
+
MSG = 'Use `%<preferred_method>s` instead of `select` with `%<map_method>s`.'
|
26
|
+
|
27
|
+
RESTRICT_ON_SEND = %i[map collect].freeze
|
28
|
+
|
29
|
+
def on_send(node)
|
30
|
+
return unless node.first_argument
|
31
|
+
|
32
|
+
column_name = node.first_argument.source.delete_prefix('&:')
|
33
|
+
return unless (select_node = find_select_node(node, column_name))
|
34
|
+
|
35
|
+
offense_range = select_node.loc.selector.begin.join(node.source_range.end)
|
36
|
+
preferred_method = "pluck(:#{column_name})"
|
37
|
+
message = format(MSG, preferred_method: preferred_method, map_method: node.method_name)
|
38
|
+
|
39
|
+
add_offense(offense_range, message: message) do |corrector|
|
40
|
+
autocorrect(corrector, select_node, node, preferred_method)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def find_select_node(node, column_name)
|
47
|
+
node.descendants.detect do |select_candidate|
|
48
|
+
next if !select_candidate.send_type? || !select_candidate.method?(:select)
|
49
|
+
|
50
|
+
match_column_name?(select_candidate, column_name)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# rubocop:disable Metrics/AbcSize
|
55
|
+
def autocorrect(corrector, select_node, node, preferred_method)
|
56
|
+
corrector.remove(select_node.loc.dot || node.loc.dot)
|
57
|
+
corrector.remove(select_node.loc.selector.begin.join(select_node.source_range.end))
|
58
|
+
corrector.replace(node.loc.selector.begin.join(node.source_range.end), preferred_method)
|
59
|
+
end
|
60
|
+
# rubocop:enable Metrics/AbcSize
|
61
|
+
|
62
|
+
def match_column_name?(select_candidate, column_name)
|
63
|
+
return false unless select_candidate.arguments.one?
|
64
|
+
return false unless (first_argument = select_candidate.first_argument)
|
65
|
+
|
66
|
+
argument = case select_candidate.first_argument.type
|
67
|
+
when :sym
|
68
|
+
first_argument.source.delete_prefix(':')
|
69
|
+
when :str
|
70
|
+
first_argument.value if first_argument.respond_to?(:value)
|
71
|
+
end
|
72
|
+
|
73
|
+
argument == column_name
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -21,6 +21,7 @@ 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
|
@@ -44,19 +45,17 @@ module RuboCop
|
|
44
45
|
extend AutoCorrector
|
45
46
|
|
46
47
|
MSG = 'Do not use `%<current>s` without zone. Use `%<prefer>s` instead.'
|
47
|
-
|
48
48
|
MSG_ACCEPTABLE = 'Do not use `%<current>s` without zone. Use one of %<prefer>s instead.'
|
49
|
-
|
50
49
|
MSG_LOCALTIME = 'Do not use `Time.localtime` without offset or zone.'
|
50
|
+
MSG_STRING_TO_TIME = 'Do not use `String#to_time` without zone. Use `Time.zone.parse` instead.'
|
51
51
|
|
52
52
|
GOOD_METHODS = %i[zone zone_default find_zone find_zone!].freeze
|
53
|
-
|
54
53
|
DANGEROUS_METHODS = %i[now local new parse at].freeze
|
55
|
-
|
56
54
|
ACCEPTED_METHODS = %i[in_time_zone utc getlocal xmlschema iso8601 jisx0301 rfc3339 httpdate to_i to_f].freeze
|
57
|
-
|
58
55
|
TIMEZONE_SPECIFIER = /([A-Za-z]|[+-]\d{2}:?\d{2})\z/.freeze
|
59
56
|
|
57
|
+
RESTRICT_ON_SEND = %i[to_time].freeze
|
58
|
+
|
60
59
|
def on_const(node)
|
61
60
|
mod, klass = *node
|
62
61
|
# we should only check core classes
|
@@ -66,6 +65,15 @@ module RuboCop
|
|
66
65
|
check_time_node(klass, node.parent) if klass == :Time
|
67
66
|
end
|
68
67
|
|
68
|
+
def on_send(node)
|
69
|
+
return if !node.receiver&.str_type? || !node.method?(:to_time)
|
70
|
+
|
71
|
+
add_offense(node.loc.selector, message: MSG_STRING_TO_TIME) do |corrector|
|
72
|
+
corrector.replace(node, "Time.zone.parse(#{node.receiver.source})") unless node.csend_type?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
alias on_csend on_send
|
76
|
+
|
69
77
|
private
|
70
78
|
|
71
79
|
def autocorrect(corrector, node)
|