rubocop-rails 2.17.3 → 2.18.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/config/default.yml +52 -26
  4. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +14 -6
  5. data/lib/rubocop/cop/rails/action_controller_test_case.rb +1 -1
  6. data/lib/rubocop/cop/rails/active_record_aliases.rb +2 -0
  7. data/lib/rubocop/cop/rails/belongs_to.rb +0 -3
  8. data/lib/rubocop/cop/rails/blank.rb +1 -1
  9. data/lib/rubocop/cop/rails/bulk_change_table.rb +1 -4
  10. data/lib/rubocop/cop/rails/deprecated_active_model_errors_methods.rb +1 -1
  11. data/lib/rubocop/cop/rails/freeze_time.rb +3 -0
  12. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +2 -2
  13. data/lib/rubocop/cop/rails/i18n_locale_texts.rb +2 -2
  14. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +0 -2
  15. data/lib/rubocop/cop/rails/inverse_of.rb +0 -3
  16. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +17 -7
  17. data/lib/rubocop/cop/rails/negate_include.rb +1 -1
  18. data/lib/rubocop/cop/rails/not_null_column.rb +9 -6
  19. data/lib/rubocop/cop/rails/pluck.rb +12 -0
  20. data/lib/rubocop/cop/rails/presence.rb +2 -2
  21. data/lib/rubocop/cop/rails/present.rb +1 -1
  22. data/lib/rubocop/cop/rails/response_parsed_body.rb +57 -0
  23. data/lib/rubocop/cop/rails/reversible_migration.rb +4 -29
  24. data/lib/rubocop/cop/rails/root_pathname_methods.rb +20 -7
  25. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +8 -1
  26. data/lib/rubocop/cop/rails/time_zone.rb +15 -2
  27. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +0 -2
  28. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +10 -1
  29. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +6 -1
  30. data/lib/rubocop/cop/rails/where_missing.rb +9 -2
  31. data/lib/rubocop/cop/rails_cops.rb +1 -0
  32. data/lib/rubocop/rails/version.rb +1 -1
  33. data/lib/rubocop/rails.rb +1 -1
  34. metadata +5 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e1cd420a9b6ec0e14a26063533cf9db61a81a43e44bec26a8b98e577cf761e49
4
- data.tar.gz: 73918575c04e0f9ee8390d3efc0a4eb49b1cb4093695ecbafddbf995c406869d
3
+ metadata.gz: 8f1cbc229165ee81ea84fc26d1c6482f917d8e8177827b139cf36974acff0176
4
+ data.tar.gz: 23cefe2f0434e5ef4c86856221f1336bfc55ffbacfd09f2dc9a24552b633d355
5
5
  SHA512:
6
- metadata.gz: 93fffc6d3f6cbb781692725c13eb6f049e3106f2fb7ccb43a48fdf7c0d85a189914740d2ef743b0642eaa94ae0fc584ec1a32be53095a12167b3c02db2b035d4
7
- data.tar.gz: 05ac2a5767f2c65437629fa623b3b4506547227c5b608a7ebc4e29a91b831e794ac6ee7c62a55b1573f3b228d40b6aaaedbda1fb7b81481c51f9c0df6884f389
6
+ metadata.gz: 59f9af07f060ff82a64bbf9db4af4d8ebb56dfada8310073bca25fde87736ef8fa0891e6f3ba997e8b847eef6218d93f25739b8022a8cb68e9aae970e8b2c674
7
+ data.tar.gz: a4415117082d7ccaeb8349f1ee31c9dba95bb6ea54bed77b176bcdd59b03cf4e2702f99c05414ff1e9028c48f96dce2f729eb2a2b98f91183cba3cb143e08894
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-22 Bozhidar Batsov
1
+ Copyright (c) 2012-23 Bozhidar Batsov
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/config/default.yml CHANGED
@@ -46,23 +46,6 @@ Lint/NumberConversion:
46
46
  - fortnights
47
47
  - in_milliseconds
48
48
  AllowedPatterns: []
49
- # Deprecated.
50
- IgnoredMethods:
51
- - ago
52
- - from_now
53
- - second
54
- - seconds
55
- - minute
56
- - minutes
57
- - hour
58
- - hours
59
- - day
60
- - days
61
- - week
62
- - weeks
63
- - fortnight
64
- - fortnights
65
- - in_milliseconds
66
49
 
67
50
  Rails:
68
51
  Enabled: true
@@ -135,7 +118,9 @@ Rails/ActiveRecordOverride:
135
118
  Check for overriding Active Record methods instead of using
136
119
  callbacks.
137
120
  Enabled: true
121
+ Severity: warning
138
122
  VersionAdded: '0.67'
123
+ VersionChanged: '2.18'
139
124
  Include:
