rubocop-rails 2.24.1 → 2.26.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -2
  3. data/config/default.yml +29 -6
  4. data/lib/rubocop/cop/mixin/target_rails_version.rb +29 -2
  5. data/lib/rubocop/cop/rails/action_order.rb +1 -5
  6. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +1 -5
  7. data/lib/rubocop/cop/rails/application_record.rb +4 -0
  8. data/lib/rubocop/cop/rails/bulk_change_table.rb +10 -4
  9. data/lib/rubocop/cop/rails/compact_blank.rb +29 -8
  10. data/lib/rubocop/cop/rails/date.rb +2 -2
  11. data/lib/rubocop/cop/rails/enum_hash.rb +31 -8
  12. data/lib/rubocop/cop/rails/enum_syntax.rb +128 -0
  13. data/lib/rubocop/cop/rails/enum_uniqueness.rb +29 -7
  14. data/lib/rubocop/cop/rails/file_path.rb +1 -1
  15. data/lib/rubocop/cop/rails/http_status.rb +12 -2
  16. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +1 -1
  17. data/lib/rubocop/cop/rails/link_to_blank.rb +2 -2
  18. data/lib/rubocop/cop/rails/not_null_column.rb +93 -13
  19. data/lib/rubocop/cop/rails/pick.rb +4 -0
  20. data/lib/rubocop/cop/rails/pluck_in_where.rb +17 -8
  21. data/lib/rubocop/cop/rails/pluralization_grammar.rb +29 -15
  22. data/lib/rubocop/cop/rails/present.rb +0 -2
  23. data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +0 -29
  24. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +1 -1
  25. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +9 -0
  26. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +1 -1
  27. data/lib/rubocop/cop/rails/reflection_class_name.rb +1 -1
  28. data/lib/rubocop/cop/rails/render_plain_text.rb +6 -3
  29. data/lib/rubocop/cop/rails/request_referer.rb +1 -1
  30. data/lib/rubocop/cop/rails/root_pathname_methods.rb +15 -11
  31. data/lib/rubocop/cop/rails/skips_model_validations.rb +8 -3
  32. data/lib/rubocop/cop/rails/unknown_env.rb +1 -1
  33. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +6 -0
  34. data/lib/rubocop/cop/rails/validation.rb +8 -3
  35. data/lib/rubocop/cop/rails/where_equals.rb +28 -12
  36. data/lib/rubocop/cop/rails/where_not.rb +11 -6
  37. data/lib/rubocop/cop/rails/where_range.rb +203 -0
  38. data/lib/rubocop/cop/rails_cops.rb +2 -0
  39. data/lib/rubocop/rails/schema_loader/schema.rb +1 -1
  40. data/lib/rubocop/rails/version.rb +1 -1
  41. metadata +8 -6
@@ -13,6 +13,8 @@ module RuboCop
13
13
  # render plain: 'foo/bar', status: 304
14
14
  # redirect_to root_url, status: 301
15
15
  # head 200
16
+ # assert_response 200
17
+ # assert_redirected_to '/some/path', status: 301
16
18
  #
17
19
  # # good
18
20
  # render :foo, status: :ok
@@ -20,6 +22,8 @@ module RuboCop
20
22
  # render plain: 'foo/bar', status: :not_modified
21
23
  # redirect_to root_url, status: :moved_permanently
22
24
  # head :ok
25
+ # assert_response :ok
26
+ # assert_redirected_to '/some/path', status: :moved_permanently
23
27
  #
24
28
  # @example EnforcedStyle: numeric
25
29
  # # bad
@@ -28,6 +32,8 @@ module RuboCop
28
32
  # render plain: 'foo/bar', status: :not_modified
29
33
  # redirect_to root_url, status: :moved_permanently
30
34
  # head :ok
35
+ # assert_response :ok
36
+ # assert_redirected_to '/some/path', status: :moved_permanently
31
37
  #
32
38
  # # good
33
39
  # render :foo, status: 200
