rubocop-rails 2.20.2 → 2.25.0

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -7
  3. data/config/default.yml +81 -12
  4. data/lib/rubocop/cop/mixin/active_record_helper.rb +15 -3
  5. data/lib/rubocop/cop/mixin/database_type_resolvable.rb +66 -0
  6. data/lib/rubocop/cop/mixin/index_method.rb +2 -2
  7. data/lib/rubocop/cop/mixin/target_rails_version.rb +29 -2
  8. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +3 -1
  9. data/lib/rubocop/cop/rails/action_controller_test_case.rb +2 -2
  10. data/lib/rubocop/cop/rails/action_filter.rb +3 -0
  11. data/lib/rubocop/cop/rails/active_record_aliases.rb +2 -2
  12. data/lib/rubocop/cop/rails/active_support_aliases.rb +6 -5
  13. data/lib/rubocop/cop/rails/active_support_on_load.rb +21 -1
  14. data/lib/rubocop/cop/rails/after_commit_override.rb +1 -1
  15. data/lib/rubocop/cop/rails/bulk_change_table.rb +8 -41
  16. data/lib/rubocop/cop/rails/content_tag.rb +1 -1
  17. data/lib/rubocop/cop/rails/dangerous_column_names.rb +446 -0
  18. data/lib/rubocop/cop/rails/date.rb +1 -1
  19. data/lib/rubocop/cop/rails/duplicate_association.rb +69 -12
  20. data/lib/rubocop/cop/rails/dynamic_find_by.rb +3 -3
  21. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +2 -2
  22. data/lib/rubocop/cop/rails/env_local.rb +46 -0
  23. data/lib/rubocop/cop/rails/expanded_date_range.rb +1 -1
  24. data/lib/rubocop/cop/rails/file_path.rb +9 -6
  25. data/lib/rubocop/cop/rails/find_by.rb +3 -3
  26. data/lib/rubocop/cop/rails/find_by_id.rb +9 -23
  27. data/lib/rubocop/cop/rails/freeze_time.rb +1 -1
  28. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +1 -1
  29. data/lib/rubocop/cop/rails/helper_instance_variable.rb +1 -1
  30. data/lib/rubocop/cop/rails/http_status.rb +16 -5
  31. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +63 -13
  32. data/lib/rubocop/cop/rails/inquiry.rb +1 -0
  33. data/lib/rubocop/cop/rails/inverse_of.rb +1 -1
  34. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +7 -8
  35. data/lib/rubocop/cop/rails/not_null_column.rb +94 -6
  36. data/lib/rubocop/cop/rails/output.rb +3 -2
  37. data/lib/rubocop/cop/rails/pick.rb +10 -5
  38. data/lib/rubocop/cop/rails/pluck.rb +1 -1
  39. data/lib/rubocop/cop/rails/pluck_id.rb +2 -1
  40. data/lib/rubocop/cop/rails/pluck_in_where.rb +18 -5
  41. data/lib/rubocop/cop/rails/rake_environment.rb +22 -6
  42. data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +219 -0
  43. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +7 -0
  44. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +1 -1
  45. data/lib/rubocop/cop/rails/response_parsed_body.rb +52 -10
  46. data/lib/rubocop/cop/rails/reversible_migration.rb +4 -4
  47. data/lib/rubocop/cop/rails/root_pathname_methods.rb +38 -4
  48. data/lib/rubocop/cop/rails/save_bang.rb +15 -8
  49. data/lib/rubocop/cop/rails/schema_comment.rb +16 -10
  50. data/lib/rubocop/cop/rails/select_map.rb +78 -0
  51. data/lib/rubocop/cop/rails/skips_model_validations.rb +1 -1
  52. data/lib/rubocop/cop/rails/time_zone.rb +13 -5
  53. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +29 -10
  54. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +12 -4
  55. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +2 -2
  56. data/lib/rubocop/cop/rails/unknown_env.rb +5 -1
  57. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +6 -0
  58. data/lib/rubocop/cop/rails/unused_render_content.rb +67 -0
  59. data/lib/rubocop/cop/rails/validation.rb +6 -4
  60. data/lib/rubocop/cop/rails/where_equals.rb +3 -2
  61. data/lib/rubocop/cop/rails/where_exists.rb +9 -9
  62. data/lib/rubocop/cop/rails/where_missing.rb +6 -2
  63. data/lib/rubocop/cop/rails/where_not.rb +8 -6
  64. data/lib/rubocop/cop/rails/where_range.rb +157 -0
  65. data/lib/rubocop/cop/rails_cops.rb +7 -0
  66. data/lib/rubocop/rails/schema_loader/schema.rb +3 -2
  67. data/lib/rubocop/rails/schema_loader.rb +5 -15
  68. data/lib/rubocop/rails/version.rb +1 -1
  69. data/lib/rubocop-rails.rb +8 -0
  70. metadata +31 -4
