rubocop-rails 2.19.1 → 2.21.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -1
  3. data/config/default.yml +86 -12
  4. data/lib/rubocop/cop/mixin/index_method.rb +2 -2
  5. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +1 -1
  6. data/lib/rubocop/cop/rails/action_controller_test_case.rb +2 -2
  7. data/lib/rubocop/cop/rails/assert_not.rb +0 -1
  8. data/lib/rubocop/cop/rails/bulk_change_table.rb +20 -3
  9. data/lib/rubocop/cop/rails/dangerous_column_names.rb +439 -0
  10. data/lib/rubocop/cop/rails/date.rb +12 -3
  11. data/lib/rubocop/cop/rails/duplicate_association.rb +3 -0
  12. data/lib/rubocop/cop/rails/dynamic_find_by.rb +3 -3
  13. data/lib/rubocop/cop/rails/file_path.rb +129 -13
  14. data/lib/rubocop/cop/rails/find_each.rb +1 -1
  15. data/lib/rubocop/cop/rails/freeze_time.rb +1 -1
  16. data/lib/rubocop/cop/rails/http_status.rb +4 -3
  17. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +63 -13
  18. data/lib/rubocop/cop/rails/i18n_locale_texts.rb +5 -1
  19. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +22 -2
  20. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +9 -10
  21. data/lib/rubocop/cop/rails/not_null_column.rb +1 -1
  22. data/lib/rubocop/cop/rails/rake_environment.rb +20 -4
  23. data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +168 -0
  24. data/lib/rubocop/cop/rails/refute_methods.rb +0 -1
  25. data/lib/rubocop/cop/rails/reversible_migration.rb +1 -1
  26. data/lib/rubocop/cop/rails/root_pathname_methods.rb +38 -4
  27. data/lib/rubocop/cop/rails/save_bang.rb +2 -2
  28. data/lib/rubocop/cop/rails/schema_comment.rb +16 -10
  29. data/lib/rubocop/cop/rails/select_map.rb +78 -0
  30. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +0 -1
  31. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +2 -4
  32. data/lib/rubocop/cop/rails/time_zone.rb +12 -5
  33. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +35 -9
  34. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +15 -19
  35. data/lib/rubocop/cop/rails/unused_render_content.rb +67 -0
  36. data/lib/rubocop/cop/rails/where_exists.rb +0 -1
  37. data/lib/rubocop/cop/rails_cops.rb +4 -0
  38. data/lib/rubocop/rails/schema_loader/schema.rb +4 -4
  39. data/lib/rubocop/rails/schema_loader.rb +1 -1
  40. data/lib/rubocop/rails/version.rb +1 -1
  41. data/lib/rubocop-rails.rb +8 -0
  42. metadata +8 -4
@@ -3,7 +3,6 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- #
7
6
  # Use `assert_not` methods instead of `refute` methods.
8
7
  #
9
8
  # @example EnforcedStyle: assert_not (default)
@@ -307,7 +307,7 @@ module RuboCop
307
307
 
308
308
  def within_reversible_or_up_only_block?(node)
309
309
  node.each_ancestor(:block).any? do |ancestor|
310
- (ancestor.block_type? && ancestor.send_node.method?(:reversible)) || ancestor.send_node.method?(:up_only)
310
+ (ancestor.block_type? && ancestor.method?(:reversible)) || ancestor.method?(:up_only)
311
311
  end
312
312
  end
313
313
 
@@ -12,7 +12,7 @@ module RuboCop
12
12
  # `Style/FileRead`, `Style/FileWrite` and `Rails/RootJoinChain`.
13
13
  #
14
14
  # @safety
15
- # This cop is unsafe for autocorrection because `Dir`'s `children`, `each_child`, `entries`, and `glob`
15
+ # This cop is unsafe for autocorrection because ``Dir``'s `children`, `each_child`, `entries`, and `glob`
16
16
  # methods return string element, but these methods of `Pathname` return `Pathname` element.
17
17
  #
18
18
  # @example
@@ -32,13 +32,28 @@ module RuboCop
32
32
  # Rails.root.join('db', 'schema.rb').write(content)
