rubocop-rails 2.14.2 → 2.19.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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +23 -2
  4. data/config/default.yml +190 -12
  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 +2 -2
  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 +7 -4
  17. data/lib/rubocop/cop/rails/active_record_override.rb +2 -5
  18. data/lib/rubocop/cop/rails/active_support_aliases.rb +1 -1
  19. data/lib/rubocop/cop/rails/active_support_on_load.rb +70 -0
  20. data/lib/rubocop/cop/rails/add_column_index.rb +3 -6
  21. data/lib/rubocop/cop/rails/after_commit_override.rb +1 -1
  22. data/lib/rubocop/cop/rails/application_controller.rb +2 -2
  23. data/lib/rubocop/cop/rails/application_job.rb +3 -3
  24. data/lib/rubocop/cop/rails/application_mailer.rb +2 -2
  25. data/lib/rubocop/cop/rails/application_record.rb +2 -2
  26. data/lib/rubocop/cop/rails/arel_star.rb +2 -2
  27. data/lib/rubocop/cop/rails/assert_not.rb +1 -1
  28. data/lib/rubocop/cop/rails/attribute_default_block_value.rb +1 -1
  29. data/lib/rubocop/cop/rails/belongs_to.rb +2 -5
  30. data/lib/rubocop/cop/rails/blank.rb +10 -11
  31. data/lib/rubocop/cop/rails/bulk_change_table.rb +8 -25
  32. data/lib/rubocop/cop/rails/compact_blank.rb +6 -2
  33. data/lib/rubocop/cop/rails/content_tag.rb +6 -7
  34. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +16 -3
  35. data/lib/rubocop/cop/rails/date.rb +12 -17
  36. data/lib/rubocop/cop/rails/default_scope.rb +1 -1
  37. data/lib/rubocop/cop/rails/delegate.rb +24 -18
  38. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +2 -2
  39. data/lib/rubocop/cop/rails/deprecated_active_model_errors_methods.rb +63 -3
  40. data/lib/rubocop/cop/rails/dot_separated_keys.rb +71 -0
  41. data/lib/rubocop/cop/rails/duplicate_association.rb +2 -2
  42. data/lib/rubocop/cop/rails/duplicate_scope.rb +1 -1
  43. data/lib/rubocop/cop/rails/duration_arithmetic.rb +4 -4
  44. data/lib/rubocop/cop/rails/dynamic_find_by.rb +26 -14
  45. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +6 -2
  46. data/lib/rubocop/cop/rails/enum_hash.rb +3 -4
  47. data/lib/rubocop/cop/rails/enum_uniqueness.rb +3 -6
  48. data/lib/rubocop/cop/rails/environment_comparison.rb +3 -4
  49. data/lib/rubocop/cop/rails/environment_variable_access.rb +1 -1
  50. data/lib/rubocop/cop/rails/exit.rb +1 -1
  51. data/lib/rubocop/cop/rails/expanded_date_range.rb +39 -23
  52. data/lib/rubocop/cop/rails/file_path.rb +41 -24
  53. data/lib/rubocop/cop/rails/find_by.rb +1 -1
  54. data/lib/rubocop/cop/rails/find_by_id.rb +3 -3
  55. data/lib/rubocop/cop/rails/find_each.rb +14 -4
  56. data/lib/rubocop/cop/rails/freeze_time.rb +79 -0
  57. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +1 -1
  58. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +14 -8
  59. data/lib/rubocop/cop/rails/helper_instance_variable.rb +3 -3
  60. data/lib/rubocop/cop/rails/http_positional_arguments.rb +23 -12
  61. data/lib/rubocop/cop/rails/http_status.rb +6 -11
  62. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +3 -1
  63. data/lib/rubocop/cop/rails/i18n_locale_assignment.rb +1 -1
  64. data/lib/rubocop/cop/rails/i18n_locale_texts.rb +2 -2
  65. data/lib/rubocop/cop/rails/ignored_columns_assignment.rb +50 -0
  66. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +5 -14
  67. data/lib/rubocop/cop/rails/index_by.rb +2 -2
  68. data/lib/rubocop/cop/rails/index_with.rb +2 -2
  69. data/lib/rubocop/cop/rails/inquiry.rb +1 -1
  70. data/lib/rubocop/cop/rails/inverse_of.rb +4 -10
  71. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +22 -16
  72. data/lib/rubocop/cop/rails/link_to_blank.rb +2 -5
  73. data/lib/rubocop/cop/rails/mailer_name.rb +5 -5
  74. data/lib/rubocop/cop/rails/match_route.rb +1 -1
  75. data/lib/rubocop/cop/rails/migration_class_name.rb +2 -2
  76. data/lib/rubocop/cop/rails/negate_include.rb +2 -2
  77. data/lib/rubocop/cop/rails/not_null_column.rb +10 -7
  78. data/lib/rubocop/cop/rails/order_by_id.rb +2 -3
  79. data/lib/rubocop/cop/rails/output.rb +7 -9
  80. data/lib/rubocop/cop/rails/output_safety.rb +6 -2
  81. data/lib/rubocop/cop/rails/pick.rb +1 -1
  82. data/lib/rubocop/cop/rails/pluck.rb +45 -13
  83. data/lib/rubocop/cop/rails/pluck_id.rb +2 -2
  84. data/lib/rubocop/cop/rails/pluck_in_where.rb +1 -1
  85. data/lib/rubocop/cop/rails/pluralization_grammar.rb +2 -3
  86. data/lib/rubocop/cop/rails/presence.rb +22 -13
  87. data/lib/rubocop/cop/rails/present.rb +10 -13
  88. data/lib/rubocop/cop/rails/rake_environment.rb +3 -3
  89. data/lib/rubocop/cop/rails/read_write_attribute.rb +2 -2
  90. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +5 -7
  91. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +3 -3
  92. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +3 -3
  93. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +31 -27
  94. data/lib/rubocop/cop/rails/redundant_travel_back.rb +1 -1
  95. data/lib/rubocop/cop/rails/reflection_class_name.rb +35 -2
  96. data/lib/rubocop/cop/rails/refute_methods.rb +1 -5
  97. data/lib/rubocop/cop/rails/relative_date_constant.rb +5 -8
  98. data/lib/rubocop/cop/rails/render_inline.rb +1 -1
  99. data/lib/rubocop/cop/rails/render_plain_text.rb +1 -1
  100. data/lib/rubocop/cop/rails/request_referer.rb +2 -3
  101. data/lib/rubocop/cop/rails/require_dependency.rb +2 -2
  102. data/lib/rubocop/cop/rails/response_parsed_body.rb +57 -0
  103. data/lib/rubocop/cop/rails/reversible_migration.rb +15 -63
  104. data/lib/rubocop/cop/rails/reversible_migration_method_definition.rb +5 -6
  105. data/lib/rubocop/cop/rails/root_join_chain.rb +1 -1
  106. data/lib/rubocop/cop/rails/root_pathname_methods.rb +238 -0
  107. data/lib/rubocop/cop/rails/root_public_path.rb +59 -0
  108. data/lib/rubocop/cop/rails/safe_navigation.rb +8 -13
  109. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +2 -4
  110. data/lib/rubocop/cop/rails/save_bang.rb +12 -24
  111. data/lib/rubocop/cop/rails/schema_comment.rb +1 -1
  112. data/lib/rubocop/cop/rails/scope_args.rb +1 -1
  113. data/lib/rubocop/cop/rails/short_i18n.rb +3 -6
  114. data/lib/rubocop/cop/rails/skips_model_validations.rb +3 -4
  115. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +10 -7
  116. data/lib/rubocop/cop/rails/strip_heredoc.rb +56 -0
  117. data/lib/rubocop/cop/rails/table_name_assignment.rb +1 -1
  118. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +73 -0
  119. data/lib/rubocop/cop/rails/time_zone.rb +34 -32
  120. data/lib/rubocop/cop/rails/time_zone_assignment.rb +4 -4
  121. data/lib/rubocop/cop/rails/to_formatted_s.rb +46 -0
  122. data/lib/rubocop/cop/rails/to_s_with_argument.rb +78 -0
  123. data/lib/rubocop/cop/rails/top_level_hash_with_indifferent_access.rb +49 -0
  124. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +17 -12
  125. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +3 -6
  126. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +14 -7
  127. data/lib/rubocop/cop/rails/unknown_env.rb +3 -5
  128. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +7 -2
  129. data/lib/rubocop/cop/rails/validation.rb +5 -13
  130. data/lib/rubocop/cop/rails/where_equals.rb +2 -2
  131. data/lib/rubocop/cop/rails/where_exists.rb +3 -3
  132. data/lib/rubocop/cop/rails/where_missing.rb +118 -0
  133. data/lib/rubocop/cop/rails/where_not.rb +2 -2
  134. data/lib/rubocop/cop/rails/where_not_with_multiple_conditions.rb +55 -0
  135. data/lib/rubocop/cop/rails_cops.rb +16 -0
  136. data/lib/rubocop/rails/schema_loader/schema.rb +8 -5
  137. data/lib/rubocop/rails/version.rb +1 -1
  138. data/lib/rubocop/rails.rb +1 -1
  139. data/lib/rubocop-rails.rb +19 -0
  140. metadata +23 -9
  141. data/bin/console +0 -11
  142. data/bin/setup +0 -7
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Favor `Rails.public_path` over `Rails.root` with `'public'`
7
+ #
8
+ # @example
9
+ # # bad
10
+ # Rails.root.join('public')
11
+ # Rails.root.join('public/file.pdf')
12
+ # Rails.root.join('public', 'file.pdf')
13
+ #
14
+ # # good
15
+ # Rails.public_path
16
+ # Rails.public_path.join('file.pdf')
17
+ # Rails.public_path.join('file.pdf')
18
+ #
19
+ class RootPublicPath < Base
20
+ extend AutoCorrector
21
+
22
+ MSG = 'Use `Rails.public_path`.'
23
+
24
+ RESTRICT_ON_SEND = %i[join].to_set.freeze
25
+
26
+ PATTERN = %r{\Apublic(/|\z)}.freeze
27
+
28
+ def_node_matcher :rails_root_public, <<~PATTERN
29
+ (send
30
+ (send
31
+ $(const {nil? cbase} :Rails) :root) :join
32
+ (str $#public_path?) $...)
33
+ PATTERN
34
+
35
+ def on_send(node)
36
+ return unless (rails, maybe_public_path, other_args = rails_root_public(node))
37
+
38
+ add_offense(node) do |corrector|
39
+ first_args = maybe_public_path.gsub(PATTERN, '')
40
+
41
+ args = other_args.map(&:source)
42
+ args.unshift("'#{first_args}'") unless first_args.empty?
43
+
44
+ replacement = "#{rails.source}.public_path"
45
+ replacement += ".join(#{args.join(', ')})" unless args.empty?
46
+
47
+ corrector.replace(node, replacement)
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def public_path?(string)
54
+ PATTERN.match?(string)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -3,8 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop converts usages of `try!` to `&.`. It can also be configured
7
- # to convert `try`. It will convert code to use safe navigation.
6
+ # Converts usages of `try!` to `&.`. It can also be configured
7
+ # to convert `try`. It will convert code to use safe navigation
8
+ # if the target Ruby version is set to 2.3+
8
9
  #
9
10
  # @example ConvertTry: false (default)
10
11
  # # bad
@@ -39,6 +40,9 @@ module RuboCop
39
40
  class SafeNavigation < Base
40
41
  include RangeHelp
41
42
  extend AutoCorrector
43
+ extend TargetRubyVersion
44
+
45
+ minimum_target_ruby_version 2.3
42
46
 
43
47
  MSG = 'Use safe navigation (`&.`) instead of `%<try>s`.'
44
48
  RESTRICT_ON_SEND = %i[try try!].freeze
@@ -47,15 +51,6 @@ module RuboCop
47
51
  (send _ ${:try :try!} $_ ...)
48
52
  PATTERN
49
53
 
50
- # Monkey patching for `Style/RedundantSelf` of RuboCop core.
51
- # rubocop:disable Style/ClassAndModuleChildren
52
- class Style::RedundantSelf
53
- def self.autocorrect_incompatible_with
54
- [Rails::SafeNavigation]
55
- end
56
- end
57
- # rubocop:enable Style/ClassAndModuleChildren
58
-
59
54
  def self.autocorrect_incompatible_with
60
55
  [Style::RedundantSelf]
61
56
  end
@@ -75,10 +70,10 @@ module RuboCop
75
70
 
76
71
  def autocorrect(corrector, node)
77
72
  method_node, *params = *node.arguments
78
- method = method_node.source[1..-1]
73
+ method = method_node.source[1..]
79
74
 
80
75
  range = if node.receiver
81
- 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)
82
77
  else
83
78
  corrector.insert_before(node, 'self')
84
79
  node
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks to make sure safe navigation isn't used with `blank?` in
6
+ # Checks to make sure safe navigation isn't used with `blank?` in
7
7
  # a conditional.
8
8
  #
9
9
  # @safety
@@ -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?) ...)
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop identifies possible cases where Active Record save! or related
6
+ # Identifies possible cases where Active Record save! or related
7
7
  # should be used instead of save because the model might have failed to
