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,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
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.
11
+ #
12
+ # @example
13
+ #
14
+ # # good
15
+ # class MyMailer < ApplicationMailer
16
+ # # ...
17
+ # end
18
+ #
19
+ # # bad
20
+ # class MyMailer < ActionMailer::Base
21
+ # # ...
22
+ # end
23
+ class ApplicationMailer < Base
24
+ extend AutoCorrector
25
+ extend TargetRailsVersion
26
+
27
+ minimum_target_rails_version 5.0
28
+
29
+ MSG = 'Mailers should subclass `ApplicationMailer`.'
30
+ SUPERCLASS = 'ApplicationMailer'
31
+ BASE_PATTERN = '(const (const {nil? cbase} :ActionMailer) :Base)'
32
+
33
+ # rubocop:disable Layout/ClassStructure
34
+ include RuboCop::Cop::EnforceSuperclass
35
+ # rubocop:enable Layout/ClassStructure
36
+ end
37
+ end
38
+ end
39
+ end
@@ -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
  #
@@ -16,24 +21,19 @@ module RuboCop
16
21
  # class Rails4Model < ActiveRecord::Base
17
22
  # # ...
18
23
  # end
19
- class ApplicationRecord < Cop
24
+ class ApplicationRecord < Base
25
+ extend AutoCorrector
20
26
  extend TargetRailsVersion
21
27
 
22
28
  minimum_target_rails_version 5.0
23
29
 
24
30
  MSG = 'Models should subclass `ApplicationRecord`.'
25
31
  SUPERCLASS = 'ApplicationRecord'
26
- BASE_PATTERN = '(const (const nil? :ActiveRecord) :Base)'
32
+ BASE_PATTERN = '(const (const {nil? cbase} :ActiveRecord) :Base)'
27
33
 
28
34
  # rubocop:disable Layout/ClassStructure
29
35
  include RuboCop::Cop::EnforceSuperclass
30
36
  # rubocop:enable Layout/ClassStructure
31
-
32
- def autocorrect(node)
33
- lambda do |corrector|
34
- corrector.replace(node.source_range, self.class::SUPERCLASS)
35
- end
36
- end
37
37
  end
38
38
  end
39
39
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Prevents usage of `"*"` on an Arel::Table column reference.
7
+ #
8
+ # Using `arel_table["*"]` causes the outputted string to be a literal
9
+ # quoted asterisk (e.g. <tt>`my_model`.`*`</tt>). This causes the
10
+ # database to look for a column named <tt>`*`</tt> (or `"*"`) as opposed
11
+ # to expanding the column list as one would likely expect.
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
+ #
19
+ # @example
20
+ # # bad
21
+ # MyTable.arel_table["*"]
22
+ #
23
+ # # good
24
+ # MyTable.arel_table[Arel.star]
25
+ #
26
+ class ArelStar < Base
27
+ extend AutoCorrector
28
+
29
+ MSG = 'Use `Arel.star` instead of `"*"` for expanded column lists.'
30
+
31
+ RESTRICT_ON_SEND = %i[[]].freeze
32
+
33
+ def_node_matcher :star_bracket?, <<~PATTERN
34
+ (send {const (send _ :arel_table)} :[] $(str "*"))
35
+ PATTERN
36
+
37
+ def on_send(node)
38
+ return unless (star = star_bracket?(node))
39
+
40
+ add_offense(star) do |corrector|
41
+ corrector.replace(star, 'Arel.star')
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -13,23 +13,21 @@ module RuboCop
13
13
  # # good
14
14
  # assert_not x
15
15
  #
16
- class AssertNot < RuboCop::Cop::Cop
16
+ class AssertNot < Base
17
+ extend AutoCorrector
18
+
17
19
  MSG = 'Prefer `assert_not` over `assert !`.'
20
+ RESTRICT_ON_SEND = %i[assert].freeze
18
21
 
19
22
  def_node_matcher :offensive?, '(send nil? :assert (send ... :!) ...)'
20
23
 
21
24
  def on_send(node)
22
- add_offense(node) if offensive?(node)
23
- end
25
+ return unless offensive?(node)
24
26
 
25
- def autocorrect(node)
26
- expression = node.loc.expression
27
+ add_offense(node) do |corrector|
28
+ expression = node.source_range
27
29
 
28
- lambda do |corrector|
29
- corrector.replace(
30
- expression,
31
- corrected_source(expression.source)
32
- )
30
+ corrector.replace(expression, corrected_source(expression.source))
33
31
  end
34
32
  end
