rubocop-rails 2.20.2 → 2.21.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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +55 -6
  3. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +1 -1
  4. data/lib/rubocop/cop/rails/action_controller_test_case.rb +2 -2
  5. data/lib/rubocop/cop/rails/bulk_change_table.rb +20 -3
  6. data/lib/rubocop/cop/rails/dangerous_column_names.rb +439 -0
  7. data/lib/rubocop/cop/rails/date.rb +1 -1
  8. data/lib/rubocop/cop/rails/duplicate_association.rb +3 -0
  9. data/lib/rubocop/cop/rails/dynamic_find_by.rb +3 -3
  10. data/lib/rubocop/cop/rails/file_path.rb +4 -1
  11. data/lib/rubocop/cop/rails/freeze_time.rb +1 -1
  12. data/lib/rubocop/cop/rails/http_status.rb +4 -3
  13. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +63 -13
  14. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +7 -8
  15. data/lib/rubocop/cop/rails/rake_environment.rb +20 -4
  16. data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +145 -0
  17. data/lib/rubocop/cop/rails/reversible_migration.rb +1 -1
  18. data/lib/rubocop/cop/rails/root_pathname_methods.rb +38 -4
  19. data/lib/rubocop/cop/rails/save_bang.rb +2 -2
  20. data/lib/rubocop/cop/rails/schema_comment.rb +16 -10
  21. data/lib/rubocop/cop/rails/select_map.rb +75 -0
  22. data/lib/rubocop/cop/rails/time_zone.rb +12 -5
  23. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +29 -10
  24. data/lib/rubocop/cop/rails/unused_render_content.rb +68 -0
  25. data/lib/rubocop/cop/rails/where_exists.rb +0 -1
  26. data/lib/rubocop/cop/rails_cops.rb +4 -0
  27. data/lib/rubocop/rails/schema_loader.rb +1 -1
  28. data/lib/rubocop/rails/version.rb +1 -1
  29. data/lib/rubocop-rails.rb +8 -0
  30. metadata +7 -3
@@ -74,13 +74,13 @@ module RuboCop
74
74
  end
75
75
 
76
76
  def allowed_method?(node)
77
- return unless cop_config['AllowedMethods']
77
+ return false unless cop_config['AllowedMethods']
78
78
 
79
79
  cop_config['AllowedMethods'].include?(node.method_name.to_s)
80
80
  end
81
81
 
82
82
  def allowed_receiver?(node)
83
- return unless cop_config['AllowedReceivers'] && node.receiver
83
+ return false unless cop_config['AllowedReceivers'] && node.receiver
84
84
 
85
85
  cop_config['AllowedReceivers'].include?(node.receiver.source)
86
86
  end
@@ -88,7 +88,7 @@ module RuboCop
88
88
  # config option `WhiteList` will be deprecated soon
89
89
  def whitelisted?(node)
90
90
  whitelist_config = cop_config['Whitelist']
91
- return unless whitelist_config
91
+ return false unless whitelist_config
92
92
 
93
93
  whitelist_config.include?(node.method_name.to_s)
94
94
  end
@@ -180,6 +180,9 @@ module RuboCop
180
180
  side: :right
181
181
  )
182
182
  )
183
+ node.arguments.filter(&:str_type?).each do |argument|
184
+ corrector.replace(argument, argument.value.delete_prefix('/').inspect)
185
+ end
183
186
  corrector.insert_after(node, '.to_s')
184
187
  end
185
188
 
@@ -230,7 +233,7 @@ module RuboCop
230
233
  end
231
234
 
232
235
  def extension_node?(node)
233
- node&.str_type? && node.source.start_with?('.')
236
+ node&.str_type? && node.source.match?(/\A\.[A-Za-z]+/)
234
237
  end
235
238
  end
236
239
  end
@@ -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
@@ -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
 
