rubocop-rails 2.21.2 → 2.23.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -10
  3. data/config/default.yml +11 -3
  4. data/lib/rubocop/cop/mixin/active_record_helper.rb +9 -2
  5. data/lib/rubocop/cop/mixin/database_type_resolvable.rb +66 -0
  6. data/lib/rubocop/cop/rails/action_filter.rb +3 -0
  7. data/lib/rubocop/cop/rails/active_record_aliases.rb +2 -2
  8. data/lib/rubocop/cop/rails/active_support_aliases.rb +6 -5
  9. data/lib/rubocop/cop/rails/after_commit_override.rb +1 -1
  10. data/lib/rubocop/cop/rails/bulk_change_table.rb +6 -56
  11. data/lib/rubocop/cop/rails/content_tag.rb +1 -1
  12. data/lib/rubocop/cop/rails/dangerous_column_names.rb +10 -2
  13. data/lib/rubocop/cop/rails/duplicate_association.rb +66 -12
  14. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +2 -2
  15. data/lib/rubocop/cop/rails/env_local.rb +46 -0
  16. data/lib/rubocop/cop/rails/file_path.rb +5 -5
  17. data/lib/rubocop/cop/rails/find_by.rb +2 -2
  18. data/lib/rubocop/cop/rails/find_by_id.rb +9 -23
  19. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +1 -1
  20. data/lib/rubocop/cop/rails/helper_instance_variable.rb +1 -1
  21. data/lib/rubocop/cop/rails/inquiry.rb +1 -0
  22. data/lib/rubocop/cop/rails/inverse_of.rb +1 -1
  23. data/lib/rubocop/cop/rails/not_null_column.rb +13 -3
  24. data/lib/rubocop/cop/rails/output.rb +3 -2
  25. data/lib/rubocop/cop/rails/pick.rb +6 -5
  26. data/lib/rubocop/cop/rails/pluck.rb +1 -1
  27. data/lib/rubocop/cop/rails/pluck_id.rb +2 -1
  28. data/lib/rubocop/cop/rails/pluck_in_where.rb +18 -5
  29. data/lib/rubocop/cop/rails/rake_environment.rb +2 -2
  30. data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +53 -2
  31. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +7 -0
  32. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +1 -1
  33. data/lib/rubocop/cop/rails/response_parsed_body.rb +52 -10
  34. data/lib/rubocop/cop/rails/reversible_migration.rb +2 -2
  35. data/lib/rubocop/cop/rails/save_bang.rb +11 -6
  36. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +1 -1
  37. data/lib/rubocop/cop/rails/unknown_env.rb +5 -1
  38. data/lib/rubocop/cop/rails/validation.rb +2 -2
  39. data/lib/rubocop/cop/rails/where_equals.rb +3 -2
  40. data/lib/rubocop/cop/rails/where_exists.rb +9 -8
  41. data/lib/rubocop/cop/rails/where_missing.rb +1 -1
  42. data/lib/rubocop/cop/rails/where_not.rb +8 -6
  43. data/lib/rubocop/cop/rails_cops.rb +2 -0
  44. data/lib/rubocop/rails/schema_loader/schema.rb +2 -2
  45. data/lib/rubocop/rails/version.rb +1 -1
  46. metadata +26 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e154e69d10b43226db08e454d992d724dc7504d54cac804e597156ee5cf13e5
4
- data.tar.gz: 60d4697e076620a134f48eff2f81be7b2004fd824796b1a1b15f5822239e3ce3
3
+ metadata.gz: f90e14aba3397c0edacdc63f7dd9d5b83ed7d79382041f5f37e3201dea96c3db
4
+ data.tar.gz: 31ebefe705b4148901128b2b5feb5948b737acdd5fae210f8da6cec845f17023
5
5
  SHA512:
6
- metadata.gz: f0aa0f69a7aa3b90d1e402ab26bdf5a4d03680080d9b06ba157cd92f3c5c290f821e81d4a99636009e17ec4ea2de0cfd405cea00134ebccd4323d424fb790ec8
7
- data.tar.gz: 6b6ddbe47c1b112638cc3693464d9509f589a6683317b0bb30925a27788e39351a3a10853ecf286b419d3ec3b17e13aee9fd09ae07581d209635f0a56a44065b
6
+ metadata.gz: b90a96dae0688c93975260b480c2add1449c5ca21503a071453464f63ba030713501e1d20e2ea4fdf3385a58d1cf71003e2ede718ec9b6aa80e86e4a82be00a7
7
+ data.tar.gz: ad4ed3bbe86f2d1179766c512f347aaa21ea6e18e8af5a00f07eb20098c6c3c0103955ba4366bf952528773b7b2ff6a21af52369969227fd05233138a1060708
data/README.md CHANGED
@@ -56,6 +56,8 @@ Note: `--rails` option is required while `rubocop` command supports `--rails` op
56
56
  ### Rake task
57
57
 
58
58
  ```ruby
59
+ require 'rubocop/rake_task'
60
+
59
61
  RuboCop::RakeTask.new do |task|
60
62
  task.requires << 'rubocop-rails'
61
63
  end
@@ -64,23 +66,21 @@ end
64
66
  ## Rails configuration tip
65
67
 
66
68
  If you are using Rails 6.1 or newer, add the following `config.generators.after_generate` setting to
67
- your config/application.rb to apply RuboCop autocorrection to code generated by `bin/rails g`.
69
+ your `config/environments/development.rb` to apply RuboCop autocorrection to code generated by `bin/rails g`.
68
70
 
69
71
  ```ruby
70
- # config/application.rb
71
- module YourCoolApp
72
- class Application < Rails::Application
73
- config.generators.after_generate do |files|
74
- parsable_files = files.filter { |file| file.end_with?('.rb') }
75
- unless parsable_files.empty?
76
- system("bundle exec rubocop -A --fail-level=E #{parsable_files.shelljoin}", exception: true)
77
- end
72
+ # config/environments/development.rb
73
+ Rails.application.configure do
74
+ config.generators.after_generate do |files|
75
+ parsable_files = files.filter { |file| file.end_with?('.rb') }
76
+ unless parsable_files.empty?
77
+ system("bundle exec rubocop -A --fail-level=E #{parsable_files.shelljoin}", exception: true)
78
78
  end
79
79
  end
80
80
  end