8
8
  # save and an exception is better than unhandled failure.
9
9
  #
@@ -37,7 +37,7 @@ module RuboCop
37
37
  #
38
38
  # update_attributes
39
39
  #
40
- # # After running rubocop --safe-auto-correct
40
+ # # After running rubocop --safe-autocorrect
41
41
  # def update_attributes
42
42
  # end
43
43
  #
@@ -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
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop enforces the use of the `comment` option when adding a new table or column
6
+ # Enforces the use of the `comment` option when adding a new table or column
7
7
  # to the database during a migration.
8
8
  #
9
9
  # @example
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for scope calls where it was passed
6
+ # Checks for scope calls where it was passed
7
7
  # a method (usually a scope) instead of a lambda/proc.
8
8
  #
9
9
  # @example
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop enforces that short forms of `I18n` methods are used:
6
+ # Enforces that short forms of `I18n` methods are used:
7
7
  # `t` instead of `translate` and `l` instead of `localize`.
8
8
  #
9
9
  # This cop has two different enforcement modes. When the EnforcedStyle
@@ -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)
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for the use of methods which skip
6
+ # Checks for the use of methods which skip
7
7
  # validations which are listed in
8
8
  # https://guides.rubyonrails.org/active_record_validations.html#skipping-validations
9
9
  #