140
125
  - app/models/**/*.rb
141
126
 
@@ -226,6 +211,9 @@ Rails/BelongsTo:
226
211
  Description: >-
227
212
  Use `optional: true` instead of `required: false` for
228
213
  `belongs_to` relations.
214
+ Reference:
215
+ - https://guides.rubyonrails.org/5_0_release_notes.html
216
+ - https://github.com/rails/rails/pull/18937
229
217
  Enabled: true
230
218
  VersionAdded: '0.62'
231
219
 
@@ -244,6 +232,9 @@ Rails/Blank:
244
232
 
245
233
  Rails/BulkChangeTable:
246
234
  Description: 'Check whether alter queries are combinable.'
235
+ Reference:
236
+ - https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-change_table
237
+ - https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html
247
238
  Enabled: true
248
239
  VersionAdded: '0.57'
249
240
  Database: null
@@ -330,9 +321,10 @@ Rails/DelegateAllowBlank:
330
321
  Rails/DeprecatedActiveModelErrorsMethods:
331
322
  Description: 'Avoid manipulating ActiveModel errors hash directly.'
332
323
  Enabled: pending
324
+ Severity: warning
333
325
  Safe: false
334
326
  VersionAdded: '2.14'
335
- VersionChanged: '2.15'
327
+ VersionChanged: '2.18'
336
328
 
337
329
  Rails/DotSeparatedKeys:
338
330
  Description: 'Enforces the use of dot-separated keys instead of `:scope` options in `I18n` translation methods.'
@@ -343,12 +335,16 @@ Rails/DotSeparatedKeys:
343
335
  Rails/DuplicateAssociation:
344
336
  Description: "Don't repeat associations in a model."
345
337
  Enabled: pending
338
+ Severity: warning
346
339
  VersionAdded: '2.14'
340
+ VersionChanged: '2.18'
347
341
 
348
342
  Rails/DuplicateScope:
349
343
  Description: 'Multiple scopes share this same where clause.'
350
344
  Enabled: pending
345
+ Severity: warning
351
346
  VersionAdded: '2.14'
347
+ VersionChanged: '2.18'
352
348
 
353
349
  Rails/DurationArithmetic:
354
350
  Description: 'Do not use duration as arithmetic operand with `Time.current`.'
@@ -463,7 +459,7 @@ Rails/FindById:
463
459
  VersionAdded: '2.7'
464
460
 
465
461
  Rails/FindEach:
466
- Description: 'Prefer all.find_each over all.find.'
462
+ Description: 'Prefer all.find_each over all.each.'
467
463
  StyleGuide: 'https://rails.rubystyle.guide#find-each'
468
464
  Enabled: true
469
465
  VersionAdded: '0.30'
@@ -477,13 +473,6 @@ Rails/FindEach:
477
473
  - select
478
474
  - lock
479
475
  AllowedPatterns: []
480
- # Deprecated.
481
- IgnoredMethods:
482
- # Methods that don't work well with `find_each`.
483
- - order
484
- - limit
485
- - select
486
- - lock
487
476
 
488
477
  Rails/FreezeTime:
489
478
  Description: 'Prefer `freeze_time` over `travel_to` with an argument of the current time.'
@@ -592,6 +581,9 @@ Rails/Inquiry:
592
581
 
593
582
  Rails/InverseOf:
594
583
  Description: 'Checks for associations where the inverse cannot be determined automatically.'
584
+ Reference:
585
+ - https://guides.rubyonrails.org/association_basics.html#bi-directional-associations
586
+ - https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Setting+Inverses
595
587
  Enabled: true
596
588
  VersionAdded: '0.52'
597
589
  IgnoreScopes: false
@@ -695,7 +687,9 @@ Rails/Pluck:
695
687
  Description: 'Prefer `pluck` over `map { ... }`.'
696
688
  StyleGuide: 'https://rails.rubystyle.guide#pluck'
697
689
  Enabled: 'pending'
690
+ Safe: false
698
691
  VersionAdded: '2.7'
692
+ VersionChanged: '2.18'
699
693
 
700
694
  Rails/PluckId:
701
695
  Description: 'Use `ids` instead of `pluck(:id)` or `pluck(primary_key)`.'
@@ -847,6 +841,17 @@ Rails/RequireDependency:
847
841
  Enabled: false
848
842
  VersionAdded: '2.10'
849
843
 
844
+ Rails/ResponseParsedBody:
845
+ Description: Prefer `response.parsed_body` to `JSON.parse(response.body)`.
846
+ Enabled: pending
847
+ SafeAutoCorrect: false
848
+ VersionAdded: '2.18'
849
+ Include:
850
+ - spec/controllers/**/*.rb
851
+ - spec/requests/**/*.rb
852
+ - test/controllers/**/*.rb
853
+ - test/integration/**/*.rb
854
+
850
855
  Rails/ReversibleMigration:
