rubocop-rails 2.15.2 → 2.20.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +23 -2
  4. data/config/default.yml +181 -13
  5. data/config/obsoletion.yml +10 -0
  6. data/lib/rubocop/cop/mixin/active_record_helper.rb +3 -6
  7. data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +1 -3
  8. data/lib/rubocop/cop/mixin/enforce_superclass.rb +1 -1
  9. data/lib/rubocop/cop/mixin/index_method.rb +7 -17
  10. data/lib/rubocop/cop/mixin/migrations_helper.rb +1 -1
  11. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +112 -0
  12. data/lib/rubocop/cop/rails/action_controller_test_case.rb +2 -2
  13. data/lib/rubocop/cop/rails/action_filter.rb +1 -1
  14. data/lib/rubocop/cop/rails/action_order.rb +116 -0
  15. data/lib/rubocop/cop/rails/active_record_aliases.rb +3 -4
  16. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +6 -3
  17. data/lib/rubocop/cop/rails/active_record_override.rb +2 -5
  18. data/lib/rubocop/cop/rails/active_support_on_load.rb +70 -0
  19. data/lib/rubocop/cop/rails/add_column_index.rb +2 -5
  20. data/lib/rubocop/cop/rails/application_controller.rb +1 -1
  21. data/lib/rubocop/cop/rails/application_job.rb +2 -2
  22. data/lib/rubocop/cop/rails/application_mailer.rb +1 -1
  23. data/lib/rubocop/cop/rails/application_record.rb +1 -1
  24. data/lib/rubocop/cop/rails/arel_star.rb +1 -1
  25. data/lib/rubocop/cop/rails/assert_not.rb +1 -2
  26. data/lib/rubocop/cop/rails/belongs_to.rb +1 -4
  27. data/lib/rubocop/cop/rails/blank.rb +6 -7
  28. data/lib/rubocop/cop/rails/bulk_change_table.rb +7 -24
  29. data/lib/rubocop/cop/rails/compact_blank.rb +5 -1
  30. data/lib/rubocop/cop/rails/content_tag.rb +5 -6
  31. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +16 -3
  32. data/lib/rubocop/cop/rails/date.rb +15 -11
  33. data/lib/rubocop/cop/rails/delegate.rb +19 -8
  34. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +1 -1
  35. data/lib/rubocop/cop/rails/deprecated_active_model_errors_methods.rb +18 -14
  36. data/lib/rubocop/cop/rails/dot_separated_keys.rb +2 -2
  37. data/lib/rubocop/cop/rails/duration_arithmetic.rb +3 -3
  38. data/lib/rubocop/cop/rails/dynamic_find_by.rb +25 -13
  39. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +5 -1
  40. data/lib/rubocop/cop/rails/enum_hash.rb +1 -1
  41. data/lib/rubocop/cop/rails/enum_uniqueness.rb +2 -5
  42. data/lib/rubocop/cop/rails/environment_comparison.rb +2 -3
  43. data/lib/rubocop/cop/rails/file_path.rb +154 -27
  44. data/lib/rubocop/cop/rails/find_by_id.rb +2 -2
  45. data/lib/rubocop/cop/rails/find_each.rb +15 -5
  46. data/lib/rubocop/cop/rails/freeze_time.rb +79 -0
  47. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +4 -6
  48. data/lib/rubocop/cop/rails/helper_instance_variable.rb +1 -1
  49. data/lib/rubocop/cop/rails/http_positional_arguments.rb +22 -11
  50. data/lib/rubocop/cop/rails/http_status.rb +6 -11
  51. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +2 -0
  52. data/lib/rubocop/cop/rails/i18n_locale_texts.rb +7 -3
  53. data/lib/rubocop/cop/rails/ignored_columns_assignment.rb +50 -0
  54. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +23 -12
  55. data/lib/rubocop/cop/rails/index_by.rb +1 -1
  56. data/lib/rubocop/cop/rails/index_with.rb +1 -1
  57. data/lib/rubocop/cop/rails/inverse_of.rb +3 -9
  58. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +21 -15
  59. data/lib/rubocop/cop/rails/link_to_blank.rb +1 -4
  60. data/lib/rubocop/cop/rails/mailer_name.rb +4 -4
  61. data/lib/rubocop/cop/rails/migration_class_name.rb +1 -1
  62. data/lib/rubocop/cop/rails/negate_include.rb +1 -1
  63. data/lib/rubocop/cop/rails/not_null_column.rb +9 -6
  64. data/lib/rubocop/cop/rails/output.rb +6 -8
  65. data/lib/rubocop/cop/rails/output_safety.rb +5 -1
  66. data/lib/rubocop/cop/rails/pluck.rb +44 -12
  67. data/lib/rubocop/cop/rails/pluck_id.rb +1 -1
  68. data/lib/rubocop/cop/rails/pluralization_grammar.rb +1 -2
  69. data/lib/rubocop/cop/rails/presence.rb +21 -12
  70. data/lib/rubocop/cop/rails/present.rb +8 -11
  71. data/lib/rubocop/cop/rails/rake_environment.rb +2 -2
  72. data/lib/rubocop/cop/rails/read_write_attribute.rb +1 -1
  73. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +5 -7
  74. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +2 -2
  75. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +3 -3
  76. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +30 -26
  77. data/lib/rubocop/cop/rails/reflection_class_name.rb +34 -1
  78. data/lib/rubocop/cop/rails/refute_methods.rb +1 -6
  79. data/lib/rubocop/cop/rails/relative_date_constant.rb +4 -7
  80. data/lib/rubocop/cop/rails/request_referer.rb +1 -2
  81. data/lib/rubocop/cop/rails/require_dependency.rb +1 -1
  82. data/lib/rubocop/cop/rails/response_parsed_body.rb +57 -0
  83. data/lib/rubocop/cop/rails/reversible_migration.rb +14 -62
  84. data/lib/rubocop/cop/rails/reversible_migration_method_definition.rb +1 -2
  85. data/lib/rubocop/cop/rails/root_join_chain.rb +1 -1
  86. data/lib/rubocop/cop/rails/root_pathname_methods.rb +238 -0
  87. data/lib/rubocop/cop/rails/safe_navigation.rb +1 -1
  88. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +1 -3
  89. data/lib/rubocop/cop/rails/save_bang.rb +10 -22
  90. data/lib/rubocop/cop/rails/short_i18n.rb +2 -5
  91. data/lib/rubocop/cop/rails/skips_model_validations.rb +2 -3
  92. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +9 -7
  93. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +71 -0
  94. data/lib/rubocop/cop/rails/time_zone.rb +27 -25
  95. data/lib/rubocop/cop/rails/time_zone_assignment.rb +1 -1
  96. data/lib/rubocop/cop/rails/to_s_with_argument.rb +78 -0
  97. data/lib/rubocop/cop/rails/top_level_hash_with_indifferent_access.rb +49 -0
  98. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +8 -3
  99. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +3 -6
  100. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +22 -19
  101. data/lib/rubocop/cop/rails/unknown_env.rb +2 -4
  102. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +6 -1
  103. data/lib/rubocop/cop/rails/validation.rb +4 -12
  104. data/lib/rubocop/cop/rails/where_equals.rb +1 -1
  105. data/lib/rubocop/cop/rails/where_exists.rb +1 -1
  106. data/lib/rubocop/cop/rails/where_missing.rb +118 -0
  107. data/lib/rubocop/cop/rails/where_not.rb +1 -1
  108. data/lib/rubocop/cop/rails/where_not_with_multiple_conditions.rb +55 -0
  109. data/lib/rubocop/cop/rails_cops.rb +12 -0
  110. data/lib/rubocop/rails/schema_loader/schema.rb +4 -4
  111. data/lib/rubocop/rails/version.rb +1 -1
  112. data/lib/rubocop/rails.rb +1 -1
  113. data/lib/rubocop-rails.rb +11 -0
  114. metadata +19 -9
  115. data/bin/console +0 -11
  116. data/bin/setup +0 -7
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Use `Rails.root` IO methods instead of passing it to `File`.
7
+ #
8
+ # `Rails.root` is an instance of `Pathname`
9
+ # so we can apply many IO methods directly.
10
+ #
11
+ # This cop works best when used together with
12
+ # `Style/FileRead`, `Style/FileWrite` and `Rails/RootJoinChain`.
13
+ #
14
+ # @safety
15
+ # This cop is unsafe for autocorrection because `Dir`'s `children`, `each_child`, `entries`, and `glob`
16
+ # methods return string element, but these methods of `Pathname` return `Pathname` element.
17
+ #
18
+ # @example
19
+ # # bad
20
+ # File.open(Rails.root.join('db', 'schema.rb'))
21
+ # File.open(Rails.root.join('db', 'schema.rb'), 'w')
22
+ # File.read(Rails.root.join('db', 'schema.rb'))
23
+ # File.binread(Rails.root.join('db', 'schema.rb'))
24
+ # File.write(Rails.root.join('db', 'schema.rb'), content)
25
+ # File.binwrite(Rails.root.join('db', 'schema.rb'), content)
26
+ #
27
+ # # good
28
+ # Rails.root.join('db', 'schema.rb').open
29
+ # Rails.root.join('db', 'schema.rb').open('w')
30
+ # Rails.root.join('db', 'schema.rb').read
31
+ # Rails.root.join('db', 'schema.rb').binread
32
+ # Rails.root.join('db', 'schema.rb').write(content)
33
+ # Rails.root.join('db', 'schema.rb').binwrite(content)
34
+ #
35
+ class RootPathnameMethods < Base
36
+ extend AutoCorrector
37
+ include RangeHelp
38
+
39
+ MSG = '`%<rails_root>s` is a `Pathname` so you can just append `#%<method>s`.'
40
+
41
+ DIR_METHODS = %i[children delete each_child empty? entries exist? glob mkdir open rmdir unlink].to_set.freeze
42
+
43
+ FILE_METHODS = %i[
44
+ atime
45
+ basename
46
+ binread
47
+ binwrite
48
+ birthtime
49
+ blockdev?
50
+ chardev?
51
+ chmod
52
+ chown
53
+ ctime
54
+ delete
55
+ directory?
56
+ dirname
57
+ empty?
58
+ executable?
59
+ executable_real?
60
+ exist?
61
+ expand_path
62
+ extname
63
+ file?
64
+ fnmatch
65
+ fnmatch?
66
+ ftype
67
+ grpowned?
68
+ join
69
+ lchmod
70
+ lchown
71
+ lstat
72
+ mtime
73
+ open
74
+ owned?
75
+ pipe?
76
+ read
77
+ readable?
78
+ readable_real?
79
+ readlines
80
+ readlink
81
+ realdirpath
82
+ realpath
83
+ rename
84
+ setgid?
85
+ setuid?
86
+ size
87
+ size?
88
+ socket?
89
+ split
90
+ stat
91
+ sticky?
92
+ symlink?
93
+ sysopen
94
+ truncate
95
+ unlink
96
+ utime
97
+ world_readable?
98
+ world_writable?
99
+ writable?
100
+ writable_real?
101
+ write
102
+ zero?
103
+ ].to_set.freeze
104
+
105
+ FILE_TEST_METHODS = %i[
106
+ blockdev?
107
+ chardev?
108
+ directory?
109
+ empty?
110
+ executable?
111
+ executable_real?
112
+ exist?
113
+ file?
114
+ grpowned?
115
+ owned?
116
+ pipe?
117
+ readable?
118
+ readable_real?
119
+ setgid?
120
+ setuid?
121
+ size
122
+ size?
123
+ socket?
124
+ sticky?
125
+ symlink?
126
+ world_readable?
127
+ world_writable?
128
+ writable?
129
+ writable_real?
130
+ zero?
131
+ ].to_set.freeze
132
+
133
+ FILE_UTILS_METHODS = %i[chmod chown mkdir mkpath rmdir rmtree].to_set.freeze
134
+
135
+ RESTRICT_ON_SEND = (DIR_METHODS + FILE_METHODS + FILE_TEST_METHODS + FILE_UTILS_METHODS).to_set.freeze
136
+
137
+ def_node_matcher :pathname_method, <<~PATTERN
138
+ {
139
+ (send (const {nil? cbase} :Dir) $DIR_METHODS $_ $...)
140
+ (send (const {nil? cbase} {:IO :File}) $FILE_METHODS $_ $...)
141
+ (send (const {nil? cbase} :FileTest) $FILE_TEST_METHODS $_ $...)
142
+ (send (const {nil? cbase} :FileUtils) $FILE_UTILS_METHODS $_ $...)
143
+ }
144
+ PATTERN
145
+
146
+ def_node_matcher :dir_glob?, <<~PATTERN
147
+ (send
148
+ (const {cbase nil?} :Dir) :glob ...)
149
+ PATTERN
150
+
151
+ def_node_matcher :rails_root_pathname?, <<~PATTERN
152
+ {
153
+ $#rails_root?
154
+ (send $#rails_root? :join ...)
155
+ }
156
+ PATTERN
157
+
158
+ # @!method rails_root?(node)
159
+ def_node_matcher :rails_root?, <<~PATTERN
160
+ (send (const {nil? cbase} :Rails) {:root :public_path})
161
+ PATTERN
162
+
163
+ def on_send(node)
164
+ evidence(node) do |method, path, args, rails_root|
165
+ add_offense(node, message: format(MSG, method: method, rails_root: rails_root.source)) do |corrector|
166
+ replacement = if dir_glob?(node)
167
+ build_path_glob_replacement(path, method)
168
+ else
169
+ build_path_replacement(path, method, args)
170
+ end
171
+
172
+ corrector.replace(node, replacement)
173
+ end
174
+ end
175
+ end
176
+
177
+ private
178
+
179
+ def evidence(node)
180
+ return if node.method?(:open) && node.parent&.send_type?
181
+ return unless (method, path, args = pathname_method(node)) && (rails_root = rails_root_pathname?(path))
182
+
183
+ yield(method, path, args, rails_root)
184
+ end
185
+
186
+ def build_path_glob_replacement(path, method)
187
+ receiver = range_between(path.source_range.begin_pos, path.children.first.loc.selector.end_pos).source
188
+
189
+ argument = path.arguments.one? ? path.first_argument.source : join_arguments(path.arguments)
190
+
191
+ "#{receiver}.#{method}(#{argument})"
192
+ end
193
+
194
+ def build_path_replacement(path, method, args)
195
+ path_replacement = path.source
196
+ if path.arguments? && !path.parenthesized_call?
197
+ path_replacement[' '] = '('
198
+ path_replacement << ')'
199
+ end
200
+
201
+ replacement = "#{path_replacement}.#{method}"
202
+ replacement += "(#{args.map(&:source).join(', ')})" unless args.empty?
203
+ replacement
204
+ end
205
+
206
+ def include_interpolation?(arguments)
207
+ arguments.any? do |argument|
208
+ argument.children.any? { |child| child.respond_to?(:begin_type?) && child.begin_type? }
209
+ end
210
+ end
211
+
212
+ def join_arguments(arguments)
213
+ use_interpolation = false
214
+
215
+ joined_arguments = arguments.map do |arg|
216
+ if arg.respond_to?(:value)
217
+ arg.value
218
+ else
219
+ use_interpolation = true
220
+ "\#{#{arg.source}}"
221
+ end
222
+ end.join('/')
223
+ quote = enforce_double_quotes? || include_interpolation?(arguments) || use_interpolation ? '"' : "'"
224
+
225
+ "#{quote}#{joined_arguments}#{quote}"
226
+ end
227
+
228
+ def enforce_double_quotes?
229
+ string_literals_config['EnforcedStyle'] == 'double_quotes'
230
+ end
231
+
232
+ def string_literals_config
233
+ config.for_cop('Style/StringLiterals')
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
@@ -73,7 +73,7 @@ module RuboCop
73
73
  method = method_node.source[1..]
74
74
 
75
75
  range = if node.receiver
76
- range_between(node.loc.dot.begin_pos, node.loc.expression.end_pos)
76
+ range_between(node.loc.dot.begin_pos, node.source_range.end_pos)
77
77
  else
78
78
  corrector.insert_before(node, 'self')
79
79
  node
@@ -31,9 +31,7 @@ module RuboCop
31
31
  class SafeNavigationWithBlank < Base
32
32
  extend AutoCorrector
33
33
 
34
- MSG =
35
- 'Avoid calling `blank?` with the safe navigation operator ' \
36
- 'in conditionals.'
34
+ MSG = 'Avoid calling `blank?` with the safe navigation operator in conditionals.'
37
35
 
38
36
  def_node_matcher :safe_navigation_blank_in_conditional?, <<~PATTERN
39
37
  (if $(csend ... :blank?) ...)
@@ -121,18 +121,12 @@ module RuboCop
121
121
  include NegativeConditional
122
122
  extend AutoCorrector
123
123
 
124
- MSG = 'Use `%<prefer>s` instead of `%<current>s` if the return ' \
125
- 'value is not checked.'
126
- CREATE_MSG = (MSG +
127
- ' Or check `persisted?` on model returned from ' \
128
- '`%<current>s`.').freeze
129
- CREATE_CONDITIONAL_MSG = '`%<current>s` returns a model which is ' \
130
- 'always truthy.'
131
-
132
- CREATE_PERSIST_METHODS = %i[create create_or_find_by
133
- first_or_create find_or_create_by].freeze
134
- MODIFY_PERSIST_METHODS = %i[save
135
- update update_attributes destroy].freeze
124
+ MSG = 'Use `%<prefer>s` instead of `%<current>s` if the return value is not checked.'
125
+ CREATE_MSG = "#{MSG} Or check `persisted?` on model returned from `%<current>s`."
126
+ CREATE_CONDITIONAL_MSG = '`%<current>s` returns a model which is always truthy.'
127
+
128
+ CREATE_PERSIST_METHODS = %i[create create_or_find_by first_or_create find_or_create_by].freeze
129
+ MODIFY_PERSIST_METHODS = %i[save update update_attributes destroy].freeze
136
130
  RESTRICT_ON_SEND = (CREATE_PERSIST_METHODS + MODIFY_PERSIST_METHODS).freeze
137
131
 
138
132
  def self.joining_forces
@@ -244,8 +238,7 @@ module RuboCop
244
238
  parent = node.parent
245
239
  return false unless parent
246
240
 
247
- operator_or_single_negative?(parent) ||
248
- (conditional?(parent) && node == parent.condition)
241
+ operator_or_single_negative?(parent) || (conditional?(parent) && node == parent.condition)
249
242
  end
250
243
 
251
244
  def operator_or_single_negative?(node)
@@ -294,9 +287,7 @@ module RuboCop
294
287
  # NameSpace::Const != ::Const
295
288
  # Const != NameSpace::Const
296
289
  def const_matches?(const, allowed_const)
297
- parts = allowed_const.split('::').reverse.zip(
298
- const.split('::').reverse
299
- )
290
+ parts = allowed_const.split('::').reverse.zip(const.split('::').reverse)
300
291
  parts.all? do |(allowed_part, const_part)|
301
292
  allowed_part == const_part.to_s
302
293
  end
@@ -335,9 +326,7 @@ module RuboCop
335
326
  end
336
327
 
337
328
  def persist_method?(node, methods = RESTRICT_ON_SEND)
338
- methods.include?(node.method_name) &&
339
- expected_signature?(node) &&
340
- !allowed_receiver?(node)
329
+ methods.include?(node.method_name) && expected_signature?(node) && !allowed_receiver?(node)
341
330
  end
342
331
 
343
332
  # Check argument signature as no arguments or one hash
@@ -345,8 +334,7 @@ module RuboCop
345
334
  !node.arguments? ||
346
335
  (node.arguments.one? &&
347
336
  node.method_name != :destroy &&
348
- (node.first_argument.hash_type? ||
349
- !node.first_argument.literal?))
337
+ (node.first_argument.hash_type? || !node.first_argument.literal?))
350
338
  end