@@ -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
@@ -8,7 +8,7 @@ module RuboCop
8
8
  #
9
9
  # @safety
10
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.
11
+ # to be preserved in order to work, thus autocorrection for this cop is not safe.
12
12
  #
13
13
  # @example
14
14
  # # bad
@@ -48,6 +48,7 @@ module RuboCop
48
48
  SQL = 'SQL'
49
49
  SQUISH = '.squish'
50
50
  MSG = 'Use `%<expect>s` instead of `%<current>s`.'
51
+ SQL_IDENTIFIER_MARKERS = /(".+?")|('.+?')|(\[.+?\])/.freeze
51
52
 
52
53
  def on_heredoc(node)
53
54
  return unless offense_detected?(node)
@@ -60,7 +61,7 @@ module RuboCop
60
61
  private
61
62
 
62
63
  def offense_detected?(node)
63
- sql_heredoc?(node) && !using_squish?(node)
64
+ sql_heredoc?(node) && !using_squish?(node) && !singleline_comments_present?(node)
64
65
  end
65
66
 
66
67
  def sql_heredoc?(node)
@@ -71,12 +72,14 @@ module RuboCop
71
72
  node.parent&.send_type? && node.parent&.method?(:squish)
72
73
  end
73
74
 
75
+ def singleline_comments_present?(node)
76
+ sql = node.children.map { |c| c.is_a?(String) ? c : c.source }.join('\n')
77
+
78
+ sql.gsub(SQL_IDENTIFIER_MARKERS, '').include?('--')
79
+ end
80
+
74
81
  def message(node)