851
856
  Description: 'Checks whether the change method of the migration file is reversible.'
852
857
  StyleGuide: 'https://rails.rubystyle.guide#reversible-migration'
@@ -1038,10 +1043,14 @@ Rails/TopLevelHashWithIndifferentAccess:
1038
1043
  Description: 'Identifies top-level `HashWithIndifferentAccess`.'
1039
1044
  Reference: 'https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#top-level-hashwithindifferentaccess-is-soft-deprecated'
1040
1045
  Enabled: pending
1046
+ Severity: warning
1041
1047
  VersionAdded: '2.16'
1048
+ VersionChanged: '2.18'
1042
1049
 
1043
1050
  Rails/TransactionExitStatement:
1044
1051
  Description: 'Avoid the usage of `return`, `break` and `throw` in transaction blocks.'
1052
+ Reference:
1053
+ - https://github.com/rails/rails/commit/15aa4200e083
1045
1054
  Enabled: pending
1046
1055
  VersionAdded: '2.14'
1047
1056
 
@@ -1066,7 +1075,9 @@ Rails/UniqueValidationWithoutIndex:
1066
1075
  Rails/UnknownEnv:
1067
1076
  Description: 'Use correct environment name.'
1068
1077
  Enabled: true
1078
+ Severity: warning
1069
1079
  VersionAdded: '0.51'
1080
+ VersionChanged: '2.18'
1070
1081
  Environments:
1071
1082
  - development
1072
1083
  - test
@@ -1121,12 +1132,27 @@ Rails/WhereNot:
1121
1132
  Rails/WhereNotWithMultipleConditions:
1122
1133
  Description: 'Do not use `where.not(...)` with multiple conditions.'
1123
1134
  Enabled: 'pending'
1135
+ Severity: warning
1124
1136
  VersionAdded: '2.17'
1137
+ VersionChanged: '2.18'
1125
1138
 
1126
1139
  # Accept `redirect_to(...) and return` and similar cases.
1127
1140
  Style/AndOr:
1128
1141
  EnforcedStyle: conditionals
1129
1142
 
1143
+ Style/FormatStringToken:
1144
+ AllowedMethods:
1145
+ - redirect
1146
+
1147
+ Style/InverseMethods:
1148
+ # `InverseMethods` are methods that can be inverted by a not (`not` or `!`)
1149
+ # The relationship of inverse methods only needs to be defined in one direction.
1150
+ # Keys and values both need to be defined as symbols.
1151
+ InverseMethods:
1152
+ :present?: :blank?
1153
+ :include?: :exclude?
1154
+ :valid?: :invalid?
1155
+
1130
1156
  Style/SymbolProc:
1131
1157
  AllowedMethods:
1132
1158
  - define_method
@@ -67,17 +67,19 @@ module RuboCop
67
67
  private
68
68
 
69
69
  def followed_by_render?(flash_node)
70
- flash_assigment_node = find_ancestor(flash_node, type: :send)
71
- context = flash_assigment_node
72
- if (if_node = context.each_ancestor(:if).first)
73
- context = if_node
70
+ flash_assignment_node = find_ancestor(flash_node, type: :send)
71
+ context = flash_assignment_node
72
+ if (node = context.each_ancestor(:if, :rescue).first)
73
+ return false if use_redirect_to?(context)
74
+
75
+ context = node
74
76
  elsif context.right_siblings.empty?
75
77
  return true
76
78
  end
77
79
  context = context.right_siblings
78
80
 
79
- context.compact.any? do |node|
80
- render?(node)
81
+ context.compact.any? do |render_candidate|
82
+ render?(render_candidate)
81
83
  end
82
84
  end
83
85
 
@@ -95,6 +97,12 @@ module RuboCop
95
97
  def_node || block_node
96
98
  end
97
99
 
100
+ def use_redirect_to?(context)
101
+ context.right_siblings.compact.any? do |sibling|
102
+ sibling.send_type? && sibling.method?(:redirect_to)
103
+ end
104
+ end
105
+
98
106
  def find_ancestor(node, type:)
99
107
  node.each_ancestor(type).first
100
108
  end
@@ -30,7 +30,7 @@ module RuboCop
30
30
 
31
31
  def_node_matcher :action_controller_test_case?, <<~PATTERN
32
32
  (class
33
- (const {nil? cbase} _)
33
+ (const _ _)
34
34
  (const (const {nil? cbase} :ActionController) :TestCase) _)
35
35
  PATTERN
36
36
 
@@ -26,6 +26,8 @@ module RuboCop
26
26
  RESTRICT_ON_SEND = ALIASES.keys.freeze