33
33
  # Rails.root.join('db', 'schema.rb').binwrite(content)
34
34
  #
35
- class RootPathnameMethods < Base
35
+ class RootPathnameMethods < Base # rubocop:disable Metrics/ClassLength
36
36
  extend AutoCorrector
37
37
  include RangeHelp
38
38
 
39
39
  MSG = '`%<rails_root>s` is a `Pathname` so you can just append `#%<method>s`.'
40
40
 
41
- DIR_METHODS = %i[children delete each_child empty? entries exist? glob mkdir open rmdir unlink].to_set.freeze
41
+ DIR_GLOB_METHODS = %i[glob].to_set.freeze
42
+
43
+ DIR_NON_GLOB_METHODS = %i[
44
+ children
45
+ delete
46
+ each_child
47
+ empty?
48
+ entries
49
+ exist?
50
+ mkdir
51
+ open
52
+ rmdir
53
+ unlink
54
+ ].to_set.freeze
55
+
56
+ DIR_METHODS = (DIR_GLOB_METHODS + DIR_NON_GLOB_METHODS).freeze
42
57
 
43
58
  FILE_METHODS = %i[
44
59
  atime
@@ -134,7 +149,8 @@ module RuboCop
134
149
 
135
150
  RESTRICT_ON_SEND = (DIR_METHODS + FILE_METHODS + FILE_TEST_METHODS + FILE_UTILS_METHODS).to_set.freeze
136
151
 
137
- def_node_matcher :pathname_method, <<~PATTERN
152
+ # @!method pathname_method_for_ruby_2_5_or_higher(node)
153
+ def_node_matcher :pathname_method_for_ruby_2_5_or_higher, <<~PATTERN
138
154
  {
139
155
  (send (const {nil? cbase} :Dir) $DIR_METHODS $_ $...)
140
156
  (send (const {nil? cbase} {:IO :File}) $FILE_METHODS $_ $...)
@@ -143,6 +159,16 @@ module RuboCop
143
159
  }
144
160
  PATTERN
145
161
 
162
+ # @!method pathname_method_for_ruby_2_4_or_lower(node)
163
+ def_node_matcher :pathname_method_for_ruby_2_4_or_lower, <<~PATTERN
164
+ {
165
+ (send (const {nil? cbase} :Dir) $DIR_NON_GLOB_METHODS $_ $...)
166
+ (send (const {nil? cbase} {:IO :File}) $FILE_METHODS $_ $...)
167
+ (send (const {nil? cbase} :FileTest) $FILE_TEST_METHODS $_ $...)
168
+ (send (const {nil? cbase} :FileUtils) $FILE_UTILS_METHODS $_ $...)
169
+ }
170
+ PATTERN
171
+
146
172
  def_node_matcher :dir_glob?, <<~PATTERN
147
173
  (send
148
174
  (const {cbase nil?} :Dir) :glob ...)
@@ -183,6 +209,14 @@ module RuboCop
183
209
  yield(method, path, args, rails_root)
184
210
  end
185
211
 
212
+ def pathname_method(node)
213
+ if target_ruby_version >= 2.5
214
+ pathname_method_for_ruby_2_5_or_higher(node)
215
+ else
216
+ pathname_method_for_ruby_2_4_or_lower(node)
217
+ end
218
+ end
219
+
186
220
  def build_path_glob_replacement(path, method)
187
221
  receiver = range_between(path.source_range.begin_pos, path.children.first.loc.selector.end_pos).source
188
222
 
@@ -188,7 +188,7 @@ module RuboCop
188
188
  end
189
189
 
190
190
  def persisted_referenced?(assignment)
191
- return unless assignment.referenced?
191
+ return false unless assignment.referenced?
192
192
 
193
193
  assignment.variable.references.any? do |reference|
194
194
  call_to_persisted?(reference.node.parent)
@@ -298,7 +298,7 @@ module RuboCop
298
298
 
299
299
  node = assignable_node(node)
300
300
  method, sibling_index = find_method_with_sibling_index(node.parent)
301
- return unless method && (method.def_type? || method.block_type?)
301
+ return false unless method && (method.def_type? || method.block_type?)
302
302
 
303
303
  method.children.size == node.sibling_index + sibling_index
304
304
  end
@@ -74,17 +74,25 @@ module RuboCop
74
74
  def on_send(node)
75
75
  if add_column_without_comment?(node)
76
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
77
+ elsif create_table_without_comment?(node)
78
+ add_offense(node, message: TABLE_MSG)
79
+ elsif create_table_with_block?(node.parent)
80
+ check_column_within_create_table_block(node.parent.body)
83
81
  end
84
82
  end
85
83
 
86
84
  private
87
85
 
86
+ def check_column_within_create_table_block(node)
87
+ if node.begin_type?
88
+ node.child_nodes.each do |child_node|
89
+ add_offense(child_node, message: COLUMN_MSG) if t_column_without_comment?(child_node)
90
+ end
91
+ elsif t_column_without_comment?(node)
92
+ add_offense(node, message: COLUMN_MSG)
93
+ end
94
+ end
95
+
88
96
  def add_column_without_comment?(node)
89
97
  add_column?(node) && !add_column_with_comment?(node)
90
98
  end
@@ -93,10 +101,8 @@ module RuboCop
93
101
  create_table?(node) && !create_table_with_comment?(node)
94
102
  end
95
103
 
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)
104
+ def t_column_without_comment?(node)
105
+ t_column?(node) && !t_column_with_comment?(node)
100
106
  end
