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,12 +3,19 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop enforces the use of `pick` over `pluck(...).first`.
6
+ # Enforces the use of `pick` over `pluck(...).first`.
7
7
  #
8
8
  # Using `pluck` followed by `first` creates an intermediate array, which
9
9
  # `pick` avoids. When called on an Active Record relation, `pick` adds a
10
10
  # limit to the query so that only one value is fetched from the database.
11
11
  #
12
+ # @safety
13
+ # This cop is unsafe because `pluck` is defined on both `ActiveRecord::Relation` and `Enumerable`,
14
+ # whereas `pick` is only defined on `ActiveRecord::Relation` in Rails 6.0. This was addressed
15
+ # in Rails 6.1 via rails/rails#38760, at which point the cop is safe.
16
+ #
17
+ # See: https://github.com/rubocop/rubocop-rails/pull/249
18
+ #
12
19
  # @example
13
20
  # # bad
14
21
  # Model.pluck(:a).first
@@ -3,12 +3,24 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop enforces the use of `pluck` over `map`.
6
+ # Enforces the use of `pluck` over `map`.
7
7
  #
8
8
  # `pluck` can be used instead of `map` to extract a single key from each
9
9
  # element in an enumerable. When called on an Active Record relation, it
10
10
  # results in a more efficient query that only selects the necessary key.
11
11
  #
12
+ # @safety
13
+ # This cop is unsafe because model can use column aliases.
14
+ #
15
+ # [source,ruby]
16
+ # ----
17
+ # # Original code
18
+ # User.select('name AS nickname').map { |user| user[:nickname] } # => array of nicknames
19
+ #
20
+ # # After autocorrection
21
+ # User.select('name AS nickname').pluck(:nickname) # => raises ActiveRecord::StatementInvalid
22
+ # ----
23
+ #
12
24
  # @example
13
25
  # # bad
14
26
  # Post.published.map { |post| post[:title] }
@@ -21,34 +33,62 @@ module RuboCop
21
33
  extend AutoCorrector
22
34
  extend TargetRailsVersion
23
35
 
24
- MSG = 'Prefer `pluck(:%<value>s)` over `%<method>s { |%<argument>s| %<element>s[:%<value>s] }`.'
36
+ MSG = 'Prefer `%<replacement>s` over `%<current>s`.'
25
37
 
26
38
  minimum_target_rails_version 5.0
27
39
 
28
40
  def_node_matcher :pluck_candidate?, <<~PATTERN
29
- (block (send _ ${:map :collect}) (args (arg $_argument)) (send (lvar $_element) :[] (sym $_value)))
41
+ ({block numblock} (send _ {:map :collect}) $_argument (send lvar :[] $_key))
30
42
  PATTERN
31
43
 
32
44
  def on_block(node)
33
- pluck_candidate?(node) do |method, argument, element, value|
34
- next unless argument == element
45
+ pluck_candidate?(node) do |argument, key|
46
+ next if key.regexp_type? || !use_one_block_argument?(argument)
35
47
 
36
- message = message(method, argument, element, value)
48
+ match = if node.block_type?
49
+ block_argument = argument.children.first.source
50
+ use_block_argument_in_key?(block_argument, key)
51
+ else # numblock
52
+ argument == 1 && use_block_argument_in_key?('_1', key)
53
+ end
54
+ next unless match
37
55
 
38
- add_offense(offense_range(node), message: message) do |corrector|
39
- corrector.replace(offense_range(node), "pluck(:#{value})")
40
- end
56
+ register_offense(node, key)
41
57
  end
42
58
  end
59
+ alias on_numblock on_block
43
60
 
44
61
  private
45
62
 
63
+ def use_one_block_argument?(argument)
64
+ return true if argument == 1 # Checks for numbered argument `_1`.
65
+
66
+ argument.respond_to?(:one?) && argument.one?
67
+ end
68
+
69
+ def use_block_argument_in_key?(block_argument, key)
70
+ return false if block_argument == key.source
71
+
72
+ key.each_descendant(:lvar).none? { |lvar| block_argument == lvar.source }
73
+ end
74
+
46
75
  def offense_range(node)
47
76
  node.send_node.loc.selector.join(node.loc.end)
48
77
  end
49
78
 
