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
@@ -26,84 +26,72 @@ module RuboCop
26
26
  # # Here, `nil` is valid but `''` is not
27
27
  # validates :x, length: { is: 5 }, allow_nil: true, allow_blank: false
28
28
  #
29
- class RedundantAllowNil < Cop
29
+ class RedundantAllowNil < Base
30
30
  include RangeHelp
31
+ extend AutoCorrector
31
32
 
32
- MSG_SAME =
33
- '`allow_nil` is redundant when `allow_blank` has the same value.'
33
+ MSG_SAME = '`allow_nil` is redundant when `allow_blank` has the same value.'
34
34
 
35
- MSG_ALLOW_NIL_FALSE =
36
- '`allow_nil: false` is redundant when `allow_blank` is true.'
35
+ MSG_ALLOW_NIL_FALSE = '`allow_nil: false` is redundant when `allow_blank` is true.'
37
36
 
38
- def on_send(node)
39
- return unless node.method_name == :validates
37
+ RESTRICT_ON_SEND = %i[validates].freeze
40
38
 
39
+ def on_send(node)
41
40
  allow_nil, allow_blank = find_allow_nil_and_allow_blank(node)
42
41
  return unless allow_nil && allow_blank
43
42
 
44
43
  allow_nil_val = allow_nil.children.last
45
44
  allow_blank_val = allow_blank.children.last
46
45
 
47
- offense(allow_nil_val, allow_blank_val, allow_nil)
46
+ if allow_nil_val.type == allow_blank_val.type
47
+ register_offense(allow_nil, MSG_SAME)
48
+ elsif allow_nil_val.false_type? && allow_blank_val.true_type?
49
+ register_offense(allow_nil, MSG_ALLOW_NIL_FALSE)
50
+ end
48
51
  end
49
52
 
50
- def autocorrect(node)
51
- prv_sib = previous_sibling(node)
52
- nxt_sib = next_sibling(node)
53
+ private
54
+
55
+ def register_offense(allow_nil, message)
56
+ add_offense(allow_nil, message: message) do |corrector|
57
+ prv_sib = allow_nil.left_sibling
58
+ nxt_sib = allow_nil.right_sibling
53
59
 
54
- lambda do |corrector|
55
60
  if nxt_sib
56
- corrector.remove(range_between(node_beg(node), node_beg(nxt_sib)))
61
+ corrector.remove(range_between(node_beg(allow_nil), node_beg(nxt_sib)))
57
62
  elsif prv_sib
58
- corrector.remove(range_between(node_end(prv_sib), node_end(node)))
63
+ corrector.remove(range_between(node_end(prv_sib), node_end(allow_nil)))
59
64
  else
60
- corrector.remove(node.loc.expression)
65
+ corrector.remove(allow_nil)
61
66
  end
62
67
  end
63
68
  end
64
69
 
65
- private
66
-
67
- def offense(allow_nil_val, allow_blank_val, allow_nil)
68
- if allow_nil_val.type == allow_blank_val.type
69
- add_offense(allow_nil, message: MSG_SAME)
70
- elsif allow_nil_val.false_type? && allow_blank_val.true_type?
71
- add_offense(allow_nil, message: MSG_ALLOW_NIL_FALSE)
72
- end
73
- end
74
-
75
70
  def find_allow_nil_and_allow_blank(node)
76
- allow_nil = nil
77
- allow_blank = nil
78
-
79
- node.each_descendant do |descendant|
80
- next unless descendant.pair_type?
71
+ allow_nil, allow_blank = nil
81
72
 
82
- key = descendant.children.first.source
73
+ node.each_child_node do |child_node|
74
+ if child_node.pair_type?
75
+ key = child_node.children.first.source
83
76
 
84
- allow_nil = descendant if key == 'allow_nil'
85
- allow_blank = descendant if key == 'allow_blank'
77
+ allow_nil = child_node if key == 'allow_nil'
78
+ allow_blank = child_node if key == 'allow_blank'
79
+ end
80
+ return [allow_nil, allow_blank] if allow_nil && allow_blank
86
81
 
87
- break if allow_nil && allow_blank
82
+ found_in_children_nodes = find_allow_nil_and_allow_blank(child_node)
83
+ return found_in_children_nodes if found_in_children_nodes
88
84
  end
89
85
 
