rubocop-rails 2.12.4 → 2.13.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/config/default.yml +34 -6
  4. data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +34 -0
  5. data/lib/rubocop/cop/rails/active_record_aliases.rb +4 -0
  6. data/lib/rubocop/cop/rails/application_controller.rb +5 -1
  7. data/lib/rubocop/cop/rails/application_job.rb +5 -1
  8. data/lib/rubocop/cop/rails/application_mailer.rb +5 -1
  9. data/lib/rubocop/cop/rails/application_record.rb +6 -1
  10. data/lib/rubocop/cop/rails/arel_star.rb +6 -0
  11. data/lib/rubocop/cop/rails/blank.rb +5 -4
  12. data/lib/rubocop/cop/rails/compact_blank.rb +98 -0
  13. data/lib/rubocop/cop/rails/content_tag.rb +2 -2
  14. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +2 -7
  15. data/lib/rubocop/cop/rails/duration_arithmetic.rb +97 -0
  16. data/lib/rubocop/cop/rails/dynamic_find_by.rb +4 -0
  17. data/lib/rubocop/cop/rails/http_positional_arguments.rb +1 -1
  18. data/lib/rubocop/cop/rails/index_by.rb +6 -6
  19. data/lib/rubocop/cop/rails/index_with.rb +6 -6
  20. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +8 -7
  21. data/lib/rubocop/cop/rails/mailer_name.rb +4 -0
  22. data/lib/rubocop/cop/rails/negate_include.rb +3 -2
  23. data/lib/rubocop/cop/rails/output.rb +4 -0
  24. data/lib/rubocop/cop/rails/pick.rb +7 -0
  25. data/lib/rubocop/cop/rails/pluck_id.rb +3 -0
  26. data/lib/rubocop/cop/rails/pluck_in_where.rb +7 -6
  27. data/lib/rubocop/cop/rails/rake_environment.rb +5 -0
  28. data/lib/rubocop/cop/rails/read_write_attribute.rb +21 -0
  29. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +256 -0
  30. data/lib/rubocop/cop/rails/reflection_class_name.rb +4 -2
  31. data/lib/rubocop/cop/rails/relative_date_constant.rb +3 -0
  32. data/lib/rubocop/cop/rails/reversible_migration.rb +1 -1
  33. data/lib/rubocop/cop/rails/root_join_chain.rb +72 -0
  34. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +12 -3
  35. data/lib/rubocop/cop/rails/save_bang.rb +19 -0
  36. data/lib/rubocop/cop/rails/schema_comment.rb +104 -0
  37. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +4 -2
  38. data/lib/rubocop/cop/rails/time_zone.rb +3 -0
  39. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +29 -35
  40. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +2 -0
  41. data/lib/rubocop/cop/rails/where_equals.rb +4 -0
  42. data/lib/rubocop/cop/rails/where_exists.rb +9 -8
  43. data/lib/rubocop/cop/rails_cops.rb +6 -0
  44. data/lib/rubocop/rails/version.rb +1 -1
  45. metadata +11 -4
@@ -9,14 +9,15 @@ module RuboCop
9
9
  # Since `pluck` is an eager method and hits the database immediately,
10
10
  # using `select` helps to avoid additional database queries.
11
11
  #
12
- # This cop has two different enforcement modes. When the EnforcedStyle
13
- # is conservative (the default) then only calls to `pluck` on a constant
12
+ # This cop has two different enforcement modes. When the `EnforcedStyle`
13
+ # is `conservative` (the default) then only calls to `pluck` on a constant
14
14
  # (i.e. a model class) in the `where` is used as offenses.
15
15
  #
16
- # When the EnforcedStyle is aggressive then all calls to `pluck` in the
17
- # `where` is used as offenses. This may lead to false positives
18
- # as the cop cannot replace to `select` between calls to `pluck` on an
19
- # `ActiveRecord::Relation` instance vs a call to `pluck` on an `Array` instance.
16
+ # @safety
17
+ # When the `EnforcedStyle` is `aggressive` then all calls to `pluck` in the
18
+ # `where` is used as offenses. This may lead to false positives
19
+ # as the cop cannot replace to `select` between calls to `pluck` on an
20
+ # `ActiveRecord::Relation` instance vs a call to `pluck` on an `Array` instance.
20
21
  #
21
22
  # @example
22
23
  # # bad
