rubocop-rails 2.14.2 → 2.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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