rubocop-rails 2.12.4 → 2.13.1

Sign up to get free protection for your applications and to get access to all the features.
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