101
107
  end
102
108
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Checks for uses of `select(:column_name)` with `map(&:column_name)`.
7
+ # These can be replaced with `pluck(:column_name)`.
8
+ #
9
+ # There also should be some performance improvement since it skips instantiating the model class for matches.
10
+ #
11
+ # @safety
12
+ # This cop is unsafe because the model might override the attribute getter.
13
+ # Additionally, the model's `after_initialize` hooks are skipped when using `pluck`.
14
+ #
15
+ # @example
16
+ # # bad
17
+ # Model.select(:column_name).map(&:column_name)
18
+ #
19
+ # # good
20
+ # Model.pluck(:column_name)
21
+ #
22
+ class SelectMap < Base
23
+ extend AutoCorrector
24
+
25
+ MSG = 'Use `%<preferred_method>s` instead of `select` with `%<map_method>s`.'
26
+
27
+ RESTRICT_ON_SEND = %i[map collect].freeze
28
+
29
+ def on_send(node)
30
+ return unless node.first_argument
31
+
32
+ column_name = node.first_argument.source.delete_prefix('&:')
33
+ return unless (select_node = find_select_node(node, column_name))
34
+
35
+ offense_range = select_node.loc.selector.begin.join(node.source_range.end)
36
+ preferred_method = "pluck(:#{column_name})"
37
+ message = format(MSG, preferred_method: preferred_method, map_method: node.method_name)
38
+
39
+ add_offense(offense_range, message: message) do |corrector|
40
+ autocorrect(corrector, select_node, node, preferred_method)
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def find_select_node(node, column_name)
47
+ node.descendants.detect do |select_candidate|
48
+ next if !select_candidate.send_type? || !select_candidate.method?(:select)
49
+
50
+ match_column_name?(select_candidate, column_name)
51
+ end
52
+ end
53
+
54
+ # rubocop:disable Metrics/AbcSize
55
+ def autocorrect(corrector, select_node, node, preferred_method)
56
+ corrector.remove(select_node.loc.dot || node.loc.dot)
57
+ corrector.remove(select_node.loc.selector.begin.join(select_node.source_range.end))
58
+ corrector.replace(node.loc.selector.begin.join(node.source_range.end), preferred_method)
59
+ end
60
+ # rubocop:enable Metrics/AbcSize
61
+
62
+ def match_column_name?(select_candidate, column_name)
63
+ return false unless select_candidate.arguments.one?
64
+ return false unless (first_argument = select_candidate.first_argument)
65
+
66
+ argument = case select_candidate.first_argument.type
67
+ when :sym
68
+ first_argument.source.delete_prefix(':')
69
+ when :str
70
+ first_argument.value if first_argument.respond_to?(:value)
71
+ end
72
+
73
+ argument == column_name
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -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
@@ -35,7 +35,7 @@ module RuboCop
35
35
  PATTERN
