rubocop-rails 2.11.2 → 2.19.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +23 -2
  4. data/config/default.yml +316 -20
  5. data/config/obsoletion.yml +10 -0
  6. data/lib/rubocop/cop/mixin/active_record_helper.rb +17 -8
  7. data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +32 -0
  8. data/lib/rubocop/cop/mixin/class_send_node_helper.rb +20 -0
  9. data/lib/rubocop/cop/mixin/enforce_superclass.rb +1 -1
  10. data/lib/rubocop/cop/mixin/index_method.rb +7 -17
  11. data/lib/rubocop/cop/mixin/migrations_helper.rb +26 -0
  12. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +112 -0
  13. data/lib/rubocop/cop/rails/action_controller_test_case.rb +47 -0
  14. data/lib/rubocop/cop/rails/action_filter.rb +2 -2
  15. data/lib/rubocop/cop/rails/action_order.rb +116 -0
  16. data/lib/rubocop/cop/rails/active_record_aliases.rb +9 -6
  17. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +8 -13
  18. data/lib/rubocop/cop/rails/active_record_override.rb +2 -5
  19. data/lib/rubocop/cop/rails/active_support_aliases.rb +1 -1
  20. data/lib/rubocop/cop/rails/active_support_on_load.rb +70 -0
  21. data/lib/rubocop/cop/rails/add_column_index.rb +3 -6
  22. data/lib/rubocop/cop/rails/after_commit_override.rb +3 -13
  23. data/lib/rubocop/cop/rails/application_controller.rb +6 -2
  24. data/lib/rubocop/cop/rails/application_job.rb +7 -3
  25. data/lib/rubocop/cop/rails/application_mailer.rb +6 -2
  26. data/lib/rubocop/cop/rails/application_record.rb +7 -2
  27. data/lib/rubocop/cop/rails/arel_star.rb +8 -2
  28. data/lib/rubocop/cop/rails/assert_not.rb +1 -1
  29. data/lib/rubocop/cop/rails/attribute_default_block_value.rb +1 -1
  30. data/lib/rubocop/cop/rails/belongs_to.rb +2 -5
  31. data/lib/rubocop/cop/rails/blank.rb +13 -13
  32. data/lib/rubocop/cop/rails/bulk_change_table.rb +28 -31
  33. data/lib/rubocop/cop/rails/compact_blank.rb +111 -0
  34. data/lib/rubocop/cop/rails/content_tag.rb +42 -33
  35. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +16 -8
  36. data/lib/rubocop/cop/rails/date.rb +12 -17
  37. data/lib/rubocop/cop/rails/default_scope.rb +1 -1
  38. data/lib/rubocop/cop/rails/delegate.rb +24 -18
  39. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +2 -2
  40. data/lib/rubocop/cop/rails/deprecated_active_model_errors_methods.rb +168 -0
  41. data/lib/rubocop/cop/rails/dot_separated_keys.rb +71 -0
  42. data/lib/rubocop/cop/rails/duplicate_association.rb +56 -0
  43. data/lib/rubocop/cop/rails/duplicate_scope.rb +46 -0
  44. data/lib/rubocop/cop/rails/duration_arithmetic.rb +98 -0
  45. data/lib/rubocop/cop/rails/dynamic_find_by.rb +31 -15
  46. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +6 -2
  47. data/lib/rubocop/cop/rails/enum_hash.rb +3 -4
  48. data/lib/rubocop/cop/rails/enum_uniqueness.rb +3 -6
  49. data/lib/rubocop/cop/rails/environment_comparison.rb +3 -4
  50. data/lib/rubocop/cop/rails/environment_variable_access.rb +1 -1
  51. data/lib/rubocop/cop/rails/exit.rb +1 -1
  52. data/lib/rubocop/cop/rails/expanded_date_range.rb +39 -23
  53. data/lib/rubocop/cop/rails/file_path.rb +41 -24
  54. data/lib/rubocop/cop/rails/find_by.rb +1 -1
  55. data/lib/rubocop/cop/rails/find_by_id.rb +3 -3
  56. data/lib/rubocop/cop/rails/find_each.rb +27 -4
  57. data/lib/rubocop/cop/rails/freeze_time.rb +79 -0
  58. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +1 -1
  59. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +19 -9
  60. data/lib/rubocop/cop/rails/helper_instance_variable.rb +3 -3
  61. data/lib/rubocop/cop/rails/http_positional_arguments.rb +29 -11
  62. data/lib/rubocop/cop/rails/http_status.rb +6 -11
  63. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +96 -0
  64. data/lib/rubocop/cop/rails/i18n_locale_assignment.rb +1 -1
  65. data/lib/rubocop/cop/rails/i18n_locale_texts.rb +110 -0
  66. data/lib/rubocop/cop/rails/ignored_columns_assignment.rb +50 -0
  67. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +5 -14
  68. data/lib/rubocop/cop/rails/index_by.rb +8 -8
  69. data/lib/rubocop/cop/rails/index_with.rb +8 -8
  70. data/lib/rubocop/cop/rails/inquiry.rb +1 -1
  71. data/lib/rubocop/cop/rails/inverse_of.rb +20 -10
  72. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +44 -16
  73. data/lib/rubocop/cop/rails/link_to_blank.rb +4 -7
  74. data/lib/rubocop/cop/rails/mailer_name.rb +9 -5
  75. data/lib/rubocop/cop/rails/match_route.rb +1 -1
  76. data/lib/rubocop/cop/rails/migration_class_name.rb +63 -0
  77. data/lib/rubocop/cop/rails/negate_include.rb +5 -4
  78. data/lib/rubocop/cop/rails/not_null_column.rb +10 -7
  79. data/lib/rubocop/cop/rails/order_by_id.rb +2 -3
  80. data/lib/rubocop/cop/rails/output.rb +26 -10
  81. data/lib/rubocop/cop/rails/output_safety.rb +6 -2
  82. data/lib/rubocop/cop/rails/pick.rb +8 -1
  83. data/lib/rubocop/cop/rails/pluck.rb +51 -11
  84. data/lib/rubocop/cop/rails/pluck_id.rb +5 -2
  85. data/lib/rubocop/cop/rails/pluck_in_where.rb +8 -7
  86. data/lib/rubocop/cop/rails/pluralization_grammar.rb +4 -5
  87. data/lib/rubocop/cop/rails/presence.rb +22 -13
  88. data/lib/rubocop/cop/rails/present.rb +10 -13
  89. data/lib/rubocop/cop/rails/rake_environment.rb +8 -3
  90. data/lib/rubocop/cop/rails/read_write_attribute.rb +52 -15
  91. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +7 -17
  92. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +3 -3
  93. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +257 -0
  94. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +31 -27
  95. data/lib/rubocop/cop/rails/redundant_travel_back.rb +57 -0
  96. data/lib/rubocop/cop/rails/reflection_class_name.rb +39 -4
  97. data/lib/rubocop/cop/rails/refute_methods.rb +1 -5
  98. data/lib/rubocop/cop/rails/relative_date_constant.rb +9 -9
  99. data/lib/rubocop/cop/rails/render_inline.rb +1 -1
  100. data/lib/rubocop/cop/rails/render_plain_text.rb +1 -1
  101. data/lib/rubocop/cop/rails/request_referer.rb +2 -3
  102. data/lib/rubocop/cop/rails/require_dependency.rb +2 -2
  103. data/lib/rubocop/cop/rails/response_parsed_body.rb +57 -0
  104. data/lib/rubocop/cop/rails/reversible_migration.rb +29 -67
  105. data/lib/rubocop/cop/rails/reversible_migration_method_definition.rb +6 -15
  106. data/lib/rubocop/cop/rails/root_join_chain.rb +72 -0
  107. data/lib/rubocop/cop/rails/root_pathname_methods.rb +238 -0
  108. data/lib/rubocop/cop/rails/root_public_path.rb +59 -0
  109. data/lib/rubocop/cop/rails/safe_navigation.rb +8 -13
  110. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +14 -7
  111. data/lib/rubocop/cop/rails/save_bang.rb +30 -23
  112. data/lib/rubocop/cop/rails/schema_comment.rb +104 -0
  113. data/lib/rubocop/cop/rails/scope_args.rb +6 -2
  114. data/lib/rubocop/cop/rails/short_i18n.rb +3 -6
  115. data/lib/rubocop/cop/rails/skips_model_validations.rb +3 -4
  116. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +13 -8
  117. data/lib/rubocop/cop/rails/strip_heredoc.rb +56 -0
  118. data/lib/rubocop/cop/rails/table_name_assignment.rb +44 -0
  119. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +73 -0
  120. data/lib/rubocop/cop/rails/time_zone.rb +37 -32
  121. data/lib/rubocop/cop/rails/time_zone_assignment.rb +4 -4
  122. data/lib/rubocop/cop/rails/to_formatted_s.rb +46 -0
  123. data/lib/rubocop/cop/rails/to_s_with_argument.rb +78 -0
  124. data/lib/rubocop/cop/rails/top_level_hash_with_indifferent_access.rb +49 -0
  125. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +99 -0
  126. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +32 -41
  127. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +16 -9
  128. data/lib/rubocop/cop/rails/unknown_env.rb +3 -5
  129. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +9 -2
  130. data/lib/rubocop/cop/rails/validation.rb +5 -13
  131. data/lib/rubocop/cop/rails/where_equals.rb +6 -2
  132. data/lib/rubocop/cop/rails/where_exists.rb +11 -10
  133. data/lib/rubocop/cop/rails/where_missing.rb +118 -0
  134. data/lib/rubocop/cop/rails/where_not.rb +2 -2
  135. data/lib/rubocop/cop/rails/where_not_with_multiple_conditions.rb +55 -0
  136. data/lib/rubocop/cop/rails_cops.rb +34 -0
  137. data/lib/rubocop/rails/schema_loader/schema.rb +8 -5
  138. data/lib/rubocop/rails/version.rb +1 -1
  139. data/lib/rubocop/rails.rb +1 -1
  140. data/lib/rubocop-rails.rb +19 -0
  141. metadata +42 -8
  142. data/bin/setup +0 -7
