rubocop 1.66.0 → 1.67.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +15 -6
  4. data/config/internal_affairs.yml +11 -0
  5. data/lib/rubocop/cli/command/auto_generate_config.rb +6 -7
  6. data/lib/rubocop/cli/command/lsp.rb +2 -2
  7. data/lib/rubocop/comment_config.rb +4 -8
  8. data/lib/rubocop/config.rb +4 -16
  9. data/lib/rubocop/config_loader.rb +14 -8
  10. data/lib/rubocop/config_loader_resolver.rb +3 -3
  11. data/lib/rubocop/config_validator.rb +7 -10
  12. data/lib/rubocop/cop/base.rb +6 -2
  13. data/lib/rubocop/cop/bundler/gem_version.rb +1 -0
  14. data/lib/rubocop/cop/cop.rb +8 -0
  15. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +1 -1
  16. data/lib/rubocop/cop/internal_affairs/cop_description.rb +0 -4
  17. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -1
  18. data/lib/rubocop/cop/internal_affairs/redundant_message_argument.rb +6 -21
  19. data/lib/rubocop/cop/internal_affairs/redundant_source_range.rb +8 -1
  20. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +0 -5
  21. data/lib/rubocop/cop/internal_affairs.rb +16 -0
  22. data/lib/rubocop/cop/layout/access_modifier_indentation.rb +5 -1
  23. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  24. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
  25. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +8 -0
  26. data/lib/rubocop/cop/layout/indentation_width.rb +4 -5
  27. data/lib/rubocop/cop/layout/leading_comment_space.rb +28 -1
  28. data/lib/rubocop/cop/lint/ambiguous_range.rb +4 -1
  29. data/lib/rubocop/cop/lint/big_decimal_new.rb +4 -7
  30. data/lib/rubocop/cop/lint/boolean_symbol.rb +1 -1
  31. data/lib/rubocop/cop/lint/duplicate_set_element.rb +74 -0
  32. data/lib/rubocop/cop/lint/ensure_return.rb +0 -3
  33. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
  34. data/lib/rubocop/cop/lint/float_comparison.rb +1 -1
  35. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +10 -4
  36. data/lib/rubocop/cop/lint/it_without_arguments_in_block.rb +5 -14
  37. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +25 -2
  38. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +5 -6
  39. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -1
  40. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +105 -41
  41. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
  42. data/lib/rubocop/cop/lint/uri_regexp.rb +25 -7
  43. data/lib/rubocop/cop/mixin/percent_array.rb +1 -1
  44. data/lib/rubocop/cop/mixin/statement_modifier.rb +3 -2
  45. data/lib/rubocop/cop/naming/inclusive_language.rb +12 -3
  46. data/lib/rubocop/cop/naming/predicate_name.rb +1 -1
  47. data/lib/rubocop/cop/offense.rb +2 -2
  48. data/lib/rubocop/cop/style/access_modifier_declarations.rb +12 -2
  49. data/lib/rubocop/cop/style/accessor_grouping.rb +10 -2
  50. data/lib/rubocop/cop/style/arguments_forwarding.rb +46 -6
  51. data/lib/rubocop/cop/style/block_delimiters.rb +14 -1
  52. data/lib/rubocop/cop/style/collection_compact.rb +10 -10
  53. data/lib/rubocop/cop/style/combinable_loops.rb +7 -0
  54. data/lib/rubocop/cop/style/commented_keyword.rb +7 -1
  55. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -1
  56. data/lib/rubocop/cop/style/data_inheritance.rb +1 -1
  57. data/lib/rubocop/cop/style/empty_else.rb +1 -0
  58. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  59. data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
  60. data/lib/rubocop/cop/style/guard_clause.rb +1 -1
  61. data/lib/rubocop/cop/style/hash_each_methods.rb +6 -0
  62. data/lib/rubocop/cop/style/hash_syntax.rb +2 -2
  63. data/lib/rubocop/cop/style/if_inside_else.rb +1 -1
  64. data/lib/rubocop/cop/style/if_with_semicolon.rb +16 -5
  65. data/lib/rubocop/cop/style/lambda.rb +1 -1
  66. data/lib/rubocop/cop/style/magic_comment_format.rb +3 -8
  67. data/lib/rubocop/cop/style/map_into_array.rb +54 -10
  68. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +12 -7
  69. data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
  70. data/lib/rubocop/cop/style/nested_modifier.rb +1 -1
  71. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +1 -1
  72. data/lib/rubocop/cop/style/one_line_conditional.rb +4 -0
  73. data/lib/rubocop/cop/style/operator_method_call.rb +25 -6
  74. data/lib/rubocop/cop/style/redundant_begin.rb +4 -0
  75. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  76. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +1 -1
  77. data/lib/rubocop/cop/style/redundant_line_continuation.rb +3 -3
  78. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
  79. data/lib/rubocop/cop/style/require_order.rb +1 -1
  80. data/lib/rubocop/cop/style/rescue_modifier.rb +13 -1
  81. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +54 -12
  82. data/lib/rubocop/cop/style/safe_navigation.rb +92 -50
  83. data/lib/rubocop/cop/style/select_by_regexp.rb +9 -6
  84. data/lib/rubocop/cop/style/semicolon.rb +1 -1
  85. data/lib/rubocop/cop/style/struct_inheritance.rb +1 -1
  86. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  87. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  88. data/lib/rubocop/cop/team.rb +8 -1
  89. data/lib/rubocop/cop/util.rb +1 -1
  90. data/lib/rubocop/cops_documentation_generator.rb +73 -34
  91. data/lib/rubocop/file_finder.rb +9 -4
  92. data/lib/rubocop/lsp/runtime.rb +2 -0
  93. data/lib/rubocop/lsp/server.rb +0 -1
  94. data/lib/rubocop/rspec/expect_offense.rb +1 -0
  95. data/lib/rubocop/runner.rb +3 -0
  96. data/lib/rubocop/server/cache.rb +6 -1
  97. data/lib/rubocop/server/core.rb +1 -0
  98. data/lib/rubocop/target_ruby.rb +12 -12
  99. data/lib/rubocop/version.rb +3 -1
  100. data/lib/rubocop/yaml_duplication_checker.rb +20 -26
  101. data/lib/rubocop.rb +2 -0
  102. metadata +12 -10
