rubocop-rails 2.27.0 → 2.30.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +23 -6
  4. data/config/default.yml +22 -0
  5. data/lib/rubocop/cop/mixin/active_record_helper.rb +1 -1
  6. data/lib/rubocop/cop/mixin/index_method.rb +68 -61
  7. data/lib/rubocop/cop/mixin/routes_helper.rb +20 -0
  8. data/lib/rubocop/cop/rails/add_column_index.rb +1 -0
  9. data/lib/rubocop/cop/rails/belongs_to.rb +1 -1
  10. data/lib/rubocop/cop/rails/blank.rb +1 -1
  11. data/lib/rubocop/cop/rails/bulk_change_table.rb +1 -0
  12. data/lib/rubocop/cop/rails/content_tag.rb +1 -1
  13. data/lib/rubocop/cop/rails/dangerous_column_names.rb +2 -0
  14. data/lib/rubocop/cop/rails/delegate.rb +50 -7
  15. data/lib/rubocop/cop/rails/duplicate_association.rb +8 -4
  16. data/lib/rubocop/cop/rails/file_path.rb +61 -9
  17. data/lib/rubocop/cop/rails/http_positional_arguments.rb +7 -0
  18. data/lib/rubocop/cop/rails/index_by.rb +28 -12
  19. data/lib/rubocop/cop/rails/index_with.rb +28 -12
  20. data/lib/rubocop/cop/rails/inquiry.rb +1 -1
  21. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +11 -1
  22. data/lib/rubocop/cop/rails/match_route.rb +1 -9
  23. data/lib/rubocop/cop/rails/multiple_route_paths.rb +50 -0
  24. data/lib/rubocop/cop/rails/not_null_column.rb +6 -2
  25. data/lib/rubocop/cop/rails/pluck.rb +21 -1
  26. data/lib/rubocop/cop/rails/pluralization_grammar.rb +1 -1
  27. data/lib/rubocop/cop/rails/presence.rb +1 -1
  28. data/lib/rubocop/cop/rails/present.rb +1 -1
  29. data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +1 -1
  30. data/lib/rubocop/cop/rails/reversible_migration.rb +3 -1
  31. data/lib/rubocop/cop/rails/root_pathname_methods.rb +6 -1
  32. data/lib/rubocop/cop/rails/save_bang.rb +7 -6
  33. data/lib/rubocop/cop/rails/schema_comment.rb +1 -0
  34. data/lib/rubocop/cop/rails/select_map.rb +1 -1
  35. data/lib/rubocop/cop/rails/skips_model_validations.rb +1 -1
  36. data/lib/rubocop/cop/rails/strip_heredoc.rb +1 -1
  37. data/lib/rubocop/cop/rails/strong_parameters_expect.rb +104 -0
  38. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +2 -1
  39. data/lib/rubocop/cop/rails/time_zone.rb +10 -6
  40. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +10 -33
  41. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +1 -1
  42. data/lib/rubocop/cop/rails_cops.rb +3 -0
  43. data/lib/rubocop/rails/migration_file_skippable.rb +54 -0
  44. data/lib/rubocop/rails/plugin.rb +48 -0
  45. data/lib/rubocop/rails/version.rb +1 -1
  46. data/lib/rubocop/rails.rb +1 -8
  47. data/lib/rubocop-rails.rb +4 -4
  48. metadata +28 -9
  49. data/lib/rubocop/rails/inject.rb +0 -18
@@ -29,18 +29,28 @@ module RuboCop
29
29
  PATTERN
30
30
 
31
31
  def_node_matcher :on_bad_to_h, <<~PATTERN
32
- (block
33
- (call _ :to_h)
34
- (args (arg $_el))
35
- (array $_ (lvar _el)))
32
+ {
33
+ (block
34
+ (call _ :to_h)
35
+ (args (arg $_el))
36
+ (array $_ (lvar _el)))
37
+ (numblock
38
+ (call _ :to_h) $1
39
+ (array $_ (lvar :_1)))
40
+ }
36
41
  PATTERN