351
339
  end
352
340
  end
@@ -44,15 +44,12 @@ module RuboCop
44
44
 
45
45
  MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
46
46
 
47
- PREFERRED_METHODS = {
48
- translate: :t,
49
- localize: :l
50
- }.freeze
47
+ PREFERRED_METHODS = { translate: :t, localize: :l }.freeze
51
48
 
52
49
  RESTRICT_ON_SEND = PREFERRED_METHODS.keys.freeze
53
50
 
54
51
  def_node_matcher :long_i18n?, <<~PATTERN
55
- (send {nil? (const nil? :I18n)} ${:translate :localize} ...)
52
+ (send {nil? (const {nil? cbase} :I18n)} ${:translate :localize} ...)
56
53
  PATTERN
57
54
 
58
55
  def on_send(node)
@@ -57,7 +57,7 @@ module RuboCop
57
57
 
58
58
  def_node_matcher :good_touch?, <<~PATTERN
59
59
  {
60
- (send (const nil? :FileUtils) :touch ...)
60
+ (send (const {nil? cbase} :FileUtils) :touch ...)
61
61
  (send _ :touch {true false})
62
62
  }
63
63
  PATTERN
@@ -93,8 +93,7 @@ module RuboCop
93
93
  end
94
94
 
95
95
  def allowed_method?(node)