@@ -22,10 +22,13 @@ module RuboCop
22
22
  # @example
23
23
  # # bad
24
24
  # Post.where(user_id: User.active.pluck(:id))
25
+ # Post.where(user_id: User.active.ids)
26
+ # Post.where.not(user_id: User.active.pluck(:id))
25
27
  #
26
28
  # # good
27
29
  # Post.where(user_id: User.active.select(:id))
28
30
  # Post.where(user_id: active_users.select(:id))
31
+ # Post.where.not(user_id: active_users.select(:id))
29
32
  #
30
33
  # @example EnforcedStyle: conservative (default)
31
34
  # # good
@@ -40,8 +43,9 @@ module RuboCop
40
43
  include ConfigurableEnforcedStyle
41
44
  extend AutoCorrector
42
45
 
43
- MSG = 'Use `select` instead of `pluck` within `where` query method.'
44
- RESTRICT_ON_SEND = %i[pluck].freeze
46
+ MSG_SELECT = 'Use `select` instead of `pluck` within `where` query method.'
47
+ MSG_IDS = 'Use `select(:id)` instead of `ids` within `where` query method.'
48
+ RESTRICT_ON_SEND = %i[pluck ids].freeze
45
49
 
46
50
  def on_send(node)
47
51
  return unless in_where?(node)
@@ -49,17 +53,26 @@ module RuboCop
49
53
 
50
54
  range = node.loc.selector
51
55
 
52
- add_offense(range) do |corrector|
53
- corrector.replace(range, 'select')
56
+ if node.method?(:ids)
57
+ replacement = 'select(:id)'
58
+ message = MSG_IDS
59
+ else
60
+ replacement = 'select'
61
+ message = MSG_SELECT
62
+ end
63
+
64
+ add_offense(range, message: message) do |corrector|
65
+ corrector.replace(range, replacement)
54
66
  end
55
67
  end
68
+ alias on_csend on_send
56
69
 
57
70
  private
58
71
 
59
72
  def root_receiver(node)
60
73
  receiver = node.receiver
61
74
 
62
- if receiver&.send_type?
75
+ if receiver&.call_type?
63
76
  root_receiver(receiver)
64
77
  else
65
78
  receiver
@@ -45,16 +45,24 @@ module RuboCop
45
45
  return if with_dependencies?(task_method)
46
46
 
47
47
  add_offense(task_method) do |corrector|
48
- task_name = task_method.arguments[0]
49
- task_dependency = correct_task_dependency(task_name)
50
-
51
- corrector.replace(task_name, task_dependency)
48
+ if with_arguments?(task_method)
49
+ new_task_dependency = correct_task_arguments_dependency(task_method)
50
+ corrector.replace(task_arguments(task_method), new_task_dependency)
51
+ else
52
+ task_name = task_method.first_argument
53
+ new_task_dependency = correct_task_dependency(task_name)
54
+ corrector.replace(task_name, new_task_dependency)
55
+ end
52
56
  end
53
57
  end
54
58
  end
55
59
 
56
60
  private
57
61
 
62
+ def correct_task_arguments_dependency(task_method)
63
+ "#{task_arguments(task_method).source} => :environment"
64
+ end
65
+
58
66
  def correct_task_dependency(task_name)
