rubocop-rails 2.0.1 → 2.19.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +52 -5
  4. data/config/default.yml +726 -32
  5. data/config/obsoletion.yml +17 -0
  6. data/lib/rubocop/cop/mixin/active_record_helper.rb +106 -0
  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 +40 -0
  10. data/lib/rubocop/cop/mixin/index_method.rb +165 -0
  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 +11 -21
  15. data/lib/rubocop/cop/rails/action_order.rb +116 -0
  16. data/lib/rubocop/cop/rails/active_record_aliases.rb +23 -24
  17. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +143 -0
  18. data/lib/rubocop/cop/rails/active_record_override.rb +3 -6
  19. data/lib/rubocop/cop/rails/active_support_aliases.rb +13 -22
  20. data/lib/rubocop/cop/rails/active_support_on_load.rb +70 -0
  21. data/lib/rubocop/cop/rails/add_column_index.rb +61 -0
  22. data/lib/rubocop/cop/rails/after_commit_override.rb +81 -0
  23. data/lib/rubocop/cop/rails/application_controller.rb +36 -0
  24. data/lib/rubocop/cop/rails/application_job.rb +9 -4
  25. data/lib/rubocop/cop/rails/application_mailer.rb +39 -0
  26. data/lib/rubocop/cop/rails/application_record.rb +9 -9
  27. data/lib/rubocop/cop/rails/arel_star.rb +47 -0
  28. data/lib/rubocop/cop/rails/assert_not.rb +8 -10
  29. data/lib/rubocop/cop/rails/attribute_default_block_value.rb +90 -0
  30. data/lib/rubocop/cop/rails/belongs_to.rb +12 -24
  31. data/lib/rubocop/cop/rails/blank.rb +40 -36
  32. data/lib/rubocop/cop/rails/bulk_change_table.rb +40 -35
  33. data/lib/rubocop/cop/rails/compact_blank.rb +111 -0
  34. data/lib/rubocop/cop/rails/content_tag.rb +93 -0
  35. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +22 -15
  36. data/lib/rubocop/cop/rails/date.rb +41 -36
  37. data/lib/rubocop/cop/rails/default_scope.rb +61 -0
  38. data/lib/rubocop/cop/rails/delegate.rb +33 -29
  39. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +9 -10
  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 +76 -31
  46. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +82 -0
  47. data/lib/rubocop/cop/rails/enum_hash.rb +75 -0
  48. data/lib/rubocop/cop/rails/enum_uniqueness.rb +30 -12
  49. data/lib/rubocop/cop/rails/environment_comparison.rb +70 -22
  50. data/lib/rubocop/cop/rails/environment_variable_access.rb +67 -0
  51. data/lib/rubocop/cop/rails/exit.rb +7 -13
  52. data/lib/rubocop/cop/rails/expanded_date_range.rb +102 -0
  53. data/lib/rubocop/cop/rails/file_path.rb +48 -31
  54. data/lib/rubocop/cop/rails/find_by.rb +43 -24
  55. data/lib/rubocop/cop/rails/find_by_id.rb +94 -0
  56. data/lib/rubocop/cop/rails/find_each.rb +42 -18
  57. data/lib/rubocop/cop/rails/freeze_time.rb +79 -0
  58. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +4 -3
  59. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +62 -25
  60. data/lib/rubocop/cop/rails/helper_instance_variable.rb +32 -4
  61. data/lib/rubocop/cop/rails/http_positional_arguments.rb +61 -32
  62. data/lib/rubocop/cop/rails/http_status.rb +27 -23
  63. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +96 -0
  64. data/lib/rubocop/cop/rails/i18n_locale_assignment.rb +37 -0
  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 +9 -16
  68. data/lib/rubocop/cop/rails/index_by.rb +65 -0
  69. data/lib/rubocop/cop/rails/index_with.rb +68 -0
  70. data/lib/rubocop/cop/rails/inquiry.rb +39 -0
  71. data/lib/rubocop/cop/rails/inverse_of.rb +33 -27
  72. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +62 -32
  73. data/lib/rubocop/cop/rails/link_to_blank.rb +31 -32
  74. data/lib/rubocop/cop/rails/mailer_name.rb +90 -0
  75. data/lib/rubocop/cop/rails/match_route.rb +120 -0
  76. data/lib/rubocop/cop/rails/migration_class_name.rb +63 -0
  77. data/lib/rubocop/cop/rails/negate_include.rb +42 -0
  78. data/lib/rubocop/cop/rails/not_null_column.rb +16 -12
  79. data/lib/rubocop/cop/rails/order_by_id.rb +51 -0
  80. data/lib/rubocop/cop/rails/output.rb +29 -10
  81. data/lib/rubocop/cop/rails/output_safety.rb +9 -4
  82. data/lib/rubocop/cop/rails/pick.rb +64 -0
  83. data/lib/rubocop/cop/rails/pluck.rb +96 -0
  84. data/lib/rubocop/cop/rails/pluck_id.rb +59 -0
  85. data/lib/rubocop/cop/rails/pluck_in_where.rb +71 -0
  86. data/lib/rubocop/cop/rails/pluralization_grammar.rb +14 -19
  87. data/lib/rubocop/cop/rails/presence.rb +54 -26
  88. data/lib/rubocop/cop/rails/present.rb +40 -37
  89. data/lib/rubocop/cop/rails/rake_environment.rb +112 -0
  90. data/lib/rubocop/cop/rails/read_write_attribute.rb +56 -18
  91. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +33 -45
  92. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +77 -0
  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 +34 -32
  95. data/lib/rubocop/cop/rails/redundant_travel_back.rb +57 -0
  96. data/lib/rubocop/cop/rails/reflection_class_name.rb +56 -7
  97. data/lib/rubocop/cop/rails/refute_methods.rb +56 -35
  98. data/lib/rubocop/cop/rails/relative_date_constant.rb +52 -33
  99. data/lib/rubocop/cop/rails/render_inline.rb +41 -0
  100. data/lib/rubocop/cop/rails/render_plain_text.rb +71 -0
  101. data/lib/rubocop/cop/rails/request_referer.rb +10 -11
  102. data/lib/rubocop/cop/rails/require_dependency.rb +38 -0
  103. data/lib/rubocop/cop/rails/response_parsed_body.rb +57 -0
  104. data/lib/rubocop/cop/rails/reversible_migration.rb +122 -82
  105. data/lib/rubocop/cop/rails/reversible_migration_method_definition.rb +66 -0
  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 +55 -43
  110. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +50 -0
  111. data/lib/rubocop/cop/rails/save_bang.rb +89 -63
  112. data/lib/rubocop/cop/rails/schema_comment.rb +104 -0
  113. data/lib/rubocop/cop/rails/scope_args.rb +8 -3
  114. data/lib/rubocop/cop/rails/short_i18n.rb +71 -0
  115. data/lib/rubocop/cop/rails/skips_model_validations.rb +53 -16
  116. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +87 -0
  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 +83 -67
  121. data/lib/rubocop/cop/rails/time_zone_assignment.rb +37 -0
  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 +40 -49
  127. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +172 -0
  128. data/lib/rubocop/cop/rails/unknown_env.rb +52 -21
  129. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +76 -0
  130. data/lib/rubocop/cop/rails/validation.rb +54 -23
  131. data/lib/rubocop/cop/rails/where_equals.rb +102 -0
  132. data/lib/rubocop/cop/rails/where_exists.rb +138 -0
  133. data/lib/rubocop/cop/rails/where_missing.rb +118 -0
  134. data/lib/rubocop/cop/rails/where_not.rb +101 -0
  135. data/lib/rubocop/cop/rails/where_not_with_multiple_conditions.rb +55 -0
  136. data/lib/rubocop/cop/rails_cops.rb +78 -8
  137. data/lib/rubocop/rails/inject.rb +1 -1
  138. data/lib/rubocop/rails/schema_loader/schema.rb +191 -0
  139. data/lib/rubocop/rails/schema_loader.rb +61 -0
  140. data/lib/rubocop/rails/version.rb +5 -1
  141. data/lib/rubocop/rails.rb +3 -1
  142. data/lib/rubocop-rails.rb +22 -0
  143. metadata +120 -19
  144. data/bin/setup +0 -7
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Identifies places where `pluck` is used in `where` query methods
7
+ # and can be replaced with `select`.
8
+ #
9
+ # Since `pluck` is an eager method and hits the database immediately,
10
+ # using `select` helps to avoid additional database queries.
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
14
+ # (i.e. a model class) in the `where` is used as offenses.
15
+ #
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.
21
+ #
22
+ # @example
23
+ # # bad
24
+ # Post.where(user_id: User.active.pluck(:id))
25
+ #
26
+ # # good
27
+ # Post.where(user_id: User.active.select(:id))
28
+ # Post.where(user_id: active_users.select(:id))
29
+ #
30
+ # @example EnforcedStyle: conservative (default)
31
+ # # good
32
+ # Post.where(user_id: active_users.pluck(:id))
33
+ #
34
+ # @example EnforcedStyle: aggressive
35
+ # # bad
36
+ # Post.where(user_id: active_users.pluck(:id))
37
+ #
38
+ class PluckInWhere < Base
39
+ include ActiveRecordHelper
40
+ include ConfigurableEnforcedStyle
41
+ extend AutoCorrector
42
+
43
+ MSG = 'Use `select` instead of `pluck` within `where` query method.'
44
+ RESTRICT_ON_SEND = %i[pluck].freeze
45
+
46
+ def on_send(node)
47
+ return unless in_where?(node)
48
+ return if style == :conservative && !root_receiver(node)&.const_type?
49
+
50
+ range = node.loc.selector
51
+
52
+ add_offense(range) do |corrector|
53
+ corrector.replace(range, 'select')
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def root_receiver(node)
60
+ receiver = node.receiver
61
+
62
+ if receiver&.send_type?
63
+ root_receiver(receiver)
64
+ else
65
+ receiver
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -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
@@ -14,7 +14,9 @@ module RuboCop
14
14
  # # good