@@ -57,6 +57,7 @@ module RuboCop
57
57
  # # good
58
58
  # (a.foo)..(b.bar)
59
59
  class AmbiguousRange < Base
60
+ include RationalLiteral
60
61
  extend AutoCorrector
61
62
 
62
63
  MSG = 'Wrap complex range boundaries with parentheses to avoid ambiguity.'
@@ -79,12 +80,14 @@ module RuboCop
79
80
  yield range.end if range.end
80
81
  end
81
82
 
83
+ # rubocop:disable Metrics/CyclomaticComplexity
82
84
  def acceptable?(node)
83
85
  node.begin_type? ||
84
- node.literal? ||
86
+ node.literal? || rational_literal?(node) ||
85
87
  node.variable? || node.const_type? || node.self_type? ||
86
88
  (node.call_type? && acceptable_call?(node))
87
89
  end
90
+ # rubocop:enable Metrics/CyclomaticComplexity
88
91
 
89
92
  def acceptable_call?(node)
90
93
  return true if node.unary_operation?
@@ -17,8 +17,7 @@ module RuboCop
17
17
  class BigDecimalNew < Base
18
18
  extend AutoCorrector
19
19
 
20
- MSG = '`%<double_colon>sBigDecimal.new()` is deprecated. ' \
21
- 'Use `%<double_colon>sBigDecimal()` instead.'
20
+ MSG = '`BigDecimal.new()` is deprecated. Use `BigDecimal()` instead.'
22
21
  RESTRICT_ON_SEND = %i[new].freeze
23
22
 
24
23
  # @!method big_decimal_new(node)
@@ -28,13 +27,11 @@ module RuboCop
28
27
  PATTERN
29
28
 
30
29
  def on_send(node)
