rubocop-rails 2.5.2 → 2.8.1

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +192 -11
  5. data/lib/rubocop/cop/mixin/active_record_helper.rb +9 -3
  6. data/lib/rubocop/cop/mixin/index_method.rb +25 -1
  7. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +143 -0
  8. data/lib/rubocop/cop/rails/after_commit_override.rb +91 -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 +41 -15
  14. data/lib/rubocop/cop/rails/exit.rb +2 -2
  15. data/lib/rubocop/cop/rails/file_path.rb +2 -1
  16. data/lib/rubocop/cop/rails/find_by_id.rb +103 -0
  17. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +1 -5
  18. data/lib/rubocop/cop/rails/helper_instance_variable.rb +2 -0
  19. data/lib/rubocop/cop/rails/http_positional_arguments.rb +7 -1
  20. data/lib/rubocop/cop/rails/http_status.rb +2 -0
  21. data/lib/rubocop/cop/rails/index_by.rb +9 -1
  22. data/lib/rubocop/cop/rails/index_with.rb +9 -1
  23. data/lib/rubocop/cop/rails/inquiry.rb +38 -0
  24. data/lib/rubocop/cop/rails/inverse_of.rb +0 -4
  25. data/lib/rubocop/cop/rails/link_to_blank.rb +3 -3
  26. data/lib/rubocop/cop/rails/mailer_name.rb +80 -0
  27. data/lib/rubocop/cop/rails/match_route.rb +119 -0
  28. data/lib/rubocop/cop/rails/negate_include.rb +39 -0
  29. data/lib/rubocop/cop/rails/order_by_id.rb +53 -0
  30. data/lib/rubocop/cop/rails/pick.rb +55 -0
  31. data/lib/rubocop/cop/rails/pluck.rb +59 -0
  32. data/lib/rubocop/cop/rails/pluck_id.rb +58 -0
  33. data/lib/rubocop/cop/rails/pluck_in_where.rb +70 -0
  34. data/lib/rubocop/cop/rails/presence.rb +2 -6
  35. data/lib/rubocop/cop/rails/rake_environment.rb +17 -0
  36. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +80 -0
  37. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +0 -3
  38. data/lib/rubocop/cop/rails/reflection_class_name.rb +1 -1
  39. data/lib/rubocop/cop/rails/relative_date_constant.rb +5 -2
  40. data/lib/rubocop/cop/rails/render_inline.rb +40 -0
  41. data/lib/rubocop/cop/rails/render_plain_text.rb +76 -0
  42. data/lib/rubocop/cop/rails/reversible_migration.rb +80 -1
  43. data/lib/rubocop/cop/rails/safe_navigation.rb +1 -1
  44. data/lib/rubocop/cop/rails/save_bang.rb +8 -9
  45. data/lib/rubocop/cop/rails/short_i18n.rb +76 -0
  46. data/lib/rubocop/cop/rails/skips_model_validations.rb +46 -8
  47. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +83 -0
  48. data/lib/rubocop/cop/rails/time_zone.rb +1 -3
  49. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +14 -12
  50. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +16 -6
  51. data/lib/rubocop/cop/rails/unknown_env.rb +18 -6
  52. data/lib/rubocop/cop/rails/where_exists.rb +131 -0
  53. data/lib/rubocop/cop/rails/where_not.rb +108 -0
  54. data/lib/rubocop/cop/rails_cops.rb +21 -0
  55. data/lib/rubocop/rails/schema_loader/schema.rb +4 -4
  56. data/lib/rubocop/rails/version.rb +1 -1
  57. metadata +31 -10
@@ -9,11 +9,11 @@ module RuboCop
9
9
  #
10
10
  # There are two obvious cases where `exit` is particularly harmful:
11
11
  #
12
- # - Usage in library code for your application. Even though Rails will
12
+ # * Usage in library code for your application. Even though Rails will
13
13
  # rescue from a `SystemExit` and continue on, unit testing that library
