rubocop-rails 2.6.0 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -0
  3. data/config/default.yml +189 -6
  4. data/lib/rubocop/cop/mixin/active_record_helper.rb +12 -3
  5. data/lib/rubocop/cop/mixin/enforce_superclass.rb +40 -0
  6. data/lib/rubocop/cop/mixin/index_method.rb +25 -11
  7. data/lib/rubocop/cop/rails/action_filter.rb +10 -14
  8. data/lib/rubocop/cop/rails/active_record_aliases.rb +13 -17
  9. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +148 -0
  10. data/lib/rubocop/cop/rails/active_record_override.rb +1 -1
  11. data/lib/rubocop/cop/rails/active_support_aliases.rb +12 -21
  12. data/lib/rubocop/cop/rails/after_commit_override.rb +91 -0
  13. data/lib/rubocop/cop/rails/application_controller.rb +3 -7
  14. data/lib/rubocop/cop/rails/application_job.rb +2 -1
  15. data/lib/rubocop/cop/rails/application_mailer.rb +2 -7
  16. data/lib/rubocop/cop/rails/application_record.rb +2 -7
  17. data/lib/rubocop/cop/rails/arel_star.rb +41 -0
  18. data/lib/rubocop/cop/rails/assert_not.rb +8 -10
  19. data/lib/rubocop/cop/rails/attribute_default_block_value.rb +90 -0
  20. data/lib/rubocop/cop/rails/belongs_to.rb +9 -18
  21. data/lib/rubocop/cop/rails/blank.rb +27 -27
  22. data/lib/rubocop/cop/rails/bulk_change_table.rb +1 -1
  23. data/lib/rubocop/cop/rails/content_tag.rb +20 -33
  24. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +2 -1
  25. data/lib/rubocop/cop/rails/date.rb +10 -11
  26. data/lib/rubocop/cop/rails/default_scope.rb +61 -0
  27. data/lib/rubocop/cop/rails/delegate.rb +10 -10
  28. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +7 -8
  29. data/lib/rubocop/cop/rails/dynamic_find_by.rb +13 -11
  30. data/lib/rubocop/cop/rails/enum_hash.rb +11 -10
  31. data/lib/rubocop/cop/rails/enum_uniqueness.rb +2 -1
  32. data/lib/rubocop/cop/rails/environment_comparison.rb +18 -14
  33. data/lib/rubocop/cop/rails/exit.rb +4 -10
  34. data/lib/rubocop/cop/rails/file_path.rb +5 -4
  35. data/lib/rubocop/cop/rails/find_by.rb +13 -13
  36. data/lib/rubocop/cop/rails/find_by_id.rb +94 -0
  37. data/lib/rubocop/cop/rails/find_each.rb +16 -14
  38. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +3 -2
  39. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +4 -7
  40. data/lib/rubocop/cop/rails/helper_instance_variable.rb +4 -2
  41. data/lib/rubocop/cop/rails/http_positional_arguments.rb +25 -21
  42. data/lib/rubocop/cop/rails/http_status.rb +7 -9
  43. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +8 -6
  44. data/lib/rubocop/cop/rails/index_by.rb +11 -2
  45. data/lib/rubocop/cop/rails/index_with.rb +11 -2
  46. data/lib/rubocop/cop/rails/inquiry.rb +39 -0
  47. data/lib/rubocop/cop/rails/inverse_of.rb +3 -2
  48. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +17 -15
  49. data/lib/rubocop/cop/rails/link_to_blank.rb +20 -20
  50. data/lib/rubocop/cop/rails/mailer_name.rb +86 -0
  51. data/lib/rubocop/cop/rails/match_route.rb +120 -0
  52. data/lib/rubocop/cop/rails/negate_include.rb +41 -0
  53. data/lib/rubocop/cop/rails/not_null_column.rb +2 -1
  54. data/lib/rubocop/cop/rails/order_by_id.rb +52 -0
  55. data/lib/rubocop/cop/rails/output.rb +5 -2
  56. data/lib/rubocop/cop/rails/output_safety.rb +3 -2
  57. data/lib/rubocop/cop/rails/pick.rb +21 -15
  58. data/lib/rubocop/cop/rails/pluck.rb +56 -0
  59. data/lib/rubocop/cop/rails/pluck_id.rb +56 -0
  60. data/lib/rubocop/cop/rails/pluck_in_where.rb +70 -0
  61. data/lib/rubocop/cop/rails/pluralization_grammar.rb +10 -14
  62. data/lib/rubocop/cop/rails/presence.rb +12 -13
  63. data/lib/rubocop/cop/rails/present.rb +30 -24
  64. data/lib/rubocop/cop/rails/rake_environment.rb +9 -11
  65. data/lib/rubocop/cop/rails/read_write_attribute.rb +12 -11
  66. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +29 -31
  67. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +9 -12
  68. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +11 -10
  69. data/lib/rubocop/cop/rails/reflection_class_name.rb +4 -3
  70. data/lib/rubocop/cop/rails/refute_methods.rb +9 -10
  71. data/lib/rubocop/cop/rails/relative_date_constant.rb +20 -9
  72. data/lib/rubocop/cop/rails/render_inline.rb +41 -0
  73. data/lib/rubocop/cop/rails/render_plain_text.rb +71 -0
  74. data/lib/rubocop/cop/rails/request_referer.rb +7 -7
  75. data/lib/rubocop/cop/rails/reversible_migration.rb +82 -7
  76. data/lib/rubocop/cop/rails/safe_navigation.rb +12 -11
  77. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +5 -10
  78. data/lib/rubocop/cop/rails/save_bang.rb +19 -22
  79. data/lib/rubocop/cop/rails/scope_args.rb +2 -1
  80. data/lib/rubocop/cop/rails/short_i18n.rb +74 -0
  81. data/lib/rubocop/cop/rails/skips_model_validations.rb +46 -11
  82. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +82 -0
  83. data/lib/rubocop/cop/rails/time_zone.rb +22 -20
  84. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +10 -10
  85. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +18 -8
  86. data/lib/rubocop/cop/rails/unknown_env.rb +15 -4
  87. data/lib/rubocop/cop/rails/validation.rb +15 -14
  88. data/lib/rubocop/cop/rails/where_equals.rb +94 -0
  89. data/lib/rubocop/cop/rails/where_exists.rb +126 -0
  90. data/lib/rubocop/cop/rails/where_not.rb +97 -0
  91. data/lib/rubocop/cop/rails_cops.rb +22 -0
  92. data/lib/rubocop/rails/schema_loader.rb +4 -4
  93. data/lib/rubocop/rails/schema_loader/schema.rb +5 -5
  94. data/lib/rubocop/rails/version.rb +5 -1
  95. metadata +37 -9