31
- big_decimal_new(node) do |captured_value|
32
- double_colon = captured_value ? '::' : ''
33
- message = format(MSG, double_colon: double_colon)
34
-
35
- add_offense(node.loc.selector, message: message) do |corrector|
30
+ big_decimal_new(node) do |cbase|
31
+ add_offense(node.loc.selector) do |corrector|
36
32
  corrector.remove(node.loc.selector)
37
33
  corrector.remove(node.loc.dot)
34
+ corrector.remove(cbase) if cbase
38
35
  end
39
36
  end
40
37
  end
@@ -36,7 +36,7 @@ module RuboCop
36
36
  return unless boolean_symbol?(node)
37
37
 
38
38
  parent = node.parent
39
- return if parent&.array_type? && parent&.percent_literal?(:symbol)
39
+ return if parent&.array_type? && parent.percent_literal?(:symbol)
40
40
 
41
41
  add_offense(node, message: format(MSG, boolean: node.value)) do |corrector|
42
42
  autocorrect(corrector, node)
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for duplicate literal, constant, or variable elements in Set.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # Set[:foo, :bar, :foo]
12
+ #
13
+ # # good
14
+ # Set[:foo, :bar]
15
+ #
16
+ # # bad
17
+ # Set.new([:foo, :bar, :foo])
18
+ #
19
+ # # good
20
+ # Set.new([:foo, :bar])
21
+ #
22
+ # # bad
23
+ # [:foo, :bar, :foo].to_set
24
+ #
25
+ # # good
26
+ # [:foo, :bar].to_set
27
+ #
28
+ class DuplicateSetElement < Base
29
+ extend AutoCorrector
30
+
31
+ MSG = 'Remove the duplicate element in Set.'
32
+ RESTRICT_ON_SEND = %i[\[\] new to_set].freeze
33
+
34
+ # @!method set_init_elements(node)
35
+ def_node_matcher :set_init_elements, <<~PATTERN
36
+ {
37
+ (send (const {nil? cbase} :Set) :[] $...)
38
+ (send (const {nil? cbase} :Set) :new (array $...))
39
+ (call (array $...) :to_set)
40
+ }
41
+ PATTERN
42
+
43
+ def on_send(node)
44
+ return unless (set_elements = set_init_elements(node))
45
+
46
+ seen_elements = Set[]
47
+
48
+ set_elements.each_with_index do |set_element, index|
49
+ # NOTE: Skip due to the possibility of corner cases where Set elements
50
+ # may have changing return values if they are not literals, constants, or variables.
51
+ next if !set_element.literal? && !set_element.const_type? && !set_element.variable?
52
+
53
+ if seen_elements.include?(set_element)
54
+ register_offense(set_element, set_elements[index - 1])
55
+ else
56
+ seen_elements << set_element
57
+ end
58
+ end
59
+ end
60
+ alias on_csend on_send
61
+
62
+ private
63
+
64
+ def register_offense(current_element, prev_element)
65
+ add_offense(current_element) do |corrector|
66
+ range = prev_element.source_range.end.join(current_element.source_range.end)
67
+
68
+ corrector.remove(range)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -40,9 +40,6 @@ module RuboCop
40
40
  # cleanup
41
41
  # end
42
42
  class EnsureReturn < Base
43
- extend AutoCorrector
44
- include RangeHelp
45
-
46
43
  MSG = 'Do not return from an `ensure` block.'
47
44
 
48
45
  def on_ensure(node)
@@ -91,7 +91,7 @@ module RuboCop
91
91
  next if !argument || argument.hash_type?
92
92
 
93
93
  add_offense(
94
- argument.source_range, message: message(i, argument.source)
94
+ argument, message: message(i, argument.source)
95
95
  ) do |corrector|
96
96
  autocorrect(corrector, node)
97
97
  end
@@ -18,7 +18,7 @@ module RuboCop
18
18
  # # good - using BigDecimal
19
19
  # x.to_d == 0.1.to_d
20
20
  #
21
- # # good - comparing against zero
21
+ # # good - comparing against zero
22
22
  # x == 0.0
23
23
  # x != 0.0
24
24
  #
@@ -28,7 +28,7 @@ module RuboCop
28
28
  FOR_METHOD = ' Or, if they were intended to be separate method ' \
