rubocop-rails 2.19.1 → 2.21.2

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -1
  3. data/config/default.yml +86 -12
  4. data/lib/rubocop/cop/mixin/index_method.rb +2 -2
  5. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +1 -1
  6. data/lib/rubocop/cop/rails/action_controller_test_case.rb +2 -2
  7. data/lib/rubocop/cop/rails/assert_not.rb +0 -1
  8. data/lib/rubocop/cop/rails/bulk_change_table.rb +20 -3
  9. data/lib/rubocop/cop/rails/dangerous_column_names.rb +439 -0
  10. data/lib/rubocop/cop/rails/date.rb +12 -3
  11. data/lib/rubocop/cop/rails/duplicate_association.rb +3 -0
  12. data/lib/rubocop/cop/rails/dynamic_find_by.rb +3 -3
  13. data/lib/rubocop/cop/rails/file_path.rb +129 -13
  14. data/lib/rubocop/cop/rails/find_each.rb +1 -1
  15. data/lib/rubocop/cop/rails/freeze_time.rb +1 -1
  16. data/lib/rubocop/cop/rails/http_status.rb +4 -3
  17. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +63 -13
  18. data/lib/rubocop/cop/rails/i18n_locale_texts.rb +5 -1
  19. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +22 -2
  20. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +9 -10
  21. data/lib/rubocop/cop/rails/not_null_column.rb +1 -1
  22. data/lib/rubocop/cop/rails/rake_environment.rb +20 -4
  23. data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +168 -0
  24. data/lib/rubocop/cop/rails/refute_methods.rb +0 -1
  25. data/lib/rubocop/cop/rails/reversible_migration.rb +1 -1
  26. data/lib/rubocop/cop/rails/root_pathname_methods.rb +38 -4
  27. data/lib/rubocop/cop/rails/save_bang.rb +2 -2
  28. data/lib/rubocop/cop/rails/schema_comment.rb +16 -10
  29. data/lib/rubocop/cop/rails/select_map.rb +78 -0
  30. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +0 -1
  31. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +2 -4
  32. data/lib/rubocop/cop/rails/time_zone.rb +12 -5
  33. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +35 -9
  34. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +15 -19
  35. data/lib/rubocop/cop/rails/unused_render_content.rb +67 -0
  36. data/lib/rubocop/cop/rails/where_exists.rb +0 -1
  37. data/lib/rubocop/cop/rails_cops.rb +4 -0
  38. data/lib/rubocop/rails/schema_loader/schema.rb +4 -4
  39. data/lib/rubocop/rails/schema_loader.rb +1 -1
  40. data/lib/rubocop/rails/version.rb +1 -1
  41. data/lib/rubocop-rails.rb +8 -0
  42. metadata +8 -4
@@ -35,6 +35,8 @@ module RuboCop
35
35
  # Rails.root.join('app', 'models', 'goober').to_s
36
36
  #
37
37
  class FilePath < Base
38
+ extend AutoCorrector
39
+
38
40
  include ConfigurableEnforcedStyle
39
41
  include RangeHelp
40
42
 
@@ -56,13 +58,10 @@ module RuboCop
56
58
 
57
59
  def on_dstr(node)
58
60
  return unless rails_root_nodes?(node)
59
- return unless node.children.last.str_type?
60
-
61
- last_child_source = node.children.last.source
62
- return unless last_child_source.start_with?('.') || last_child_source.include?(File::SEPARATOR)
63
- return if last_child_source.start_with?(':')
61
+ return if dstr_separated_by_colon?(node)
64
62
 
65
- register_offense(node, require_to_s: true)
63
+ check_for_slash_after_rails_root_in_dstr(node)
64
+ check_for_extension_after_rails_root_join_in_dstr(node)
66
65
  end
67
66
 
68
67
  def on_send(node)
@@ -73,11 +72,33 @@ module RuboCop
73
72
 
74
73
  private
75
74
 
