rubocop-rails 2.7.0 → 2.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -0
  3. data/config/default.yml +78 -4
  4. data/lib/rubocop/cop/mixin/active_record_helper.rb +5 -3
  5. data/lib/rubocop/cop/mixin/enforce_superclass.rb +40 -0
  6. data/lib/rubocop/cop/mixin/index_method.rb +25 -11
  7. data/lib/rubocop/cop/rails/action_filter.rb +10 -14
  8. data/lib/rubocop/cop/rails/active_record_aliases.rb +13 -17
  9. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +19 -16
  10. data/lib/rubocop/cop/rails/active_record_override.rb +1 -1
  11. data/lib/rubocop/cop/rails/active_support_aliases.rb +12 -21
  12. data/lib/rubocop/cop/rails/after_commit_override.rb +91 -0
  13. data/lib/rubocop/cop/rails/application_controller.rb +3 -7
  14. data/lib/rubocop/cop/rails/application_job.rb +2 -1
  15. data/lib/rubocop/cop/rails/application_mailer.rb +2 -7
  16. data/lib/rubocop/cop/rails/application_record.rb +2 -7
  17. data/lib/rubocop/cop/rails/arel_star.rb +41 -0
  18. data/lib/rubocop/cop/rails/assert_not.rb +8 -10
  19. data/lib/rubocop/cop/rails/attribute_default_block_value.rb +90 -0
  20. data/lib/rubocop/cop/rails/belongs_to.rb +9 -18
  21. data/lib/rubocop/cop/rails/blank.rb +27 -27
  22. data/lib/rubocop/cop/rails/bulk_change_table.rb +1 -1
  23. data/lib/rubocop/cop/rails/content_tag.rb +17 -17
  24. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +2 -1
  25. data/lib/rubocop/cop/rails/date.rb +10 -11
  26. data/lib/rubocop/cop/rails/default_scope.rb +11 -4
  27. data/lib/rubocop/cop/rails/delegate.rb +9 -9
  28. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +7 -8
  29. data/lib/rubocop/cop/rails/dynamic_find_by.rb +13 -11
  30. data/lib/rubocop/cop/rails/enum_hash.rb +11 -10
  31. data/lib/rubocop/cop/rails/enum_uniqueness.rb +2 -1
  32. data/lib/rubocop/cop/rails/environment_comparison.rb +18 -14
  33. data/lib/rubocop/cop/rails/exit.rb +4 -10
  34. data/lib/rubocop/cop/rails/file_path.rb +5 -4
  35. data/lib/rubocop/cop/rails/find_by.rb +13 -13
  36. data/lib/rubocop/cop/rails/find_by_id.rb +12 -21
  37. data/lib/rubocop/cop/rails/find_each.rb +17 -18
  38. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +3 -2
  39. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +4 -7
  40. data/lib/rubocop/cop/rails/helper_instance_variable.rb +30 -2
  41. data/lib/rubocop/cop/rails/http_positional_arguments.rb +25 -21
  42. data/lib/rubocop/cop/rails/http_status.rb +7 -9
  43. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +8 -6
  44. data/lib/rubocop/cop/rails/index_by.rb +11 -2
  45. data/lib/rubocop/cop/rails/index_with.rb +11 -2
  46. data/lib/rubocop/cop/rails/inquiry.rb +7 -2
  47. data/lib/rubocop/cop/rails/inverse_of.rb +3 -2
  48. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +17 -15
  49. data/lib/rubocop/cop/rails/link_to_blank.rb +20 -22
  50. data/lib/rubocop/cop/rails/mailer_name.rb +19 -13
  51. data/lib/rubocop/cop/rails/match_route.rb +16 -13
  52. data/lib/rubocop/cop/rails/negate_include.rb +10 -8
  53. data/lib/rubocop/cop/rails/not_null_column.rb +2 -1
  54. data/lib/rubocop/cop/rails/order_by_id.rb +52 -0
  55. data/lib/rubocop/cop/rails/output.rb +5 -2
  56. data/lib/rubocop/cop/rails/output_safety.rb +3 -2
  57. data/lib/rubocop/cop/rails/pick.rb +14 -12
  58. data/lib/rubocop/cop/rails/pluck.rb +6 -9
  59. data/lib/rubocop/cop/rails/pluck_id.rb +4 -6
  60. data/lib/rubocop/cop/rails/pluck_in_where.rb +39 -5
  61. data/lib/rubocop/cop/rails/pluralization_grammar.rb +10 -14
  62. data/lib/rubocop/cop/rails/presence.rb +12 -13
  63. data/lib/rubocop/cop/rails/present.rb +30 -24
  64. data/lib/rubocop/cop/rails/rake_environment.rb +9 -11
  65. data/lib/rubocop/cop/rails/read_write_attribute.rb +12 -11
  66. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +29 -31
  67. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +9 -12
  68. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +11 -10
  69. data/lib/rubocop/cop/rails/reflection_class_name.rb +4 -3
  70. data/lib/rubocop/cop/rails/refute_methods.rb +9 -10
  71. data/lib/rubocop/cop/rails/relative_date_constant.rb +20 -9
  72. data/lib/rubocop/cop/rails/render_inline.rb +5 -12
  73. data/lib/rubocop/cop/rails/render_plain_text.rb +9 -14
  74. data/lib/rubocop/cop/rails/request_referer.rb +7 -7
  75. data/lib/rubocop/cop/rails/reversible_migration.rb +82 -7
  76. data/lib/rubocop/cop/rails/safe_navigation.rb +11 -10
  77. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +5 -10
  78. data/lib/rubocop/cop/rails/save_bang.rb +19 -22
  79. data/lib/rubocop/cop/rails/scope_args.rb +2 -1
  80. data/lib/rubocop/cop/rails/short_i18n.rb +7 -9
  81. data/lib/rubocop/cop/rails/skips_model_validations.rb +4 -4
  82. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +82 -0
  83. data/lib/rubocop/cop/rails/time_zone.rb +22 -20
  84. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +6 -6
  85. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +18 -8
  86. data/lib/rubocop/cop/rails/unknown_env.rb +15 -4
  87. data/lib/rubocop/cop/rails/validation.rb +15 -14
  88. data/lib/rubocop/cop/rails/where_equals.rb +98 -0
  89. data/lib/rubocop/cop/rails/where_exists.rb +74 -16
  90. data/lib/rubocop/cop/rails/where_not.rb +97 -0
  91. data/lib/rubocop/cop/rails_cops.rb +8 -0
  92. data/lib/rubocop/rails/schema_loader.rb +4 -4
  93. data/lib/rubocop/rails/schema_loader/schema.rb +5 -5
  94. data/lib/rubocop/rails/version.rb +5 -1
  95. metadata +23 -9