96
- METHODS_WITH_ARGUMENTS.include?(node.method_name.to_s) &&
97
- !node.arguments?
96
+ METHODS_WITH_ARGUMENTS.include?(node.method_name.to_s) && !node.arguments?
98
97
  end
99
98
 
100
99
  def forbidden_methods
@@ -3,7 +3,6 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- #
7
6
  # Checks SQL heredocs to use `.squish`.
8
7
  #
9
8
  # @safety
@@ -48,6 +47,7 @@ module RuboCop
48
47
  SQL = 'SQL'
49
48
  SQUISH = '.squish'
50
49
  MSG = 'Use `%<expect>s` instead of `%<current>s`.'
50
+ SQL_IDENTIFIER_MARKERS = /(".+?")|('.+?')|(\[.+?\])/.freeze
51
51
 
52
52
  def on_heredoc(node)
53
53
  return unless offense_detected?(node)
@@ -60,7 +60,7 @@ module RuboCop
60
60
  private
61
61
 
62
62
  def offense_detected?(node)
63
- sql_heredoc?(node) && !using_squish?(node)
63
+ sql_heredoc?(node) && !using_squish?(node) && !singleline_comments_present?(node)
64
64
  end
65
65
 
66
66
  def sql_heredoc?(node)
@@ -71,12 +71,14 @@ module RuboCop
71
71
  node.parent&.send_type? && node.parent&.method?(:squish)