14
14
  # code will result in specs exiting (potentially silently if `exit(0)`
15
15
  # is used.)
16
- # - Usage in application code outside of the web process could result in
16
+ # * Usage in application code outside of the web process could result in
17
17
  # the program exiting, which could result in the code failing to run and
18
18
  # do its job.
19
19
  #
@@ -48,6 +48,7 @@ module RuboCop
48
48
 
49
49
  def on_dstr(node)
50
50
  return unless rails_root_nodes?(node)
51
+ return unless node.children.last.str_type?
51
52
  return unless node.children.last.source.start_with?('.') ||
52
53
  node.children.last.source.include?(File::SEPARATOR)
53
54
 
@@ -89,7 +90,7 @@ module RuboCop
89
90
  end
90
91
 
91
92
  def string_with_slash?(node)
92
- node.str_type? && node.source =~ %r{/}
93
+ node.str_type? && node.source.include?('/')
93
94
  end
94
95
 
95
96
  def register_offense(node)
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces that `ActiveRecord#find` is used instead of
7
+ # `where.take!`, `find_by!`, and `find_by_id!` to retrieve a single record
8
+ # by primary key when you expect it to be found.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # User.where(id: id).take!
13
+ # User.find_by_id!(id)
14
+ # User.find_by!(id: id)
15
+ #
16
+ # # good
17
+ # User.find(id)
18
+ #
19
+ class FindById < Cop
20
+ include RangeHelp
21
+
22
+ MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
23
+
24
+ def_node_matcher :where_take?, <<~PATTERN
25
+ (send
26
+ $(send _ :where
27
+ (hash
28
+ (pair (sym :id) $_))) :take!)
29
+ PATTERN
30
+
31
+ def_node_matcher :find_by?, <<~PATTERN
32
+ {
33
+ (send _ :find_by_id! $_)
34
+ (send _ :find_by! (hash (pair (sym :id) $_)))
35
+ }
36
+ PATTERN
37
+
38
+ def on_send(node)
39
+ where_take?(node) do |where, id_value|
40
+ range = where_take_offense_range(node, where)
41
+
42
+ good_method = build_good_method(id_value)
43
+ bad_method = build_where_take_bad_method(id_value)
44
+ message = format(MSG, good_method: good_method, bad_method: bad_method)
45
+
46
+ add_offense(node, location: range, message: message)
47
+ end
48
+
49
+ find_by?(node) do |id_value|
50
+ range = find_by_offense_range(node)
51
+
52
+ good_method = build_good_method(id_value)
53
+ bad_method = build_find_by_bad_method(node, id_value)
54
+ message = format(MSG, good_method: good_method, bad_method: bad_method)
55
+
56
+ add_offense(node, location: range, message: message)
57
+ end
58
+ end
59
+
60
+ def autocorrect(node)
61
+ if (matches = where_take?(node))
62
+ where, id_value = *matches
63
+ range = where_take_offense_range(node, where)
64
+ elsif (id_value = find_by?(node))
65
+ range = find_by_offense_range(node)
66
+ end
67
+
68
+ lambda do |corrector|
69
+ replacement = build_good_method(id_value)
70
+ corrector.replace(range, replacement)
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def where_take_offense_range(node, where)
77
+ range_between(where.loc.selector.begin_pos, node.loc.expression.end_pos)
78
+ end
79
+
80
+ def find_by_offense_range(node)
81
+ range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
82
+ end
83
+
84
+ def build_good_method(id_value)
85
+ "find(#{id_value.source})"
86
+ end
87
+
88
+ def build_where_take_bad_method(id_value)
89
+ "where(id: #{id_value.source}).take!"
90
+ end
91
+
92
+ def build_find_by_bad_method(node, id_value)
93
+ case node.method_name
94
+ when :find_by_id!
95
+ "find_by_id!(#{id_value.source})"
96
+ when :find_by!
97
+ "find_by!(id: #{id_value.source})"
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -52,11 +52,7 @@ module RuboCop
52
52
 