@@ -19,11 +19,13 @@ module RuboCop
19
19
  #
20
20
  # # good
21
21
  # request.referrer
22
- class RequestReferer < Cop
22
+ class RequestReferer < Base
23
23
  include ConfigurableEnforcedStyle
24
+ extend AutoCorrector
24
25
 
25
26
  MSG = 'Use `request.%<prefer>s` instead of ' \
26
27
  '`request.%<current>s`.'
28
+ RESTRICT_ON_SEND = %i[referer referrer].freeze
27
29
 
28
30
  def_node_matcher :referer?, <<~PATTERN
29
31
  (send (send nil? :request) {:referer :referrer})
@@ -33,17 +35,15 @@ module RuboCop
33
35
  referer?(node) do
34
36
  return unless node.method?(wrong_method_name)
35
37
 
36
- add_offense(node.source_range, location: node.source_range)
38
+ add_offense(node.source_range) do |corrector|
39
+ corrector.replace(node, "request.#{style}")
40
+ end
37
41
  end
38
42
  end
39
43
 
40
- def autocorrect(node)
41
- ->(corrector) { corrector.replace(node, "request.#{style}") }
42
- end
43
-
44
44
  private
45
45
 
46
- def message(_node)
46
+ def message(_range)
47
47
  format(MSG, prefer: style, current: wrong_method_name)
