rubocop-rails 2.20.2 → 2.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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)
|