75
+ def check_for_slash_after_rails_root_in_dstr(node)
76
+ rails_root_index = find_rails_root_index(node)
77
+ slash_node = node.children[rails_root_index + 1]
78
+ return unless slash_node&.str_type? && slash_node.source.start_with?(File::SEPARATOR)
79
+
80
+ register_offense(node, require_to_s: false) do |corrector|
81
+ autocorrect_slash_after_rails_root_in_dstr(corrector, node, rails_root_index)
82
+ end
83
+ end
84
+
85
+ def check_for_extension_after_rails_root_join_in_dstr(node)
86
+ rails_root_index = find_rails_root_index(node)
87
+ extension_node = node.children[rails_root_index + 1]
88
+ return unless extension_node?(extension_node)
89
+
90
+ register_offense(node, require_to_s: false) do |corrector|
91
+ autocorrect_extension_after_rails_root_join_in_dstr(corrector, node, rails_root_index, extension_node)
92
+ end
93
+ end
94
+
76
95
  def check_for_file_join_with_rails_root(node)
77
96
  return unless file_join_nodes?(node)
78
97
  return unless node.arguments.any? { |e| rails_root_nodes?(e) }
79
98
 
80
- register_offense(node, require_to_s: true)
99
+ register_offense(node, require_to_s: true) do |corrector|
100
+ autocorrect_file_join(corrector, node)
101
+ end
81
102
  end
82
103
 
83
104
  def check_for_rails_root_join_with_string_arguments(node)
@@ -87,7 +108,9 @@ module RuboCop
87
108
  return unless node.arguments.size > 1
88
109
  return unless node.arguments.all?(&:str_type?)
89
110
 
90
- register_offense(node, require_to_s: false)
111
+ register_offense(node, require_to_s: false) do |corrector|
112
+ autocorrect_rails_root_join_with_string_arguments(corrector, node)
113
+ end
91
114
  end
92
115
 
93
116
  def check_for_rails_root_join_with_slash_separated_path(node)
@@ -96,21 +119,22 @@ module RuboCop
96
119
  return unless rails_root_join_nodes?(node)
97
120
  return unless node.arguments.any? { |arg| string_with_slash?(arg) }
98
121
 
99
- register_offense(node, require_to_s: false)
122
+ register_offense(node, require_to_s: false) do |corrector|
123
+ autocorrect_rails_root_join_with_slash_separated_path(corrector, node)
124
+ end
100
125
  end
101
126
 
102
127
  def string_with_slash?(node)
103
- node.str_type? && node.source.include?('/')
128
+ node.str_type? && node.source.include?(File::SEPARATOR)
104
129
  end
105
130
 
106
- def register_offense(node, require_to_s:)
131
+ def register_offense(node, require_to_s:, &block)
107
132
  line_range = node.loc.column...node.loc.last_column
108
133
  source_range = source_range(processed_source.buffer, node.first_line, line_range)
109
- require_to_s = false if node.dstr_type?
110
134
 
111
135
  message = build_message(require_to_s)
112
136
 
113
- add_offense(source_range, message: message)
137
+ add_offense(source_range, message: message, &block)
114
138
  end
115
139
 
116
140
  def build_message(require_to_s)
@@ -119,6 +143,98 @@ module RuboCop
119
143
 
120
144
  format(message_template, to_s: to_s)
121
145
  end