@@ -36,10 +36,12 @@ module RuboCop
36
36
  # foo&.bar
37
37
  # foo&.bar(baz)
38
38
  # foo&.bar { |e| e.baz }
39
- class SafeNavigation < Cop
39
+ class SafeNavigation < Base
40
40
  include RangeHelp
41
+ extend AutoCorrector
41
42
 
42
43
  MSG = 'Use safe navigation (`&.`) instead of `%<try>s`.'
44
+ RESTRICT_ON_SEND = %i[try try!].freeze
43
45
 
44
46
  def_node_matcher :try_call, <<~PATTERN
45
47
  (send !nil? ${:try :try!} $_ ...)
@@ -48,26 +50,25 @@ module RuboCop
48
50
  def on_send(node)
49
51
  try_call(node) do |try_method, dispatch|
50
52
  return if try_method == :try && !cop_config['ConvertTry']
51
- return unless dispatch.sym_type? && dispatch.value =~ /\w+[=!?]?/
53
+ return unless dispatch.sym_type? && dispatch.value.match?(/\w+[=!?]?/)
52
54
 
53
- add_offense(node, message: format(MSG, try: try_method))
55
+ add_offense(node, message: format(MSG, try: try_method)) do |corrector|
56
+ autocorrect(corrector, node)
57
+ end
54
58
  end
55
59
  end
56
60
 
57
- def autocorrect(node)
61
+ private
62
+
63
+ def autocorrect(corrector, node)
58
64
  method_node, *params = *node.arguments
59
65
  method = method_node.source[1..-1]
60
66
 
61
- range = range_between(node.loc.dot.begin_pos,
62
- node.loc.expression.end_pos)
67
+ range = range_between(node.loc.dot.begin_pos, node.loc.expression.end_pos)
63
68
 
64
- lambda do |corrector|
65
- corrector.replace(range, replacement(method, params))
66
- end
69
+ corrector.replace(range, replacement(method, params))
67
70
  end
68
71
 
69
- private
70
-
71
72
  def replacement(method, params)
72
73
  new_params = params.map(&:source).join(', ')
73
74
 
@@ -19,7 +19,9 @@ module RuboCop
19
19
  # do_something if foo.blank?
20
20
  # do_something unless foo.blank?
21
21
  #
