rubocop-rails 2.20.2 → 2.25.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -7
  3. data/config/default.yml +81 -12
  4. data/lib/rubocop/cop/mixin/active_record_helper.rb +15 -3
  5. data/lib/rubocop/cop/mixin/database_type_resolvable.rb +66 -0
  6. data/lib/rubocop/cop/mixin/index_method.rb +2 -2
  7. data/lib/rubocop/cop/mixin/target_rails_version.rb +29 -2
  8. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +3 -1
  9. data/lib/rubocop/cop/rails/action_controller_test_case.rb +2 -2
  10. data/lib/rubocop/cop/rails/action_filter.rb +3 -0
  11. data/lib/rubocop/cop/rails/active_record_aliases.rb +2 -2
  12. data/lib/rubocop/cop/rails/active_support_aliases.rb +6 -5
  13. data/lib/rubocop/cop/rails/active_support_on_load.rb +21 -1
  14. data/lib/rubocop/cop/rails/after_commit_override.rb +1 -1
  15. data/lib/rubocop/cop/rails/bulk_change_table.rb +8 -41
  16. data/lib/rubocop/cop/rails/content_tag.rb +1 -1
  17. data/lib/rubocop/cop/rails/dangerous_column_names.rb +446 -0
  18. data/lib/rubocop/cop/rails/date.rb +1 -1
  19. data/lib/rubocop/cop/rails/duplicate_association.rb +69 -12
  20. data/lib/rubocop/cop/rails/dynamic_find_by.rb +3 -3
  21. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +2 -2
  22. data/lib/rubocop/cop/rails/env_local.rb +46 -0
  23. data/lib/rubocop/cop/rails/expanded_date_range.rb +1 -1
  24. data/lib/rubocop/cop/rails/file_path.rb +9 -6
  25. data/lib/rubocop/cop/rails/find_by.rb +3 -3
  26. data/lib/rubocop/cop/rails/find_by_id.rb +9 -23
  27. data/lib/rubocop/cop/rails/freeze_time.rb +1 -1
  28. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +1 -1
  29. data/lib/rubocop/cop/rails/helper_instance_variable.rb +1 -1
  30. data/lib/rubocop/cop/rails/http_status.rb +16 -5
  31. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +63 -13
  32. data/lib/rubocop/cop/rails/inquiry.rb +1 -0
  33. data/lib/rubocop/cop/rails/inverse_of.rb +1 -1
  34. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +7 -8
  35. data/lib/rubocop/cop/rails/not_null_column.rb +94 -6
  36. data/lib/rubocop/cop/rails/output.rb +3 -2
  37. data/lib/rubocop/cop/rails/pick.rb +10 -5
  38. data/lib/rubocop/cop/rails/pluck.rb +1 -1
  39. data/lib/rubocop/cop/rails/pluck_id.rb +2 -1
  40. data/lib/rubocop/cop/rails/pluck_in_where.rb +18 -5
  41. data/lib/rubocop/cop/rails/rake_environment.rb +22 -6
  42. data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +219 -0
  43. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +7 -0
  44. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +1 -1
  45. data/lib/rubocop/cop/rails/response_parsed_body.rb +52 -10
  46. data/lib/rubocop/cop/rails/reversible_migration.rb +4 -4
  47. data/lib/rubocop/cop/rails/root_pathname_methods.rb +38 -4
  48. data/lib/rubocop/cop/rails/save_bang.rb +15 -8
  49. data/lib/rubocop/cop/rails/schema_comment.rb +16 -10
  50. data/lib/rubocop/cop/rails/select_map.rb +78 -0
  51. data/lib/rubocop/cop/rails/skips_model_validations.rb +1 -1
  52. data/lib/rubocop/cop/rails/time_zone.rb +13 -5
  53. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +29 -10
  54. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +12 -4
  55. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +2 -2
  56. data/lib/rubocop/cop/rails/unknown_env.rb +5 -1
  57. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +6 -0
  58. data/lib/rubocop/cop/rails/unused_render_content.rb +67 -0
  59. data/lib/rubocop/cop/rails/validation.rb +6 -4
  60. data/lib/rubocop/cop/rails/where_equals.rb +3 -2
  61. data/lib/rubocop/cop/rails/where_exists.rb +9 -9
  62. data/lib/rubocop/cop/rails/where_missing.rb +6 -2
  63. data/lib/rubocop/cop/rails/where_not.rb +8 -6
  64. data/lib/rubocop/cop/rails/where_range.rb +157 -0
  65. data/lib/rubocop/cop/rails_cops.rb +7 -0
  66. data/lib/rubocop/rails/schema_loader/schema.rb +3 -2
  67. data/lib/rubocop/rails/schema_loader.rb +5 -15
  68. data/lib/rubocop/rails/version.rb +1 -1
  69. data/lib/rubocop-rails.rb +8 -0
  70. 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
@@ -63,7 +63,7 @@ module RuboCop
63
63
  PATTERN
64
64
 
65
65
  def_node_matcher :good_insert?, <<~PATTERN
66
- (send _ {:insert :insert!} _ {
66
+ (call _ {:insert :insert!} _ {
67
67
  !(hash ...)
68
68
  (hash <(pair (sym !{:returning :unique_by}) _) ...>)
69
69
  } ...)
@@ -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 = <<~MSG.chomp
55
- Exit statement `%<statement>s` is not allowed. Use `raise` (rollback) or `next` (commit).
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 (parent = node.parent)
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
- block_node = statement_node.ancestors.find(&:block_type?)
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
- RESTRICT_ON_SEND.none? { |name| block_node.method?(name) }
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
- method = node.method_name
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.arguments.last
128
- return unless pairs.hash_type?
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
- cop_config['Environments']
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, size: true
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".to_sym }.freeze
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.arguments.last
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
- (send _ :where (array $str_type? $_ ?))
37
- (send _ :where $str_type? $_ ?)
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
- (send (send _ :where $...) :exists?)
58
+ (call (call _ :where $...) :exists?)
60
59
  PATTERN
61
60
 
62
61
  def_node_matcher :exists_with_args?, <<~PATTERN
63
- (send _ :exists? $...)
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(', ')}).exists?"
130
+ "where(#{args.map(&:source).join(', ')})#{dot_source}exists?"
131
131
  else
132
- "where(#{args[0].source}).exists?"
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.sym_type?
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
- else
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
- (send _ :where (array $str_type? $_ ?))
36
- (send _ :where $str_type? $_ ?)
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.not(#{table}: { #{column}: #{value} })"
95
+ "where#{dot}not(#{table}: { #{column}: #{value} })"
94
96
  else
95
- "where.not(#{column}: #{value})"
97
+ "where#{dot}not(#{column}: #{value})"
96
98
  end
97
99
  end
98
100
  end