37
42
 
38
43
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
39
44
  (call
40
- (block
41
- (call _ {:map :collect})
42
- (args (arg $_el))
43
- (array $_ (lvar _el)))
45
+ {
46
+ (block
47
+ (call _ {:map :collect})
48
+ (args (arg $_el))
49
+ (array $_ (lvar _el)))
50
+ (numblock
51
+ (call _ {:map :collect}) $1
52
+ (array $_ (lvar :_1)))
53
+ }
44
54
  :to_h)
45
55
  PATTERN
46
56
 
@@ -48,10 +58,16 @@ module RuboCop
48
58
  (send
49
59
  (const {nil? cbase} :Hash)
50
60
  :[]
51
- (block
52
- (call _ {:map :collect})
53
- (args (arg $_el))
54
- (array $_ (lvar _el))))
61
+ {
62
+ (block
63
+ (call _ {:map :collect})
64
+ (args (arg $_el))
65
+ (array $_ (lvar _el)))
66
+ (numblock
67
+ (call _ {:map :collect}) $1
68
+ (array $_ (lvar :_1)))
69
+ }
70
+ )
55
71
  PATTERN
56
72
 
57
73
  private
@@ -32,18 +32,28 @@ module RuboCop
32
32
  PATTERN
33
33
 
34
34
  def_node_matcher :on_bad_to_h, <<~PATTERN
35
- (block
36
- (call _ :to_h)
37
- (args (arg $_el))
38
- (array (lvar _el) $_))
35
+ {
36
+ (block
37
+ (call _ :to_h)
38
+ (args (arg $_el))
39
+ (array (lvar _el) $_))
40
+ (numblock
41
+ (call _ :to_h) $1
42
+ (array (lvar :_1) $_))
43
+ }
39
44
  PATTERN
40
45
 
41
46
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
42
47
  (call
43
- (block
44
- (call _ {:map :collect})
45
- (args (arg $_el))
46
- (array (lvar _el) $_))
48
+ {
49
+ (block
50
+ (call _ {:map :collect})
51
+ (args (arg $_el))
52
+ (array (lvar _el) $_))
53
+ (numblock
54
+ (call _ {:map :collect}) $1
55
+ (array (lvar :_1) $_))
56
+ }
47
57
  :to_h)
48
58
  PATTERN
49
59
 
@@ -51,10 +61,16 @@ module RuboCop
51
61
  (send
52
62
  (const {nil? cbase} :Hash)
53
63
  :[]
54
- (block
55
- (call _ {:map :collect})
56
- (args (arg $_el))
57
- (array (lvar _el) $_)))
64
+ {
65
+ (block
66
+ (call _ {:map :collect})
67
+ (args (arg $_el))
68
+ (array (lvar _el) $_))
69
+ (numblock
70
+ (call _ {:map :collect}) $1
71
+ (array (lvar :_1) $_))
72
+ }
73
+ )
58
74
  PATTERN
59
75
 
60
76
  private
@@ -29,7 +29,7 @@ module RuboCop
29
29
  def on_send(node)
30
30
  return unless node.arguments.empty?
31
31
  return unless (receiver = node.receiver)
32
- return if !receiver.str_type? && !receiver.array_type?
32
+ return unless receiver.type?(:str, :array)
33
33
 
34
34
  add_offense(node.loc.selector)
35
35
  end
@@ -115,6 +115,10 @@ module RuboCop
115
115
  $_)))
116
116
  PATTERN
117
117
 
118
+ def_node_matcher :delegated_methods, <<~PATTERN
119
+ (send nil? :delegate (sym $_)+ (hash <(pair (sym :to) _) ...>))
120
+ PATTERN
121
+
118
122
  def on_send(node)
119
123
  methods_node = only_or_except_filter_methods(node)
120
124
  return unless methods_node
@@ -139,7 +143,13 @@ module RuboCop
139
143
  return [] unless block
140
144
 
141
145
  defined_methods = block.each_child_node(:def).map(&:method_name)