72
72
  end
73
73
 
74
+ def singleline_comments_present?(node)
75
+ sql = node.children.map { |c| c.is_a?(String) ? c : c.source }.join('\n')
76
+
77
+ sql.gsub(SQL_IDENTIFIER_MARKERS, '').include?('--')
78
+ end
79
+
74
80
  def message(node)
75
- format(
76
- MSG,
77
- expect: "#{node.source}#{SQUISH}",
78
- current: node.source
79
- )
81
+ format(MSG, expect: "#{node.source}#{SQUISH}", current: node.source)
80
82
  end
81
83
  end
82
84
  end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Enforces that boolean columns are created with default values (`false` or `true`) and
7
+ # `NOT NULL` constraint.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # add_column :users, :active, :boolean
12
+ # t.column :active, :boolean
13
+ # t.boolean :active
14
+ #
15
+ # # good
16
+ # add_column :users, :active, :boolean, default: true, null: false
17
+ # t.column :active, :boolean, default: true, null: false
18
+ # t.boolean :active, default: true, null: false
19
+ #
20
+ class ThreeStateBooleanColumn < Base
21
+ MSG = 'Boolean columns should always have a default value and a `NOT NULL` constraint.'
22
+
23
+ RESTRICT_ON_SEND = %i[add_column column boolean].freeze
24
+
25
+ def_node_matcher :three_state_boolean?, <<~PATTERN
26
+ {
27
+ (send nil? :add_column _ $_ {(sym :boolean) (str "boolean")} $_ ?)
28
+ (send !nil? :column $_ {(sym :boolean) (str "boolean")} $_ ?)
29
+ (send !nil? :boolean $_ $_ ?)
30
+ }
31
+ PATTERN
32
+
33
+ def_node_matcher :required_options?, <<~PATTERN
34
+ (hash <(pair (sym :default) !nil?) (pair (sym :null) false) ...>)
35
+ PATTERN
36
+
37
+ def_node_search :change_column_null?, <<~PATTERN
38
+ (send nil? :change_column_null %1 %2 false)
39
+ PATTERN
40
+
41
+ def on_send(node)
42
+ three_state_boolean?(node) do |column_node, options_node|
43
+ options_node = options_node.first
44
+
45
+ return if required_options?(options_node)
46
+
47
+ def_node = node.each_ancestor(:def, :defs).first
48
+ table_node = table_node(node)
49
+ return if def_node && (table_node.nil? || change_column_null?(def_node, table_node, column_node))
50
+
51
+ add_offense(node)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def table_node(node)
58
+ case node.method_name
59
+ when :add_column
60
+ node.first_argument
61
+ when :column, :boolean
62
+ ancestor = node.each_ancestor(:block).find do |n|
63
+ n.method?(:create_table) || n.method?(:change_table)
64
+ end
65
+ ancestor&.send_node&.first_argument
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -43,23 +43,19 @@ module RuboCop
43
43
  include ConfigurableEnforcedStyle