146
+
147
+ def dstr_separated_by_colon?(node)
148
+ node.children[1..].any? do |child|
149
+ child.str_type? && child.source.start_with?(':')
150
+ end
151
+ end
152
+
153
+ def autocorrect_slash_after_rails_root_in_dstr(corrector, node, rails_root_index)
154
+ rails_root_node = node.children[rails_root_index].children.first
155
+ argument_source = extract_rails_root_join_argument_source(node, rails_root_index)
156
+ if rails_root_node.method?(:join)
157
+ append_argument(corrector, rails_root_node, argument_source)
158
+ else
159
+ replace_with_rails_root_join(corrector, rails_root_node, argument_source)
160
+ end
161
+ node.children[rails_root_index + 1..].each { |child| corrector.remove(child) }
162
+ end
163
+
164
+ def autocorrect_extension_after_rails_root_join_in_dstr(corrector, node, rails_root_index, extension_node)
165
+ rails_root_node = node.children[rails_root_index].children.first
166
+ return unless rails_root_node.arguments.last.str_type?
167
+
168
+ corrector.insert_before(rails_root_node.arguments.last.location.end, extension_node.source)
169
+ corrector.remove(extension_node)
170
+ end
171
+
172
+ def autocorrect_file_join(corrector, node)
173
+ corrector.replace(node.receiver, 'Rails.root')
174
+ corrector.remove(
175
+ range_with_surrounding_space(
176
+ range_with_surrounding_comma(
177
+ node.arguments.first.source_range,
178
+ :right
179
+ ),
180
+ side: :right
181
+ )
182
+ )
183
+ node.arguments.filter(&:str_type?).each do |argument|
184
+ corrector.replace(argument, argument.value.delete_prefix('/').inspect)
185
+ end
186
+ corrector.insert_after(node, '.to_s')
187
+ end
188
+
189
+ def autocorrect_rails_root_join_with_string_arguments(corrector, node)
190
+ corrector.replace(node.arguments.first, %("#{node.arguments.map(&:value).join('/')}"))
191
+ node.arguments[1..].each do |argument|
192
+ corrector.remove(
193
+ range_with_surrounding_comma(
194
+ range_with_surrounding_space(
195
+ argument.source_range,
196
+ side: :left
197
+ ),
198
+ :left
199
+ )
200
+ )
201
+ end
202
+ end
203
+
204
+ def autocorrect_rails_root_join_with_slash_separated_path(corrector, node)
205
+ node.arguments.each do |argument|
206
+ next unless string_with_slash?(argument)
207
+
208
+ index = argument.source.index(File::SEPARATOR)
209
+ rest = inner_range_of(argument).adjust(begin_pos: index - 1)
210
+ corrector.remove(rest)
211
+ corrector.insert_after(argument, %(, "#{rest.source.delete_prefix(File::SEPARATOR)}"))
212
+ end
213
+ end
214
+
215
+ def inner_range_of(node)
216
+ node.location.end.with(begin_pos: node.location.begin.end_pos).adjust(end_pos: -1)
217
+ end
218
+
219
+ def find_rails_root_index(node)
220
+ node.children.index { |child| rails_root_nodes?(child) }
221
+ end
222
+
223
+ def append_argument(corrector, node, argument_source)
224
+ corrector.insert_after(node.arguments.last, %(, "#{argument_source}"))
225
+ end
226
+
227
+ def replace_with_rails_root_join(corrector, node, argument_source)
228
+ corrector.replace(node, %<Rails.root.join("#{argument_source}")>)
229
+ end
230
+
231
+ def extract_rails_root_join_argument_source(node, rails_root_index)
232
+ node.children[rails_root_index + 1..].map(&:source).join.delete_prefix(File::SEPARATOR)
233
+ end
234
+
235
+ def extension_node?(node)
236
+ node&.str_type? && node.source.match?(/\A\.[A-Za-z]+/)
237
+ end
122
238
  end
123
239
  end
124
240
  end
@@ -34,7 +34,7 @@ module RuboCop
34
34
  RESTRICT_ON_SEND = %i[each].freeze
35
35
 
36
36
  SCOPE_METHODS = %i[
37
- all eager_load includes joins left_joins left_outer_joins not preload
37
+ all eager_load includes joins left_joins left_outer_joins not or preload
38
38
  references unscoped where
39
39
  ].freeze
40
40
 
@@ -69,7 +69,7 @@ module RuboCop
69
69
  return false unless CONVERT_METHODS.include?(method_name)
70
70
 
71
71
  child_node, child_method_name, time_argument = *node.children
72
- return if time_argument
72
+ return false if time_argument
73
73
 
74
74
  current_time?(child_node, child_method_name)
75
75
  end