@@ -3,8 +3,12 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop is used to identify usages of `all.each` and
7
- # change them to use `all.find_each` instead.
6
+ # Identifies usages of `all.each` and change them to use `all.find_each` instead.
7
+ #
8
+ # @safety
9
+ # This cop is unsafe if the receiver object is not an Active Record object.
10
+ # Also, `all.each` returns an `Array` instance and `all.find_each` returns nil,
11
+ # so the return values are different.
8
12
  #
9
13
  # @example
10
14
  # # bad
@@ -13,11 +17,17 @@ module RuboCop
13
17
  # # good
14
18
  # User.all.find_each
15
19
  #
16
- # @example IgnoredMethods: ['order']
20
+ # @example AllowedMethods: ['order']
21
+ # # good
22
+ # User.order(:foo).each
23
+ #
24
+ # @example AllowedPattern: ['order']
17
25
  # # good
18
26
  # User.order(:foo).each
19
27
  class FindEach < Base
20
28
  include ActiveRecordHelper
29
+ include AllowedMethods
30
+ include AllowedPattern
21
31
  extend AutoCorrector
22
32
 
23
33
  MSG = 'Use `find_each` instead of `each`.'
@@ -43,8 +53,21 @@ module RuboCop
43
53
  private
44
54
 
45
55
  def ignored?(node)
