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
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Checks for Rails framework classes that are patched directly instead of using Active Support load hooks. Direct
7
+ # patching forcibly loads the framework referenced, using hooks defers loading until it's actually needed.
8
+ #
9
+ # @safety
10
+ # While using lazy load hooks is recommended, it changes the order in which is code is loaded and may reveal
11
+ # load order dependency bugs.
12
+ #
13
+ # @example
14
+ #
15
+ # # bad
16
+ # ActiveRecord::Base.include(MyClass)
17
+ #
18
+ # # good
19
+ # ActiveSupport.on_load(:active_record) { include MyClass }
20
+ class ActiveSupportOnLoad < Base
21
+ extend AutoCorrector
22
+
23
+ MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
24
+ RESTRICT_ON_SEND = %i[prepend include extend].freeze
25
+ LOAD_HOOKS = {
26
+ 'ActionCable' => 'action_cable',
27
+ 'ActionCable::Channel::Base' => 'action_cable_channel',
28
+ 'ActionCable::Connection::Base' => 'action_cable_connection',
29
+ 'ActionCable::Connection::TestCase' => 'action_cable_connection_test_case',
30
+ 'ActionController::API' => 'action_controller',
31
+ 'ActionController::Base' => 'action_controller',
32
+ 'ActionController::TestCase' => 'action_controller_test_case',
33
+ 'ActionDispatch::IntegrationTest' => 'action_dispatch_integration_test',
34
+ 'ActionDispatch::Request' => 'action_dispatch_request',
35
+ 'ActionDispatch::Response' => 'action_dispatch_response',
36
+ 'ActionDispatch::SystemTestCase' => 'action_dispatch_system_test_case',
37
+ 'ActionMailbox::Base' => 'action_mailbox',
38
+ 'ActionMailbox::InboundEmail' => 'action_mailbox_inbound_email',
39
+ 'ActionMailbox::Record' => 'action_mailbox_record',
40
+ 'ActionMailbox::TestCase' => 'action_mailbox_test_case',
41
+ 'ActionMailer::Base' => 'action_mailer',
42
+ 'ActionMailer::TestCase' => 'action_mailer_test_case',
43
+ 'ActionText::Content' => 'action_text_content',
44
+ 'ActionText::Record' => 'action_text_record',
45
+ 'ActionText::RichText' => 'action_text_rich_text',
46
+ 'ActionView::Base' => 'action_view',
47
+ 'ActionView::TestCase' => 'action_view_test_case',
48
+ 'ActiveJob::Base' => 'active_job',
49
+ 'ActiveJob::TestCase' => 'active_job_test_case',
50
+ 'ActiveRecord::Base' => 'active_record',
51
+ 'ActiveStorage::Attachment' => 'active_storage_attachment',
52
+ 'ActiveStorage::Blob' => 'active_storage_blob',
53
+ 'ActiveStorage::Record' => 'active_storage_record',
54
+ 'ActiveStorage::VariantRecord' => 'active_storage_variant_record',
55
+ 'ActiveSupport::TestCase' => 'active_support_test_case'
56
+ }.freeze
57
+
58
+ def on_send(node)
59
+ receiver, method, arguments = *node # rubocop:disable InternalAffairs/NodeDestructuring
60
+ return unless receiver && (hook = LOAD_HOOKS[receiver.const_name])
61
+
62
+ preferred = "ActiveSupport.on_load(:#{hook}) { #{method} #{arguments.source} }"
63
+ add_offense(node, message: format(MSG, prefer: preferred, current: node.source)) do |corrector|
64
+ corrector.replace(node, preferred)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for migrations using `add_column` that have an `index`
6
+ # Checks for migrations using `add_column` that have an `index`
7
7
  # key. `add_column` does not accept `index`, but also does not raise an
8
8
  # error for extra keys, so it is possible to mistakenly add the key without
9
9
  # realizing it will not actually add an index.
@@ -42,7 +42,7 @@ module RuboCop
42
42
  add_index_opts = ''
43
43
 
44
44
  if value.hash_type?