@@ -8,6 +8,7 @@ module RuboCop
8
8
  # @example EnforcedStyle: symbolic (default)
9
9
  # # bad
10
10
  # render :foo, status: 200
11
+ # render :foo, status: '200'
11
12
  # render json: { foo: 'bar' }, status: 200
12
13
  # render plain: 'foo/bar', status: 304
13
14
  # redirect_to root_url, status: 301
@@ -50,7 +51,7 @@ module RuboCop
50
51
  PATTERN
51
52
 
52
53
  def_node_matcher :status_code, <<~PATTERN
53
- (hash <(pair (sym :status) ${int sym}) ...>)
54
+ (hash <(pair (sym :status) ${int sym str}) ...>)
54
55
  PATTERN
55
56
 
56
57
  def on_send(node)
@@ -108,7 +109,7 @@ module RuboCop
108
109
  private
109
110
 
110
111
  def symbol
111
- ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(number)
112
+ ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(number.to_i)
112
113
  end
113
114
 
114
115
  def number
@@ -133,7 +134,7 @@ module RuboCop
133
134
  end
134
135
 
135
136
  def offensive?
136
- !node.int_type? && !permitted_symbol?
137
+ !node.int_type? && !permitted_symbol? && number
137
138
  end
138
139
 
139
140
  def message
@@ -5,7 +5,13 @@ module RuboCop
5
5
  module Rails
6
6
  # Checks for places where I18n "lazy" lookup can be used.
7
7
  #
8
- # @example
8
+ # This cop has two different enforcement modes. When the EnforcedStyle
9
+ # is `lazy` (the default), explicit lookups are added as offenses.
10
+ #
11
+ # When the EnforcedStyle is `explicit` then lazy lookups are added as
12
+ # offenses.
13
+ #
14
+ # @example EnforcedStyle: lazy (default)
9
15
  # # en.yml
10
16
  # # en:
11
17
  # # books:
@@ -28,11 +34,29 @@ module RuboCop
28
34
  # end
29
35
  # end
30
36
  #
37
+ # @example EnforcedStyle: explicit
38
+ # # bad
39
+ # class BooksController < ApplicationController
40
+ # def create
41
+ # # ...
42
+ # redirect_to books_url, notice: t('.success')
43
+ # end
44
+ # end
45
+ #
46
+ # # good
47
+ # class BooksController < ApplicationController
48
+ # def create
49
+ # # ...
50
+ # redirect_to books_url, notice: t('books.create.success')
51
+ # end
52
+ # end
53
+ #
31
54
  class I18nLazyLookup < Base
55
+ include ConfigurableEnforcedStyle
32
56
  include VisibilityHelp
33
57
  extend AutoCorrector
34
58
 
35
- MSG = 'Use "lazy" lookup for the text used in controllers.'
59
+ MSG = 'Use %<style>s lookup for the text used in controllers.'
36
60
 
37
61
  RESTRICT_ON_SEND = %i[translate t].freeze
38
62
 
@@ -42,23 +66,45 @@ module RuboCop
42
66
 
43
67
  def on_send(node)
44
68
  translate_call?(node) do |key_node|
45
- key = key_node.value
46
- return if key.to_s.start_with?('.')
69
+ case style
70
+ when :lazy
71
+ handle_lazy_style(node, key_node)
72
+ when :explicit
73
+ handle_explicit_style(node, key_node)
74
+ end
75
+ end
76
+ end
77
+
78
+ private
47
79
 
48
- controller, action = controller_and_action(node)
49
- return unless controller && action
80
+ def handle_lazy_style(node, key_node)
81
+ key = key_node.value
82
+ return if key.to_s.start_with?('.')
50
83
 
51
- scoped_key = get_scoped_key(key_node, controller, action)
52
- return unless key == scoped_key
84
+ controller, action = controller_and_action(node)
85
+ return unless controller && action
53
86
 