56
+ return true if active_model_error_where?(node.receiver)
57
+
46
58
  method_chain = node.each_node(:send).map(&:method_name)
47
- (cop_config['IgnoredMethods'].map(&:to_sym) & method_chain).any?
59
+
60
+ method_chain.any? { |method_name| allowed_method?(method_name) || matches_allowed_pattern?(method_name) }
61
+ end
62
+
63
+ def active_model_error_where?(node)
64
+ node.method?(:where) && active_model_error?(node.receiver)
65
+ end
66
+
67
+ def active_model_error?(node)
68
+ return false if node.nil?
69
+
70
+ node.send_type? && node.method?(:errors)
48
71
  end
49
72
  end
50
73
  end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Identifies usages of `travel_to` with an argument of the current time and
7
+ # change them to use `freeze_time` instead.
8
+ #
9
+ # @safety
10
+ # This cop’s autocorrection is unsafe because `freeze_time` just delegates to
11
+ # `travel_to` with a default `Time.now`, it is not strictly equivalent to `Time.now`
12
+ # if the argument of `travel_to` is the current time considering time zone.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # travel_to(Time.now)
17
+ # travel_to(Time.new)
18
+ # travel_to(DateTime.now)
19
+ # travel_to(Time.current)
20
+ # travel_to(Time.zone.now)
21
+ # travel_to(Time.now.in_time_zone)
22
+ # travel_to(Time.current.to_time)
23
+ #
24
+ # # good
25
+ # freeze_time
26
+ #
27
+ class FreezeTime < Base
28
+ extend AutoCorrector
29
+ extend TargetRailsVersion
30
+
31
+ minimum_target_rails_version 5.2
32
+
33
+ MSG = 'Use `freeze_time` instead of `travel_to`.'
34
+ NOW_METHODS = %i[now new current].freeze
35
+ CONVERT_METHODS = %i[to_time in_time_zone].freeze
36
+ RESTRICT_ON_SEND = %i[travel_to].freeze
37
+
38
+ # @!method time_now?(node)
39
+ def_node_matcher :time_now?, <<~PATTERN
40
+ (const {nil? cbase} {:Time :DateTime})
41
+ PATTERN
42
+
43
+ # @!method zoned_time_now?(node)
44
+ def_node_matcher :zoned_time_now?, <<~PATTERN
45
+ (send (const {nil? cbase} :Time) :zone)
46
+ PATTERN
47
+
48
+ def on_send(node)
49
+ child_node, method_name, time_argument = *node.first_argument&.children
50
+ return if time_argument || !child_node
51
+ return unless current_time?(child_node, method_name) || current_time_with_convert?(child_node, method_name)
52
+
53
+ add_offense(node) do |corrector|
54
+ last_argument = node.last_argument
55
+ freeze_time_method = last_argument.block_pass_type? ? "freeze_time(#{last_argument.source})" : 'freeze_time'
56
+ corrector.replace(node, freeze_time_method)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def current_time?(node, method_name)
63
+ return false unless NOW_METHODS.include?(method_name)
64
+
65
+ node.send_type? ? zoned_time_now?(node) : time_now?(node)
66
+ end
67
+
68
+ def current_time_with_convert?(node, method_name)
69
+ return false unless CONVERT_METHODS.include?(method_name)
70
+
71
+ child_node, child_method_name, time_argument = *node.children
72
+ return if time_argument
73
+
74
+ current_time?(child_node, child_method_name)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for the use of the has_and_belongs_to_many macro.
6
+ # Checks for the use of the has_and_belongs_to_many macro.
7
7
  #