44
44
  extend AutoCorrector
45
45
 
46
- MSG = 'Do not use `%<current>s` without zone. Use `%<prefer>s` ' \
47
- 'instead.'
46
+ MSG = 'Do not use `%<current>s` without zone. Use `%<prefer>s` instead.'
48
47
 
49
- MSG_ACCEPTABLE = 'Do not use `%<current>s` without zone. ' \
50
- 'Use one of %<prefer>s instead.'
48
+ MSG_ACCEPTABLE = 'Do not use `%<current>s` without zone. Use one of %<prefer>s instead.'
51
49
 
52
- MSG_LOCALTIME = 'Do not use `Time.localtime` without ' \
53
- 'offset or zone.'
50
+ MSG_LOCALTIME = 'Do not use `Time.localtime` without offset or zone.'
54
51
 
55
52
  GOOD_METHODS = %i[zone zone_default find_zone find_zone!].freeze
56
53
 
57
54
  DANGEROUS_METHODS = %i[now local new parse at].freeze
58
55
 
59
- ACCEPTED_METHODS = %i[in_time_zone utc getlocal xmlschema iso8601
60
- jisx0301 rfc3339 httpdate to_i to_f].freeze
56
+ ACCEPTED_METHODS = %i[in_time_zone utc getlocal xmlschema iso8601 jisx0301 rfc3339 httpdate to_i to_f].freeze
61
57
 
