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
@@ -10,37 +10,41 @@ module RuboCop
10
10
  # @example
11
11
  # # bad
12
12
  # User.find_by_name(name)
13
- #
14
- # # bad
15
13
  # User.find_by_name_and_email(name)
16
- #
17
- # # bad
18
14
  # User.find_by_email!(name)
19
15
  #
20
16
  # # good
21
17
  # User.find_by(name: name)
18
+ # User.find_by(name: name, email: email)
19
+ # User.find_by!(email: email)
20
+ #
21
+ # @example AllowedMethods: find_by_sql
22
+ # # bad
23
+ # User.find_by_query(users_query)
22
24
  #
23
25
  # # good
24
- # User.find_by(name: name, email: email)
26
+ # User.find_by_sql(users_sql)
27
+ #
28
+ # @example AllowedReceivers: Gem::Specification
29
+ # # bad
30
+ # Specification.find_by_name('backend').gem_dir
25
31
  #
26
32
  # # good
27
- # User.find_by!(email: email)
33
+ # Gem::Specification.find_by_name('backend').gem_dir
28
34
  class DynamicFindBy < Cop
29
35
  MSG = 'Use `%<static_name>s` instead of dynamic `%<method>s`.'
30
36
  METHOD_PATTERN = /^find_by_(.+?)(!)?$/.freeze
31
37
 
32
38
  def on_send(node)
33
- method_name = node.method_name.to_s
34
-
35
- return if whitelist.include?(method_name)
39
+ return if allowed_invocation?(node)
36
40
 
41
+ method_name = node.method_name
37
42
  static_name = static_method_name(method_name)
38
-
39
43
  return unless static_name
40
44
 
41
45
  add_offense(node,
42
46
  message: format(MSG, static_name: static_name,
43
- method: node.method_name))
47
+ method: method_name))
44
48
  end
45
49
  alias on_csend on_send
46
50
 
@@ -57,6 +61,31 @@ module RuboCop
57
61
 
58
62
  private
59
63
 
64
+ def allowed_invocation?(node)
65
+ allowed_method?(node) || allowed_receiver?(node) ||
66
+ whitelisted?(node)
67
+ end
68
+
69
+ def allowed_method?(node)
70
+ return unless cop_config['AllowedMethods']
71
+
72
+ cop_config['AllowedMethods'].include?(node.method_name.to_s)
73
+ end
74
+
75
+ def allowed_receiver?(node)
76
+ return unless cop_config['AllowedReceivers'] && node.receiver
77
+
78
+ cop_config['AllowedReceivers'].include?(node.receiver.source)
79
+ end
80
+
81
+ # config option `WhiteList` will be deprecated soon
82
+ def whitelisted?(node)
83
+ whitelist_config = cop_config['Whitelist']
84
+ return unless whitelist_config
85
+
86
+ whitelist_config.include?(node.method_name.to_s)
87
+ end
88
+
60
89
  def autocorrect_method_name(corrector, node)
61
90
  corrector.replace(node.loc.selector,
62
91
  static_method_name(node.method_name.to_s))
@@ -68,10 +97,6 @@ module RuboCop
68
97
  end
69
98
  end
70
99
 
71
- def whitelist
72
- cop_config['Whitelist']
73
- end
74
-
75
100
  def column_keywords(method)
76
101
  keyword_string = method.to_s[METHOD_PATTERN, 1]
77
102
  keyword_string.split('_and_').map { |keyword| "#{keyword}: " }
@@ -16,52 +16,98 @@ module RuboCop
16
16
  # # good
17
17
  # Rails.env.production?
18
18
  class EnvironmentComparison < Cop
19
- MSG = "Favor `Rails.env.%<env>s?` over `Rails.env == '%<env>s'`."
19
+ MSG = 'Favor `%<bang>sRails.env.%<env>s?` over `%<source>s`.'
20
20
 
21
21
  SYM_MSG = 'Do not compare `Rails.env` with a symbol, it will always ' \
22
22
  'evaluate to `false`.'
23
23
 