45
- hash = value.loc.expression.adjust(begin_pos: 1, end_pos: -1).source.strip
45
+ hash = value.source_range.adjust(begin_pos: 1, end_pos: -1).source.strip
46
46
  add_index_opts = ", #{hash}"
47
47
  end
48
48
 
@@ -53,10 +53,7 @@ module RuboCop
53
53
  private
54
54
 
55
55
  def index_range(pair_node)
56
- range_with_surrounding_comma(
57
- range_with_surrounding_space(range: pair_node.loc.expression, side: :left),
58
- :left
59
- )
56
+ range_with_surrounding_comma(range_with_surrounding_space(pair_node.source_range, side: :left), :left)
60
57
  end
61
58
  end
62
59
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop enforces that there is only one call to `after_commit`
6
+ # Enforces that there is only one call to `after_commit`
7
7
  # (and its aliases - `after_create_commit`, `after_update_commit`,
8
8
  # and `after_destroy_commit`) with the same callback name per model.
9
9
  #
@@ -32,6 +32,8 @@ module RuboCop
32
32
  # after_update_commit :log_update_action
33
33
  #
34
34
  class AfterCommitOverride < Base
35
+ include ClassSendNodeHelper
36
+
35
37
  MSG = 'There can only be one `after_*_commit :%<name>s` hook defined for a model.'
36
38
 