142
- defined_methods + aliased_action_methods(block, defined_methods)
146
+ defined_methods + delegated_action_methods(block) + aliased_action_methods(block, defined_methods)
147
+ end
148
+
149
+ def delegated_action_methods(node)
150
+ node.each_child_node(:send).flat_map do |child_node|
151
+ delegated_methods(child_node) || []
152
+ end
143
153
  end
144
154
 
145
155
  def aliased_action_methods(node, defined_methods)
@@ -21,11 +21,11 @@ module RuboCop
21
21
  # match 'photos/:id', to: 'photos#show', via: :all
22
22
  #
23
23
  class MatchRoute < Base
24
+ include RoutesHelper
24
25
  extend AutoCorrector
25
26
 
26
27
  MSG = 'Use `%<http_method>s` instead of `match` to define a route.'
27
28
  RESTRICT_ON_SEND = %i[match].freeze
28
- HTTP_METHODS = %i[get post put patch delete].freeze
29
29
 
30
30
  def_node_matcher :match_method_call?, <<~PATTERN
31
31
  (send nil? :match $_ $(hash ...) ?)
@@ -60,14 +60,6 @@ module RuboCop
60
60
  end
61
61
  end
62
62
 
63
- def_node_matcher :routes_draw?, <<~PATTERN
64
- (send (send _ :routes) :draw)
65
- PATTERN
66
-
67
- def within_routes?(node)
68
- node.each_ancestor(:block).any? { |a| routes_draw?(a.send_node) }
69
- end
70
-
71
63
  def extract_via(node)
72
64
  via_pair = via_pair(node)
73
65
  return %i[get] unless via_pair
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Checks for mapping a route with multiple paths, which is deprecated and will be removed in Rails 8.1.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # get '/users', '/other_path', to: 'users#index'
12
+ #
13
+ # # good
14
+ # get '/users', to: 'users#index'
15
+ # get '/other_path', to: 'users#index'
16
+ #
17
+ class MultipleRoutePaths < Base
18
+ include RoutesHelper
19
+ extend AutoCorrector
20
+
21
+ MSG = 'Use separate routes instead of combining multiple route paths in a single route.'
22
+ RESTRICT_ON_SEND = HTTP_METHODS
23
+
24
+ IGNORED_ARGUMENT_TYPES = %i[array hash].freeze
25
+
26
+ def on_send(node)
27
+ return unless within_routes?(node)
28
+
29
+ route_paths = node.arguments.reject { |argument| IGNORED_ARGUMENT_TYPES.include?(argument.type) }
30
+ return if route_paths.count < 2
31
+
32
+ add_offense(node) do |corrector|
33
+ corrector.replace(node, migrate_to_multiple_routes(node, route_paths))
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def migrate_to_multiple_routes(node, route_paths)
40
+ rest = route_paths.last.source_range.end.join(node.source_range.end).source
41
+ indentation = ' ' * node.source_range.column
42
+
43
+ route_paths.map do |route_path|
44
+ "#{node.method_name} #{route_path.source}#{rest}"
45
+ end.join("\n#{indentation}")
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -41,10 +41,14 @@ module RuboCop
41
41
  # change_column_null :products, :category_id, false
42
42
  class NotNullColumn < Base
43
43
  include DatabaseTypeResolvable
44
+ include MigrationsHelper
44
45
 
45
46
  MSG = 'Do not add a NOT NULL column without a default value.'
46
47
  RESTRICT_ON_SEND = %i[add_column add_reference].freeze
47
48
 
49
+ VIRTUAL_TYPE_VALUES = [:virtual, 'virtual'].freeze
50
+ TEXT_TYPE_VALUES = [:text, 'text'].freeze
51
+
48
52
  def_node_matcher :add_not_null_column?, <<~PATTERN
49
53
  (send nil? :add_column _ _ $_ (hash $...))
50
54
  PATTERN
@@ -91,8 +95,8 @@ module RuboCop
91
95
 
92
96
  def check_column(type, pairs)
93
97
  if type.respond_to?(:value)