48
48
  end
49
49
 
@@ -129,8 +129,53 @@ module RuboCop
129
129
  # end
130
130
  # end
131
131
  #
132
+ # @example
133
+ # # remove_columns
134
+ #
135
+ # # bad
136
+ # def change
137
+ # remove_columns :users, :name, :email
138
+ # end
139
+ #
140
+ # # good
141
+ # def change
142
+ # reversible do |dir|
143
+ # dir.up do
144
+ # remove_columns :users, :name, :email
145
+ # end
146
+ #
147
+ # dir.down do
148
+ # add_column :users, :name, :string
149
+ # add_column :users, :email, :string
150
+ # end
151
+ # end
152
+ # end
153
+ #
154
+ # # good (Rails >= 6.1, see https://github.com/rails/rails/pull/36589)
155
+ # def change
156
+ # remove_columns :users, :name, :email, type: :string
157
+ # end
158
+ #
159
+ # @example
160
+ # # remove_index
161
+ #
162
+ # # bad
163
+ # def change
164
+ # remove_index :users, name: :index_users_on_email
165
+ # end
166
+ #
167
+ # # good
168
+ # def change
169
+ # remove_index :users, :email
170
+ # end
171
+ #
172
+ # # good
173
+ # def change
174
+ # remove_index :users, column: :email
175
+ # end
176
+ #
132
177
  # @see https://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html
133
- class ReversibleMigration < Cop
178
+ class ReversibleMigration < Base
134
179
  MSG = '%<action>s is not reversible.'
135
180
 
136
181
  def_node_matcher :irreversible_schema_statement_call, <<~PATTERN
@@ -153,6 +198,14 @@ module RuboCop
153
198
  (send nil? :change_table $_ ...)
154
199
  PATTERN
155
200
 
201
+ def_node_matcher :remove_columns_call, <<~PATTERN
202
+ (send nil? :remove_columns ... $_)
203
+ PATTERN
204
+
205
+ def_node_matcher :remove_index_call, <<~PATTERN
206
+ (send nil? :remove_index _ $_)
207
+ PATTERN
208
+
156
209
  def on_send(node)
157
210
  return unless within_change_method?(node)
158
211
  return if within_reversible_or_up_only_block?(node)
@@ -162,6 +215,8 @@ module RuboCop
162
215
  check_reversible_hash_node(node)
163
216
  check_remove_column_node(node)
164
217
  check_remove_foreign_key_node(node)
218
+ check_remove_columns_node(node)
219
+ check_remove_index_node(node)
165
220
  end
166
221
 
167
222
  def on_block(node)
@@ -216,11 +271,7 @@ module RuboCop
216
271
  def check_remove_foreign_key_node(node)
217
272
  remove_foreign_key_call(node) do |arg|
218
273
  if arg.hash_type? && !all_hash_key?(arg, :to_table)
219
- add_offense(
220
- node,
221
- message: format(MSG,
222
- action: 'remove_foreign_key(without table)')
223
- )
274
+ add_offense(node, message: format(MSG, action: 'remove_foreign_key(without table)'))
224
275
  end
225
276
  end
226
277
  end
@@ -237,6 +288,30 @@ module RuboCop
237
288
  end
238
289
  end
239
290
 
291
+ def check_remove_columns_node(node)
292
+ remove_columns_call(node) do |args|
293
+ unless all_hash_key?(args, :type) && target_rails_version >= 6.1
294
+ action = target_rails_version >= 6.1 ? 'remove_columns(without type)' : 'remove_columns'
295
+
296
+ add_offense(
297
+ node,
298
+ message: format(MSG, action: action)
299
+ )
300
+ end
301
+ end
302
+ end
303
+
304
+ def check_remove_index_node(node)
305
+ remove_index_call(node) do |args|
306
+ if args.hash_type? && !all_hash_key?(args, :column)
307
+ add_offense(
308
+ node,
309
+ message: format(MSG, action: 'remove_index(without column)')
310
+ )
311
+ end
312
+ end
313
+ end
314
+
240
315
  def check_change_table_offense(receiver, node)