90
- [allow_nil, allow_blank]
91
- end
92
-
93
- def previous_sibling(node)
94
- node.parent.children[node.sibling_index - 1]
95
- end
96
-
97
- def next_sibling(node)
98
- node.parent.children[node.sibling_index + 1]
86
+ nil
99
87
  end
100
88
 
101
89
  def node_beg(node)
102
- node.loc.expression.begin_pos
90
+ node.source_range.begin_pos
103
91
  end
104
92
 
105
93
  def node_end(node)
106
- node.loc.expression.end_pos
94
+ node.source_range.end_pos
107
95
  end
108
96
  end
109
97
  end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Detects cases where the `:foreign_key` option on associations
7
+ # is redundant.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # class Post
12
+ # has_many :comments, foreign_key: 'post_id'
13
+ # end
14
+ #
15
+ # class Comment
16
+ # belongs_to :post, foreign_key: 'post_id'
17
+ # end
18
+ #
19
+ # # good
20
+ # class Post
21
+ # has_many :comments
22
+ # end
23
+ #
24
+ # class Comment
25
+ # belongs_to :author, foreign_key: 'user_id'
26
+ # end
27
+ class RedundantForeignKey < Base
28
+ include RangeHelp
29
+ extend AutoCorrector
30
+
31
+ MSG = 'Specifying the default value for `foreign_key` is redundant.'
32
+ RESTRICT_ON_SEND = %i[belongs_to has_one has_many has_and_belongs_to_many].freeze
33
+
34
+ def_node_matcher :association_with_foreign_key, <<~PATTERN
35
+ (send nil? ${:belongs_to :has_one :has_many :has_and_belongs_to_many} ({sym str} $_)
36
+ $(hash <$(pair (sym :foreign_key) ({sym str} $_)) ...>)
37
+ )
38
+ PATTERN
39
+
40
+ def on_send(node)
41
+ association_with_foreign_key(node) do |type, name, options, foreign_key_pair, foreign_key|
42
+ if redundant?(node, type, name, options, foreign_key)
43
+ add_offense(foreign_key_pair.source_range) do |corrector|
44
+ range = range_with_surrounding_space(foreign_key_pair.source_range, side: :left)
45
+ range = range_with_surrounding_comma(range, :left)
46
+
47
+ corrector.remove(range)
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def redundant?(node, association_type, association_name, options, foreign_key)
56
+ foreign_key.to_s == default_foreign_key(node, association_type, association_name, options)
57
+ end
58
+
59
+ def default_foreign_key(node, association_type, association_name, options)
60
+ if association_type == :belongs_to
61
+ "#{association_name}_id"
62
+ elsif (as = find_as_option(options))
63
+ "#{as}_id"
64
+ else
65
+ node.parent_module_name&.foreign_key
66
+ end
67
+ end
68
+
69
+ def find_as_option(options)
70
+ options.pairs.find do |pair|
71
+ pair.key.sym_type? && pair.key.value == :as
72
+ end&.value&.value
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,257 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Since Rails 5.0 the default for `belongs_to` is `optional: false`
7
+ # unless `config.active_record.belongs_to_required_by_default` is
8
+ # explicitly set to `false`. The presence validator is added
9
+ # automatically, and explicit presence validation is redundant.
10
+ #
11
+ # @safety
12
+ # This cop's autocorrection is unsafe because it changes the default error message
13
+ # from "can't be blank" to "must exist".
14
+ #
15
+ # @example
16
+ # # bad
17
+ # belongs_to :user
18
+ # validates :user, presence: true
19
+ #
20
+ # # bad
21
+ # belongs_to :user
22
+ # validates :user_id, presence: true
23
+ #
24
+ # # bad
25
+ # belongs_to :author, foreign_key: :user_id
26
+ # validates :user_id, presence: true
27
+ #
28
+ # # good
29
+ # belongs_to :user
30
+ #
31
+ # # good
32
+ # belongs_to :author, foreign_key: :user_id
33
+ #
34
+ class RedundantPresenceValidationOnBelongsTo < Base
35
+ include RangeHelp
36
+ extend AutoCorrector
37
+ extend TargetRailsVersion
38
+
39
+ MSG = 'Remove explicit presence validation for %<association>s.'
40
+ RESTRICT_ON_SEND = %i[validates].freeze
41
+
42
+ minimum_target_rails_version 5.0
43
+
44
+ # @!method presence_validation?(node)
45
+ # Match a `validates` statement with a presence check
46
+ #
47
+ # @example source that matches - by association
48
+ # validates :user, presence: true
49
+ #
50
+ # @example source that matches - by association
51
+ # validates :name, :user, presence: true
52
+ #
53
+ # @example source that matches - by a foreign key
54
+ # validates :user_id, presence: true
55
+ #
56
+ # @example source that DOES NOT match - strict validation
57
+ # validates :user_id, presence: true, strict: true
58
+ #
59
+ # @example source that DOES NOT match - custom strict validation
60
+ # validates :user_id, presence: true, strict: MissingUserError
61
+ def_node_matcher :presence_validation?, <<~PATTERN
62
+ (
63
+ send nil? :validates
64
+ (sym $_)+
65
+ $[
66
+ (hash <$(pair (sym :presence) true) ...>) # presence: true
67
+ !(hash <$(pair (sym :strict) {true const}) ...>) # strict: true
68
+ ]
69
+ )
70
+ PATTERN
71
+
72
+ # @!method optional?(node)
73
+ # Match a `belongs_to` association with an optional option in a hash
74
+ def_node_matcher :optional?, <<~PATTERN
75
+ (send nil? :belongs_to _ ... #optional_option?)
76
+ PATTERN
77
+
78
+ # @!method optional_option?(node)
79
+ # Match an optional option in a hash
80
+ def_node_matcher :optional_option?, <<~PATTERN
81
+ {
82
+ (hash <(pair (sym :optional) true) ...>) # optional: true
83
+ (hash <(pair (sym :required) false) ...>) # required: false
84
+ }
85
+ PATTERN
86
+
87
+ # @!method any_belongs_to?(node, association:)
88
+ # Match a class with `belongs_to` with no regard to `foreign_key` option
89
+ #
90
+ # @example source that matches
91
+ # belongs_to :user
92
+ #
93
+ # @example source that matches - regardless of `foreign_key`
94
+ # belongs_to :author, foreign_key: :user_id
95
+ #
96
+ # @param node [RuboCop::AST::Node]
97
+ # @param association [Symbol]
98
+ # @return [Array<RuboCop::AST::Node>, nil] matching node
99
+ def_node_matcher :any_belongs_to?, <<~PATTERN
100
+ (begin
101
+ <
102
+ $(send nil? :belongs_to (sym %association) ...)
103
+ ...
104
+ >
105
+ )
106
+ PATTERN
107
+
108
+ # @!method belongs_to?(node, key:, fk:)
109
+ # Match a class with a matching association, either by name or an explicit
110
+ # `foreign_key` option
111
+ #
112
+ # @example source that matches - fk matches `foreign_key` option
113
+ # belongs_to :author, foreign_key: :user_id
114
+ #
115
+ # @example source that matches - key matches association name
116
+ # belongs_to :user
117
+ #
118
+ # @example source that does not match - explicit `foreign_key` does not match
119
+ # belongs_to :user, foreign_key: :account_id
120
+ #
121
+ # @param node [RuboCop::AST::Node]
122
+ # @param key [Symbol] e.g. `:user`
123
+ # @param fk [Symbol] e.g. `:user_id`
124
+ # @return [Array<RuboCop::AST::Node>] matching nodes
125
+ def_node_matcher :belongs_to?, <<~PATTERN
126
+ (begin
127
+ <
128
+ ${
129
+ #belongs_to_without_fk?(%key) # belongs_to :user
130
+ #belongs_to_with_a_matching_fk?(%fk) # belongs_to :author, foreign_key: :user_id
131
+ }
132
+ ...
133
+ >
134
+ )
135
+ PATTERN
136
+
137
+ # @!method belongs_to_without_fk?(node, key)
138
+ # Match a matching `belongs_to` association, without an explicit `foreign_key` option
139
+ #
140
+ # @param node [RuboCop::AST::Node]
141
+ # @param key [Symbol] e.g. `:user`
142
+ # @return [Array<RuboCop::AST::Node>] matching nodes
143
+ def_node_matcher :belongs_to_without_fk?, <<~PATTERN
144
+ {
145
+ (send nil? :belongs_to (sym %1)) # belongs_to :user
146
+ (send nil? :belongs_to (sym %1) !hash ...) # belongs_to :user, -> { not_deleted }
147
+ (send nil? :belongs_to (sym %1) !(hash <(pair (sym :foreign_key) _) ...>))
148
+ }
149
+ PATTERN
150
+
151
+ # @!method belongs_to_with_a_matching_fk?(node, fk)
152
+ # Match a matching `belongs_to` association with a matching explicit `foreign_key` option
153
+ #
154
+ # @example source that matches
155
+ # belongs_to :author, foreign_key: :user_id
156
+ #
157
+ # @param node [RuboCop::AST::Node]
158
+ # @param fk [Symbol] e.g. `:user_id`
159
+ # @return [Array<RuboCop::AST::Node>] matching nodes
160
+ def_node_matcher :belongs_to_with_a_matching_fk?, <<~PATTERN
161
+ (send nil? :belongs_to ... (hash <(pair (sym :foreign_key) (sym %1)) ...>))
162
+ PATTERN
163
+
164
+ def on_send(node)
165
+ presence_validation?(node) do |all_keys, options, presence|
166
+ keys = non_optional_belongs_to(node.parent, all_keys)
167
+ return if keys.none?
168
+
169
+ add_offense_and_correct(node, all_keys, keys, options, presence)
170
+ end
171
+ end
172
+
173
+ private
174
+
175
+ def add_offense_and_correct(node, all_keys, keys, options, presence)
176
+ add_offense(presence, message: message_for(keys)) do |corrector|
177
+ if options.children.one? # `presence: true` is the only option
178
+ if keys == all_keys
179
+ remove_validation(corrector, node)
180
+ else
181
+ remove_keys_from_validation(corrector, node, keys)
182
+ end
183
+ elsif keys == all_keys
184
+ remove_presence_option(corrector, presence)
185
+ else
186
+ extract_validation_for_keys(corrector, node, keys, options)
187
+ end
188
+ end
189
+ end
190
+
191
+ def message_for(keys)
192
+ display_keys = keys.map { |key| "`#{key}`" }.join('/')
193
+ format(MSG, association: display_keys)
194
+ end
195
+
196
+ def non_optional_belongs_to(node, keys)
197
+ keys.select do |key|
198
+ belongs_to = belongs_to_for(node, key)
199
+ belongs_to && !optional?(belongs_to)
200
+ end
201
+ end
202
+
203
+ def belongs_to_for(model_class_node, key)
204
+ if key.to_s.end_with?('_id')
205
+ normalized_key = key.to_s.delete_suffix('_id').to_sym
206
+ belongs_to?(model_class_node, key: normalized_key, fk: key)
207
+ else
208
+ any_belongs_to?(model_class_node, association: key)
209
+ end
210
+ end
211
+
212
+ def remove_validation(corrector, node)
213
+ corrector.remove(validation_range(node))
214
+ end
215
+
216
+ def remove_keys_from_validation(corrector, node, keys)
217
+ keys.each do |key|
218
+ key_node = node.arguments.find { |arg| arg.value == key }
219
+ key_range = range_with_surrounding_space(
220
+ range_with_surrounding_comma(key_node.source_range, :right),
221
+ side: :right
222
+ )
223
+ corrector.remove(key_range)
224
+ end
225
+ end
226
+
227
+ def remove_presence_option(corrector, presence)
228
+ range = range_with_surrounding_comma(
229
+ range_with_surrounding_space(presence.source_range, side: :left),
230
+ :left
231
+ )
232
+ corrector.remove(range)
233
+ end
234
+
235
+ def extract_validation_for_keys(corrector, node, keys, options)
236
+ indentation = ' ' * node.source_range.column
237
+ options_without_presence = options.children.reject { |pair| pair.key.value == :presence }
238
+ source = [
239
+ indentation,
240
+ 'validates ',
241
+ keys.map(&:inspect).join(', '),
242
+ ', ',
243
+ options_without_presence.map(&:source).join(', '),
244
+ "\n"
245
+ ].join
246
+
247
+ remove_keys_from_validation(corrector, node, keys)
248
+ corrector.insert_after(validation_range(node), source)
249
+ end
250
+
251
+ def validation_range(node)
252
+ range_by_whole_lines(node.source_range, include_final_newline: true)
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for redundant receiver in `with_options`.
6
+ # Checks for redundant receiver in `with_options`.
7
7
  # Receiver is implicit from Rails 4.2 or higher.
8
8
  #
9
9
  # @example
@@ -54,57 +54,59 @@ module RuboCop
54
54
  # merger.invoke(another_receiver)
55
55
  # end
56
56
  # end
57
- class RedundantReceiverInWithOptions < Cop
58
- extend TargetRailsVersion
57
+ class RedundantReceiverInWithOptions < Base
59
58
  include RangeHelp
60
-
61
- minimum_target_rails_version 4.2
59
+ extend AutoCorrector
62
60
 
63
61
  MSG = 'Redundant receiver in `with_options`.'
64
62
 
65
- def_node_matcher :with_options?, <<-PATTERN
66
- (block
67
- (send nil? :with_options
68
- (...))
69
- (args
70
- $_arg)
71
- $_body)
72
- PATTERN
73
-
74
- def_node_search :all_block_nodes_in, <<-PATTERN
63
+ def_node_search :all_block_nodes_in, <<~PATTERN
75
64
  (block ...)
76
65
  PATTERN
77
66
 
78
- def_node_search :all_send_nodes_in, <<-PATTERN
67
+ def_node_search :all_send_nodes_in, <<~PATTERN
79
68
  (send ...)
80
69
  PATTERN
81
70
 
82
71
  def on_block(node)
83
- with_options?(node) do |arg, body|
84
- return if body.nil?
85
- return unless all_block_nodes_in(body).count.zero?
72
+ return unless node.method?(:with_options)
73
+ return unless (body = node.body)
74
+ return unless all_block_nodes_in(body).count.zero?
86
75
 
87
- send_nodes = all_send_nodes_in(body)
76
+ send_nodes = all_send_nodes_in(body)
77
+ return unless redundant_receiver?(send_nodes, node)
88
78
 
89
- if send_nodes.all? { |n| same_value?(arg, n.receiver) }
90
- send_nodes.each do |send_node|
91
- receiver = send_node.receiver
92
- add_offense(send_node, location: receiver.source_range)
93
- end
79
+ send_nodes.each do |send_node|
80
+ receiver = send_node.receiver
81
+ add_offense(receiver.source_range) do |corrector|
82
+ autocorrect(corrector, send_node, node)
94
83
  end
95
84
  end
96
85
  end
97
86
 
98
- def autocorrect(node)
99
- lambda do |corrector|
100
- corrector.remove(node.receiver.source_range)
101
- corrector.remove(node.loc.dot)
102
- corrector.remove(block_argument_range(node))
103
- end
104
- end
87
+ alias on_numblock on_block
105
88
 
106
89
  private
107
90
 
91
+ def autocorrect(corrector, send_node, node)
92
+ corrector.remove(send_node.receiver)
93
+ corrector.remove(send_node.loc.dot)
94
+ corrector.remove(block_argument_range(send_node)) unless node.numblock_type?
95
+ end
96
+
97
+ def redundant_receiver?(send_nodes, node)
98
+ proc = if node.numblock_type?
99
+ ->(n) { n.receiver.lvar_type? && n.receiver.source == '_1' }
100
+ else
101
+ return false if node.arguments.empty?
102
+
103
+ arg = node.arguments.first
104
+ ->(n) { same_value?(arg, n.receiver) }
105
+ end
106
+
107
+ send_nodes.all?(&proc)
108
+ end
109
+
108
110
  def block_argument_range(node)
109
111
  block_node = node.each_ancestor(:block).first
110
112
  block_argument = block_node.children[1].source_range
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Checks for redundant `travel_back` calls.
7
+ # Since Rails 5.2, `travel_back` is automatically called at the end of the test.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # def teardown
13
+ # do_something
14
+ # travel_back
15
+ # end
16
+ #
17
+ # # good
18
+ # def teardown
19
+ # do_something
20
+ # end
21
+ #
22
+ # # bad
23
+ # after do
24
+ # do_something
25
+ # travel_back
26
+ # end
27
+ #
28
+ # # good
29
+ # after do
30
+ # do_something
31
+ # end
32
+ #
33
+ class RedundantTravelBack < Base
34
+ include RangeHelp
35
+ extend AutoCorrector
36
+ extend TargetRailsVersion
37
+
38
+ minimum_target_rails_version 5.2
39
+
40
+ MSG = 'Redundant `travel_back` detected.'
41
+ RESTRICT_ON_SEND = %i[travel_back].freeze
42
+
43
+ def on_send(node)
44
+ return unless node.each_ancestor(:def, :block).any? do |ancestor|
45
+ method_name = ancestor.def_type? ? :teardown : :after
46
+
47
+ ancestor.method?(method_name)
48
+ end
49
+
50
+ add_offense(node) do |corrector|
51
+ corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end