@@ -14,6 +14,11 @@ module RuboCop
14
14
  # * The task does not need application code.
15
15
  # * The task invokes the `:environment` task.
16
16
  #
17
+ # @safety
18
+ # Probably not a problem in most cases, but it is possible that calling `:environment` task
19
+ # will break a behavior. It's also slower. E.g. some task that only needs one gem to be
20
+ # loaded to run will run significantly faster without loading the whole application.
21
+ #
17
22
  # @example
18
23
  # # bad
19
24
  # task :foo do
@@ -23,6 +23,17 @@ module RuboCop
23
23
  # # good
24
24
  # x = self[:attr]
25
25
  # self[:attr] = val
26
+ #
27
+ # When called from within a method with the same name as the attribute,
28
+ # `read_attribute` and `write_attribute` must be used to prevent an
29
+ # infinite loop:
30
+ #
31
+ # @example
32
+ #
33
+ # # good
34
+ # def foo
35
+ # bar || read_attribute(:foo)
36
+ # end
26
37
  class ReadWriteAttribute < Base
27
38
  extend AutoCorrector
28
39
 
@@ -38,6 +49,7 @@ module RuboCop
38
49
 
39
50
  def on_send(node)
40
51
  return unless read_write_attribute?(node)
52
+ return if within_shadowing_method?(node)
41
53
 
42
54
  add_offense(node.loc.selector, message: message(node)) do |corrector|
43
55
  case node.method_name
@@ -53,6 +65,15 @@ module RuboCop
53
65
 
54
66
  private
55
67
 
68
+ def within_shadowing_method?(node)
69
+ node.each_ancestor(:def).any? do |enclosing_method|
70
+ shadowing_method_name = node.first_argument.value.to_s
71
+ shadowing_method_name << '=' if node.method?(:write_attribute)
72
+
73
+ enclosing_method.method_name.to_s == shadowing_method_name
74
+ end
75
+ end
76
+
56
77
  def message(node)
57
78
  if node.method?(:read_attribute)
58
79
  format(MSG, prefer: 'self[:attr]', current: 'read_attribute(:attr)')