8
8
  # @example
9
9
  # # bad
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop looks for `has_many` or `has_one` associations that don't
6
+ # Looks for `has_many` or `has_one` associations that don't
7
7
  # specify a `:dependent` option.
8
8
  #
9
9
  # It doesn't register an offense if `:through` or `dependent: nil`
@@ -37,15 +37,15 @@ module RuboCop
37
37
  RESTRICT_ON_SEND = %i[has_many has_one].freeze
38
38
 
39
39
  def_node_search :active_resource_class?, <<~PATTERN
40
- (const (const nil? :ActiveResource) :Base)
40
+ (const (const {nil? cbase} :ActiveResource) :Base)
41
41
  PATTERN
42
42
 
43
43
  def_node_matcher :association_without_options?, <<~PATTERN
44
- (send nil? {:has_many :has_one} _)
44
+ (send _ {:has_many :has_one} _)
45
45
  PATTERN
46
46
 
47
47
  def_node_matcher :association_with_options?, <<~PATTERN
48
- (send nil? {:has_many :has_one} _ (hash $...))
48
+ (send _ {:has_many :has_one} ... (hash $...))
49
49
  PATTERN
50
50
 
51
51
  def_node_matcher :dependent_option?, <<~PATTERN
@@ -100,20 +100,22 @@ module RuboCop
100
100
  end