59
67
  if task_name.sym_type?
60
68
  "#{task_name.source.delete(':|\'|"')}: :environment"
@@ -64,7 +72,7 @@ module RuboCop
64
72
  end
65
73
 
66
74
  def task_name(node)
67
- first_arg = node.arguments[0]
75
+ first_arg = node.first_argument
68
76
  case first_arg&.type
69
77
  when :sym, :str
70
78
  first_arg.value.to_sym
@@ -80,8 +88,16 @@ module RuboCop
80
88
  end
81
89
  end
82
90
 
91
+ def task_arguments(node)
92
+ node.arguments[1]
93
+ end
94
+
95
+ def with_arguments?(node)
96
+ node.arguments.size > 1 && node.arguments[1].array_type?
97
+ end
98
+
83
99
  def with_dependencies?(node)
84
- first_arg = node.arguments[0]
100
+ first_arg = node.first_argument
85
101
  return false unless first_arg
86
102
 
87
103
  if first_arg.hash_type?
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # TODO: In the future, please support only RuboCop 1.52+ and use `RuboCop::Cop::AllowedReceivers`:
7
+ # https://github.com/rubocop/rubocop/blob/v1.52.0/lib/rubocop/cop/mixin/allowed_receivers.rb
8
+ # At that time, this duplicated module implementation can be removed.
9
+ module AllowedReceivers
10
+ def allowed_receiver?(receiver)
11
+ receiver_name = receiver_name(receiver)
12
+
13
+ allowed_receivers.include?(receiver_name)
14
+ end
15
+
16
+ def receiver_name(receiver)
17
+ return receiver_name(receiver.receiver) if receiver.receiver && !receiver.receiver.const_type?
18
+
19
+ if receiver.send_type?
20
+ if receiver.receiver
21
+ "#{receiver_name(receiver.receiver)}.#{receiver.method_name}"
22
+ else
23
+ receiver.method_name.to_s
24
+ end
25
+ else
26
+ receiver.source
27
+ end
28
+ end
29
+
30
+ def allowed_receivers
31
+ cop_config.fetch('AllowedReceivers', [])
32
+ end
33
+ end
34
+
35
+ # Detect redundant `all` used as a receiver for Active Record query methods.
36
+ #
37
+ # For the methods `delete_all` and `destroy_all`, this cop will only check cases where the receiver is a model.
38
+ # It will ignore cases where the receiver is an association (e.g., `user.articles.all.delete_all`).
39
+ # This is because omitting `all` from an association changes the methods
40
+ # from `ActiveRecord::Relation` to `ActiveRecord::Associations::CollectionProxy`,
41
+ # which can affect their behavior.
42
+ #
43
+ # @safety
44
+ # This cop is unsafe for autocorrection if the receiver for `all` is not an Active Record object.
45
+ #
46
+ # @example
47
+ # # bad
48
+ # User.all.find(id)
49
+ # User.all.order(:created_at)
50
+ # users.all.where(id: ids)
51
+ # user.articles.all.order(:created_at)
52
+ #
53
+ # # good
54
+ # User.find(id)
55
+ # User.order(:created_at)
56
+ # users.where(id: ids)
57
+ # user.articles.order(:created_at)
58
+ #
59
+ # @example AllowedReceivers: ['ActionMailer::Preview', 'ActiveSupport::TimeZone'] (default)
60
+ # # good
61
+ # ActionMailer::Preview.all.first
62
+ # ActiveSupport::TimeZone.all.first
63
+ class RedundantActiveRecordAllMethod < Base
64
+ include ActiveRecordHelper
65
+ include AllowedReceivers
66
+ include RangeHelp
67
+ extend AutoCorrector
68
+
69
+ MSG = 'Redundant `all` detected.'
70
+
71
+ RESTRICT_ON_SEND = [:all].freeze
72
+
73
+ # Defined methods in `ActiveRecord::Querying::QUERYING_METHODS` on activerecord 7.1.0.
74
+ QUERYING_METHODS = %i[
75
+ and
76
+ annotate
77
+ any?
78
+ async_average
79
+ async_count
80
+ async_ids
81
+ async_maximum
82
+ async_minimum
83
+ async_pick
84
+ async_pluck
85
+ async_sum
86
+ average
87
+ calculate
88
+ count
89
+ create_or_find_by
90
+ create_or_find_by!
91
+ create_with
92
+ delete_all
93
+ delete_by
94
+ destroy_all
95
+ destroy_by
96
+ distinct
97
+ eager_load
98
+ except
99
+ excluding
100
+ exists?
101
+ extending
102
+ extract_associated
103
+ fifth
104
+ fifth!
105
+ find
106
+ find_by
107
+ find_by!
108
+ find_each
109
+ find_in_batches
110
+ find_or_create_by
111
+ find_or_create_by!
112
+ find_or_initialize_by
113
+ find_sole_by
114
+ first
115
+ first!
116
+ first_or_create
117
+ first_or_create!
118
+ first_or_initialize
119
+ forty_two
120
+ forty_two!
121
+ fourth
122
+ fourth!
123
+ from
124
+ group
125
+ having
126
+ ids
127
+ in_batches
128
+ in_order_of
129
+ includes
130
+ invert_where
131
+ joins
132
+ last
133
+ last!
134
+ left_joins
135
+ left_outer_joins
136
+ limit
137
+ lock
138
+ many?
139
+ maximum
140
+ merge
141
+ minimum
142
+ none
143
+ none?
144
+ offset
145
+ one?
146
+ only
147
+ optimizer_hints
148
+ or
149
+ order
150
+ pick
151
+ pluck
152
+ preload
153
+ readonly
154
+ references
155
+ regroup
156
+ reorder
157
+ reselect
158
+ rewhere
159
+ second
160
+ second!
161
+ second_to_last
162
+ second_to_last!
163
+ select
164
+ sole
165
+ strict_loading
166
+ sum
167
+ take
168
+ take!
169
+ third
170
+ third!
171
+ third_to_last
172
+ third_to_last!
173
+ touch_all
174
+ unscope
175
+ update_all
176
+ where
177
+ with
178
+ without
179
+ ].to_set.freeze
180
+
181
+ POSSIBLE_ENUMERABLE_BLOCK_METHODS = %i[any? count find none? one? select sum].freeze
182
+ SENSITIVE_METHODS_ON_ASSOCIATION = %i[delete_all destroy_all].freeze
183
+
184
+ def_node_matcher :followed_by_query_method?, <<~PATTERN
185
+ (send (send _ :all) QUERYING_METHODS ...)
186
+ PATTERN
187
+
188
+ def on_send(node)
189
+ return unless followed_by_query_method?(node.parent)
190
+ return if possible_enumerable_block_method?(node) || sensitive_association_method?(node)
191
+ return if node.receiver ? allowed_receiver?(node.receiver) : !inherit_active_record_base?(node)
192
+
193
+ range_of_all_method = offense_range(node)
194
+ add_offense(range_of_all_method) do |collector|
195
+ collector.remove(range_of_all_method)
196
+ collector.remove(node.parent.loc.dot)
197
+ end
198
+ end
199
+
200
+ private
201
+
202
+ def possible_enumerable_block_method?(node)
203
+ parent = node.parent
204
+ return false unless POSSIBLE_ENUMERABLE_BLOCK_METHODS.include?(parent.method_name)
205
+
206
+ parent.parent&.block_type? || parent.parent&.numblock_type? || parent.first_argument&.block_pass_type?
207
+ end
208
+
209
+ def sensitive_association_method?(node)
210
+ !node.receiver&.const_type? && SENSITIVE_METHODS_ON_ASSOCIATION.include?(node.parent.method_name)
211
+ end
212
+
213
+ def offense_range(node)
214
+ range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
@@ -53,6 +53,12 @@ module RuboCop
53
53
  # @example source that matches - by a foreign key