29
29
  'arguments, separate them with a comma.'
30
30
 
31
- # rubocop:disable Metrics/AbcSize
31
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
32
32
  def on_dstr(node)
33
33
  each_bad_cons(node) do |lhs_node, rhs_node|
34
34
  range = lhs_node.source_range.join(rhs_node.source_range)
@@ -40,13 +40,19 @@ module RuboCop
40
40
  end
41
41
 
42
42
  add_offense(range, message: message) do |corrector|
43
- range = lhs_node.source_range.end.join(rhs_node.source_range.begin)
43
+ if lhs_node.value == ''
44
+ corrector.remove(lhs_node)
45
+ elsif rhs_node.value == ''
46
+ corrector.remove(rhs_node)
47
+ else
48
+ range = lhs_node.source_range.end.join(rhs_node.source_range.begin)
44
49
 
45
- corrector.replace(range, ' + ')
50
+ corrector.replace(range, ' + ')
51
+ end
46
52
  end
47
53
  end
48
54
  end
49
- # rubocop:enable Metrics/AbcSize
55
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
50
56
 
51
57
  private
52
58
 
@@ -29,25 +29,16 @@ module RuboCop
29
29
 
30
30
  MSG = '`it` calls without arguments will refer to the first block param in Ruby 3.4; ' \
31
31
  'use `it()` or `self.it`.'
32
+ RESTRICT_ON_SEND = %i[it].freeze
32
33
 
33
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
34
- return unless (body = node.body)
35
- return unless node.arguments.empty_and_without_delimiters?
34
+ def on_send(node)
35
+ return unless (block_node = node.each_ancestor(:block).first)
36
+ return unless block_node.arguments.empty_and_without_delimiters?
36
37
 
37
- if body.send_type? && deprecated_it_method?(body)
38
- add_offense(body)
39
- else
40
- body.each_descendant(:send).each do |send_node|
41
- next unless deprecated_it_method?(send_node)
42
-
43
- add_offense(send_node)
44
- end
45
- end
38
+ add_offense(node) if deprecated_it_method?(node)
46
39
  end
47
40
 
48
41
  def deprecated_it_method?(node)
49
- return false unless node.method?(:it)
50
-
51
42
  !node.receiver && node.arguments.empty? && !node.parenthesized? && !node.block_literal?
52
43
  end
53
44
  end
@@ -30,6 +30,8 @@ module RuboCop
30
30
  # interpolation should not be removed if the expanded value
31
31
  # contains a space character.
32
32
  expanded_value = autocorrected_value(final_node)
33
+ expanded_value = handle_special_regexp_chars(begin_node, expanded_value)
34
+
33
35
  return if in_array_percent_literal?(begin_node) && /\s|\A\z/.match?(expanded_value)
34
36
 
35
37
  add_offense(final_node) do |corrector|
@@ -77,6 +79,27 @@ module RuboCop
77
79
  end
78
80
  # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
79
81
 
82
+ def handle_special_regexp_chars(begin_node, value)
83
+ parent_node = begin_node.parent
84
+
85
+ return value unless parent_node.regexp_type? && parent_node.slash_literal? && value['/']
86
+
87
+ # When a literal string containing a forward slash preceded by backslashes
88
+ # is interpolated inside a regexp, the number of resultant backslashes in the
89
+ # compiled Regexp is `(2(n+1) / 4)+1`, where `n` is the number of backslashes
90
+ # inside the interpolation.
91
+ # ie. 0-2 backslashes is compiled to 1, 3-6 is compiled to 3, etc.
92
+ # This maintains that same behavior in order to ensure the Regexp behavior
93
+ # does not change upon removing the interpolation.
94
+ value.gsub(%r{(\\*)/}) do
95
+ backslashes = Regexp.last_match[1]
96
+ backslash_count = backslashes.length
97
+ needed_backslashes = (2 * ((backslash_count + 1) / 4)) + 1
98
+
99
+ "#{'\\' * needed_backslashes}/"
100
+ end
101
+ end
102
+
80
103
  def autocorrected_value_for_string(node)
81
104
  if node.source.start_with?("'", '%q')