101
101
 
102
102
  def contain_valid_options_in_with_options_block?(node)
103
- if with_options_block(node)
104
- return true if valid_options?(with_options_block(node))
103
+ if (options = with_options_block(node))
104
+ return true if valid_options?(options)
105
105
 
106
106
  return false unless node.parent
107
107
 
108
- return true if contain_valid_options_in_with_options_block?(
109
- node.parent.parent
110
- )
108
+ return true if contain_valid_options_in_with_options_block?(node.parent.parent)
111
109
  end
112
110
 
113
111
  false
114
112
  end
115
113
 
116
114
  def valid_options?(options)
115
+ return false if options.nil?
116
+
117
+ options = extract_option_if_kwsplat(options)
118
+
117
119
  return true unless options
118
120
  return true if options.any? do |o|
119
121
  dependent_option?(o) || present_option?(o)
@@ -122,6 +124,14 @@ module RuboCop
122
124
  false
123
125
  end
124
126
 
127
+ def extract_option_if_kwsplat(options)
128
+ if options.first.kwsplat_type? && options.first.children.first.hash_type?
129
+ return options.first.children.first.pairs
130
+ end
131
+
132
+ options
133
+ end
134
+
125
135
  def active_resource?(node)
126
136
  return false if node.nil?
127
137
 
@@ -3,14 +3,14 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for use of the helper methods which reference
6
+ # Checks for use of the helper methods which reference
7
7
  # instance variables.
8
8
  #
9
9
  # Relying on instance variables makes it difficult to re-use helper
10
10
  # methods.
11
11
  #
12
12
  # If it seems awkward to explicitly pass in each dependent
13
- # variable, consider moving the behaviour elsewhere, for
13
+ # variable, consider moving the behavior elsewhere, for
14
14
  # example to a model, decorator or presenter.
15
15
  #
16
16
  # Provided that a class inherits `ActionView::Helpers::FormBuilder`,
@@ -37,7 +37,7 @@ module RuboCop
37
37
  def_node_matcher :form_builder_class?, <<~PATTERN
38
38
  (const
39
39
  (const
40
- (const nil? :ActionView) :Helpers) :FormBuilder)
40
+ (const {nil? cbase} :ActionView) :Helpers) :FormBuilder)
41
41
  PATTERN
42
42
 
43
43
  def on_ivar(node)
@@ -3,13 +3,16 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop is used to identify usages of http methods like `get`, `post`,
6
+ # Identifies usages of http methods like `get`, `post`,
7
7
  # `put`, `patch` without the usage of keyword arguments in your tests and
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
11
  # .rubocop.yml file to 4.2.
12
12
  #
13
+ # NOTE: It does not detect any cases where `include Rack::Test::Methods` is used
14
+ # which makes the http methods incompatible behavior.
15
+ #
13
16
  # @example
14
17
  # # bad
15
18
  # get :new, { user_id: 1}
@@ -22,11 +25,9 @@ module RuboCop
22
25
  extend AutoCorrector
23
26
  extend TargetRailsVersion
24
27
 
25
- MSG = 'Use keyword arguments instead of ' \
26
- 'positional arguments for http call: `%<verb>s`.'
27
- KEYWORD_ARGS = %i[
28
- method params session body flash xhr as headers env to
29
- ].freeze
28
+ MSG = 'Use keyword arguments instead of positional arguments for http call: `%<verb>s`.'
29
+ KEYWORD_ARGS = %i[method params session body flash xhr as headers env to].freeze
30
+ ROUTING_METHODS = %i[draw routes].freeze
30
31
  RESTRICT_ON_SEND = %i[get post put patch delete head].freeze
31
32
 
32
33
  minimum_target_rails_version 5.0