36
36
 
37
37
  def_node_search :change_column_null?, <<~PATTERN
38
- (send nil? :change_column_null {(sym %1) (str %1)} {(sym %2) (str %2)} false)
38
+ (send nil? :change_column_null %1 %2 false)
39
39
  PATTERN
40
40
 
41
41
  def on_send(node)
@@ -46,9 +46,7 @@ module RuboCop
46
46
 
47
47
  def_node = node.each_ancestor(:def, :defs).first
48
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
49
+ return if def_node && (table_node.nil? || change_column_null?(def_node, table_node, column_node))
52
50
 
53
51
  add_offense(node)
54
52
  end
@@ -21,6 +21,7 @@ module RuboCop
21
21
  # # bad
22
22
  # Time.now
23
23
  # Time.parse('2015-03-02T19:05:37')
24
+ # '2015-03-02T19:05:37'.to_time
24
25
  #
25
26
  # # good
26
27
  # Time.current
@@ -44,18 +45,16 @@ module RuboCop
44
45
  extend AutoCorrector
45
46
 
46
47
  MSG = 'Do not use `%<current>s` without zone. Use `%<prefer>s` instead.'
47
-
48
48
  MSG_ACCEPTABLE = 'Do not use `%<current>s` without zone. Use one of %<prefer>s instead.'
49
-
50
49
  MSG_LOCALTIME = 'Do not use `Time.localtime` without offset or zone.'
50
+ MSG_STRING_TO_TIME = 'Do not use `String#to_time` without zone. Use `Time.zone.parse` instead.'
51
51
 
52
52
  GOOD_METHODS = %i[zone zone_default find_zone find_zone!].freeze
53
-
54
53
  DANGEROUS_METHODS = %i[now local new parse at].freeze
55
-
56
54
  ACCEPTED_METHODS = %i[in_time_zone utc getlocal xmlschema iso8601 jisx0301 rfc3339 httpdate to_i to_f].freeze
55
+ TIMEZONE_SPECIFIER = /([A-Za-z]|[+-]\d{2}:?\d{2})\z/.freeze
57
56
 
58
- TIMEZONE_SPECIFIER = /([A-z]|[+-]\d{2}:?\d{2})\z/.freeze
57
+ RESTRICT_ON_SEND = %i[to_time].freeze
59
58
 
60
59
  def on_const(node)
61
60
  mod, klass = *node
@@ -66,6 +65,14 @@ module RuboCop
66
65
  check_time_node(klass, node.parent) if klass == :Time
67
66
  end
68
67
 
68
+ def on_send(node)
69
+ return if !node.receiver&.str_type? || !node.method?(:to_time)
70
+
71
+ add_offense(node.loc.selector, message: MSG_STRING_TO_TIME) do |corrector|
72
+ corrector.replace(node, "Time.zone.parse(#{node.receiver.source})")
73
+ end
74
+ end
75
+
69
76
  private
70
77
 
71
78
  def autocorrect(corrector, node)
@@ -13,6 +13,8 @@ module RuboCop
13
13
  # error when rollback is desired, and to use `next` when commit is
14
14
  # desired.
15
15
  #
16
+ # If you are defining custom transaction methods, you can configure it with `TransactionMethods`.
17
+ #
16
18
  # @example
17
19
  # # bad
18
20
  # ApplicationRecord.transaction do
@@ -34,6 +36,11 @@ module RuboCop
34
36
  # throw if user.active?
35
37
  # end
36
38
  #
39
+ # # bad, as `with_lock` implicitly opens a transaction too
40
+ # ApplicationRecord.with_lock do
41
+ # break if user.active?
42
+ # end
43
+ #
37
44
  # # good
38
45
  # ApplicationRecord.transaction do
39
46
  # # Rollback
@@ -45,12 +52,16 @@ module RuboCop
45
52
  # # Commit
46
53
  # next if user.active?
47
54
  # end
55
+ #
56
+ # @example TransactionMethods: ["custom_transaction"]
57
+ # # bad
58
+ # CustomModel.custom_transaction do
59
+ # return if user.active?
60
+ # end
61
+ #
48
62
  class TransactionExitStatement < Base
