rubocop-rails 2.9.1 → 2.11.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +4 -4
  4. data/config/default.yml +111 -5
  5. data/config/obsoletion.yml +7 -0
  6. data/lib/rubocop/cop/mixin/active_record_helper.rb +11 -0
  7. data/lib/rubocop/cop/rails/add_column_index.rb +64 -0
  8. data/lib/rubocop/cop/rails/attribute_default_block_value.rb +1 -1
  9. data/lib/rubocop/cop/rails/belongs_to.rb +1 -1
  10. data/lib/rubocop/cop/rails/blank.rb +4 -0
  11. data/lib/rubocop/cop/rails/bulk_change_table.rb +5 -1
  12. data/lib/rubocop/cop/rails/content_tag.rb +18 -3
  13. data/lib/rubocop/cop/rails/date.rb +17 -6
  14. data/lib/rubocop/cop/rails/dynamic_find_by.rb +4 -2
  15. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +78 -0
  16. data/lib/rubocop/cop/rails/environment_comparison.rb +1 -2
  17. data/lib/rubocop/cop/rails/environment_variable_access.rb +67 -0
  18. data/lib/rubocop/cop/rails/expanded_date_range.rb +86 -0
  19. data/lib/rubocop/cop/rails/file_path.rb +2 -4
  20. data/lib/rubocop/cop/rails/find_by.rb +31 -12
  21. data/lib/rubocop/cop/rails/find_each.rb +2 -0
  22. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +34 -4
  23. data/lib/rubocop/cop/rails/http_positional_arguments.rb +8 -1
  24. data/lib/rubocop/cop/rails/http_status.rb +12 -3
  25. data/lib/rubocop/cop/rails/i18n_locale_assignment.rb +37 -0
  26. data/lib/rubocop/cop/rails/inverse_of.rb +1 -2
  27. data/lib/rubocop/cop/rails/link_to_blank.rb +5 -1
  28. data/lib/rubocop/cop/rails/reflection_class_name.rb +14 -1
  29. data/lib/rubocop/cop/rails/relative_date_constant.rb +18 -19
  30. data/lib/rubocop/cop/rails/require_dependency.rb +38 -0
  31. data/lib/rubocop/cop/rails/reversible_migration.rb +1 -1
  32. data/lib/rubocop/cop/rails/reversible_migration_method_definition.rb +75 -0
  33. data/lib/rubocop/cop/rails/safe_navigation.rb +20 -2
  34. data/lib/rubocop/cop/rails/time_zone.rb +22 -22
  35. data/lib/rubocop/cop/rails/time_zone_assignment.rb +37 -0
  36. data/lib/rubocop/cop/rails/unknown_env.rb +1 -1
  37. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +69 -0
  38. data/lib/rubocop/cop/rails/where_exists.rb +11 -0
  39. data/lib/rubocop/cop/rails/where_not.rb +5 -1
  40. data/lib/rubocop/cop/rails_cops.rb +9 -0
  41. data/lib/rubocop/rails.rb +2 -0
  42. data/lib/rubocop/rails/schema_loader/schema.rb +2 -4
  43. data/lib/rubocop/rails/version.rb +1 -1
  44. metadata +21 -11
@@ -12,20 +12,21 @@ module RuboCop
12
12
  # The cop also reports warnings when you are using `to_time` method,
13
13
  # because it doesn't know about Rails time zone either.
14
14
  #
15
- # Two styles are supported for this cop. When EnforcedStyle is 'strict'
15
+ # Two styles are supported for this cop. When `EnforcedStyle` is 'strict'
16
16
  # then the Date methods `today`, `current`, `yesterday`, and `tomorrow`
17
17
  # are prohibited and the usage of both `to_time`
18
18
  # and 'to_time_in_current_zone' are reported as warning.
19
19
  #
20
- # When EnforcedStyle is 'flexible' then only `Date.today` is prohibited
21
- # and only `to_time` is reported as warning.
20
+ # When `EnforcedStyle` is `flexible` then only `Date.today` is prohibited.
21
+ #
22
+ # And you can set a warning for `to_time` with `AllowToTime: false`.
23
+ # `AllowToTime` is `true` by default to prevent false positive on `DateTime` object.
22
24
  #
23
25
  # @example EnforcedStyle: strict
24
26
  # # bad
25
27
  # Date.current
26
28
  # Date.yesterday
27
29
  # Date.today
28
- # date.to_time
29
30
  #
30
31
  # # good
31
32
  # Time.zone.today
@@ -34,7 +35,6 @@ module RuboCop
34
35
  # @example EnforcedStyle: flexible (default)