241
316
  method_name = node.method_name
242
317
  return if receiver != node.receiver &&
@@ -281,7 +356,7 @@ module RuboCop
281
356
  key.children.first.to_sym
282
357
  end
283
358
 
284
- hash_keys & keys == keys
359
+ (hash_keys & keys).sort == keys
285
360
  end
286
361
  end
287
362
  end
@@ -36,10 +36,12 @@ module RuboCop
36
36
  # foo&.bar
37
37
  # foo&.bar(baz)
38
38
  # foo&.bar { |e| e.baz }
39
- class SafeNavigation < Cop
39
+ class SafeNavigation < Base
40
40
  include RangeHelp
41
+ extend AutoCorrector
41
42
 
42
43
  MSG = 'Use safe navigation (`&.`) instead of `%<try>s`.'
44
+ RESTRICT_ON_SEND = %i[try try!].freeze
43
45
 
44
46
  def_node_matcher :try_call, <<~PATTERN
45
47
  (send !nil? ${:try :try!} $_ ...)
@@ -50,24 +52,23 @@ module RuboCop
50
52
  return if try_method == :try && !cop_config['ConvertTry']
51
53
  return unless dispatch.sym_type? && dispatch.value.match?(/\w+[=!?]?/)
52
54
 
53
- add_offense(node, message: format(MSG, try: try_method))
55
+ add_offense(node, message: format(MSG, try: try_method)) do |corrector|
56
+ autocorrect(corrector, node)
57
+ end
54
58
  end
55
59
  end
56
60
 
57
- def autocorrect(node)
61
+ private
62
+
63
+ def autocorrect(corrector, node)
58
64
  method_node, *params = *node.arguments
59
65
  method = method_node.source[1..-1]
60
66
 
61
- range = range_between(node.loc.dot.begin_pos,
62
- node.loc.expression.end_pos)
67
+ range = range_between(node.loc.dot.begin_pos, node.loc.expression.end_pos)
63
68
 
64
- lambda do |corrector|
65
- corrector.replace(range, replacement(method, params))
66
- end
69
+ corrector.replace(range, replacement(method, params))
67
70
  end
68
71
 
69
- private
70
-
71
72
  def replacement(method, params)
72
73
  new_params = params.map(&:source).join(', ')
73
74
 
@@ -19,7 +19,9 @@ module RuboCop
19
19
  # do_something if foo.blank?
20
20
  # do_something unless foo.blank?
21
21
  #
22
- class SafeNavigationWithBlank < Cop
22
+ class SafeNavigationWithBlank < Base
23
+ extend AutoCorrector
24
+
23
25
  MSG =
24
26
  'Avoid calling `blank?` with the safe navigation operator ' \
25
27
  'in conditionals.'
@@ -31,15 +33,8 @@ module RuboCop
31
33
  def on_if(node)
32
34
  return unless safe_navigation_blank_in_conditional?(node)
33
35
 
34
- add_offense(node)
35
- end
36
-
37
- def autocorrect(node)
38
- lambda do |corrector|
39
- corrector.replace(
40
- safe_navigation_blank_in_conditional?(node).location.dot,
41
- '.'
42
- )
36
+ add_offense(node) do |corrector|
37
+ corrector.replace(safe_navigation_blank_in_conditional?(node).location.dot, '.')
43
38
  end
44
39
  end
45
40
  end
@@ -98,8 +98,9 @@ module RuboCop
98
98
  # Services::Service::Mailer.update(message: 'Message')
99
99
  # Service::Mailer::update
100
100
  #
101
- class SaveBang < Cop
101
+ class SaveBang < Base
102
102
  include NegativeConditional
103
+ extend AutoCorrector
103
104
 