49
- MSG = <<~MSG.chomp
50
- Exit statement `%<statement>s` is not allowed. Use `raise` (rollback) or `next` (commit).
51
- MSG
52
-
53
- RESTRICT_ON_SEND = %i[transaction with_lock].freeze
63
+ MSG = 'Exit statement `%<statement>s` is not allowed. Use `raise` (rollback) or `next` (commit).'
64
+ BUILT_IN_TRANSACTION_METHODS = %i[transaction with_lock].freeze
54
65
 
55
66
  def_node_search :exit_statements, <<~PATTERN
56
67
  ({return | break | send nil? :throw} ...)
@@ -65,10 +76,9 @@ module RuboCop
65
76
  PATTERN
66
77
 
67
78
  def on_send(node)
68
- return unless (parent = node.parent)
69
- return unless parent.block_type? && parent.body
79
+ return unless in_transaction_block?(node)
70
80
 
71
- exit_statements(parent.body).each do |statement_node|
81
+ exit_statements(node.parent.body).each do |statement_node|
72
82
  next if statement_node.break_type? && nested_block?(statement_node)
73
83
 
74
84
  statement = statement(statement_node)
@@ -80,6 +90,13 @@ module RuboCop
80
90
 
81
91
  private
82
92
 
93
+ def in_transaction_block?(node)
94
+ return false unless transaction_method_name?(node.method_name)
95
+ return false unless (parent = node.parent)
96
+
97
+ parent.block_type? && parent.body
98
+ end
99
+
83
100
  def statement(statement_node)
84
101
  if statement_node.return_type?
85
102
  'return'
@@ -91,7 +108,16 @@ module RuboCop
91
108
  end
92
109
 
93
110
  def nested_block?(statement_node)
94
- !statement_node.ancestors.find(&:block_type?).method?(:transaction)
111
+ name = statement_node.ancestors.find(&:block_type?).children.first.method_name
112
+ !transaction_method_name?(name)
113
+ end
114
+
115
+ def transaction_method_name?(method_name)
116
+ BUILT_IN_TRANSACTION_METHODS.include?(method_name) || transaction_method?(method_name)
117
+ end
118
+
119
+ def transaction_method?(method_name)
120
+ cop_config.fetch('TransactionMethods', []).include?(method_name.to_s)
95
121
  end
96
122
  end
97
123
  end
@@ -31,11 +31,11 @@ module RuboCop
31
31
  RESTRICT_ON_SEND = %i[validates].freeze
32
32
 
33
33
  def on_send(node)
34
- return unless uniqueness_part(node)
35
- return if condition_part?(node)
36
34
  return unless schema
35
+ return unless (uniqueness_part = uniqueness_part(node))
36
+ return if uniqueness_part.falsey_literal? || condition_part?(node, uniqueness_part)
37
37
 
38
- klass, table, names = find_schema_information(node)
38
+ klass, table, names = find_schema_information(node, uniqueness_part)
39
39
  return unless names
40
40
  return if with_index?(klass, table, names)
41
41
 
@@ -44,12 +44,12 @@ module RuboCop
44
44
 
45
45
  private
46
46
 
47
- def find_schema_information(node)
47
+ def find_schema_information(node, uniqueness_part)
48
48
  klass = class_node(node)
49
49
  return unless klass
50
50
 
51
51
  table = schema.table_by(name: table_name(klass))
52
- names = column_names(node)
52
+ names = column_names(node, uniqueness_part)
53
53
 
54
54
  [klass, table, names]
55
55
  end
@@ -71,12 +71,12 @@ module RuboCop
71
71
  end
72
72
  end
73
73
 
74
- def column_names(node)
74
+ def column_names(node, uniqueness_part)
75
75
  arg = node.first_argument
76
76
  return unless arg.str_type? || arg.sym_type?
77
77
 
78
78
  ret = [arg.value]
79
- names_from_scope = column_names_from_scope(node)
79
+ names_from_scope = column_names_from_scope(uniqueness_part)
80
80
  ret.concat(names_from_scope) if names_from_scope