50
- def message(method, argument, element, value)
51
- format(MSG, method: method, argument: argument, element: element, value: value)
79
+ def register_offense(node, key)
80
+ replacement = "pluck(#{key.source})"
81
+ message = message(replacement, node)
82
+
83
+ add_offense(offense_range(node), message: message) do |corrector|
84
+ corrector.replace(offense_range(node), replacement)
85
+ end
86
+ end
87
+
88
+ def message(replacement, node)
89
+ current = offense_range(node).source
90
+
91
+ format(MSG, replacement: replacement, current: current)
52
92
  end
53
93
  end
54
94
  end
@@ -3,7 +3,10 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop enforces the use of `ids` over `pluck(:id)` and `pluck(primary_key)`.
6
+ # Enforces the use of `ids` over `pluck(:id)` and `pluck(primary_key)`.
7
+ #
8
+ # @safety
9
+ # This cop is unsafe if the receiver object is not an Active Record object.
7
10
  #
8
11
  # @example
9
12
  # # bad
@@ -48,7 +51,7 @@ module RuboCop
48
51
  private
49
52
 
50
53
  def offense_range(node)
51
- range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
54
+ range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
52
55
  end
53
56
  end
54
57
  end
@@ -3,20 +3,21 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop identifies places where `pluck` is used in `where` query methods
6
+ # Identifies places where `pluck` is used in `where` query methods
7
7
  # and can be replaced with `select`.
8
8
  #
9
9
  # Since `pluck` is an eager method and hits the database immediately,
10
10
  # using `select` helps to avoid additional database queries.
11
11
  #
12
- # This cop has two different enforcement modes. When the EnforcedStyle
13
- # is conservative (the default) then only calls to `pluck` on a constant
12
+ # This cop has two different enforcement modes. When the `EnforcedStyle`
13
+ # is `conservative` (the default) then only calls to `pluck` on a constant
14
14
  # (i.e. a model class) in the `where` is used as offenses.
15
15
  #
16
- # When the EnforcedStyle is aggressive then all calls to `pluck` in the
17
- # `where` is used as offenses. This may lead to false positives
18
- # as the cop cannot replace to `select` between calls to `pluck` on an
19
- # `ActiveRecord::Relation` instance vs a call to `pluck` on an `Array` instance.
16
+ # @safety
17
+ # When the `EnforcedStyle` is `aggressive` then all calls to `pluck` in the
18
+ # `where` is used as offenses. This may lead to false positives
19
+ # as the cop cannot replace to `select` between calls to `pluck` on an
20
+ # `ActiveRecord::Relation` instance vs a call to `pluck` on an `Array` instance.
20
21
  #
21
22
  # @example
22
23
  # # bad
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for correct grammar when using ActiveSupport's
6
+ # Checks for correct grammar when using ActiveSupport's
7
7
  # core extensions to the numeric classes.
8
8
  #
9
9
  # @example
@@ -61,8 +61,8 @@ module RuboCop
61
61
  def offense?(node)
62
62
  number, = *node.receiver
63
63
 
64
- singular_receiver?(number) && plural_method?(node.method_name) ||
65
- plural_receiver?(number) && singular_method?(node.method_name)
64
+ (singular_receiver?(number) && plural_method?(node.method_name)) ||
65
+ (plural_receiver?(number) && singular_method?(node.method_name))
66
66
  end
67
67
 
68
68
  def plural_method?(method_name)
@@ -94,8 +94,7 @@ module RuboCop
94
94
  end
95
95
 
96
96
  def duration_method?(method_name)
97
- SINGULAR_DURATION_METHODS.key?(method_name) ||
98
- PLURAL_DURATION_METHODS.key?(method_name)
97
+ SINGULAR_DURATION_METHODS.key?(method_name) || PLURAL_DURATION_METHODS.key?(method_name)
99
98
  end
100
99
  end
101
100
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks code that can be written more easily using
6
+ # Checks code that can be written more easily using
7
7
  # `Object#presence` defined by Active Support.
8
8
  #
9
9
  # @example
@@ -93,7 +93,7 @@ module RuboCop
93
93
 
94
94
  def register_offense(node, receiver, other)
95
95
  add_offense(node, message: message(node, receiver, other)) do |corrector|
96
- corrector.replace(node.source_range, replacement(receiver, other))
96
+ corrector.replace(node, replacement(receiver, other, node.left_sibling))
97
97
  end
98
98
  end
99
99
 
@@ -106,12 +106,20 @@ module RuboCop
106
106
  end
107
107
 
108
108
  def message(node, receiver, other)