54
- add_offense(key_node) do |corrector|
55
- unscoped_key = key_node.value.to_s.split('.').last
56
- corrector.replace(key_node, "'.#{unscoped_key}'")
57
- end
87
+ scoped_key = get_scoped_key(key_node, controller, action)
88
+ return unless key == scoped_key
89
+
90
+ add_offense(key_node) do |corrector|
91
+ unscoped_key = key_node.value.to_s.split('.').last
92
+ corrector.replace(key_node, "'.#{unscoped_key}'")
58
93
  end
59
94
  end
60
95
 
61
- private
96
+ def handle_explicit_style(node, key_node)
97
+ key = key_node.value
98
+ return unless key.to_s.start_with?('.')
99
+
100
+ controller, action = controller_and_action(node)
101
+ return unless controller && action
102
+
103
+ scoped_key = get_scoped_key(key_node, controller, action)
104
+ add_offense(key_node) do |corrector|
105
+ corrector.replace(key_node, "'#{scoped_key}'")
106
+ end
107
+ end
62
108
 
63
109
  def controller_and_action(node)
64
110
  action_node = node.each_ancestor(:def).first
@@ -90,6 +136,10 @@ module RuboCop
90
136
 
91
137
  path.delete_suffix('Controller').underscore
92
138
  end
139
+
140
+ def message(_range)
141
+ format(MSG, style: style)
142
+ end
93
143
  end
94
144
  end
95
145
  end
@@ -80,7 +80,11 @@ module RuboCop
80
80
  PATTERN
81
81
 
82
82
  def_node_matcher :flash_assignment?, <<~PATTERN
83
- (send (send nil? :flash) :[]= _ $str)
83
+ (send
84
+ {
85
+ (send nil? :flash)
86
+ (send (send nil? :flash) :now)
87
+ } :[]= _ $str)
84
88
  PATTERN
85
89
 
86
90
  def_node_search :mail_subject, <<~PATTERN
@@ -36,6 +36,10 @@ module RuboCop
36
36
  # if: -> { trusted_origin? && action_name != "admin" }
37
37
  # end
38
38
  class IgnoredSkipActionFilterOption < Base
39
+ extend AutoCorrector
40
+
41
+ include RangeHelp
42
+
39
43
  MSG = <<~MSG.chomp.freeze
40
44
  `%<ignore>s` option will be ignored when `%<prefer>s` and `%<ignore>s` are used together.
41
45
  MSG
@@ -60,9 +64,13 @@ module RuboCop
60
64
  options = options_hash(options)
61
65
 
62
66
  if if_and_only?(options)
63
- add_offense(options[:if], message: format(MSG, prefer: :only, ignore: :if))
67
+ add_offense(options[:if], message: format(MSG, prefer: :only, ignore: :if)) do |corrector|
68
+ remove_node_with_left_space_and_comma(corrector, options[:if])
69
+ end
64
70
  elsif if_and_except?(options)
65
- add_offense(options[:except], message: format(MSG, prefer: :if, ignore: :except))
71
+ add_offense(options[:except], message: format(MSG, prefer: :if, ignore: :except)) do |corrector|
72
+ remove_node_with_left_space_and_comma(corrector, options[:except])
73
+ end
66
74
  end
67
75
  end
68
76
 
@@ -81,6 +89,18 @@ module RuboCop
81
89
  def if_and_except?(options)
82
90
  options.key?(:if) && options.key?(:except)
83
91
  end
92
+
93
+ def remove_node_with_left_space_and_comma(corrector, node)
94
+ corrector.remove(
95
+ range_with_surrounding_comma(
96
+ range_with_surrounding_space(
97
+ node.source_range,
98
+ side: :left
99
+ ),
100
+ :left
101
+ )
102
+ )
103
+ end
84
104
  end
85
105
  end
86
106
  end
@@ -122,24 +122,23 @@ module RuboCop
122
122
  parent = node.each_ancestor(:class, :module).first
123
123
  return unless parent
124
124
 
125
+ # NOTE: a `:begin` node may not exist if the class/module consists of a single statement
125
126
  block = parent.each_child_node(:begin).first