15
15
  # 3.days.ago
16
16
  # 1.month.ago
17
- class PluralizationGrammar < Cop
17
+ class PluralizationGrammar < Base
18
+ extend AutoCorrector
19
+
18
20
  SINGULAR_DURATION_METHODS = { second: :seconds,
19
21
  minute: :minutes,
20
22
  hour: :hours,
@@ -24,21 +26,18 @@ module RuboCop
24
26
  month: :months,
25
27
  year: :years }.freeze
26
28
 
29
+ RESTRICT_ON_SEND = SINGULAR_DURATION_METHODS.keys + SINGULAR_DURATION_METHODS.values
30
+
27
31
  PLURAL_DURATION_METHODS = SINGULAR_DURATION_METHODS.invert.freeze
28
32
 
29
33
  MSG = 'Prefer `%<number>s.%<correct>s`.'
30
34
 
31
35
  def on_send(node)
32
- return unless duration_method?(node.method_name)
33
- return unless literal_number?(node.receiver)
36
+ return unless duration_method?(node.method_name) && literal_number?(node.receiver) && offense?(node)
34
37
 
35
- return unless offense?(node)
36
-
37
- add_offense(node)
38
- end
38
+ number, = *node.receiver
39
39
 
40
- def autocorrect(node)
41
- lambda do |corrector|
40
+ add_offense(node, message: message(number, node.method_name)) do |corrector|
42
41
  method_name = node.loc.selector.source
