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
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks that `tag` is used instead of `content_tag`
7
+ # because `content_tag` is legacy syntax.
8
+ #
9
+ # NOTE: Allow `content_tag` when the first argument is a variable because
10
+ # `content_tag(name)` is simpler rather than `tag.public_send(name)`.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # content_tag(:p, 'Hello world!')
15
+ # content_tag(:br)
16
+ #
17
+ # # good
18
+ # tag.p('Hello world!')
19
+ # tag.br
20
+ # content_tag(name, 'Hello world!')
21
+ class ContentTag < Cop
22
+ include RangeHelp
23
+ extend TargetRailsVersion
24
+
25
+ minimum_target_rails_version 5.1
26
+
27
+ MSG = 'Use `tag` instead of `content_tag`.'
28
+
29
+ def on_send(node)
30
+ return unless node.method?(:content_tag)
31
+
32
+ first_argument = node.first_argument
33
+ return unless first_argument
34
+
35
+ return if first_argument.variable? || first_argument.send_type? || first_argument.const_type?
36
+
37
+ add_offense(node)
38
+ end
39
+
40
+ def autocorrect(node)
41
+ lambda do |corrector|
42
+ if method_name?(node.first_argument)
43
+ replace_method_with_tag_method(corrector, node)
44
+ remove_first_argument(corrector, node)
45
+ else
46
+ corrector.replace(node.loc.selector, 'tag')
47
+ end
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def method_name?(node)
54
+ return false unless node.str_type? || node.sym_type?
55
+
56
+ /^[a-zA-Z_][a-zA-Z_0-9]*$/.match?(node.value)
57
+ end
58
+
59
+ def replace_method_with_tag_method(corrector, node)
60
+ corrector.replace(
61
+ node.loc.selector,
62
+ "tag.#{node.first_argument.value}"
63
+ )
64
+ end
65
+
66
+ def remove_first_argument(corrector, node)
67
+ if node.arguments.length > 1
68
+ corrector.remove(
69
+ range_between(child_node_beg(node, 0), child_node_beg(node, 1))
70
+ )
71
+ elsif node.arguments.length == 1
72
+ corrector.remove(node.arguments[0].loc.expression)
73
+ end
74
+ end
75
+
76
+ def child_node_beg(node, index)
77
+ node.arguments[index].loc.expression.begin_pos
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -70,9 +70,7 @@ module RuboCop
70
70
  parent = node.parent
71
71
 
72
72
  if create_table_with_block?(parent)
73
- if parent.body.nil? || !time_columns_included?(parent.body)
74
- add_offense(parent)
75
- end
73
+ add_offense(parent) if parent.body.nil? || !time_columns_included?(parent.body)
76
74
  elsif create_table_with_timestamps_proc?(node)
77
75
  # nothing to do
78
76
  else
@@ -71,9 +71,7 @@ module RuboCop
71
71
  delegation = ["delegate :#{node.body.method_name}",
72
72
  "to: :#{node.body.receiver.method_name}"]
73
73
 
74
- if node.method?(prefixed_method_name(node.body))
75
- delegation << ['prefix: true']
76
- end
74
+ delegation << ['prefix: true'] if node.method?(prefixed_method_name(node.body))
77
75
 
78
76
  lambda do |corrector|
79
77
  corrector.replace(node.source_range, delegation.join(', '))
@@ -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
 
@@ -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