94
- return if type.value == :virtual || type.value == 'virtual'
95
- return if (type.value == :text || type.value == 'text') && database == MYSQL
98
+ return if VIRTUAL_TYPE_VALUES.include?(type.value)
99
+ return if TEXT_TYPE_VALUES.include?(type.value) && database == MYSQL
96
100
  end
97
101
 
98
102
  check_pairs(pairs)
@@ -9,6 +9,24 @@ module RuboCop
9
9
  # element in an enumerable. When called on an Active Record relation, it
10
10
  # results in a more efficient query that only selects the necessary key.
11
11
  #
12
+ # NOTE: If the receiver's relation is not loaded and `pluck` is used inside an iteration,
13
+ # it may result in N+1 queries because `pluck` queries the database on each iteration.
14
+ # This cop ignores offenses for `map/collect` when they are suspected to be part of an iteration
15
+ # to prevent such potential issues.
16
+ #
17
+ # [source,ruby]
18
+ # ----
19
+ # users = User.all
20
+ # 5.times do
21
+ # users.map { |user| user[:foo] } # Only one query is executed
22
+ # end
23
+ #
24
+ # users = User.all
25
+ # 5.times do
26
+ # users.pluck(:id) # A query is executed on every iteration
27
+ # end
28
+ # ----
29
+ #
12
30
  # @safety
13
31
  # This cop is unsafe because model can use column aliases.
14
32
  #
@@ -38,10 +56,12 @@ module RuboCop
38
56
  minimum_target_rails_version 5.0
39
57
 
40
58
  def_node_matcher :pluck_candidate?, <<~PATTERN
41
- ({block numblock} (call _ {:map :collect}) $_argument (send lvar :[] $_key))
59
+ (any_block (call _ {:map :collect}) $_argument (send lvar :[] $_key))
42
60
  PATTERN
43
61
 
44
62
  def on_block(node)
63
+ return if node.each_ancestor(:block, :numblock).any?
64
+
45
65
  pluck_candidate?(node) do |argument, key|
46
66
  next if key.regexp_type? || !use_one_block_argument?(argument)
47
67
 
@@ -96,7 +96,7 @@ module RuboCop
96
96
  end
97
97
 
98
98
  def literal_number?(node)
99
- node && (node.int_type? || node.float_type?)
99
+ node&.type?(:int, :float)
100
100
  end
101
101
 
102
102
  def pluralize(method_name)
@@ -102,7 +102,7 @@ module RuboCop
102
102
  end
103
103
 
104
104
  def ignore_other_node?(node)
105
- node && (node.if_type? || node.rescue_type? || node.while_type?)
105
+ node&.type?(:if, :rescue, :while)
106
106
  end
107
107
 
108
108
  def message(node, receiver, other)
@@ -110,7 +110,7 @@ module RuboCop
110
110
  def on_if(node)
111
111
  return unless cop_config['UnlessBlank']
112
112
  return unless node.unless?
113
- return if node.else? && config.for_cop('Style/UnlessElse')['Enabled']
113
+ return if node.else? && config.cop_enabled?('Style/UnlessElse')
114
114
 
115
115
  unless_blank?(node) do |method_call, receiver|
116
116
  range = unless_condition(node, method_call)
@@ -174,7 +174,7 @@ module RuboCop
174
174
  parent = node.parent
175
175
  return false unless POSSIBLE_ENUMERABLE_BLOCK_METHODS.include?(parent.method_name)
176
176
 
177
- parent.parent&.block_type? || parent.parent&.numblock_type? || parent.first_argument&.block_pass_type?
177
+ parent.block_literal? || parent.first_argument&.block_pass_type?
178
178
  end
179
179
 
180
180
  def sensitive_association_method?(node)
@@ -215,8 +215,10 @@ module RuboCop
215
215
  end
216
216
 
217
217
  def check_drop_table_node(node)
218
+ return unless (last_argument = node.last_argument)
219
+
218
220
  drop_table_call(node) do