75
- format(
76
- MSG,
77
- expect: "#{node.source}#{SQUISH}",
78
- current: node.source
79
- )
82
+ format(MSG, expect: "#{node.source}#{SQUISH}", current: node.source)
80
83
  end
81
84
  end
82
85
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Enforces the use of squiggly heredoc over `strip_heredoc`.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # <<EOS.strip_heredoc
12
+ # some text
13
+ # EOS
14
+ #
15
+ # # bad
16
+ # <<-EOS.strip_heredoc
17
+ # some text
18
+ # EOS
19
+ #
20
+ # # good
21
+ # <<~EOS
22
+ # some text
23
+ # EOS
24
+ #
25
+ class StripHeredoc < Base
26
+ extend AutoCorrector
27
+ extend TargetRubyVersion
28
+
29
+ MSG = 'Use squiggly heredoc (`<<~`) instead of `strip_heredoc`.'
30
+ RESTRICT_ON_SEND = %i[strip_heredoc].freeze
31
+
32
+ minimum_target_ruby_version 2.3
33
+
34
+ def on_send(node)
35
+ return unless (receiver = node.receiver)
36
+ return unless receiver.str_type? || receiver.dstr_type?
37
+ return unless receiver.respond_to?(:heredoc?) && receiver.heredoc?
38
+
39
+ register_offense(node, receiver)
40
+ end
41
+
42
+ private
43
+
44
+ def register_offense(node, heredoc)
45
+ add_offense(node) do |corrector|
46
+ squiggly_heredoc = heredoc.source.sub(/\A<<(-|~)?/, '<<~')
47
+
48
+ corrector.replace(heredoc, squiggly_heredoc)
49
+ corrector.remove(node.loc.dot)
50
+ corrector.remove(node.loc.selector)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop enforces the absence of explicit table name assignment.
6
+ # Enforces the absence of explicit table name assignment.
7
7
  #
