rubocop-rails 2.4.1 → 2.6.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +6 -2
  4. data/config/default.yml +71 -6
  5. data/lib/rubocop-rails.rb +3 -0
  6. data/lib/rubocop/cop/mixin/active_record_helper.rb +77 -0
  7. data/lib/rubocop/cop/mixin/index_method.rb +161 -0
  8. data/lib/rubocop/cop/rails/content_tag.rb +82 -0
  9. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +1 -3
  10. data/lib/rubocop/cop/rails/delegate.rb +1 -3
  11. data/lib/rubocop/cop/rails/dynamic_find_by.rb +40 -15
  12. data/lib/rubocop/cop/rails/environment_comparison.rb +60 -14
  13. data/lib/rubocop/cop/rails/exit.rb +2 -2
  14. data/lib/rubocop/cop/rails/file_path.rb +1 -0
  15. data/lib/rubocop/cop/rails/http_positional_arguments.rb +2 -2
  16. data/lib/rubocop/cop/rails/http_status.rb +2 -0
  17. data/lib/rubocop/cop/rails/index_by.rb +56 -0
  18. data/lib/rubocop/cop/rails/index_with.rb +59 -0
  19. data/lib/rubocop/cop/rails/inverse_of.rb +0 -4
  20. data/lib/rubocop/cop/rails/link_to_blank.rb +1 -3
  21. data/lib/rubocop/cop/rails/pick.rb +51 -0
  22. data/lib/rubocop/cop/rails/presence.rb +2 -6
  23. data/lib/rubocop/cop/rails/rake_environment.rb +24 -6
  24. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +80 -0
  25. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +0 -3
  26. data/lib/rubocop/cop/rails/refute_methods.rb +52 -26
  27. data/lib/rubocop/cop/rails/reversible_migration.rb +6 -1
  28. data/lib/rubocop/cop/rails/save_bang.rb +16 -9
  29. data/lib/rubocop/cop/rails/skips_model_validations.rb +4 -1
  30. data/lib/rubocop/cop/rails/time_zone.rb +1 -3
  31. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +16 -16
  32. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +155 -0
  33. data/lib/rubocop/cop/rails/unknown_env.rb +7 -6
  34. data/lib/rubocop/cop/rails_cops.rb +8 -0
  35. data/lib/rubocop/rails/inject.rb +1 -1
  36. data/lib/rubocop/rails/schema_loader.rb +61 -0
  37. data/lib/rubocop/rails/schema_loader/schema.rb +190 -0
  38. data/lib/rubocop/rails/version.rb +1 -1
  39. metadata +32 -8
@@ -129,10 +129,6 @@ module RuboCop
129
129
  # @see https://guides.rubyonrails.org/association_basics.html#bi-directional-associations
130
130
  # @see https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Setting+Inverses
131
131
  class InverseOf < Cop
132
- extend TargetRailsVersion
133
-
134
- minimum_target_rails_version 4.1
135
-
136
132
  SPECIFY_MSG = 'Specify an `:inverse_of` option.'
137
133
  NIL_MSG = 'You specified `inverse_of: nil`, you probably meant to ' \
138
134
  'use `inverse_of: false`.'
@@ -42,9 +42,7 @@ module RuboCop
42
42
 
43
43
  option_nodes.map(&:children).each do |options|
44
44
  blank = options.find { |o| blank_target?(o) }
45
- if blank && options.none? { |o| includes_noopener?(o) }
46
- add_offense(blank)
47
- end
45
+ add_offense(blank) if blank && options.none? { |o| includes_noopener?(o) }
48
46
  end
49
47
  end
50
48
 
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces the use `pick` over `pluck(...).first`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # Model.pluck(:a).first
11
+ # Model.pluck(:a, :b).first
12
+ #
13
+ # # good
14
+ # Model.pick(:a)
15
+ # Model.pick(:a, :b)
16
+ class Pick < Cop
17
+ extend TargetRailsVersion
18
+
19
+ MSG = 'Prefer `pick(%<args>s)` over `pluck(%<args>s).first`.'
20
+
21
+ minimum_target_rails_version 6.0
22
+
23
+ def_node_matcher :pick_candidate?, <<~PATTERN
24
+ (send (send _ :pluck ...) :first)
25
+ PATTERN
26
+
27
+ def on_send(node)
28
+ pick_candidate?(node) do
29
+ range = node.receiver.loc.selector.join(node.loc.selector)
30
+ add_offense(node, location: range)
31
+ end
32
+ end
33
+
34
+ def autocorrect(node)
35
+ first_range = node.receiver.source_range.end.join(node.loc.selector)
36
+
37
+ lambda do |corrector|
38
+ corrector.remove(first_range)
39
+ corrector.replace(node.receiver.loc.selector, 'pick')
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def message(node)
46
+ format(MSG, args: node.receiver.arguments.map(&:source).join(', '))
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -76,15 +76,11 @@ module RuboCop
76
76
  return if ignore_if_node?(node)