@@ -0,0 +1,256 @@
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
+ # @example
12
+ # # bad
13
+ # belongs_to :user
14
+ # validates :user, presence: true
15
+ #
16
+ # # bad
17
+ # belongs_to :user
18
+ # validates :user_id, presence: true
19
+ #
20
+ # # bad
21
+ # belongs_to :author, foreign_key: :user_id
22
+ # validates :user_id, presence: true
23
+ #
24
+ # # good
25
+ # belongs_to :user
26
+ #
27
+ # # good
28
+ # belongs_to :author, foreign_key: :user_id
29
+ #
30
+ class RedundantPresenceValidationOnBelongsTo < Base
31
+ include RangeHelp
32
+ extend AutoCorrector
33
+ extend TargetRailsVersion
34
+
35
+ MSG = 'Remove explicit presence validation for %<association>s.'
36
+ RESTRICT_ON_SEND = %i[validates].freeze
37
+
38
+ minimum_target_rails_version 5.0
39
+
40
+ # @!method presence_validation?(node)
41
+ # Match a `validates` statement with a presence check
42
+ #
43
+ # @example source that matches - by association
44
+ # validates :user, presence: true
45
+ #
46
+ # @example source that matches - by association
47
+ # validates :name, :user, presence: true
48
+ #
49
+ # @example source that matches - with presence options
50
+ # validates :user, presence: { message: 'duplicate' }
51
+ #
52
+ # @example source that matches - by a foreign key
53
+ # validates :user_id, presence: true
54
+ #
55
+ # @example source that DOES NOT match - strict validation
56
+ # validates :user_id, presence: true, strict: true
57
+ #
58
+ # @example source that DOES NOT match - custom strict validation
59
+ # validates :user_id, presence: true, strict: MissingUserError
60
+ def_node_matcher :presence_validation?, <<~PATTERN
61
+ (
62
+ send nil? :validates
63
+ (sym $_)+
64
+ $[
65
+ (hash <$(pair (sym :presence) {true hash}) ...>) # presence: true
66
+ !(hash <$(pair (sym :strict) {true const}) ...>) # strict: true
67
+ ]
68
+ )
69
+ PATTERN
70
+
71
+ # @!method optional?(node)
72
+ # Match a `belongs_to` association with an optional option in a hash
73
+ def_node_matcher :optional?, <<~PATTERN
74
+ (send nil? :belongs_to _ ... #optional_option?)
75
+ PATTERN
76
+
77
+ # @!method optional_option?(node)
78
+ # Match an optional option in a hash
79
+ def_node_matcher :optional_option?, <<~PATTERN
80
+ {
81
+ (hash <(pair (sym :optional) true) ...>) # optional: true
82
+ (hash <(pair (sym :required) false) ...>) # required: false
83
+ }
84
+ PATTERN
85
+
86
+ # @!method any_belongs_to?(node, association:)
87
+ # Match a class with `belongs_to` with no regard to `foreign_key` option
88
+ #
89
+ # @example source that matches
90
+ # belongs_to :user
91
+ #
92
+ # @example source that matches - regardless of `foreign_key`
93
+ # belongs_to :author, foreign_key: :user_id
94
+ #
95
+ # @param node [RuboCop::AST::Node]
96
+ # @param association [Symbol]
97
+ # @return [Array<RuboCop::AST::Node>, nil] matching node
98
+ def_node_matcher :any_belongs_to?, <<~PATTERN
99
+ (begin
100
+ <
101
+ $(send nil? :belongs_to (sym %association) ...)
102
+ ...
103
+ >
104
+ )
105
+ PATTERN
106
+
107
+ # @!method belongs_to?(node, key:, fk:)
108
+ # Match a class with a matching association, either by name or an explicit
109
+ # `foreign_key` option
110
+ #
111
+ # @example source that matches - fk matches `foreign_key` option
112
+ # belongs_to :author, foreign_key: :user_id
113
+ #
114
+ # @example source that matches - key matches association name
115
+ # belongs_to :user
116
+ #
117
+ # @example source that does not match - explicit `foreign_key` does not match
118
+ # belongs_to :user, foreign_key: :account_id
119
+ #
120
+ # @param node [RuboCop::AST::Node]
121
+ # @param key [Symbol] e.g. `:user`
122
+ # @param fk [Symbol] e.g. `:user_id`
123
+ # @return [Array<RuboCop::AST::Node>] matching nodes
124
+ def_node_matcher :belongs_to?, <<~PATTERN
125
+ (begin
126
+ <
127
+ ${
128
+ #belongs_to_without_fk?(%key) # belongs_to :user
129
+ #belongs_to_with_a_matching_fk?(%fk) # belongs_to :author, foreign_key: :user_id
130
+ }
131
+ ...
132
+ >
133
+ )
134
+ PATTERN
135
+
136
+ # @!method belongs_to_without_fk?(node, key)
137
+ # Match a matching `belongs_to` association, without an explicit `foreign_key` option
138
+ #
139
+ # @param node [RuboCop::AST::Node]
140
+ # @param key [Symbol] e.g. `:user`
141
+ # @return [Array<RuboCop::AST::Node>] matching nodes
142
+ def_node_matcher :belongs_to_without_fk?, <<~PATTERN
143
+ {
144
+ (send nil? :belongs_to (sym %1)) # belongs_to :user
145
+ (send nil? :belongs_to (sym %1) !hash) # belongs_to :user, -> { not_deleted }
146
+ (send nil? :belongs_to (sym %1) !(hash <(pair (sym :foreign_key) _) ...>))
147
+ }
148
+ PATTERN
149
+
150
+ # @!method belongs_to_with_a_matching_fk?(node, fk)
151
+ # Match a matching `belongs_to` association with a matching explicit `foreign_key` option
152
+ #
153
+ # @example source that matches
154
+ # belongs_to :author, foreign_key: :user_id
155
+ #
156
+ # @param node [RuboCop::AST::Node]
157
+ # @param fk [Symbol] e.g. `:user_id`
158
+ # @return [Array<RuboCop::AST::Node>] matching nodes
159
+ def_node_matcher :belongs_to_with_a_matching_fk?, <<~PATTERN
160
+ (send nil? :belongs_to ... (hash <(pair (sym :foreign_key) (sym %1)) ...>))
161
+ PATTERN
162
+
163
+ def on_send(node)
164
+ presence_validation?(node) do |all_keys, options, presence|
165
+ keys = non_optional_belongs_to(node.parent, all_keys)
166
+ return if keys.none?
167
+
168
+ add_offense_and_correct(node, all_keys, keys, options, presence)
169
+ end
170
+ end
171
+
172
+ private
173
+
174
+ def add_offense_and_correct(node, all_keys, keys, options, presence)
175
+ add_offense(presence, message: message_for(keys)) do |corrector|
176
+ if options.children.one? # `presence: true` is the only option
177
+ if keys == all_keys
178
+ remove_validation(corrector, node)
179
+ else
180
+ remove_keys_from_validation(corrector, node, keys)
181
+ end
182
+ elsif keys == all_keys
183
+ remove_presence_option(corrector, presence)
184
+ else
185
+ extract_validation_for_keys(corrector, node, keys, options)
186
+ end
187
+ end
188
+ end
189
+
190
+ def message_for(keys)
191
+ display_keys = keys.map { |key| "`#{key}`" }.join('/')
192
+ format(MSG, association: display_keys)
193
+ end
194
+
195
+ def non_optional_belongs_to(node, keys)
196
+ keys.select do |key|
197
+ belongs_to = belongs_to_for(node, key)
198
+ belongs_to && !optional?(belongs_to)
199
+ end
200
+ end
201
+
202
+ def belongs_to_for(model_class_node, key)
203
+ if key.to_s.end_with?('_id')
204
+ normalized_key = key.to_s.delete_suffix('_id').to_sym
205
+ belongs_to?(model_class_node, key: normalized_key, fk: key)
206
+ else
207
+ any_belongs_to?(model_class_node, association: key)
208
+ end
209
+ end
210
+
211
+ def remove_validation(corrector, node)
212
+ corrector.remove(validation_range(node))
213
+ end
214
+
215
+ def remove_keys_from_validation(corrector, node, keys)
216
+ keys.each do |key|
217
+ key_node = node.arguments.find { |arg| arg.value == key }
218
+ key_range = range_with_surrounding_space(
219
+ range: range_with_surrounding_comma(key_node.source_range, :right),
220
+ side: :right
221
+ )
222
+ corrector.remove(key_range)
223
+ end
224
+ end
225
+
226
+ def remove_presence_option(corrector, presence)
227
+ range = range_with_surrounding_comma(
228
+ range_with_surrounding_space(range: presence.source_range, side: :left),
229
+ :left
230
+ )
231
+ corrector.remove(range)
232
+ end
233
+
234
+ def extract_validation_for_keys(corrector, node, keys, options)
235
+ indentation = ' ' * node.source_range.column
236
+ options_without_presence = options.children.reject { |pair| pair.key.value == :presence }
237
+ source = [
238
+ indentation,
239
+ 'validates ',
240
+ keys.map(&:inspect).join(', '),
241
+ ', ',
242
+ options_without_presence.map(&:source).join(', '),
243
+ "\n"
244
+ ].join
245
+
246
+ remove_keys_from_validation(corrector, node, keys)
247
+ corrector.insert_after(validation_range(node), source)
248
+ end
249
+
250
+ def validation_range(node)
251
+ range_by_whole_lines(node.source_range, include_final_newline: true)
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
@@ -5,8 +5,10 @@ module RuboCop
5
5
  module Rails
6
6
  # This cop checks if the value of the option `class_name`, in
7
7
  # the definition of a reflection is a string.
8
- # It is marked as unsafe because it cannot be determined whether
9
- # constant or method return value specified to `class_name` is a string.
8
+ #
9
+ # @safety
10
+ # This cop is unsafe because it cannot be determined whether
11
+ # constant or method return value specified to `class_name` is a string.
10
12
  #
11
13
  # @example
12
14
  # # bad
@@ -6,6 +6,9 @@ module RuboCop
6
6
  # This cop checks whether constant value isn't relative date.
7
7
  # Because the relative date will be evaluated only once.
8
8
  #
9
+ # @safety
10
+ # This cop's autocorrection is unsafe because its dependence on the constant is not corrected.
11
+ #
9
12
  # @example
10
13
  # # bad
11
14
  # class SomeClass
@@ -179,7 +179,7 @@ module RuboCop
179
179
  MSG = '%<action>s is not reversible.'
180
180
 
181
181
  def_node_matcher :irreversible_schema_statement_call, <<~PATTERN
182
- (send nil? ${:execute :remove_belongs_to} ...)
182
+ (send nil? ${:change_column :execute} ...)
183
183
  PATTERN
184
184
 
185
185
  def_node_matcher :drop_table_call, <<~PATTERN
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Use a single `#join` instead of chaining on `Rails.root` or `Rails.public_path`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # Rails.root.join('db').join('schema.rb')
11
+ # Rails.root.join('db').join(migrate).join('migration.rb')
12
+ # Rails.public_path.join('path').join('file.pdf')
13
+ # Rails.public_path.join('path').join(to).join('file.pdf')
14
+ #
15
+ # # good
16
+ # Rails.root.join('db', 'schema.rb')
17
+ # Rails.root.join('db', migrate, 'migration.rb')
18
+ # Rails.public_path.join('path', 'file.pdf')
19
+ # Rails.public_path.join('path', to, 'file.pdf')
20
+ #
21
+ class RootJoinChain < Base
22
+ extend AutoCorrector
23
+ include RangeHelp
24
+
25
+ MSG = 'Use `%<root>s.join(...)` instead of chaining `#join` calls.'
26
+
27
+ RESTRICT_ON_SEND = %i[join].to_set.freeze
28
+
29
+ # @!method rails_root?(node)
30
+ def_node_matcher :rails_root?, <<~PATTERN
31
+ (send (const {nil? cbase} :Rails) {:root :public_path})
32
+ PATTERN
33
+
34
+ # @!method join?(node)
35
+ def_node_matcher :join?, <<~PATTERN
36
+ (send _ :join $...)
37
+ PATTERN
38
+
39
+ def on_send(node)
40
+ evidence(node) do |rails_node, args|
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)
43
+ replacement = ".join(#{args.map(&:source).join(', ')})"
44
+
45
+ corrector.replace(range, replacement)
46
+ end
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def evidence(node)
53
+ # Are we at the *end* of the join chain?
54
+ return if join?(node.parent)
55
+ # Is there only one join?
56
+ return if rails_root?(node.receiver)
57
+
58
+ all_args = []
59
+
60
+ while (args = join?(node))
61
+ all_args = args + all_args
62
+ node = node.receiver
63
+ end
64
+
65
+ rails_root?(node) do
66
+ yield(node, all_args)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -6,9 +6,18 @@ module RuboCop
6
6
  # This cop checks to make sure safe navigation isn't used with `blank?` in
