rubocop-rails 2.14.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 +190 -12
  5. data/config/obsoletion.yml +10 -0
  6. data/lib/rubocop/cop/mixin/active_record_helper.rb +3 -6
  7. data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +1 -3
  8. data/lib/rubocop/cop/mixin/enforce_superclass.rb +1 -1
  9. data/lib/rubocop/cop/mixin/index_method.rb +7 -17
  10. data/lib/rubocop/cop/mixin/migrations_helper.rb +1 -1
  11. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +112 -0
  12. data/lib/rubocop/cop/rails/action_controller_test_case.rb +2 -2
  13. data/lib/rubocop/cop/rails/action_filter.rb +2 -2
  14. data/lib/rubocop/cop/rails/action_order.rb +116 -0
  15. data/lib/rubocop/cop/rails/active_record_aliases.rb +3 -4
  16. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +7 -4
  17. data/lib/rubocop/cop/rails/active_record_override.rb +2 -5
  18. data/lib/rubocop/cop/rails/active_support_aliases.rb +1 -1
  19. data/lib/rubocop/cop/rails/active_support_on_load.rb +70 -0
  20. data/lib/rubocop/cop/rails/add_column_index.rb +3 -6
  21. data/lib/rubocop/cop/rails/after_commit_override.rb +1 -1
  22. data/lib/rubocop/cop/rails/application_controller.rb +2 -2
  23. data/lib/rubocop/cop/rails/application_job.rb +3 -3
  24. data/lib/rubocop/cop/rails/application_mailer.rb +2 -2
  25. data/lib/rubocop/cop/rails/application_record.rb +2 -2
  26. data/lib/rubocop/cop/rails/arel_star.rb +2 -2
  27. data/lib/rubocop/cop/rails/assert_not.rb +1 -1
  28. data/lib/rubocop/cop/rails/attribute_default_block_value.rb +1 -1
  29. data/lib/rubocop/cop/rails/belongs_to.rb +2 -5
  30. data/lib/rubocop/cop/rails/blank.rb +10 -11
  31. data/lib/rubocop/cop/rails/bulk_change_table.rb +8 -25
  32. data/lib/rubocop/cop/rails/compact_blank.rb +6 -2
  33. data/lib/rubocop/cop/rails/content_tag.rb +6 -7
  34. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +16 -3
  35. data/lib/rubocop/cop/rails/date.rb +12 -17
  36. data/lib/rubocop/cop/rails/default_scope.rb +1 -1
  37. data/lib/rubocop/cop/rails/delegate.rb +24 -18
  38. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +2 -2
  39. data/lib/rubocop/cop/rails/deprecated_active_model_errors_methods.rb +63 -3
  40. data/lib/rubocop/cop/rails/dot_separated_keys.rb +71 -0
  41. data/lib/rubocop/cop/rails/duplicate_association.rb +2 -2
  42. data/lib/rubocop/cop/rails/duplicate_scope.rb +1 -1
  43. data/lib/rubocop/cop/rails/duration_arithmetic.rb +4 -4
  44. data/lib/rubocop/cop/rails/dynamic_find_by.rb +26 -14
  45. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +6 -2
  46. data/lib/rubocop/cop/rails/enum_hash.rb +3 -4
  47. data/lib/rubocop/cop/rails/enum_uniqueness.rb +3 -6
  48. data/lib/rubocop/cop/rails/environment_comparison.rb +3 -4
  49. data/lib/rubocop/cop/rails/environment_variable_access.rb +1 -1
  50. data/lib/rubocop/cop/rails/exit.rb +1 -1
  51. data/lib/rubocop/cop/rails/expanded_date_range.rb +39 -23
  52. data/lib/rubocop/cop/rails/file_path.rb +41 -24
  53. data/lib/rubocop/cop/rails/find_by.rb +1 -1
  54. data/lib/rubocop/cop/rails/find_by_id.rb +3 -3
  55. data/lib/rubocop/cop/rails/find_each.rb +14 -4
  56. data/lib/rubocop/cop/rails/freeze_time.rb +79 -0
  57. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +1 -1
  58. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +14 -8
  59. data/lib/rubocop/cop/rails/helper_instance_variable.rb +3 -3
  60. data/lib/rubocop/cop/rails/http_positional_arguments.rb +23 -12
  61. data/lib/rubocop/cop/rails/http_status.rb +6 -11
  62. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +3 -1
  63. data/lib/rubocop/cop/rails/i18n_locale_assignment.rb +1 -1
  64. data/lib/rubocop/cop/rails/i18n_locale_texts.rb +2 -2
  65. data/lib/rubocop/cop/rails/ignored_columns_assignment.rb +50 -0
  66. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +5 -14
  67. data/lib/rubocop/cop/rails/index_by.rb +2 -2
  68. data/lib/rubocop/cop/rails/index_with.rb +2 -2
  69. data/lib/rubocop/cop/rails/inquiry.rb +1 -1
  70. data/lib/rubocop/cop/rails/inverse_of.rb +4 -10
  71. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +22 -16
  72. data/lib/rubocop/cop/rails/link_to_blank.rb +2 -5
  73. data/lib/rubocop/cop/rails/mailer_name.rb +5 -5
  74. data/lib/rubocop/cop/rails/match_route.rb +1 -1
  75. data/lib/rubocop/cop/rails/migration_class_name.rb +2 -2
  76. data/lib/rubocop/cop/rails/negate_include.rb +2 -2
  77. data/lib/rubocop/cop/rails/not_null_column.rb +10 -7
  78. data/lib/rubocop/cop/rails/order_by_id.rb +2 -3
  79. data/lib/rubocop/cop/rails/output.rb +7 -9
  80. data/lib/rubocop/cop/rails/output_safety.rb +6 -2
  81. data/lib/rubocop/cop/rails/pick.rb +1 -1
  82. data/lib/rubocop/cop/rails/pluck.rb +45 -13
  83. data/lib/rubocop/cop/rails/pluck_id.rb +2 -2
  84. data/lib/rubocop/cop/rails/pluck_in_where.rb +1 -1
  85. data/lib/rubocop/cop/rails/pluralization_grammar.rb +2 -3
  86. data/lib/rubocop/cop/rails/presence.rb +22 -13
  87. data/lib/rubocop/cop/rails/present.rb +10 -13
  88. data/lib/rubocop/cop/rails/rake_environment.rb +3 -3
  89. data/lib/rubocop/cop/rails/read_write_attribute.rb +2 -2
  90. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +5 -7
  91. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +3 -3
  92. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +3 -3
  93. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +31 -27
  94. data/lib/rubocop/cop/rails/redundant_travel_back.rb +1 -1
  95. data/lib/rubocop/cop/rails/reflection_class_name.rb +35 -2
  96. data/lib/rubocop/cop/rails/refute_methods.rb +1 -5
  97. data/lib/rubocop/cop/rails/relative_date_constant.rb +5 -8
  98. data/lib/rubocop/cop/rails/render_inline.rb +1 -1
  99. data/lib/rubocop/cop/rails/render_plain_text.rb +1 -1
  100. data/lib/rubocop/cop/rails/request_referer.rb +2 -3
  101. data/lib/rubocop/cop/rails/require_dependency.rb +2 -2
  102. data/lib/rubocop/cop/rails/response_parsed_body.rb +57 -0
  103. data/lib/rubocop/cop/rails/reversible_migration.rb +15 -63
  104. data/lib/rubocop/cop/rails/reversible_migration_method_definition.rb +5 -6
  105. data/lib/rubocop/cop/rails/root_join_chain.rb +1 -1
  106. data/lib/rubocop/cop/rails/root_pathname_methods.rb +238 -0
  107. data/lib/rubocop/cop/rails/root_public_path.rb +59 -0
  108. data/lib/rubocop/cop/rails/safe_navigation.rb +8 -13
  109. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +2 -4
  110. data/lib/rubocop/cop/rails/save_bang.rb +12 -24
  111. data/lib/rubocop/cop/rails/schema_comment.rb +1 -1
  112. data/lib/rubocop/cop/rails/scope_args.rb +1 -1
  113. data/lib/rubocop/cop/rails/short_i18n.rb +3 -6
  114. data/lib/rubocop/cop/rails/skips_model_validations.rb +3 -4
  115. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +10 -7
  116. data/lib/rubocop/cop/rails/strip_heredoc.rb +56 -0
  117. data/lib/rubocop/cop/rails/table_name_assignment.rb +1 -1
  118. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +73 -0
  119. data/lib/rubocop/cop/rails/time_zone.rb +34 -32
  120. data/lib/rubocop/cop/rails/time_zone_assignment.rb +4 -4
  121. data/lib/rubocop/cop/rails/to_formatted_s.rb +46 -0
  122. data/lib/rubocop/cop/rails/to_s_with_argument.rb +78 -0
  123. data/lib/rubocop/cop/rails/top_level_hash_with_indifferent_access.rb +49 -0
  124. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +17 -12
  125. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +3 -6
  126. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +14 -7
  127. data/lib/rubocop/cop/rails/unknown_env.rb +3 -5
  128. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +7 -2
  129. data/lib/rubocop/cop/rails/validation.rb +5 -13
  130. data/lib/rubocop/cop/rails/where_equals.rb +2 -2
  131. data/lib/rubocop/cop/rails/where_exists.rb +3 -3
  132. data/lib/rubocop/cop/rails/where_missing.rb +118 -0
  133. data/lib/rubocop/cop/rails/where_not.rb +2 -2
  134. data/lib/rubocop/cop/rails/where_not_with_multiple_conditions.rb +55 -0
  135. data/lib/rubocop/cop/rails_cops.rb +16 -0
  136. data/lib/rubocop/rails/schema_loader/schema.rb +8 -5
  137. data/lib/rubocop/rails/version.rb +1 -1
  138. data/lib/rubocop/rails.rb +1 -1
  139. data/lib/rubocop-rails.rb +19 -0
  140. metadata +23 -9
  141. data/bin/console +0 -11
  142. data/bin/setup +0 -7
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks if the value of the option `class_name`, in
6
+ # Checks if the value of the option `class_name`, in
7
7
  # the definition of a reflection is a string.