82
105
  node.children.last.inspect[1..-2]
@@ -150,7 +173,7 @@ module RuboCop
150
173
 
151
174
  def ends_heredoc_line?(node)
152
175
  grandparent = node.parent.parent
153
- return false unless grandparent&.dstr_type? && grandparent&.heredoc?
176
+ return false unless grandparent&.dstr_type? && grandparent.heredoc?
154
177
 
155
178
  line = processed_source.lines[node.last_line - 1]
156
179
  line.size == node.loc.last_column + 1
@@ -161,7 +184,7 @@ module RuboCop
161
184
  return false unless parent.dstr_type? || parent.dsym_type?
162
185
 
163
186
  grandparent = parent.parent
164
- grandparent&.array_type? && grandparent&.percent_literal?
187
+ grandparent&.array_type? && grandparent.percent_literal?
165
188
  end
166
189
  end
167
190
  end
@@ -37,9 +37,7 @@ module RuboCop
37
37
  private
38
38
 
39
39
  def valid_context?(node)
40
- unless node.arguments.one? && first_argument_starts_with_left_parenthesis?(node)
41
- return true
42
- end
40
+ return true unless node.arguments.one? && node.first_argument.parenthesized_call?
43
41
  return true if first_argument_block_type?(node.first_argument)
44
42
 
45
43
  node.operator_method? || node.setter_method? || chained_calls?(node) ||
@@ -51,11 +49,12 @@ module RuboCop
51
49
  end
52
50
 
53
51
  def valid_first_argument?(first_arg)
54
- first_arg.operator_keyword? || first_arg.hash_type? || ternary_expression?(first_arg)
52
+ first_arg.operator_keyword? || first_arg.hash_type? || ternary_expression?(first_arg) ||
53
+ compound_range?(first_arg)
55
54
  end
56
55
 
57
- def first_argument_starts_with_left_parenthesis?(node)
58
- node.first_argument.source.start_with?('(')
56
+ def compound_range?(first_arg)
57
+ first_arg.range_type? && first_arg.parenthesized_call?
59
58
  end
60
59
 
61
60
  def chained_calls?(node)
@@ -131,7 +131,7 @@ module RuboCop
131
131
  private
132
132
 
133
133
  def assume_receiver_instance_exists?(receiver)
134
- return true if receiver.const_type? && !receiver.source.match?(SNAKE_CASE)
134
+ return true if receiver.const_type? && !receiver.short_name.match?(SNAKE_CASE)
135
135
 
136
136
  receiver.literal? && !receiver.nil_type?
137
137
  end
@@ -3,88 +3,152 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Check to make sure that if safe navigation is used for a method
7
- # call in an `&&` or `||` condition that safe navigation is used for all
8
- # method calls on that same object.
6
+ # Check to make sure that if safe navigation is used in an `&&` or `||` condition,
7
+ # consistent and appropriate safe navigation, without excess or deficiency,
8
+ # is used for all method calls on the same object.
9
9
  #
10
10
  # @example
11
11
  # # bad
12
- # foo&.bar && foo.baz
12
+ # foo&.bar && foo&.baz
13
13
  #
14
- # # bad
15
- # foo.bar || foo&.baz
14
+ # # good
15
+ # foo&.bar && foo.baz
16
16
  #
17
17
  # # bad
18
- # foo&.bar && (foobar.baz || foo.baz)
18
+ # foo.bar && foo&.baz
19
19
  #
20
20
  # # good
21
21
  # foo.bar && foo.baz
22
22
  #
23
+ # # bad
24
+ # foo&.bar || foo.baz
25
+ #
23
26
  # # good
24
27
  # foo&.bar || foo&.baz
25
28
  #
29
+ # # bad
30
+ # foo.bar || foo&.baz
31
+ #
26
32
  # # good
33
+ # foo.bar || foo.baz
34
+ #
35
+ # # bad
27
36
  # foo&.bar && (foobar.baz || foo&.baz)
28
37
  #
38
+ # # good
39
+ # foo&.bar && (foobar.baz || foo.baz)
40
+ #
29
41
  class SafeNavigationConsistency < Base
