rubocop-rails 2.20.2 → 2.25.0

Sign up to get free protection for your applications and to get access to all the features.
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