219
- unless node.parent.block_type? || node.last_argument.block_pass_type?
221
+ unless node.parent.block_type? || last_argument.block_pass_type?
220
222
  add_offense(node, message: format(MSG, action: 'drop_table(without block)'))
221
223
  end
222
224
  end
@@ -237,7 +237,12 @@ module RuboCop
237
237
  end
238
238
 
239
239
  replacement = "#{path_replacement}.#{method}"
240
- replacement += "(#{args.map(&:source).join(', ')})" unless args.empty?
240
+
241
+ if args.any?
242
+ formatted_args = args.map { |arg| arg.array_type? ? "*#{arg.source}" : arg.source }
243
+ replacement += "(#{formatted_args.join(', ')})"
244
+ end
245
+
241
246
  replacement
242
247
  end
243
248
 
@@ -244,11 +244,11 @@ module RuboCop
244
244
  end
245
245
 
246
246
  def operator_or_single_negative?(node)
247
- node.or_type? || node.and_type? || single_negative?(node)
247
+ node.operator_keyword? || single_negative?(node)
248
248
  end
249
249
 
250
250
  def conditional?(parent)
251
- parent.if_type? || parent.case_type?
251
+ parent.type?(:if, :case)
252
252
  end
253
253
 
254
254
  def deparenthesize(node)
@@ -305,7 +305,7 @@ module RuboCop
305
305
 
306
306
  node = assignable_node(node)
307
307
  method, sibling_index = find_method_with_sibling_index(node.parent)
308
- return false unless method && (method.def_type? || method.block_type?)
308
+ return false unless method&.type?(:def, :block)
309
309
 
310
310
  method.children.size == node.sibling_index + sibling_index
311
311
  end
@@ -324,12 +324,13 @@ module RuboCop
324
324
 
325
325
  def explicit_return?(node)
326
326
  ret = assignable_node(node).parent
327
- ret && (ret.return_type? || ret.next_type?)
327
+ ret&.type?(:return, :next)
328
328
  end
329
329
 
330
330
  def return_value_assigned?(node)
331
- assignment = assignable_node(node).parent
332
- assignment&.lvasgn_type?
331
+ return false unless (assignment = assignable_node(node).parent)
332
+
333
+ assignment.assignment?
333
334
  end
334
335
 
335
336
  def persist_method?(node, methods = RESTRICT_ON_SEND)
@@ -23,6 +23,7 @@ module RuboCop
23
23
  #
24
24
  class SchemaComment < Base
25
25
  include ActiveRecordMigrationsHelper
26
+ include MigrationsHelper
26
27
 
27
28
  COLUMN_MSG = 'New database column without `comment`.'
28
29
  TABLE_MSG = 'New database table without `comment`.'
@@ -54,7 +54,7 @@ module RuboCop
54
54
 
55
55
  # rubocop:disable Metrics/AbcSize
56
56
  def autocorrect(corrector, select_node, node, preferred_method)
57
- corrector.remove(select_node.loc.dot || node.loc.dot)
57
+ corrector.remove(select_node.parent.loc.dot)
58
58
  corrector.remove(select_node.loc.selector.begin.join(select_node.source_range.end))
59
59
  corrector.replace(node.loc.selector.begin.join(node.source_range.end), preferred_method)
60
60
  end
@@ -61,7 +61,7 @@ module RuboCop
61
61
  def_node_matcher :good_touch?, <<~PATTERN
62
62
  {
63
63
  (send (const {nil? cbase} :FileUtils) :touch ...)
64
- (send _ :touch {true false})
64
+ (send _ :touch boolean)
65
65
  }
66
66
  PATTERN
67
67
 
@@ -33,7 +33,7 @@ module RuboCop
33
33
 
34
34
  def on_send(node)
35
35
  return unless (receiver = node.receiver)
36
- return unless receiver.str_type? || receiver.dstr_type?
36
+ return unless receiver.type?(:str, :dstr)
37
37
  return unless receiver.respond_to?(:heredoc?) && receiver.heredoc?
38
38
 