7
7
  # a conditional.
8
8
  #
9
- # While the safe navigation operator is generally a good idea, when
10
- # checking `foo&.blank?` in a conditional, `foo` being `nil` will actually
11
- # do the opposite of what the author intends.
9
+ # @safety
10
+ # While the safe navigation operator is generally a good idea, when
11
+ # checking `foo&.blank?` in a conditional, `foo` being `nil` will actually
12
+ # do the opposite of what the author intends.
13
+ #
14
+ # For example:
15
+ #
16
+ # [source,ruby]
17
+ # ----
18
+ # foo&.blank? #=> nil
19
+ # foo.blank? #=> true
20
+ # ----
12
21
  #
13
22
  # @example
14
23
  # # bad
@@ -25,6 +25,25 @@ module RuboCop
25
25
  # You can permit receivers that are giving false positives with
26
26
  # `AllowedReceivers: []`
27
27
  #
28
+ # @safety
29
+ # This cop's autocorrection is unsafe because a custom `update` method call would be changed to `update!`,
30
+ # but the method name in the definition would be unchanged.
31
+ #
32
+ # [source,ruby]
33
+ # ----
34
+ # # Original code
35
+ # def update_attributes
36
+ # end
37
+ #
38
+ # update_attributes
39
+ #
40
+ # # After running rubocop --safe-auto-correct
41
+ # def update_attributes
42
+ # end
43
+ #
44
+ # update
45
+ # ----
46
+ #
28
47
  # @example
