rubocop-rails 2.4.2 → 2.7.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +5 -1
  4. data/config/default.yml +179 -9
  5. data/lib/rubocop-rails.rb +3 -0
  6. data/lib/rubocop/cop/mixin/active_record_helper.rb +84 -0
  7. data/lib/rubocop/cop/mixin/index_method.rb +161 -0
  8. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +145 -0
  9. data/lib/rubocop/cop/rails/content_tag.rb +69 -0
  10. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +1 -3
  11. data/lib/rubocop/cop/rails/default_scope.rb +54 -0
  12. data/lib/rubocop/cop/rails/delegate.rb +2 -4
  13. data/lib/rubocop/cop/rails/dynamic_find_by.rb +40 -15
  14. data/lib/rubocop/cop/rails/environment_comparison.rb +60 -14
  15. data/lib/rubocop/cop/rails/exit.rb +2 -2
  16. data/lib/rubocop/cop/rails/file_path.rb +2 -1
  17. data/lib/rubocop/cop/rails/find_by_id.rb +103 -0
  18. data/lib/rubocop/cop/rails/http_positional_arguments.rb +2 -2
  19. data/lib/rubocop/cop/rails/http_status.rb +2 -0
  20. data/lib/rubocop/cop/rails/index_by.rb +56 -0
  21. data/lib/rubocop/cop/rails/index_with.rb +59 -0
  22. data/lib/rubocop/cop/rails/inquiry.rb +34 -0
  23. data/lib/rubocop/cop/rails/inverse_of.rb +0 -4
  24. data/lib/rubocop/cop/rails/link_to_blank.rb +3 -3
  25. data/lib/rubocop/cop/rails/mailer_name.rb +80 -0
  26. data/lib/rubocop/cop/rails/match_route.rb +117 -0
  27. data/lib/rubocop/cop/rails/negate_include.rb +39 -0
  28. data/lib/rubocop/cop/rails/pick.rb +55 -0
  29. data/lib/rubocop/cop/rails/pluck.rb +59 -0
  30. data/lib/rubocop/cop/rails/pluck_id.rb +58 -0
  31. data/lib/rubocop/cop/rails/pluck_in_where.rb +36 -0
  32. data/lib/rubocop/cop/rails/presence.rb +2 -6
  33. data/lib/rubocop/cop/rails/rake_environment.rb +17 -0
  34. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +80 -0
  35. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +0 -3
  36. data/lib/rubocop/cop/rails/refute_methods.rb +52 -26
  37. data/lib/rubocop/cop/rails/render_inline.rb +48 -0
  38. data/lib/rubocop/cop/rails/render_plain_text.rb +76 -0
  39. data/lib/rubocop/cop/rails/reversible_migration.rb +6 -1
  40. data/lib/rubocop/cop/rails/safe_navigation.rb +1 -1
  41. data/lib/rubocop/cop/rails/save_bang.rb +6 -7
  42. data/lib/rubocop/cop/rails/short_i18n.rb +76 -0
  43. data/lib/rubocop/cop/rails/skips_model_validations.rb +46 -8
  44. data/lib/rubocop/cop/rails/time_zone.rb +1 -3
  45. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +12 -12
  46. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +155 -0
  47. data/lib/rubocop/cop/rails/unknown_env.rb +7 -6
  48. data/lib/rubocop/cop/rails/where_exists.rb +68 -0
  49. data/lib/rubocop/cop/rails_cops.rb +22 -0
  50. data/lib/rubocop/rails/schema_loader.rb +61 -0
  51. data/lib/rubocop/rails/schema_loader/schema.rb +190 -0
  52. data/lib/rubocop/rails/version.rb +1 -1
  53. metadata +46 -8
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop identifies places where `pluck` is used in `where` query methods
7
+ # and can be replaced with `select`.
8
+ #
9
+ # Since `pluck` is an eager method and hits the database immediately,
10
+ # using `select` helps to avoid additional database queries.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # Post.where(user_id: User.active.pluck(:id))
15
+ #
16
+ # # good
17
+ # Post.where(user_id: User.active.select(:id))
18
+ #
19
+ class PluckInWhere < Cop
20
+ include ActiveRecordHelper
21
+
22
+ MSG = 'Use `select` instead of `pluck` within `where` query method.'
23
+
24
+ def on_send(node)
25
+ add_offense(node, location: :selector) if node.method?(:pluck) && in_where?(node)
26
+ end
27
+
28
+ def autocorrect(node)
29
+ lambda do |corrector|
30
+ corrector.replace(node.loc.selector, 'select')
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ 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
 