39
39
  register_offense(node, receiver)
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Enforces the use of `ActionController::Parameters#expect` as a method for strong parameter handling.
7
+ #
8
+ # @safety
9
+ # This cop's autocorrection is considered unsafe because there are cases where the HTTP status may change
10
+ # from 500 to 400 when handling invalid parameters. This change, however, reflects an intentional
11
+ # incompatibility introduced for valid reasons by the `expect` method, which aligns better with
12
+ # strong parameter conventions.
13
+ #
14
+ # @example
15
+ #
16
+ # # bad
17
+ # params.require(:user).permit(:name, :age)
18
+ # params.permit(user: [:name, :age]).require(:user)
19
+ #
20
+ # # good
21
+ # params.expect(user: [:name, :age])
22
+ #
23
+ class StrongParametersExpect < Base
24
+ extend AutoCorrector
25
+ extend TargetRailsVersion
26
+
27
+ MSG = 'Use `%<prefer>s` instead.'
28
+ RESTRICT_ON_SEND = %i[require permit].freeze
29
+
30
+ minimum_target_rails_version 8.0
31
+
32
+ def_node_matcher :params_require_permit, <<~PATTERN
33
+ $(call
34
+ $(call
35
+ (send nil? :params) :require _) :permit _+)
36
+ PATTERN
37
+
38
+ def_node_matcher :params_permit_require, <<~PATTERN
39
+ $(call
40
+ $(call
41
+ (send nil? :params) :permit (hash (pair _require_param_name _ )))
42
+ :require _require_param_name)
43
+ PATTERN
44
+
45
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
46
+ def on_send(node)
47
+ return if part_of_ignored_node?(node)
48
+
49
+ if (permit_method, require_method = params_require_permit(node))
50
+ range = offense_range(require_method, node)
51
+ prefer = expect_method(require_method, permit_method)
52
+ replace_argument = true
53
+ elsif (require_method, permit_method = params_permit_require(node))
54
+ range = offense_range(permit_method, node)
55
+ prefer = "expect(#{permit_method.arguments.map(&:source).join(', ')})"
56
+ replace_argument = false
57
+ else
58
+ return
59
+ end
60
+
61
+ add_offense(range, message: format(MSG, prefer: prefer)) do |corrector|
62
+ corrector.remove(require_method.receiver.source_range.end.join(require_method.source_range.end))
63
+ corrector.replace(permit_method.loc.selector, 'expect')
64
+ if replace_argument
65
+ corrector.insert_before(permit_method.first_argument, "#{require_key(require_method)}[")
66
+ corrector.insert_after(permit_method.last_argument, ']')
67
+ end
68
+ end
69
+
70
+ ignore_node(node)
71
+ end
72
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
73
+ alias on_csend on_send
74
+
75
+ private
76
+
77
+ def offense_range(method_node, node)
78
+ method_node.loc.selector.join(node.source_range.end)
79
+ end
80
+
81
+ def expect_method(require_method, permit_method)
82
+ require_key = require_key(require_method)
83
+ permit_args = permit_method.arguments.map(&:source).join(', ')
84
+
85
+ arguments = "#{require_key}[#{permit_args}]"
86
+
87
+ "expect(#{arguments})"
88
+ end
89
+
90
+ def require_key(require_method)
91
+ if (first_argument = require_method.first_argument).respond_to?(:value)
92
+ require_arg = first_argument.value
93
+ separator = ': '
94
+ else
95
+ require_arg = first_argument.source
96
+ separator = ' => '
97
+ end
98
+
99
+ "#{require_arg}#{separator}"
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -18,8 +18,9 @@ module RuboCop
18
18
  # t.boolean :active, default: true, null: false
19
19
  #
20
20
  class ThreeStateBooleanColumn < Base
21
- MSG = 'Boolean columns should always have a default value and a `NOT NULL` constraint.'
21
+ include MigrationsHelper
22
22
 
23
+ MSG = 'Boolean columns should always have a default value and a `NOT NULL` constraint.'
23
24
  RESTRICT_ON_SEND = %i[add_column column boolean].freeze