35
36
  # # bad
36
37
  # Date.today
37
- # date.to_time
38
38
  #
39
39
  # # good
40
40
  # Time.zone.today
@@ -43,6 +43,13 @@ module RuboCop
43
43
  # Date.yesterday
44
44
  # date.in_time_zone
45
45
  #
46
+ # @example AllowToTime: true (default)
47
+ # # good
48
+ # date.to_time
49
+ #
50
+ # @example AllowToTime: false
51
+ # # bad
52
+ # date.to_time
46
53
  class Date < Base
47
54
  include ConfigurableEnforcedStyle
48
55
 
@@ -73,7 +80,7 @@ module RuboCop
73
80
 
74
81
  def on_send(node)
75
82
  return unless node.receiver && bad_methods.include?(node.method_name)
76
-
83
+ return if allow_to_time? && node.method?(:to_time)
77
84
  return if safe_chain?(node) || safe_to_time?(node)
78
85
 
79
86
  check_deprecated_methods(node)
@@ -139,6 +146,10 @@ module RuboCop
139
146
  end
140
147
  end
141
148
 
149
+ def allow_to_time?
150
+ cop_config.fetch('AllowToTime', true)
151
+ end
152
+
142
153
  def good_days
143
154
  style == :strict ? [] : %i[current yesterday tomorrow]
144
155
  end
@@ -32,18 +32,20 @@ module RuboCop
32
32
  # # good
33
33
  # Gem::Specification.find_by_name('backend').gem_dir
34
34
  class DynamicFindBy < Base
35
+ include ActiveRecordHelper
35
36
  extend AutoCorrector
36
37
 
37
38
  MSG = 'Use `%<static_name>s` instead of dynamic `%<method>s`.'
38
39
  METHOD_PATTERN = /^find_by_(.+?)(!)?$/.freeze
40
+ IGNORED_ARGUMENT_TYPES = %i[hash splat].freeze
39
41
 
40
42
  def on_send(node)
41
- return if allowed_invocation?(node)
43
+ return if node.receiver.nil? && !inherit_active_record_base?(node) || allowed_invocation?(node)
42
44
 
43
45
  method_name = node.method_name
44
46
  static_name = static_method_name(method_name)
45
47
  return unless static_name
46
- return if node.arguments.any?(&:splat_type?)
48
+ return if node.arguments.any? { |argument| IGNORED_ARGUMENT_TYPES.include?(argument.type) }
47
49
 
48
50
  message = format(MSG, static_name: static_name, method: method_name)
49
51
  add_offense(node, message: message) do |corrector|
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks that blocks are used for interpolated strings passed to
7
+ # `Rails.logger.debug`.
8
+ #
9
+ # By default, Rails production environments use the `:info` log level.
10
+ # At the `:info` log level, `Rails.logger.debug` statements do not result
11
+ # in log output. However, Ruby must eagerly evaluate interpolated string
12
+ # arguments passed as method arguments. Passing a block to
13
+ # `Rails.logger.debug` prevents costly evaluation of interpolated strings
14
+ # when no output would be produced anyway.
15
+ #
16
+ # @example
17
+ # #bad
18
+ # Rails.logger.debug "The time is #{Time.zone.now}."
19
+ #
20
+ # #good
21
+ # Rails.logger.debug { "The time is #{Time.zone.now}." }
22
+ #
23
+ class EagerEvaluationLogMessage < Base
24
+ extend AutoCorrector
25
+
26
+ MSG = 'Pass a block to `Rails.logger.debug`.'
27
+ RESTRICT_ON_SEND = %i[debug].freeze
28
+
29
+ def_node_matcher :interpolated_string_passed_to_debug, <<~PATTERN
30
+ (send
31
+ (send
32
+ (const {cbase nil?} :Rails)
33
+ :logger
34
+ )
35
+ :debug
36
+ $(dstr ...)
37
+ )
38
+ PATTERN
39
+
40
+ def on_send(node)
41
+ return if node.parent&.block_type?
42
+
43
+ interpolated_string_passed_to_debug(node) do |arguments|
44
+ message = format(MSG)
45
+
46
+ range = replacement_range(node)
47
+ replacement = replacement_source(node, arguments)
48
+
49
+ add_offense(range, message: message) do |corrector|
50
+ corrector.replace(range, replacement)
51
+ end
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def replacement_range(node)
58
+ stop = node.loc.expression.end
59
+ start = node.loc.selector.end
60
+
61
+ if node.parenthesized_call?
62
+ stop.with(begin_pos: start.begin_pos)
63
+ else
64
+ stop.with(begin_pos: start.begin_pos + 1)
65
+ end
66
+ end
67
+
68
+ def replacement_source(node, arguments)
69
+ if node.parenthesized_call?
70
+ " { #{arguments.source} }"
71
+ else
72
+ "{ #{arguments.source} }"
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -20,8 +20,7 @@ module RuboCop
20
20
 