77
77
 
78
78
  redundant_receiver_and_other(node) do |receiver, other|
79
- unless ignore_other_node?(other) || receiver.nil?
80
- add_offense(node, message: message(node, receiver, other))
81
- end
79
+ add_offense(node, message: message(node, receiver, other)) unless ignore_other_node?(other) || receiver.nil?
82
80
  end
83
81
 
84
82
  redundant_negative_receiver_and_other(node) do |receiver, other|
85
- unless ignore_other_node?(other) || receiver.nil?
86
- add_offense(node, message: message(node, receiver, other))
87
- end
83
+ add_offense(node, message: message(node, receiver, other)) unless ignore_other_node?(other) || receiver.nil?
88
84
  end
89
85
  end
90
86
 
@@ -29,19 +29,37 @@ module RuboCop
29
29
  MSG = 'Include `:environment` task as a dependency for all Rake tasks.'
30
30
 
31
31
  def_node_matcher :task_definition?, <<~PATTERN
32
- (send nil? :task ...)
32
+ (block $(send nil? :task ...) ...)
33
33
  PATTERN
34
34
 
35
- def on_send(node)
36
- return unless task_definition?(node)
37
- return if task_name(node) == :default
38
- return if with_dependencies?(node)
35
+ def on_block(node)
36
+ task_definition?(node) do |task_method|
37
+ return if task_name(task_method) == :default
38
+ return if with_dependencies?(task_method)
39
39
 
40
- add_offense(node)
40
+ add_offense(task_method)
41
+ end
42
+ end
43
+
44
+ def autocorrect(node)
45
+ lambda do |corrector|
46
+ task_name = node.arguments[0]
47
+ task_dependency = correct_task_dependency(task_name)
48
+
49
+ corrector.replace(task_name.loc.expression, task_dependency)
50
+ end
41
51
  end
42
52
 
43
53
  private
44
54
 
55
+ def correct_task_dependency(task_name)
56
+ if task_name.sym_type?
57
+ task_name.source.delete(':|\'|"') + ': :environment'
58
+ else
59
+ "#{task_name.source} => :environment"
60
+ end
61
+ end
62
+
45
63
  def task_name(node)
46
64
  first_arg = node.arguments[0]
47
65
  case first_arg&.type
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop detects cases where the `:foreign_key` option on associations
7
+ # is redundant.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # class Post
12
+ # has_many :comments, foreign_key: 'post_id'
13
+ # end
14
+ #
15
+ # class Comment
16
+ # belongs_to :post, foreign_key: 'post_id'
17
+ # end
18
+ #
19
+ # # good
20
+ # class Post
21
+ # has_many :comments
22
+ # end
23
+ #
24
+ # class Comment
25
+ # belongs_to :author, foreign_key: 'user_id'
26
+ # end
27
+ class RedundantForeignKey < Cop
28
+ include RangeHelp
29
+
30
+ MSG = 'Specifying the default value for `foreign_key` is redundant.'
31
+
32
+ def_node_matcher :association_with_foreign_key, <<~PATTERN
33
+ (send nil? ${:belongs_to :has_one :has_many :has_and_belongs_to_many} ({sym str} $_)
34
+ $(hash <$(pair (sym :foreign_key) ({sym str} $_)) ...>)
35
+ )
36
+ PATTERN
37
+
38
+ def on_send(node)
39
+ association_with_foreign_key(node) do |type, name, options, foreign_key_pair, foreign_key|
40
+ if redundant?(node, type, name, options, foreign_key)
41
+ add_offense(node, location: foreign_key_pair.loc.expression)
42
+ end
43
+ end
44
+ end
45
+
46
+ def autocorrect(node)
47
+ _type, _name, _options, foreign_key_pair, _foreign_key = association_with_foreign_key(node)
48
+ range = range_with_surrounding_space(range: foreign_key_pair.source_range, side: :left)
49
+ range = range_with_surrounding_comma(range, :left)
50
+
51
+ lambda do |corrector|
52
+ corrector.remove(range)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def redundant?(node, association_type, association_name, options, foreign_key)
59
+ foreign_key.to_s == default_foreign_key(node, association_type, association_name, options)
60
+ end
61
+
62
+ def default_foreign_key(node, association_type, association_name, options)
63
+ if association_type == :belongs_to
64
+ "#{association_name}_id"
65
+ elsif (as = find_as_option(options))
66
+ "#{as}_id"
67
+ else
68
+ node.parent_module_name&.foreign_key
69
+ end
70
+ end
71
+
72
+ def find_as_option(options)
73
+ options.pairs.find do |pair|
74
+ pair.key.sym_type? && pair.key.value == :as
75
+ end&.value&.value
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -55,11 +55,8 @@ module RuboCop
55
55
  # end