@@ -41,8 +41,25 @@ module RuboCop
41
41
  end
42
42
  end
43
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
51
+ end
52
+
44
53
  private
45
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
+
46
63
  def task_name(node)
47
64
  first_arg = node.arguments[0]
48
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
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop looks for inline rendering within controller actions.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # class ProductsController < ApplicationController
11
+ # def index
12
+ # render inline: "<% products.each do |p| %><p><%= p.name %></p><% end %>", type: :erb
13
+ # end
14
+ # end
15
+ #
16
+ # # good
17
+ # # app/views/products/index.html.erb
18
+ # # <% products.each do |p| %>
19
+ # # <p><%= p.name %></p>
20
+ # # <% end %>
21
+ #
22
+ # class ProductsController < ApplicationController
23
+ # def index
24
+ # end
25
+ # end
26
+ #
27
+ class RenderInline < Cop
28
+ MSG = 'Prefer using a template over inline rendering.'
29
+
30
+ def_node_matcher :render_with_options?, <<~PATTERN
31
+ (send nil? :render $(hash ...))
32
+ PATTERN
33
+
34
+ def on_send(node)
35
+ render_with_options?(node) do |options|
36
+ add_offense(node) if includes_inline_key?(options)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def includes_inline_key?(node)
43
+ node.keys.find { |key| key.value.to_sym == :inline }
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop identifies places where `render text:` can be
7
+ # replaced with `render plain:`.
8
+ #
9
+ # @example
10
+ # # bad - explicit MIME type to `text/plain`
11
+ # render text: 'Ruby!', content_type: 'text/plain'
12
+ #
13
+ # # good - short and precise
14
+ # render plain: 'Ruby!'
15
+ #
16
+ # # good - explicit MIME type not to `text/plain`
17
+ # render text: 'Ruby!', content_type: 'text/html'
18
+ #
19
+ # @example ContentTypeCompatibility: true (default)
20
+ # # good - sets MIME type to `text/html`
21
+ # render text: 'Ruby!'
22
+ #
23
+ # @example ContentTypeCompatibility: false
24
+ # # bad - sets MIME type to `text/html`
25
+ # render text: 'Ruby!'
26
+ #
27
+ class RenderPlainText < Cop
28
+ MSG = 'Prefer `render plain:` over `render text:`.'
29
+
30
+ def_node_matcher :render_plain_text?, <<~PATTERN
31
+ (send nil? :render $(hash <$(pair (sym :text) $_) ...>))
32
+ PATTERN
33
+
34
+ def on_send(node)
35
+ render_plain_text?(node) do |options_node, _option_node, _option_value|
36
+ content_type_node = find_content_type(options_node)
37
+ add_offense(node) if compatible_content_type?(content_type_node)
38
+ end
39
+ end
40
+
41
+ def autocorrect(node)
42
+ render_plain_text?(node) do |options_node, option_node, option_value|
43
+ content_type_node = find_content_type(options_node)
44
+ rest_options = options_node.pairs - [option_node, content_type_node].compact
45
+
46
+ lambda do |corrector|
47
+ corrector.replace(
48
+ node,
49
+ replacement(rest_options, option_value)
50
+ )
51
+ end
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def find_content_type(node)
58
+ node.pairs.find { |p| p.key.value.to_sym == :content_type }
59
+ end
60
+
61
+ def compatible_content_type?(node)
62
+ (node && node.value.value == 'text/plain') ||
63
+ (!node && !cop_config['ContentTypeCompatibility'])
64
+ end
65
+
66
+ def replacement(rest_options, option_value)
67
+ if rest_options.any?
68
+ "render plain: #{option_value.source}, #{rest_options.map(&:source).join(', ')}"
69
+ else
70
+ "render plain: #{option_value.source}"
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ 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,
@@ -48,7 +48,7 @@ module RuboCop
48
48
  def on_send(node)
49
49
  try_call(node) do |try_method, dispatch|
50
50
  return if try_method == :try && !cop_config['ConvertTry']
51
- return unless dispatch.sym_type? && dispatch.value =~ /\w+[=!?]?/
51
+ return unless dispatch.sym_type? && dispatch.value.match?(/\w+[=!?]?/)
52
52
 
53
53
  add_offense(node, message: format(MSG, try: try_method))
54
54
  end