30
- include IgnoredNode
31
42
  include NilMethods
32
43
  extend AutoCorrector
33
44
 
34
- MSG = 'Ensure that safe navigation is used consistently inside of `&&` and `||`.'
45
+ USE_DOT_MSG = 'Use `.` instead of unnecessary `&.`.'
46
+ USE_SAFE_NAVIGATION_MSG = 'Use `&.` for consistency with safe navigation.'
47
+
48
+ def on_and(node)
49
+ all_operands = collect_operands(node, [])
50
+ operand_groups = all_operands.group_by { |operand| receiver_name_as_key(operand, +'') }
51
+
52
+ operand_groups.each_value do |grouped_operands|
53
+ next unless (dot_op, begin_of_rest_operands = find_consistent_parts(grouped_operands))
35
54
 
36
- def on_csend(node)
37
- return unless node.parent&.operator_keyword?
55
+ rest_operands = grouped_operands[begin_of_rest_operands..]
56
+ rest_operands.each do |operand|
57
+ next if already_appropriate_call?(operand, dot_op)
38
58
 
39
- check(node)
59
+ register_offense(operand, dot_op)
60
+ end
61
+ end
40
62
  end
63
+ alias on_or on_and
41
64
 
42
- def check(node)
43
- ancestor = top_conditional_ancestor(node)
44
- conditions = ancestor.conditions
45
- safe_nav_receiver = node.receiver
65
+ private
46
66
 
47
- method_calls = conditions.select(&:send_type?)
48
- unsafe_method_calls = unsafe_method_calls(method_calls, safe_nav_receiver)
67
+ def collect_operands(node, operand_nodes)
68
+ operand_nodes(node.lhs, operand_nodes)
69
+ operand_nodes(node.rhs, operand_nodes)
49
70
 
50
- unsafe_method_calls.each do |unsafe_method_call|
51
- location = location(node, unsafe_method_call)
71
+ operand_nodes
72
+ end
52
73
 
53
- add_offense(location) { |corrector| autocorrect(corrector, unsafe_method_call) }
74
+ def receiver_name_as_key(method, fully_receivers)
75
+ if method.parent.call_type?
76
+ receiver(method.parent, fully_receivers)
77
+ else
78
+ fully_receivers << method.receiver&.source.to_s
79
+ end
80
+ end
54
81
 
55
- ignore_node(unsafe_method_call)
82
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
83
+ def find_consistent_parts(grouped_operands)
84
+ csend_in_and, csend_in_or, send_in_and, send_in_or = most_left_indices(grouped_operands)
85
+
86
+ if csend_in_and
87
+ ['.', (send_in_and ? [send_in_and, csend_in_and].min : csend_in_and) + 1]
88
+ elsif send_in_or && csend_in_or
89
+ send_in_or < csend_in_or ? ['.', send_in_or + 1] : ['&.', csend_in_or + 1]
90
+ elsif send_in_and && csend_in_or && send_in_and < csend_in_or
91
+ ['.', csend_in_or]
56
92
  end
57
93
  end
94
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
58
95
 
59
- private
96
+ def already_appropriate_call?(operand, dot_op)
97
+ (operand.safe_navigation? && dot_op == '&.') || (operand.dot? && dot_op == '.')
98
+ end
99
+
100
+ def register_offense(operand, dot_operator)
101
+ offense_range = operand.operator_method? ? operand : operand.loc.dot
102
+ message = dot_operator == '.' ? USE_DOT_MSG : USE_SAFE_NAVIGATION_MSG
60
103
 
61
- def autocorrect(corrector, node)
62
- return unless node.dot?
104
+ add_offense(offense_range, message: message) do |corrector|
105
+ next if operand.operator_method?
63
106
 
64
- corrector.insert_before(node.loc.dot, '&')
107
+ corrector.replace(operand.loc.dot, dot_operator)
108
+ end
65
109
  end
66
110
 
67
- def location(node, unsafe_method_call)
68
- node.source_range.join(unsafe_method_call.source_range)
111
+ def operand_nodes(operand, operand_nodes)
112
+ if operand.operator_keyword?
113
+ collect_operands(operand, operand_nodes)
114
+ elsif operand.call_type?
115
+ operand_nodes << operand
116
+ end
69
117
  end