109
- format(MSG,
110
- prefer: replacement(receiver, other),
111
- current: node.source)
109
+ prefer = replacement(receiver, other, node.left_sibling).gsub(/^\s*|\n/, '')
110
+ current = current(node).gsub(/^\s*|\n/, '')
111
+ format(MSG, prefer: prefer, current: current)
112
112
  end
113
113
 
114
- def replacement(receiver, other)
114
+ def current(node)
115
+ if !node.ternary? && node.source.include?("\n")
116
+ "#{node.loc.keyword.with(end_pos: node.condition.loc.selector.end_pos).source} ... end"
117
+ else
118
+ node.source.gsub(/\n\s*/, ' ')
119
+ end
120
+ end
121
+
122
+ def replacement(receiver, other, left_sibling)
115
123
  or_source = if other&.send_type?
116
124
  build_source_for_or_method(other)
117
125
  elsif other.nil? || other.nil_type?
@@ -120,23 +128,24 @@ module RuboCop
120
128
  " || #{other.source}"
121
129
  end
122
130
 
123
- "#{receiver.source}.presence" + or_source
131
+ replaced = "#{receiver.source}.presence#{or_source}"
132
+ left_sibling ? "(#{replaced})" : replaced
124
133
  end
125
134
 
126
135
  def build_source_for_or_method(other)
127
- if other.parenthesized? || other.method?('[]') || !other.arguments?
136
+ if other.parenthesized? || other.method?('[]') || other.arithmetic_operation? || !other.arguments?
128
137
  " || #{other.source}"
129
138
  else
130
- method = range_between(
131
- other.source_range.begin_pos,
132
- other.first_argument.source_range.begin_pos - 1
133
- ).source
134
-
139
+ method = method_range(other).source
135
140
  arguments = other.arguments.map(&:source).join(', ')
136
141
 
137
142
  " || #{method}(#{arguments})"
138
143
  end
139
144
  end
145
+
146
+ def method_range(node)
147
+ range_between(node.source_range.begin_pos, node.first_argument.source_range.begin_pos - 1)
148
+ end
140
149
  end
141
150
  end
142
151
  end
@@ -3,13 +3,13 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for code that can be written with simpler conditionals
6
+ # Checks for code that can be written with simpler conditionals
7
7
  # using `Object#present?` defined by Active Support.
8
8
  #
9
9
  # Interaction with `Style/UnlessElse`:
10
10
  # The configuration of `NotBlank` will not produce an offense in the
11
- # context of `unless else` if `Style/UnlessElse` is inabled. This is
12
- # to prevent interference between the auto-correction of the two cops.
11
+ # context of `unless else` if `Style/UnlessElse` is enabled. This is
12
+ # to prevent interference between the autocorrection of the two cops.
13
13
  #
14
14
  # @example NotNilAndNotEmpty: true (default)
15
15
  # # Converts usages of `!nil? && !empty?` to `present?`
@@ -47,10 +47,8 @@ module RuboCop
47
47
  extend AutoCorrector
48
48
 
49
49
  MSG_NOT_BLANK = 'Use `%<prefer>s` instead of `%<current>s`.'
50
- MSG_EXISTS_AND_NOT_EMPTY = 'Use `%<prefer>s` instead of ' \
51
- '`%<current>s`.'
52
- MSG_UNLESS_BLANK = 'Use `if %<prefer>s` instead of ' \
53
- '`%<current>s`.'
50
+ MSG_EXISTS_AND_NOT_EMPTY = 'Use `%<prefer>s` instead of `%<current>s`.'
51
+ MSG_UNLESS_BLANK = 'Use `if %<prefer>s` instead of `%<current>s`.'
54
52
  RESTRICT_ON_SEND = %i[!].freeze
55
53
 
56
54
  def_node_matcher :exists_and_not_empty?, <<~PATTERN
@@ -118,8 +116,7 @@ module RuboCop
118
116
 
119
117
  unless_blank?(node) do |method_call, receiver|
120
118
  range = unless_condition(node, method_call)
121
- msg = format(MSG_UNLESS_BLANK, prefer: replacement(receiver),
122
- current: range.source)
119
+ msg = format(MSG_UNLESS_BLANK, prefer: replacement(receiver), current: range.source)
123
120
  add_offense(range, message: msg) do |corrector|
124
121
  autocorrect(corrector, node)
125
122
  end
@@ -131,10 +128,10 @@ module RuboCop
131
128
 
132
129
  if method_call
133
130
  corrector.replace(node.loc.keyword, 'if')