22
- class SafeNavigationWithBlank < Cop
22
+ class SafeNavigationWithBlank < Base
23
+ extend AutoCorrector
24
+
23
25
  MSG =
24
26
  'Avoid calling `blank?` with the safe navigation operator ' \
25
27
  'in conditionals.'
@@ -31,15 +33,8 @@ module RuboCop
31
33
  def on_if(node)
32
34
  return unless safe_navigation_blank_in_conditional?(node)
33
35
 
34
- add_offense(node)
35
- end
36
-
37
- def autocorrect(node)
38
- lambda do |corrector|
39
- corrector.replace(
40
- safe_navigation_blank_in_conditional?(node).location.dot,
41
- '.'
42
- )
36
+ add_offense(node) do |corrector|
37
+ corrector.replace(safe_navigation_blank_in_conditional?(node).location.dot, '.')
43
38
  end
44
39
  end
45
40
  end
@@ -98,8 +98,9 @@ module RuboCop
98
98
  # Services::Service::Mailer.update(message: 'Message')
99
99
  # Service::Mailer::update
100
100
  #
101
- class SaveBang < Cop
101
+ class SaveBang < Base
102
102
  include NegativeConditional
103
+ extend AutoCorrector
103
104
 
104
105
  MSG = 'Use `%<prefer>s` instead of `%<current>s` if the return ' \
105
106
  'value is not checked.'
@@ -113,11 +114,10 @@ module RuboCop
113
114
  first_or_create find_or_create_by].freeze
114
115
  MODIFY_PERSIST_METHODS = %i[save
115
116
  update update_attributes destroy].freeze
116
- PERSIST_METHODS = (CREATE_PERSIST_METHODS +
117
- MODIFY_PERSIST_METHODS).freeze
117
+ RESTRICT_ON_SEND = (CREATE_PERSIST_METHODS + MODIFY_PERSIST_METHODS).freeze
118
118
 
119
- def join_force?(force_class)
120
- force_class == VariableForce
119
+ def self.joining_forces
120
+ VariableForce
121
121
  end
122
122
 
123
123
  def after_leaving_scope(scope, _variable_table)
@@ -135,10 +135,10 @@ module RuboCop
135
135
  return unless persist_method?(node, CREATE_PERSIST_METHODS)
136
136
  return if persisted_referenced?(assignment)
137
137
 
138
- add_offense_for_node(node, CREATE_MSG)
138
+ register_offense(node, CREATE_MSG)
139
139
  end
140
140
 
141
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
141
+ # rubocop:disable Metrics/CyclomaticComplexity
142
142
  def on_send(node)
143
143
  return unless persist_method?(node)
144
144
  return if return_value_assigned?(node)
@@ -148,25 +148,22 @@ module RuboCop
148
148
  return if explicit_return?(node)
149
149
  return if checked_immediately?(node)
150
150
 
151
- add_offense_for_node(node)
151
+ register_offense(node, MSG)
152
152
  end
153
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
153
+ # rubocop:enable Metrics/CyclomaticComplexity
154
154
  alias on_csend on_send
155
155
 
156
- def autocorrect(node)
157
- save_loc = node.loc.selector
158
- new_method = "#{node.method_name}!"
159
-
160
- ->(corrector) { corrector.replace(save_loc, new_method) }
161
- end
162
-
163
156
  private
164
157
 
165
- def add_offense_for_node(node, msg = MSG)
166
- name = node.method_name
167
- full_message = format(msg, prefer: "#{name}!", current: name.to_s)
158
+ def register_offense(node, msg)
159
+ current_method = node.method_name
160
+ bang_method = "#{current_method}!"
161
+ full_message = format(msg, prefer: bang_method, current: current_method)
168
162
 
169
- add_offense(node, location: :selector, message: full_message)
163
+ range = node.loc.selector
164
+ add_offense(range, message: full_message) do |corrector|
165
+ corrector.replace(range, bang_method)
166
+ end
170
167
  end
171
168
 
172
169
  def right_assignment_node(assignment)
@@ -218,7 +215,7 @@ module RuboCop
218
215
  def check_used_in_condition_or_compound_boolean(node)
219
216
  return false unless in_condition_or_compound_boolean?(node)
220
217
 
221
- add_offense_for_node(node, CREATE_CONDITIONAL_MSG) unless MODIFY_PERSIST_METHODS.include?(node.method_name)
218
+ register_offense(node, CREATE_CONDITIONAL_MSG) unless MODIFY_PERSIST_METHODS.include?(node.method_name)
222
219
 
223
220
  true
224
221
  end
@@ -318,7 +315,7 @@ module RuboCop
318
315
  assignment&.lvasgn_type?
