rubocop-rails 2.5.0 → 2.7.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +156 -9
  5. data/lib/rubocop/cop/mixin/active_record_helper.rb +22 -0
  6. data/lib/rubocop/cop/mixin/index_method.rb +8 -1
  7. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +145 -0
  8. data/lib/rubocop/cop/rails/content_tag.rb +69 -0
  9. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +1 -3
  10. data/lib/rubocop/cop/rails/default_scope.rb +54 -0
  11. data/lib/rubocop/cop/rails/delegate.rb +2 -4
  12. data/lib/rubocop/cop/rails/dynamic_find_by.rb +40 -15
  13. data/lib/rubocop/cop/rails/exit.rb +2 -2
  14. data/lib/rubocop/cop/rails/file_path.rb +2 -1
  15. data/lib/rubocop/cop/rails/find_by_id.rb +103 -0
  16. data/lib/rubocop/cop/rails/http_positional_arguments.rb +1 -1
  17. data/lib/rubocop/cop/rails/http_status.rb +2 -0
  18. data/lib/rubocop/cop/rails/inquiry.rb +34 -0
  19. data/lib/rubocop/cop/rails/inverse_of.rb +0 -4
  20. data/lib/rubocop/cop/rails/link_to_blank.rb +3 -3
  21. data/lib/rubocop/cop/rails/mailer_name.rb +80 -0
  22. data/lib/rubocop/cop/rails/match_route.rb +119 -0
  23. data/lib/rubocop/cop/rails/negate_include.rb +39 -0
  24. data/lib/rubocop/cop/rails/pick.rb +55 -0
  25. data/lib/rubocop/cop/rails/pluck.rb +59 -0
  26. data/lib/rubocop/cop/rails/pluck_id.rb +58 -0
  27. data/lib/rubocop/cop/rails/pluck_in_where.rb +36 -0
  28. data/lib/rubocop/cop/rails/presence.rb +2 -6
  29. data/lib/rubocop/cop/rails/rake_environment.rb +17 -0
  30. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +80 -0
  31. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +0 -3
  32. data/lib/rubocop/cop/rails/render_inline.rb +40 -0
  33. data/lib/rubocop/cop/rails/render_plain_text.rb +76 -0
  34. data/lib/rubocop/cop/rails/safe_navigation.rb +1 -1
  35. data/lib/rubocop/cop/rails/save_bang.rb +6 -7
  36. data/lib/rubocop/cop/rails/short_i18n.rb +76 -0
  37. data/lib/rubocop/cop/rails/skips_model_validations.rb +46 -8
  38. data/lib/rubocop/cop/rails/time_zone.rb +1 -3
  39. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +12 -12
  40. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +28 -6
  41. data/lib/rubocop/cop/rails/unknown_env.rb +18 -6
  42. data/lib/rubocop/cop/rails/where_exists.rb +68 -0
  43. data/lib/rubocop/cop/rails_cops.rb +17 -0
  44. data/lib/rubocop/rails/schema_loader.rb +10 -10
  45. data/lib/rubocop/rails/schema_loader/schema.rb +48 -14
  46. data/lib/rubocop/rails/version.rb +1 -1
  47. metadata +27 -10
@@ -0,0 +1,69 @@
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
+ range = correction_range(node)
44
+
45
+ rest_args = node.arguments.drop(1)
46
+ replacement = "tag.#{node.first_argument.value}(#{rest_args.map(&:source).join(', ')})"
47
+
48
+ corrector.replace(range, replacement)
49
+ else
50
+ corrector.replace(node.loc.selector, 'tag')
51
+ end
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def method_name?(node)
58
+ return false unless node.str_type? || node.sym_type?
59
+
60
+ /^[a-zA-Z_][a-zA-Z_0-9]*$/.match?(node.value)
61
+ end
62
+
63
+ def correction_range(node)
64
+ range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ 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
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop looks for uses of `default_scope`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # default_scope -> { where(hidden: false) }
11
+ #
12
+ # # good
13
+ # scope :published, -> { where(hidden: false) }
14
+ #
15
+ # # bad
16
+ # def self.default_scope
17
+ # where(hidden: false)
18
+ # end
19
+ #
20
+ # # good
21
+ # def self.published
22
+ # where(hidden: false)
23
+ # end
24
+ #
25
+ class DefaultScope < Cop
26
+ MSG = 'Avoid use of `default_scope`. It is better to use explicitly named scopes.'
27
+
28
+ def_node_matcher :method_call?, <<~PATTERN
29
+ (send nil? :default_scope ...)
30
+ PATTERN
31
+
32
+ def_node_matcher :class_method_definition?, <<~PATTERN
33
+ (defs _ :default_scope args ...)
34
+ PATTERN
35
+
36
+ def_node_matcher :eigenclass_method_definition?, <<~PATTERN
37
+ (sclass (self) $(def :default_scope args ...))
38
+ PATTERN
39
+
40
+ def on_send(node)
41
+ add_offense(node, location: :selector) if method_call?(node)
42
+ end
43
+
44
+ def on_defs(node)
45
+ add_offense(node, location: :name) if class_method_definition?(node)
46
+ end
47
+
48
+ def on_sclass(node)
49
+ eigenclass_method_definition?(node) { |default_scope| add_offense(default_scope, location: :name) }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -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(', '))
@@ -124,7 +122,7 @@ module RuboCop
124
122
  end
125
123
 
126
124
  def private_or_protected_inline(line)
127
- processed_source[line - 1].strip =~ /\A(private )|(protected )/
125
+ processed_source[line - 1].strip.match?(/\A(private )|(protected )/)
128
126
  end
129
127
  end
130
128
  end
@@ -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}: " }
@@ -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
@@ -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,34 @@
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
+ add_offense(node, location: :selector) if node.method?(:inquiry) && node.arguments.empty?
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end