rubocop-rails 2.6.0 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -0
  3. data/config/default.yml +189 -6
  4. data/lib/rubocop/cop/mixin/active_record_helper.rb +12 -3
  5. data/lib/rubocop/cop/mixin/enforce_superclass.rb +40 -0
  6. data/lib/rubocop/cop/mixin/index_method.rb +25 -11
  7. data/lib/rubocop/cop/rails/action_filter.rb +10 -14
  8. data/lib/rubocop/cop/rails/active_record_aliases.rb +13 -17
  9. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +148 -0
  10. data/lib/rubocop/cop/rails/active_record_override.rb +1 -1
  11. data/lib/rubocop/cop/rails/active_support_aliases.rb +12 -21
  12. data/lib/rubocop/cop/rails/after_commit_override.rb +91 -0
  13. data/lib/rubocop/cop/rails/application_controller.rb +3 -7
  14. data/lib/rubocop/cop/rails/application_job.rb +2 -1
  15. data/lib/rubocop/cop/rails/application_mailer.rb +2 -7
  16. data/lib/rubocop/cop/rails/application_record.rb +2 -7
  17. data/lib/rubocop/cop/rails/arel_star.rb +41 -0
  18. data/lib/rubocop/cop/rails/assert_not.rb +8 -10
  19. data/lib/rubocop/cop/rails/attribute_default_block_value.rb +90 -0
  20. data/lib/rubocop/cop/rails/belongs_to.rb +9 -18
  21. data/lib/rubocop/cop/rails/blank.rb +27 -27
  22. data/lib/rubocop/cop/rails/bulk_change_table.rb +1 -1
  23. data/lib/rubocop/cop/rails/content_tag.rb +20 -33
  24. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +2 -1
  25. data/lib/rubocop/cop/rails/date.rb +10 -11
  26. data/lib/rubocop/cop/rails/default_scope.rb +61 -0
  27. data/lib/rubocop/cop/rails/delegate.rb +10 -10
  28. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +7 -8
  29. data/lib/rubocop/cop/rails/dynamic_find_by.rb +13 -11
  30. data/lib/rubocop/cop/rails/enum_hash.rb +11 -10
  31. data/lib/rubocop/cop/rails/enum_uniqueness.rb +2 -1
  32. data/lib/rubocop/cop/rails/environment_comparison.rb +18 -14
  33. data/lib/rubocop/cop/rails/exit.rb +4 -10
  34. data/lib/rubocop/cop/rails/file_path.rb +5 -4
  35. data/lib/rubocop/cop/rails/find_by.rb +13 -13
  36. data/lib/rubocop/cop/rails/find_by_id.rb +94 -0
  37. data/lib/rubocop/cop/rails/find_each.rb +16 -14
  38. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +3 -2
  39. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +4 -7
  40. data/lib/rubocop/cop/rails/helper_instance_variable.rb +4 -2
  41. data/lib/rubocop/cop/rails/http_positional_arguments.rb +25 -21
  42. data/lib/rubocop/cop/rails/http_status.rb +7 -9
  43. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +8 -6
  44. data/lib/rubocop/cop/rails/index_by.rb +11 -2
  45. data/lib/rubocop/cop/rails/index_with.rb +11 -2
  46. data/lib/rubocop/cop/rails/inquiry.rb +39 -0
  47. data/lib/rubocop/cop/rails/inverse_of.rb +3 -2
  48. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +17 -15
  49. data/lib/rubocop/cop/rails/link_to_blank.rb +20 -20
  50. data/lib/rubocop/cop/rails/mailer_name.rb +86 -0
  51. data/lib/rubocop/cop/rails/match_route.rb +120 -0
  52. data/lib/rubocop/cop/rails/negate_include.rb +41 -0
  53. data/lib/rubocop/cop/rails/not_null_column.rb +2 -1
  54. data/lib/rubocop/cop/rails/order_by_id.rb +52 -0
  55. data/lib/rubocop/cop/rails/output.rb +5 -2
  56. data/lib/rubocop/cop/rails/output_safety.rb +3 -2
  57. data/lib/rubocop/cop/rails/pick.rb +21 -15
  58. data/lib/rubocop/cop/rails/pluck.rb +56 -0
  59. data/lib/rubocop/cop/rails/pluck_id.rb +56 -0
  60. data/lib/rubocop/cop/rails/pluck_in_where.rb +70 -0
  61. data/lib/rubocop/cop/rails/pluralization_grammar.rb +10 -14
  62. data/lib/rubocop/cop/rails/presence.rb +12 -13
  63. data/lib/rubocop/cop/rails/present.rb +30 -24
  64. data/lib/rubocop/cop/rails/rake_environment.rb +9 -11
  65. data/lib/rubocop/cop/rails/read_write_attribute.rb +12 -11
  66. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +29 -31
  67. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +9 -12
  68. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +11 -10
  69. data/lib/rubocop/cop/rails/reflection_class_name.rb +4 -3
  70. data/lib/rubocop/cop/rails/refute_methods.rb +9 -10
  71. data/lib/rubocop/cop/rails/relative_date_constant.rb +20 -9
  72. data/lib/rubocop/cop/rails/render_inline.rb +41 -0
  73. data/lib/rubocop/cop/rails/render_plain_text.rb +71 -0
  74. data/lib/rubocop/cop/rails/request_referer.rb +7 -7
  75. data/lib/rubocop/cop/rails/reversible_migration.rb +82 -7
  76. data/lib/rubocop/cop/rails/safe_navigation.rb +12 -11
  77. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +5 -10
  78. data/lib/rubocop/cop/rails/save_bang.rb +19 -22
  79. data/lib/rubocop/cop/rails/scope_args.rb +2 -1
  80. data/lib/rubocop/cop/rails/short_i18n.rb +74 -0
  81. data/lib/rubocop/cop/rails/skips_model_validations.rb +46 -11
  82. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +82 -0
  83. data/lib/rubocop/cop/rails/time_zone.rb +22 -20
  84. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +10 -10
  85. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +18 -8
  86. data/lib/rubocop/cop/rails/unknown_env.rb +15 -4
  87. data/lib/rubocop/cop/rails/validation.rb +15 -14
  88. data/lib/rubocop/cop/rails/where_equals.rb +94 -0
  89. data/lib/rubocop/cop/rails/where_exists.rb +126 -0
  90. data/lib/rubocop/cop/rails/where_not.rb +97 -0
  91. data/lib/rubocop/cop/rails_cops.rb +22 -0
  92. data/lib/rubocop/rails/schema_loader.rb +4 -4
  93. data/lib/rubocop/rails/schema_loader/schema.rb +5 -5
  94. data/lib/rubocop/rails/version.rb +5 -1
  95. metadata +37 -9
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces that mailer names end with `Mailer` suffix.
7
+ #
8
+ # Without the `Mailer` suffix it isn't immediately apparent what's a mailer
9
+ # and which views are related to the mailer.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # class User < ActionMailer::Base
14
+ # end
15
+ #
16
+ # class User < ApplicationMailer
17
+ # end
18
+ #
19
+ # # good
20
+ # class UserMailer < ActionMailer::Base
21
+ # end
22
+ #
23
+ # class UserMailer < ApplicationMailer
24
+ # end
25
+ #
26
+ class MailerName < Base
27
+ extend AutoCorrector
28
+
29
+ MSG = 'Mailer should end with `Mailer` suffix.'
30
+
31
+ def_node_matcher :mailer_base_class?, <<~PATTERN
32
+ {
33
+ (const (const nil? :ActionMailer) :Base)
34
+ (const nil? :ApplicationMailer)
35
+ }
36
+ PATTERN
37
+
38
+ def_node_matcher :class_definition?, <<~PATTERN
39
+ (class $(const _ !#mailer_suffix?) #mailer_base_class? ...)
40
+ PATTERN
41
+
42
+ def_node_matcher :class_new_definition?, <<~PATTERN
43
+ (send (const nil? :Class) :new #mailer_base_class?)
44
+ PATTERN
45
+
46
+ def on_class(node)
47
+ class_definition?(node) do |name_node|
48
+ add_offense(name_node) do |corrector|
49
+ autocorrect(corrector, name_node)
50
+ end
51
+ end
52
+ end
53
+
54
+ def on_send(node)
55
+ return unless class_new_definition?(node)
56
+
57
+ casgn_parent = node.each_ancestor(:casgn).first
58
+ return unless casgn_parent
59
+
60
+ name = casgn_parent.children[1]
61
+ return if mailer_suffix?(name)
62
+
63
+ add_offense(casgn_parent.loc.name) do |corrector|
64
+ autocorrect(corrector, casgn_parent)
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def autocorrect(corrector, node)
71
+ if node.casgn_type?
72
+ name = node.children[1]
73
+ corrector.replace(node.loc.name, "#{name}Mailer")
74
+ else
75
+ name = node.children.last
76
+ corrector.replace(node.source_range, "#{name}Mailer")
77
+ end
78
+ end
79
+
80
+ def mailer_suffix?(mailer_name)
81
+ mailer_name.to_s.end_with?('Mailer')
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop identifies places where defining routes with `match`
7
+ # can be replaced with a specific HTTP method.
8
+ #
9
+ # Don't use `match` to define any routes unless there is a need to map multiple request types
10
+ # among [:get, :post, :patch, :put, :delete] to a single action using the `:via` option.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # match ':controller/:action/:id'
15
+ # match 'photos/:id', to: 'photos#show', via: :get
16
+ #
17
+ # # good
18
+ # get ':controller/:action/:id'
19
+ # get 'photos/:id', to: 'photos#show'
20
+ # match 'photos/:id', to: 'photos#show', via: [:get, :post]
21
+ # match 'photos/:id', to: 'photos#show', via: :all
22
+ #
23
+ class MatchRoute < Base
24
+ extend AutoCorrector
25
+
26
+ MSG = 'Use `%<http_method>s` instead of `match` to define a route.'
27
+ RESTRICT_ON_SEND = %i[match].freeze
28
+ HTTP_METHODS = %i[get post put patch delete].freeze
29
+
30
+ def_node_matcher :match_method_call?, <<~PATTERN
31
+ (send nil? :match $_ $(hash ...) ?)
32
+ PATTERN
33
+
34
+ def on_send(node)
35
+ match_method_call?(node) do |path_node, options_node|
36
+ return unless within_routes?(node)
37
+
38
+ options_node = path_node.hash_type? ? path_node : options_node.first
39
+
40
+ if options_node.nil?
41
+ register_offense(node, 'get')
42
+ else
43
+ via = extract_via(options_node)
44
+ return unless via.size == 1 && http_method?(via.first)
45
+
46
+ register_offense(node, via.first)
47
+ end
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def register_offense(node, http_method)
54
+ add_offense(node, message: format(MSG, http_method: http_method)) do |corrector|
55
+ match_method_call?(node) do |path_node, options_node|
56
+ options_node = options_node.first
57
+
58
+ corrector.replace(node, replacement(path_node, options_node))
59
+ end
60
+ end
61
+ end
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
+ def extract_via(node)
72
+ via_pair = via_pair(node)
73
+ return %i[get] unless via_pair
74
+
75
+ _, via = *via_pair
76
+
77
+ if via.basic_literal?
78
+ [via.value]
79
+ elsif via.array_type?
80
+ via.values.map(&:value)
81
+ else
82
+ []
83
+ end
84
+ end
85
+
86
+ def via_pair(node)
87
+ node.pairs.find { |p| p.key.value == :via }
88
+ end
89
+
90
+ def http_method?(method)
91
+ HTTP_METHODS.include?(method.to_sym)
92
+ end
93
+
94
+ def replacement(path_node, options_node)
95
+ if path_node.hash_type?
96
+ http_method, options = *http_method_and_options(path_node)
97
+ "#{http_method} #{options.map(&:source).join(', ')}"
98
+ elsif options_node.nil?
99
+ "get #{path_node.source}"
100
+ else
101
+ http_method, options = *http_method_and_options(options_node)
102
+
103
+ if options.any?
104
+ "#{http_method} #{path_node.source}, #{options.map(&:source).join(', ')}"
105
+ else
106
+ "#{http_method} #{path_node.source}"
107
+ end
108
+ end
109
+ end
110
+
111
+ def http_method_and_options(node)
112
+ via_pair = via_pair(node)
113
+ http_method = extract_via(node).first
114
+ rest_pairs = node.pairs - [via_pair]
115
+ [http_method, rest_pairs]
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces the use of `collection.exclude?(obj)`
7
+ # over `!collection.include?(obj)`.
8
+ #
9
+ # It is marked as unsafe by default because false positive will occur for
10
+ # a receiver object that do not have `exclude?` method. (e.g. `IPAddr`)
11
+ #
12
+ # @example
13
+ # # bad
14
+ # !array.include?(2)
15
+ # !hash.include?(:key)
16
+ #
17
+ # # good
18
+ # array.exclude?(2)
19
+ # hash.exclude?(:key)
20
+ #
21
+ class NegateInclude < Base
22
+ extend AutoCorrector
23
+
24
+ MSG = 'Use `.exclude?` and remove the negation part.'
25
+ RESTRICT_ON_SEND = %i[!].freeze
26
+
27
+ def_node_matcher :negate_include_call?, <<~PATTERN
28
+ (send (send $_ :include? $_) :!)
29
+ PATTERN
30
+
31
+ def on_send(node)
32
+ return unless (receiver, obj = negate_include_call?(node))
33
+
34
+ add_offense(node) do |corrector|
35
+ corrector.replace(node, "#{receiver.source}.exclude?(#{obj.source})")
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -16,8 +16,9 @@ module RuboCop
16
16
  # add_column :users, :name, :string, null: false, default: ''
17
17
  # add_reference :products, :category
18
18
  # add_reference :products, :category, null: false, default: 1
19
- class NotNullColumn < Cop
19
+ class NotNullColumn < Base
20
20
  MSG = 'Do not add a NOT NULL column without a default value.'
21
+ RESTRICT_ON_SEND = %i[add_column add_reference].freeze
21
22
 
22
23
  def_node_matcher :add_not_null_column?, <<~PATTERN
23
24
  (send nil? :add_column _ _ _ (hash $...))
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks for places where ordering by `id` column is used.
7
+ #
8
+ # Don't use the `id` column for ordering. The sequence of ids is not guaranteed
9
+ # to be in any particular order, despite often (incidentally) being chronological.
10
+ # Use a timestamp column to order chronologically. As a bonus the intent is clearer.
11
+ #
12
+ # NOTE: Make sure the changed order column does not introduce performance
13
+ # bottlenecks and appropriate database indexes are added.
14
+ #
15
+ # @example
16
+ # # bad
17
+ # scope :chronological, -> { order(id: :asc) }
18
+ # scope :chronological, -> { order(primary_key => :asc) }
19
+ #
20
+ # # good
21
+ # scope :chronological, -> { order(created_at: :asc) }
22
+ #
23
+ class OrderById < Base
24
+ include RangeHelp
25
+
26
+ MSG = 'Do not use the `id` column for ordering. '\
27
+ 'Use a timestamp column to order chronologically.'
28
+ RESTRICT_ON_SEND = %i[order].freeze
29
+
30
+ def_node_matcher :order_by_id?, <<~PATTERN
31
+ (send _ :order
32
+ {
33
+ (sym :id)
34
+ (hash (pair (sym :id) _))
35
+ (send _ :primary_key)
36
+ (hash (pair (send _ :primary_key) _))
37
+ })
38
+ PATTERN
39
+
40
+ def on_send(node)
41
+ add_offense(offense_range(node)) if order_by_id?(node)
42
+ end
43
+
44
+ private
45
+
46
+ def offense_range(node)
47
+ range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -13,9 +13,12 @@ module RuboCop
13
13
  #
14
14
  # # good
15
15
  # Rails.logger.debug 'A debug message'
16
- class Output < Cop
16
+ class Output < Base
17
17
  MSG = 'Do not write to stdout. ' \
18
18
  "Use Rails's logger if you want to log."
19
+ RESTRICT_ON_SEND = %i[
20
+ ap p pp pretty_print print puts binwrite syswrite write write_nonblock
21
+ ].freeze
19
22
 
20
23
  def_node_matcher :output?, <<~PATTERN
21
24
  (send nil? {:ap :p :pp :pretty_print :print :puts} ...)
@@ -35,7 +38,7 @@ module RuboCop
35
38
  return unless (output?(node) || io_output?(node)) &&
36
39
  node.arguments?
37
40
 
38
- add_offense(node, location: :selector)
41
+ add_offense(node.loc.selector)
39
42
  end
40
43
 
41
44
  private
@@ -62,8 +62,9 @@ module RuboCop
62
62
  # safe_join([user_content, " ", content_tag(:span, user_content)])
63
63
  # # => ActiveSupport::SafeBuffer
64
64
  # # "&lt;b&gt;hi&lt;/b&gt; <span>&lt;b&gt;hi&lt;/b&gt;</span>"
65
- class OutputSafety < Cop
65
+ class OutputSafety < Base
66
66
  MSG = 'Tagging a string as html safe may be a security risk.'
67
+ RESTRICT_ON_SEND = %i[html_safe raw safe_concat].freeze
67
68
 
68
69
  def on_send(node)
69
70
  return if non_interpolated_string?(node)
@@ -72,7 +73,7 @@ module RuboCop
72
73
  looks_like_rails_raw?(node) ||
73
74
  looks_like_rails_safe_concat?(node)
74
75
 
75
- add_offense(node, location: :selector)
76
+ add_offense(node.loc.selector)
76
77
  end
77
78
  alias on_csend on_send
78
79
 
@@ -3,20 +3,26 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop enforces the use `pick` over `pluck(...).first`.
6
+ # This cop enforces the use of `pick` over `pluck(...).first`.
7
+ #
8
+ # Using `pluck` followed by `first` creates an intermediate array, which
9
+ # `pick` avoids. When called on an Active Record relation, `pick` adds a
10
+ # limit to the query so that only one value is fetched from the database.
7
11
  #
8
12
  # @example
9
13
  # # bad
10
14
  # Model.pluck(:a).first
11
- # Model.pluck(:a, :b).first
15
+ # [{ a: :b, c: :d }].pluck(:a, :b).first
12
16
  #
13
17
  # # good
14
18
  # Model.pick(:a)
15
- # Model.pick(:a, :b)
16
- class Pick < Cop
19
+ # [{ a: :b, c: :d }].pick(:a, :b)
20
+ class Pick < Base
21
+ extend AutoCorrector
17
22
  extend TargetRailsVersion
18
23
 
19
24
  MSG = 'Prefer `pick(%<args>s)` over `pluck(%<args>s).first`.'
25
+ RESTRICT_ON_SEND = %i[first].freeze
20
26
 
21
27
  minimum_target_rails_version 6.0
22
28
 
@@ -26,24 +32,24 @@ module RuboCop
26
32
 
27
33
  def on_send(node)
28
34
  pick_candidate?(node) do
29
- range = node.receiver.loc.selector.join(node.loc.selector)
30
- add_offense(node, location: range)
31
- end
32
- end
35
+ receiver = node.receiver
36
+ receiver_selector = receiver.loc.selector
37
+ node_selector = node.loc.selector
38
+ range = receiver_selector.join(node_selector)
33
39
 
34
- def autocorrect(node)
35
- first_range = node.receiver.source_range.end.join(node.loc.selector)
40
+ add_offense(range, message: message(receiver)) do |corrector|
41
+ first_range = receiver.source_range.end.join(node_selector)
36
42
 
37
- lambda do |corrector|
38
- corrector.remove(first_range)
39
- corrector.replace(node.receiver.loc.selector, 'pick')
43
+ corrector.remove(first_range)
44
+ corrector.replace(receiver_selector, 'pick')
45
+ end
40
46
  end
41
47
  end
42
48
 
43
49
  private
44
50
 
45
- def message(node)
46
- format(MSG, args: node.receiver.arguments.map(&:source).join(', '))
51
+ def message(receiver)
52
+ format(MSG, args: receiver.arguments.map(&:source).join(', '))
47
53
  end
48
54
  end
49
55
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces the use of `pluck` over `map`.
7
+ #
8
+ # `pluck` can be used instead of `map` to extract a single key from each
9
+ # element in an enumerable. When called on an Active Record relation, it
10
+ # results in a more efficient query that only selects the necessary key.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # Post.published.map { |post| post[:title] }
15
+ # [{ a: :b, c: :d }].collect { |el| el[:a] }
16
+ #
17
+ # # good
18
+ # Post.published.pluck(:title)
19
+ # [{ a: :b, c: :d }].pluck(:a)
20
+ class Pluck < Base
21
+ extend AutoCorrector
22
+ extend TargetRailsVersion
23
+
24
+ MSG = 'Prefer `pluck(:%<value>s)` over `%<method>s { |%<argument>s| %<element>s[:%<value>s] }`.'
25
+
26
+ minimum_target_rails_version 5.0
27
+
28
+ def_node_matcher :pluck_candidate?, <<~PATTERN
29
+ (block (send _ ${:map :collect}) (args (arg $_argument)) (send (lvar $_element) :[] (sym $_value)))
30
+ PATTERN
31
+
32
+ def on_block(node)
33
+ pluck_candidate?(node) do |method, argument, element, value|
34
+ next unless argument == element
35
+
36
+ message = message(method, argument, element, value)
37
+
38
+ add_offense(offense_range(node), message: message) do |corrector|
39
+ corrector.replace(offense_range(node), "pluck(:#{value})")
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def offense_range(node)
47
+ node.send_node.loc.selector.join(node.loc.end)
48
+ end
49
+
50
+ def message(method, argument, element, value)
51
+ format(MSG, method: method, argument: argument, element: element, value: value)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end