126
- return unless block
127
-
128
127
  defined_action_methods = defined_action_methods(block)
129
128
 
130
- methods = array_values(methods_node).reject do |method|
131
- defined_action_methods.include?(method)
132
- end
129
+ unmatched_methods = array_values(methods_node) - defined_action_methods
130
+ return if unmatched_methods.empty?
133
131
 
134
- message = message(methods, parent)
135
- add_offense(node, message: message) unless methods.empty?
132
+ message = message(unmatched_methods, parent)
133
+ add_offense(node, message: message)
136
134
  end
137
135
 
138
136
  private
139
137
 
140
138
  def defined_action_methods(block)
141
- defined_methods = block.each_child_node(:def).map(&:method_name)
139
+ return [] unless block
142
140
 
141
+ defined_methods = block.each_child_node(:def).map(&:method_name)
143
142
  defined_methods + aliased_action_methods(block, defined_methods)
144
143
  end
145
144
 
@@ -176,14 +175,14 @@ module RuboCop
176
175
  when :sym
177
176
  [node.value]
178
177
  when :array
179
- node.values.map do |v|
178
+ node.values.filter_map do |v|
180
179
  case v.type
181
180
  when :str
182
181
  v.str_content.to_sym
183
182
  when :sym
184
183
  v.value
185
184
  end
186
- end.compact
185
+ end
187
186
  else
188
187
  []
189
188
  end
@@ -45,7 +45,7 @@ module RuboCop
45
45
 
46
46
  def check_add_column(node)
47
47
  add_not_null_column?(node) do |type, pairs|
48
- return if type.value == :virtual || type.value == 'virtual'
48
+ return if type.respond_to?(:value) && (type.value == :virtual || type.value == 'virtual')
49
49
 
50
50
  check_pairs(pairs)
51
51
  end
@@ -45,16 +45,24 @@ module RuboCop
45
45
  return if with_dependencies?(task_method)
46
46
 
47
47
  add_offense(task_method) do |corrector|
48
- task_name = task_method.arguments[0]
49
- task_dependency = correct_task_dependency(task_name)
50
-
51
- corrector.replace(task_name, task_dependency)
48
+ if with_arguments?(task_method)
49
+ new_task_dependency = correct_task_arguments_dependency(task_method)
50
+ corrector.replace(task_arguments(task_method), new_task_dependency)
51
+ else
52
+ task_name = task_method.first_argument
53
+ new_task_dependency = correct_task_dependency(task_name)
54
+ corrector.replace(task_name, new_task_dependency)
55
+ end
52
56
  end
53
57
  end
54
58
  end
55
59
 
56
60
  private
57
61
 
62
+ def correct_task_arguments_dependency(task_method)
63
+ "#{task_arguments(task_method).source} => :environment"
64
+ end
65
+
58
66
  def correct_task_dependency(task_name)
59
67
  if task_name.sym_type?
60
68
  "#{task_name.source.delete(':|\'|"')}: :environment"
@@ -80,6 +88,14 @@ module RuboCop
80
88
  end
81
89
  end
82
90
 
91
+ def task_arguments(node)
92
+ node.arguments[1]
93
+ end
94
+
95
+ def with_arguments?(node)
96
+ node.arguments.size > 1 && node.arguments[1].array_type?
97
+ end
98
+
83
99
  def with_dependencies?(node)
84
100
  first_arg = node.arguments[0]