24
- def_node_matcher :environment_str_comparison?, <<~PATTERN
24
+ def_node_matcher :comparing_str_env_with_rails_env_on_lhs?, <<~PATTERN
25
25
  (send
26
26
  (send (const {nil? cbase} :Rails) :env)
27
- :==
27
+ {:== :!=}
28
28
  $str
29
29
  )
30
30
  PATTERN
31
31
 
32
- def_node_matcher :environment_sym_comparison?, <<~PATTERN
32
+ def_node_matcher :comparing_str_env_with_rails_env_on_rhs?, <<~PATTERN
33
+ (send
34
+ $str
35
+ {:== :!=}
36
+ (send (const {nil? cbase} :Rails) :env)
37
+ )
38
+ PATTERN
39
+
40
+ def_node_matcher :comparing_sym_env_with_rails_env_on_lhs?, <<~PATTERN
33
41
  (send
34
42
  (send (const {nil? cbase} :Rails) :env)
35
- :==
43
+ {:== :!=}
36
44
  $sym
37
45
  )
38
46
  PATTERN
39
47
 
48
+ def_node_matcher :comparing_sym_env_with_rails_env_on_rhs?, <<~PATTERN
49
+ (send
50
+ $sym
51
+ {:== :!=}
52
+ (send (const {nil? cbase} :Rails) :env)
53
+ )
54
+ PATTERN
55
+
56
+ def_node_matcher :content, <<~PATTERN
57
+ ({str sym} $_)
58
+ PATTERN
59
+
40
60
  def on_send(node)
41
- environment_str_comparison?(node) do |env_node|
61
+ if (env_node = comparing_str_env_with_rails_env_on_lhs?(node) ||
62
+ comparing_str_env_with_rails_env_on_rhs?(node))
42
63
  env, = *env_node
43
- add_offense(node, message: format(MSG, env: env))
64
+ bang = node.method?(:!=) ? '!' : ''
65
+
66
+ add_offense(node, message: format(
67
+ MSG, bang: bang, env: env, source: node.source
68
+ ))
44
69
  end
45
- environment_sym_comparison?(node) do |_|
70
+
71
+ if comparing_sym_env_with_rails_env_on_lhs?(node) ||
72
+ comparing_sym_env_with_rails_env_on_rhs?(node)
46
73
  add_offense(node, message: SYM_MSG)
47
74
  end
48
75
  end
49
76
 
50
77
  def autocorrect(node)
51
78
  lambda do |corrector|
52
- corrector.replace(node.source_range, replacement(node))
79
+ replacement = build_predicate_method(node)
80
+
81
+ corrector.replace(node.source_range, replacement)
53
82
  end
54
83
  end
55
84
 
56
85
  private
57
86
 
58
- def replacement(node)
59
- "#{node.receiver.source}.#{content(node.first_argument)}?"
87
+ def build_predicate_method(node)
88
+ if rails_env_on_lhs?(node)
89
+ build_predicate_method_for_rails_env_on_lhs(node)
90
+ else
91
+ build_predicate_method_for_rails_env_on_rhs(node)
92
+ end
60
93
  end
61
94
 
62
- def_node_matcher :content, <<~PATTERN
63
- ({str sym} $_)
64
- PATTERN
95
+ def rails_env_on_lhs?(node)
96
+ comparing_str_env_with_rails_env_on_lhs?(node) ||
97
+ comparing_sym_env_with_rails_env_on_lhs?(node)
98
+ end
99
+
100
+ def build_predicate_method_for_rails_env_on_lhs(node)
101
+ bang = node.method?(:!=) ? '!' : ''
102
+
103
+ "#{bang}#{node.receiver.source}.#{content(node.first_argument)}?"
104
+ end
105
+
106
+ def build_predicate_method_for_rails_env_on_rhs(node)
107
+ bang = node.method?(:!=) ? '!' : ''
108
+
109
+ "#{bang}#{node.first_argument.source}.#{content(node.receiver)}?"
110
+ end
65
111
  end
66
112
  end
67
113
  end
@@ -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.match?(%r{/})
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
@@ -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
@@ -22,7 +22,7 @@ module RuboCop
22
22
  MSG = 'Use keyword arguments instead of ' \