43
42
 
44
43
  corrector.replace(node.loc.selector, correct_method(method_name))
@@ -47,11 +46,8 @@ module RuboCop
47
46
 
48
47
  private
49
48
 
50
- def message(node)
51
- number, = *node.receiver
52
-
53
- format(MSG, number: number,
54
- correct: correct_method(node.method_name.to_s))
49
+ def message(number, method_name)
50
+ format(MSG, number: number, correct: correct_method(method_name))
55
51
  end
56
52
 
57
53
  def correct_method(method_name)
@@ -65,8 +61,8 @@ module RuboCop
65
61
  def offense?(node)
66
62
  number, = *node.receiver
67
63
 
68
- singular_receiver?(number) && plural_method?(node.method_name) ||
69
- 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))
70
66
  end
71
67
 
72
68
  def plural_method?(method_name)
@@ -98,8 +94,7 @@ module RuboCop
98
94
  end
99
95
 
100
96
  def duration_method?(method_name)
101
- SINGULAR_DURATION_METHODS.key?(method_name) ||
102
- PLURAL_DURATION_METHODS.key?(method_name)
97
+ SINGULAR_DURATION_METHODS.key?(method_name) || PLURAL_DURATION_METHODS.key?(method_name)
103
98
  end
104
99
  end
105
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
@@ -37,10 +37,13 @@ module RuboCop
37
37
  #
38
38
  # # good
39
39
  # a.presence || b
40
- class Presence < Cop
40
+ class Presence < Base
41
+ include RangeHelp
42
+ extend AutoCorrector
43
+
41
44
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
42
45
 