27
27
 
28
28
  def on_send(node)
29
+ return if node.arguments.empty?
30
+
29
31
  method_name = node.method_name
30
32
  alias_method = ALIASES[method_name]
31
33
 
@@ -47,9 +47,6 @@ module RuboCop
47
47
  # class Post < ApplicationRecord
48
48
  # belongs_to :blog, optional: false
49
49
  # end
50
- #
51
- # @see https://guides.rubyonrails.org/5_0_release_notes.html
52
- # @see https://github.com/rails/rails/pull/18937
53
50
  class BelongsTo < Base
54
51
  extend AutoCorrector
55
52
  extend TargetRailsVersion
@@ -8,7 +8,7 @@ module RuboCop
8
8
  #
9
9
  # Interaction with `Style/UnlessElse`:
10
10
  # The configuration of `NotPresent` will not produce an offense in the
11
- # context of `unless else` if `Style/UnlessElse` is inabled. This is
11
+ # context of `unless else` if `Style/UnlessElse` is enabled. This is
12
12
  # to prevent interference between the autocorrection of the two cops.
13
13
  #
14
14
  # @safety
@@ -62,9 +62,6 @@ module RuboCop
62
62
  # t.string :nickname
63
63
  # end
64
64
  # end
65
- #
66
- # @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-change_table
67
- # @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html
68
65
  class BulkChangeTable < Base
69
66
  MSG_FOR_CHANGE_TABLE = <<~MSG.chomp
70
67
  You can combine alter queries using `bulk: true` options.
@@ -216,7 +213,7 @@ module RuboCop
216
213
  true
217
214
  when POSTGRESQL
218
215
  # Add bulk alter support for PostgreSQL in 5.2.0
219
- # @see https://github.com/rails/rails/pull/31331
216
+ # See: https://github.com/rails/rails/pull/31331
220
217
  target_rails_version >= 5.2
221
218
  else
222
219
  false
@@ -160,7 +160,7 @@ module RuboCop
160
160
  end
161
161
 
162
162
  def model_file?
163
- processed_source.buffer.name.include?('/models/')
163
+ processed_source.file_path.include?('/models/')
164
164
  end
165
165
  end
166
166
  end
@@ -26,6 +26,9 @@ module RuboCop
26
26
  #
27
27
  class FreezeTime < Base
28
28
  extend AutoCorrector
29
+ extend TargetRailsVersion
30
+
31
+ minimum_target_rails_version 5.2
29
32
 
30
33
  MSG = 'Use `freeze_time` instead of `travel_to`.'
31
34
  NOW_METHODS = %i[now new current].freeze
@@ -41,11 +41,11 @@ module RuboCop
41
41
  PATTERN
42
42
 
43
43
  def_node_matcher :association_without_options?, <<~PATTERN
44
- (send nil? {:has_many :has_one} _)
44
+ (send _ {:has_many :has_one} _)
45
45
  PATTERN
46
46
 
47
47
  def_node_matcher :association_with_options?, <<~PATTERN
48
- (send nil? {:has_many :has_one} ... (hash $...))
48
+ (send _ {:has_many :has_one} ... (hash $...))
49
49
  PATTERN
50
50
 
51
51
  def_node_matcher :dependent_option?, <<~PATTERN
@@ -69,7 +69,7 @@ module RuboCop
69
69
  class I18nLocaleTexts < Base
70
70
  MSG = 'Move locale texts to the locale files in the `config/locales` directory.'
71
71
 
72
- RESTRICT_ON_SEND = %i[validates redirect_to []= mail].freeze
72
+ RESTRICT_ON_SEND = %i[validates redirect_to redirect_back []= mail].freeze
73
73
 
74
74
  def_node_search :validation_message, <<~PATTERN
75
75
  (pair (sym :message) $str)
@@ -94,7 +94,7 @@ module RuboCop
94
94
  add_offense(text_node)
95
95
  end
96
96
  return
97
- when :redirect_to
97
+ when :redirect_to, :redirect_back
98
98
  text_node = redirect_to_flash(node).to_a.last
99
99
  when :[]=
100
100
  text_node = flash_assignment?(node)
@@ -35,8 +35,6 @@ module RuboCop
35
35
  # skip_before_action :login_required,
36
36
  # if: -> { trusted_origin? && action_name != "admin" }
37
37
  # end
38
- #
39
- # @see https://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html#method-i-_normalize_callback_options
40
38
  class IgnoredSkipActionFilterOption < Base
41
39
  MSG = <<~MSG.chomp.freeze
42
40
  `%<ignore>s` option will be ignored when `%<prefer>s` and `%<ignore>s` are used together.
@@ -137,9 +137,6 @@ module RuboCop
137
137
  # class Blog < ApplicationRecord