23
23
  'positional arguments for http call: `%<verb>s`.'
24
24
  KEYWORD_ARGS = %i[
25
- method params session body flash xhr as headers env
25
+ method params session body flash xhr as headers env to
26
26
  ].freeze
27
27
  HTTP_METHODS = %i[get post put patch delete head].freeze
28
28
 
@@ -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
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop looks for uses of `each_with_object({}) { ... }`,
7
+ # `map { ... }.to_h`, and `Hash[map { ... }]` that are transforming
8
+ # an enumerable into a hash where the values are the original elements.
9
+ # Rails provides the `index_by` method for this purpose.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # [1, 2, 3].each_with_object({}) { |el, h| h[foo(el)] = el }
14
+ # [1, 2, 3].map { |el| [foo(el), el] }.to_h
15
+ # Hash[[1, 2, 3].collect { |el| [foo(el), el] }]
16
+ #
17
+ # # good
18
+ # [1, 2, 3].index_by { |el| foo(el) }
19
+ class IndexBy < Cop
20
+ include IndexMethod
21
+
22
+ def_node_matcher :on_bad_each_with_object, <<~PATTERN
23
+ (block
24
+ ({send csend} _ :each_with_object (hash))
25
+ (args (arg $_el) (arg _memo))
26
+ ({send csend} (lvar _memo) :[]= $_ (lvar _el)))
27
+ PATTERN
28
+
29
+ def_node_matcher :on_bad_map_to_h, <<~PATTERN
30
+ ({send csend}
31
+ (block
32
+ ({send csend} _ {:map :collect})
33
+ (args (arg $_el))
34
+ (array $_ (lvar _el)))
35
+ :to_h)
36
+ PATTERN
37
+
38
+ def_node_matcher :on_bad_hash_brackets_map, <<~PATTERN
39
+ (send
40
+ (const _ :Hash)
41
+ :[]
42
+ (block
43
+ ({send csend} _ {:map :collect})
44
+ (args (arg $_el))
45
+ (array $_ (lvar _el))))
46
+ PATTERN
47
+
48
+ private
49
+
50
+ def new_method_name
51
+ 'index_by'
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop looks for uses of `each_with_object({}) { ... }`,
7
+ # `map { ... }.to_h`, and `Hash[map { ... }]` that are transforming
8
+ # an enumerable into a hash where the keys are the original elements.
9
+ # Rails provides the `index_with` method for this purpose.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # [1, 2, 3].each_with_object({}) { |el, h| h[el] = foo(el) }
14
+ # [1, 2, 3].map { |el| [el, foo(el)] }.to_h
15
+ # Hash[[1, 2, 3].collect { |el| [el, foo(el)] }]
16
+ #
17
+ # # good
18
+ # [1, 2, 3].index_with { |el| foo(el) }
19
+ class IndexWith < Cop
20
+ extend TargetRailsVersion
21
+ include IndexMethod
22
+
23
+ minimum_target_rails_version 6.0
24
+
25
+ def_node_matcher :on_bad_each_with_object, <<~PATTERN
26
+ (block
27
+ ({send csend} _ :each_with_object (hash))
28
+ (args (arg $_el) (arg _memo))
29
+ ({send csend} (lvar _memo) :[]= (lvar _el) $_))
30
+ PATTERN
31
+
32
+ def_node_matcher :on_bad_map_to_h, <<~PATTERN
33
+ ({send csend}
34
+ (block
35
+ ({send csend} _ {:map :collect})
36
+ (args (arg $_el))
37
+ (array (lvar _el) $_))
38
+ :to_h)
39
+ PATTERN
40
+
41
+ def_node_matcher :on_bad_hash_brackets_map, <<~PATTERN
42
+ (send
43
+ (const _ :Hash)
44
+ :[]
45
+ (block
46
+ ({send csend} _ {:map :collect})
47
+ (args (arg $_el))
48
+ (array (lvar _el) $_)))
49
+ PATTERN
50
+
51
+ private
52
+
53
+ def new_method_name
54
+ 'index_with'
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end