81
81
 
82
82
  ret = ret.flat_map do |name|
@@ -90,11 +90,10 @@ module RuboCop
90
90
  ret.include?(nil) ? nil : ret.to_set
91
91
  end
92
92
 
93
- def column_names_from_scope(node)
94
- uniq = uniqueness_part(node)
95
- return unless uniq.hash_type?
93
+ def column_names_from_scope(uniqueness_part)
94
+ return unless uniqueness_part.hash_type?
96
95
 
97
- scope = find_scope(uniq)
96
+ scope = find_scope(uniqueness_part)
98
97
  return unless scope
99
98
 
100
99
  scope = unfreeze_scope(scope)
@@ -126,7 +125,7 @@ module RuboCop
126
125
 
127
126
  def uniqueness_part(node)
128
127
  pairs = node.arguments.last
129
- return unless pairs.hash_type?
128
+ return unless pairs&.hash_type?
130
129
 
131
130
  pairs.each_pair.find do |pair|
132
131
  next unless pair.key.sym_type? && pair.key.value == :uniqueness
@@ -135,14 +134,11 @@ module RuboCop
135
134
  end
136
135
  end
137
136
 
138
- def condition_part?(node)
139
- pairs = node.arguments.last
140
- return unless pairs.hash_type?
141
-
137
+ def condition_part?(node, uniqueness_node)
138
+ pairs = node.last_argument
139
+ return false unless pairs.hash_type?
142
140
  return true if condition_hash_part?(pairs, keys: %i[if unless])
143
-
144
- uniqueness_node = uniqueness_part(node)
145
- return unless uniqueness_node&.hash_type?
141
+ return false unless uniqueness_node.hash_type?
146
142
 
147
143
  condition_hash_part?(uniqueness_node, keys: %i[if unless conditions])