8
8
  # `self.table_name=` should only be used for very good reasons,
9
9
  # such as not having control over the database, or working
@@ -0,0 +1,73 @@
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 {(sym %1) (str %1)} {(sym %2) (str %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
+ if def_node && (table_node.nil? || change_column_null?(def_node, table_node.value, column_node.value))
50
+ return
51
+ end
52
+
53
+ add_offense(node)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def table_node(node)
60
+ case node.method_name
61
+ when :add_column
62
+ node.first_argument
63
+ when :column, :boolean
64
+ ancestor = node.each_ancestor(:block).find do |n|
65
+ n.method?(:create_table) || n.method?(:change_table)
66
+ end
67
+ ancestor&.send_node&.first_argument
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for the use of Time methods without zone.
6
+ # Checks for the use of Time methods without zone.
7
7
  #
8
8
  # Built on top of Ruby on Rails style guide (https://rails.rubystyle.guide#time)
9
9
  # and the article http://danilenko.org/2012/7/6/rails_timezones/
@@ -28,38 +28,34 @@ module RuboCop
28
28
  # Time.zone.parse('2015-03-02T19:05:37')
29
29
  # Time.zone.parse('2015-03-02T19:05:37Z') # Respect ISO 8601 format with timezone specifier.
30
30
  #
31
- # @example EnforcedStyle: strict
32
- # # `strict` means that `Time` should be used with `zone`.
33
- #
34
- # # bad
35
- # Time.at(timestamp).in_time_zone
36
- #
37
31
  # @example EnforcedStyle: flexible (default)
38
32
  # # `flexible` allows usage of `in_time_zone` instead of `zone`.
39
33
  #
40
34
  # # good
41
35
  # Time.at(timestamp).in_time_zone
36
+ #
37
+ # @example EnforcedStyle: strict
38
+ # # `strict` means that `Time` should be used with `zone`.
39
+ #
40
+ # # bad
41
+ # Time.at(timestamp).in_time_zone
42
42
  class TimeZone < Base
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-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
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for the use of `Time.zone=` method.
6
+ # Checks for the use of `Time.zone=` method.
7
7
  #
8
8
  # The `zone` attribute persists for the rest of the Ruby runtime, potentially causing
9
9
  # unexpected behavior at a later time.
@@ -22,12 +22,12 @@ module RuboCop
22
22
  MSG = 'Use `Time.use_zone` with block instead of `Time.zone=`.'
23
23
  RESTRICT_ON_SEND = %i[zone=].freeze
24
24
 
25
- def_node_matcher :time_zone_assignement?, <<~PATTERN
26
- (send (const nil? :Time) :zone= ...)
25
+ def_node_matcher :time_zone_assignment?, <<~PATTERN
26
+ (send (const {nil? cbase} :Time) :zone= ...)
27
27
  PATTERN
28
28
 
29
29
  def on_send(node)
30
- return unless time_zone_assignement?(node)
30
+ return unless time_zone_assignment?(node)
31
31
 
32
32
  add_offense(node)
33
33
  end