@@ -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,145 @@
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
+ class RedundantActiveRecordAllMethod < Base
24
+ include ActiveRecordHelper
25
+ extend AutoCorrector
26
+
27
+ MSG = 'Redundant `all` detected.'
28
+
29
+ RESTRICT_ON_SEND = [:all].freeze
30
+
31
+ # Defined methods in `ActiveRecord::Querying::QUERYING_METHODS` on activerecord 7.0.5.
32
+ QUERYING_METHODS = %i[
33
+ and
34
+ annotate
35
+ any?
36
+ average
37
+ calculate
38
+ count
39
+ create_or_find_by
40
+ create_or_find_by!
41
+ create_with
42
+ delete_all
43
+ delete_by
44
+ destroy_all
45
+ destroy_by
46
+ distinct
47
+ eager_load
48
+ except
49
+ excluding
50
+ exists?
51
+ extending
52
+ extract_associated
53
+ fifth
54
+ fifth!
55
+ find
56
+ find_by
57
+ find_by!
58
+ find_each
59
+ find_in_batches
60
+ find_or_create_by
61
+ find_or_create_by!
62
+ find_or_initialize_by
63
+ find_sole_by
64
+ first
65
+ first!
66
+ first_or_create
67
+ first_or_create!
68
+ first_or_initialize
69
+ forty_two
70
+ forty_two!
71
+ fourth
72
+ fourth!
73
+ from
74
+ group
75
+ having
76
+ ids
77
+ in_batches
78
+ in_order_of
79
+ includes
80
+ invert_where
81
+ joins
82
+ last
83
+ last!
84
+ left_joins
85
+ left_outer_joins
86
+ limit
87
+ lock
88
+ many?
89
+ maximum
90
+ merge
91
+ minimum
92
+ none
93
+ none?
94
+ offset
95
+ one?
96
+ only
97
+ optimizer_hints
98
+ or
99
+ order
100
+ pick
101
+ pluck
102
+ preload
103
+ readonly
104
+ references
105
+ reorder
106
+ reselect
107
+ rewhere
108
+ second
109
+ second!
110
+ second_to_last
111
+ second_to_last!
112
+ select
113
+ sole
114
+ strict_loading
115
+ sum
116
+ take
117
+ take!
118
+ third
119
+ third!
120
+ third_to_last
121
+ third_to_last!
122
+ touch_all
123
+ unscope
124
+ update_all
125
+ where
126
+ without
127
+ ].freeze
128
+
129
+ def on_send(node)
130
+ query_node = node.parent
131
+
132
+ return unless query_node&.send_type?
133
+ return unless QUERYING_METHODS.include?(query_node.method_name)
134
+ return if node.receiver.nil? && !inherit_active_record_base?(node)
135
+
136
+ range_of_all_method = node.loc.selector
137
+ add_offense(range_of_all_method) do |collector|
138
+ collector.remove(range_of_all_method)
139
+ collector.remove(query_node.loc.dot)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ 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.send_node.method?(:reversible)) || ancestor.send_node.method?(:up_only)
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 `Dir`'s `children`, `each_child`, `entries`, and `glob`
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
- DIR_METHODS = %i[children delete each_child empty? entries exist? glob mkdir open rmdir unlink].to_set.freeze
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
- def_node_matcher :pathname_method, <<~PATTERN
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)
@@ -298,7 +298,7 @@ module RuboCop
298
298
 
299
299
  node = assignable_node(node)
300
300
  method, sibling_index = find_method_with_sibling_index(node.parent)
301
- return unless method && (method.def_type? || method.block_type?)
301
+ return false unless method && (method.def_type? || method.block_type?)
302
302
 
303
303
  method.children.size == node.sibling_index + sibling_index
304
304
  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 create_table?(node)
78
- if create_table_without_comment?(node)
79
- add_offense(node, message: TABLE_MSG)
80
- elsif create_table_column_call_without_comment?(node)
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 create_table_column_call_without_comment?(node)
97
- create_table_with_block?(node.parent) &&
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,75 @@
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
+ def autocorrect(corrector, select_node, node, preferred_method)
55
+ corrector.remove(select_node.loc.dot.begin.join(select_node.source_range.end))
56
+ corrector.replace(node.loc.selector.begin.join(node.source_range.end), preferred_method)
57
+ end
58
+
59
+ def match_column_name?(select_candidate, column_name)
60
+ return false unless select_candidate.arguments.one?
61
+ return false unless (first_argument = select_candidate.first_argument)
62
+
63
+ argument = case select_candidate.first_argument.type
64
+ when :sym
65
+ first_argument.source.delete_prefix(':')
66
+ when :str
67
+ first_argument.value if first_argument.respond_to?(:value)
68
+ end
69
+
70
+ argument == column_name
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end