53
53
  def on_send(node)
54
54
  return if active_resource?(node.parent)
55
-
56
- unless association_without_options?(node)
57
- return if valid_options?(association_with_options?(node))
58
- end
59
-
55
+ return if !association_without_options?(node) && valid_options?(association_with_options?(node))
60
56
  return if valid_options_in_with_options_block?(node)
61
57
 
62
58
  add_offense(node, location: :selector)
@@ -31,6 +31,8 @@ module RuboCop
31
31
  end
32
32
 
33
33
  def on_ivasgn(node)
34
+ return if node.parent.or_asgn_type?
35
+
34
36
  add_offense(node, location: :name)
35
37
  end
36
38
  end
@@ -8,7 +8,7 @@ module RuboCop
8
8
  # change them to use keyword args. This cop only applies to Rails >= 5.
9
9
  # If you are running Rails < 5 you should disable the
10
10
  # Rails/HttpPositionalArguments cop or set your TargetRailsVersion in your
11
- # .rubocop.yml file to 4.0, etc.
11
+ # .rubocop.yml file to 4.2.
12
12
  #
13
13
  # @example
14
14
  # # bad
@@ -16,6 +16,7 @@ module RuboCop
16
16
  #
17
17
  # # good
18
18
  # get :new, params: { user_id: 1 }
19
+ # get :new, **options
19
20
  class HttpPositionalArguments < Cop
20
21
  extend TargetRailsVersion
21
22
 