21
21
  MSG = 'Favor `%<bang>sRails.env.%<env>s?` over `%<source>s`.'
22
22
 
23
- SYM_MSG = 'Do not compare `Rails.env` with a symbol, it will always ' \
24
- 'evaluate to `false`.'
23
+ SYM_MSG = 'Do not compare `Rails.env` with a symbol, it will always evaluate to `false`.'
25
24
 
26
25
  RESTRICT_ON_SEND = %i[== !=].freeze
27
26
 
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop looks for direct access to environment variables through the
7
+ # `ENV` variable within the application code. This can lead to runtime
8
+ # errors due to misconfiguration that could have been discovered at boot
9
+ # time if the environment variables were loaded as part of initialization
10
+ # and copied into the application's configuration or secrets. The cop can
11
+ # be configured to allow either reads or writes if required.
12
+ #
13
+ # @example
14
+ # # good
15
+ # Rails.application.config.foo
16
+ # Rails.application.config.x.foo.bar
17
+ # Rails.application.secrets.foo
18
+ # Rails.application.config.foo = "bar"
19
+ #
20
+ # @example AllowReads: false (default)
21
+ # # bad
22
+ # ENV["FOO"]
23
+ # ENV.fetch("FOO")
24
+ #
25
+ # @example AllowReads: true
26
+ # # good
27
+ # ENV["FOO"]
28
+ # ENV.fetch("FOO")
29
+ #
30
+ # @example AllowWrites: false (default)
31
+ # # bad
32
+ # ENV["FOO"] = "bar"
33
+ #
34
+ # @example AllowWrites: true
35
+ # # good
36
+ # ENV["FOO"] = "bar"
37
+ class EnvironmentVariableAccess < Base
38
+ READ_MSG = 'Do not read from `ENV` directly post initialization.'
39
+ WRITE_MSG = 'Do not write to `ENV` directly post initialization.'
40
+
41
+ def on_const(node)
42
+ add_offense(node, message: READ_MSG) if env_read?(node) && !allow_reads?
43
+ add_offense(node, message: WRITE_MSG) if env_write?(node) && !allow_writes?
44
+ end
45
+
46
+ def_node_search :env_read?, <<~PATTERN
47
+ ^(send (const {cbase nil?} :ENV) !:[]= ...)
48
+ PATTERN
49
+
50
+ def_node_search :env_write?, <<~PATTERN
51
+ {^(indexasgn (const {cbase nil?} :ENV) ...)
52
+ ^(send (const {cbase nil?} :ENV) :[]= ...)}
53
+ PATTERN
54
+
55
+ private
56
+
57
+ def allow_reads?
58
+ cop_config['AllowReads'] == true
59
+ end
60
+
61
+ def allow_writes?
62
+ cop_config['AllowWrites'] == true
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks for expanded date range. It only compatible `..` range is targeted.
7
+ # Incompatible `...` range is ignored.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # date.beginning_of_day..date.end_of_day
12
+ # date.beginning_of_week..date.end_of_week
13
+ # date.beginning_of_month..date.end_of_month
14
+ # date.beginning_of_quarter..date.end_of_quarter
15
+ # date.beginning_of_year..date.end_of_year
16
+ #
17
+ # # good
18
+ # date.all_day
19
+ # date.all_week
20
+ # date.all_month
21
+ # date.all_quarter
22
+ # date.all_year
23
+ #
24
+ class ExpandedDateRange < Base
25
+ extend AutoCorrector
26
+ extend TargetRailsVersion
27
+
28
+ MSG = 'Use `%<preferred_method>s` instead.'
29
+
30
+ minimum_target_rails_version 5.1
31
+
32
+ def_node_matcher :expanded_date_range, <<~PATTERN
33
+ (irange
34
+ (send
35
+ $_ {:beginning_of_day :beginning_of_week :beginning_of_month :beginning_of_quarter :beginning_of_year})
36
+ (send
37
+ $_ {:end_of_day :end_of_week :end_of_month :end_of_quarter :end_of_year}))
38
+ PATTERN
39
+
40
+ PREFERRED_METHODS = {
41
+ beginning_of_day: 'all_day',
42
+ beginning_of_week: 'all_week',
43
+ beginning_of_month: 'all_month',
44
+ beginning_of_quarter: 'all_quarter',
45
+ beginning_of_year: 'all_year'
46
+ }.freeze
47
+
48
+ MAPPED_DATE_RANGE_METHODS = {
49
+ beginning_of_day: :end_of_day,
50
+ beginning_of_week: :end_of_week,
51
+ beginning_of_month: :end_of_month,
52
+ beginning_of_quarter: :end_of_quarter,
53
+ beginning_of_year: :end_of_year
54
+ }.freeze
55
+
56
+ def on_irange(node)
57
+ return unless expanded_date_range(node)
58
+
59
+ begin_node = node.begin
60
+ end_node = node.end
61
+ return unless same_receiver?(begin_node, end_node)
62
+
63
+ beginning_method = begin_node.method_name
64
+ end_method = end_node.method_name
65
+ return unless use_mapped_methods?(beginning_method, end_method)
66
+
67
+ preferred_method = "#{begin_node.receiver.source}.#{PREFERRED_METHODS[beginning_method]}"
68
+
69
+ add_offense(node, message: format(MSG, preferred_method: preferred_method)) do |corrector|
70
+ corrector.replace(node, preferred_method)
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def same_receiver?(begin_node, end_node)
77
+ begin_node.receiver.source == end_node.receiver.source
78
+ end
79
+
80
+ def use_mapped_methods?(beginning_method, end_method)
81
+ MAPPED_DATE_RANGE_METHODS[beginning_method] == end_method
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -29,10 +29,8 @@ module RuboCop
29
29
  include ConfigurableEnforcedStyle