104
105
  MSG = 'Use `%<prefer>s` instead of `%<current>s` if the return ' \
105
106
  'value is not checked.'
@@ -113,11 +114,10 @@ module RuboCop
113
114
  first_or_create find_or_create_by].freeze
114
115
  MODIFY_PERSIST_METHODS = %i[save
115
116
  update update_attributes destroy].freeze
116
- PERSIST_METHODS = (CREATE_PERSIST_METHODS +
117
- MODIFY_PERSIST_METHODS).freeze
117
+ RESTRICT_ON_SEND = (CREATE_PERSIST_METHODS + MODIFY_PERSIST_METHODS).freeze
118
118
 
119
- def join_force?(force_class)
120
- force_class == VariableForce
119
+ def self.joining_forces
120
+ VariableForce
121
121
  end
122
122
 
123
123
  def after_leaving_scope(scope, _variable_table)
@@ -135,10 +135,10 @@ module RuboCop
135
135
  return unless persist_method?(node, CREATE_PERSIST_METHODS)
136
136
  return if persisted_referenced?(assignment)
137
137
 
138
- add_offense_for_node(node, CREATE_MSG)
138
+ register_offense(node, CREATE_MSG)
139
139
  end
140
140
 
141
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
141
+ # rubocop:disable Metrics/CyclomaticComplexity
142
142
  def on_send(node)
143
143
  return unless persist_method?(node)
144
144
  return if return_value_assigned?(node)
@@ -148,25 +148,22 @@ module RuboCop
148
148
  return if explicit_return?(node)
149
149
  return if checked_immediately?(node)
150
150
 
151
- add_offense_for_node(node)
151
+ register_offense(node, MSG)
152
152
  end
153
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
153
+ # rubocop:enable Metrics/CyclomaticComplexity
154
154
  alias on_csend on_send
155
155
 
156
- def autocorrect(node)
157
- save_loc = node.loc.selector
158
- new_method = "#{node.method_name}!"
159
-
160
- ->(corrector) { corrector.replace(save_loc, new_method) }
161
- end
162
-
163
156
  private
164
157
 
165
- def add_offense_for_node(node, msg = MSG)
166
- name = node.method_name
167
- full_message = format(msg, prefer: "#{name}!", current: name.to_s)
158
+ def register_offense(node, msg)
159
+ current_method = node.method_name
160
+ bang_method = "#{current_method}!"
161
+ full_message = format(msg, prefer: bang_method, current: current_method)
168
162
 
169
- add_offense(node, location: :selector, message: full_message)
163
+ range = node.loc.selector
164
+ add_offense(range, message: full_message) do |corrector|
165
+ corrector.replace(range, bang_method)
166
+ end
170
167
  end
171
168
 
172
169
  def right_assignment_node(assignment)
@@ -218,7 +215,7 @@ module RuboCop
218
215
  def check_used_in_condition_or_compound_boolean(node)
219
216
  return false unless in_condition_or_compound_boolean?(node)
220
217
 
221
- add_offense_for_node(node, CREATE_CONDITIONAL_MSG) unless MODIFY_PERSIST_METHODS.include?(node.method_name)
218
+ register_offense(node, CREATE_CONDITIONAL_MSG) unless MODIFY_PERSIST_METHODS.include?(node.method_name)
222
219
 
223
220
  true
224
221
  end
@@ -318,7 +315,7 @@ module RuboCop
318
315
  assignment&.lvasgn_type?
319
316
  end
320
317
 
321
- def persist_method?(node, methods = PERSIST_METHODS)
318
+ def persist_method?(node, methods = RESTRICT_ON_SEND)
322
319
  methods.include?(node.method_name) &&
323
320
  expected_signature?(node) &&
324
321
  !allowed_receiver?(node)
@@ -13,8 +13,9 @@ module RuboCop
13
13
  #
14
14
  # # good
15
15
  # scope :something, -> { where(something: true) }
16
- class ScopeArgs < Cop
16
+ class ScopeArgs < Base
17
17
  MSG = 'Use `lambda`/`proc` instead of a plain method call.'