319
316
  end
320
317
 
321
- def persist_method?(node, methods = PERSIST_METHODS)
318
+ def persist_method?(node, methods = RESTRICT_ON_SEND)
322
319
  methods.include?(node.method_name) &&
323
320
  expected_signature?(node) &&
324
321
  !allowed_receiver?(node)
@@ -13,8 +13,9 @@ module RuboCop
13
13
  #
14
14
  # # good
15
15
  # scope :something, -> { where(something: true) }
16
- class ScopeArgs < Cop
16
+ class ScopeArgs < Base
17
17
  MSG = 'Use `lambda`/`proc` instead of a plain method call.'
18
+ RESTRICT_ON_SEND = %i[scope].freeze
18
19
 
19
20
  def_node_matcher :scope?, '(send nil? :scope _ $send)'
20
21
 
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces that short forms of `I18n` methods are used:
7
+ # `t` instead of `translate` and `l` instead of `localize`.
8
+ #
9
+ # This cop has two different enforcement modes. When the EnforcedStyle
10
+ # is conservative (the default) then only `I18n.translate` and `I18n.localize`
11
+ # calls are added as offenses.
12
+ #
13
+ # When the EnforcedStyle is aggressive then all `translate` and `localize` calls
14
+ # without a receiver are added as offenses.
15
+ #
16
+ # @example
17
+ # # bad
18
+ # I18n.translate :key
19
+ # I18n.localize Time.now
20
+ #
21
+ # # good
22
+ # I18n.t :key
23
+ # I18n.l Time.now
24
+ #
25
+ # @example EnforcedStyle: conservative (default)
26
+ # # good
27
+ # translate :key
28
+ # localize Time.now
29
+ # t :key
30
+ # l Time.now
31
+ #
32
+ # @example EnforcedStyle: aggressive
33
+ # # bad
34
+ # translate :key
35
+ # localize Time.now
36
+ #
37
+ # # good
38
+ # t :key
39
+ # l Time.now
40
+ #
41
+ class ShortI18n < Base
42
+ include ConfigurableEnforcedStyle
43
+ extend AutoCorrector
44
+
45
+ MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
46
+
47
+ PREFERRED_METHODS = {
48
+ translate: :t,
49
+ localize: :l
50
+ }.freeze
51
+
52
+ RESTRICT_ON_SEND = PREFERRED_METHODS.keys.freeze
53
+
54
+ def_node_matcher :long_i18n?, <<~PATTERN
55
+ (send {nil? (const nil? :I18n)} ${:translate :localize} ...)
56
+ PATTERN
57
+
58
+ def on_send(node)
59
+ return if style == :conservative && !node.receiver
60
+
61
+ long_i18n?(node) do |method_name|
62
+ good_method = PREFERRED_METHODS[method_name]
63
+ message = format(MSG, good_method: good_method, bad_method: method_name)
64
+ range = node.loc.selector
65
+
66
+ add_offense(range, message: message) do |corrector|
67
+ corrector.replace(range, PREFERRED_METHODS[method_name])
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -7,7 +7,7 @@ module RuboCop
7
7
  # validations which are listed in
8
8
  # https://guides.rubyonrails.org/active_record_validations.html#skipping-validations
9
9
  #
10
- # Methods may be ignored from this rule by configuring a `Whitelist`.
10
+ # Methods may be ignored from this rule by configuring a `AllowedMethods`.
11
11
  #
12
12
  # @example
13
13
  # # bad
@@ -26,7 +26,7 @@ module RuboCop
26
26
  # user.update(website: 'example.com')
27
27
  # FileUtils.touch('file')
28
28
  #
29
- # @example Whitelist: ["touch"]
29
+ # @example AllowedMethods: ["touch"]
30
30
  # # bad
31
31
  # DiscussionBoard.decrement_counter(:post_count, 5)
32
32
  # DiscussionBoard.increment_counter(:post_count, 5)
@@ -35,19 +35,25 @@ module RuboCop
35
35
  # # good
36
36
  # user.touch
37
37
  #
38
- class SkipsModelValidations < Cop
38
+ class SkipsModelValidations < Base
39
39
  MSG = 'Avoid using `%<method>s` because it skips validations.'
40
40
 
41
41
  METHODS_WITH_ARGUMENTS = %w[decrement!
42
42
  decrement_counter
43
43
  increment!
44
44
  increment_counter
45
+ insert
46
+ insert!
47
+ insert_all
48
+ insert_all!
45
49
  toggle!
46
50
  update_all
47
51
  update_attribute
48
52
  update_column
49
53
  update_columns
50
- update_counters].freeze
54
+ update_counters
55
+ upsert
56
+ upsert_all].freeze
51
57
 