37
39
  AFTER_COMMIT_CALLBACKS = %i[
@@ -63,18 +65,6 @@ module RuboCop
63
65
  end
64
66
  end
65
67
 
66
- def class_send_nodes(class_node)
67
- class_def = class_node.body
68
-
69
- return [] unless class_def
70
-
71
- if class_def.send_type?
72
- [class_def]
73
- else
74
- class_def.each_child_node(:send).to_a
75
- end
76
- end
77
-
78
68
  def after_commit_callback?(node)
79
69
  AFTER_COMMIT_CALLBACKS.include?(node.method_name)
80
70
  end
@@ -3,7 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks that controllers subclass ApplicationController.
6
+ # Checks that controllers subclass `ApplicationController`.
7
+ #
8
+ # @safety
9
+ # This cop's autocorrection is unsafe because it may let the logic from `ApplicationController`
10
+ # sneak into a controller that is not purposed to inherit logic common among other controllers.
7
11
  #
8
12
  # @example
9
13
  #
@@ -21,7 +25,7 @@ module RuboCop
21
25
 
22
26
  MSG = 'Controllers should subclass `ApplicationController`.'
23
27
  SUPERCLASS = 'ApplicationController'
24
- BASE_PATTERN = '(const (const nil? :ActionController) :Base)'
28
+ BASE_PATTERN = '(const (const {nil? cbase} :ActionController) :Base)'
25
29
 
26
30
  # rubocop:disable Layout/ClassStructure
27
31
  include RuboCop::Cop::EnforceSuperclass
@@ -3,7 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks that jobs subclass ApplicationJob with Rails 5.0.
6
+ # Checks that jobs subclass `ApplicationJob` with Rails 5.0.
7
+ #
8
+ # @safety
9
+ # This cop's autocorrection is unsafe because it may let the logic from `ApplicationJob`
10
+ # sneak into a job that is not purposed to inherit logic common among other jobs.
7
11
  #
8
12
  # @example
9
13
  #
@@ -24,7 +28,7 @@ module RuboCop
24
28
 
25
29
  MSG = 'Jobs should subclass `ApplicationJob`.'
26
30
  SUPERCLASS = 'ApplicationJob'
27
- BASE_PATTERN = '(const (const nil? :ActiveJob) :Base)'
31
+ BASE_PATTERN = '(const (const {nil? cbase} :ActiveJob) :Base)'
28
32
 
29
33
  # rubocop:disable Layout/ClassStructure
30
34
  include RuboCop::Cop::EnforceSuperclass
@@ -32,7 +36,7 @@ module RuboCop
32
36
 
33
37
  def autocorrect(node)
34
38
  lambda do |corrector|
35
- corrector.replace(node.source_range, self.class::SUPERCLASS)
39
+ corrector.replace(node, self.class::SUPERCLASS)
36
40
  end
37
41
  end
38
42
  end
@@ -3,7 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks that mailers subclass ApplicationMailer with Rails 5.0.
6
+ # Checks that mailers subclass `ApplicationMailer` with Rails 5.0.
7
+ #
8
+ # @safety
9
+ # This cop's autocorrection is unsafe because it may let the logic from `ApplicationMailer`
10
+ # sneak into a mailer that is not purposed to inherit logic common among other mailers.
7
11
  #
8
12
  # @example
9
13
  #
@@ -24,7 +28,7 @@ module RuboCop
24
28
 
25
29
  MSG = 'Mailers should subclass `ApplicationMailer`.'
26
30
  SUPERCLASS = 'ApplicationMailer'
27
- BASE_PATTERN = '(const (const nil? :ActionMailer) :Base)'
31
+ BASE_PATTERN = '(const (const {nil? cbase} :ActionMailer) :Base)'
28
32
 
29
33
  # rubocop:disable Layout/ClassStructure
30
34
  include RuboCop::Cop::EnforceSuperclass
@@ -3,7 +3,12 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks that models subclass ApplicationRecord with Rails 5.0.
6
+ # Checks that models subclass `ApplicationRecord` with Rails 5.0.
7
+ #
8
+ # @safety
9
+ # This cop's autocorrection is unsafe because it may let the logic from `ApplicationRecord`
10
+ # sneak into an Active Record model that is not purposed to inherit logic common among other
11
+ # Active Record models.
7
12
  #
8
13
  # @example
9
14
  #
@@ -24,7 +29,7 @@ module RuboCop
24
29
 
25
30
  MSG = 'Models should subclass `ApplicationRecord`.'
26
31
  SUPERCLASS = 'ApplicationRecord'
27
- BASE_PATTERN = '(const (const nil? :ActiveRecord) :Base)'
32
+ BASE_PATTERN = '(const (const {nil? cbase} :ActiveRecord) :Base)'
28
33
 
29
34
  # rubocop:disable Layout/ClassStructure
30
35
  include RuboCop::Cop::EnforceSuperclass
@@ -3,13 +3,19 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop prevents usage of `"*"` on an Arel::Table column reference.
6
+ # Prevents usage of `"*"` on an Arel::Table column reference.
7
7
  #
8
8
  # Using `arel_table["*"]` causes the outputted string to be a literal
9
9
  # quoted asterisk (e.g. <tt>`my_model`.`*`</tt>). This causes the
10
10
  # database to look for a column named <tt>`*`</tt> (or `"*"`) as opposed
11
11
  # to expanding the column list as one would likely expect.
12
12
  #
13
+ # @safety
14
+ # This cop's autocorrection is unsafe because it turns a quoted `*` into
15
+ # an SQL `*`, unquoted. `*` is a valid column name in certain databases
16
+ # supported by Rails, and even though it is usually a mistake,
17
+ # it might denote legitimate access to a column named `*`.
18
+ #
13
19
  # @example
14
20
  # # bad
15
21
  # MyTable.arel_table["*"]
@@ -32,7 +38,7 @@ module RuboCop
32
38
  return unless (star = star_bracket?(node))
33
39
 
34
40
  add_offense(star) do |corrector|
35
- corrector.replace(star.loc.expression, 'Arel.star')
41
+ corrector.replace(star, 'Arel.star')
36
42
  end
37
43
  end
38
44
  end
@@ -25,7 +25,7 @@ module RuboCop
25
25
  return unless offensive?(node)
26
26
 
27
27
  add_offense(node) do |corrector|
28
- expression = node.loc.expression
28
+ expression = node.source_range
29
29
 
30
30
  corrector.replace(expression, corrected_source(expression.source))
31
31
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop looks for `attribute` class methods that specify a `:default` option
6
+ # Looks for `attribute` class methods that specify a `:default` option
7
7
  # which value is an array, string literal or method call without a block.
8
8
  # It will accept all other values, such as string, symbol, integer and float literals
9
9
  # as well as constants.
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop looks for belongs_to associations where we control whether the
6
+ # Looks for belongs_to associations where we control whether the
7
7
  # association is required via the deprecated `required` option instead.
8
8
  #
9
9
  # Since Rails 5, belongs_to associations are required by default and this
@@ -47,9 +47,6 @@ module RuboCop
47
47
  # class Post < ApplicationRecord
48
48
  # belongs_to :blog, optional: false
49
49
  # end
50
- #
51
- # @see https://guides.rubyonrails.org/5_0_release_notes.html
52
- # @see https://github.com/rails/rails/pull/18937
53
50
  class BelongsTo < Base
54
51
  extend AutoCorrector
55
52
  extend TargetRailsVersion
@@ -83,7 +80,7 @@ module RuboCop
83
80
  end
84
81
 
85
82
  add_offense(node.loc.selector, message: message) do |corrector|
86
- corrector.replace(option_node.loc.expression, replacement)
83
+ corrector.replace(option_node, replacement)
87
84
  end
88
85
  end
89
86
  end
@@ -3,17 +3,18 @@
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#blank?` defined by Active Support.
8
8
  #
9
- # This cop is marked as unsafe auto-correction, because `' '.empty?` returns false,
10
- # but `' '.blank?` returns true. Therefore, auto-correction is not compatible
11
- # if the receiver is a non-empty blank string, tab, or newline meta characters.
12
- #
13
9
  # Interaction with `Style/UnlessElse`:
14
10
  # The configuration of `NotPresent` will not produce an offense in the
15
- # context of `unless else` if `Style/UnlessElse` is inabled. This is
16
- # 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
+ #
14
+ # @safety
15
+ # This cop is unsafe autocorrection, because `' '.empty?` returns false,
16
+ # but `' '.blank?` returns true. Therefore, autocorrection is not compatible
17
+ # if the receiver is a non-empty blank string, tab, or newline meta characters.
17
18
  #
18
19
  # @example NilOrEmpty: true (default)
19
20
  # # Converts usages of `nil? || empty?` to `blank?`
@@ -62,8 +63,7 @@ module RuboCop
62
63
 
63
64
  MSG_NIL_OR_EMPTY = 'Use `%<prefer>s` instead of `%<current>s`.'
64
65
  MSG_NOT_PRESENT = 'Use `%<prefer>s` instead of `%<current>s`.'
65
- MSG_UNLESS_PRESENT = 'Use `if %<prefer>s` instead of ' \
66
- '`%<current>s`.'
66
+ MSG_UNLESS_PRESENT = 'Use `if %<prefer>s` instead of `%<current>s`.'
67
67
  RESTRICT_ON_SEND = %i[!].freeze
68
68
 
69
69
  # `(send nil $_)` is not actually a valid match for an offense. Nodes
@@ -142,10 +142,10 @@ module RuboCop
142
142
 
143
143
  if method_call
144
144
  corrector.replace(node.loc.keyword, 'if')
145
- range = method_call.loc.expression
145
+ range = method_call.source_range
146
146
  else
147
147
  variable1, _variable2 = nil_or_empty?(node) || not_present?(node)
148
- range = node.loc.expression
148
+ range = node.source_range
149
149
  end
150
150
 
151
151
  corrector.replace(range, replacement(variable1))
@@ -153,9 +153,9 @@ module RuboCop
153
153
 
154
154
  def unless_condition(node, method_call)
155
155
  if node.modifier_form?
156
- node.loc.keyword.join(node.loc.expression.end)
156
+ node.loc.keyword.join(node.source_range.end)
157
157
  else
158
- node.loc.expression.begin.join(method_call.loc.expression)
158
+ node.source_range.begin.join(method_call.source_range)
159
159
  end
160
160
  end
161
161
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This Cop checks whether alter queries are combinable.
6
+ # Checks whether alter queries are combinable.
7
7
  # If combinable queries are detected, it suggests to you
8
8
  # to use `change_table` with `bulk: true` instead.
9
9
  # This option causes the migration to generate a single
@@ -62,9 +62,6 @@ module RuboCop
62
62
  # t.string :nickname
63
63
  # end
64
64
  # end
65
- #
66
- # @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-change_table
67
- # @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html
68
65
  class BulkChangeTable < Base
69
66
  MSG_FOR_CHANGE_TABLE = <<~MSG.chomp
70
67
  You can combine alter queries using `bulk: true` options.
@@ -111,25 +108,13 @@ module RuboCop
111
108
  remove_timestamps
112
109
  ].freeze
