rubocop-rails 2.27.0 → 2.29.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a20ef13a67612f99cb522625350e57836511b07a2666b75a8cbc556620adf01
4
- data.tar.gz: 436aaa2f094779e0101e17d2cb56f4a7bdd64fbf14360e9e22102be13da413af
3
+ metadata.gz: 210e46df0db68aeef5761f3afeeb86d62a52015be55190d3e6e1fd65368721be
4
+ data.tar.gz: 7ff2ef268227cf39cb23b0479a8cf19c4ecc2acdf62c18f8e1766756e5965119
5
5
  SHA512:
6
- metadata.gz: 4d73f8ec4a0567acff57d0e39b267a24ee615e58d20114fa8fd37d5614ea2d4b3e6435c9ed34db322d7368086f9df150de0646c5e63cfd2987d0847dc3abd057
7
- data.tar.gz: 93f42a0c60daec890a8cd363f588e37e5f65210d63dbb7e85e1c19cd32dc6459d6e593fbc3947b1cef832b981ca7b06bb5871c60cf6439a07e32c3df54257bcc
6
+ metadata.gz: c84c5690318920b054525a7fc21ca4e0f067a2093757f7ff8182d429587c94898ba1ea1160893fe29e9a8ef9427f6cc076f613f283b5b857da0529fea7654a47
7
+ data.tar.gz: ca501a5661bcebf2a6769a8cba620c637872e17023ac19fe8f6415b5ff4305d735ead0d24c7ffaee46f24c6cbeb466fcffeebdfb9cd96836211ab446643fc2a7
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-23 Bozhidar Batsov
1
+ Copyright (c) 2012-25 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/README.md CHANGED
@@ -83,6 +83,22 @@ gems.locked file to find the version of Rails that has been bound to the
83
83
  application. If neither of those files exist, RuboCop will use Rails 5.0
84
84
  as the default.
85
85
 
86
+ ### `AllCops: MigratedSchemaVersion`
87
+
88
+ By specifying the `MigratedSchemaVersion` option, migration files that have already been run can be ignored.
89
+ When `MigratedSchemaVersion: '20241225000000'` is set, migration files lower than or equal to '20241225000000' will be ignored.
90
+ For example, to ignore db/migrate/20241225000000_create_articles.rb and earlier migrations you would configure it the following way:
91
+
92
+ ```yaml
93
+ AllCops:
94
+ MigratedSchemaVersion: '20241225000000'
95
+ ```
96
+
97
+ This prevents inspecting schema settings for already applied migration files.
98
+ Changing already applied migrations should be avoided because it can lead to the schema getting out of sync
99
+ between your local copy and what it actually is in production, depending on when `bin/rails db:migrate` was executed.
100
+ If you want to modify your schema to comply with the cops, you should instead create new migrations.
101
+
86
102
  ## Rails configuration tip
87
103
 
88
104
  In Rails 6.1+, add the following `config.generators.after_generate` setting to
data/config/default.yml CHANGED
@@ -25,6 +25,10 @@ AllCops:
25
25
  # application. If neither of those files exist, RuboCop will use Rails 5.0
26
26
  # as the default.
27
27
  TargetRailsVersion: ~
28
+ # By specifying `MigratedSchemaVersion` option, migration files that have been migrated can be ignored.
29
+ # When `MigratedSchemaVersion: '20241231000000'` is set. Migration files lower than or equal to '20250101000000' will be ignored.
30
+ # For example, this is the timestamp in db/migrate/20250101000000_create_articles.rb.
31
+ MigratedSchemaVersion: ~
28
32
 
29
33
  Lint/NumberConversion:
30
34
  # Add Rails' duration methods to the ignore list for `Lint/NumberConversion`
@@ -694,6 +698,15 @@ Rails/MigrationClassName:
694
698
  Include:
695
699
  - db/**/*.rb
696
700
 
701
+ Rails/MultipleRoutePaths:
702
+ Description: 'Checks for mapping a route with multiple paths, which is deprecated and will be removed in Rails 8.1.'
703
+ Enabled: pending
704
+ Severity: warning
705
+ VersionAdded: '2.29'
706
+ Include:
707
+ - config/routes.rb
708
+ - config/routes/**/*.rb
709
+
697
710
  Rails/NegateInclude:
698
711
  Description: 'Prefer `collection.exclude?(obj)` over `!collection.include?(obj)`.'