@@ -39,7 +40,16 @@ module RuboCop
39
40
  (hash (kwsplat _))
40
41
  PATTERN
41
42
 
43
+ def_node_matcher :include_rack_test_methods?, <<~PATTERN
44
+ (send nil? :include
45
+ (const
46
+ (const
47
+ (const {nil? cbase} :Rack) :Test) :Methods))
48
+ PATTERN
49
+
42
50
  def on_send(node)
51
+ return if in_routing_block?(node) || use_rack_test_methods?
52
+
43
53
  http_request?(node) do |data|
44
54
  return unless needs_conversion?(data)
45
55
 
@@ -56,20 +66,29 @@ module RuboCop
56
66
  # that represents the path/action on the Rails controller
57
67
  # the data is the http parameters and environment sent in
58
68
  # the Rails 5 http call
59
- corrector.replace(node.loc.expression, correction(node))
69
+ corrector.replace(node, correction(node))
60
70
  end
61
71
  end
62
72
  end
63
73
 
64
74
  private
65
75
 
76
+ def in_routing_block?(node)
77
+ !!node.each_ancestor(:block).detect { |block| ROUTING_METHODS.include?(block.method_name) }
78
+ end
79
+
80
+ def use_rack_test_methods?
81
+ processed_source.ast.each_descendant(:send).any? do |node|
82
+ include_rack_test_methods?(node)
83
+ end
84
+ end
85
+
66
86
  def needs_conversion?(data)
67
87
  return true unless data.hash_type?
68
88
  return false if kwsplat_hash?(data)
69
89
 
70
90
  data.each_pair.none? do |pair|
71
- special_keyword_arg?(pair.key) ||
72
- format_arg?(pair.key) && data.pairs.one?
91
+ special_keyword_arg?(pair.key) || (format_arg?(pair.key) && data.pairs.one?)
73
92
  end
74
93
  end
75
94
 
@@ -91,8 +110,7 @@ module RuboCop
91
110
  return '' if data.hash_type? && data.empty?
92
111
 
93
112
  hash_data = if data.hash_type?
94
- format('{ %<data>s }',
95
- data: data.pairs.map(&:source).join(', '))
113
+ format('{ %<data>s }', data: data.pairs.map(&:source).join(', '))
96
114
  else
97
115
  # user supplies an object,
98
116
  # no need to surround with braces
@@ -66,7 +66,7 @@ module RuboCop
66
66
  return unless checker.offensive?
67
67
 
68
68
  add_offense(checker.node, message: checker.message) do |corrector|
69
- corrector.replace(checker.node.loc.expression, checker.preferred_style)
69
+ corrector.replace(checker.node, checker.preferred_style)
70
70
  end
71
71
  end
72
72
  end
@@ -84,10 +84,8 @@ module RuboCop
84
84
 
85
85
  # :nodoc:
86
86
  class SymbolicStyleChecker
87
- MSG = 'Prefer `%<prefer>s` over `%<current>s` ' \
88
- 'to define HTTP status code.'
89
- DEFAULT_MSG = 'Prefer `symbolic` over `numeric` ' \
90
- 'to define HTTP status code.'
87
+ MSG = 'Prefer `%<prefer>s` over `%<current>s` to define HTTP status code.'
88
+ DEFAULT_MSG = 'Prefer `symbolic` over `numeric` to define HTTP status code.'
91
89
 
92
90
  attr_reader :node
93
91
 
@@ -118,17 +116,14 @@ module RuboCop
118
116
  end
119
117
 
120
118
  def custom_http_status_code?
121
- node.int_type? &&
122
- !::Rack::Utils::SYMBOL_TO_STATUS_CODE.value?(number)
119
+ node.int_type? && !::Rack::Utils::SYMBOL_TO_STATUS_CODE.value?(number)
123
120
  end
124
121
  end
125
122
 
126
123
  # :nodoc:
127
124
  class NumericStyleChecker