113
110
 
114
- MYSQL_COMBINABLE_TRANSFORMATIONS = %i[
115
- rename
116
- index
117
- remove_index
118
- ].freeze
111
+ MYSQL_COMBINABLE_TRANSFORMATIONS = %i[rename index remove_index].freeze
119
112
 
120
- MYSQL_COMBINABLE_ALTER_METHODS = %i[
121
- rename_column
122
- add_index
123
- remove_index
124
- ].freeze
113
+ MYSQL_COMBINABLE_ALTER_METHODS = %i[rename_column add_index remove_index].freeze
125
114
 
126
- POSTGRESQL_COMBINABLE_TRANSFORMATIONS = %i[
127
- change_default
128
- ].freeze
115
+ POSTGRESQL_COMBINABLE_TRANSFORMATIONS = %i[change_default].freeze
129
116
 
130
- POSTGRESQL_COMBINABLE_ALTER_METHODS = %i[
131
- change_column_default
132
- ].freeze
117
+ POSTGRESQL_COMBINABLE_ALTER_METHODS = %i[change_column_default].freeze
133
118
 
134
119
  def on_def(node)
135
120
  return unless support_bulk_alter?
@@ -155,25 +140,38 @@ module RuboCop
155
140
  return if include_bulk_options?(node)
156
141
  return unless node.block_node