43
- def_node_matcher :redundant_receiver_and_other, <<-PATTERN
46
+ def_node_matcher :redundant_receiver_and_other, <<~PATTERN
44
47
  {
45
48
  (if
46
49
  (send $_recv :present?)
@@ -55,7 +58,7 @@ module RuboCop
55
58
  }
56
59
  PATTERN
57
60
 
58
- def_node_matcher :redundant_negative_receiver_and_other, <<-PATTERN
61
+ def_node_matcher :redundant_negative_receiver_and_other, <<~PATTERN
59
62
  {
60
63
  (if
61
64
  (send (send $_recv :present?) :!)
@@ -74,32 +77,26 @@ module RuboCop
74
77
  return if ignore_if_node?(node)
75
78
 
76
79
  redundant_receiver_and_other(node) do |receiver, other|
77
- unless ignore_other_node?(other) || receiver.nil?
78
- add_offense(node, message: message(node, receiver, other))
79
- end
80
+ return if ignore_other_node?(other) || receiver.nil?
81
+
82
+ register_offense(node, receiver, other)
80
83
  end
81
84
 
82
85
  redundant_negative_receiver_and_other(node) do |receiver, other|
83
- unless ignore_other_node?(other) || receiver.nil?
84
- add_offense(node, message: message(node, receiver, other))
85
- end
86
+ return if ignore_other_node?(other) || receiver.nil?
87
+
88
+ register_offense(node, receiver, other)
86
89
  end
87
90
  end
88
91
 
89
- def autocorrect(node)
90
- lambda do |corrector|
91
- redundant_receiver_and_other(node) do |receiver, other|
92
- corrector.replace(node.source_range, replacement(receiver, other))
93
- end
92
+ private
94
93
 
95
- redundant_negative_receiver_and_other(node) do |receiver, other|
96
- corrector.replace(node.source_range, replacement(receiver, other))
97
- end
94
+ def register_offense(node, receiver, other)
95
+ add_offense(node, message: message(node, receiver, other)) do |corrector|
96
+ corrector.replace(node, replacement(receiver, other, node.left_sibling))
98
97
  end
99
98
  end
100
99
 
101
- private
102
-
103
100
  def ignore_if_node?(node)
104
101
  node.elsif?
105
102
  end
@@ -109,14 +106,45 @@ module RuboCop
109
106
  end
110
107
 
111
108
  def message(node, receiver, other)
112
- format(MSG,
113
- prefer: replacement(receiver, other),
114
- 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
+ end
113
+
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)
123
+ or_source = if other&.send_type?
124
+ build_source_for_or_method(other)
125
+ elsif other.nil? || other.nil_type?
126
+ ''
127
+ else
128
+ " || #{other.source}"
129
+ end
130
+
131
+ replaced = "#{receiver.source}.presence#{or_source}"
132
+ left_sibling ? "(#{replaced})" : replaced
133
+ end
134
+
135
+ def build_source_for_or_method(other)
136
+ if other.parenthesized? || other.method?('[]') || other.arithmetic_operation? || !other.arguments?
137
+ " || #{other.source}"
138
+ else
139
+ method = method_range(other).source
140
+ arguments = other.arguments.map(&:source).join(', ')
141
+
142
+ " || #{method}(#{arguments})"
143
+ end
115
144
  end
116
145
 
117
- def replacement(receiver, other)
118
- or_source = other.nil? || other.nil_type? ? '' : " || #{other.source}"
119
- "#{receiver.source}.presence" + or_source
146
+ def method_range(node)
147
+ range_between(node.source_range.begin_pos, node.first_argument.source_range.begin_pos - 1)
120
148
  end
121
149
  end
122
150
  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?`
@@ -43,14 +43,15 @@ module RuboCop
43
43
  #
44
44
  # # good
45
45
  # something if foo.present?
46
- class Present < Cop
46
+ class Present < Base
47
+ extend AutoCorrector
48
+
47
49
  MSG_NOT_BLANK = 'Use `%<prefer>s` instead of `%<current>s`.'
48
- MSG_EXISTS_AND_NOT_EMPTY = 'Use `%<prefer>s` instead of ' \
49
- '`%<current>s`.'
50
- MSG_UNLESS_BLANK = 'Use `if %<prefer>s` instead of ' \
51
- '`%<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`.'
52
+ RESTRICT_ON_SEND = %i[!].freeze
52
53
 
53
- def_node_matcher :exists_and_not_empty?, <<-PATTERN
54
+ def_node_matcher :exists_and_not_empty?, <<~PATTERN
54
55
  (and
55
56
  {
56
57
  (send (send $_ :nil?) :!)
@@ -66,7 +67,7 @@ module RuboCop
66
67
 
67
68
  def_node_matcher :not_blank?, '(send (send $_ :blank?) :!)'
68
69
 
69
- def_node_matcher :unless_blank?, <<-PATTERN
70
+ def_node_matcher :unless_blank?, <<~PATTERN
70
71
  (:if $(send $_ :blank?) {nil? (...)} ...)
71
72
  PATTERN
72
73
 
@@ -74,10 +75,11 @@ module RuboCop
74
75
  return unless cop_config['NotBlank']
75
76
 
76
77
  not_blank?(node) do |receiver|
77
- add_offense(node,
78
- message: format(MSG_NOT_BLANK,
79
- prefer: replacement(receiver),
80
- current: node.source))
78
+ message = format(MSG_NOT_BLANK, prefer: replacement(receiver), current: node.source)
79
+
80
+ add_offense(node, message: message) do |corrector|
81
+ autocorrect(corrector, node)
82
+ end
81
83
  end
82
84
  end
83
85
 
@@ -87,10 +89,11 @@ module RuboCop
87
89
  exists_and_not_empty?(node) do |var1, var2|
88
90
  return unless var1 == var2
89
91
 
90
- add_offense(node,
91
- message: format(MSG_EXISTS_AND_NOT_EMPTY,
92
- prefer: replacement(var1),
93
- current: node.source))
92
+ message = format(MSG_EXISTS_AND_NOT_EMPTY, prefer: replacement(var1), current: node.source)
93
+
94
+ add_offense(node, message: message) do |corrector|
95
+ autocorrect(corrector, node)
96
+ end
94
97
  end
95
98
  end
96
99
 
@@ -100,7 +103,9 @@ module RuboCop
100
103
  exists_and_not_empty?(node) do |var1, var2|
101
104
  return unless var1 == var2
102
105
 
103
- add_offense(node, message: MSG_EXISTS_AND_NOT_EMPTY)
106
+ add_offense(node, message: MSG_EXISTS_AND_NOT_EMPTY) do |corrector|
107
+ autocorrect(corrector, node)
108
+ end
104
109
  end
105
110
  end
106
111
 
@@ -111,36 +116,34 @@ module RuboCop
111
116
 
112
117
  unless_blank?(node) do |method_call, receiver|
113
118
  range = unless_condition(node, method_call)
114
- msg = format(MSG_UNLESS_BLANK, prefer: replacement(receiver),
115
- current: range.source)
116
- add_offense(node, location: range, message: msg)
119
+ msg = format(MSG_UNLESS_BLANK, prefer: replacement(receiver), current: range.source)
120
+ add_offense(range, message: msg) do |corrector|
121
+ autocorrect(corrector, node)
122
+ end
117
123
  end
118
124
  end
119
125
 
120
- def autocorrect(node)
121
- lambda do |corrector|
122
- method_call, variable1 = unless_blank?(node)
123
-
124
- if method_call
125
- corrector.replace(node.loc.keyword, 'if')
126
- range = method_call.loc.expression
127
- else
128
- variable1, _variable2 =
129
- exists_and_not_empty?(node) || not_blank?(node)
130
- range = node.loc.expression
131
- end
126
+ def autocorrect(corrector, node)
127
+ method_call, variable1 = unless_blank?(node)
132
128
 
133
- corrector.replace(range, replacement(variable1))
129
+ if method_call
130
+ corrector.replace(node.loc.keyword, 'if')
131
+ range = method_call.source_range
132
+ else
133
+ variable1, _variable2 = exists_and_not_empty?(node) || not_blank?(node)
134
+ range = node.source_range
134
135
  end
136
+
137
+ corrector.replace(range, replacement(variable1))
135
138
  end
136
139
 
137
140
  private
138
141
 
139
142
  def unless_condition(node, method_call)
140
143
  if node.modifier_form?
141
- node.loc.keyword.join(node.loc.expression.end)
144
+ node.loc.keyword.join(node.source_range.end)
142
145
  else
143
- node.loc.expression.begin.join(method_call.loc.expression)
146
+ node.source_range.begin.join(method_call.source_range)
144
147
  end
145
148
  end
146
149
 
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Checks for Rake tasks without the `:environment` task
7
+ # dependency. The `:environment` task loads application code for other
8
+ # Rake tasks. Without it, tasks cannot make use of application code like
9
+ # models.
10
+ #
11
+ # You can ignore the offense if the task satisfies at least one of the
12
+ # following conditions:
13
+ #
14
+ # * The task does not need application code.
15
+ # * The task invokes the `:environment` task.
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
+ #
22
+ # @example
23
+ # # bad
24
+ # task :foo do
25
+ # do_something
26
+ # end
27
+ #
28
+ # # good
29
+ # task foo: :environment do
30
+ # do_something
31
+ # end
32
+ #
33
+ class RakeEnvironment < Base
34
+ extend AutoCorrector
35
+
36
+ MSG = 'Include `:environment` task as a dependency for all Rake tasks.'
37
+
38
+ def_node_matcher :task_definition?, <<~PATTERN
39
+ (block $(send nil? :task ...) ...)
40
+ PATTERN
41
+
42
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
43
+ task_definition?(node) do |task_method|
44
+ return if task_name(task_method) == :default
45
+ return if with_dependencies?(task_method)
46
+
47
+ add_offense(task_method) do |corrector|
48
+ task_name = task_method.arguments[0]
49
+ task_dependency = correct_task_dependency(task_name)
50
+
51
+ corrector.replace(task_name, task_dependency)
52
+ end
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def correct_task_dependency(task_name)
59
+ if task_name.sym_type?
60
+ "#{task_name.source.delete(':|\'|"')}: :environment"
61
+ else
62
+ "#{task_name.source} => :environment"
63
+ end
64
+ end
65
+
66
+ def task_name(node)
67
+ first_arg = node.arguments[0]
68
+ case first_arg&.type
69
+ when :sym, :str
70
+ first_arg.value.to_sym
71
+ when :hash
72
+ return nil if first_arg.children.size != 1
73
+
74
+ pair = first_arg.children.first
75
+ key = pair.children.first
76
+ case key.type
77
+ when :sym, :str
78
+ key.value.to_sym
79
+ end
80
+ end
81
+ end
82
+
83
+ def with_dependencies?(node)
84
+ first_arg = node.arguments[0]
85
+ return false unless first_arg
86
+
87
+ if first_arg.hash_type?
88
+ with_hash_style_dependencies?(first_arg)
89
+ else
90
+ task_args = node.arguments[1]
91
+ return false unless task_args
92
+ return false unless task_args.hash_type?
93
+
94
+ with_hash_style_dependencies?(task_args)
95
+ end
96
+ end
97
+
98
+ def with_hash_style_dependencies?(hash_node)
99
+ deps = hash_node.pairs.first&.value
100
+ return false unless deps
101
+
102
+ case deps.type
103
+ when :array
104
+ !deps.values.empty?
105
+ else
106
+ true
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ 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,24 @@ module RuboCop
23
23
  # # good
24
24
  # x = self[:attr]
25
25
  # self[:attr] = val
26
- class ReadWriteAttribute < Cop
27
- MSG = 'Prefer `%<prefer>s` over `%<current>s`.'
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
37
+ class ReadWriteAttribute < Base
38
+ extend AutoCorrector
39
+
40
+ MSG = 'Prefer `%<prefer>s`.'
41
+ RESTRICT_ON_SEND = %i[read_attribute write_attribute].freeze
28
42
 
29
- def_node_matcher :read_write_attribute?, <<-PATTERN
43
+ def_node_matcher :read_write_attribute?, <<~PATTERN
30
44
  {
31
45
  (send nil? :read_attribute _)
32
46
  (send nil? :write_attribute _ _)
@@ -35,29 +49,53 @@ module RuboCop
35
49
 
36
50
  def on_send(node)
37
51
  return unless read_write_attribute?(node)
52
+ return if within_shadowing_method?(node)
38
53
 
39
- add_offense(node, location: :selector)
54
+ add_offense(node, message: build_message(node)) do |corrector|
55
+ corrector.replace(node, node_replacement(node))
56
+ end
40
57
  end
41
58
 
42
- def autocorrect(node)
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
59
+ private
60
+
61
+ def within_shadowing_method?(node)
62
+ first_arg = node.first_argument
63
+ return false unless first_arg.respond_to?(:value)
49
64
 
50
- ->(corrector) { corrector.replace(node.source_range, replacement) }
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)
51
71
  end
52
72
 
53
- private
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
54
84
 
55
- def message(node)
85
+ def multi_line_message(node)
56
86
  if node.method?(:read_attribute)
57
- format(MSG, prefer: 'self[:attr]', current: 'read_attribute(:attr)')
87
+ format(MSG, prefer: 'self[:attr]')
58
88
  else
59
- format(MSG, prefer: 'self[:attr] = val',
60
- 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)
61
99
  end
62
100
  end
63
101