rubocop-rails 2.8.0 → 2.10.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/LICENSE.txt +1 -1
- data/README.md +18 -2
- data/config/default.yml +101 -5
- data/config/obsoletion.yml +7 -0
- data/lib/rubocop/cop/mixin/active_record_helper.rb +16 -3
- data/lib/rubocop/cop/mixin/enforce_superclass.rb +40 -0
- data/lib/rubocop/cop/mixin/index_method.rb +8 -11
- data/lib/rubocop/cop/rails/action_filter.rb +10 -14
- data/lib/rubocop/cop/rails/active_record_aliases.rb +13 -17
- data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +17 -12
- data/lib/rubocop/cop/rails/active_record_override.rb +1 -1
- data/lib/rubocop/cop/rails/active_support_aliases.rb +12 -21
- data/lib/rubocop/cop/rails/after_commit_override.rb +9 -2
- data/lib/rubocop/cop/rails/application_controller.rb +3 -7
- data/lib/rubocop/cop/rails/application_job.rb +2 -1
- data/lib/rubocop/cop/rails/application_mailer.rb +2 -7
- data/lib/rubocop/cop/rails/application_record.rb +2 -7
- data/lib/rubocop/cop/rails/arel_star.rb +41 -0
- data/lib/rubocop/cop/rails/assert_not.rb +8 -10
- data/lib/rubocop/cop/rails/attribute_default_block_value.rb +90 -0
- data/lib/rubocop/cop/rails/belongs_to.rb +10 -19
- data/lib/rubocop/cop/rails/blank.rb +31 -27
- data/lib/rubocop/cop/rails/bulk_change_table.rb +1 -1
- data/lib/rubocop/cop/rails/content_tag.rb +33 -18
- data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +2 -1
- data/lib/rubocop/cop/rails/date.rb +10 -11
- data/lib/rubocop/cop/rails/default_scope.rb +11 -4
- data/lib/rubocop/cop/rails/delegate.rb +9 -9
- data/lib/rubocop/cop/rails/delegate_allow_blank.rb +7 -8
- data/lib/rubocop/cop/rails/dynamic_find_by.rb +15 -12
- data/lib/rubocop/cop/rails/enum_hash.rb +11 -10
- data/lib/rubocop/cop/rails/enum_uniqueness.rb +2 -1
- data/lib/rubocop/cop/rails/environment_comparison.rb +18 -14
- data/lib/rubocop/cop/rails/environment_variable_access.rb +67 -0
- data/lib/rubocop/cop/rails/exit.rb +4 -10
- data/lib/rubocop/cop/rails/file_path.rb +6 -7
- data/lib/rubocop/cop/rails/find_by.rb +13 -13
- data/lib/rubocop/cop/rails/find_by_id.rb +12 -21
- data/lib/rubocop/cop/rails/find_each.rb +19 -18
- data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +3 -2
- data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +37 -6
- data/lib/rubocop/cop/rails/helper_instance_variable.rb +29 -3
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +32 -21
- data/lib/rubocop/cop/rails/http_status.rb +7 -9
- data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +8 -6
- data/lib/rubocop/cop/rails/index_by.rb +3 -2
- data/lib/rubocop/cop/rails/index_with.rb +3 -2
- data/lib/rubocop/cop/rails/inquiry.rb +4 -3
- data/lib/rubocop/cop/rails/inverse_of.rb +3 -2
- data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +17 -15
- data/lib/rubocop/cop/rails/link_to_blank.rb +25 -23
- data/lib/rubocop/cop/rails/mailer_name.rb +19 -13
- data/lib/rubocop/cop/rails/match_route.rb +14 -13
- data/lib/rubocop/cop/rails/negate_include.rb +10 -8
- data/lib/rubocop/cop/rails/not_null_column.rb +2 -1
- data/lib/rubocop/cop/rails/order_by_id.rb +1 -2
- data/lib/rubocop/cop/rails/output.rb +5 -2
- data/lib/rubocop/cop/rails/output_safety.rb +3 -2
- data/lib/rubocop/cop/rails/pick.rb +14 -12
- data/lib/rubocop/cop/rails/pluck.rb +6 -9
- data/lib/rubocop/cop/rails/pluck_id.rb +4 -6
- data/lib/rubocop/cop/rails/pluck_in_where.rb +7 -7
- data/lib/rubocop/cop/rails/pluralization_grammar.rb +10 -14
- data/lib/rubocop/cop/rails/presence.rb +12 -13
- data/lib/rubocop/cop/rails/present.rb +30 -24
- data/lib/rubocop/cop/rails/rake_environment.rb +8 -10
- data/lib/rubocop/cop/rails/read_write_attribute.rb +12 -11
- data/lib/rubocop/cop/rails/redundant_allow_nil.rb +29 -31
- data/lib/rubocop/cop/rails/redundant_foreign_key.rb +9 -12
- data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +11 -10
- data/lib/rubocop/cop/rails/reflection_class_name.rb +17 -3
- data/lib/rubocop/cop/rails/refute_methods.rb +9 -10
- data/lib/rubocop/cop/rails/relative_date_constant.rb +30 -21
- data/lib/rubocop/cop/rails/render_inline.rb +2 -1
- data/lib/rubocop/cop/rails/render_plain_text.rb +9 -14
- data/lib/rubocop/cop/rails/request_referer.rb +7 -7
- data/lib/rubocop/cop/rails/require_dependency.rb +38 -0
- data/lib/rubocop/cop/rails/reversible_migration.rb +4 -8
- data/lib/rubocop/cop/rails/reversible_migration_method_definition.rb +75 -0
- data/lib/rubocop/cop/rails/safe_navigation.rb +30 -11
- data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +5 -10
- data/lib/rubocop/cop/rails/save_bang.rb +17 -20
- data/lib/rubocop/cop/rails/scope_args.rb +2 -1
- data/lib/rubocop/cop/rails/short_i18n.rb +7 -9
- data/lib/rubocop/cop/rails/skips_model_validations.rb +4 -4
- data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +5 -6
- data/lib/rubocop/cop/rails/time_zone.rb +35 -25
- data/lib/rubocop/cop/rails/time_zone_assignment.rb +37 -0
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +4 -6
- data/lib/rubocop/cop/rails/unique_validation_without_index.rb +4 -2
- data/lib/rubocop/cop/rails/unknown_env.rb +3 -3
- data/lib/rubocop/cop/rails/validation.rb +15 -14
- data/lib/rubocop/cop/rails/where_equals.rb +98 -0
- data/lib/rubocop/cop/rails/where_exists.rb +19 -13
- data/lib/rubocop/cop/rails/where_not.rb +14 -19
- data/lib/rubocop/cop/rails_cops.rb +8 -0
- data/lib/rubocop/rails.rb +2 -0
- data/lib/rubocop/rails/schema_loader.rb +4 -4
- data/lib/rubocop/rails/schema_loader/schema.rb +2 -4
- data/lib/rubocop/rails/version.rb +5 -1
- metadata +29 -14
@@ -7,7 +7,7 @@ module RuboCop
|
|
7
7
|
# validations which are listed in
|
8
8
|
# https://guides.rubyonrails.org/active_record_validations.html#skipping-validations
|
9
9
|
#
|
10
|
-
# Methods may be ignored from this rule by configuring a `
|
10
|
+
# Methods may be ignored from this rule by configuring a `AllowedMethods`.
|
11
11
|
#
|
12
12
|
# @example
|
13
13
|
# # bad
|
@@ -26,7 +26,7 @@ module RuboCop
|
|
26
26
|
# user.update(website: 'example.com')
|
27
27
|
# FileUtils.touch('file')
|
28
28
|
#
|
29
|
-
# @example
|
29
|
+
# @example AllowedMethods: ["touch"]
|
30
30
|
# # bad
|
31
31
|
# DiscussionBoard.decrement_counter(:post_count, 5)
|
32
32
|
# DiscussionBoard.increment_counter(:post_count, 5)
|
@@ -35,7 +35,7 @@ module RuboCop
|
|
35
35
|
# # good
|
36
36
|
# user.touch
|
37
37
|
#
|
38
|
-
class SkipsModelValidations <
|
38
|
+
class SkipsModelValidations < Base
|
39
39
|
MSG = 'Avoid using `%<method>s` because it skips validations.'
|
40
40
|
|
41
41
|
METHODS_WITH_ARGUMENTS = %w[decrement!
|
@@ -76,7 +76,7 @@ module RuboCop
|
|
76
76
|
return if good_touch?(node)
|
77
77
|
return if good_insert?(node)
|
78
78
|
|
79
|
-
add_offense(node,
|
79
|
+
add_offense(node.loc.selector, message: message(node))
|
80
80
|
end
|
81
81
|
alias on_csend on_send
|
82
82
|
|
@@ -5,6 +5,8 @@ module RuboCop
|
|
5
5
|
module Rails
|
6
6
|
#
|
7
7
|
# Checks SQL heredocs to use `.squish`.
|
8
|
+
# Some SQL syntax (e.g. PostgreSQL comments and functions) requires newlines
|
9
|
+
# to be preserved in order to work, thus auto-correction for this cop is not safe.
|
8
10
|
#
|
9
11
|
# @example
|
10
12
|
# # bad
|
@@ -37,8 +39,9 @@ module RuboCop
|
|
37
39
|
# WHERE post_id = 1
|
38
40
|
# SQL
|
39
41
|
#
|
40
|
-
class SquishedSQLHeredocs <
|
42
|
+
class SquishedSQLHeredocs < Base
|
41
43
|
include Heredoc
|
44
|
+
extend AutoCorrector
|
42
45
|
|
43
46
|
SQL = 'SQL'
|
44
47
|
SQUISH = '.squish'
|
@@ -47,11 +50,7 @@ module RuboCop
|
|
47
50
|
def on_heredoc(node)
|
48
51
|
return unless offense_detected?(node)
|
49
52
|
|
50
|
-
add_offense(node)
|
51
|
-
end
|
52
|
-
|
53
|
-
def autocorrect(node)
|
54
|
-
lambda do |corrector|
|
53
|
+
add_offense(node) do |corrector|
|
55
54
|
corrector.insert_after(node, SQUISH)
|
56
55
|
end
|
57
56
|
end
|
@@ -19,7 +19,7 @@ module RuboCop
|
|
19
19
|
#
|
20
20
|
# # bad
|
21
21
|
# Time.now
|
22
|
-
# Time.parse('2015-03-
|
22
|
+
# Time.parse('2015-03-02T19:05:37')
|
23
23
|
#
|
24
24
|
# # bad
|
25
25
|
# Time.current
|
@@ -27,24 +27,26 @@ module RuboCop
|
|
27
27
|
#
|
28
28
|
# # good
|
29
29
|
# Time.zone.now
|
30
|
-
# Time.zone.parse('2015-03-
|
30
|
+
# Time.zone.parse('2015-03-02T19:05:37')
|
31
|
+
# Time.zone.parse('2015-03-02T19:05:37Z') # Respect ISO 8601 format with timezone specifier.
|
31
32
|
#
|
32
33
|
# @example EnforcedStyle: flexible (default)
|
33
34
|
# # `flexible` allows usage of `in_time_zone` instead of `zone`.
|
34
35
|
#
|
35
36
|
# # bad
|
36
37
|
# Time.now
|
37
|
-
# Time.parse('2015-03-
|
38
|
+
# Time.parse('2015-03-02T19:05:37')
|
38
39
|
#
|
39
40
|
# # good
|
40
41
|
# Time.zone.now
|
41
|
-
# Time.zone.parse('2015-03-
|
42
|
+
# Time.zone.parse('2015-03-02T19:05:37')
|
42
43
|
#
|
43
44
|
# # good
|
44
45
|
# Time.current
|
45
46
|
# Time.at(timestamp).in_time_zone
|
46
|
-
class TimeZone <
|
47
|
+
class TimeZone < Base
|
47
48
|
include ConfigurableEnforcedStyle
|
49
|
+
extend AutoCorrector
|
48
50
|
|
49
51
|
MSG = 'Do not use `%<current>s` without zone. Use `%<prefer>s` ' \
|
50
52
|
'instead.'
|
@@ -62,6 +64,8 @@ module RuboCop
|
|
62
64
|
ACCEPTED_METHODS = %i[in_time_zone utc getlocal xmlschema iso8601
|
63
65
|
jisx0301 rfc3339 httpdate to_i to_f].freeze
|
64
66
|
|
67
|
+
TIMEZONE_SPECIFIER = /[A-z]/.freeze
|
68
|
+
|
65
69
|
def on_const(node)
|
66
70
|
mod, klass = *node
|
67
71
|
# we should only check core classes
|
@@ -71,26 +75,24 @@ module RuboCop
|
|
71
75
|
check_time_node(klass, node.parent) if klass == :Time
|
72
76
|
end
|
73
77
|
|
74
|
-
|
75
|
-
lambda do |corrector|
|
76
|
-
# add `.zone`: `Time.at` => `Time.zone.at`
|
77
|
-
corrector.insert_after(node.children[0].source_range, '.zone')
|
78
|
+
private
|
78
79
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
corrector.replace(node.loc.selector, 'now')
|
83
|
-
when :new
|
84
|
-
autocorrect_time_new(node, corrector)
|
85
|
-
end
|
80
|
+
def autocorrect(corrector, node)
|
81
|
+
# add `.zone`: `Time.at` => `Time.zone.at`
|
82
|
+
corrector.insert_after(node.children[0].source_range, '.zone')
|
86
83
|
|
87
|
-
|
88
|
-
|
89
|
-
|
84
|
+
case node.method_name
|
85
|
+
when :current
|
86
|
+
# replace `Time.zone.current` => `Time.zone.now`
|
87
|
+
corrector.replace(node.loc.selector, 'now')
|
88
|
+
when :new
|
89
|
+
autocorrect_time_new(node, corrector)
|
90
90
|
end
|
91
|
-
end
|
92
91
|
|
93
|
-
|
92
|
+
# prefer `Time` over `DateTime` class
|
93
|
+
corrector.replace(node.children.first.source_range, 'Time') if strict?
|
94
|
+
remove_redundant_in_time_zone(corrector, node)
|
95
|
+
end
|
94
96
|
|
95
97
|
def autocorrect_time_new(node, corrector)
|
96
98
|
if node.arguments?
|
@@ -117,9 +119,10 @@ module RuboCop
|
|
117
119
|
end
|
118
120
|
|
119
121
|
def check_time_node(klass, node)
|
122
|
+
return if attach_timezone_specifier?(node.first_argument)
|
123
|
+
|
120
124
|
chain = extract_method_chain(node)
|
121
125
|
return if not_danger_chain?(chain)
|
122
|
-
|
123
126
|
return check_localtime(node) if need_check_localtime?(chain)
|
124
127
|
|
125
128
|
method_name = (chain & DANGEROUS_METHODS).join('.')
|
@@ -128,7 +131,13 @@ module RuboCop
|
|
128
131
|
|
129
132
|
message = build_message(klass, method_name, node)
|
130
133
|
|
131
|
-
add_offense(node
|
134
|
+
add_offense(node.loc.selector, message: message) do |corrector|
|
135
|
+
autocorrect(corrector, node)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def attach_timezone_specifier?(date)
|
140
|
+
date.respond_to?(:value) && TIMEZONE_SPECIFIER.match?(date.value.to_s[-1])
|
132
141
|
end
|
133
142
|
|
134
143
|
def build_message(klass, method_name, node)
|
@@ -193,8 +202,9 @@ module RuboCop
|
|
193
202
|
|
194
203
|
return if node.arguments?
|
195
204
|
|
196
|
-
add_offense(selector_node,
|
197
|
-
|
205
|
+
add_offense(selector_node.loc.selector, message: MSG_LOCALTIME) do |corrector|
|
206
|
+
autocorrect(corrector, selector_node)
|
207
|
+
end
|
198
208
|
end
|
199
209
|
|
200
210
|
def not_danger_chain?(chain)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks for the use of `Time.zone=` method.
|
7
|
+
#
|
8
|
+
# The `zone` attribute persists for the rest of the Ruby runtime, potentially causing
|
9
|
+
# unexpected behaviour at a later time.
|
10
|
+
# Using `Time.use_zone` ensures the code passed in block is the only place Time.zone is affected.
|
11
|
+
# It eliminates the possibility of a `zone` sticking around longer than intended.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# # bad
|
15
|
+
# Time.zone = 'EST'
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# Time.use_zone('EST') do
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
class TimeZoneAssignment < Base
|
22
|
+
MSG = 'Use `Time.use_zone` with blocks instead of `Time.zone=`.'
|
23
|
+
RESTRICT_ON_SEND = %i[zone=].freeze
|
24
|
+
|
25
|
+
def_node_matcher :time_zone_assignement?, <<~PATTERN
|
26
|
+
(send (const nil? :Time) :zone= ...)
|
27
|
+
PATTERN
|
28
|
+
|
29
|
+
def on_send(node)
|
30
|
+
return unless time_zone_assignement?(node)
|
31
|
+
|
32
|
+
add_offense(node)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -45,11 +45,13 @@ module RuboCop
|
|
45
45
|
# # good
|
46
46
|
# Model.distinct.pluck(:id)
|
47
47
|
#
|
48
|
-
class UniqBeforePluck <
|
48
|
+
class UniqBeforePluck < Base
|
49
49
|
include ConfigurableEnforcedStyle
|
50
50
|
include RangeHelp
|
51
|
+
extend AutoCorrector
|
51
52
|
|
52
53
|
MSG = 'Use `distinct` before `pluck`.'
|
54
|
+
RESTRICT_ON_SEND = %i[uniq distinct pluck].freeze
|
53
55
|
NEWLINE = "\n"
|
54
56
|
PATTERN = '[!^block (send (send %<type>s :pluck ...) ' \
|
55
57
|
'${:uniq :distinct} ...)]'
|
@@ -69,11 +71,7 @@ module RuboCop
|
|
69
71
|
|
70
72
|
return unless method
|
71
73
|
|
72
|
-
add_offense(node
|
73
|
-
end
|
74
|
-
|
75
|
-
def autocorrect(node)
|
76
|
-
lambda do |corrector|
|
74
|
+
add_offense(node.loc.selector) do |corrector|
|
77
75
|
method = node.method_name
|
78
76
|
|
79
77
|
corrector.remove(dot_method_with_whitespace(method, node))
|
@@ -24,13 +24,13 @@ module RuboCop
|
|
24
24
|
# # good - even if the schema does not have a unique index
|
25
25
|
# validates :account, length: { minimum: MIN_LENGTH }
|
26
26
|
#
|
27
|
-
class UniqueValidationWithoutIndex <
|
27
|
+
class UniqueValidationWithoutIndex < Base
|
28
28
|
include ActiveRecordHelper
|
29
29
|
|
30
30
|
MSG = 'Uniqueness validation should be with a unique index.'
|
31
|
+
RESTRICT_ON_SEND = %i[validates].freeze
|
31
32
|
|
32
33
|
def on_send(node)
|
33
|
-
return unless node.method?(:validates)
|
34
34
|
return unless uniqueness_part(node)
|
35
35
|
return if condition_part?(node)
|
36
36
|
return unless schema
|
@@ -46,6 +46,8 @@ module RuboCop
|
|
46
46
|
|
47
47
|
def find_schema_information(node)
|
48
48
|
klass = class_node(node)
|
49
|
+
return unless klass
|
50
|
+
|
49
51
|
table = schema.table_by(name: table_name(klass))
|
50
52
|
names = column_names(node)
|
51
53
|
|
@@ -17,7 +17,7 @@ module RuboCop
|
|
17
17
|
# # good
|
18
18
|
# Rails.env.production?
|
19
19
|
# Rails.env == 'production'
|
20
|
-
class UnknownEnv <
|
20
|
+
class UnknownEnv < Base
|
21
21
|
MSG = 'Unknown environment `%<name>s`.'
|
22
22
|
MSG_SIMILAR = 'Unknown environment `%<name>s`. ' \
|
23
23
|
'Did you mean `%<similar>s`?'
|
@@ -41,7 +41,7 @@ module RuboCop
|
|
41
41
|
|
42
42
|
def on_send(node)
|
43
43
|
unknown_environment_predicate?(node) do |name|
|
44
|
-
add_offense(node
|
44
|
+
add_offense(node.loc.selector, message: message(name))
|
45
45
|
end
|
46
46
|
|
47
47
|
unknown_environment_equal?(node) do |str_node|
|
@@ -62,7 +62,7 @@ module RuboCop
|
|
62
62
|
# DidYouMean::SpellChecker is not available in all versions of Ruby,
|
63
63
|
# and even on versions where it *is* available (>= 2.3), it is not
|
64
64
|
# always required correctly. So we do a feature check first. See:
|
65
|
-
# https://github.com/rubocop
|
65
|
+
# https://github.com/rubocop/rubocop/issues/7979
|
66
66
|
similar_names = if defined?(DidYouMean::SpellChecker)
|
67
67
|
spell_checker = DidYouMean::SpellChecker.new(dictionary: environments)
|
68
68
|
spell_checker.correct(name)
|
@@ -32,7 +32,9 @@ module RuboCop
|
|
32
32
|
# validates :foo, size: true
|
33
33
|
# validates :foo, uniqueness: true
|
34
34
|
#
|
35
|
-
class Validation <
|
35
|
+
class Validation < Base
|
36
|
+
extend AutoCorrector
|
37
|
+
|
36
38
|
MSG = 'Prefer the new style validations `%<prefer>s` over ' \
|
37
39
|
'`%<current>s`.'
|
38
40
|
|
@@ -50,22 +52,20 @@ module RuboCop
|
|
50
52
|
uniqueness
|
51
53
|
].freeze
|
52
54
|
|
53
|
-
|
55
|
+
RESTRICT_ON_SEND = TYPES.map { |p| "validates_#{p}_of".to_sym }.freeze
|
54
56
|
ALLOWLIST = TYPES.map { |p| "validates :column, #{p}: value" }.freeze
|
55
57
|
|
56
58
|
def on_send(node)
|
57
|
-
return
|
59
|
+
return if node.receiver
|
58
60
|
|
59
|
-
|
60
|
-
end
|
61
|
+
range = node.loc.selector
|
61
62
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
add_offense(range, message: message(node)) do |corrector|
|
64
|
+
last_argument = node.arguments.last
|
65
|
+
return if !last_argument.literal? && !last_argument.splat_type? &&
|
66
|
+
!frozen_array_argument?(last_argument)
|
66
67
|
|
67
|
-
|
68
|
-
corrector.replace(node.loc.selector, 'validates')
|
68
|
+
corrector.replace(range, 'validates')
|
69
69
|
correct_validate_type(corrector, node)
|
70
70
|
end
|
71
71
|
end
|
@@ -73,12 +73,13 @@ module RuboCop
|
|
73
73
|
private
|
74
74
|
|
75
75
|
def message(node)
|
76
|
-
|
77
|
-
|
76
|
+
method_name = node.method_name
|
77
|
+
|
78
|
+
format(MSG, prefer: preferred_method(method_name), current: method_name)
|
78
79
|
end
|
79
80
|
|
80
81
|
def preferred_method(method)
|
81
|
-
ALLOWLIST[
|
82
|
+
ALLOWLIST[RESTRICT_ON_SEND.index(method.to_sym)]
|
82
83
|
end
|
83
84
|
|
84
85
|
def correct_validate_type(corrector, node)
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop identifies places where manually constructed SQL
|
7
|
+
# in `where` can be replaced with `where(attribute: value)`.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# User.where('name = ?', 'Gabe')
|
12
|
+
# User.where('name = :name', name: 'Gabe')
|
13
|
+
# User.where('name IS NULL')
|
14
|
+
# User.where('name IN (?)', ['john', 'jane'])
|
15
|
+
# User.where('name IN (:names)', names: ['john', 'jane'])
|
16
|
+
# User.where('users.name = :name', name: 'Gabe')
|
17
|
+
#
|
18
|
+
# # good
|
19
|
+
# User.where(name: 'Gabe')
|
20
|
+
# User.where(name: nil)
|
21
|
+
# User.where(name: ['john', 'jane'])
|
22
|
+
# User.where(users: { name: 'Gabe' })
|
23
|
+
class WhereEquals < Base
|
24
|
+
include RangeHelp
|
25
|
+
extend AutoCorrector
|
26
|
+
|
27
|
+
MSG = 'Use `%<good_method>s` instead of manually constructing SQL.'
|
28
|
+
RESTRICT_ON_SEND = %i[where].freeze
|
29
|
+
|
30
|
+
def_node_matcher :where_method_call?, <<~PATTERN
|
31
|
+
{
|
32
|
+
(send _ :where (array $str_type? $_ ?))
|
33
|
+
(send _ :where $str_type? $_ ?)
|
34
|
+
}
|
35
|
+
PATTERN
|
36
|
+
|
37
|
+
def on_send(node)
|
38
|
+
where_method_call?(node) do |template_node, value_node|
|
39
|
+
value_node = value_node.first
|
40
|
+
|
41
|
+
range = offense_range(node)
|
42
|
+
|
43
|
+
column_and_value = extract_column_and_value(template_node, value_node)
|
44
|
+
return unless column_and_value
|
45
|
+
|
46
|
+
good_method = build_good_method(*column_and_value)
|
47
|
+
message = format(MSG, good_method: good_method)
|
48
|
+
|
49
|
+
add_offense(range, message: message) do |corrector|
|
50
|
+
corrector.replace(range, good_method)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
EQ_ANONYMOUS_RE = /\A([\w.]+)\s+=\s+\?\z/.freeze # column = ?
|
56
|
+
IN_ANONYMOUS_RE = /\A([\w.]+)\s+IN\s+\(\?\)\z/i.freeze # column IN (?)
|
57
|
+
EQ_NAMED_RE = /\A([\w.]+)\s+=\s+:(\w+)\z/.freeze # column = :column
|
58
|
+
IN_NAMED_RE = /\A([\w.]+)\s+IN\s+\(:(\w+)\)\z/i.freeze # column IN (:column)
|
59
|
+
IS_NULL_RE = /\A([\w.]+)\s+IS\s+NULL\z/i.freeze # column IS NULL
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def offense_range(node)
|
64
|
+
range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
|
65
|
+
end
|
66
|
+
|
67
|
+
def extract_column_and_value(template_node, value_node)
|
68
|
+
value =
|
69
|
+
case template_node.value
|
70
|
+
when EQ_ANONYMOUS_RE, IN_ANONYMOUS_RE
|
71
|
+
value_node.source
|
72
|
+
when EQ_NAMED_RE, IN_NAMED_RE
|
73
|
+
return unless value_node&.hash_type?
|
74
|
+
|
75
|
+
pair = value_node.pairs.find { |p| p.key.value.to_sym == Regexp.last_match(2).to_sym }
|
76
|
+
pair.value.source
|
77
|
+
when IS_NULL_RE
|
78
|
+
'nil'
|
79
|
+
else
|
80
|
+
return
|
81
|
+
end
|
82
|
+
|
83
|
+
[Regexp.last_match(1), value]
|
84
|
+
end
|
85
|
+
|
86
|
+
def build_good_method(column, value)
|
87
|
+
if column.include?('.')
|
88
|
+
table, column = column.split('.')
|
89
|
+
|
90
|
+
"where(#{table}: { #{column}: #{value} })"
|
91
|
+
else
|
92
|
+
"where(#{column}: #{value})"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|