134
- range = method_call.loc.expression
131
+ range = method_call.source_range
135
132
  else
136
133
  variable1, _variable2 = exists_and_not_empty?(node) || not_blank?(node)
137
- range = node.loc.expression
134
+ range = node.source_range
138
135
  end
139
136
 
140
137
  corrector.replace(range, replacement(variable1))
@@ -144,9 +141,9 @@ module RuboCop
144
141
 
145
142
  def unless_condition(node, method_call)
146
143
  if node.modifier_form?
147
- node.loc.keyword.join(node.loc.expression.end)
144
+ node.loc.keyword.join(node.source_range.end)
148
145
  else
149
- node.loc.expression.begin.join(method_call.loc.expression)
146
+ node.source_range.begin.join(method_call.source_range)
150
147
  end
151
148
  end
152
149
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for Rake tasks without the `:environment` task
6
+ # Checks for Rake tasks without the `:environment` task
7
7
  # dependency. The `:environment` task loads application code for other
8
8
  # Rake tasks. Without it, tasks cannot make use of application code like
9
9
  # models.
@@ -14,6 +14,11 @@ module RuboCop
14
14
  # * The task does not need application code.
15
15
  # * The task invokes the `:environment` task.
16
16
  #
17
+ # @safety
18
+ # Probably not a problem in most cases, but it is possible that calling `:environment` task
19
+ # will break a behavior. It's also slower. E.g. some task that only needs one gem to be
20
+ # loaded to run will run significantly faster without loading the whole application.
21
+ #
17
22
  # @example
18
23
  # # bad
19
24
  # task :foo do
@@ -34,7 +39,7 @@ module RuboCop
34
39
  (block $(send nil? :task ...) ...)
35
40
  PATTERN
36
41
 
37
- def on_block(node)
42
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
38
43
  task_definition?(node) do |task_method|
39
44
  return if task_name(task_method) == :default
40
45
  return if with_dependencies?(task_method)
@@ -43,7 +48,7 @@ module RuboCop
43
48
  task_name = task_method.arguments[0]
44
49
  task_dependency = correct_task_dependency(task_name)
45
50
 
46
- corrector.replace(task_name.loc.expression, task_dependency)
51
+ corrector.replace(task_name, task_dependency)
47
52
  end
48
53
  end
49
54
  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 `read_attribute` or `write_attribute`
6
+ # Checks for the use of the `read_attribute` or `write_attribute`
7
7
  # methods and recommends square brackets instead.
8
8
  #
9
9
  # If an attribute is missing from the instance (for example, when
@@ -23,10 +23,21 @@ module RuboCop
23
23
  # # good
24
24
  # x = self[:attr]
25
25
  # self[:attr] = val
26
+ #
27
+ # When called from within a method with the same name as the attribute,
28
+ # `read_attribute` and `write_attribute` must be used to prevent an
29
+ # infinite loop:
30
+ #
31
+ # @example
32
+ #
33
+ # # good
34
+ # def foo
35
+ # bar || read_attribute(:foo)
36
+ # end
26
37
  class ReadWriteAttribute < Base
27
38
  extend AutoCorrector
28
39
 
29
- MSG = 'Prefer `%<prefer>s` over `%<current>s`.'
40
+ MSG = 'Prefer `%<prefer>s`.'
30
41
  RESTRICT_ON_SEND = %i[read_attribute write_attribute].freeze
31
42
 
32
43
  def_node_matcher :read_write_attribute?, <<~PATTERN
@@ -38,27 +49,53 @@ module RuboCop
38
49
 
39
50
  def on_send(node)
40
51
  return unless read_write_attribute?(node)
52
+ return if within_shadowing_method?(node)
41
53
 
42
- add_offense(node.loc.selector, message: message(node)) do |corrector|
43
- case node.method_name
44
- when :read_attribute
45
- replacement = read_attribute_replacement(node)
46
- when :write_attribute
47
- replacement = write_attribute_replacement(node)
48
- end
49
-
50
- corrector.replace(node.source_range, replacement)
54
+ add_offense(node, message: build_message(node)) do |corrector|
55
+ corrector.replace(node, node_replacement(node))
51
56
  end
52
57
  end
53
58
 
54
59
  private
55
60
 
