rubocop-rails 2.20.2 → 2.25.0
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 +81 -12
- 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/mixin/target_rails_version.rb +29 -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 +16 -5
- 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 +94 -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 +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/skips_model_validations.rb +1 -1
- 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_ignored_columns.rb +6 -0
- data/lib/rubocop/cop/rails/unused_render_content.rb +67 -0
- data/lib/rubocop/cop/rails/validation.rb +6 -4
- 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/where_range.rb +157 -0
- data/lib/rubocop/cop/rails_cops.rb +7 -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 +31 -4
@@ -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)
|
@@ -13,6 +13,8 @@ module RuboCop
|
|
13
13
|
# error when rollback is desired, and to use `next` when commit is
|
14
14
|
# desired.
|
15
15
|
#
|
16
|
+
# If you are defining custom transaction methods, you can configure it with `TransactionMethods`.
|
17
|
+
#
|
16
18
|
# @example
|
17
19
|
# # bad
|
18
20
|
# ApplicationRecord.transaction do
|
@@ -50,12 +52,16 @@ module RuboCop
|
|
50
52
|
# # Commit
|
51
53
|
# next if user.active?
|
52
54
|
# end
|
55
|
+
#
|
56
|
+
# @example TransactionMethods: ["custom_transaction"]
|
57
|
+
# # bad
|
58
|
+
# CustomModel.custom_transaction do
|
59
|
+
# return if user.active?
|
60
|
+
# end
|
61
|
+
#
|
53
62
|
class TransactionExitStatement < Base
|
54
|
-
MSG =
|
55
|
-
|
56
|
-
MSG
|
57
|
-
|
58
|
-
RESTRICT_ON_SEND = %i[transaction with_lock].freeze
|
63
|
+
MSG = 'Exit statement `%<statement>s` is not allowed. Use `raise` (rollback) or `next` (commit).'
|
64
|
+
BUILT_IN_TRANSACTION_METHODS = %i[transaction with_lock].freeze
|
59
65
|
|
60
66
|
def_node_search :exit_statements, <<~PATTERN
|
61
67
|
({return | break | send nil? :throw} ...)
|
@@ -70,10 +76,9 @@ module RuboCop
|
|
70
76
|
PATTERN
|
71
77
|
|
72
78
|
def on_send(node)
|
73
|
-
return unless (
|
74
|
-
return unless parent.block_type? && parent.body
|
79
|
+
return unless in_transaction_block?(node)
|
75
80
|
|
76
|
-
exit_statements(parent.body).each do |statement_node|
|
81
|
+
exit_statements(node.parent.body).each do |statement_node|
|
77
82
|
next if statement_node.break_type? && nested_block?(statement_node)
|
78
83
|
|
79
84
|
statement = statement(statement_node)
|
@@ -85,6 +90,13 @@ module RuboCop
|
|
85
90
|
|
86
91
|
private
|
87
92
|
|
93
|
+
def in_transaction_block?(node)
|
94
|
+
return false unless transaction_method_name?(node.method_name)
|
95
|
+
return false unless (parent = node.parent)
|
96
|
+
|
97
|
+
parent.block_type? && parent.body
|
98
|
+
end
|
99
|
+
|
88
100
|
def statement(statement_node)
|
89
101
|
if statement_node.return_type?
|
90
102
|
'return'
|
@@ -96,9 +108,16 @@ module RuboCop
|
|
96
108
|
end
|
97
109
|
|
98
110
|
def nested_block?(statement_node)
|
99
|
-
|
111
|
+
name = statement_node.ancestors.find(&:block_type?).children.first.method_name
|
112
|
+
!transaction_method_name?(name)
|
113
|
+
end
|
114
|
+
|
115
|
+
def transaction_method_name?(method_name)
|
116
|
+
BUILT_IN_TRANSACTION_METHODS.include?(method_name) || transaction_method?(method_name)
|
117
|
+
end
|
100
118
|
|
101
|
-
|
119
|
+
def transaction_method?(method_name)
|
120
|
+
cop_config.fetch('TransactionMethods', []).include?(method_name.to_s)
|
102
121
|
end
|
103
122
|
end
|
104
123
|
end
|
@@ -68,15 +68,23 @@ module RuboCop
|
|
68
68
|
return unless uniq
|
69
69
|
|
70
70
|
add_offense(node.loc.selector) do |corrector|
|
71
|
-
|
72
|
-
|
73
|
-
corrector.remove(dot_method_with_whitespace(method, node))
|
74
|
-
corrector.insert_before(node.receiver.loc.dot.begin, '.distinct')
|
71
|
+
autocorrect(corrector, node)
|
75
72
|
end
|
76
73
|
end
|
77
74
|
|
78
75
|
private
|
79
76
|
|
77
|
+
def autocorrect(corrector, node)
|
78
|
+
method = node.method_name
|
79
|
+
|
80
|
+
corrector.remove(dot_method_with_whitespace(method, node))
|
81
|
+
if (dot = node.receiver.loc.dot)
|
82
|
+
corrector.insert_before(dot.begin, '.distinct')
|
83
|
+
else
|
84
|
+
corrector.insert_before(node.receiver, 'distinct.')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
80
88
|
def dot_method_with_whitespace(method, node)
|
81
89
|
range_between(dot_method_begin_pos(method, node), node.loc.selector.end_pos)
|
82
90
|
end
|
@@ -124,8 +124,8 @@ module RuboCop
|
|
124
124
|
end
|
125
125
|
|
126
126
|
def uniqueness_part(node)
|
127
|
-
pairs = node.
|
128
|
-
return unless pairs
|
127
|
+
pairs = node.last_argument
|
128
|
+
return unless pairs&.hash_type?
|
129
129
|
|
130
130
|
pairs.each_pair.find do |pair|
|
131
131
|
next unless pair.key.sym_type? && pair.key.value == :uniqueness
|
@@ -86,7 +86,11 @@ module RuboCop
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def environments
|
89
|
-
|
89
|
+
@environments ||= begin
|
90
|
+
environments = cop_config['Environments'].dup || []
|
91
|
+
environments << 'local' if target_rails_version >= 7.1
|
92
|
+
environments
|
93
|
+
end
|
90
94
|
end
|
91
95
|
end
|
92
96
|
end
|
@@ -7,6 +7,12 @@ module RuboCop
|
|
7
7
|
# `ignored_columns` is necessary to drop a column from RDBMS, but you don't need it after the migration
|
8
8
|
# to drop the column. You avoid forgetting to remove `ignored_columns` by this cop.
|
9
9
|
#
|
10
|
+
# IMPORTANT: This cop can't be used to effectively check for unused columns because the development
|
11
|
+
# and production schema can be out of sync until the migration has been run on production. As such,
|
12
|
+
# this cop can cause `ignored_columns` to be removed even though the production schema still contains
|
13
|
+
# the column, which can lead to downtime when the migration is actually executed. Only enable this cop
|
14
|
+
# if you know your migrations will be run before any of your Rails applications boot with the modified code.
|
15
|
+
#
|
10
16
|
# @example
|
11
17
|
# # bad
|
12
18
|
# class User < ApplicationRecord
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# If you try to render content along with a non-content status code (100-199, 204, 205, or 304),
|
7
|
+
# it will be dropped from the response.
|
8
|
+
#
|
9
|
+
# This cop checks for uses of `render` which specify both body content and a non-content status.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# # bad
|
13
|
+
# render 'foo', status: :continue
|
14
|
+
# render status: 100, plain: 'Ruby!'
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# head :continue
|
18
|
+
# head 100
|
19
|
+
class UnusedRenderContent < Base
|
20
|
+
include RangeHelp
|
21
|
+
|
22
|
+
MSG = 'Do not specify body content for a response with a non-content status code'
|
23
|
+
RESTRICT_ON_SEND = %i[render].freeze
|
24
|
+
NON_CONTENT_STATUS_CODES = Set[*100..199, 204, 205, 304] & ::Rack::Utils::SYMBOL_TO_STATUS_CODE.values
|
25
|
+
NON_CONTENT_STATUSES = Set[
|
26
|
+
*::Rack::Utils::SYMBOL_TO_STATUS_CODE.invert.fetch_values(*NON_CONTENT_STATUS_CODES)
|
27
|
+
]
|
28
|
+
BODY_OPTIONS = Set[
|
29
|
+
:action,
|
30
|
+
:body,
|
31
|
+
:content_type,
|
32
|
+
:file,
|
33
|
+
:html,
|
34
|
+
:inline,
|
35
|
+
:json,
|
36
|
+
:js,
|
37
|
+
:layout,
|
38
|
+
:plain,
|
39
|
+
:raw,
|
40
|
+
:template,
|
41
|
+
:text,
|
42
|
+
:xml
|
43
|
+
]
|
44
|
+
|
45
|
+
def_node_matcher :non_content_status?, <<~PATTERN
|
46
|
+
(pair
|
47
|
+
(sym :status)
|
48
|
+
{(sym NON_CONTENT_STATUSES) (int NON_CONTENT_STATUS_CODES)}
|
49
|
+
)
|
50
|
+
PATTERN
|
51
|
+
|
52
|
+
def_node_matcher :unused_render_content?, <<~PATTERN
|
53
|
+
(send nil? :render {
|
54
|
+
(hash <#non_content_status? $(pair (sym BODY_OPTIONS) _) ...>) |
|
55
|
+
$({str sym} _) (hash <#non_content_status? ...>)
|
56
|
+
})
|
57
|
+
PATTERN
|
58
|
+
|
59
|
+
def on_send(node)
|
60
|
+
unused_render_content?(node) do |unused_content_node|
|
61
|
+
add_offense(unused_content_node)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -29,7 +29,7 @@ module RuboCop
|
|
29
29
|
# validates :foo, numericality: true
|
30
30
|
# validates :foo, presence: true
|
31
31
|
# validates :foo, absence: true
|
32
|
-
# validates :foo,
|
32
|
+
# validates :foo, length: true
|
33
33
|
# validates :foo, uniqueness: true
|
34
34
|
#
|
35
35
|
class Validation < Base
|
@@ -51,7 +51,7 @@ module RuboCop
|
|
51
51
|
uniqueness
|
52
52
|
].freeze
|
53
53
|
|
54
|
-
RESTRICT_ON_SEND = TYPES.map { |p| "validates_#{p}_of"
|
54
|
+
RESTRICT_ON_SEND = TYPES.map { |p| :"validates_#{p}_of" }.freeze
|
55
55
|
ALLOWLIST = TYPES.map { |p| "validates :column, #{p}: value" }.freeze
|
56
56
|
|
57
57
|
def on_send(node)
|
@@ -60,7 +60,7 @@ module RuboCop
|
|
60
60
|
range = node.loc.selector
|
61
61
|
|
62
62
|
add_offense(range, message: message(node)) do |corrector|
|
63
|
-
last_argument = node.
|
63
|
+
last_argument = node.last_argument
|
64
64
|
return if !last_argument.literal? && !last_argument.splat_type? && !frozen_array_argument?(last_argument)
|
65
65
|
|
66
66
|
corrector.replace(range, 'validates')
|
@@ -120,7 +120,9 @@ module RuboCop
|
|
120
120
|
end
|
121
121
|
|
122
122
|
def validate_type(node)
|
123
|
-
node.method_name.to_s.split('_')[1]
|
123
|
+
type = node.method_name.to_s.split('_')[1]
|
124
|
+
|
125
|
+
type == 'size' ? 'length' : type
|
124
126
|
end
|
125
127
|
|
126
128
|
def frozen_array_argument?(argument)
|
@@ -33,8 +33,8 @@ module RuboCop
|
|
33
33
|
|
34
34
|
def_node_matcher :where_method_call?, <<~PATTERN
|
35
35
|
{
|
36
|
-
(
|
37
|
-
(
|
36
|
+
(call _ :where (array $str_type? $_ ?))
|
37
|
+
(call _ :where $str_type? $_ ?)
|
38
38
|
}
|
39
39
|
PATTERN
|
40
40
|
|
@@ -55,6 +55,7 @@ module RuboCop
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
58
|
+
alias on_csend on_send
|
58
59
|
|
59
60
|
EQ_ANONYMOUS_RE = /\A([\w.]+)\s+=\s+\?\z/.freeze # column = ?
|
60
61
|
IN_ANONYMOUS_RE = /\A([\w.]+)\s+IN\s+\(\?\)\z/i.freeze # column IN (?)
|
@@ -39,7 +39,6 @@ module RuboCop
|
|
39
39
|
# # bad
|
40
40
|
# User.exists?(name: 'john')
|
41
41
|
# User.exists?(['name = ?', 'john'])
|
42
|
-
# User.exists?('name = ?', 'john')
|
43
42
|
# user.posts.exists?(published: true)
|
44
43
|
#
|
45
44
|
# # good
|
@@ -56,11 +55,11 @@ module RuboCop
|
|
56
55
|
RESTRICT_ON_SEND = %i[exists?].freeze
|
57
56
|
|
58
57
|
def_node_matcher :where_exists_call?, <<~PATTERN
|
59
|
-
(
|
58
|
+
(call (call _ :where $...) :exists?)
|
60
59
|
PATTERN
|
61
60
|
|
62
61
|
def_node_matcher :exists_with_args?, <<~PATTERN
|
63
|
-
(
|
62
|
+
(call _ :exists? $...)
|
64
63
|
PATTERN
|
65
64
|
|
66
65
|
def on_send(node)
|
@@ -68,7 +67,7 @@ module RuboCop
|
|
68
67
|
return unless convertable_args?(args)
|
69
68
|
|
70
69
|
range = correction_range(node)
|
71
|
-
good_method = build_good_method(args)
|
70
|
+
good_method = build_good_method(args, dot: node.loc.dot)
|
72
71
|
message = format(MSG, good_method: good_method, bad_method: range.source)
|
73
72
|
|
74
73
|
add_offense(range, message: message) do |corrector|
|
@@ -76,6 +75,7 @@ module RuboCop
|
|
76
75
|
end
|
77
76
|
end
|
78
77
|
end
|
78
|
+
alias on_csend on_send
|
79
79
|
|
80
80
|
private
|
81
81
|
|
@@ -109,11 +109,11 @@ module RuboCop
|
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
112
|
-
def build_good_method(args)
|
112
|
+
def build_good_method(args, dot:)
|
113
113
|
if exists_style?
|
114
114
|
build_good_method_exists(args)
|
115
115
|
elsif where_style?
|
116
|
-
build_good_method_where(args)
|
116
|
+
build_good_method_where(args, dot&.source || '.')
|
117
117
|
end
|
118
118
|
end
|
119
119
|
|
@@ -125,11 +125,11 @@ module RuboCop
|
|
125
125
|
end
|
126
126
|
end
|
127
127
|
|
128
|
-
def build_good_method_where(args)
|
128
|
+
def build_good_method_where(args, dot_source)
|
129
129
|
if args.size > 1
|
130
|
-
"where(#{args.map(&:source).join(', ')})
|
130
|
+
"where(#{args.map(&:source).join(', ')})#{dot_source}exists?"
|
131
131
|
else
|
132
|
-
"where(#{args[0].source})
|
132
|
+
"where(#{args[0].source})#{dot_source}exists?"
|
133
133
|
end
|
134
134
|
end
|
135
135
|
end
|
@@ -36,7 +36,7 @@ module RuboCop
|
|
36
36
|
PATTERN
|
37
37
|
|
38
38
|
def on_send(node)
|
39
|
-
return unless node.first_argument
|
39
|
+
return unless node.first_argument&.sym_type?
|
40
40
|
|
41
41
|
root_receiver = root_receiver(node)
|
42
42
|
where_node_and_argument(root_receiver) do |where_node, where_argument|
|
@@ -89,16 +89,20 @@ module RuboCop
|
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
|
+
# rubocop:disable Metrics/AbcSize
|
92
93
|
def remove_where_method(corrector, node, where_node)
|
93
94
|
range = range_between(where_node.loc.selector.begin_pos, where_node.loc.end.end_pos)
|
94
95
|
if node.multiline? && !same_line?(node, where_node)
|
95
96
|
range = range_by_whole_lines(range, include_final_newline: true)
|
96
|
-
|
97
|
+
elsif where_node.receiver
|
97
98
|
corrector.remove(where_node.loc.dot)
|
99
|
+
else
|
100
|
+
corrector.remove(node.loc.dot)
|
98
101
|
end
|
99
102
|
|
100
103
|
corrector.remove(range)
|
101
104
|
end
|
105
|
+
# rubocop:enable Metrics/AbcSize
|
102
106
|
|
103
107
|
def same_line?(left_joins_node, where_node)
|
104
108
|
left_joins_node.loc.selector.line == where_node.loc.selector.line
|
@@ -32,8 +32,8 @@ module RuboCop
|
|
32
32
|
|
33
33
|
def_node_matcher :where_method_call?, <<~PATTERN
|
34
34
|
{
|
35
|
-
(
|
36
|
-
(
|
35
|
+
(call _ :where (array $str_type? $_ ?))
|
36
|
+
(call _ :where $str_type? $_ ?)
|
37
37
|
}
|
38
38
|
PATTERN
|
39
39
|
|
@@ -46,7 +46,7 @@ module RuboCop
|
|
46
46
|
column_and_value = extract_column_and_value(template_node, value_node)
|
47
47
|
return unless column_and_value
|
48
48
|
|
49
|
-
good_method = build_good_method(*column_and_value)
|
49
|
+
good_method = build_good_method(node.loc.dot&.source, *column_and_value)
|
50
50
|
message = format(MSG, good_method: good_method)
|
51
51
|
|
52
52
|
add_offense(range, message: message) do |corrector|
|
@@ -54,6 +54,7 @@ module RuboCop
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
57
|
+
alias on_csend on_send
|
57
58
|
|
58
59
|
NOT_EQ_ANONYMOUS_RE = /\A([\w.]+)\s+(?:!=|<>)\s+\?\z/.freeze # column != ?, column <> ?
|
59
60
|
NOT_IN_ANONYMOUS_RE = /\A([\w.]+)\s+NOT\s+IN\s+\(\?\)\z/i.freeze # column NOT IN (?)
|
@@ -86,13 +87,14 @@ module RuboCop
|
|
86
87
|
[Regexp.last_match(1), value]
|
87
88
|
end
|
88
89
|
|
89
|
-
def build_good_method(column, value)
|
90
|
+
def build_good_method(dot, column, value)
|
91
|
+
dot ||= '.'
|
90
92
|
if column.include?('.')
|
91
93
|
table, column = column.split('.')
|
92
94
|
|
93
|
-
"where
|
95
|
+
"where#{dot}not(#{table}: { #{column}: #{value} })"
|
94
96
|
else
|
95
|
-
"where
|
97
|
+
"where#{dot}not(#{column}: #{value})"
|
96
98
|
end
|
97
99
|
end
|
98
100
|
end
|