128
- MSG = 'Prefer `%<prefer>s` over `%<current>s` ' \
129
- 'to define HTTP status code.'
130
- DEFAULT_MSG = 'Prefer `numeric` over `symbolic` ' \
131
- 'to define HTTP status code.'
125
+ MSG = 'Prefer `%<prefer>s` over `%<current>s` to define HTTP status code.'
126
+ DEFAULT_MSG = 'Prefer `numeric` over `symbolic` to define HTTP status code.'
132
127
  PERMITTED_STATUS = %i[error success missing redirect].freeze
133
128
 
134
129
  attr_reader :node
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Checks for places where I18n "lazy" lookup can be used.
7
+ #
8
+ # @example
9
+ # # en.yml
10
+ # # en:
11
+ # # books:
12
+ # # create:
13
+ # # success: Book created!
14
+ #
15
+ # # bad
16
+ # class BooksController < ApplicationController
17
+ # def create
18
+ # # ...
19
+ # redirect_to books_url, notice: t('books.create.success')
20
+ # end
21
+ # end
22
+ #
23
+ # # good
24
+ # class BooksController < ApplicationController
25
+ # def create
26
+ # # ...
27
+ # redirect_to books_url, notice: t('.success')
28
+ # end
29
+ # end
30
+ #
31
+ class I18nLazyLookup < Base
32
+ include VisibilityHelp
33
+ extend AutoCorrector
34
+
35
+ MSG = 'Use "lazy" lookup for the text used in controllers.'
36
+
37
+ RESTRICT_ON_SEND = %i[translate t].freeze
38
+
39
+ def_node_matcher :translate_call?, <<~PATTERN
40
+ (send nil? {:translate :t} ${sym_type? str_type?} ...)
41
+ PATTERN
42
+
43
+ def on_send(node)
44
+ translate_call?(node) do |key_node|
45
+ key = key_node.value
46
+ return if key.to_s.start_with?('.')
47
+
48
+ controller, action = controller_and_action(node)
49
+ return unless controller && action
50
+
51
+ scoped_key = get_scoped_key(key_node, controller, action)
52
+ return unless key == scoped_key
53
+
54
+ add_offense(key_node) do |corrector|
55
+ unscoped_key = key_node.value.to_s.split('.').last
56
+ corrector.replace(key_node, "'.#{unscoped_key}'")
57
+ end
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def controller_and_action(node)
64
+ action_node = node.each_ancestor(:def).first
65
+ return unless action_node && node_visibility(action_node) == :public
66
+
67
+ controller_node = node.each_ancestor(:class).first
68
+ return unless controller_node && controller_node.identifier.source.end_with?('Controller')
69
+
70
+ [controller_node, action_node]
71
+ end
72
+
73
+ def get_scoped_key(key_node, controller, action)
74
+ path = controller_path(controller).tr('/', '.')
75
+ action_name = action.method_name
76
+ key = key_node.value.to_s.split('.').last
77
+
78
+ "#{path}.#{action_name}.#{key}"
79
+ end
80
+
81
+ def controller_path(controller)
82
+ module_name = controller.parent_module_name
83
+ controller_name = controller.identifier.source
84
+
85
+ path = if module_name == 'Object'
86
+ controller_name
87
+ else
88
+ "#{module_name}::#{controller_name}"
89
+ end
90
+
91
+ path.delete_suffix('Controller').underscore
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for the use of `I18n.locale=` method.
6
+ # Checks for the use of `I18n.locale=` method.
7
7
  #
8
8
  # The `locale` attribute persists for the rest of the Ruby runtime, potentially causing