56
56
  # end
57
57
  class RedundantReceiverInWithOptions < Cop
58
- extend TargetRailsVersion
59
58
  include RangeHelp
60
59
 
61
- minimum_target_rails_version 4.2
62
-
63
60
  MSG = 'Redundant receiver in `with_options`.'
64
61
 
65
62
  def_node_matcher :with_options?, <<~PATTERN
@@ -6,7 +6,7 @@ module RuboCop
6
6
  #
7
7
  # Use `assert_not` methods instead of `refute` methods.
8
8
  #
9
- # @example
9
+ # @example EnforcedStyle: assert_not (default)
10
10
  # # bad
11
11
  # refute false
12
12
  # refute_empty [1, 2, 3]
@@ -17,29 +17,43 @@ module RuboCop
17
17
  # assert_not_empty [1, 2, 3]
18
18
  # assert_not_equal true, false
19
19
  #
20
+ # @example EnforcedStyle: refute
21
+ # # bad
22
+ # assert_not false
23
+ # assert_not_empty [1, 2, 3]
24
+ # assert_not_equal true, false
25
+ #
26
+ # # good
27
+ # refute false
28
+ # refute_empty [1, 2, 3]
29
+ # refute_equal true, false
30
+ #
20
31
  class RefuteMethods < Cop
21
- MSG = 'Prefer `%<assert_method>s` over `%<refute_method>s`.'
32
+ include ConfigurableEnforcedStyle
33
+
34
+ MSG = 'Prefer `%<good_method>s` over `%<bad_method>s`.'
22
35
 
23
36
  CORRECTIONS = {
24
- refute: 'assert_not',
25
- refute_empty: 'assert_not_empty',
26
- refute_equal: 'assert_not_equal',
27
- refute_in_delta: 'assert_not_in_delta',
28
- refute_in_epsilon: 'assert_not_in_epsilon',
29
- refute_includes: 'assert_not_includes',
30
- refute_instance_of: 'assert_not_instance_of',
31
- refute_kind_of: 'assert_not_kind_of',
32
- refute_nil: 'assert_not_nil',
33
- refute_operator: 'assert_not_operator',
34
- refute_predicate: 'assert_not_predicate',
35
- refute_respond_to: 'assert_not_respond_to',
36
- refute_same: 'assert_not_same',
37
- refute_match: 'assert_no_match'
37
+ refute: :assert_not,
38
+ refute_empty: :assert_not_empty,
39
+ refute_equal: :assert_not_equal,
40
+ refute_in_delta: :assert_not_in_delta,
41
+ refute_in_epsilon: :assert_not_in_epsilon,
42
+ refute_includes: :assert_not_includes,
43
+ refute_instance_of: :assert_not_instance_of,
44
+ refute_kind_of: :assert_not_kind_of,
45
+ refute_nil: :assert_not_nil,
46
+ refute_operator: :assert_not_operator,
47
+ refute_predicate: :assert_not_predicate,
48
+ refute_respond_to: :assert_not_respond_to,
49
+ refute_same: :assert_not_same,
50
+ refute_match: :assert_no_match
38
51
  }.freeze
39
52
 
40
- OFFENSIVE_METHODS = CORRECTIONS.keys.freeze
53
+ REFUTE_METHODS = CORRECTIONS.keys.freeze
54
+ ASSERT_NOT_METHODS = CORRECTIONS.values.freeze
41
55
 
42
- def_node_matcher :offensive?, '(send nil? #refute_method? ...)'
56
+ def_node_matcher :offensive?, '(send nil? #bad_method? ...)'
43
57
 
44
58
  def on_send(node)
45
59
  return unless offensive?(node)
@@ -49,27 +63,39 @@ module RuboCop
49
63
  end
50
64
 
51
65
  def autocorrect(node)
66
+ bad_method = node.method_name
67
+ good_method = convert_good_method(bad_method)
68
+
52
69
  lambda do |corrector|
53
- corrector.replace(
54
- node.loc.selector,
55
- CORRECTIONS[node.method_name]
56
- )
70
+ corrector.replace(node.loc.selector, good_method.to_s)
57
71
  end
58
72
  end
59
73
 
60
74
  private
61
75
 
62
- def refute_method?(method_name)
63
- OFFENSIVE_METHODS.include?(method_name)
76
+ def bad_method?(method_name)
77
+ if style == :assert_not
78
+ REFUTE_METHODS.include?(method_name)
79
+ else
80
+ ASSERT_NOT_METHODS.include?(method_name)
81
+ end
64
82
  end