138
138
  # has_many :posts, -> { order(published_at: :desc) }
139
139
  # end
140
- #
141
- # @see https://guides.rubyonrails.org/association_basics.html#bi-directional-associations
142
- # @see https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Setting+Inverses
143
140
  class InverseOf < Base
144
141
  SPECIFY_MSG = 'Specify an `:inverse_of` option.'
145
142
  NIL_MSG = 'You specified `inverse_of: nil`, you probably meant to use `inverse_of: false`.'
@@ -144,19 +144,29 @@ module RuboCop
144
144
  end
145
145
 
146
146
  def aliased_action_methods(node, defined_methods)
147
- alias_methods = node.each_child_node(:send).select { |send_node| send_node.method?(:alias_method) }
148
-
149
- hash_of_alias_methods = alias_methods.each_with_object({}) do |alias_method, result|
150
- result[alias_method.last_argument.value] = alias_method.first_argument.value
151
- end
152
-
147
+ alias_methods = alias_methods(node)
153
148
  defined_methods.each_with_object([]) do |defined_method, aliased_method|
154
- if (new_method_name = hash_of_alias_methods[defined_method])
149
+ if (new_method_name = alias_methods[defined_method])
155
150
  aliased_method << new_method_name
156
151
  end
157
152
  end
158
153
  end
159
154
 
155
+ def alias_methods(node)
156
+ result = {}
157
+ node.each_child_node(:send, :alias) do |child_node|
158
+ case child_node.type
159
+ when :send
160
+ if child_node.method?(:alias_method)
161
+ result[child_node.last_argument.value] = child_node.first_argument.value
162
+ end
163
+ when :alias
164
+ result[child_node.old_identifier.value] = child_node.new_identifier.value
165
+ end
166
+ end
167
+ result
168
+ end
169
+
160
170
  # @param node [RuboCop::AST::Node]
161
171
  # @return [Array<Symbol>]
162
172
  def array_values(node) # rubocop:disable Metrics/MethodLength
@@ -26,7 +26,7 @@ module RuboCop
26
26
  RESTRICT_ON_SEND = %i[!].freeze
27
27
 
28
28
  def_node_matcher :negate_include_call?, <<~PATTERN
29
- (send (send $_ :include? $_) :!)
29
+ (send (send $!nil? :include? $_) :!)
30
30
  PATTERN
31
31
 
32
32
  def on_send(node)
@@ -21,7 +21,7 @@ module RuboCop
21
21
  RESTRICT_ON_SEND = %i[add_column add_reference].freeze
22
22
 
23
23
  def_node_matcher :add_not_null_column?, <<~PATTERN
24
- (send nil? :add_column _ _ _ (hash $...))
24
+ (send nil? :add_column _ _ $_ (hash $...))
25
25
  PATTERN
26
26
 
27
27
  def_node_matcher :add_not_null_reference?, <<~PATTERN
@@ -44,17 +44,20 @@ module RuboCop
44
44
  private
45
45
 
46
46
  def check_add_column(node)
47
- pairs = add_not_null_column?(node)
48
- check_pairs(pairs)
47
+ add_not_null_column?(node) do |type, pairs|
48
+ return if type.value == :virtual || type.value == 'virtual'
49
+
50
+ check_pairs(pairs)
51
+ end
49
52
  end
50
53
 
51
54
  def check_add_reference(node)
52
- pairs = add_not_null_reference?(node)
53
- check_pairs(pairs)
55
+ add_not_null_reference?(node) do |pairs|
56
+ check_pairs(pairs)
57
+ end
54
58
  end
55
59
 
56
60
  def check_pairs(pairs)
57
- return unless pairs
58
61
  return if pairs.any? { |pair| default_option?(pair) }
59
62
 
60
63
  null_false = pairs.find { |pair| null_false?(pair) }
@@ -9,6 +9,18 @@ module RuboCop
9
9
  # element in an enumerable. When called on an Active Record relation, it
10
10
  # results in a more efficient query that only selects the necessary key.
11
11
  #
12
+ # @safety
13
+ # This cop is unsafe because model can use column aliases.
14
+ #
15
+ # [source,ruby]
16
+ # ----
17
+ # # Original code
18
+ # User.select('name AS nickname').map { |user| user[:nickname] } # => array of nicknames
19
+ #
20
+ # # After autocorrection
21
+ # User.select('name AS nickname').pluck(:nickname) # => raises ActiveRecord::StatementInvalid
22
+ # ----
23
+ #
12
24
  # @example
13
25
  # # bad
14
26
  # Post.published.map { |post| post[:title] }
@@ -112,10 +112,10 @@ module RuboCop
112
112
  end
113
113
 
114
114
  def current(node)