9
9
  # unexpected behavior at a later time.
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Enforces use of I18n and locale files instead of locale specific strings.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # class User < ApplicationRecord
11
+ # validates :email, presence: { message: "must be present" }
12
+ # end
13
+ #
14
+ # # good
15
+ # # config/locales/en.yml
16
+ # # en:
17
+ # # activerecord:
18
+ # # errors:
19
+ # # models:
20
+ # # user:
21
+ # # blank: "must be present"
22
+ #
23
+ # class User < ApplicationRecord
24
+ # validates :email, presence: true
25
+ # end
26
+ #
27
+ # # bad
28
+ # class PostsController < ApplicationController
29
+ # def create
30
+ # # ...
31
+ # redirect_to root_path, notice: "Post created!"
32
+ # end
33
+ # end
34
+ #
35
+ # # good
36
+ # # config/locales/en.yml
37
+ # # en:
38
+ # # posts:
39
+ # # create:
40
+ # # success: "Post created!"
41
+ #
42
+ # class PostsController < ApplicationController
43
+ # def create
44
+ # # ...
45
+ # redirect_to root_path, notice: t(".success")
46
+ # end
47
+ # end
48
+ #
49
+ # # bad
50
+ # class UserMailer < ApplicationMailer
51
+ # def welcome(user)
52
+ # mail(to: user.email, subject: "Welcome to My Awesome Site")
53
+ # end
54
+ # end
55
+ #
56
+ # # good
57
+ # # config/locales/en.yml
58
+ # # en:
59
+ # # user_mailer:
60
+ # # welcome:
61
+ # # subject: "Welcome to My Awesome Site"
62
+ #
63
+ # class UserMailer < ApplicationMailer
64
+ # def welcome(user)
65
+ # mail(to: user.email)
66
+ # end
67
+ # end
68
+ #
69
+ class I18nLocaleTexts < Base
70
+ MSG = 'Move locale texts to the locale files in the `config/locales` directory.'
71
+
72
+ RESTRICT_ON_SEND = %i[validates redirect_to redirect_back []= mail].freeze
73
+
74
+ def_node_search :validation_message, <<~PATTERN
75
+ (pair (sym :message) $str)
76
+ PATTERN
77
+
78
+ def_node_search :redirect_to_flash, <<~PATTERN
79
+ (pair (sym {:notice :alert}) $str)
80
+ PATTERN
81
+
82
+ def_node_matcher :flash_assignment?, <<~PATTERN
83
+ (send (send nil? :flash) :[]= _ $str)
84
+ PATTERN
85
+
86
+ def_node_search :mail_subject, <<~PATTERN
87
+ (pair (sym :subject) $str)
88
+ PATTERN
89
+
90
+ def on_send(node)
91
+ case node.method_name
92
+ when :validates
93
+ validation_message(node) do |text_node|
94
+ add_offense(text_node)
95
+ end
96
+ return
97
+ when :redirect_to, :redirect_back
98
+ text_node = redirect_to_flash(node).to_a.last
99
+ when :[]=
100
+ text_node = flash_assignment?(node)
101
+ when :mail
102
+ text_node = mail_subject(node).to_a.last
103
+ end
104
+
105
+ add_offense(text_node) if text_node
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Looks for assignments of `ignored_columns` that may override previous
7
+ # assignments.
8
+ #
9
+ # Overwriting previous assignments is usually a mistake, since it will
10
+ # un-ignore the first set of columns. Since duplicate column names is not
11
+ # a problem, it is better to simply append to the list.
12
+ #
13
+ # @example
14
+ #
15
+ # # bad
16
+ # class User < ActiveRecord::Base
17
+ # self.ignored_columns = [:one]
18
+ # end
19
+ #
20
+ # # bad
21
+ # class User < ActiveRecord::Base
22
+ # self.ignored_columns = [:one, :two]
23
+ # end
24
+ #
25
+ # # good
26
+ # class User < ActiveRecord::Base
27
+ # self.ignored_columns += [:one, :two]
28
+ # end
29
+ #
30
+ # # good
31
+ # class User < ActiveRecord::Base
32
+ # self.ignored_columns += [:one]
33
+ # self.ignored_columns += [:two]
34
+ # end
35
+ #
36
+ class IgnoredColumnsAssignment < Base
37
+ extend AutoCorrector
38
+
39
+ MSG = 'Use `+=` instead of `=`.'
40
+ RESTRICT_ON_SEND = %i[ignored_columns=].freeze
41
+
42
+ def on_send(node)
43
+ add_offense(node.loc.operator) do |corrector|
44
+ corrector.replace(node.loc.operator, '+=')
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end