81
81
  ```
82
82
 
83
- It uses `rubocop -A` to apply `Style/FrozenStringLiteralComment` and other unsafe autocorretion cops.
83
+ It uses `rubocop -A` to apply `Style/FrozenStringLiteralComment` and other unsafe autocorrection cops.
84
84
  `rubocop -A` is unsafe autocorrection, but code generated by default is simple and less likely to
85
85
  be incompatible with `rubocop -A`. If you have problems you can replace it with `rubocop -a` instead.
86
86
 
data/config/default.yml CHANGED
@@ -95,8 +95,9 @@ Rails/ActionControllerTestCase:
95
95
 
96
96
  Rails/ActionFilter:
97
97
  Description: 'Enforces consistent use of action filter methods.'
98
- Enabled: true
98
+ Enabled: false
99
99
  VersionAdded: '0.19'
100
+ VersionChanged: '2.22'
100
101
  EnforcedStyle: action
101
102
  SupportedStyles:
102
103
  - action
@@ -338,7 +339,6 @@ Rails/Date:
338
339
 
339
340
  Rails/DefaultScope:
340
341
  Description: 'Avoid use of `default_scope`.'
341
- StyleGuide: 'https://rails.rubystyle.guide#avoid-default-scope'
342
342
  Enabled: false
343
343
  VersionAdded: '2.7'
344
344
 
@@ -430,6 +430,11 @@ Rails/EnumUniqueness:
430
430
  Include:
431
431
  - app/models/**/*.rb
432
432
 
433
+ Rails/EnvLocal:
434
+ Description: 'Use `Rails.env.local?` instead of `Rails.env.development? || Rails.env.test?`.'
435
+ Enabled: pending
436
+ VersionAdded: '2.22'
437
+
433
438
  Rails/EnvironmentComparison:
434
439
  Description: "Favor `Rails.env.production?` over `Rails.env == 'production'`."
435
440
  Enabled: true
@@ -690,6 +695,9 @@ Rails/NotNullColumn:
690
695
  Enabled: true
691
696
  VersionAdded: '0.43'
692
697
  VersionChanged: '2.20'
698
+ Database: null
699
+ SupportedDatabases:
700
+ - mysql
693
701
  Include:
694
702
  - db/**/*.rb
695
703
 
@@ -894,7 +902,7 @@ Rails/RequireDependency:
894
902
  VersionAdded: '2.10'
895
903
 
896
904
  Rails/ResponseParsedBody:
897
- Description: Prefer `response.parsed_body` to `JSON.parse(response.body)`.
905
+ Description: Prefer `response.parsed_body` to custom parsing logic for `response.body`.
898
906
  Enabled: pending
899
907
  Safe: false
900
908
  VersionAdded: '2.18'
@@ -98,8 +98,15 @@ module RuboCop
98
98
  end
99
99
 
100
100
  def in_where?(node)
101
- send_node = node.each_ancestor(:send).first
102
- send_node && WHERE_METHODS.include?(send_node.method_name)
101
+ send_node = node.each_ancestor(:send, :csend).first
102
+ return false unless send_node
103
+
104
+ return true if WHERE_METHODS.include?(send_node.method_name)
105
+
106
+ receiver = send_node.receiver
107
+ return false unless receiver&.send_type?
108
+
109
+ send_node.method?(:not) && WHERE_METHODS.include?(receiver.method_name)
103
110
  end
104
111
  end
105
112
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # A mixin to extend cops in order to determine the database type.
6
+ #
7
+ # This module automatically detect an adapter from `development` environment
8
+ # in `config/database.yml` or the environment variable `DATABASE_URL`
9
+ # when the `Database` option is not set.
10
+ module DatabaseTypeResolvable
11
+ MYSQL = 'mysql'
12
+ POSTGRESQL = 'postgresql'
13
+
14
+ def database
15
+ cop_config['Database'] || database_from_yaml || database_from_env
16
+ end
17
+
18
+ private
19
+
20
+ def database_from_yaml
21
+ return unless database_yaml
22
+
23
+ case database_adapter
24
+ when 'mysql2', 'trilogy'
25
+ MYSQL
26
+ when 'postgresql', 'postgis'
27
+ POSTGRESQL
28
+ end
29
+ end
30
+
31
+ def database_from_env
32
+ url = ENV['DATABASE_URL'].presence
33
+ return unless url
34
+
35
+ case url
36
+ when %r{\A(mysql2|trilogy)://}
37
+ MYSQL
38
+ when %r{\Apostgres(ql)?://}
39
+ POSTGRESQL
40
+ end
41
+ end
42
+
43
+ def database_yaml
44
+ return unless File.exist?('config/database.yml')
45
+
46
+ yaml = if YAML.respond_to?(:unsafe_load_file)
47
+ YAML.unsafe_load_file('config/database.yml')
48
+ else
49
+ YAML.load_file('config/database.yml')
50
+ end
51
+ return unless yaml.is_a? Hash
52
+
53
+ config = yaml['development']
54
+ return unless config.is_a?(Hash)
55
+
56
+ config
57
+ rescue Psych::SyntaxError
58
+ # noop
59
+ end
60
+
61
+ def database_adapter
62
+ database_yaml['adapter'] || database_yaml.first.last['adapter']
63
+ end
64
+ end
65
+ end
66
+ end
@@ -8,6 +8,9 @@ module RuboCop
8
8
  # The cop is configurable and can enforce the use of the older
9
9
  # something_filter methods or the newer something_action methods.
10
10
  #
11
+ # IMPORTANT: This cop is deprecated. Because the `*_filter` methods were removed in Rails 4.2,
12
+ # and that Rails version is no longer supported by RuboCop Rails. This cop will be removed in RuboCop Rails 3.0.
13
+ #
11
14
  # @example EnforcedStyle: action (default)
12
15
  # # bad
13
16
  # after_filter :do_stuff
@@ -11,10 +11,10 @@ module RuboCop
11
11
  # `update` but the method name remained same in the method definition.
12
12
  #
13
13
  # @example
14
- # #bad
14
+ # # bad
15
15
  # book.update_attributes!(author: 'Alice')
16
16
  #
17
- # #good
17
+ # # good
18
18
  # book.update!(author: 'Alice')
19
19
  class ActiveRecordAliases < Base
20
20
  extend AutoCorrector
@@ -27,13 +27,13 @@ module RuboCop
27
27
 
28
28
  ALIASES = {
29
29
  starts_with?: {
30
- original: :start_with?, matcher: '(send str :starts_with? _)'
30
+ original: :start_with?, matcher: '(call str :starts_with? _)'
31
31
  },
32
32
  ends_with?: {
33
- original: :end_with?, matcher: '(send str :ends_with? _)'
33
+ original: :end_with?, matcher: '(call str :ends_with? _)'
34
34
  },
35
- append: { original: :<<, matcher: '(send array :append _)' },
36
- prepend: { original: :unshift, matcher: '(send array :prepend _)' }
35
+ append: { original: :<<, matcher: '(call array :append _)' },
36
+ prepend: { original: :unshift, matcher: '(call array :prepend _)' }
37
37
  }.freeze
38
38
 
39
39
  ALIASES.each do |aliased_method, options|
@@ -47,13 +47,14 @@ module RuboCop
47
47
  preferred_method = ALIASES[aliased_method][:original]
48
48
  message = format(MSG, prefer: preferred_method, current: aliased_method)
49
49
 
50
- add_offense(node, message: message) do |corrector|
50
+ add_offense(node.loc.selector.join(node.source_range.end), message: message) do |corrector|
51
51
  next if append(node)
52
52
 
53
53
  corrector.replace(node.loc.selector, preferred_method)
54
54
  end
55
55
  end
56
56
  end
57
+ alias on_csend on_send
57
58
  end
58
59
  end
59
60
  end
@@ -48,7 +48,7 @@ module RuboCop
48
48
  seen_callback_names = {}
49
49
 
50
50
  each_after_commit_callback(class_node) do |node|
51
- callback_name = node.arguments[0].value
51
+ callback_name = node.first_argument.value
52
52
  if seen_callback_names.key?(callback_name)
53
53
  add_offense(node, message: format(MSG, name: callback_name))
54
54
  else
@@ -14,7 +14,7 @@ module RuboCop
14
14
  # automatically detect an adapter from `development` environment
15
15
  # in `config/database.yml` or the environment variable `DATABASE_URL`
16
16
  # when the `Database` option is not set.
17
- # If the adapter is not `mysql2` or `postgresql`,
17
+ # If the adapter is not `mysql2`, `trilogy`, `postgresql`, or `postgis`,
18
18
  # this Cop ignores offenses.
19
19
  #
20
20
  # @example
@@ -64,6 +64,8 @@ module RuboCop
64
64
  # end
65
65
  # end
66
66
  class BulkChangeTable < Base
67
+ include DatabaseTypeResolvable
68
+
67
69
  MSG_FOR_CHANGE_TABLE = <<~MSG.chomp
68
70
  You can combine alter queries using `bulk: true` options.
69
71
  MSG
@@ -71,9 +73,6 @@ module RuboCop
71
73
  You can use `change_table :%<table>s, bulk: true` to combine alter queries.
72
74
  MSG
73
75
 
74
- MYSQL = 'mysql'
75
- POSTGRESQL = 'postgresql'
76
-
77
76
  MIGRATION_METHODS = %i[change up down].freeze
78
77
 
79
78
  COMBINABLE_TRANSFORMATIONS = %i[
@@ -175,55 +174,6 @@ module RuboCop
175
174
  options.hash_type? && options.keys.any? { |key| key.sym_type? && key.value == :bulk }
176
175
  end
177
176
 
178
- def database
179
- cop_config['Database'] || database_from_yaml || database_from_env
180
- end
181
-
182
- def database_from_yaml
183
- return nil unless database_yaml
184
-
185
- case database_adapter
186
- when 'mysql2'
187
- MYSQL
188
- when 'postgresql'
189
- POSTGRESQL
190
- end
191
- end
192
-
193
- def database_adapter
194
- database_yaml['adapter'] || database_yaml.first.last['adapter']
195
- end
196
-
197
- def database_yaml
198
- return nil unless File.exist?('config/database.yml')
199
-
200
- yaml = if YAML.respond_to?(:unsafe_load_file)
201
- YAML.unsafe_load_file('config/database.yml')
202
- else
203
- YAML.load_file('config/database.yml')
204
- end
205
- return nil unless yaml.is_a? Hash
206
-
207
- config = yaml['development']
208
- return nil unless config.is_a?(Hash)
209
-
210
- config
211
- rescue Psych::SyntaxError
212
- nil
213
- end
214
-
215
- def database_from_env
216
- url = ENV['DATABASE_URL'].presence
217
- return nil unless url
218
-
219
- case url
220
- when %r{\Amysql2://}
221
- MYSQL
222
- when %r{\Apostgres(ql)?://}
223
- POSTGRESQL
224
- end
225
- end
226
-
227
177
  def support_bulk_alter?
228
178
  case database
229
179
  when MYSQL
@@ -262,7 +212,7 @@ module RuboCop
262
212
  # @param node [RuboCop::AST::SendNode]
263
213
  def add_offense_for_alter_methods(node)
264
214
  # arguments: [{(sym :table)(str "table")} ...]
265
- table_node = node.arguments[0]
215
+ table_node = node.first_argument
266
216
  return unless table_node.is_a? RuboCop::AST::BasicLiteralNode
267
217
 
268
218
  message = format(MSG_FOR_ALTER_METHODS, table: table_node.value)
@@ -284,10 +234,10 @@ module RuboCop
284
234
  # @param new_node [RuboCop::AST::SendNode]
285
235
  def process(new_node)
286
236
  # arguments: [{(sym :table)(str "table")} ...]
287
- table_node = new_node.arguments[0]
237
+ table_node = new_node.first_argument
288
238
  if table_node.is_a? RuboCop::AST::BasicLiteralNode
289
239
  flush unless @nodes.all? do |node|
290
- node.arguments[0].value.to_s == table_node.value.to_s
240
+ node.first_argument.value.to_s == table_node.value.to_s
291
241
  end
292
242
  @nodes << new_node
293
243
  else
@@ -7,7 +7,7 @@ module RuboCop
7
7
  #
8
8
  # NOTE: Allow `tag` when the first argument is a variable because
9
9
  # `tag(name)` is simpler rather than `tag.public_send(name)`.
10
- # And this cop will be renamed to something like `LegacyTag` in the future. (e.g. RuboCop Rails 2.0)
10
+ # And this cop will be renamed to something like `LegacyTag` in the future. (e.g. RuboCop Rails 3.0)
11
11
  #
12
12
  # @example
13
13
  # # bad
@@ -31,10 +31,11 @@ module RuboCop
31
31
  time
32
32
  ].to_set.freeze
33
33
 
34
- # Generated from `ActiveRecord::AttributeMethods.dangerous_attribute_methods` on activerecord 7.0.5.
34
+ # Generated from `ActiveRecord::AttributeMethods.dangerous_attribute_methods` on activerecord 7.1.0.
35
35
  # rubocop:disable Metrics/CollectionLiteralLength
36
36
  DANGEROUS_COLUMN_NAMES = %w[
37
37
  __callbacks
38
+ __id__
38
39
  _assign_attribute
39
40
  _assign_attributes
40
41
  _before_commit_callbacks
@@ -195,11 +196,13 @@ module RuboCop
195
196
  changes_to_save
196
197
  check_record_limit
197
198
  ciphertext_for
199
+ class
198
200
  clear_attribute_change
199
201
  clear_attribute_changes
200
202
  clear_changes_information
201
203
  clear_timestamp_attributes
202
204
  clear_transaction_record_state
205
+ clone
203
206
  collection_cache_versioning
204
207
  column_for_attribute
205
208
  committed
@@ -227,6 +230,7 @@ module RuboCop
227
230
  destroyed
228
231
  destroyed_by_association
229
232
  destroyed_by_association=
233
+ dup
230
234
  each_counter_cached_associations
231
235
  encode_with
232
236
  encrypt
@@ -243,7 +247,9 @@ module RuboCop
243
247
  find_parameter_position
244
248
  forget_attribute_assignments
245
249
  format_for_inspect
250
+ freeze
246
251
  from_json
252
+ frozen?
247
253
  halted_callback_hook
248
254
  has_attribute
249
255
  has_changes_to_save
@@ -252,6 +258,7 @@ module RuboCop
252
258
  has_encrypted_attributes
253
259
  has_encrypted_rich_texts
254
260
  has_transactional_callbacks
261
+ hash
255
262
  id
256
263
  id_before_type_cast
257
264
  id_for_database
@@ -283,6 +290,7 @@ module RuboCop
283
290
  new_record
284
291
  no_touching
285
292
  normalize_reflection_attribute
293
+ object_id
286
294
  partial_inserts
287
295
  partial_updates
288
296
  perform_validations
@@ -420,7 +428,7 @@ module RuboCop
420
428
  when :rename_column
421
429
  node.arguments[2]
422
430
  when *COLUMN_TYPE_METHOD_NAMES
423
- node.arguments[0]
431
+ node.first_argument
424
432
  end
425
433
  end
426
434
 
@@ -20,6 +20,15 @@ module RuboCop
20
20
  # belongs_to :bar
21
21
  # has_one :foo
22
22
  #
23
+ # # bad
24
+ # has_many :foo, class_name: 'Foo'
25
+ # has_many :bar, class_name: 'Foo'
26
+ # has_one :baz
27
+ #
28
+ # # good
29
+ # has_many :bar, class_name: 'Foo'
30
+ # has_one :foo
31
+ #
23
32
  class DuplicateAssociation < Base
24
33
  include RangeHelp
25
34
  extend AutoCorrector
@@ -27,31 +36,76 @@ module RuboCop
27
36
  include ActiveRecordHelper
28
37
 
29
38
  MSG = "Association `%<name>s` is defined multiple times. Don't repeat associations."
39
+ MSG_CLASS_NAME = "Association `class_name: %<name>s` is defined multiple times. Don't repeat associations."
30
40
 
31
41
  def_node_matcher :association, <<~PATTERN
32
- (send nil? {:belongs_to :has_one :has_many :has_and_belongs_to_many} ({sym str} $_) ...)
42
+ (send nil? {:belongs_to :has_one :has_many :has_and_belongs_to_many} ({sym str} $_) $...)
43
+ PATTERN
44
+
45
+ def_node_matcher :class_name, <<~PATTERN
46
+ (hash (pair (sym :class_name) $_))
33
47
  PATTERN
34
48
 
35
49
  def on_class(class_node)
36
50
  return unless active_record?(class_node.parent_class)
37
51
 
38
- offenses(class_node).each do |name, nodes|
39
- nodes.each do |node|
40
- add_offense(node, message: format(MSG, name: name)) do |corrector|
41
- next if same_line?(nodes.last, node)
52
+ association_nodes = association_nodes(class_node)
42
53
 
43
- corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
44
- end
45
- end
54
+ duplicated_association_name_nodes(association_nodes).each do |name, nodes|
55
+ register_offense(name, nodes, MSG)
56
+ end
57
+
58
+ duplicated_class_name_nodes(association_nodes).each do |class_name, nodes|
59
+ register_offense(class_name, nodes, MSG_CLASS_NAME)
46
60
  end
47
61
  end
48
62
 
49
63
  private
50
64
 
51
- def offenses(class_node)
52
- class_send_nodes(class_node).select { |node| association(node) }
53
- .group_by { |node| association(node).to_sym }
54
- .select { |_, nodes| nodes.length > 1 }
65
+ def register_offense(name, nodes, message_template)
66
+ nodes.each do |node|
67
+ add_offense(node, message: format(message_template, name: name)) do |corrector|
68
+ next if same_line?(nodes.last, node)
69
+
70
+ corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
71
+ end
72
+ end
73
+ end
74
+
75
+ def association_nodes(class_node)
76
+ class_send_nodes(class_node).select do |node|
77
+ association(node)&.first
78
+ end
79
+ end
80
+
81
+ def duplicated_association_name_nodes(association_nodes)
82
+ grouped_associations = association_nodes.group_by do |node|
83
+ association(node).first.to_sym
84
+ end
85
+
86
+ leave_duplicated_association(grouped_associations)
87
+ end
88
+
89
+ def duplicated_class_name_nodes(association_nodes)
90
+ filtered_nodes = association_nodes.reject { |node| node.method?(:belongs_to) }
91
+ grouped_associations = filtered_nodes.group_by do |node|
92
+ arguments = association(node).last
93
+ next unless arguments.count == 1
94
+
95
+ if (class_name = class_name(arguments.first))
96
+ class_name.source
97
+ end
98
+ end
99
+
100
+ grouped_associations.delete(nil)
101
+
102
+ leave_duplicated_association(grouped_associations)
103
+ end
104
+
105
+ def leave_duplicated_association(grouped_associations)
106
+ grouped_associations.select do |_, nodes|
107
+ nodes.length > 1
108
+ end
55
109
  end
56
110
  end
57
111
  end
@@ -14,10 +14,10 @@ module RuboCop
14
14
  # when no output would be produced anyway.
15
15
  #
16
16
  # @example
17
- # #bad
17
+ # # bad
18
18
  # Rails.logger.debug "The time is #{Time.zone.now}."
19
19
  #
20
- # #good
20
+ # # good
21
21
  # Rails.logger.debug { "The time is #{Time.zone.now}." }
22
22
  #
23
23
  class EagerEvaluationLogMessage < Base
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Checks for usage of `Rails.env.development? || Rails.env.test?` which
7
+ # can be replaced with `Rails.env.local?`, introduced in Rails 7.1.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # Rails.env.development? || Rails.env.test?
13
+ #
14
+ # # good
15
+ # Rails.env.local?
16
+ #
17
+ class EnvLocal < Base
18
+ extend AutoCorrector
19
+ extend TargetRailsVersion
20
+
21
+ MSG = 'Use `Rails.env.local?` instead.'
22
+ LOCAL_ENVIRONMENTS = %i[development? test?].to_set.freeze
23
+
24
+ minimum_target_rails_version 7.1
25
+
26
+ # @!method rails_env_local_candidate?(node)
27
+ def_node_matcher :rails_env_local_candidate?, <<~PATTERN
28
+ (or
29
+ (send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
30
+ (send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
31
+ )
32
+ PATTERN
33
+
34
+ def on_or(node)
35
+ rails_env_local_candidate?(node) do |*environments|
36
+ next unless environments.to_set == LOCAL_ENVIRONMENTS
37
+
38
+ add_offense(node) do |corrector|
39
+ corrector.replace(node, 'Rails.env.local?')
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -163,9 +163,9 @@ module RuboCop
163
163
 
164
164
  def autocorrect_extension_after_rails_root_join_in_dstr(corrector, node, rails_root_index, extension_node)
165
165
  rails_root_node = node.children[rails_root_index].children.first
166
- return unless rails_root_node.arguments.last.str_type?
166
+ return unless rails_root_node.last_argument.str_type?
167
167
 
168
- corrector.insert_before(rails_root_node.arguments.last.location.end, extension_node.source)
168
+ corrector.insert_before(rails_root_node.last_argument.location.end, extension_node.source)
169
169
  corrector.remove(extension_node)
170
170
  end
171
171
 
@@ -174,7 +174,7 @@ module RuboCop
174
174
  corrector.remove(
175
175
  range_with_surrounding_space(
176
176
  range_with_surrounding_comma(
177
- node.arguments.first.source_range,
177
+ node.first_argument.source_range,
178
178
  :right
179
179
  ),
180
180
  side: :right
@@ -187,7 +187,7 @@ module RuboCop
187
187
  end
188
188
 
189
189
  def autocorrect_rails_root_join_with_string_arguments(corrector, node)
190
- corrector.replace(node.arguments.first, %("#{node.arguments.map(&:value).join('/')}"))
190
+ corrector.replace(node.first_argument, %("#{node.arguments.map(&:value).join('/')}"))
191
191
  node.arguments[1..].each do |argument|
192
192
  corrector.remove(
193
193
  range_with_surrounding_comma(
@@ -221,7 +221,7 @@ module RuboCop
221
221
  end
222
222
 
223
223
  def append_argument(corrector, node, argument_source)
224
- corrector.insert_after(node.arguments.last, %(, "#{argument_source}"))
224
+ corrector.insert_after(node.last_argument, %(, "#{argument_source}"))
225
225
  end
226
226
 
227
227
  def replace_with_rails_root_join(corrector, node, argument_source)
@@ -28,7 +28,7 @@ module RuboCop
28
28
  include RangeHelp
29
29
  extend AutoCorrector
30
30
 
31
- MSG = 'Use `find_by` instead of `where.%<method>s`.'
31
+ MSG = 'Use `find_by` instead of `where%<dot>s%<method>s`.'
32
32
  RESTRICT_ON_SEND = %i[first take].freeze
33
33
 
34
34
  def on_send(node)
@@ -37,7 +37,7 @@ module RuboCop
37
37
 
38
38
  range = offense_range(node)
39
39
 
40
- add_offense(range, message: format(MSG, method: node.method_name)) do |corrector|
40
+ add_offense(range, message: format(MSG, dot: node.loc.dot.source, method: node.method_name)) do |corrector|
41
41
  autocorrect(corrector, node)
42
42
  end
43
43
  end