54
54
  # validates :user_id, presence: true
55
55
  #
56
+ # @example source that DOES NOT match - if condition
57
+ # validates :user_id, presence: true, if: condition
58
+ #
59
+ # @example source that DOES NOT match - unless condition
60
+ # validates :user_id, presence: true, unless: condition
61
+ #
56
62
  # @example source that DOES NOT match - strict validation
57
63
  # validates :user_id, presence: true, strict: true
58
64
  #
@@ -65,6 +71,7 @@ module RuboCop
65
71
  $[
66
72
  (hash <$(pair (sym :presence) true) ...>) # presence: true
67
73
  !(hash <$(pair (sym :strict) {true const}) ...>) # strict: true
74
+ !(hash <$(pair (sym {:if :unless}) _) ...>) # if: some_condition or unless: some_condition
68
75
  ]
69
76
  )
70
77
  PATTERN
@@ -100,7 +100,7 @@ module RuboCop
100
100
  else
101
101
  return false if node.arguments.empty?
102
102
 
103
- arg = node.arguments.first
103
+ arg = node.first_argument
104
104
  ->(n) { same_value?(arg, n.receiver) }
105
105
  end
106
106
 
@@ -3,25 +3,30 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # Prefer `response.parsed_body` to `JSON.parse(response.body)`.
6
+ # Prefer `response.parsed_body` to custom parsing logic for `response.body`.
7
7
  #