18
+ RESTRICT_ON_SEND = %i[scope].freeze
18
19
 
19
20
  def_node_matcher :scope?, '(send nil? :scope _ $send)'
20
21
 
@@ -38,8 +38,9 @@ module RuboCop
38
38
  # t :key
39
39
  # l Time.now
40
40
  #
41
- class ShortI18n < Cop
41
+ class ShortI18n < Base
42
42
  include ConfigurableEnforcedStyle
43
+ extend AutoCorrector
43
44
 
44
45
  MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
45
46
 
@@ -48,6 +49,8 @@ module RuboCop
48
49
  localize: :l
49
50
  }.freeze
50
51
 
52
+ RESTRICT_ON_SEND = PREFERRED_METHODS.keys.freeze
53
+
51
54
  def_node_matcher :long_i18n?, <<~PATTERN
52
55
  (send {nil? (const nil? :I18n)} ${:translate :localize} ...)
53
56
  PATTERN
@@ -58,15 +61,10 @@ module RuboCop
58
61
  long_i18n?(node) do |method_name|
59
62
  good_method = PREFERRED_METHODS[method_name]
60
63
  message = format(MSG, good_method: good_method, bad_method: method_name)
64
+ range = node.loc.selector
61
65
 
62
- add_offense(node, location: :selector, message: message)
63
- end
64
- end
65
-
66
- def autocorrect(node)
67
- long_i18n?(node) do |method_name|
68
- lambda do |corrector|
69
- corrector.replace(node.loc.selector, PREFERRED_METHODS[method_name])
66
+ add_offense(range, message: message) do |corrector|
67
+ corrector.replace(range, PREFERRED_METHODS[method_name])
70
68
  end
71
69
  end
72
70
  end
@@ -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 `Whitelist`.
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 Whitelist: ["touch"]
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 < Cop
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, location: :selector)
79
+ add_offense(node.loc.selector, message: message(node))
80
80
  end
81
81
  alias on_csend on_send
82
82
 
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ #
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.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # <<-SQL
14
+ # SELECT * FROM posts;
15
+ # SQL
16
+ #
17
+ # <<-SQL
18
+ # SELECT * FROM posts
19
+ # WHERE id = 1
20
+ # SQL
21
+ #
22
+ # execute(<<~SQL, "Post Load")
23
+ # SELECT * FROM posts
24
+ # WHERE post_id = 1
25
+ # SQL
26
+ #
27
+ # # good
28
+ # <<-SQL.squish
29
+ # SELECT * FROM posts;
30
+ # SQL
31
+ #
32
+ # <<~SQL.squish
33
+ # SELECT * FROM table
34
+ # WHERE id = 1
35
+ # SQL
36
+ #
37
+ # execute(<<~SQL.squish, "Post Load")
38
+ # SELECT * FROM posts
39
+ # WHERE post_id = 1
40
+ # SQL
41
+ #
42
+ class SquishedSQLHeredocs < Base
43
+ include Heredoc
44
+ extend AutoCorrector
45
+
46
+ SQL = 'SQL'
47
+ SQUISH = '.squish'
48
+ MSG = 'Use `%<expect>s` instead of `%<current>s`.'
49
+
50
+ def on_heredoc(node)
51
+ return unless offense_detected?(node)
52
+
53
+ add_offense(node) do |corrector|
54
+ corrector.insert_after(node, SQUISH)
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def offense_detected?(node)
61
+ sql_heredoc?(node) && !using_squish?(node)
62
+ end
63
+
64
+ def sql_heredoc?(node)
65
+ delimiter_string(node) == SQL
66
+ end
67
+
68
+ def using_squish?(node)
69
+ node.parent&.send_type? && node.parent&.method?(:squish)
70
+ end
71
+
72
+ def message(node)
73
+ format(
74
+ MSG,
75
+ expect: "#{node.source}#{SQUISH}",
76
+ current: node.source
77
+ )
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end