30
30
  include RangeHelp
31
31
 
32
- MSG_SLASHES = 'Please use `Rails.root.join(\'path/to\')` ' \
33
- 'instead.'
34
- MSG_ARGUMENTS = 'Please use `Rails.root.join(\'path\', \'to\')` ' \
35
- 'instead.'
32
+ MSG_SLASHES = 'Prefer `Rails.root.join(\'path/to\')`.'
33
+ MSG_ARGUMENTS = 'Prefer `Rails.root.join(\'path\', \'to\')`.'
36
34
  RESTRICT_ON_SEND = %i[join].freeze
37
35
 
38
36
  def_node_matcher :file_join_nodes?, <<~PATTERN
@@ -3,16 +3,27 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop is used to identify usages of `where.first` and
7
- # change them to use `find_by` instead.
6
+ # This cop is used to identify usages of `where.take` and change them to use `find_by` instead.
7
+ #
8
+ # And `where(...).first` can return different results from `find_by`.
9
+ # (They order records differently, so the "first" record can be different.)
10
+ #
11
+ # If you also want to detect `where.first`, you can set `IgnoreWhereFirst` to false.
8
12
  #
9
13
  # @example
10
14
  # # bad
11
- # User.where(name: 'Bruce').first
12
15
  # User.where(name: 'Bruce').take
13
16
  #
14
17
  # # good
15
18
  # User.find_by(name: 'Bruce')
19
+ #
20
+ # @example IgnoreWhereFirst: true (default)
21
+ # # good
22
+ # User.where(name: 'Bruce').first
23
+ #
24
+ # @example IgnoreWhereFirst: false
25
+ # # bad
26
+ # User.where(name: 'Bruce').first
16
27
  class FindBy < Base
17
28
  include RangeHelp
18
29
  extend AutoCorrector
@@ -20,14 +31,11 @@ module RuboCop
20
31
  MSG = 'Use `find_by` instead of `where.%<method>s`.'
21
32
  RESTRICT_ON_SEND = %i[first take].freeze
22
33
 
23
- def_node_matcher :where_first?, <<~PATTERN
24
- (send ({send csend} _ :where ...) {:first :take})
25
- PATTERN
26
-
27
34
  def on_send(node)
28
- return unless where_first?(node)
35
+ return unless node.arguments.empty? && where_method?(node.receiver)
36
+ return if ignore_where_first? && node.method?(:first)
29
37
 
30
- range = range_between(node.receiver.loc.selector.begin_pos, node.loc.selector.end_pos)
38
+ range = offense_range(node)
31
39
 
32
40
  add_offense(range, message: format(MSG, method: node.method_name)) do |corrector|
33
41
  autocorrect(corrector, node)
@@ -37,10 +45,17 @@ module RuboCop
37
45
 
38
46
  private
39
47
 
48
+ def where_method?(receiver)
49
+ return false unless receiver
50
+
51
+ receiver.respond_to?(:method?) && receiver.method?(:where)
52
+ end
53
+
54
+ def offense_range(node)
55
+ range_between(node.receiver.loc.selector.begin_pos, node.loc.selector.end_pos)
56
+ end
57
+
40
58
  def autocorrect(corrector, node)