85
101
  return false unless first_arg
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Detect redundant `all` used as a receiver for Active Record query methods.
7
+ #
8
+ # @safety
9
+ # This cop is unsafe for autocorrection if the receiver for `all` is not an Active Record object.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # User.all.find(id)
14
+ # User.all.order(:created_at)
15
+ # users.all.where(id: ids)
16
+ # user.articles.all.order(:created_at)
17
+ #
18
+ # # good
19
+ # User.find(id)
20
+ # User.order(:created_at)
21
+ # users.where(id: ids)
22
+ # user.articles.order(:created_at)
23
+ #
24
+ # @example AllowedReceivers: ['ActionMailer::Preview', 'ActiveSupport::TimeZone'] (default)
25
+ # # good
26
+ # ActionMailer::Preview.all.first
27
+ # ActiveSupport::TimeZone.all.first
28
+ class RedundantActiveRecordAllMethod < Base
29
+ include ActiveRecordHelper
30
+ include AllowedReceivers
31
+ include RangeHelp
32
+ extend AutoCorrector
33
+
34
+ MSG = 'Redundant `all` detected.'
35
+
36
+ RESTRICT_ON_SEND = [:all].freeze
37
+
38
+ # Defined methods in `ActiveRecord::Querying::QUERYING_METHODS` on activerecord 7.0.5.
39
+ QUERYING_METHODS = %i[
40
+ and
41
+ annotate
42
+ any?
43
+ average
44
+ calculate
45
+ count
46
+ create_or_find_by
47
+ create_or_find_by!
48
+ create_with
49
+ delete_all
50
+ delete_by
51
+ destroy_all
52
+ destroy_by
53
+ distinct
54
+ eager_load
55
+ except
56
+ excluding
57
+ exists?
58
+ extending
59
+ extract_associated
60
+ fifth
61
+ fifth!
62
+ find
63
+ find_by
64
+ find_by!
65
+ find_each
66
+ find_in_batches
67
+ find_or_create_by
68
+ find_or_create_by!
69
+ find_or_initialize_by
70
+ find_sole_by
71
+ first
72
+ first!
73
+ first_or_create
74
+ first_or_create!
75
+ first_or_initialize
76
+ forty_two
77
+ forty_two!
78
+ fourth
79
+ fourth!
80
+ from
81
+ group
82
+ having
83
+ ids
84
+ in_batches
85
+ in_order_of
86
+ includes
87
+ invert_where
88
+ joins
89
+ last
90
+ last!
91
+ left_joins
92
+ left_outer_joins
93
+ limit
94
+ lock
95
+ many?
96
+ maximum
97
+ merge
98
+ minimum
99
+ none
100
+ none?
101
+ offset
102
+ one?
103
+ only
104
+ optimizer_hints
105
+ or
106
+ order
107
+ pick
108
+ pluck
109
+ preload
110
+ readonly
111
+ references
112
+ reorder
113
+ reselect
114
+ rewhere
115
+ second
116
+ second!
117
+ second_to_last
118
+ second_to_last!
119
+ select
120
+ sole
121
+ strict_loading
122
+ sum
123
+ take
124
+ take!
125
+ third
126
+ third!
127
+ third_to_last
128
+ third_to_last!
129
+ touch_all
130
+ unscope
131
+ update_all
132
+ where
133
+ without
134
+ ].to_set.freeze
135
+
136
+ POSSIBLE_ENUMERABLE_BLOCK_METHODS = %i[any? count find none? one? select sum].freeze
137
+
138
+ def_node_matcher :followed_by_query_method?, <<~PATTERN
139
+ (send (send _ :all) QUERYING_METHODS ...)
140
+ PATTERN
141
+
142
+ def on_send(node)
143
+ return if !followed_by_query_method?(node.parent) || possible_enumerable_block_method?(node)
144
+ return if node.receiver ? allowed_receiver?(node.receiver) : !inherit_active_record_base?(node)
145
+
146
+ range_of_all_method = offense_range(node)
147
+ add_offense(range_of_all_method) do |collector|
148
+ collector.remove(range_of_all_method)
149
+ collector.remove(node.parent.loc.dot)
150
+ end
151
+ end
152
+
153
+ private
154
+
155
+ def possible_enumerable_block_method?(node)
156
+ parent = node.parent
157
+ return false unless POSSIBLE_ENUMERABLE_BLOCK_METHODS.include?(parent.method_name)
158
+
159
+ parent.parent&.block_type? || parent.parent&.numblock_type? || parent.first_argument&.block_pass_type?
160
+ end
161
+
162
+ def offense_range(node)
163
+ range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end