24
25
 
25
26
  def_node_matcher :three_state_boolean?, <<~PATTERN
@@ -97,11 +97,9 @@ module RuboCop
97
97
  end
98
98
 
99
99
  def autocorrect_time_new(node, corrector)
100
- if node.arguments?
101
- corrector.replace(node.loc.selector, 'local')
102
- else
103
- corrector.replace(node.loc.selector, 'now')
104
- end
100
+ replacement = replacement(node)
101
+
102
+ corrector.replace(node.loc.selector, replacement)
105
103
  end
106
104
 
107
105
  # remove redundant `.in_time_zone` from `Time.zone.now.in_time_zone`
@@ -183,7 +181,7 @@ module RuboCop
183
181
 
184
182
  def safe_method(method_name, node)
185
183
  if %w[new current].include?(method_name)
186
- node.arguments? ? 'local' : 'now'
184
+ replacement(node)
187
185
  else
188
186
  method_name
189
187
  end
@@ -259,6 +257,12 @@ module RuboCop
259
257
  pair.key.sym_type? && pair.key.value == :in && !pair.value.nil_type?
260
258
  end
261
259
  end
260
+
261
+ def replacement(node)
262
+ return 'now' unless node.arguments?
263
+
264
+ node.first_argument.str_type? ? 'parse' : 'local'
265
+ end
262
266
  end
263
267
  end
264
268
  end
@@ -51,51 +51,28 @@ module RuboCop
51
51
 
52
52
  MSG = 'Use `distinct` before `pluck`.'
53
53
  RESTRICT_ON_SEND = %i[uniq].freeze
54
- NEWLINE = "\n"
55
- PATTERN = '[!^block (send (send %<type>s :pluck ...) :uniq ...)]'
56
54
 
57
- def_node_matcher :conservative_node_match, format(PATTERN, type: 'const')
58
-
59
- def_node_matcher :aggressive_node_match, format(PATTERN, type: '_')
55
+ def_node_matcher :uniq_before_pluck, '[!^block $(send $(send _ :pluck ...) :uniq ...)]'
60
56
 
61
57
  def on_send(node)
62
- uniq = if style == :conservative
63
- conservative_node_match(node)
64
- else
65
- aggressive_node_match(node)
66
- end
67
-
68
- return unless uniq
58
+ uniq_before_pluck(node) do |uniq_node, pluck_node|
59
+ next if style == :conservative && !pluck_node.receiver&.const_type?
69
60
 
70
- add_offense(node.loc.selector) do |corrector|
71
- autocorrect(corrector, node)
61
+ add_offense(uniq_node.loc.selector) do |corrector|
62
+ autocorrect(corrector, uniq_node, pluck_node)
63
+ end
72
64
  end
73
65
  end
74
66
 
75
67
  private
76
68
 
77
- def autocorrect(corrector, node)
78
- method = node.method_name
69
+ def autocorrect(corrector, uniq_node, pluck_node)
70
+ corrector.remove(range_between(pluck_node.loc.end.end_pos, uniq_node.loc.selector.end_pos))
79
71
 
80
- corrector.remove(dot_method_with_whitespace(method, node))
81
- if (dot = node.receiver.loc.dot)
72
+ if (dot = pluck_node.loc.dot)
82
73
  corrector.insert_before(dot.begin, '.distinct')
83
74
  else
84
- corrector.insert_before(node.receiver, 'distinct.')
85
- end
86
- end
87
-
88
- def dot_method_with_whitespace(method, node)
89
- range_between(dot_method_begin_pos(method, node), node.loc.selector.end_pos)
90
- end
91
-
92
- def dot_method_begin_pos(method, node)
93
- lines = node.source.split(NEWLINE)
94
-
95
- if lines.last.strip == ".#{method}"
96
- node.source.rindex(NEWLINE)
97
- else
98
- node.loc.dot.begin_pos
75
+ corrector.insert_before(pluck_node, 'distinct.')
99
76
  end
100
77
  end
101
78
  end