115
- if node.source.include?("\n")
115
+ if !node.ternary? && node.source.include?("\n")
116
116
  "#{node.loc.keyword.with(end_pos: node.condition.loc.selector.end_pos).source} ... end"
117
117
  else
118
- node.source
118
+ node.source.gsub(/\n\s*/, ' ')
119
119
  end
120
120
  end
121
121
 
@@ -8,7 +8,7 @@ module RuboCop
8
8
  #
9
9
  # Interaction with `Style/UnlessElse`:
10
10
  # The configuration of `NotBlank` will not produce an offense in the
11
- # context of `unless else` if `Style/UnlessElse` is inabled. This is
11
+ # context of `unless else` if `Style/UnlessElse` is enabled. This is
12
12
  # to prevent interference between the autocorrection of the two cops.
13
13
  #
14
14
  # @example NotNilAndNotEmpty: true (default)
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Prefer `response.parsed_body` to `JSON.parse(response.body)`.
7
+ #
8
+ # @safety
9
+ # This cop's autocorrection is unsafe because Content-Type may not be `application/json`. For example, the
10
+ # proprietary Content-Type provided by corporate entities such as `application/vnd.github+json` is not
11
+ # supported at `response.parsed_body` by default, so you still have to use `JSON.parse(response.body)` there.
12
+ #
13
+ # @example
14
+ # # bad
15
+ # JSON.parse(response.body)
16
+ #
17
+ # # good
18
+ # response.parsed_body
19
+ class ResponseParsedBody < Base
20
+ extend AutoCorrector
21
+ extend TargetRailsVersion
22
+
23
+ MSG = 'Prefer `response.parsed_body` to `JSON.parse(response.body)`.'
24
+
25
+ RESTRICT_ON_SEND = %i[parse].freeze
26
+
27
+ minimum_target_rails_version 5.0
28
+
29
+ # @!method json_parse_response_body?(node)
30
+ def_node_matcher :json_parse_response_body?, <<~PATTERN
31
+ (send
32
+ (const {nil? cbase} :JSON)
33
+ :parse
34
+ (send
35
+ (send nil? :response)
36
+ :body
37
+ )
38
+ )
39
+ PATTERN
40
+
41
+ def on_send(node)
42
+ return unless json_parse_response_body?(node)
43
+
44
+ add_offense(node) do |corrector|
45
+ autocorrect(corrector, node)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def autocorrect(corrector, node)
52
+ corrector.replace(node, 'response.parsed_body')
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -16,23 +16,15 @@ module RuboCop
16
16
  #
17
17
  # # good
18
18
  # def change
19
- # create_table :users do |t|
20
- # t.string :name
19
+ # change_table :users do |t|
20
+ # t.remove :name, :string
21
21
  # end
22
22
  # end
23
23
  #
24
24
  # # good
25
25
  # def change
26
- # reversible do |dir|
27
- # change_table :users do |t|
28
- # dir.up do
29
- # t.column :name, :string
30
- # end
31
- #
32
- # dir.down do
33
- # t.remove :name
34
- # end
35
- # end
26
+ # create_table :users do |t|
27
+ # t.string :name
36
28
  # end
37
29
  # end
38
30
  #
@@ -114,21 +106,6 @@ module RuboCop
114
106
  # end
115
107
  # end
116
108
  #
117
- # # good
118
- # def change
119
- # reversible do |dir|
120
- # change_table :users do |t|
121
- # dir.up do
122
- # t.change :price, :string
123
- # end
124
- #
125
- # dir.down do
126
- # t.change :price, :integer
127
- # end
128
- # end
129
- # end
130
- # end
131
- #
132
109
  # @example
133
110
  # # remove_columns
134
111
  #
@@ -173,8 +150,6 @@ module RuboCop
173
150
  # def change
174
151
  # remove_index :users, column: :email
175
152
  # end
176
- #
177
- # @see https://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html
178
153
  class ReversibleMigration < Base
179
154
  include MigrationsHelper
180
155
 
@@ -186,11 +186,7 @@ module RuboCop
186
186
  def build_path_glob_replacement(path, method)
187
187
  receiver = range_between(path.loc.expression.begin_pos, path.children.first.loc.selector.end_pos).source
188
188
 
189
- argument = if path.arguments.one?
190
- path.first_argument.source
191
- else
192
- join_arguments(path.arguments)
193
- end
189
+ argument = path.arguments.one? ? path.first_argument.source : join_arguments(path.arguments)
194
190
 
195
191
  "#{receiver}.#{method}(#{argument})"
196
192
  end
@@ -214,11 +210,28 @@ module RuboCop
214
210
  end
215
211
 
216
212
  def join_arguments(arguments)