35
33
 
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Looks for `attribute` class methods that specify a `:default` option
7
+ # which value is an array, string literal or method call without a block.
8
+ # It will accept all other values, such as string, symbol, integer and float literals
9
+ # as well as constants.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # class User < ApplicationRecord
14
+ # attribute :confirmed_at, :datetime, default: Time.zone.now
15
+ # end
16
+ #
17
+ # # good
18
+ # class User < ApplicationRecord
19
+ # attribute :confirmed_at, :datetime, default: -> { Time.zone.now }
20
+ # end
21
+ #
22
+ # # bad
23
+ # class User < ApplicationRecord
24
+ # attribute :roles, :string, array: true, default: []
25
+ # end
26
+ #
27
+ # # good
28
+ # class User < ApplicationRecord
29
+ # attribute :roles, :string, array: true, default: -> { [] }
30
+ # end
31
+ #
32
+ # # bad
33
+ # class User < ApplicationRecord
34
+ # attribute :configuration, default: {}
35
+ # end
36
+ #
37
+ # # good
38
+ # class User < ApplicationRecord
39
+ # attribute :configuration, default: -> { {} }
40
+ # end
41
+ #
42
+ # # good
43
+ # class User < ApplicationRecord
44
+ # attribute :role, :string, default: :customer
45
+ # end
46
+ #
47
+ # # good
48
+ # class User < ApplicationRecord
49
+ # attribute :activated, :boolean, default: false
50
+ # end
51
+ #
52
+ # # good
53
+ # class User < ApplicationRecord
54
+ # attribute :login_count, :integer, default: 0
55
+ # end
56
+ #
57
+ # # good
58
+ # class User < ApplicationRecord
59
+ # FOO = 123
60
+ # attribute :custom_attribute, :integer, default: FOO
61
+ # end
62
+ class AttributeDefaultBlockValue < Base
63
+ extend AutoCorrector
64
+
65
+ MSG = 'Pass method in a block to `:default` option.'
66
+ RESTRICT_ON_SEND = %i[attribute].freeze
67
+ TYPE_OFFENDERS = %i[send array hash].freeze
68
+
69
+ def_node_matcher :default_attribute, <<~PATTERN
70
+ (send nil? :attribute _ ?_ (hash <$#attribute ...>))
71
+ PATTERN
72
+
73
+ def_node_matcher :attribute, '(pair (sym :default) $_)'
74
+
75
+ def on_send(node)
76
+ default_attribute(node) do |attribute|
77
+ value = attribute.children.last
78
+ return unless TYPE_OFFENDERS.any?(value.type)
79
+
80
+ add_offense(value) do |corrector|
81
+ expression = default_attribute(node).children.last
82
+
83
+ corrector.replace(value, "-> { #{expression.source} }")
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -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,10 +47,8 @@ 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
- class BelongsTo < Cop
50
+ class BelongsTo < Base
51
+ extend AutoCorrector
54
52
  extend TargetRailsVersion
55
53
 
56
54
  minimum_target_rails_version 5.0
@@ -64,35 +62,25 @@ module RuboCop
64
62
  'option is deprecated and you want to use `optional: false`. ' \
65
63
  'In most configurations, this is the default and you can omit ' \
66
64
  'this option altogether'
65
+ RESTRICT_ON_SEND = %i[belongs_to].freeze
67
66
 
68
- def_node_matcher :match_belongs_to_with_options, <<-PATTERN
69
- (send _ :belongs_to _
67
+ def_node_matcher :match_belongs_to_with_options, <<~PATTERN
68
+ (send _ :belongs_to ...
70
69
  (hash <$(pair (sym :required) ${true false}) ...>)
71
70
  )
72
71
  PATTERN
73
72
 
74
73
  def on_send(node)
75
- match_belongs_to_with_options(node) do |_option_node, option_value|
76
- message =
74
+ match_belongs_to_with_options(node) do |option_node, option_value|
75
+ message, replacement =
77
76
  if option_value.true_type?
78
- SUPERFLOUS_REQUIRE_TRUE_MSG
77
+ [SUPERFLOUS_REQUIRE_TRUE_MSG, 'optional: false']
79
78
  elsif option_value.false_type?
80
- SUPERFLOUS_REQUIRE_FALSE_MSG
79
+ [SUPERFLOUS_REQUIRE_FALSE_MSG, 'optional: true']
81
80
  end
82
81
 
83
- add_offense(node, message: message, location: :selector)
84
- end
85
- end
86
-
87
- def autocorrect(node)
88
- option_node, option_value = match_belongs_to_with_options(node)
89
- return unless option_node
90
-
91
- lambda do |corrector|
92
- if option_value.true_type?
93
- corrector.replace(option_node.loc.expression, 'optional: false')
94
- elsif option_value.false_type?
95
- corrector.replace(option_node.loc.expression, 'optional: true')
82
+ add_offense(node.loc.selector, message: message) do |corrector|
83
+ corrector.replace(option_node, replacement)
96
84
  end
97
85
  end
98
86
  end
@@ -3,13 +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
9
  # Interaction with `Style/UnlessElse`:
10
10
  # The configuration of `NotPresent` 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
+ #
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.
13
18
  #
14
19
  # @example NilOrEmpty: true (default)
15
20
  # # Converts usages of `nil? || empty?` to `blank?`
@@ -53,17 +58,19 @@ module RuboCop
53
58
  # def blank?
54
59
  # !present?
55
60
  # end
56
- class Blank < Cop
61
+ class Blank < Base
62
+ extend AutoCorrector
63
+
57
64
  MSG_NIL_OR_EMPTY = 'Use `%<prefer>s` instead of `%<current>s`.'
58
65
  MSG_NOT_PRESENT = 'Use `%<prefer>s` instead of `%<current>s`.'
59
- MSG_UNLESS_PRESENT = 'Use `if %<prefer>s` instead of ' \
60
- '`%<current>s`.'
66
+ MSG_UNLESS_PRESENT = 'Use `if %<prefer>s` instead of `%<current>s`.'
67
+ RESTRICT_ON_SEND = %i[!].freeze
61
68
 
62
69
  # `(send nil $_)` is not actually a valid match for an offense. Nodes
63
70
  # that have a single method call on the left hand side
64
71
  # (`bar || foo.empty?`) will blow up when checking
65
72
  # `(send (:nil) :== $_)`.
66
- def_node_matcher :nil_or_empty?, <<-PATTERN
73
+ def_node_matcher :nil_or_empty?, <<~PATTERN
67
74
  (or
68
75
  {
69
76
  (send $_ :!)
@@ -82,7 +89,7 @@ module RuboCop
82
89
 
83
90
  def_node_matcher :defining_blank?, '(def :blank? (args) ...)'
84
91
 
85
- def_node_matcher :unless_present?, <<-PATTERN
92
+ def_node_matcher :unless_present?, <<~PATTERN
86
93
  (:if $(send $_ :present?) {nil? (...)} ...)
87
94
  PATTERN
88
95
 
@@ -93,10 +100,10 @@ module RuboCop
93
100
  # accepts !present? if its in the body of a `blank?` method
94
101
  next if defining_blank?(node.parent)
95
102
 
96
- add_offense(node,
97
- message: format(MSG_NOT_PRESENT,
98
- prefer: replacement(receiver),
99
- current: node.source))
103
+ message = format(MSG_NOT_PRESENT, prefer: replacement(receiver), current: node.source)
104
+ add_offense(node, message: message) do |corrector|
105
+ autocorrect(corrector, node)
106
+ end
100
107
  end
101
108
  end
102
109
 
@@ -106,10 +113,10 @@ module RuboCop
106
113
  nil_or_empty?(node) do |var1, var2|
107
114
  return unless var1 == var2
108
115
 
109
- add_offense(node,
110
- message: format(MSG_NIL_OR_EMPTY,
111
- prefer: replacement(var1),
112
- current: node.source))
116
+ message = format(MSG_NIL_OR_EMPTY, prefer: replacement(var1), current: node.source)
117
+ add_offense(node, message: message) do |corrector|
118
+ autocorrect(corrector, node)
119
+ end
113
120
  end
114
121
  end
115
122
 
@@ -121,37 +128,34 @@ module RuboCop
121
128
  unless_present?(node) do |method_call, receiver|
122
129
  range = unless_condition(node, method_call)
123
130
 
124
- add_offense(node,
125
- location: range,
126
- message: format(MSG_UNLESS_PRESENT,
127
- prefer: replacement(receiver),
128
- current: range.source))
131
+ message = format(MSG_UNLESS_PRESENT, prefer: replacement(receiver), current: range.source)
132
+ add_offense(range, message: message) do |corrector|
133
+ autocorrect(corrector, node)
134
+ end
129
135
  end
130
136
  end
131
137
 
132
- def autocorrect(node)
133
- lambda do |corrector|
134
- method_call, variable1 = unless_present?(node)
138
+ private
135
139
 
136
- if method_call
137
- corrector.replace(node.loc.keyword, 'if')
138
- range = method_call.loc.expression
139
- else
140
- variable1, _variable2 = nil_or_empty?(node) || not_present?(node)
141
- range = node.loc.expression
142
- end
140
+ def autocorrect(corrector, node)
141
+ method_call, variable1 = unless_present?(node)
143
142
 
144
- corrector.replace(range, replacement(variable1))
143
+ if method_call
144
+ corrector.replace(node.loc.keyword, 'if')
145
+ range = method_call.source_range
146
+ else
147
+ variable1, _variable2 = nil_or_empty?(node) || not_present?(node)
148
+ range = node.source_range
145
149
  end
146
- end
147
150
 
148
- private
151
+ corrector.replace(range, replacement(variable1))
152
+ end
149
153
 
150
154
  def unless_condition(node, method_call)
151
155
  if node.modifier_form?
152
- node.loc.keyword.join(node.loc.expression.end)
156
+ node.loc.keyword.join(node.source_range.end)
153
157
  else
154
- node.loc.expression.begin.join(method_call.loc.expression)
158
+ node.source_range.begin.join(method_call.source_range)
155
159
  end
156
160
  end
157
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,10 +62,7 @@ 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
- class BulkChangeTable < Cop
65
+ class BulkChangeTable < Base
69
66
  MSG_FOR_CHANGE_TABLE = <<~MSG.chomp
70
67
  You can combine alter queries using `bulk: true` options.
71
68
  MSG
@@ -111,26 +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
- change_column_null
133
- ].freeze
117
+ POSTGRESQL_COMBINABLE_ALTER_METHODS = %i[change_column_default].freeze
134
118
 
135
119
  def on_def(node)
136
120
  return unless support_bulk_alter?
@@ -139,9 +123,9 @@ module RuboCop
139
123
 
140
124
  recorder = AlterMethodsRecorder.new
141
125
 
142
- node.body.each_child_node(:send) do |send_node|
143
- if combinable_alter_methods.include?(send_node.method_name)
144
- recorder.process(send_node)
126
+ node.body.child_nodes.each do |child_node|
127
+ if call_to_combinable_alter_method? child_node
128
+ recorder.process(child_node)
145
129
  else
146
130
  recorder.flush
147
131
  end
@@ -156,25 +140,38 @@ module RuboCop
156
140
  return if include_bulk_options?(node)
157
141
  return unless node.block_node
158
142
 
159
- send_nodes = node.block_node.body.each_child_node(:send).to_a
143
+ send_nodes = send_nodes_from_change_table_block(node.block_node.body)
160
144
 
161
- transformations = send_nodes.select do |send_node|
162
- combinable_transformations.include?(send_node.method_name)
163
- end
164
-
165
- add_offense_for_change_table(node) if transformations.size > 1
145
+ add_offense_for_change_table(node) if count_transformations(send_nodes) > 1
166
146
  end
167
147
 
168
148
  private
169
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
+
170
168
  # @param node [RuboCop::AST::SendNode] (send nil? :change_table ...)
171
169
  def include_bulk_options?(node)
172
170
  # arguments: [{(sym :table)(str "table")} (hash (pair (sym :bulk) _))]
173
171
  options = node.arguments[1]
174
172
  return false unless options
175
173
 
176
- options.hash_type? &&
177
- options.keys.any? { |key| key.sym_type? && key.value == :bulk }
174
+ options.hash_type? && options.keys.any? { |key| key.sym_type? && key.value == :bulk }
178
175
  end
179
176
 
180
177
  def database
@@ -195,7 +192,11 @@ module RuboCop
195
192
  def database_yaml
196
193
  return nil unless File.exist?('config/database.yml')
197
194
 
198
- yaml = YAML.load_file('config/database.yml')
195
+ yaml = if YAML.respond_to?(:unsafe_load_file)
196
+ YAML.unsafe_load_file('config/database.yml')
197
+ else
198
+ YAML.load_file('config/database.yml')
199
+ end
199
200
  return nil unless yaml.is_a? Hash
200
201
 
201
202
  config = yaml['development']
@@ -212,13 +213,17 @@ module RuboCop
212
213
  true
213
214
  when POSTGRESQL
214
215
  # Add bulk alter support for PostgreSQL in 5.2.0
215
- # @see https://github.com/rails/rails/pull/31331
216
+ # See: https://github.com/rails/rails/pull/31331
216
217
  target_rails_version >= 5.2
217
218
  else
218
219
  false
219
220
  end
220
221
  end
221
222
 
223
+ def call_to_combinable_alter_method?(child_node)
224
+ child_node.send_type? && combinable_alter_methods.include?(child_node.method_name)
225
+ end
226
+
222
227
  def combinable_alter_methods
223
228
  case database
224
229
  when MYSQL