65
83
 
66
84
  def offense_message(method_name)
67
85
  format(
68
86
  MSG,
69
- refute_method: method_name,
70
- assert_method: CORRECTIONS[method_name]
87
+ bad_method: method_name,
88
+ good_method: convert_good_method(method_name)
71
89
  )
72
90
  end
91
+
92
+ def convert_good_method(bad_method)
93
+ if style == :assert_not
94
+ CORRECTIONS.fetch(bad_method)
95
+ else
96
+ CORRECTIONS.invert.fetch(bad_method)
97
+ end
98
+ end
73
99
  end
74
100
  end
75
101
  end
@@ -90,6 +90,11 @@ module RuboCop
90
90
  # remove_foreign_key :accounts, :branches
91
91
  # end
92
92
  #
93
+ # # good
94
+ # def change
95
+ # remove_foreign_key :accounts, to_table: :branches
96
+ # end
97
+ #
93
98
  # @example
94
99
  # # change_table
95
100
  #
@@ -210,7 +215,7 @@ module RuboCop
210
215
 
211
216
  def check_remove_foreign_key_node(node)
212
217
  remove_foreign_key_call(node) do |arg|
213
- if arg.hash_type?
218
+ if arg.hash_type? && !all_hash_key?(arg, :to_table)
214
219
  add_offense(
215
220
  node,
216
221
  message: format(MSG,
@@ -9,13 +9,14 @@ module RuboCop
9
9
  #
10
10
  # This will allow:
11
11
  #
12
- # - update or save calls, assigned to a variable,
12
+ # * update or save calls, assigned to a variable,
13
13
  # or used as a condition in an if/unless/case statement.
14
- # - create calls, assigned to a variable that then has a
15
- # call to `persisted?`.
16
- # - calls if the result is explicitly returned from methods and blocks,
14
+ # * create calls, assigned to a variable that then has a
15
+ # call to `persisted?`, or whose return value is checked by
16
+ # `persisted?` immediately
17
+ # * calls if the result is explicitly returned from methods and blocks,
17
18
  # or provided as arguments.
18
- # - calls whose signature doesn't look like an ActiveRecord
19
+ # * calls whose signature doesn't look like an ActiveRecord
19
20
  # persistence method.
20
21
  #
21
22
  # By default it will also allow implicit returns from methods and blocks.
@@ -137,16 +138,19 @@ module RuboCop
137
138
  add_offense_for_node(node, CREATE_MSG)
138
139
  end
139
140
 
140
- def on_send(node) # rubocop:disable Metrics/CyclomaticComplexity
141
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
142
+ def on_send(node)
141
143
  return unless persist_method?(node)
142
144
  return if return_value_assigned?(node)
143
145
  return if implicit_return?(node)
144
146
  return if check_used_in_condition_or_compound_boolean(node)
145
147
  return if argument?(node)
146
148
  return if explicit_return?(node)
149
+ return if checked_immediately?(node)
147
150
 
148
151
  add_offense_for_node(node)
149
152
  end
153
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
150
154
  alias on_csend on_send
151
155
 
152
156
  def autocorrect(node)
@@ -214,9 +218,7 @@ module RuboCop
214
218
  def check_used_in_condition_or_compound_boolean(node)
215
219
  return false unless in_condition_or_compound_boolean?(node)
216
220
 
217
- unless MODIFY_PERSIST_METHODS.include?(node.method_name)
218
- add_offense_for_node(node, CREATE_CONDITIONAL_MSG)
219
- end
221
+ add_offense_for_node(node, CREATE_CONDITIONAL_MSG) unless MODIFY_PERSIST_METHODS.include?(node.method_name)
220
222
 
221
223
  true
222
224
  end
@@ -238,8 +240,13 @@ module RuboCop
238
240
  parent.if_type? || parent.case_type?
239
241
  end
240
242
 
243
+ def checked_immediately?(node)
244
+ node.parent && call_to_persisted?(node.parent)
245
+ end
246
+
241
247
  def allowed_receiver?(node)
242
248
  return false unless node.receiver
249
+ return true if node.receiver.const_name == 'ENV'
243
250
  return false unless cop_config['AllowedReceivers']
244
251
 
245
252
  cop_config['AllowedReceivers'].any? do |allowed_receiver|
@@ -50,7 +50,10 @@ module RuboCop
50
50
  update_counters].freeze
51
51
 
52
52
  def_node_matcher :good_touch?, <<~PATTERN
53
- (send (const nil? :FileUtils) :touch ...)
53
+ {
54
+ (send (const nil? :FileUtils) :touch ...)
55
+ (send _ :touch {true false})
56
+ }
54
57
  PATTERN
55
58
 
56
59
  def on_send(node)