@@ -32,6 +33,10 @@ module RuboCop
32
33
  (send nil? {#{HTTP_METHODS.map(&:inspect).join(' ')}} !nil? $_ ...)
33
34
  PATTERN
34
35
 
36
+ def_node_matcher :kwsplat_hash?, <<~PATTERN
37
+ (hash (kwsplat _))
38
+ PATTERN
39
+
35
40
  def on_send(node)
36
41
  http_request?(node) do |data|
37
42
  return unless needs_conversion?(data)
@@ -61,6 +66,7 @@ module RuboCop
61
66
 
62
67
  def needs_conversion?(data)
63
68
  return true unless data.hash_type?
69
+ return false if kwsplat_hash?(data)
64
70
 
65
71
  data.each_pair.none? do |pair|
66
72
  special_keyword_arg?(pair.key) ||
@@ -83,6 +83,7 @@ module RuboCop
83
83
  'to define HTTP status code.'
84
84
 
85
85
  attr_reader :node
86
+
86
87
  def initialize(node)
87
88
  @node = node
88
89
  end
@@ -124,6 +125,7 @@ module RuboCop
124
125
  PERMITTED_STATUS = %i[error success missing redirect].freeze
125
126
 
126
127
  attr_reader :node
128
+
127
129
  def initialize(node)
128
130
  @node = node
129
131
  end
@@ -11,6 +11,7 @@ module RuboCop
11
11
  # @example
12
12
  # # bad
13
13
  # [1, 2, 3].each_with_object({}) { |el, h| h[foo(el)] = el }
14
+ # [1, 2, 3].to_h { |el| [foo(el), el] }
14
15
  # [1, 2, 3].map { |el| [foo(el), el] }.to_h
15
16
  # Hash[[1, 2, 3].collect { |el| [foo(el), el] }]
16
17
  #
@@ -23,7 +24,14 @@ module RuboCop
23
24
  (block
24
25
  ({send csend} _ :each_with_object (hash))
25
26
  (args (arg $_el) (arg _memo))
26
- ({send csend} (lvar _memo) :[]= $_ (lvar _el)))
27
+ ({send csend} (lvar _memo) :[]= $!`_memo (lvar _el)))
28
+ PATTERN
29
+
30
+ def_node_matcher :on_bad_to_h, <<~PATTERN
31
+ (block
32
+ ({send csend} _ :to_h)
33
+ (args (arg $_el))
34
+ (array $_ (lvar _el)))
27
35
  PATTERN
28
36
 
29
37
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
@@ -11,6 +11,7 @@ module RuboCop
11
11
  # @example
12
12
  # # bad
13
13
  # [1, 2, 3].each_with_object({}) { |el, h| h[el] = foo(el) }
14
+ # [1, 2, 3].to_h { |el| [el, foo(el)] }
14
15
  # [1, 2, 3].map { |el| [el, foo(el)] }.to_h
15
16
  # Hash[[1, 2, 3].collect { |el| [el, foo(el)] }]
16
17
  #
@@ -26,7 +27,14 @@ module RuboCop
26
27
  (block
27
28
  ({send csend} _ :each_with_object (hash))
28
29
  (args (arg $_el) (arg _memo))
29
- ({send csend} (lvar _memo) :[]= (lvar _el) $_))
30
+ ({send csend} (lvar _memo) :[]= (lvar _el) $!`_memo))
31
+ PATTERN
32
+
33
+ def_node_matcher :on_bad_to_h, <<~PATTERN
34
+ (block
35
+ ({send csend} _ :to_h)
36
+ (args (arg $_el))
37
+ (array (lvar _el) $_))
30
38
  PATTERN
31
39
 
32
40
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks that Active Support's `inquiry` method is not used.
7
+ #
8
+ # @example
9
+ # # bad - String#inquiry
10
+ # ruby = 'two'.inquiry
11
+ # ruby.two?
12
+ #
13
+ # # good
14
+ # ruby = 'two'
15
+ # ruby == 'two'
16
+ #
17
+ # # bad - Array#inquiry
18
+ # pets = %w(cat dog).inquiry
19
+ # pets.gopher?
20
+ #
21
+ # # good
22
+ # pets = %w(cat dog)
23
+ # pets.include? 'cat'
24
+ #
25
+ class Inquiry < Cop
26
+ MSG = "Prefer Ruby's comparison operators over Active Support's `inquiry`."
27
+
28
+ def on_send(node)
29
+ return unless node.method?(:inquiry) && node.arguments.empty?
30
+ return unless (receiver = node.receiver)
31
+ return if !receiver.str_type? && !receiver.array_type?
32
+
33
+ add_offense(node, location: :selector)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -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`.'
@@ -35,6 +35,7 @@ module RuboCop
35
35
  (pair {(sym :rel) (str "rel")} (str _))
36
36
  PATTERN
37
37
 
38
+ # rubocop:disable Metrics/CyclomaticComplexity
38
39
  def on_send(node)
39
40
  return unless node.method?(:link_to)
40
41
 
@@ -42,11 +43,10 @@ module RuboCop
42
43
 
43
44
  option_nodes.map(&:children).each do |options|
44
45
  blank = options.find { |o| blank_target?(o) }
45
- if blank && options.none? { |o| includes_noopener?(o) }
46
- add_offense(blank)
47
- end
46
+ add_offense(blank) if blank && options.none? { |o| includes_noopener?(o) }
48
47
  end
49
48
  end
49
+ # rubocop:enable Metrics/CyclomaticComplexity
50
50
 
51
51
  def autocorrect(node)
52
52
  lambda do |corrector|
@@ -0,0 +1,80 @@
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 < Cop
27
+ MSG = 'Mailer should end with `Mailer` suffix.'
28
+
29
+ def_node_matcher :mailer_base_class?, <<~PATTERN
30
+ {
31
+ (const (const nil? :ActionMailer) :Base)
32
+ (const nil? :ApplicationMailer)
33
+ }
34
+ PATTERN
35
+
36
+ def_node_matcher :class_definition?, <<~PATTERN
37
+ (class $(const _ !#mailer_suffix?) #mailer_base_class? ...)
38
+ PATTERN
39
+
40
+ def_node_matcher :class_new_definition?, <<~PATTERN
41
+ (send (const nil? :Class) :new #mailer_base_class?)
42
+ PATTERN
43
+
44
+ def on_class(node)
45
+ class_definition?(node) do |name_node|
46
+ add_offense(name_node)
47
+ end
48
+ end
49
+
50
+ def on_send(node)
51
+ return unless class_new_definition?(node)
52
+
53
+ casgn_parent = node.each_ancestor(:casgn).first
54
+ return unless casgn_parent
55
+
56
+ name = casgn_parent.children[1]
57
+ add_offense(casgn_parent, location: :name) unless mailer_suffix?(name)
58
+ end
59
+
60
+ def autocorrect(node)
61
+ lambda do |corrector|
62
+ if node.casgn_type?
63
+ name = node.children[1]
64
+ corrector.replace(node.loc.name, "#{name}Mailer")
65
+ else
66
+ name = node.children.last
67
+ corrector.replace(node.source_range, "#{name}Mailer")
68
+ end
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def mailer_suffix?(mailer_name)
75
+ mailer_name.to_s.end_with?('Mailer')
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,119 @@
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 < Cop
24
+ MSG = 'Use `%<http_method>s` instead of `match` to define a route.'
25
+ HTTP_METHODS = %i[get post put patch delete].freeze
26
+
27
+ def_node_matcher :match_method_call?, <<~PATTERN
28
+ (send nil? :match $_ $(hash ...) ?)
29
+ PATTERN
30
+
31
+ def on_send(node)
32
+ match_method_call?(node) do |path_node, options_node|
33
+ return unless within_routes?(node)
34
+
35
+ options_node = path_node.hash_type? ? path_node : options_node.first
36
+
37
+ if options_node.nil?
38
+ message = format(MSG, http_method: 'get')
39
+ add_offense(node, message: message)
40
+ else
41
+ via = extract_via(options_node)
42
+ if via.size == 1 && http_method?(via.first)
43
+ message = format(MSG, http_method: via.first)
44
+ add_offense(node, message: message)
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ def autocorrect(node)
51
+ match_method_call?(node) do |path_node, options_node|
52
+ options_node = options_node.first
53
+
54
+ lambda do |corrector|
55
+ corrector.replace(node, replacement(path_node, options_node))
56
+ end
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def_node_matcher :routes_draw?, <<~PATTERN
63
+ (send (send _ :routes) :draw)
64
+ PATTERN
65
+
66
+ def within_routes?(node)
67
+ node.each_ancestor(:block).any? { |a| routes_draw?(a.send_node) }
68
+ end
69
+
70
+ def extract_via(node)
71
+ via_pair = via_pair(node)
72
+ return %i[get] unless via_pair
73
+
74
+ _, via = *via_pair
75
+
76
+ if via.basic_literal?
77
+ [via.value]
78
+ elsif via.array_type?
79
+ via.values.map(&:value)
80
+ else
81
+ []
82
+ end
83
+ end
84
+
85
+ def via_pair(node)
86
+ node.pairs.find { |p| p.key.value == :via }
87
+ end
88
+
89
+ def http_method?(method)
90
+ HTTP_METHODS.include?(method.to_sym)
91
+ end
92
+
93
+ def replacement(path_node, options_node)
94
+ if path_node.hash_type?
95
+ http_method, options = *http_method_and_options(path_node)
96
+ "#{http_method} #{options.map(&:source).join(', ')}"
97
+ elsif options_node.nil?
98
+ "get #{path_node.source}"
99
+ else
100
+ http_method, options = *http_method_and_options(options_node)
101
+
102
+ if options.any?
103
+ "#{http_method} #{path_node.source}, #{options.map(&:source).join(', ')}"
104
+ else
105
+ "#{http_method} #{path_node.source}"
106
+ end
107
+ end
108
+ end
109
+
110
+ def http_method_and_options(node)
111
+ via_pair = via_pair(node)
112
+ http_method = extract_via(node).first
113
+ rest_pairs = node.pairs - [via_pair]
114
+ [http_method, rest_pairs]
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end