29
48
  #
30
49
  # # bad
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces the use of the `comment` option when adding a new table or column
7
+ # to the database during a migration.
8
+ #
9
+ # @example
10
+ # # bad (no comment for a new column or table)
11
+ # add_column :table, :column, :integer
12
+ #
13
+ # create_table :table do |t|
14
+ # t.type :column
15
+ # end
16
+ #
17
+ # # good
18
+ # add_column :table, :column, :integer, comment: 'Number of offenses'
19
+ #
20
+ # create_table :table, comment: 'Table of offenses data' do |t|
21
+ # t.type :column, comment: 'Number of offenses'
22
+ # end
23
+ #
24
+ class SchemaComment < Base
25
+ include ActiveRecordMigrationsHelper
26
+
27
+ COLUMN_MSG = 'New database column without `comment`.'
28
+ TABLE_MSG = 'New database table without `comment`.'
29
+ RESTRICT_ON_SEND = %i[add_column create_table].freeze
30
+ CREATE_TABLE_COLUMN_METHODS = Set[
31
+ *(
32
+ RAILS_ABSTRACT_SCHEMA_DEFINITIONS |
33
+ RAILS_ABSTRACT_SCHEMA_DEFINITIONS_HELPERS |
34
+ POSTGRES_SCHEMA_DEFINITIONS |
35
+ MYSQL_SCHEMA_DEFINITIONS
36
+ )
37
+ ].freeze
38
+
39
+ # @!method comment_present?(node)
40
+ def_node_matcher :comment_present?, <<~PATTERN
41
+ (hash <(pair {(sym :comment) (str "comment")} (_ [present?])) ...>)
42
+ PATTERN
43
+
44
+ # @!method add_column?(node)
45
+ def_node_matcher :add_column?, <<~PATTERN
46
+ (send nil? :add_column _table _column _type _?)
47
+ PATTERN
48
+
49
+ # @!method add_column_with_comment?(node)
50
+ def_node_matcher :add_column_with_comment?, <<~PATTERN
51
+ (send nil? :add_column _table _column _type #comment_present?)
52
+ PATTERN
53
+
54
+ # @!method create_table?(node)
55
+ def_node_matcher :create_table?, <<~PATTERN
56
+ (send nil? :create_table _table _?)
57
+ PATTERN
58
+
59
+ # @!method create_table?(node)
60
+ def_node_matcher :create_table_with_comment?, <<~PATTERN
61
+ (send nil? :create_table _table #comment_present? ...)
62
+ PATTERN
63
+
64
+ # @!method t_column?(node)
65
+ def_node_matcher :t_column?, <<~PATTERN
66
+ (send _var CREATE_TABLE_COLUMN_METHODS ...)
67
+ PATTERN
68
+
69
+ # @!method t_column_with_comment?(node)
70
+ def_node_matcher :t_column_with_comment?, <<~PATTERN
71
+ (send _var CREATE_TABLE_COLUMN_METHODS _column _type? #comment_present?)
72
+ PATTERN
73
+
74
+ def on_send(node)
75
+ if add_column_without_comment?(node)
76
+ add_offense(node, message: COLUMN_MSG)
77
+ elsif create_table?(node)
78
+ if create_table_without_comment?(node)
79
+ add_offense(node, message: TABLE_MSG)
80
+ elsif create_table_column_call_without_comment?(node)
81
+ add_offense(node.parent.body, message: COLUMN_MSG)
82
+ end
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def add_column_without_comment?(node)
89
+ add_column?(node) && !add_column_with_comment?(node)
90
+ end
91
+
92
+ def create_table_without_comment?(node)
93
+ create_table?(node) && !create_table_with_comment?(node)
94
+ end
95
+
96
+ def create_table_column_call_without_comment?(node)
97
+ create_table_with_block?(node.parent) &&
98
+ t_column?(node.parent.body) &&
99
+ !t_column_with_comment?(node.parent.body)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -5,8 +5,10 @@ module RuboCop
5
5
  module Rails
6
6
  #
7
7
  # Checks SQL heredocs to use `.squish`.
8
- # Some SQL syntax (e.g. PostgreSQL comments and functions) requires newlines
9
- # to be preserved in order to work, thus auto-correction for this cop is not safe.
8
+ #
9
+ # @safety
10
+ # Some SQL syntax (e.g. PostgreSQL comments and functions) requires newlines
11
+ # to be preserved in order to work, thus auto-correction for this cop is not safe.
10
12
  #
11
13
  # @example
12
14
  # # bad
@@ -14,6 +14,9 @@ module RuboCop
14
14
  # When EnforcedStyle is 'flexible' then it's also allowed
15
15
  # to use `Time#in_time_zone`.
16
16
  #
17
+ # @safety
18
+ # This cop's autocorrection is unsafe because it may change handling time.
19
+ #
17
20
  # @example
18
21
  # # bad
19
22
  # Time.now