41
- # Don't autocorrect where(...).first, because it can return different
42
- # results from find_by. (They order records differently, so the
43
- # 'first' record can be different.)
44
59
  return if node.method?(:first)
45
60
 
46
61
  where_loc = node.receiver.loc.selector
@@ -49,6 +64,10 @@ module RuboCop
49
64
  corrector.replace(where_loc, 'find_by')
50
65
  corrector.replace(first_loc, '')
51
66
  end
67
+
68
+ def ignore_where_first?
69
+ cop_config.fetch('IgnoreWhereFirst', true)
70
+ end
52
71
  end
53
72
  end
54
73
  end
@@ -17,6 +17,7 @@ module RuboCop
17
17
  # # good
18
18
  # User.order(:foo).each
19
19
  class FindEach < Base
20
+ include ActiveRecordHelper
20
21
  extend AutoCorrector
21
22
 
22
23
  MSG = 'Use `find_each` instead of `each`.'
@@ -30,6 +31,7 @@ module RuboCop
30
31
  def on_send(node)
31
32
  return unless node.receiver&.send_type?
32
33
  return unless SCOPE_METHODS.include?(node.receiver.method_name)
34
+ return if node.receiver.receiver.nil? && !inherit_active_record_base?(node)
33
35
  return if ignored?(node)
34
36
 
35
37
  range = node.loc.selector
@@ -5,7 +5,9 @@ module RuboCop
5
5
  module Rails
6
6
  # This cop looks for `has_many` or `has_one` associations that don't
7
7
  # specify a `:dependent` option.
8
- # It doesn't register an offense if `:through` option was specified.
8
+ #
9
+ # It doesn't register an offense if `:through` or `dependent: nil`
10
+ # is specified, or if the model is read-only.
9
11
  #
10
12
  # @example
11
13
  # # bad
@@ -18,8 +20,18 @@ module RuboCop
18
20
  # class User < ActiveRecord::Base
19
21
  # has_many :comments, dependent: :restrict_with_exception
20
22
  # has_one :avatar, dependent: :destroy
23
+ # has_many :articles, dependent: nil
21
24
  # has_many :patients, through: :appointments
22
25
  # end
26
+ #
27
+ # class User < ActiveRecord::Base
28
+ # has_many :comments
29
+ # has_one :avatar
30
+ #
31
+ # def readonly?
32
+ # true
33
+ # end
34
+ # end
23
35
  class HasManyOrHasOneDependent < Base
24
36
  MSG = 'Specify a `:dependent` option.'
25
37
  RESTRICT_ON_SEND = %i[has_many has_one].freeze
@@ -37,7 +49,7 @@ module RuboCop
37
49
  PATTERN
38
50
 
39
51
  def_node_matcher :dependent_option?, <<~PATTERN
40
- (pair (sym :dependent) !nil)
52
+ (pair (sym :dependent) {!nil (nil)})
41
53
  PATTERN
42
54
 
43
55
  def_node_matcher :present_option?, <<~PATTERN
@@ -51,8 +63,20 @@ module RuboCop
51
63
  (args) ...)
52
64
  PATTERN
53
65
 
66
+ def_node_matcher :association_extension_block?, <<~PATTERN
67
+ (block
68
+ (send nil? :has_many _)
69
+ (args) ...)
70
+ PATTERN
71
+
72
+ def_node_matcher :readonly?, <<~PATTERN
73
+ (def :readonly?
74
+ (args)
75
+ (true))
76
+ PATTERN
77
+
54
78
  def on_send(node)
55
- return if active_resource?(node.parent)
79
+ return if active_resource?(node.parent) || readonly_model?(node)
56
80
  return if !association_without_options?(node) && valid_options?(association_with_options?(node))
57
81
  return if valid_options_in_with_options_block?(node)
58
82
 
@@ -61,10 +85,16 @@ module RuboCop
61
85
 
62
86
  private
63
87
 
88
+ def readonly_model?(node)
89
+ return false unless (parent = node.parent)
90
+
91
+ parent.each_descendant(:def).any? { |def_node| readonly?(def_node) }
92
+ end
93
+
64
94
  def valid_options_in_with_options_block?(node)
65
95
  return true unless node.parent
66
96
 
67
- n = node.parent.begin_type? ? node.parent.parent : node.parent
97
+ n = node.parent.begin_type? || association_extension_block?(node.parent) ? node.parent.parent : node.parent
68
98
 
69
99
  contain_valid_options_in_with_options_block?(n)
70
100
  end