157
142
 
158
- send_nodes = node.block_node.body.each_child_node(:send).to_a
159
-
160
- transformations = send_nodes.select do |send_node|
161
- combinable_transformations.include?(send_node.method_name)
162
- end
143
+ send_nodes = send_nodes_from_change_table_block(node.block_node.body)
163
144
 
164
- add_offense_for_change_table(node) if transformations.size > 1
145
+ add_offense_for_change_table(node) if count_transformations(send_nodes) > 1
165
146
  end
166
147
 
167
148
  private
168
149
 
150
+ def send_nodes_from_change_table_block(body)
151
+ if body.send_type?
152
+ [body]
153
+ else
154
+ body.each_child_node(:send).to_a
155
+ end
156
+ end
157
+
158
+ def count_transformations(send_nodes)
159
+ send_nodes.sum do |node|
160
+ if node.method?(:remove)
161
+ node.arguments.count { |arg| !arg.hash_type? }
162
+ else
163
+ combinable_transformations.include?(node.method_name) ? 1 : 0
164
+ end
165
+ end
166
+ end
167
+
169
168
  # @param node [RuboCop::AST::SendNode] (send nil? :change_table ...)
170
169
  def include_bulk_options?(node)
171
170
  # arguments: [{(sym :table)(str "table")} (hash (pair (sym :bulk) _))]
172
171
  options = node.arguments[1]
173
172
  return false unless options
174
173
 
175
- options.hash_type? &&
176
- options.keys.any? { |key| key.sym_type? && key.value == :bulk }
174
+ options.hash_type? && options.keys.any? { |key| key.sym_type? && key.value == :bulk }
177
175
  end
178
176
 
179
177
  def database
@@ -215,7 +213,7 @@ module RuboCop
215
213
  true
216
214
  when POSTGRESQL
217
215
  # Add bulk alter support for PostgreSQL in 5.2.0
218
- # @see https://github.com/rails/rails/pull/31331
216
+ # See: https://github.com/rails/rails/pull/31331
219
217
  target_rails_version >= 5.2
220
218
  else
221
219
  false
@@ -223,8 +221,7 @@ module RuboCop
223
221
  end
224
222
 
225
223
  def call_to_combinable_alter_method?(child_node)
226
- child_node.send_type? &&
227
- combinable_alter_methods.include?(child_node.method_name)
224
+ child_node.send_type? && combinable_alter_methods.include?(child_node.method_name)
228
225
  end
229
226
 