62
- TIMEZONE_SPECIFIER = /[A-z]/.freeze
58
+ TIMEZONE_SPECIFIER = /([A-Za-z]|[+-]\d{2}:?\d{2})\z/.freeze
63
59
 
64
60
  def on_const(node)
65
61
  mod, klass = *node
@@ -74,7 +70,7 @@ module RuboCop
74
70
 
75
71
  def autocorrect(corrector, node)
76
72
  # add `.zone`: `Time.at` => `Time.zone.at`
77
- corrector.insert_after(node.children[0].source_range, '.zone')
73
+ corrector.insert_after(node.children[0], '.zone')
78
74
 
79
75
  case node.method_name
80
76
  when :current
@@ -85,7 +81,7 @@ module RuboCop
85
81
  end
86
82
 
87
83
  # prefer `Time` over `DateTime` class
88
- corrector.replace(node.children.first.source_range, 'Time') if strict?
84
+ corrector.replace(node.children.first, 'Time') if strict?
89
85
  remove_redundant_in_time_zone(corrector, node)
90
86
  end
91
87
 
@@ -100,13 +96,11 @@ module RuboCop
100
96
  # remove redundant `.in_time_zone` from `Time.zone.now.in_time_zone`
101
97
  def remove_redundant_in_time_zone(corrector, node)