@@ -35,18 +41,22 @@ module RuboCop
35
41
  # render plain: 'foo/bar', status: 304
36
42
  # redirect_to root_url, status: 301
37
43
  # head 200
44
+ # assert_response 200
45
+ # assert_redirected_to '/some/path', status: 301
38
46
  #
39
47
  class HttpStatus < Base
40
48
  include ConfigurableEnforcedStyle
41
49
  extend AutoCorrector
42
50
 
43
- RESTRICT_ON_SEND = %i[render redirect_to head].freeze
51
+ RESTRICT_ON_SEND = %i[render redirect_to head assert_response assert_redirected_to].freeze
44
52
 
45
53
  def_node_matcher :http_status, <<~PATTERN
46
54
  {
47
55
  (send nil? {:render :redirect_to} _ $hash)
48
56
  (send nil? {:render :redirect_to} $hash)
49
- (send nil? :head ${int sym} ...)
57
+ (send nil? {:head :assert_response} ${int sym} ...)
58
+ (send nil? :assert_redirected_to _ $hash ...)
59
+ (send nil? :assert_redirected_to $hash ...)
50
60
  }
51
61
  PATTERN
52
62
 
@@ -52,7 +52,7 @@ module RuboCop
52
52
  (send
53
53
  nil?
54
54
  {#{FILTERS.join(' ')}}
55
- _
55
+ ...
56
56
  $_)
57
57
  PATTERN
58
58
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # Checks for calls to `link_to` that contain a
6
+ # Checks for calls to `link_to`, `link_to_if`, and `link_to_unless` methods that contain a
7
7
  # `target: '_blank'` but no `rel: 'noopener'`. This can be a security
8
8
  # risk as the loaded page will have control over the previous page
9
9
  # and could change its location for phishing purposes.
@@ -24,7 +24,7 @@ module RuboCop
24
24
  extend AutoCorrector
25
25
 
26
26
  MSG = 'Specify a `:rel` option containing noopener.'
27
- RESTRICT_ON_SEND = %i[link_to].freeze
27
+ RESTRICT_ON_SEND = %i[link_to link_to_if link_to_unless].freeze
28
28
 
29
29
  def_node_matcher :blank_target?, <<~PATTERN
30
30
  (pair {(sym :target) (str "target")} {(str "_blank") (sym :_blank)})
@@ -3,24 +3,42 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # Checks for add_column call with NOT NULL constraint in migration file.
6
+ # Checks for add_column calls with a NOT NULL constraint without a default
7
+ # value.
7
8
  #
8
- # `TEXT` can have default values in PostgreSQL, but not in MySQL.
9
- # It will automatically detect an adapter from `development` environment
10
- # in `config/database.yml` or the environment variable `DATABASE_URL`
11
- # when the `Database` option is not set. If the database is MySQL,
12
- # this cop ignores offenses for the `TEXT`.
9
+ # This cop only applies when adding a column to an existing table, since
10
+ # existing records will not have a value for the new column. New tables
11
+ # can freely use NOT NULL columns without defaults, since there are no
12
+ # records that could violate the constraint.
13
+ #
14
+ # If you need to add a NOT NULL column to an existing table, you must add
15
+ # it as nullable first, back-fill the data, and then use
16
+ # `change_column_null`. Alternatively, you could add the column with a
17
+ # default first to have the database automatically backfill existing rows,
18
+ # and then use `change_column_default` to remove the default.
19
+ #
20
+ # `TEXT` cannot have a default value in MySQL.
21
+ # The cop will automatically detect an adapter from `development`
22
+ # environment in `config/database.yml` or the environment variable
23
+ # `DATABASE_URL` when the `Database` option is not set. If the database
24
+ # is MySQL, this cop ignores offenses for `TEXT` columns.
13
25
  #
14
26
  # @example
15
27
  # # bad
16
28
  # add_column :users, :name, :string, null: false
17
29
  # add_reference :products, :category, null: false
30
+ # change_table :users do |t|
31
+ # t.string :name, null: false
32
+ # end
18
33
  #
19
34
  # # good
20
35
  # add_column :users, :name, :string, null: true
21
36
  # add_column :users, :name, :string, null: false, default: ''
37
+ # change_table :users do |t|
38
+ # t.string :name, null: false, default: ''
39
+ # end
22
40
  # add_reference :products, :category
23
- # add_reference :products, :category, null: false, default: 1
41
+ # change_column_null :products, :category_id, false
24
42
  class NotNullColumn < Base
25
43
  include DatabaseTypeResolvable
26
44
 
@@ -35,6 +53,22 @@ module RuboCop
35
53
  (send nil? :add_reference _ _ (hash $...))
36
54
  PATTERN
37
55
 
56
+ def_node_matcher :change_table?, <<~PATTERN
57
+ (block (send nil? :change_table ...) (args (arg $_)) _)
58
+ PATTERN
59
+
60
+ def_node_matcher :add_not_null_column_in_change_table?, <<~PATTERN
61
+ (send (lvar $_) :column _ $_ (hash $...))
62
+ PATTERN
63
+
64
+ def_node_matcher :add_not_null_column_via_shortcut_in_change_table?, <<~PATTERN
65
+ (send (lvar $_) $_ _ (hash $...))
66
+ PATTERN
67
+
68
+ def_node_matcher :add_not_null_reference_in_change_table?, <<~PATTERN
69
+ (send (lvar $_) :add_reference _ _ (hash $...))
70
+ PATTERN
71
+
38
72
  def_node_matcher :null_false?, <<~PATTERN
39
73
  (pair (sym :null) (false))
40
74
  PATTERN
@@ -48,16 +82,25 @@ module RuboCop
48
82
  check_add_reference(node)
49
83
  end
50
84
 
85
+ def on_block(node)
86
+ check_change_table(node)
87
+ end
88
+ alias on_numblock on_block
89
+
51
90
  private
52
91
 
92
+ def check_column(type, pairs)
93
+ 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
96
+ end
97
+
98
+ check_pairs(pairs)
99
+ end
100
+
53
101
  def check_add_column(node)
54
102
  add_not_null_column?(node) do |type, pairs|
55
- if type.respond_to?(:value)
56
- return if type.value == :virtual || type.value == 'virtual'
57
- return if (type.value == :text || type.value == 'text') && database == MYSQL
58
- end
59
-
60
- check_pairs(pairs)
103
+ check_column(type, pairs)
61
104
  end
62
105
  end
63
106
 
@@ -67,6 +110,43 @@ module RuboCop
67
110
  end
68
111
  end
69
112
 
113
+ def check_add_column_in_change_table(node, table)
114
+ add_not_null_column_in_change_table?(node) do |receiver, type, pairs|
115
+ next unless receiver == table
116
+
117
+ check_column(type, pairs)
118
+ end
119
+ end
120
+
121
+ def check_add_column_via_shortcut_in_change_table(node, table)
122
+ add_not_null_column_via_shortcut_in_change_table?(node) do |receiver, type, pairs|
123
+ next unless receiver == table
124
+
125
+ check_column(type, pairs)
126
+ end
127
+ end
128
+
129
+ def check_add_reference_in_change_table(node, table)
130
+ add_not_null_reference_in_change_table?(node) do |receiver, pairs|
131
+ next unless receiver == table
132
+
133
+ check_pairs(pairs)
134
+ end
135
+ end
136
+
137
+ def check_change_table(node)
138
+ change_table?(node) do |table|
139
+ next unless node.body
140
+
141
+ children = node.body.begin_type? ? node.body.children : [node.body]
142
+ children.each do |child|
143
+ check_add_column_in_change_table(child, table)
144
+ check_add_column_via_shortcut_in_change_table(child, table)
145
+ check_add_reference_in_change_table(child, table)
146
+ end
147
+ end
148
+ end
149
+
70
150
  def check_pairs(pairs)
71
151
  return if pairs.any? { |pair| default_option?(pair) }
72
152
 
@@ -9,6 +9,10 @@ module RuboCop
9
9
  # `pick` avoids. When called on an Active Record relation, `pick` adds a
10
10
  # limit to the query so that only one value is fetched from the database.
11
11
  #
12
+ # Note that when `pick` is added to a relation with an existing limit, it
13
+ # causes a subquery to be added. In most cases this is undesirable, and
14
+ # care should be taken while resolving this violation.
15
+ #
12
16
  # @safety
13
17
  # This cop is unsafe because `pluck` is defined on both `ActiveRecord::Relation` and `Enumerable`,
14
18
  # whereas `pick` is only defined on `ActiveRecord::Relation` in Rails 6.0. This was addressed
@@ -7,17 +7,26 @@ module RuboCop
7
7
  # and can be replaced with `select`.
8
8
  #
9
9
  # Since `pluck` is an eager method and hits the database immediately,
10
- # using `select` helps to avoid additional database queries.
10
+ # using `select` helps to avoid additional database queries by running as
11
+ # a subquery.
11
12
  #
12
- # This cop has two different enforcement modes. When the `EnforcedStyle`
13
- # is `conservative` (the default) then only calls to `pluck` on a constant
14
- # (i.e. a model class) in the `where` is used as offenses.
13
+ # This cop has two modes of enforcement. When the `EnforcedStyle` is set
14
+ # to `conservative` (the default), only calls to `pluck` on a constant
15
+ # (e.g. a model class) within `where` are considered offenses.
15
16
  #
16
17
  # @safety
17
- # When the `EnforcedStyle` is `aggressive` then all calls to `pluck` in the
18
- # `where` is used as offenses. This may lead to false positives
19
- # as the cop cannot replace to `select` between calls to `pluck` on an
20
- # `ActiveRecord::Relation` instance vs a call to `pluck` on an `Array` instance.
18
+ # When `EnforcedStyle` is set to `aggressive`, all calls to `pluck`
19
+ # within `where` are considered offenses. This might lead to false
20
+ # positives because the check cannot distinguish between calls to
21
+ # `pluck` on an `ActiveRecord::Relation` instance and calls to `pluck`
22
+ # on an `Array` instance.
23
+ #
24
+ # Additionally, when using a subquery with the SQL `IN` operator,
25
+ # databases like PostgreSQL and MySQL can't optimize complex queries as
26
+ # well. They need to scan all records of the outer table against the
27
+ # subquery result sequentially, rather than using an index. This can
28
+ # cause significant performance issues compared to writing the query
29
+ # differently or using `pluck`.
21
30
  #
22
31
  # @example
23
32
  # # bad
@@ -10,25 +10,39 @@ module RuboCop
10
10
  # # bad
11
11
  # 3.day.ago
12
12
  # 1.months.ago
13
+ # 5.megabyte
14
+ # 1.gigabytes
13
15
  #
14
16
  # # good
15
17
  # 3.days.ago
16
18
  # 1.month.ago
19
+ # 5.megabytes
20
+ # 1.gigabyte
17
21
  class PluralizationGrammar < Base
18
22
  extend AutoCorrector
19
23
 
20
- SINGULAR_DURATION_METHODS = { second: :seconds,
21
- minute: :minutes,
22
- hour: :hours,
23
- day: :days,
24
- week: :weeks,
25
- fortnight: :fortnights,
26
- month: :months,
27
- year: :years }.freeze
28
-
29
- RESTRICT_ON_SEND = SINGULAR_DURATION_METHODS.keys + SINGULAR_DURATION_METHODS.values
30
-
31
- PLURAL_DURATION_METHODS = SINGULAR_DURATION_METHODS.invert.freeze
24
+ SINGULAR_METHODS = {
25
+ second: :seconds,
26
+ minute: :minutes,
27
+ hour: :hours,
28
+ day: :days,
29
+ week: :weeks,
30
+ fortnight: :fortnights,
31
+ month: :months,
32
+ year: :years,
33
+ byte: :bytes,
34
+ kilobyte: :kilobytes,
35
+ megabyte: :megabytes,
36
+ gigabyte: :gigabytes,
37
+ terabyte: :terabytes,
38
+ petabyte: :petabytes,
39
+ exabyte: :exabytes,
40
+ zettabyte: :zettabytes
41
+ }.freeze
42
+
43
+ RESTRICT_ON_SEND = SINGULAR_METHODS.keys + SINGULAR_METHODS.values
44
+
45
+ PLURAL_METHODS = SINGULAR_METHODS.invert.freeze
32
46
 
33
47
  MSG = 'Prefer `%<number>s.%<correct>s`.'
34
48
 
@@ -86,15 +100,15 @@ module RuboCop
86
100
  end
87
101
 
88
102
  def pluralize(method_name)
89
- SINGULAR_DURATION_METHODS.fetch(method_name.to_sym).to_s
103
+ SINGULAR_METHODS.fetch(method_name.to_sym).to_s
90
104
  end
91
105
 
92
106
  def singularize(method_name)
93
- PLURAL_DURATION_METHODS.fetch(method_name.to_sym).to_s
107
+ PLURAL_METHODS.fetch(method_name.to_sym).to_s
94
108
  end
95
109
 
96
110
  def duration_method?(method_name)
97
- SINGULAR_DURATION_METHODS.key?(method_name) || PLURAL_DURATION_METHODS.key?(method_name)
111
+ SINGULAR_METHODS.key?(method_name) || PLURAL_METHODS.key?(method_name)
98
112
  end
99
113
  end
100
114
  end
@@ -98,8 +98,6 @@ module RuboCop
98
98
  end
99
99
 
100
100
  def on_or(node)
101
- return unless cop_config['NilOrEmpty']
102
-
103
101
  exists_and_not_empty?(node) do |var1, var2|
104
102
  return unless var1 == var2
105
103
 
@@ -3,35 +3,6 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # TODO: In the future, please support only RuboCop 1.52+ and use `RuboCop::Cop::AllowedReceivers`:
7
- # https://github.com/rubocop/rubocop/blob/v1.52.0/lib/rubocop/cop/mixin/allowed_receivers.rb
8
- # At that time, this duplicated module implementation can be removed.
9
- module AllowedReceivers
10
- def allowed_receiver?(receiver)
11
- receiver_name = receiver_name(receiver)
12
-
13
- allowed_receivers.include?(receiver_name)
14
- end
15
-
16
- def receiver_name(receiver)
17
- return receiver_name(receiver.receiver) if receiver.receiver && !receiver.receiver.const_type?
18
-
19
- if receiver.send_type?
20
- if receiver.receiver
21
- "#{receiver_name(receiver.receiver)}.#{receiver.method_name}"
22
- else
23
- receiver.method_name.to_s
24
- end
25
- else
26
- receiver.source
27
- end
28
- end
29
-
30
- def allowed_receivers
31
- cop_config.fetch('AllowedReceivers', [])
32
- end
33
- end
34
-
35
6
  # Detect redundant `all` used as a receiver for Active Record query methods.
36
7
  #
37
8
  # For the methods `delete_all` and `destroy_all`, this cop will only check cases where the receiver is a model.
@@ -40,7 +40,7 @@ module RuboCop
40
40
  def on_send(node)
41
41
  association_with_foreign_key(node) do |type, name, options, foreign_key_pair, foreign_key|
42
42
  if redundant?(node, type, name, options, foreign_key)
43
- add_offense(foreign_key_pair.source_range) do |corrector|
43
+ add_offense(foreign_key_pair) do |corrector|
44
44
  range = range_with_surrounding_space(foreign_key_pair.source_range, side: :left)
45
45
  range = range_with_surrounding_comma(range, :left)
46
46
 
@@ -39,6 +39,9 @@ module RuboCop
39
39
  MSG = 'Remove explicit presence validation for %<association>s.'
40
40
  RESTRICT_ON_SEND = %i[validates].freeze
41
41
 
42
+ # From https://github.com/rails/rails/blob/7a0bf93b9dd291c7f61121a41b3a813ac8857e6a/activemodel/lib/active_model/validations/validates.rb#L157-L159
43
+ NON_VALIDATION_OPTIONS = %i[if unless on allow_blank allow_nil strict].freeze
44
+
42
45
  minimum_target_rails_version 5.0
43
46
 
44
47
  # @!method presence_validation?(node)
@@ -170,6 +173,12 @@ module RuboCop
170
173
 
171
174
  def on_send(node)
172
175
  presence_validation?(node) do |all_keys, options, presence|
176
+ # If presence is the only validation option and other non-validation options
177
+ # are present, removing it will cause rails to error.
178
+ used_option_keys = options.keys.select(&:sym_type?).map(&:value)
179
+ remaining_validations = used_option_keys - NON_VALIDATION_OPTIONS - [:presence]
180
+ return if remaining_validations.none? && options.keys.length > 1
181
+
173
182
  keys = non_optional_belongs_to(node.parent, all_keys)
174
183
  return if keys.none?
175
184
 
@@ -78,7 +78,7 @@ module RuboCop
78
78
 
79
79
  send_nodes.each do |send_node|
80
80
  receiver = send_node.receiver
81
- add_offense(receiver.source_range) do |corrector|
81
+ add_offense(receiver) do |corrector|
82
82
  autocorrect(corrector, send_node, node)
83
83
  end
84
84
  end
@@ -43,7 +43,7 @@ module RuboCop
43
43
  return if reflection_class_name.value.send_type? && reflection_class_name.value.receiver.nil?
44
44
  return if reflection_class_name.value.lvar_type? && str_assigned?(reflection_class_name)
45
45
 
46
- add_offense(reflection_class_name.source_range) do |corrector|
46
+ add_offense(reflection_class_name) do |corrector|
47
47
  autocorrect(corrector, reflection_class_name)
48
48
  end
49
49
  end
@@ -53,9 +53,12 @@ module RuboCop
53
53
  node.pairs.find { |p| p.key.value.to_sym == :content_type }
54
54
  end
55
55
 
56
- def compatible_content_type?(node)
57
- (node && node.value.value == 'text/plain') ||
58
- (!node && !cop_config['ContentTypeCompatibility'])
56
+ def compatible_content_type?(pair_node)
57
+ if pair_node.nil?
58
+ !cop_config['ContentTypeCompatibility']
59
+ elsif pair_node.value.respond_to?(:value)
60
+ pair_node.value.value == 'text/plain'
61
+ end
59
62
  end
60
63
 
61
64
  def replacement(rest_options, option_value)
@@ -34,7 +34,7 @@ module RuboCop
34
34
  referer?(node) do
35
35
  return unless node.method?(wrong_method_name)
36
36
 
37
- add_offense(node.source_range) do |corrector|
37
+ add_offense(node) do |corrector|
38
38
  corrector.replace(node, "request.#{style}")
39
39
  end
40
40
  end
@@ -23,6 +23,8 @@ module RuboCop
23
23
  # File.binread(Rails.root.join('db', 'schema.rb'))
24
24
  # File.write(Rails.root.join('db', 'schema.rb'), content)
25
25
  # File.binwrite(Rails.root.join('db', 'schema.rb'), content)
26
+ # Dir.glob(Rails.root.join('db', 'schema.rb'))
27
+ # Dir[Rails.root.join('db', 'schema.rb')]
26
28
  #
27
29
  # # good
28
30
  # Rails.root.join('db', 'schema.rb').open
@@ -31,14 +33,15 @@ module RuboCop
31
33
  # Rails.root.join('db', 'schema.rb').binread
32
34
  # Rails.root.join('db', 'schema.rb').write(content)
33
35
  # Rails.root.join('db', 'schema.rb').binwrite(content)
36
+ # Rails.root.glob("db/schema.rb")
34
37
  #
35
38
  class RootPathnameMethods < Base # rubocop:disable Metrics/ClassLength
36
39
  extend AutoCorrector
37
40
  include RangeHelp
38
41
 
39
- MSG = '`%<rails_root>s` is a `Pathname` so you can just append `#%<method>s`.'
42
+ MSG = '`%<rails_root>s` is a `Pathname`, so you can use `%<replacement>s`.'
40
43
 
41
- DIR_GLOB_METHODS = %i[glob].to_set.freeze
44
+ DIR_GLOB_METHODS = %i[[] glob].to_set.freeze
42
45
 
43
46
  DIR_NON_GLOB_METHODS = %i[
44
47
  children
@@ -171,7 +174,7 @@ module RuboCop
171
174
 
172
175
  def_node_matcher :dir_glob?, <<~PATTERN
173
176
  (send
174
- (const {cbase nil?} :Dir) :glob ...)
177
+ (const {cbase nil?} :Dir) DIR_GLOB_METHODS ...)
175
178
  PATTERN
176
179
 
177
180
  def_node_matcher :rails_root_pathname?, <<~PATTERN
@@ -188,13 +191,14 @@ module RuboCop
188
191
 
189
192
  def on_send(node)
190
193
  evidence(node) do |method, path, args, rails_root|
191
- add_offense(node, message: format(MSG, method: method, rails_root: rails_root.source)) do |corrector|
192
- replacement = if dir_glob?(node)
193
- build_path_glob_replacement(path, method)
194
- else
195
- build_path_replacement(path, method, args)
196
- end
194
+ replacement = if dir_glob?(node)
195
+ build_path_glob_replacement(path)
196
+ else
197
+ build_path_replacement(path, method, args)
198
+ end
197
199
 
200
+ message = format(MSG, rails_root: rails_root.source, replacement: replacement)
201
+ add_offense(node, message: message) do |corrector|
198
202
  corrector.replace(node, replacement)
199
203
  end
200
204
  end
@@ -217,12 +221,12 @@ module RuboCop
217
221
  end
218
222
  end
219
223
 
220
- def build_path_glob_replacement(path, method)
224
+ def build_path_glob_replacement(path)
221
225
  receiver = range_between(path.source_range.begin_pos, path.children.first.loc.selector.end_pos).source
222
226
 
223
227
  argument = path.arguments.one? ? path.first_argument.source : join_arguments(path.arguments)
224
228
 
225
- "#{receiver}.#{method}(#{argument})"
229
+ "#{receiver}.glob(#{argument})"
226
230
  end
227
231
 
228
232
  def build_path_replacement(path, method, args)
@@ -9,6 +9,9 @@ module RuboCop
9
9
  #
10
10
  # Methods may be ignored from this rule by configuring a `AllowedMethods`.
11
11
  #
12
+ # @safety
13
+ # This cop is unsafe if the receiver object is not an Active Record object.
14
+ #
12
15
  # @example
13
16
  # # bad
14
17
  # Article.first.decrement!(:view_count)
@@ -63,7 +66,7 @@ module RuboCop
63
66
  PATTERN
64
67
 
65
68
  def_node_matcher :good_insert?, <<~PATTERN
66
- (send _ {:insert :insert!} _ {
69
+ (call _ {:insert :insert!} _ {
67
70
  !(hash ...)
68
71
  (hash <(pair (sym !{:returning :unique_by}) _) ...>)
69
72
  } ...)
@@ -97,7 +100,8 @@ module RuboCop
97
100
  end
98
101
 
99
102
  def forbidden_methods
100
- obsolete_result = cop_config['Blacklist']
103
+ # TODO: Remove when RuboCop Rails 3 releases.
104
+ obsolete_result = cop_config['Blacklist'] # rubocop:disable InternalAffairs/UndefinedConfig
101
105
  if obsolete_result
102
106
  warn '`Blacklist` has been renamed to `ForbiddenMethods`.' unless @displayed_forbidden_warning
103
107
  @displayed_forbidden_warning = true
@@ -108,7 +112,8 @@ module RuboCop
108
112
  end
109
113
 
110
114
  def allowed_methods
111
- obsolete_result = cop_config['Whitelist']
115
+ # TODO: Remove when RuboCop Rails 3 releases.
116
+ obsolete_result = cop_config['Whitelist'] # rubocop:disable InternalAffairs/UndefinedConfig
112
117
  if obsolete_result
113
118
  warn '`Whitelist` has been renamed to `AllowedMethods`.' unless @displayed_allowed_warning
114
119
  @displayed_allowed_warning = true
@@ -87,7 +87,7 @@ module RuboCop
87
87
 
88
88
  def environments
89
89
  @environments ||= begin
90
- environments = cop_config['Environments'] || []
90
+ environments = cop_config['Environments'].dup || []
91
91
  environments << 'local' if target_rails_version >= 7.1
92
92
  environments
93
93
  end
@@ -7,6 +7,12 @@ module RuboCop
7
7
  # `ignored_columns` is necessary to drop a column from RDBMS, but you don't need it after the migration
8
8
  # to drop the column. You avoid forgetting to remove `ignored_columns` by this cop.
9
9
  #
10
+ # IMPORTANT: This cop can't be used to effectively check for unused columns because the development
11
+ # and production schema can be out of sync until the migration has been run on production. As such,
12
+ # this cop can cause `ignored_columns` to be removed even though the production schema still contains
13
+ # the column, which can lead to downtime when the migration is actually executed. Only enable this cop
14
+ # if you know your migrations will be run before any of your Rails applications boot with the modified code.
15
+ #
10
16
  # @example
11
17
  # # bad
12
18
  # class User < ApplicationRecord
@@ -8,6 +8,7 @@ module RuboCop
8
8
  # @example
9
9
  # # bad
10
10
  # validates_acceptance_of :foo
11
+ # validates_comparison_of :foo
11
12
  # validates_confirmation_of :foo
12
13
  # validates_exclusion_of :foo
13
14
  # validates_format_of :foo
@@ -22,6 +23,7 @@ module RuboCop
22
23
  # # good
23
24
  # validates :foo, acceptance: true
24
25
  # validates :foo, confirmation: true
26
+ # validates :foo, comparison: true
25
27
  # validates :foo, exclusion: true
26
28
  # validates :foo, format: true
27
29
  # validates :foo, inclusion: true
@@ -29,7 +31,7 @@ module RuboCop
29
31
  # validates :foo, numericality: true
30
32
  # validates :foo, presence: true
31
33
  # validates :foo, absence: true
32
- # validates :foo, size: true
34
+ # validates :foo, length: true
33
35
  # validates :foo, uniqueness: true
34
36
  #
35
37
  class Validation < Base
@@ -39,6 +41,7 @@ module RuboCop
39
41
 
40
42
  TYPES = %w[
41
43
  acceptance
44
+ comparison
42
45
  confirmation
43
46
  exclusion
44
47
  format
@@ -56,11 +59,11 @@ module RuboCop
56
59
 
57
60
  def on_send(node)
58
61
  return if node.receiver
62
+ return unless (last_argument = node.last_argument)
59
63
 
60
64
  range = node.loc.selector
61
65
 
62
66
  add_offense(range, message: message(node)) do |corrector|
63
- last_argument = node.last_argument
64
67
  return if !last_argument.literal? && !last_argument.splat_type? && !frozen_array_argument?(last_argument)
65
68
 
66
69
  corrector.replace(range, 'validates')
@@ -120,7 +123,9 @@ module RuboCop
120
123
  end
121
124
 
122
125
  def validate_type(node)
123
- node.method_name.to_s.split('_')[1]
126
+ type = node.method_name.to_s.split('_')[1]
127
+
128
+ type == 'size' ? 'length' : type
124
129
  end
125
130
 
126
131
  def frozen_array_argument?(argument)