699
712
  StyleGuide: 'https://rails.rubystyle.guide#exclude'
@@ -1068,6 +1081,15 @@ Rails/StripHeredoc:
1068
1081
  Enabled: pending
1069
1082
  VersionAdded: '2.15'
1070
1083
 
1084
+ Rails/StrongParametersExpect:
1085
+ Description: 'Enforces the use of `ActionController::Parameters#expect` as a method for strong parameter handling.'
1086
+ Reference: 'https://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-expect'
1087
+ Enabled: pending
1088
+ Include:
1089
+ - app/controllers/**/*.rb
1090
+ SafeAutoCorrect: false
1091
+ VersionAdded: '2.29'
1092
+
1071
1093
  Rails/TableNameAssignment:
1072
1094
  Description: >-
1073
1095
  Do not use `self.table_name =`. Use Inflections or `table_name_prefix` instead.
@@ -6,7 +6,7 @@ module RuboCop
6
6
  module IndexMethod # rubocop:disable Metrics/ModuleLength
7
7
  RESTRICT_ON_SEND = %i[each_with_object to_h map collect []].freeze
8
8
 
9
- def on_block(node) # rubocop:todo InternalAffairs/NumblockHandler
9
+ def on_block(node)
10
10
  on_bad_each_with_object(node) do |*match|
11
11
  handle_possible_offense(node, match, 'each_with_object')
12
12
  end
@@ -18,6 +18,8 @@ module RuboCop
18
18
  end
19
19
  end
20
20
 
21
+ alias on_numblock on_block
22
+
21
23
  def on_send(node)
22
24
  on_bad_map_to_h(node) do |*match|
23
25
  handle_possible_offense(node, match, 'map { ... }.to_h')
@@ -122,9 +124,9 @@ module RuboCop
122
124
  end
123
125
 
124
126
  def self.from_map_to_h(node, match)
125
- strip_trailing_chars = 0
126
-
127
- unless node.parent&.block_type?
127
+ if node.block_literal?
128
+ strip_trailing_chars = 0
129
+ else
128
130
  map_range = node.children.first.source_range
129
131
  node_range = node.source_range
130
132
  strip_trailing_chars = node_range.end_pos - map_range.end_pos
@@ -153,11 +155,16 @@ module RuboCop
153
155
  end
154
156
 
155
157
  def set_new_arg_name(transformed_argname, corrector)
158
+ return if block_node.numblock_type?
159
+
156
160
  corrector.replace(block_node.arguments, "|#{transformed_argname}|")
157
161
  end
158
162
 
159
163
  def set_new_body_expression(transforming_body_expr, corrector)
160
- corrector.replace(block_node.body, transforming_body_expr.source)
164
+ body_source = transforming_body_expr.source
165
+ body_source = "{ #{body_source} }" if transforming_body_expr.hash_type? && !transforming_body_expr.braces?
166
+
167
+ corrector.replace(block_node.body, body_source)
161
168
  end
162
169
  end
163
170
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Common functionality for cops working with routes.
6
+ module RoutesHelper
7
+ extend NodePattern::Macros
8
+
9
+ HTTP_METHODS = %i[get post put patch delete].freeze
10
+
11
+ def_node_matcher :routes_draw?, <<~PATTERN
12
+ (send (send _ :routes) :draw)
13
+ PATTERN
14
+
15
+ def within_routes?(node)
16
+ node.each_ancestor(:block).any? { |block| routes_draw?(block.send_node) }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -19,6 +19,7 @@ module RuboCop
19
19
  class AddColumnIndex < Base
20
20
  extend AutoCorrector
21
21
  include RangeHelp
22
+ include MigrationsHelper
22
23
 
23
24
  MSG = '`add_column` does not accept an `index` key, use `add_index` instead.'
24
25
  RESTRICT_ON_SEND = %i[add_column].freeze
@@ -65,6 +65,7 @@ module RuboCop
65
65
  # end
66
66
  class BulkChangeTable < Base
67
67
  include DatabaseTypeResolvable
68
+ include MigrationsHelper
68
69
 
69
70
  MSG_FOR_CHANGE_TABLE = <<~MSG.chomp
70
71
  You can combine alter queries using `bulk: true` options.
@@ -14,6 +14,8 @@ module RuboCop
14
14
  # # good
15
15
  # add_column :users, :saved