8
8
  # @safety
9
- # This cop is unsafe because Content-Type may not be `application/json`. For example, the proprietary
10
- # Content-Type provided by corporate entities such as `application/vnd.github+json` is not supported at
11
- # `response.parsed_body` by default, so you still have to use `JSON.parse(response.body)` there.
9
+ # This cop is unsafe because Content-Type may not be `application/json` or `text/html`.
10
+ # For example, the proprietary Content-Type provided by corporate entities such as
11
+ # `application/vnd.github+json` is not supported at `response.parsed_body` by default,
12
+ # so you still have to use `JSON.parse(response.body)` there.
12
13
  #
13
14
  # @example
14
15
  # # bad
15
16
  # JSON.parse(response.body)
16
17
  #
18
+ # # bad
19
+ # Nokogiri::HTML.parse(response.body)
20
+ #
21
+ # # bad
22
+ # Nokogiri::HTML5.parse(response.body)
23
+ #
17
24
  # # good
18
25
  # response.parsed_body
19
26
  class ResponseParsedBody < Base
20
27
  extend AutoCorrector
21
28
  extend TargetRailsVersion
22
29
 
23
- MSG = 'Prefer `response.parsed_body` to `JSON.parse(response.body)`.'
24
-
25
30
  RESTRICT_ON_SEND = %i[parse].freeze
26
31
 
27
32
  minimum_target_rails_version 5.0
@@ -38,12 +43,27 @@ module RuboCop
38
43
  )
39
44
  PATTERN
40
45
 
46
+ # @!method nokogiri_html_parse_response_body(node)
47
+ def_node_matcher :nokogiri_html_parse_response_body, <<~PATTERN
48
+ (send
49
+ (const
50
+ (const {nil? cbase} :Nokogiri)
51
+ ${:HTML :HTML5}
52
+ )
53
+ :parse
54
+ (send
55
+ (send nil? :response)
56
+ :body
57
+ )
58
+ )
59
+ PATTERN
60
+
41
61
  def on_send(node)
42
- return unless json_parse_response_body?(node)
62
+ check_json_parse_response_body(node)
43
63
 
44
- add_offense(node) do |corrector|
45
- autocorrect(corrector, node)
46
- end
64
+ return unless target_rails_version >= 7.1
65
+
66
+ check_nokogiri_html_parse_response_body(node)
47
67
  end
48
68
 
49
69
  private
@@ -51,6 +71,28 @@ module RuboCop
51
71
  def autocorrect(corrector, node)
52
72
  corrector.replace(node, 'response.parsed_body')
53
73
  end