217
- quote = include_interpolation?(arguments) ? '"' : "'"
218
- joined_arguments = arguments.map(&:value).join('/')
213
+ use_interpolation = false
214
+
215
+ joined_arguments = arguments.map do |arg|
216
+ if arg.respond_to?(:value)
217
+ arg.value
218
+ else
219
+ use_interpolation = true
220
+ "\#{#{arg.source}}"
221
+ end
222
+ end.join('/')
223
+ quote = enforce_double_quotes? || include_interpolation?(arguments) || use_interpolation ? '"' : "'"
219
224
 
220
225
  "#{quote}#{joined_arguments}#{quote}"
221
226
  end
227
+
228
+ def enforce_double_quotes?
229
+ string_literals_config['EnforcedStyle'] == 'double_quotes'
230
+ end
231
+
232
+ def string_literals_config
233
+ config.for_cop('Style/StringLiterals')
234
+ end
222
235
  end
223
236
  end
224
237
  end
@@ -48,6 +48,7 @@ module RuboCop
48
48
  SQL = 'SQL'
49
49
  SQUISH = '.squish'
50
50
  MSG = 'Use `%<expect>s` instead of `%<current>s`.'
51
+ SQL_IDENTIFIER_MARKERS = /(".+?")|('.+?')|(\[.+?\])/.freeze
51
52
 
52
53
  def on_heredoc(node)
53
54
  return unless offense_detected?(node)
@@ -60,7 +61,7 @@ module RuboCop
60
61
  private
61
62
 
62
63
  def offense_detected?(node)
63
- sql_heredoc?(node) && !using_squish?(node)
64
+ sql_heredoc?(node) && !using_squish?(node) && !singleline_comments_present?(node)
64
65
  end
65
66
 
66
67
  def sql_heredoc?(node)
@@ -71,6 +72,12 @@ module RuboCop
71
72
  node.parent&.send_type? && node.parent&.method?(:squish)
72
73
  end
73
74
 
75
+ def singleline_comments_present?(node)
76
+ sql = node.children.map { |c| c.is_a?(String) ? c : c.source }.join('\n')
77
+
78
+ sql.gsub(SQL_IDENTIFIER_MARKERS, '').include?('--')
79
+ end
80
+
74
81
  def message(node)
75
82
  format(MSG, expect: "#{node.source}#{SQUISH}", current: node.source)
76
83
  end
@@ -228,12 +228,25 @@ module RuboCop
228
228
  acceptable
229
229
  end
230
230
 
231
- # Time.new can be called with a time zone offset
231
+ # Time.new, Time.at, and Time.now can be called with a time zone offset
232
232
  # When it is, that should be considered safe
233
233
  # Example:
234
234
  # Time.new(1988, 3, 15, 3, 0, 0, "-05:00")
235
235
  def offset_provided?(node)
236
- node.arguments.size >= 7
236
+ case node.method_name
237
+ when :new
238
+ node.arguments.size == 7 || offset_option_provided?(node)
239
+ when :at, :now
240
+ offset_option_provided?(node)
241
+ end
242
+ end
243
+
244
+ def offset_option_provided?(node)
245
+ options = node.last_argument
246
+ options&.hash_type? &&
247
+ options.each_pair.any? do |pair|
248
+ pair.key.sym_type? && pair.key.value == :in && !pair.value.nil_type?
249
+ end
237
250
  end
238
251
  end
239
252
  end
@@ -45,8 +45,6 @@ module RuboCop
45
45
  # # Commit
46
46
  # next if user.active?
47
47
  # end
48
- #
49
- # @see https://github.com/rails/rails/commit/15aa4200e083
50
48
  class TransactionExitStatement < Base
51
49
  MSG = <<~MSG.chomp
52
50
  Exit statement `%<statement>s` is not allowed. Use `raise` (rollback) or `next` (commit).
@@ -139,11 +139,20 @@ module RuboCop
139
139
  pairs = node.arguments.last
140
140
  return unless pairs.hash_type?
141
141
 
142
+ return true if condition_hash_part?(pairs, keys: %i[if unless])
143
+
144
+ uniqueness_node = uniqueness_part(node)
145
+ return unless uniqueness_node&.hash_type?
146
+
147
+ condition_hash_part?(uniqueness_node, keys: %i[if unless conditions])
148
+ end
149
+
150
+ def condition_hash_part?(pairs, keys:)
142
151
  pairs.each_pair.any? do |pair|
143
152
  key = pair.key
144
153
  next unless key.sym_type?
145
154
 
146
- key.value == :if || key.value == :unless
155
+ keys.include?(key.value)
147
156
  end
148
157
  end
149
158
 
@@ -28,12 +28,16 @@ module RuboCop
28
28
  (send self :ignored_columns= $array)