8
8
  #
9
9
  # @safety
@@ -18,6 +18,8 @@ module RuboCop
18
18
  # # good
19
19
  # has_many :accounts, class_name: 'Account'
20
20
  class ReflectionClassName < Base
21
+ extend AutoCorrector
22
+
21
23
  MSG = 'Use a string value for `class_name`.'
22
24
  RESTRICT_ON_SEND = %i[has_many has_one belongs_to].freeze
23
25
  ALLOWED_REFLECTION_CLASS_TYPES = %i[dstr str sym].freeze
@@ -32,14 +34,37 @@ module RuboCop
32
34
  (pair (sym :class_name) #reflection_class_value?)
33
35
  PATTERN
34
36
 
37
+ def_node_matcher :const_or_string, <<~PATTERN
38
+ {$(const nil? _) (send $(const nil? _) :name) (send $(const nil? _) :to_s)}
39
+ PATTERN
40
+
35
41
  def on_send(node)
36
42
  association_with_reflection(node) do |reflection_class_name|
37
- add_offense(reflection_class_name.loc.expression)
43
+ return if reflection_class_name.value.send_type? && reflection_class_name.value.receiver.nil?
44
+ return if reflection_class_name.value.lvar_type? && str_assigned?(reflection_class_name)
45
+
46
+ add_offense(reflection_class_name.source_range) do |corrector|
47
+ autocorrect(corrector, reflection_class_name)
48
+ end
38
49
  end
39
50
  end
40
51
 
41
52
  private
42
53
 
54
+ def str_assigned?(reflection_class_name)
55
+ lvar = reflection_class_name.value.source
56
+
57
+ reflection_class_name.ancestors.each do |nodes|
58
+ return true if nodes.each_child_node(:lvasgn).detect do |node|
59
+ lhs, rhs = *node
60
+
61
+ lhs.to_s == lvar && ALLOWED_REFLECTION_CLASS_TYPES.include?(rhs.type)
62
+ end
63
+ end
64
+
65
+ false
66
+ end
67
+
43
68
  def reflection_class_value?(class_value)
44
69
  if class_value.send_type?
45
70
  !class_value.method?(:to_s) || class_value.receiver&.const_type?
@@ -47,6 +72,14 @@ module RuboCop
47
72
  !ALLOWED_REFLECTION_CLASS_TYPES.include?(class_value.type)
48
73
  end
49
74
  end
75
+
76
+ def autocorrect(corrector, class_config)
77
+ class_value = class_config.value
78
+ replacement = const_or_string(class_value)
79
+ return unless replacement.present?
80
+
81
+ corrector.replace(class_value, replacement.source.inspect)
82
+ end
50
83
  end
51
84
  end
52
85
  end
@@ -81,11 +81,7 @@ module RuboCop
81
81
  end
82
82
 
83
83
  def offense_message(method_name)
84
- format(
85
- MSG,
86
- bad_method: method_name,
87
- good_method: convert_good_method(method_name)
88
- )
84
+ format(MSG, bad_method: method_name, good_method: convert_good_method(method_name))
89
85
  end
90
86
 
91
87
  def convert_good_method(bad_method)
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks whether constant value isn't relative date.
6
+ # Checks whether constant value isn't relative date.
7
7
  # Because the relative date will be evaluated only once.
8
8
  #
9
9
  # @safety
@@ -34,8 +34,7 @@ module RuboCop
34
34
  include RangeHelp
35
35
  extend AutoCorrector
36
36
 
37
- MSG = 'Do not assign `%<method_name>s` to constants as it ' \
38
- 'will be evaluated only once.'
37
+ MSG = 'Do not assign `%<method_name>s` to constants as it will be evaluated only once.'
39
38
  RELATIVE_DATE_METHODS = %i[since from_now after ago until before yesterday tomorrow].to_set.freeze
40
39
 
41
40
  def on_casgn(node)
@@ -77,11 +76,9 @@ module RuboCop
77
76
  return unless scope.nil?
78
77
 
79
78
  indent = ' ' * node.loc.column
80
- new_code = ["def self.#{const_name.downcase}",
81
- "#{indent}#{value.source}",
82
- 'end'].join("\n#{indent}")
79
+ new_code = ["def self.#{const_name.downcase}", "#{indent}#{value.source}", 'end'].join("\n#{indent}")
83
80
 
84
- corrector.replace(node.source_range, new_code)
81
+ corrector.replace(node, new_code)
85
82
  end
86
83
 
87
84
  def message(method_name)
@@ -89,7 +86,7 @@ module RuboCop
89
86
  end
90
87
 
91
88
  def offense_range(name, value)
92
- range_between(name.loc.expression.begin_pos, value.loc.expression.end_pos)
89
+ range_between(name.source_range.begin_pos, value.source_range.end_pos)
93
90
  end
94
91
 
95
92
  def nested_relative_date(node, &callback)
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop looks for inline rendering within controller actions.
6
+ # Looks for inline rendering within controller actions.
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 identifies places where `render text:` can be
6
+ # Identifies places where `render text:` can be
7
7
  # replaced with `render plain:`.
8
8
  #
9
9
  # @example
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for consistent uses of `request.referer` or
6
+ # Checks for consistent uses of `request.referer` or
7
7
  # `request.referrer`, depending on the cop's configuration.
8
8
  #
9
9
  # @example EnforcedStyle: referer (default)
@@ -23,8 +23,7 @@ module RuboCop
23
23
  include ConfigurableEnforcedStyle
24
24
  extend AutoCorrector
25
25
 
26
- MSG = 'Use `request.%<prefer>s` instead of ' \
27
- '`request.%<current>s`.'
26
+ MSG = 'Use `request.%<prefer>s` instead of `request.%<current>s`.'
28
27
  RESTRICT_ON_SEND = %i[referer referrer].freeze
29
28
 
30
29
  def_node_matcher :referer?, <<~PATTERN
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for the usage of `require_dependency`.
6
+ # Checks for the usage of `require_dependency`.
7
7
  #
8
8
  # `require_dependency` is an obsolete method for Rails applications running in Zeitwerk mode.
9
9
  # In Zeitwerk mode, the semantics should match Ruby's and no need to be defensive with load order,
@@ -26,7 +26,7 @@ module RuboCop
26
26
  RESTRICT_ON_SEND = %i[require_dependency].freeze
27
27
 
28
28
  def_node_matcher :require_dependency_call?, <<~PATTERN
29
- (send {nil? (const _ :Kernel)} :require_dependency _)
29
+ (send {nil? (const {nil? cbase} :Kernel)} :require_dependency _)
30
30
  PATTERN
31
31
 
32
32
  def on_send(node)
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Prefer `response.parsed_body` to `JSON.parse(response.body)`.
7
+ #
8
+ # @safety
9
+ # This cop is unsafe because Content-Type may not be `application/json`. For example, the proprietary
10
+ # Content-Type provided by corporate entities such as `application/vnd.github+json` is not supported at
11
+ # `response.parsed_body` by default, so you still have to use `JSON.parse(response.body)` there.
12
+ #
13
+ # @example
14
+ # # bad
15
+ # JSON.parse(response.body)
16
+ #
17
+ # # good
18
+ # response.parsed_body
19
+ class ResponseParsedBody < Base
20
+ extend AutoCorrector
21
+ extend TargetRailsVersion
22
+
23
+ MSG = 'Prefer `response.parsed_body` to `JSON.parse(response.body)`.'
24
+
25
+ RESTRICT_ON_SEND = %i[parse].freeze
26
+
27
+ minimum_target_rails_version 5.0
28
+
29
+ # @!method json_parse_response_body?(node)
30
+ def_node_matcher :json_parse_response_body?, <<~PATTERN
31
+ (send
32
+ (const {nil? cbase} :JSON)
33
+ :parse
34
+ (send
35
+ (send nil? :response)
36
+ :body
37
+ )
38
+ )
39
+ PATTERN
40
+
41
+ def on_send(node)
42
+ return unless json_parse_response_body?(node)
43
+
44
+ add_offense(node) do |corrector|
45
+ autocorrect(corrector, node)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def autocorrect(corrector, node)
52
+ corrector.replace(node, 'response.parsed_body')
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks whether the change method of the migration file is
6
+ # Checks whether the change method of the migration file is
7
7
  # reversible.
8
8
  #
9
9
  # @example
@@ -16,23 +16,15 @@ module RuboCop
16
16
  #
17
17
  # # good
18
18
  # def change
19
- # create_table :users do |t|
20
- # t.string :name
19
+ # change_table :users do |t|
20
+ # t.remove :name, :string
21
21
  # end
22
22
  # end
23
23
  #
24
24
  # # good
25
25
  # def change
26
- # reversible do |dir|
27
- # change_table :users do |t|
28
- # dir.up do
29
- # t.column :name, :string
30
- # end
31
- #
32
- # dir.down do
33
- # t.remove :name
34
- # end
35
- # end
26
+ # create_table :users do |t|
27
+ # t.string :name
36
28
  # end
37
29
  # end
38
30
  #
@@ -114,21 +106,6 @@ module RuboCop
114
106
  # end
115
107
  # end
116
108
  #
117
- # # good
118
- # def change
119
- # reversible do |dir|
120
- # change_table :users do |t|
121
- # dir.up do
122
- # t.change :price, :string
123
- # end
124
- #
125
- # dir.down do
126
- # t.change :price, :integer
127
- # end
128
- # end
129
- # end
130
- # end
131
- #
132
109
  # @example
133
110
  # # remove_columns
134
111
  #
@@ -173,8 +150,6 @@ module RuboCop
173
150
  # def change
174
151
  # remove_index :users, column: :email
175
152
  # end
176
- #
177
- # @see https://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html
178
153
  class ReversibleMigration < Base
179
154
  include MigrationsHelper
180
155
 
@@ -229,6 +204,8 @@ module RuboCop
229
204
  check_change_table_node(node.send_node, node.body)
230
205
  end
231
206
 
207
+ alias on_numblock on_block
208
+
232
209
  private
233
210
 
234
211
  def check_irreversible_schema_statement_node(node)
@@ -240,10 +217,7 @@ module RuboCop
240
217
  def check_drop_table_node(node)
241
218
  drop_table_call(node) do
242
219
  unless node.parent.block_type? || node.last_argument.block_pass_type?
243
- add_offense(
244
- node,
245
- message: format(MSG, action: 'drop_table(without block)')
246
- )
220
+ add_offense(node, message: format(MSG, action: 'drop_table(without block)'))
247
221
  end
248
222
  end
249
223
  end
@@ -251,22 +225,12 @@ module RuboCop
251
225
  def check_reversible_hash_node(node)
252
226
  return if reversible_change_table_call?(node)
253
227
 
254
- add_offense(
255
- node,
256
- message: format(
257
- MSG, action: "#{node.method_name}(without :from and :to)"
258
- )
259
- )
228
+ add_offense(node, message: format(MSG, action: "#{node.method_name}(without :from and :to)"))
260
229
  end
261
230
 
262
231
  def check_remove_column_node(node)
263
232
  remove_column_call(node) do |args|
264
- if args.to_a.size < 3
265
- add_offense(
266
- node,
267
- message: format(MSG, action: 'remove_column(without type)')
268
- )
269
- end
233
+ add_offense(node, message: format(MSG, action: 'remove_column(without type)')) if args.to_a.size < 3
270
234
  end
271
235
  end
272
236
 
@@ -295,10 +259,7 @@ module RuboCop
295
259
  unless all_hash_key?(args, :type) && target_rails_version >= 6.1
296
260
  action = target_rails_version >= 6.1 ? 'remove_columns(without type)' : 'remove_columns'
297
261
 
298
- add_offense(
299
- node,
300
- message: format(MSG, action: action)
301
- )
262
+ add_offense(node, message: format(MSG, action: action))
302
263
  end
303
264
  end
304
265
  end
@@ -306,18 +267,14 @@ module RuboCop
306
267
  def check_remove_index_node(node)
307
268
  remove_index_call(node) do |args|
308
269
  if args.hash_type? && !all_hash_key?(args, :column)
309
- add_offense(
310
- node,
311
- message: format(MSG, action: 'remove_index(without column)')
312
- )
270
+ add_offense(node, message: format(MSG, action: 'remove_index(without column)'))
313
271
  end
314
272
  end
315
273
  end
316
274
 
317
275
  def check_change_table_offense(receiver, node)
318
276
  method_name = node.method_name
319
- return if receiver != node.receiver &&
320
- reversible_change_table_call?(node)
277
+ return if receiver != node.receiver && reversible_change_table_call?(node)
321
278
 
322
279
  action = if method_name == :remove
323
280
  target_rails_version >= 6.1 ? 't.remove (without type)' : 't.remove'
@@ -325,10 +282,7 @@ module RuboCop
325
282
  "change_table(with #{method_name})"
326
283
  end
327
284
 
328
- add_offense(
329
- node,
330
- message: format(MSG, action: action)
331
- )
285
+ add_offense(node, message: format(MSG, action: action))
332
286
  end
333
287
 
334
288
  def reversible_change_table_call?(node)
@@ -353,9 +307,7 @@ module RuboCop
353
307
 
354
308
  def within_reversible_or_up_only_block?(node)
355
309
  node.each_ancestor(:block).any? do |ancestor|
356
- (ancestor.block_type? &&
357
- ancestor.send_node.method?(:reversible)) ||
358
- ancestor.send_node.method?(:up_only)
310
+ (ancestor.block_type? && ancestor.send_node.method?(:reversible)) || ancestor.send_node.method?(:up_only)
359
311
  end
360
312
  end
361
313
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks whether the migration implements
6
+ # Checks whether the migration implements
7
7
  # either a `change` method or both an `up` and a `down`
8
8
  # method.
9
9
  #
@@ -45,19 +45,18 @@ module RuboCop
45
45
  class ReversibleMigrationMethodDefinition < Base
46
46
  include MigrationsHelper
47
47
 
48
- MSG = 'Migrations must contain either a `change` method, or ' \
49
- 'both an `up` and a `down` method.'
48
+ MSG = 'Migrations must contain either a `change` method, or both an `up` and a `down` method.'
50
49
 
51
50
  def_node_matcher :change_method?, <<~PATTERN
52
- [ #migration_class? `(def :change (args) _) ]
51
+ `(def :change (args) _)
53
52
  PATTERN
54
53
 
55
54
  def_node_matcher :up_and_down_methods?, <<~PATTERN
56
- [ #migration_class? `(def :up (args) _) `(def :down (args) _) ]
55
+ [`(def :up (args) _) `(def :down (args) _)]
57
56
  PATTERN
58
57
 
59
58
  def on_class(node)
60
- return if change_method?(node) || up_and_down_methods?(node)
59
+ return if !migration_class?(node) || change_method?(node) || up_and_down_methods?(node)
61
60
 
62
61
  add_offense(node)
63
62
  end
@@ -39,7 +39,7 @@ module RuboCop
39
39
  def on_send(node)
40
40
  evidence(node) do |rails_node, args|
41
41
  add_offense(node, message: format(MSG, root: rails_node.source)) do |corrector|
42
- range = range_between(rails_node.loc.selector.end_pos, node.loc.expression.end_pos)
42
+ range = range_between(rails_node.loc.selector.end_pos, node.source_range.end_pos)
43
43
  replacement = ".join(#{args.map(&:source).join(', ')})"
44
44
 
45
45
  corrector.replace(range, replacement)
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Use `Rails.root` IO methods instead of passing it to `File`.
7
+ #
8
+ # `Rails.root` is an instance of `Pathname`
9
+ # so we can apply many IO methods directly.
10
+ #
11
+ # This cop works best when used together with
12
+ # `Style/FileRead`, `Style/FileWrite` and `Rails/RootJoinChain`.
13
+ #
14
+ # @safety
15
+ # This cop is unsafe for autocorrection because `Dir`'s `children`, `each_child`, `entries`, and `glob`
16
+ # methods return string element, but these methods of `Pathname` return `Pathname` element.
17
+ #
18
+ # @example
19
+ # # bad
20
+ # File.open(Rails.root.join('db', 'schema.rb'))
21
+ # File.open(Rails.root.join('db', 'schema.rb'), 'w')
22
+ # File.read(Rails.root.join('db', 'schema.rb'))
23
+ # File.binread(Rails.root.join('db', 'schema.rb'))
24
+ # File.write(Rails.root.join('db', 'schema.rb'), content)
25
+ # File.binwrite(Rails.root.join('db', 'schema.rb'), content)
26
+ #
27
+ # # good
28
+ # Rails.root.join('db', 'schema.rb').open
29
+ # Rails.root.join('db', 'schema.rb').open('w')
30
+ # Rails.root.join('db', 'schema.rb').read
31
+ # Rails.root.join('db', 'schema.rb').binread
32
+ # Rails.root.join('db', 'schema.rb').write(content)
33
+ # Rails.root.join('db', 'schema.rb').binwrite(content)
34
+ #
35
+ class RootPathnameMethods < Base
36
+ extend AutoCorrector
37
+ include RangeHelp
38
+
39
+ MSG = '`%<rails_root>s` is a `Pathname` so you can just append `#%<method>s`.'
40
+
41
+ DIR_METHODS = %i[children delete each_child empty? entries exist? glob mkdir open rmdir unlink].to_set.freeze
42
+
43
+ FILE_METHODS = %i[
44
+ atime
45
+ basename
46
+ binread
47
+ binwrite
48
+ birthtime
49
+ blockdev?
50
+ chardev?
51
+ chmod
52
+ chown
53
+ ctime
54
+ delete
55
+ directory?
56
+ dirname
57
+ empty?
58
+ executable?
59
+ executable_real?
60
+ exist?
61
+ expand_path
62
+ extname
63
+ file?
64
+ fnmatch
65
+ fnmatch?
66
+ ftype
67
+ grpowned?
68
+ join
69
+ lchmod
70
+ lchown
71
+ lstat
72
+ mtime
73
+ open
74
+ owned?
75
+ pipe?
76
+ read
77
+ readable?
78
+ readable_real?
79
+ readlines
80
+ readlink
81
+ realdirpath
82
+ realpath
83
+ rename
84
+ setgid?
85
+ setuid?
86
+ size
87
+ size?
88
+ socket?
89
+ split
90
+ stat
91
+ sticky?
92
+ symlink?
93
+ sysopen
94
+ truncate
95
+ unlink
96
+ utime
97
+ world_readable?
98
+ world_writable?
99
+ writable?
100
+ writable_real?
101
+ write
102
+ zero?
103
+ ].to_set.freeze
104
+
105
+ FILE_TEST_METHODS = %i[
106
+ blockdev?
107
+ chardev?
108
+ directory?
109
+ empty?
110
+ executable?
111
+ executable_real?
112
+ exist?
113
+ file?
114
+ grpowned?
115
+ owned?
116
+ pipe?
117
+ readable?
118
+ readable_real?
119
+ setgid?
120
+ setuid?
121
+ size
122
+ size?
123
+ socket?
124
+ sticky?
125
+ symlink?
126
+ world_readable?
127
+ world_writable?
128
+ writable?
129
+ writable_real?
130
+ zero?
131
+ ].to_set.freeze
132
+
133
+ FILE_UTILS_METHODS = %i[chmod chown mkdir mkpath rmdir rmtree].to_set.freeze
134
+
135
+ RESTRICT_ON_SEND = (DIR_METHODS + FILE_METHODS + FILE_TEST_METHODS + FILE_UTILS_METHODS).to_set.freeze
136
+
137
+ def_node_matcher :pathname_method, <<~PATTERN
138
+ {
139
+ (send (const {nil? cbase} :Dir) $DIR_METHODS $_ $...)
140
+ (send (const {nil? cbase} {:IO :File}) $FILE_METHODS $_ $...)
141
+ (send (const {nil? cbase} :FileTest) $FILE_TEST_METHODS $_ $...)
142
+ (send (const {nil? cbase} :FileUtils) $FILE_UTILS_METHODS $_ $...)
143
+ }
144
+ PATTERN
145
+
146
+ def_node_matcher :dir_glob?, <<~PATTERN
147
+ (send
148
+ (const {cbase nil?} :Dir) :glob ...)
149
+ PATTERN
150
+
151
+ def_node_matcher :rails_root_pathname?, <<~PATTERN
152
+ {
153
+ $#rails_root?
154
+ (send $#rails_root? :join ...)
155
+ }
156
+ PATTERN
157
+
158
+ # @!method rails_root?(node)
159
+ def_node_matcher :rails_root?, <<~PATTERN
160
+ (send (const {nil? cbase} :Rails) {:root :public_path})
161
+ PATTERN
162
+
163
+ def on_send(node)
164
+ evidence(node) do |method, path, args, rails_root|
165
+ add_offense(node, message: format(MSG, method: method, rails_root: rails_root.source)) do |corrector|
166
+ replacement = if dir_glob?(node)
167
+ build_path_glob_replacement(path, method)
168
+ else
169
+ build_path_replacement(path, method, args)
170
+ end
171
+
172
+ corrector.replace(node, replacement)
173
+ end
174
+ end
175
+ end
176
+
177
+ private
178
+
179
+ def evidence(node)
180
+ return if node.method?(:open) && node.parent&.send_type?
181
+ return unless (method, path, args = pathname_method(node)) && (rails_root = rails_root_pathname?(path))
182
+
183
+ yield(method, path, args, rails_root)
184
+ end
185
+
186
+ def build_path_glob_replacement(path, method)
187
+ receiver = range_between(path.source_range.begin_pos, path.children.first.loc.selector.end_pos).source
188
+
189
+ argument = path.arguments.one? ? path.first_argument.source : join_arguments(path.arguments)
190
+
191
+ "#{receiver}.#{method}(#{argument})"
192
+ end
193
+
194
+ def build_path_replacement(path, method, args)
195
+ path_replacement = path.source
196
+ if path.arguments? && !path.parenthesized_call?
197
+ path_replacement[' '] = '('
198
+ path_replacement << ')'
199
+ end
200
+
201
+ replacement = "#{path_replacement}.#{method}"
202
+ replacement += "(#{args.map(&:source).join(', ')})" unless args.empty?
203
+ replacement
204
+ end
205
+
206
+ def include_interpolation?(arguments)
207
+ arguments.any? do |argument|
208
+ argument.children.any? { |child| child.respond_to?(:begin_type?) && child.begin_type? }
209
+ end
210
+ end
211
+
212
+ def join_arguments(arguments)
213
+ use_interpolation = false
214
+
215
+ joined_arguments = arguments.map do |arg|
216
+ if arg.respond_to?(:value)
217
+ arg.value
218
+ else
219
+ use_interpolation = true
220
+ "\#{#{arg.source}}"
221
+ end
222
+ end.join('/')
223
+ quote = enforce_double_quotes? || include_interpolation?(arguments) || use_interpolation ? '"' : "'"
224
+
225
+ "#{quote}#{joined_arguments}#{quote}"
226
+ end
227
+
228
+ def enforce_double_quotes?
229
+ string_literals_config['EnforcedStyle'] == 'double_quotes'
230
+ end
231
+
232
+ def string_literals_config
233
+ config.for_cop('Style/StringLiterals')
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end