102
98
  time_methods_called = extract_method_chain(node)
103
- return unless time_methods_called.include?(:in_time_zone) ||
104
- time_methods_called.include?(:zone)
99
+ return unless time_methods_called.include?(:in_time_zone) || time_methods_called.include?(:zone)
105
100
 
106
101
  while node&.send_type?
107
102
  if node.children.last == :in_time_zone
108
- in_time_zone_with_dot =
109
- node.loc.selector.adjust(begin_pos: -1)
103
+ in_time_zone_with_dot = node.loc.selector.adjust(begin_pos: -1)
110
104
  corrector.remove(in_time_zone_with_dot)
111
105
  end
112
106
  node = node.parent
@@ -132,7 +126,7 @@ module RuboCop
132
126
  end
133
127
 
134
128
  def attach_timezone_specifier?(date)
135
- date.respond_to?(:value) && TIMEZONE_SPECIFIER.match?(date.value.to_s[-1])
129
+ date.respond_to?(:value) && TIMEZONE_SPECIFIER.match?(date.value.to_s)
136
130
  end
137
131
 
138
132
  def build_message(klass, method_name, node)
@@ -144,9 +138,7 @@ module RuboCop
144
138
  )
145
139
  else
146
140
  safe_method_name = safe_method(method_name, node)
147
- format(MSG,
148
- current: "#{klass}.#{method_name}",
149
- prefer: "Time.zone.#{safe_method_name}")
141
+ format(MSG, current: "#{klass}.#{method_name}", prefer: "Time.zone.#{safe_method_name}")
150
142
  end