16
16
  class DangerousColumnNames < Base # rubocop:disable Metrics/ClassLength
17
+ include MigrationsHelper
18
+
17
19
  COLUMN_TYPE_METHOD_NAMES = %i[
18
20
  bigint
19
21
  binary
@@ -66,6 +66,8 @@ module RuboCop
66
66
 
67
67
  def on_send(node)
68
68
  check_for_file_join_with_rails_root(node)
69
+ return unless node.receiver
70
+
69
71
  check_for_rails_root_join_with_slash_separated_path(node)
70
72
  check_for_rails_root_join_with_string_arguments(node)
71
73
  end
@@ -76,6 +78,7 @@ module RuboCop
76
78
  rails_root_index = find_rails_root_index(node)
77
79
  slash_node = node.children[rails_root_index + 1]
78
80
  return unless slash_node&.str_type? && slash_node.source.start_with?(File::SEPARATOR)
81
+ return unless node.children[rails_root_index].children.first.send_type?
79
82
 
80
83
  register_offense(node, require_to_s: false) do |corrector|
81
84
  autocorrect_slash_after_rails_root_in_dstr(corrector, node, rails_root_index)
@@ -40,6 +40,10 @@ module RuboCop
40
40
  (hash (kwsplat _))
41
41
  PATTERN
42
42
 
43
+ def_node_matcher :forwarded_kwrestarg?, <<~PATTERN
44
+ (hash (forwarded-kwrestarg))
45
+ PATTERN
46
+
43
47
  def_node_matcher :include_rack_test_methods?, <<~PATTERN
44
48
  (send nil? :include
45
49
  (const
@@ -83,7 +87,9 @@ module RuboCop
83
87
  end
84
88
  end
85
89
 
90
+ # rubocop:disable Metrics/CyclomaticComplexity
86
91
  def needs_conversion?(data)
92
+ return false if data.forwarded_args_type? || forwarded_kwrestarg?(data)
87
93
  return true unless data.hash_type?
88
94
  return false if kwsplat_hash?(data)
89
95
 
@@ -91,6 +97,7 @@ module RuboCop
91
97
  special_keyword_arg?(pair.key) || (format_arg?(pair.key) && data.pairs.one?)
92
98
  end
93
99
  end
100
+ # rubocop:enable Metrics/CyclomaticComplexity
94
101
 
95
102
  def special_keyword_arg?(node)
96
103
  node.sym_type? && KEYWORD_ARGS.include?(node.value)
@@ -29,18 +29,28 @@ module RuboCop
29
29
  PATTERN
30
30
 
31
31
  def_node_matcher :on_bad_to_h, <<~PATTERN
32
- (block
33
- (call _ :to_h)
34
- (args (arg $_el))
35
- (array $_ (lvar _el)))
32
+ {
33
+ (block
34
+ (call _ :to_h)
35
+ (args (arg $_el))
36
+ (array $_ (lvar _el)))
37
+ (numblock
38
+ (call _ :to_h) $1
39
+ (array $_ (lvar :_1)))
40
+ }
36
41
  PATTERN
37
42
 
38
43
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
39
44
  (call
40
- (block
41
- (call _ {:map :collect})
42
- (args (arg $_el))
43
- (array $_ (lvar _el)))
45
+ {
46
+ (block
47
+ (call _ {:map :collect})
48
+ (args (arg $_el))
49
+ (array $_ (lvar _el)))
50
+ (numblock
51
+ (call _ {:map :collect}) $1
52
+ (array $_ (lvar :_1)))
53
+ }
44
54
  :to_h)
45
55
  PATTERN
46
56
 
@@ -48,10 +58,16 @@ module RuboCop
48
58
  (send
49
59
  (const {nil? cbase} :Hash)
50
60
  :[]
51
- (block
52
- (call _ {:map :collect})
53
- (args (arg $_el))
54
- (array $_ (lvar _el))))
61
+ {
62
+ (block
63
+ (call _ {:map :collect})
64
+ (args (arg $_el))
65
+ (array $_ (lvar _el)))
66
+ (numblock
67
+ (call _ {:map :collect}) $1
68
+ (array $_ (lvar :_1)))
69
+ }
70
+ )
55
71
  PATTERN
56
72
 
57
73
  private
@@ -32,18 +32,28 @@ module RuboCop
32
32
  PATTERN
33
33
 
34
34
  def_node_matcher :on_bad_to_h, <<~PATTERN
35
- (block
36
- (call _ :to_h)
37
- (args (arg $_el))
38
- (array (lvar _el) $_))
35
+ {
36
+ (block
37
+ (call _ :to_h)
38
+ (args (arg $_el))
39
+ (array (lvar _el) $_))
40
+ (numblock
41
+ (call _ :to_h) $1
42
+ (array (lvar :_1) $_))
43
+ }
39
44
  PATTERN
40
45
 
41
46
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
42
47
  (call
43
- (block
44
- (call _ {:map :collect})
45
- (args (arg $_el))
46
- (array (lvar _el) $_))
48
+ {
49
+ (block
50
+ (call _ {:map :collect})
51
+ (args (arg $_el))
52
+ (array (lvar _el) $_))
53
+ (numblock
54
+ (call _ {:map :collect}) $1
55
+ (array (lvar :_1) $_))
56
+ }
47
57
  :to_h)
48
58
  PATTERN
49
59
 
@@ -51,10 +61,16 @@ module RuboCop
51
61
  (send
52
62
  (const {nil? cbase} :Hash)
53
63
  :[]
54
- (block
55
- (call _ {:map :collect})
56
- (args (arg $_el))
57
- (array (lvar _el) $_)))
64
+ {
65
+ (block
66
+ (call _ {:map :collect})
67
+ (args (arg $_el))
68
+ (array (lvar _el) $_))
69
+ (numblock
70
+ (call _ {:map :collect}) $1
71
+ (array (lvar :_1) $_))
72
+ }
73
+ )
58
74
  PATTERN
59
75
 
60
76
  private
@@ -21,11 +21,11 @@ module RuboCop
21
21
  # match 'photos/:id', to: 'photos#show', via: :all
22
22
  #
23
23
  class MatchRoute < Base
24
+ include RoutesHelper
24
25
  extend AutoCorrector
25
26
 
26
27
  MSG = 'Use `%<http_method>s` instead of `match` to define a route.'
27
28
  RESTRICT_ON_SEND = %i[match].freeze
28
- HTTP_METHODS = %i[get post put patch delete].freeze
29
29
 
30
30
  def_node_matcher :match_method_call?, <<~PATTERN
31
31
  (send nil? :match $_ $(hash ...) ?)
@@ -60,14 +60,6 @@ module RuboCop
60
60
  end
61
61
  end
62
62
 
63
- def_node_matcher :routes_draw?, <<~PATTERN
64
- (send (send _ :routes) :draw)
65
- PATTERN
66
-
67
- def within_routes?(node)
68
- node.each_ancestor(:block).any? { |a| routes_draw?(a.send_node) }
69
- end
70
-
71
63
  def extract_via(node)
72
64
  via_pair = via_pair(node)
73
65
  return %i[get] unless via_pair
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Checks for mapping a route with multiple paths, which is deprecated and will be removed in Rails 8.1.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # get '/users', '/other_path', to: 'users#index'
12
+ #
13
+ # # good
14
+ # get '/users', to: 'users#index'
15
+ # get '/other_path', to: 'users#index'
16
+ #
17
+ class MultipleRoutePaths < Base
18
+ include RoutesHelper
19
+ extend AutoCorrector
20
+
21
+ MSG = 'Use separate routes instead of combining multiple route paths in a single route.'
22
+ RESTRICT_ON_SEND = HTTP_METHODS
23
+
24
+ IGNORED_ARGUMENT_TYPES = %i[array hash].freeze
25
+
26
+ def on_send(node)
27
+ return unless within_routes?(node)
28
+
29
+ route_paths = node.arguments.reject { |argument| IGNORED_ARGUMENT_TYPES.include?(argument.type) }
30
+ return if route_paths.count < 2
31
+
32
+ add_offense(node) do |corrector|
33
+ corrector.replace(node, migrate_to_multiple_routes(node, route_paths))
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def migrate_to_multiple_routes(node, route_paths)
40
+ rest = route_paths.last.source_range.end.join(node.source_range.end).source
41
+ indentation = ' ' * node.source_range.column
42
+
43
+ route_paths.map do |route_path|
44
+ "#{node.method_name} #{route_path.source}#{rest}"
45
+ end.join("\n#{indentation}")
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -41,10 +41,14 @@ module RuboCop
41
41
  # change_column_null :products, :category_id, false
42
42
  class NotNullColumn < Base
43
43
  include DatabaseTypeResolvable
44
+ include MigrationsHelper
44
45
 
45
46
  MSG = 'Do not add a NOT NULL column without a default value.'
46
47
  RESTRICT_ON_SEND = %i[add_column add_reference].freeze
47
48
 
49
+ VIRTUAL_TYPE_VALUES = [:virtual, 'virtual'].freeze
50
+ TEXT_TYPE_VALUES = [:text, 'text'].freeze
51
+
48
52
  def_node_matcher :add_not_null_column?, <<~PATTERN
49
53
  (send nil? :add_column _ _ $_ (hash $...))
50
54
  PATTERN
@@ -91,8 +95,8 @@ module RuboCop
91
95
 
92
96
  def check_column(type, pairs)
93
97
  if type.respond_to?(:value)
94
- return if type.value == :virtual || type.value == 'virtual'
95
- return if (type.value == :text || type.value == 'text') && database == MYSQL
98
+ return if VIRTUAL_TYPE_VALUES.include?(type.value)
99
+ return if TEXT_TYPE_VALUES.include?(type.value) && database == MYSQL
96
100
  end
97
101
 
98
102
  check_pairs(pairs)
@@ -9,6 +9,24 @@ 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
+ # NOTE: If the receiver's relation is not loaded and `pluck` is used inside an iteration,
13
+ # it may result in N+1 queries because `pluck` queries the database on each iteration.
14
+ # This cop ignores offenses for `map/collect` when they are suspected to be part of an iteration
15
+ # to prevent such potential issues.
16
+ #
17
+ # [source,ruby]
18
+ # ----
19
+ # users = User.all
20
+ # 5.times do
21
+ # users.map { |user| user[:foo] } # Only one query is executed
22
+ # end
23
+ #
24
+ # users = User.all
25
+ # 5.times do
26
+ # users.pluck(:id) # A query is executed on every iteration
27
+ # end
28
+ # ----
29
+ #
12
30
  # @safety
13
31
  # This cop is unsafe because model can use column aliases.
14
32
  #
@@ -42,6 +60,8 @@ module RuboCop
42
60
  PATTERN
43
61
 
44
62
  def on_block(node)
63
+ return if node.each_ancestor(:block, :numblock).any?
64
+
45
65
  pluck_candidate?(node) do |argument, key|
46
66
  next if key.regexp_type? || !use_one_block_argument?(argument)
47
67
 
@@ -174,7 +174,7 @@ module RuboCop
174
174
  parent = node.parent
175
175
  return false unless POSSIBLE_ENUMERABLE_BLOCK_METHODS.include?(parent.method_name)
176
176
 
177
- parent.parent&.block_type? || parent.parent&.numblock_type? || parent.first_argument&.block_pass_type?
177
+ parent.block_literal? || parent.first_argument&.block_pass_type?
178
178
  end
179
179
 
180
180
  def sensitive_association_method?(node)
@@ -215,8 +215,10 @@ module RuboCop
215
215
  end
216
216
 
217
217
  def check_drop_table_node(node)
218
+ return unless (last_argument = node.last_argument)
219
+
218
220
  drop_table_call(node) do
219
- unless node.parent.block_type? || node.last_argument.block_pass_type?
221
+ unless node.parent.block_type? || last_argument.block_pass_type?
220
222
  add_offense(node, message: format(MSG, action: 'drop_table(without block)'))
221
223
  end
222
224
  end
@@ -244,7 +244,7 @@ module RuboCop
244
244
  end
245
245
 
246
246
  def operator_or_single_negative?(node)
247
- node.or_type? || node.and_type? || single_negative?(node)
247
+ node.operator_keyword? || single_negative?(node)
248
248
  end
249
249
 
250
250
  def conditional?(parent)
@@ -23,6 +23,7 @@ module RuboCop
23
23
  #
24
24
  class SchemaComment < Base
25
25
  include ActiveRecordMigrationsHelper
26
+ include MigrationsHelper
26
27
 
27
28
  COLUMN_MSG = 'New database column without `comment`.'
28
29
  TABLE_MSG = 'New database table without `comment`.'
@@ -54,7 +54,7 @@ module RuboCop
54
54
 
55
55
  # rubocop:disable Metrics/AbcSize
56
56
  def autocorrect(corrector, select_node, node, preferred_method)
57
- corrector.remove(select_node.loc.dot || node.loc.dot)
57
+ corrector.remove(select_node.parent.loc.dot)
58
58
  corrector.remove(select_node.loc.selector.begin.join(select_node.source_range.end))
59
59
  corrector.replace(node.loc.selector.begin.join(node.source_range.end), preferred_method)
60
60
  end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Enforces the use of `ActionController::Parameters#expect` as a method for strong parameter handling.
7
+ #
8
+ # @safety
9
+ # This cop's autocorrection is considered unsafe because there are cases where the HTTP status may change
10
+ # from 500 to 400 when handling invalid parameters. This change, however, reflects an intentional
11
+ # incompatibility introduced for valid reasons by the `expect` method, which aligns better with
12
+ # strong parameter conventions.
13
+ #
14
+ # @example
15
+ #
16
+ # # bad
17
+ # params.require(:user).permit(:name, :age)
18
+ # params.permit(user: [:name, :age]).require(:user)
19
+ #
20
+ # # good
21
+ # params.expect(user: [:name, :age])
22
+ #
23
+ class StrongParametersExpect < Base
24
+ extend AutoCorrector
25
+ extend TargetRailsVersion
26
+
27
+ MSG = 'Use `%<prefer>s` instead.'
28
+ RESTRICT_ON_SEND = %i[require permit].freeze
29
+
30
+ minimum_target_rails_version 8.0
31
+
32
+ def_node_matcher :params_require_permit, <<~PATTERN
33
+ $(call
34
+ $(call
35
+ (send nil? :params) :require _) :permit ...)
36
+ PATTERN
37
+
38
+ def_node_matcher :params_permit_require, <<~PATTERN
39
+ $(call
40
+ $(call
41
+ (send nil? :params) :permit (hash (pair _require_param_name _ )))
42
+ :require _require_param_name)
43
+ PATTERN
44
+
45
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
46
+ def on_send(node)
47
+ return if part_of_ignored_node?(node)
48
+
49
+ if (permit_method, require_method = params_require_permit(node))
50
+ range = offense_range(require_method, node)
51
+ prefer = expect_method(require_method, permit_method)
52
+ replace_argument = true
53
+ elsif (require_method, permit_method = params_permit_require(node))
54
+ range = offense_range(permit_method, node)
55
+ prefer = "expect(#{permit_method.arguments.map(&:source).join(', ')})"
56
+ replace_argument = false
57
+ else
58
+ return
59
+ end
60
+
61
+ add_offense(range, message: format(MSG, prefer: prefer)) do |corrector|
62
+ corrector.remove(require_method.loc.dot.join(require_method.source_range.end))
63
+ corrector.replace(permit_method.loc.selector, 'expect')
64
+ if replace_argument
65
+ corrector.insert_before(permit_method.first_argument, "#{require_key(require_method)}[")
66
+ corrector.insert_after(permit_method.last_argument, ']')
67
+ end
68
+ end
69
+
70
+ ignore_node(node)
71
+ end
72
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
73
+ alias on_csend on_send
74
+
75
+ private
76
+
77
+ def offense_range(method_node, node)
78
+ method_node.loc.selector.join(node.source_range.end)
79
+ end
80
+
81
+ def expect_method(require_method, permit_method)
82
+ require_key = require_key(require_method)
83
+ permit_args = permit_method.arguments.map(&:source).join(', ')
84
+
85
+ arguments = "#{require_key}[#{permit_args}]"
86
+
87
+ "expect(#{arguments})"
88
+ end
89
+
90
+ def require_key(require_method)
91
+ if (first_argument = require_method.first_argument).respond_to?(:value)
92
+ require_arg = first_argument.value
93
+ separator = ': '
94
+ else
95
+ require_arg = first_argument.source
96
+ separator = ' => '
97
+ end
98
+
99
+ "#{require_arg}#{separator}"
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -18,8 +18,9 @@ module RuboCop
18
18
  # t.boolean :active, default: true, null: false
19
19
  #
20
20
  class ThreeStateBooleanColumn < Base
21
- MSG = 'Boolean columns should always have a default value and a `NOT NULL` constraint.'
21
+ include MigrationsHelper
22
22
 
23
+ MSG = 'Boolean columns should always have a default value and a `NOT NULL` constraint.'
23
24
  RESTRICT_ON_SEND = %i[add_column column boolean].freeze
24
25
 
25
26
  def_node_matcher :three_state_boolean?, <<~PATTERN
@@ -97,11 +97,9 @@ module RuboCop
97
97
  end
98
98
 
99
99
  def autocorrect_time_new(node, corrector)
100
- if node.arguments?
101
- corrector.replace(node.loc.selector, 'local')
102
- else
103
- corrector.replace(node.loc.selector, 'now')
104
- end
100
+ replacement = replacement(node)
101
+
102
+ corrector.replace(node.loc.selector, replacement)
105
103
  end
106
104
 
107
105
  # remove redundant `.in_time_zone` from `Time.zone.now.in_time_zone`
@@ -183,7 +181,7 @@ module RuboCop
183
181
 
184
182
  def safe_method(method_name, node)
185
183
  if %w[new current].include?(method_name)
186
- node.arguments? ? 'local' : 'now'
184
+ replacement(node)
187
185
  else
188
186
  method_name
189
187
  end
@@ -259,6 +257,12 @@ module RuboCop
259
257
  pair.key.sym_type? && pair.key.value == :in && !pair.value.nil_type?
260
258
  end
261
259
  end
260
+
261
+ def replacement(node)
262
+ return 'now' unless node.arguments?
263
+
264
+ node.first_argument.str_type? ? 'parse' : 'local'
265
+ end
262
266
  end
263
267
  end
264
268
  end
@@ -7,9 +7,11 @@ require_relative 'mixin/database_type_resolvable'
7
7
  require_relative 'mixin/enforce_superclass'
8
8
  require_relative 'mixin/index_method'
9
9
  require_relative 'mixin/migrations_helper'
10
+ require_relative 'mixin/routes_helper'
10
11
  require_relative 'mixin/target_rails_version'
11
12
 
12
13
  require_relative 'rails/action_controller_flash_before_render'
14
+ require_relative 'rails/strong_parameters_expect'
13
15
  require_relative 'rails/action_controller_test_case'
14
16
  require_relative 'rails/action_filter'
15
17
  require_relative 'rails/action_order'
@@ -77,6 +79,7 @@ require_relative 'rails/link_to_blank'
77
79
  require_relative 'rails/mailer_name'
78
80
  require_relative 'rails/match_route'
79
81
  require_relative 'rails/migration_class_name'
82
+ require_relative 'rails/multiple_route_paths'
80
83
  require_relative 'rails/negate_include'
81
84
  require_relative 'rails/not_null_column'
82
85
  require_relative 'rails/order_by_id'
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Rails
5
+ # This module allows cops to detect and ignore files that have already been migrated
6
+ # by leveraging the `AllCops: MigratedSchemaVersion` configuration.
7
+ #
8
+ # [source,yaml]
9
+ # -----
10
+ # AllCops:
11
+ # MigratedSchemaVersion: '20241225000000'
12
+ # -----
13
+ #
14
+ # When applied to cops, it overrides the `add_global_offense` and `add_offense` methods,
15
+ # ensuring that cops skip processing if the file is determined to be a migrated file
16
+ # based on the schema version.
17
+ #
18
+ # @api private
19
+ module MigrationFileSkippable
20
+ def add_global_offense(message = nil, severity: nil)
21
+ return if already_migrated_file?
22
+
23
+ super if method(__method__).super_method
24
+ end
25
+
26
+ def add_offense(node_or_range, message: nil, severity: nil, &block)
27
+ return if already_migrated_file?
28
+
29
+ super if method(__method__).super_method
30
+ end
31
+
32
+ def self.apply_to_cops!
33
+ RuboCop::Cop::Registry.all.each { |cop| cop.prepend(MigrationFileSkippable) }
34
+ end
35
+
36
+ private
37
+
38
+ def already_migrated_file?
39
+ return false unless migrated_schema_version
40
+
41
+ match_data = File.basename(processed_source.file_path).match(/(?<timestamp>\d{14})/)
42
+ schema_version = match_data['timestamp'] if match_data
43
+
44
+ return false unless schema_version
45
+
46
+ schema_version <= migrated_schema_version.to_s # Ignore applied migration files.
47
+ end
48
+
49
+ def migrated_schema_version
50
+ @migrated_schema_version ||= config.for_all_cops.fetch('MigratedSchemaVersion', nil)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -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.27.0'
7
+ STRING = '2.29.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,6 @@ 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, permitted_classes: [Regexp, Symbol]).freeze
9
8
 
10
9
  private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
11
10
 
data/lib/rubocop-rails.rb CHANGED
@@ -15,6 +15,9 @@ RuboCop::Rails::Inject.defaults!
15
15
 
16
16
  require_relative 'rubocop/cop/rails_cops'
17
17
 
18
+ require_relative 'rubocop/rails/migration_file_skippable'
19
+ RuboCop::Rails::MigrationFileSkippable.apply_to_cops!
20
+
18
21
  RuboCop::Cop::Style::HashExcept.minimum_target_ruby_version(2.0)
19
22
 
20
23
  RuboCop::Cop::Style::InverseMethods.singleton_class.prepend(
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.27.0
4
+ version: 2.29.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -9,7 +9,7 @@ authors:
9
9
  - Yuji Nakayama
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-10-26 00:00:00.000000000 Z
12
+ date: 2025-01-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -101,6 +101,7 @@ files:
101
101
  - lib/rubocop/cop/mixin/enforce_superclass.rb
102
102
  - lib/rubocop/cop/mixin/index_method.rb
103
103
  - lib/rubocop/cop/mixin/migrations_helper.rb
104
+ - lib/rubocop/cop/mixin/routes_helper.rb
104
105
  - lib/rubocop/cop/mixin/target_rails_version.rb
105
106
  - lib/rubocop/cop/rails/action_controller_flash_before_render.rb
106
107
  - lib/rubocop/cop/rails/action_controller_test_case.rb
@@ -170,6 +171,7 @@ files:
170
171
  - lib/rubocop/cop/rails/mailer_name.rb
171
172
  - lib/rubocop/cop/rails/match_route.rb
172
173
  - lib/rubocop/cop/rails/migration_class_name.rb
174
+ - lib/rubocop/cop/rails/multiple_route_paths.rb
173
175
  - lib/rubocop/cop/rails/negate_include.rb
174
176
  - lib/rubocop/cop/rails/not_null_column.rb
175
177
  - lib/rubocop/cop/rails/order_by_id.rb
@@ -213,6 +215,7 @@ files:
213
215
  - lib/rubocop/cop/rails/skips_model_validations.rb
214
216
  - lib/rubocop/cop/rails/squished_sql_heredocs.rb
215
217
  - lib/rubocop/cop/rails/strip_heredoc.rb
218
+ - lib/rubocop/cop/rails/strong_parameters_expect.rb
216
219
  - lib/rubocop/cop/rails/table_name_assignment.rb
217
220
  - lib/rubocop/cop/rails/three_state_boolean_column.rb
218
221
  - lib/rubocop/cop/rails/time_zone.rb
@@ -236,6 +239,7 @@ files:
236
239
  - lib/rubocop/cop/rails_cops.rb
237
240
  - lib/rubocop/rails.rb
238
241
  - lib/rubocop/rails/inject.rb
242
+ - lib/rubocop/rails/migration_file_skippable.rb
239
243
  - lib/rubocop/rails/schema_loader.rb
240
244
  - lib/rubocop/rails/schema_loader/schema.rb
241
245
  - lib/rubocop/rails/version.rb
@@ -246,7 +250,7 @@ metadata:
246
250
  homepage_uri: https://docs.rubocop.org/rubocop-rails/
247
251
  changelog_uri: https://github.com/rubocop/rubocop-rails/blob/master/CHANGELOG.md
248
252
  source_code_uri: https://github.com/rubocop/rubocop-rails/
249
- documentation_uri: https://docs.rubocop.org/rubocop-rails/2.27/
253
+ documentation_uri: https://docs.rubocop.org/rubocop-rails/2.29/
250
254
  bug_tracker_uri: https://github.com/rubocop/rubocop-rails/issues
251
255
  rubygems_mfa_required: 'true'
252
256
  rdoc_options: []
@@ -263,7 +267,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
263
267
  - !ruby/object:Gem::Version
264
268
  version: '0'
265
269
  requirements: []
266
- rubygems_version: 3.6.0.dev
270
+ rubygems_version: 3.6.2
267
271
  specification_version: 4
268
272
  summary: Automatic Rails code style checking tool.
269
273
  test_files: []