70
118
 
71
- def top_conditional_ancestor(node)
72
- parent = node.parent
73
- unless parent &&
74
- (parent.operator_keyword? ||
75
- (parent.begin_type? && parent.parent && parent.parent.operator_keyword?))
76
- return node
119
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
120
+ def most_left_indices(grouped_operands)
121
+ indices = { csend_in_and: nil, csend_in_or: nil, send_in_and: nil, send_in_or: nil }
122
+
123
+ grouped_operands.each_with_index do |operand, index|
124
+ indices[:csend_in_and] ||= index if operand_in_and?(operand) && operand.csend_type?
125
+ indices[:csend_in_or] ||= index if operand_in_or?(operand) && operand.csend_type?
126
+ indices[:send_in_and] ||= index if operand_in_and?(operand) && !nilable?(operand)
127
+ indices[:send_in_or] ||= index if operand_in_or?(operand) && !nilable?(operand)
77
128
  end
78
129
 
79
- top_conditional_ancestor(parent)
130
+ indices.values
80
131
  end
132
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
81
133
 
82
- def unsafe_method_calls(method_calls, safe_nav_receiver)
83
- method_calls.select do |method_call|
84
- safe_nav_receiver == method_call.receiver &&
85
- !nil_methods.include?(method_call.method_name) &&
86
- !ignored_node?(method_call)
87
- end
134
+ def operand_in_and?(node)
135
+ return true if node.parent.and_type?
136
+
137
+ parent = node.parent.parent while node.parent.begin_type?
138
+
139
+ parent&.and_type?
140
+ end
141
+
142
+ def operand_in_or?(node)
143
+ return true if node.parent.or_type?
144
+
145
+ parent = node.parent.parent while node.parent.begin_type?
146
+
147
+ parent&.or_type?
148
+ end
149
+
150
+ def nilable?(node)
151
+ node.csend_type? || nil_methods.include?(node.method_name)
88
152
  end
89
153
  end
90
154
  end
@@ -141,7 +141,7 @@ module RuboCop
141
141
  end
142
142
 
143
143
  def in_percent_literal_array?(node)
144
- node.parent&.array_type? && node.parent&.percent_literal?
144
+ node.parent&.array_type? && node.parent.percent_literal?
145
145
  end
146
146
 
147
147
  def correct_hash_key(node)
@@ -3,29 +3,47 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Identifies places where `URI.regexp` is obsolete and should
7
- # not be used. Instead, use `URI::DEFAULT_PARSER.make_regexp`.
6
+ # Identifies places where `URI.regexp` is obsolete and should not be used.
7
+ #
8
+ # For Ruby 3.3 or lower, use `URI::DEFAULT_PARSER.make_regexp`.
9
+ # For Ruby 3.4 or higher, use `URI::RFC2396_PARSER.make_regexp`.
10
+ #
11
+ # NOTE: If you need to support both Ruby 3.3 and lower as well as Ruby 3.4 and higher,
12
+ # consider manually changing the code as follows:
13
+ #
14
+ # [source,ruby]
15
+ # ----
16
+ # defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::DEFAULT_PARSER
17
+ # ----
8
18
  #
9
19
  # @example
10
20
  # # bad
11
21
  # URI.regexp('http://example.com')
12
22
  #
13
- # # good
23
+ # # good - Ruby 3.3 or lower
14
24
  # URI::DEFAULT_PARSER.make_regexp('http://example.com')
15
25
  #
26
+ # # good - Ruby 3.4 or higher
27
+ # URI::RFC2396_PARSER.make_regexp('http://example.com')
28
+ #
16
29
  class UriRegexp < Base
17
30
  extend AutoCorrector
18
31
 
19
32
  MSG = '`%<current>s` is obsolete and should not be used. Instead, use `%<preferred>s`.'
20
- URI_CONSTANTS = ['URI', '::URI'].freeze
21
33
  RESTRICT_ON_SEND = %i[regexp].freeze
22
34
 