151
143
  end
152
144
 
@@ -227,10 +219,7 @@ module RuboCop
227
219
  end
228
220
 
229
221
  def acceptable_methods(klass, method_name, node)
230
- acceptable = [
231
- "`Time.zone.#{safe_method(method_name, node)}`",
232
- "`#{klass}.current`"
233
- ]
222
+ acceptable = ["`Time.zone.#{safe_method(method_name, node)}`", "`#{klass}.current`"]
234
223
 
235
224
  ACCEPTED_METHODS.each do |am|
236
225
  acceptable << "`#{klass}.#{method_name}.#{am}`"
@@ -239,12 +228,25 @@ module RuboCop
239
228
  acceptable
240
229
  end
241
230
 
242
- # Time.new can be called with a time zone offset
231
+ # Time.new, Time.at, and Time.now can be called with a time zone offset
243
232
  # When it is, that should be considered safe
244
233
  # Example:
245
234
  # Time.new(1988, 3, 15, 3, 0, 0, "-05:00")
246
235
  def offset_provided?(node)
247
- node.arguments.size >= 7
236
+ case node.method_name
237
+ when :new
238
+ node.arguments.size == 7 || offset_option_provided?(node)
239
+ when :at, :now
240
+ offset_option_provided?(node)
241
+ end
242
+ end
243
+
244
+ def offset_option_provided?(node)
245
+ options = node.last_argument
246
+ options&.hash_type? &&
247
+ options.each_pair.any? do |pair|
248
+ pair.key.sym_type? && pair.key.value == :in && !pair.value.nil_type?
249
+ end
248
250
  end
249
251
  end
250
252
  end
@@ -23,7 +23,7 @@ module RuboCop
23
23
  RESTRICT_ON_SEND = %i[zone=].freeze
24
24
 
25
25
  def_node_matcher :time_zone_assignment?, <<~PATTERN
26
- (send (const nil? :Time) :zone= ...)
26
+ (send (const {nil? cbase} :Time) :zone= ...)
27
27
  PATTERN
28
28
 
29
29
  def on_send(node)
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Identifies passing any argument to `#to_s`.
7
+ #
8
+ # @safety
9
+ # This cop is marked as unsafe because it may detect `#to_s` calls
10
+ # that are not related to Active Support implementation.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # obj.to_s(:delimited)
16
+ #
17
+ # # good
18
+ # obj.to_formatted_s(:delimited)
19
+ #
20
+ class ToSWithArgument < Base
21
+ extend AutoCorrector
22
+ extend TargetRailsVersion
23
+
24
+ # These types are defined by the following files in ActiveSupport:
25
+ # lib/active_support/core_ext/array/conversions.rb
26
+ # lib/active_support/core_ext/date/conversions.rb
27
+ # lib/active_support/core_ext/date_time/conversions.rb
28
+ # lib/active_support/core_ext/numeric/conversions.rb
29
+ # lib/active_support/core_ext/range/conversions.rb
30
+ # lib/active_support/core_ext/time/conversions.rb
31
+ # lib/active_support/time_with_zone.rb
32
+ EXTENDED_FORMAT_TYPES = Set.new(
33
+ %i[
34
+ currency
35
+ db
36
+ delimited
37
+ human
38
+ human_size
39
+ inspect
40
+ iso8601
41
+ long
42
+ long_ordinal
43
+ nsec
44
+ number
45
+ percentage
46
+ phone
47
+ rfc822
48
+ rounded
49
+ short
50
+ time
51
+ usec
52
+ ]
53
+ )
54
+
55
+ MSG = 'Use `to_formatted_s` instead.'
56
+
57
+ RESTRICT_ON_SEND = %i[to_s].freeze
58
+
59
+ minimum_target_rails_version 7.0
60
+
61
+ def on_send(node)
62
+ return unless rails_extended_to_s?(node)
63
+
64
+ add_offense(node.loc.selector) do |corrector|
65
+ corrector.replace(node.loc.selector, 'to_formatted_s')
66
+ end
67
+ end
68
+ alias on_csend on_send
69
+
70
+ private
71
+
72
+ def rails_extended_to_s?(node)
73
+ node.first_argument&.sym_type? && EXTENDED_FORMAT_TYPES.include?(node.first_argument.value)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end