74
+
75
+ def check_json_parse_response_body(node)
76
+ return unless json_parse_response_body?(node)
77
+
78
+ add_offense(
79
+ node,
80
+ message: 'Prefer `response.parsed_body` to `JSON.parse(response.body)`.'
81
+ ) do |corrector|
82
+ autocorrect(corrector, node)
83
+ end
84
+ end
85
+
86
+ def check_nokogiri_html_parse_response_body(node)
87
+ return unless (const = nokogiri_html_parse_response_body(node))
88
+
89
+ add_offense(
90
+ node,
91
+ message: "Prefer `response.parsed_body` to `Nokogiri::#{const}.parse(response.body)`."
92
+ ) do |corrector|
93
+ autocorrect(corrector, node)
94
+ end
95
+ end
54
96
  end
55
97
  end
56
98
  end
@@ -17,7 +17,7 @@ module RuboCop
17
17
  # # good
18
18
  # def change
19
19
  # change_table :users do |t|
20
- # t.remove :name, :string
20
+ # t.remove :name, type: :string
21
21
  # end
22
22
  # end
23
23
  #
@@ -290,10 +290,10 @@ module RuboCop
290
290
  when :change
291
291
  false
292
292
  when :remove
293
- target_rails_version >= 6.1 && all_hash_key?(node.arguments.last, :type)
293
+ target_rails_version >= 6.1 && all_hash_key?(node.last_argument, :type)
294
294
  when :change_default, :change_column_default, :change_table_comment,
295
295
  :change_column_comment
296
- all_hash_key?(node.arguments.last, :from, :to)
296
+ all_hash_key?(node.last_argument, :from, :to)
297
297
  else
298
298
  true
299
299
  end
@@ -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)
@@ -196,6 +196,8 @@ module RuboCop
196
196
  end
197
197
 
198
198
  def call_to_persisted?(node)
199
+ node = node.parent.condition if node.parenthesized_call? && node.parent.if_type?
200
+
199
201
  node.send_type? && node.method?(:persisted?)
200
202
  end
201
203
 
@@ -235,10 +237,10 @@ module RuboCop
235
237
 
236
238
  def in_condition_or_compound_boolean?(node)
237
239
  node = node.block_node || node
238
- parent = node.parent
240
+ parent = node.each_ancestor.find { |ancestor| !ancestor.begin_type? }
239
241
  return false unless parent
240
242
 
241
- operator_or_single_negative?(parent) || (conditional?(parent) && node == parent.condition)
243
+ operator_or_single_negative?(parent) || (conditional?(parent) && node == deparenthesize(parent.condition))
242
244
  end
243
245
 
244
246
  def operator_or_single_negative?(node)
@@ -249,6 +251,11 @@ module RuboCop
249
251
  parent.if_type? || parent.case_type?
250
252
  end
251
253
 
254
+ def deparenthesize(node)
255
+ node = node.children.last while node.begin_type?
256
+ node
257
+ end
258
+
252
259
  def checked_immediately?(node)
253
260
  node.parent && call_to_persisted?(node.parent)
254
261
  end
@@ -298,7 +305,7 @@ module RuboCop
298
305
 
299
306
  node = assignable_node(node)
300
307
  method, sibling_index = find_method_with_sibling_index(node.parent)
301
- return unless method && (method.def_type? || method.block_type?)
308
+ return false unless method && (method.def_type? || method.block_type?)
302
309
 
303
310
  method.children.size == node.sibling_index + sibling_index
304
311
  end
@@ -331,10 +338,10 @@ module RuboCop
331
338
 
332
339
  # Check argument signature as no arguments or one hash
333
340
  def expected_signature?(node)
334
- !node.arguments? ||
335
- (node.arguments.one? &&
336
- node.method_name != :destroy &&
337
- (node.first_argument.hash_type? || !node.first_argument.literal?))
341
+ return true unless node.arguments?
342
+ return false if !node.arguments.one? || node.method?(:destroy)
343
+
344
+ node.first_argument.hash_type? || !node.first_argument.literal?
338
345
  end
339
346
  end
340
347
  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