52
58
  def_node_matcher :good_touch?, <<~PATTERN
53
59
  {
@@ -56,16 +62,30 @@ module RuboCop
56
62
  }
57
63
  PATTERN
58
64
 
65
+ def_node_matcher :good_insert?, <<~PATTERN
66
+ (send _ {:insert :insert!} _ {
67
+ !(hash ...)
68
+ (hash <(pair (sym !{:returning :unique_by}) _) ...>)
69
+ } ...)
70
+ PATTERN
71
+
59
72
  def on_send(node)
60
- return if whitelist.include?(node.method_name.to_s)
61
- return unless blacklist.include?(node.method_name.to_s)
73
+ return if allowed_methods.include?(node.method_name.to_s)
74
+ return unless forbidden_methods.include?(node.method_name.to_s)
62
75
  return if allowed_method?(node)
63
76
  return if good_touch?(node)
77
+ return if good_insert?(node)
64
78
 
65
- add_offense(node, location: :selector)
79
+ add_offense(node.loc.selector, message: message(node))
66
80
  end
67
81
  alias on_csend on_send
68
82
 
83
+ def initialize(*)
84
+ super
85
+ @displayed_allowed_warning = false
86
+ @displayed_forbidden_warning = false
87
+ end
88
+
69
89
  private
70
90
 
71
91
  def message(node)
@@ -77,12 +97,27 @@ module RuboCop
77
97
  !node.arguments?
78
98
  end
79
99
 
80
- def blacklist
81
- cop_config['Blacklist'] || []
100
+ def forbidden_methods
101
+ obsolete_result = cop_config['Blacklist']
102
+ if obsolete_result
103
+ warn '`Blacklist` has been renamed to `ForbiddenMethods`.' unless @displayed_forbidden_warning
104
+ @displayed_forbidden_warning = true
105
+ return obsolete_result
106
+ end
107
+
108
+ cop_config['ForbiddenMethods'] || []
82
109
  end
83
110
 
84
- def whitelist
85
- cop_config['Whitelist'] || []
111
+ def allowed_methods
112
+ obsolete_result = cop_config['Whitelist']
113
+ if obsolete_result
114
+ warn '`Whitelist` has been renamed to `AllowedMethods`.' unless @displayed_allowed_warning
115
+ @displayed_allowed_warning = true
116
+
117
+ return obsolete_result
118
+ end
119
+
120
+ cop_config['AllowedMethods'] || []
86
121
  end
87
122
  end
88
123
  end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ #
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.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # <<-SQL
14
+ # SELECT * FROM posts;
15
+ # SQL
16
+ #
17
+ # <<-SQL
18
+ # SELECT * FROM posts
19
+ # WHERE id = 1
20
+ # SQL
21
+ #
22
+ # execute(<<~SQL, "Post Load")
23
+ # SELECT * FROM posts
24
+ # WHERE post_id = 1
25
+ # SQL
26
+ #
27
+ # # good
28
+ # <<-SQL.squish
29
+ # SELECT * FROM posts;
30
+ # SQL
31
+ #
32
+ # <<~SQL.squish
33
+ # SELECT * FROM table
34
+ # WHERE id = 1
35
+ # SQL
36
+ #
37
+ # execute(<<~SQL.squish, "Post Load")
38
+ # SELECT * FROM posts
39
+ # WHERE post_id = 1
40
+ # SQL
41
+ #
42
+ class SquishedSQLHeredocs < Base
43
+ include Heredoc
44
+ extend AutoCorrector
45
+
46
+ SQL = 'SQL'
47
+ SQUISH = '.squish'
48
+ MSG = 'Use `%<expect>s` instead of `%<current>s`.'
49
+
50
+ def on_heredoc(node)
51
+ return unless offense_detected?(node)
52
+
53
+ add_offense(node) do |corrector|
54
+ corrector.insert_after(node, SQUISH)
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def offense_detected?(node)
61
+ sql_heredoc?(node) && !using_squish?(node)
62
+ end
63
+
64
+ def sql_heredoc?(node)
65
+ delimiter_string(node) == SQL
66
+ end
67
+
68
+ def using_squish?(node)
69
+ node.parent&.send_type? && node.parent&.method?(:squish)
70
+ end
71
+
72
+ def message(node)
73
+ format(
74
+ MSG,
75
+ expect: "#{node.source}#{SQUISH}",
76
+ current: node.source
77
+ )
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end