35
+ # @!method uri_constant?(node)
36
+ def_node_matcher :uri_constant?, <<~PATTERN
37
+ (const {cbase nil?} :URI)
38
+ PATTERN
39
+
23
40
  def on_send(node)
24
- return unless node.receiver
25
- return unless URI_CONSTANTS.include?(node.receiver.source)
41
+ return unless uri_constant?(node.receiver)
26
42
 
43
+ parser = target_ruby_version >= 3.4 ? 'RFC2396_PARSER' : 'DEFAULT_PARSER'
27
44
  argument = node.first_argument ? "(#{node.first_argument.source})" : ''
28
- preferred_method = "#{node.receiver.source}::DEFAULT_PARSER.make_regexp#{argument}"
45
+
46
+ preferred_method = "#{node.receiver.source}::#{parser}.make_regexp#{argument}"
29
47
  message = format(MSG, current: node.source, preferred: preferred_method)
30
48
 
31
49
  add_offense(node.loc.selector, message: message) do |corrector|
@@ -15,7 +15,7 @@ module RuboCop
15
15
  parent = node.parent
16
16
 
17
17
  parent&.send_type? && parent.arguments.include?(node) &&
18
- !parent.parenthesized? && parent&.block_literal?
18
+ !parent.parenthesized? && parent.block_literal?
19
19
  end
20
20
 
21
21
  # Override to determine values that are invalid in a percent array
@@ -5,7 +5,6 @@ module RuboCop
5
5
  # Common functionality for modifier cops.
6
6
  module StatementModifier
7
7
  include LineLengthHelp
8
- include RangeHelp
9
8
 
10
9
  private
11
10
 
@@ -65,7 +64,9 @@ module RuboCop
65
64
  end
66
65
 
67
66
  def method_source(if_body)
68
- range_between(if_body.source_range.begin_pos, if_body.loc.selector.end_pos).source
67
+ end_range = if_body.implicit_call? ? if_body.loc.dot.end : if_body.loc.selector
68
+
69
+ if_body.source_range.begin.join(end_range).source
69
70
  end
70
71
 
71
72
  def first_line_comment(node)
@@ -116,9 +116,9 @@ module RuboCop
116
116
  add_offense(range, message: create_message(word)) do |corrector|
117
117
  suggestions = find_flagged_term(word)['Suggestions']
118
118
 
119
- next unless suggestions.is_a?(String)
120
-
121
- corrector.replace(range, suggestions)
119
+ if (preferred_term = preferred_sole_term(suggestions))
120
+ corrector.replace(range, preferred_term)
121
+ end
122
122
  end
123
123
  end
124
124
  end
@@ -157,6 +157,15 @@ module RuboCop
157
157
  set_regexes(flagged_term_strings, allowed_strings)
158
158
  end
159
159
 
160
+ def preferred_sole_term(suggestions)
161
+ case suggestions
162
+ when Array
163
+ suggestions.one? && preferred_sole_term(suggestions.first)
164
+ when String
165
+ suggestions
166
+ end
167
+ end
168
+
160
169
  def extract_regexp(term, term_definition)
161
170
  return term_definition['Regex'] if term_definition['Regex']
162
171
  return /(?:\b|(?<=[\W_]))#{term}(?:\b|(?=[\W_]))/ if term_definition['WholeWord']
@@ -88,7 +88,7 @@ module RuboCop
88
88
  next if allowed_method_name?(method_name.to_s, prefix)
89
89
 
90
90
  add_offense(
91
- node.first_argument.source_range,
91
+ node.first_argument,
92
92
  message: message(method_name, expected_name(method_name.to_s, prefix))
93
93
  )
94
94
  end
@@ -97,8 +97,8 @@ module RuboCop
97
97
  # @!attribute [r] correctable?
98
98
  #
99
99
  # @return [Boolean]
100
- # whether this offense can be automatically corrected via
101
- # autocorrect or a todo.
100
+ # whether this offense can be automatically corrected via autocorrect.
101
+ # This includes todo comments, for example when requested with `--disable-uncorrectable`.
102
102
  def correctable?
103
103
  @status != :unsupported
104
104
  end