rubocop-rails 2.19.1 → 2.21.2

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 (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