230
227
  def combinable_alter_methods
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Checks if collection can be blank-compacted with `compact_blank`.
7
+ #
8
+ # @safety
9
+ # It is unsafe by default because false positives may occur in the
10
+ # blank check of block arguments to the receiver object.
11
+ #
12
+ # For example, `[[1, 2], [3, nil]].reject { |first, second| second.blank? }` and
13
+ # `[[1, 2], [3, nil]].compact_blank` are not compatible. The same is true for `blank?`.
14
+ # This will work fine when the receiver is a hash object.
15
+ #
16
+ # And `compact_blank!` has different implementations for `Array`, `Hash`, and
17
+ # `ActionController::Parameters`.
18
+ # `Array#compact_blank!`, `Hash#compact_blank!` are equivalent to `delete_if(&:blank?)`.
19
+ # `ActionController::Parameters#compact_blank!` is equivalent to `reject!(&:blank?)`.
20
+ # If the cop makes a mistake, autocorrected code may get unexpected behavior.
21
+ #
22
+ # @example
23
+ #
24
+ # # bad
25
+ # collection.reject(&:blank?)
26
+ # collection.reject { |_k, v| v.blank? }
27
+ #
28
+ # # good
29
+ # collection.compact_blank
30
+ #
31
+ # # bad
32
+ # collection.delete_if(&:blank?) # Same behavior as `Array#compact_blank!` and `Hash#compact_blank!`
33
+ # collection.delete_if { |_k, v| v.blank? } # Same behavior as `Array#compact_blank!` and `Hash#compact_blank!`
34
+ # collection.reject!(&:blank?) # Same behavior as `ActionController::Parameters#compact_blank!`
35
+ # collection.reject! { |_k, v| v.blank? } # Same behavior as `ActionController::Parameters#compact_blank!`
36
+ #
37
+ # # good
38
+ # collection.compact_blank!
39
+ #
40
+ class CompactBlank < Base
41
+ include RangeHelp
42
+ extend AutoCorrector
43
+ extend TargetRailsVersion
44
+
45
+ MSG = 'Use `%<preferred_method>s` instead.'
46
+ RESTRICT_ON_SEND = %i[reject delete_if reject!].freeze
47
+
48
+ minimum_target_rails_version 6.1
49
+
50
+ def_node_matcher :reject_with_block?, <<~PATTERN
51
+ (block
52
+ (send _ {:reject :delete_if :reject!})
53
+ $(args ...)
54
+ (send
55
+ $(lvar _) :blank?))
56
+ PATTERN
57
+
58
+ def_node_matcher :reject_with_block_pass?, <<~PATTERN
59
+ (send _ {:reject :delete_if :reject!}
60
+ (block_pass
61
+ (sym :blank?)))
62
+ PATTERN
63
+
64
+ def on_send(node)
65
+ return unless bad_method?(node)
66
+
67
+ range = offense_range(node)
68
+ preferred_method = preferred_method(node)
69
+ add_offense(range, message: format(MSG, preferred_method: preferred_method)) do |corrector|
70
+ corrector.replace(range, preferred_method)
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def bad_method?(node)
77
+ return true if reject_with_block_pass?(node)
78
+
79
+ if (arguments, receiver_in_block = reject_with_block?(node.parent))
80
+ return use_single_value_block_argument?(arguments, receiver_in_block) ||
81
+ use_hash_value_block_argument?(arguments, receiver_in_block)
82
+ end
83
+
84
+ false
85
+ end
86
+
87
+ def use_single_value_block_argument?(arguments, receiver_in_block)
88
+ arguments.length == 1 && arguments[0].source == receiver_in_block.source
89
+ end
90
+
91
+ def use_hash_value_block_argument?(arguments, receiver_in_block)
92
+ arguments.length == 2 && arguments[1].source == receiver_in_block.source
93
+ end
94
+
95
+ def offense_range(node)
96
+ end_pos = if node.parent&.block_type? && node.parent&.send_node == node
97
+ node.parent.source_range.end_pos
98
+ else
99
+ node.source_range.end_pos
100
+ end
101
+
102
+ range_between(node.loc.selector.begin_pos, end_pos)
103
+ end
104
+
105
+ def preferred_method(node)
106
+ node.method?(:reject) ? 'compact_blank' : 'compact_blank!'
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end