29
29
  PATTERN
30
30
 
31
+ def_node_matcher :appended_ignored_columns, <<~PATTERN
32
+ (op-asgn (send self :ignored_columns) :+ $array)
33
+ PATTERN
34
+
31
35
  def_node_matcher :column_name, <<~PATTERN
32
36
  ({str sym} $_)
33
37
  PATTERN
34
38
 
35
39
  def on_send(node)
36
- return unless (columns = ignored_columns(node))
40
+ return unless (columns = ignored_columns(node) || appended_ignored_columns(node))
37
41
  return unless schema
38
42
 
39
43
  table = table(node)
@@ -43,6 +47,7 @@ module RuboCop
43
47
  check_column_existence(column_node, table)
44
48
  end
45
49
  end
50
+ alias on_op_asgn on_send
46
51
 
47
52
  private
48
53
 
@@ -38,7 +38,9 @@ module RuboCop
38
38
  def on_send(node)
39
39
  return unless node.first_argument.sym_type?
40
40
 
41
- where_node_and_argument(root_receiver(node)) do |where_node, where_argument|
41
+ root_receiver = root_receiver(node)
42
+ where_node_and_argument(root_receiver) do |where_node, where_argument|
43
+ next unless root_receiver == root_receiver(where_node)
42
44
  next unless same_relationship?(where_argument, node.first_argument)
43
45
 
44
46
  range = range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
@@ -50,7 +52,12 @@ module RuboCop
50
52
  private
51
53
 
52
54
  def root_receiver(node)
53
- node&.parent&.send_type? ? root_receiver(node.parent) : node
55
+ parent = node.parent
56
+ if !parent&.send_type? || parent.method?(:or) || parent.method?(:and)
57
+ node
58
+ else
59
+ root_receiver(parent)
60
+ end
54
61
  end
55
62
 
56
63
  def same_relationship?(where, left_joins)
@@ -99,6 +99,7 @@ require_relative 'rails/render_inline'
99
99
  require_relative 'rails/render_plain_text'
100
100
  require_relative 'rails/request_referer'
101
101
  require_relative 'rails/require_dependency'
102
+ require_relative 'rails/response_parsed_body'
102
103
  require_relative 'rails/reversible_migration'
103
104
  require_relative 'rails/reversible_migration_method_definition'
104
105
  require_relative 'rails/root_join_chain'
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Rails
5
5
  # This module holds the RuboCop Rails version information.
6
6
  module Version
7
- STRING = '2.17.3'
7
+ STRING = '2.18.0'
8
8
 
9
9
  def self.document_version
10
10
  STRING.match('\d+\.\d+').to_s
data/lib/rubocop/rails.rb CHANGED
@@ -5,7 +5,7 @@ module RuboCop
5
5
  module Rails
6
6
  PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
7
7
  CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
8
- CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze
8
+ CONFIG = YAML.safe_load(CONFIG_DEFAULT.read, permitted_classes: [Regexp, Symbol]).freeze
9
9
 
10
10
  private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
11
11
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.17.3
4
+ version: 2.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-11-20 00:00:00.000000000 Z
13
+ date: 2023-02-25 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -173,6 +173,7 @@ files:
173
173
  - lib/rubocop/cop/rails/render_plain_text.rb
174
174
  - lib/rubocop/cop/rails/request_referer.rb
175
175
  - lib/rubocop/cop/rails/require_dependency.rb
176
+ - lib/rubocop/cop/rails/response_parsed_body.rb
176
177
  - lib/rubocop/cop/rails/reversible_migration.rb
177
178
  - lib/rubocop/cop/rails/reversible_migration_method_definition.rb
178
179
  - lib/rubocop/cop/rails/root_join_chain.rb
@@ -217,7 +218,7 @@ metadata:
217
218
  homepage_uri: https://docs.rubocop.org/rubocop-rails/
218
219
  changelog_uri: https://github.com/rubocop/rubocop-rails/blob/master/CHANGELOG.md
219
220
  source_code_uri: https://github.com/rubocop/rubocop-rails/
220
- documentation_uri: https://docs.rubocop.org/rubocop-rails/2.17/
221
+ documentation_uri: https://docs.rubocop.org/rubocop-rails/2.18/
221
222
  bug_tracker_uri: https://github.com/rubocop/rubocop-rails/issues
222
223
  rubygems_mfa_required: 'true'
223
224
  post_install_message:
@@ -235,7 +236,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
235
236
  - !ruby/object:Gem::Version
236
237
  version: '0'
237
238
  requirements: []
238
- rubygems_version: 3.2.22
239
+ rubygems_version: 3.4.1
239
240
  signing_key:
240
241
  specification_version: 4
241
242
  summary: Automatic Rails code style checking tool.