148
144
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # If you try to render content along with a non-content status code (100-199, 204, 205, or 304),
7
+ # it will be dropped from the response.
8
+ #
9
+ # This cop checks for uses of `render` which specify both body content and a non-content status.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # render 'foo', status: :continue
14
+ # render status: 100, plain: 'Ruby!'
15
+ #
16
+ # # good
17
+ # head :continue
18
+ # head 100
19
+ class UnusedRenderContent < Base
20
+ include RangeHelp
21
+
22
+ MSG = 'Do not specify body content for a response with a non-content status code'
23
+ RESTRICT_ON_SEND = %i[render].freeze
24
+ NON_CONTENT_STATUS_CODES = Set[*100..199, 204, 205, 304] & ::Rack::Utils::SYMBOL_TO_STATUS_CODE.values
25
+ NON_CONTENT_STATUSES = Set[
26
+ *::Rack::Utils::SYMBOL_TO_STATUS_CODE.invert.fetch_values(*NON_CONTENT_STATUS_CODES)
27
+ ]
28
+ BODY_OPTIONS = Set[
29
+ :action,
30
+ :body,
31
+ :content_type,
32
+ :file,
33
+ :html,
34
+ :inline,
35
+ :json,
36
+ :js,
37
+ :layout,
38
+ :plain,
39
+ :raw,
40
+ :template,
41
+ :text,
42
+ :xml
43
+ ]
44
+
45
+ def_node_matcher :non_content_status?, <<~PATTERN
46
+ (pair
47
+ (sym :status)
48
+ {(sym NON_CONTENT_STATUSES) (int NON_CONTENT_STATUS_CODES)}
49
+ )
50
+ PATTERN
51
+
52
+ def_node_matcher :unused_render_content?, <<~PATTERN
53
+ (send nil? :render {
54
+ (hash <#non_content_status? $(pair (sym BODY_OPTIONS) _) ...>) |
55
+ $({str sym} _) (hash <#non_content_status? ...>)
56
+ })
57
+ PATTERN
58
+
59
+ def on_send(node)
60
+ unused_render_content?(node) do |unused_content_node|
61
+ add_offense(unused_content_node)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -39,7 +39,6 @@ module RuboCop
39
39
  # # bad
40
40
  # User.exists?(name: 'john')
41
41
  # User.exists?(['name = ?', 'john'])
42
- # User.exists?('name = ?', 'john')
43
42
  # user.posts.exists?(published: true)
44
43
  #
45
44
  # # good
@@ -32,6 +32,7 @@ require_relative 'rails/bulk_change_table'
32
32
  require_relative 'rails/compact_blank'
33
33
  require_relative 'rails/content_tag'
34
34
  require_relative 'rails/create_table_with_timestamps'
35
+ require_relative 'rails/dangerous_column_names'
35
36
  require_relative 'rails/date'
36
37
  require_relative 'rails/default_scope'
37
38
  require_relative 'rails/delegate'
@@ -87,6 +88,7 @@ require_relative 'rails/presence'
87
88
  require_relative 'rails/present'
88
89
  require_relative 'rails/rake_environment'
89
90
  require_relative 'rails/read_write_attribute'
91
+ require_relative 'rails/redundant_active_record_all_method'
90
92
  require_relative 'rails/redundant_allow_nil'
91
93
  require_relative 'rails/redundant_foreign_key'
92
94
  require_relative 'rails/redundant_presence_validation_on_belongs_to'
@@ -110,6 +112,7 @@ require_relative 'rails/safe_navigation_with_blank'
110
112
  require_relative 'rails/save_bang'
111
113
  require_relative 'rails/schema_comment'
112
114
  require_relative 'rails/scope_args'
115
+ require_relative 'rails/select_map'
113
116
  require_relative 'rails/short_i18n'
114
117
  require_relative 'rails/skips_model_validations'
115
118
  require_relative 'rails/squished_sql_heredocs'
@@ -126,6 +129,7 @@ require_relative 'rails/uniq_before_pluck'
126
129
  require_relative 'rails/unique_validation_without_index'
127
130
  require_relative 'rails/unknown_env'
128
131
  require_relative 'rails/unused_ignored_columns'
132
+ require_relative 'rails/unused_render_content'
129
133
  require_relative 'rails/validation'
130
134
  require_relative 'rails/where_equals'
131
135
  require_relative 'rails/where_exists'
@@ -83,21 +83,21 @@ module RuboCop
83
83
  private
84
84
 
85
85
  def build_columns(node)
86
- each_content(node).map do |child|
86
+ each_content(node).filter_map do |child|
87
87
  next unless child&.send_type?
88
88
  next if child.method?(:index)
89
89
 
90
90
  Column.new(child)
91
- end.compact
91
+ end
92
92
  end
93
93
 
94
94
  def build_indices(node)
95
- each_content(node).map do |child|
95
+ each_content(node).filter_map do |child|
96
96
  next unless child&.send_type?
97
97
  next unless child.method?(:index)
98
98
 
99
99
  Index.new(child)
100
- end.compact
100
+ end
101
101
  end
102
102
 
103
103
  def each_content(node, &block)
@@ -43,7 +43,7 @@ module RuboCop
43
43
  return unless path
44
44
 
45
45
  ast = parse(path, target_ruby_version)
46
- Schema.new(ast)
46
+ Schema.new(ast) if ast
47
47
  end
48
48
 
49
49
  def parse(path, target_ruby_version)
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Rails
5
5
  # This module holds the RuboCop Rails version information.
6
6
  module Version
7
- STRING = '2.19.1'
7
+ STRING = '2.21.2'
8
8
 
9
9
  def self.document_version
10
10
  STRING.match('\d+\.\d+').to_s
data/lib/rubocop-rails.rb CHANGED
@@ -17,6 +17,14 @@ require_relative 'rubocop/cop/rails_cops'
17
17
 
18
18
  RuboCop::Cop::Style::HashExcept.minimum_target_ruby_version(2.0)
19
19
 
20
+ RuboCop::Cop::Style::InverseMethods.singleton_class.prepend(
21
+ Module.new do
22
+ def autocorrect_incompatible_with
23
+ super.push(RuboCop::Cop::Rails::NegateInclude)
24
+ end
25
+ end
26
+ )
27
+
20
28
  RuboCop::Cop::Style::MethodCallWithArgsParentheses.singleton_class.prepend(
21
29
  Module.new do
22
30
  def autocorrect_incompatible_with