56
- def message(node)
61
+ def within_shadowing_method?(node)
62
+ first_arg = node.first_argument
63
+ return false unless first_arg.respond_to?(:value)
64
+
65
+ enclosing_method = node.each_ancestor(:def).first
66
+ return false unless enclosing_method
67
+
68
+ shadowing_method_name = first_arg.value.to_s
69
+ shadowing_method_name << '=' if node.method?(:write_attribute)
70
+ enclosing_method.method?(shadowing_method_name)
71
+ end
72
+
73
+ def build_message(node)
74
+ if node.single_line?
75
+ single_line_message(node)
76
+ else
77
+ multi_line_message(node)
78
+ end
79
+ end
80
+
81
+ def single_line_message(node)
82
+ format(MSG, prefer: node_replacement(node))
83
+ end
84
+
85
+ def multi_line_message(node)
57
86
  if node.method?(:read_attribute)
58
- format(MSG, prefer: 'self[:attr]', current: 'read_attribute(:attr)')
87
+ format(MSG, prefer: 'self[:attr]')
59
88
  else
60
- format(MSG, prefer: 'self[:attr] = val',
61
- current: 'write_attribute(:attr, val)')
89
+ format(MSG, prefer: 'self[:attr] = val')
90
+ end
91
+ end
92
+
93
+ def node_replacement(node)
94
+ case node.method_name
95
+ when :read_attribute
96
+ read_attribute_replacement(node)
97
+ when :write_attribute
98
+ write_attribute_replacement(node)
62
99
  end
63
100
  end
64
101
 
@@ -30,11 +30,9 @@ module RuboCop
30
30
  include RangeHelp
31
31
  extend AutoCorrector
32
32
 
33
- MSG_SAME =
34
- '`allow_nil` is redundant when `allow_blank` has the same value.'
33
+ MSG_SAME = '`allow_nil` is redundant when `allow_blank` has the same value.'
35
34
 
36
- MSG_ALLOW_NIL_FALSE =
37
- '`allow_nil: false` is redundant when `allow_blank` is true.'
35
+ MSG_ALLOW_NIL_FALSE = '`allow_nil: false` is redundant when `allow_blank` is true.'
38
36
 
39
37
  RESTRICT_ON_SEND = %i[validates].freeze
40
38
 
@@ -56,15 +54,15 @@ module RuboCop
56
54
 
57
55
  def register_offense(allow_nil, message)
58
56
  add_offense(allow_nil, message: message) do |corrector|
59
- prv_sib = previous_sibling(allow_nil)
60
- nxt_sib = next_sibling(allow_nil)
57
+ prv_sib = allow_nil.left_sibling
58
+ nxt_sib = allow_nil.right_sibling
61
59
 
62
60
  if nxt_sib
63
61
  corrector.remove(range_between(node_beg(allow_nil), node_beg(nxt_sib)))
64
62
  elsif prv_sib
65
63
  corrector.remove(range_between(node_end(prv_sib), node_end(allow_nil)))
66
64
  else
67
- corrector.remove(allow_nil.loc.expression)
65
+ corrector.remove(allow_nil)
68
66
  end
69
67
  end
70
68
  end
@@ -88,20 +86,12 @@ module RuboCop
88
86
  nil
89
87
  end
90
88
 
91
- def previous_sibling(node)
92
- node.parent.children[node.sibling_index - 1]
93
- end
94
-
95
- def next_sibling(node)
96
- node.parent.children[node.sibling_index + 1]
97
- end
98
-
99
89
  def node_beg(node)
100
- node.loc.expression.begin_pos
90
+ node.source_range.begin_pos
101
91
  end
102
92
 
103
93
  def node_end(node)
104
- node.loc.expression.end_pos
94
+ node.source_range.end_pos
105
95
  end
106
96
  end
107
97
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop detects cases where the `:foreign_key` option on associations
6
+ # Detects cases where the `:foreign_key` option on associations
7
7
  # is redundant.
8
8
  #
9
9
  # @example
@@ -40,8 +40,8 @@ module RuboCop
40
40
  def on_send(node)
41
41
  association_with_foreign_key(node) do |type, name, options, foreign_key_pair, foreign_key|
42
42
  if redundant?(node, type, name, options, foreign_key)
43
- add_offense(foreign_key_pair.loc.expression) do |corrector|
44
- range = range_with_surrounding_space(range: foreign_key_pair.source_range, side: :left)
43
+ add_offense(foreign_key_pair.source_range) do |corrector|
44
+ range = range_with_surrounding_space(foreign_key_pair.source_range, side: :left)
45
45
  range = range_with_surrounding_comma(range, :left)
46
46
 
47
47
  corrector.remove(range)