rubocop 1.78.0 → 1.81.6

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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -3
  3. data/config/default.yml +46 -21
  4. data/exe/rubocop +1 -8
  5. data/lib/rubocop/cli/command/auto_generate_config.rb +2 -2
  6. data/lib/rubocop/cli.rb +6 -2
  7. data/lib/rubocop/config_loader.rb +3 -1
  8. data/lib/rubocop/config_store.rb +5 -0
  9. data/lib/rubocop/cop/autocorrect_logic.rb +4 -4
  10. data/lib/rubocop/cop/correctors/alignment_corrector.rb +7 -4
  11. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +7 -2
  12. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +3 -1
  13. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +3 -2
  14. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  15. data/lib/rubocop/cop/internal_affairs/useless_restrict_on_send.rb +1 -1
  16. data/lib/rubocop/cop/layout/class_structure.rb +1 -1
  17. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  18. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +30 -12
  19. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +101 -0
  20. data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +8 -29
  21. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +1 -1
  22. data/lib/rubocop/cop/layout/hash_alignment.rb +0 -5
  23. data/lib/rubocop/cop/layout/line_length.rb +9 -1
  24. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +8 -4
  25. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +8 -0
  26. data/lib/rubocop/cop/layout/space_around_keyword.rb +6 -1
  27. data/lib/rubocop/cop/layout/space_around_operators.rb +8 -0
  28. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
  29. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +3 -2
  30. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +13 -7
  31. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +4 -1
  32. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +5 -42
  33. data/lib/rubocop/cop/lint/empty_interpolation.rb +11 -0
  34. data/lib/rubocop/cop/lint/literal_as_condition.rb +12 -0
  35. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +1 -2
  36. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +1 -0
  37. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +101 -2
  38. data/lib/rubocop/cop/lint/require_range_parentheses.rb +1 -1
  39. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -4
  40. data/lib/rubocop/cop/lint/rescue_type.rb +1 -1
  41. data/lib/rubocop/cop/lint/self_assignment.rb +6 -5
  42. data/lib/rubocop/cop/lint/shadowed_argument.rb +7 -7
  43. data/lib/rubocop/cop/lint/uri_escape_unescape.rb +2 -0
  44. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +1 -0
  45. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +121 -0
  46. data/lib/rubocop/cop/lint/void.rb +7 -0
  47. data/lib/rubocop/cop/message_annotator.rb +1 -1
  48. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  49. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -7
  50. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  51. data/lib/rubocop/cop/naming/method_name.rb +40 -1
  52. data/lib/rubocop/cop/naming/predicate_method.rb +19 -3
  53. data/lib/rubocop/cop/security/json_load.rb +33 -11
  54. data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -1
  55. data/lib/rubocop/cop/style/accessor_grouping.rb +13 -1
  56. data/lib/rubocop/cop/style/arguments_forwarding.rb +11 -17
  57. data/lib/rubocop/cop/style/array_intersect.rb +99 -35
  58. data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +47 -0
  59. data/lib/rubocop/cop/style/bitwise_predicate.rb +8 -1
  60. data/lib/rubocop/cop/style/block_delimiters.rb +1 -1
  61. data/lib/rubocop/cop/style/conditional_assignment.rb +8 -4
  62. data/lib/rubocop/cop/style/dig_chain.rb +1 -1
  63. data/lib/rubocop/cop/style/double_negation.rb +1 -1
  64. data/lib/rubocop/cop/style/endless_method.rb +15 -2
  65. data/lib/rubocop/cop/style/explicit_block_argument.rb +1 -1
  66. data/lib/rubocop/cop/style/exponential_notation.rb +1 -0
  67. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  68. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  69. data/lib/rubocop/cop/style/inverse_methods.rb +1 -1
  70. data/lib/rubocop/cop/style/it_assignment.rb +69 -12
  71. data/lib/rubocop/cop/style/it_block_parameter.rb +2 -0
  72. data/lib/rubocop/cop/style/map_to_hash.rb +1 -3
  73. data/lib/rubocop/cop/style/map_to_set.rb +1 -3
  74. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -4
  75. data/lib/rubocop/cop/style/nil_comparison.rb +9 -7
  76. data/lib/rubocop/cop/style/one_line_conditional.rb +17 -9
  77. data/lib/rubocop/cop/style/parallel_assignment.rb +32 -20
  78. data/lib/rubocop/cop/style/redundant_begin.rb +34 -0
  79. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  80. data/lib/rubocop/cop/style/redundant_exception.rb +1 -1
  81. data/lib/rubocop/cop/style/redundant_format.rb +26 -5
  82. data/lib/rubocop/cop/style/redundant_freeze.rb +1 -1
  83. data/lib/rubocop/cop/style/redundant_interpolation.rb +11 -2
  84. data/lib/rubocop/cop/style/redundant_line_continuation.rb +1 -1
  85. data/lib/rubocop/cop/style/redundant_parentheses.rb +29 -11
  86. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +4 -0
  87. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -0
  88. data/lib/rubocop/cop/style/safe_navigation.rb +20 -1
  89. data/lib/rubocop/cop/style/semicolon.rb +20 -5
  90. data/lib/rubocop/cop/style/single_line_methods.rb +3 -3
  91. data/lib/rubocop/cop/style/sole_nested_conditional.rb +30 -1
  92. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +1 -1
  93. data/lib/rubocop/cop/style/string_concatenation.rb +17 -13
  94. data/lib/rubocop/cop/style/symbol_array.rb +1 -1
  95. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +45 -0
  96. data/lib/rubocop/cop/style/unless_else.rb +10 -9
  97. data/lib/rubocop/cop/utils/format_string.rb +10 -0
  98. data/lib/rubocop/cop/variable_force/variable.rb +1 -1
  99. data/lib/rubocop/cop/variable_force.rb +25 -8
  100. data/lib/rubocop/cops_documentation_generator.rb +5 -4
  101. data/lib/rubocop/formatter/disabled_config_formatter.rb +18 -5
  102. data/lib/rubocop/formatter/markdown_formatter.rb +1 -0
  103. data/lib/rubocop/formatter/pacman_formatter.rb +1 -0
  104. data/lib/rubocop/lsp/diagnostic.rb +21 -20
  105. data/lib/rubocop/lsp/routes.rb +65 -9
  106. data/lib/rubocop/lsp/runtime.rb +2 -2
  107. data/lib/rubocop/lsp/server.rb +2 -2
  108. data/lib/rubocop/lsp/stdin_runner.rb +0 -16
  109. data/lib/rubocop/result_cache.rb +14 -12
  110. data/lib/rubocop/runner.rb +6 -4
  111. data/lib/rubocop/target_finder.rb +9 -9
  112. data/lib/rubocop/target_ruby.rb +10 -1
  113. data/lib/rubocop/version.rb +1 -1
  114. data/lib/rubocop.rb +3 -0
  115. data/lib/ruby_lsp/rubocop/addon.rb +23 -8
  116. data/lib/ruby_lsp/rubocop/runtime_adapter.rb +49 -15
  117. metadata +9 -6
@@ -3,33 +3,90 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for assignments to a local `it` variable inside a block
6
+ # Checks for local variables and method parameters named `it`,
7
7
  # where `it` can refer to the first anonymous parameter as of Ruby 3.4.
8
+ # Use a meaningful variable name instead.
8
9
  #
9
- # Although Ruby allows reassigning `it` in these cases, it could
10
+ # NOTE: Although Ruby allows reassigning `it` in these cases, it could
10
11
  # cause confusion if `it` is used as a block parameter elsewhere.
11
- # For consistency, this also applies to numblocks and blocks with
12
- # parameters, even though `it` cannot be used in those cases.
13
12
  #
14
13
  # @example
15
14
  # # bad
16
- # foo { it = 5 }
17
- # foo { |bar| it = bar }
18
- # foo { it = _2 }
15
+ # it = 5
19
16
  #
20
- # # good - use a different variable name
21
- # foo { var = 5 }
22
- # foo { |bar| var = bar }
23
- # foo { bar = _2 }
17
+ # # good
18
+ # var = 5
19
+ #
20
+ # # bad
21
+ # def foo(it)
22
+ # end
23
+ #
24
+ # # good
25
+ # def foo(arg)
26
+ # end
27
+ #
28
+ # # bad
29
+ # def foo(it = 5)
30
+ # end
31
+ #
32
+ # # good
33
+ # def foo(arg = 5)
34
+ # end
35
+ #
36
+ # # bad
37
+ # def foo(*it)
38
+ # end
39
+ #
40
+ # # good
41
+ # def foo(*args)
42
+ # end
43
+ #
44
+ # # bad
45
+ # def foo(it:)
46
+ # end
47
+ #
48
+ # # good
49
+ # def foo(arg:)
50
+ # end
51
+ #
52
+ # # bad
53
+ # def foo(it: 5)
54
+ # end
55
+ #
56
+ # # good
57
+ # def foo(arg: 5)
58
+ # end
59
+ #
60
+ # # bad
61
+ # def foo(**it)
62
+ # end
63
+ #
64
+ # # good
65
+ # def foo(**kwargs)
66
+ # end
67
+ #
68
+ # # bad
69
+ # def foo(&it)
70
+ # end
71
+ #
72
+ # # good
73
+ # def foo(&block)
74
+ # end
24
75
  class ItAssignment < Base
25
76
  MSG = '`it` is the default block parameter; consider another name.'
26
77
 
27
78
  def on_lvasgn(node)
28
79
  return unless node.name == :it
29
- return unless node.each_ancestor(:any_block).any?
30
80
 
31
81
  add_offense(node.loc.name)
32
82
  end
83
+ alias on_arg on_lvasgn
84
+ alias on_optarg on_lvasgn
85
+ alias on_restarg on_lvasgn
86
+ alias on_blockarg on_lvasgn
87
+ alias on_kwarg on_lvasgn
88
+ alias on_kwoptarg on_lvasgn
89
+ alias on_kwrestarg on_lvasgn
33
90
  end
34
91
  end
35
92
  end
@@ -109,6 +109,8 @@ module RuboCop
109
109
  private
110
110
 
111
111
  def find_block_variables(node, block_argument_name)
112
+ return [] unless node.body
113
+
112
114
  node.body.each_descendant(:lvar).select do |descendant|
113
115
  descendant.source == block_argument_name
114
116
  end
@@ -56,12 +56,10 @@ module RuboCop
56
56
 
57
57
  def on_send(node)
58
58
  return unless (to_h_node, map_node = map_to_h(node))
59
+ return if to_h_node.block_literal?
59
60
 
60
61
  message = format(MSG, method: map_node.loc.selector.source, dot: to_h_node.loc.dot.source)
61
62
  add_offense(map_node.loc.selector, message: message) do |corrector|
62
- # If the `to_h` call already has a block, do not autocorrect.
63
- next if to_h_node.block_literal?
64
-
65
63
  autocorrect(corrector, to_h_node, map_node)
66
64
  end
67
65
  end
@@ -40,12 +40,10 @@ module RuboCop
40
40
 
41
41
  def on_send(node)
42
42
  return unless (to_set_node, map_node = map_to_set?(node))
43
+ return if to_set_node.block_literal?
43
44
 
44
45
  message = format(MSG, method: map_node.loc.selector.source)
45
46
  add_offense(map_node.loc.selector, message: message) do |corrector|
46
- # If the `to_set` call already has a block, do not autocorrect.
47
- next if to_set_node.block_literal?
48
-
49
47
  autocorrect(corrector, to_set_node, map_node)
50
48
  end
51
49
  end
@@ -222,11 +222,9 @@ module RuboCop
222
222
  end
223
223
 
224
224
  def unary_literal?(node)
225
- # NOTE: should be removed after releasing https://github.com/rubocop/rubocop-ast/pull/379
226
- return node.source.match?(/\A[+-]/) if node.complex_type?
225
+ return true if node.numeric_type? && node.sign?
227
226
 
228
- (node.numeric_type? && node.sign?) ||
229
- (node.parent&.send_type? && node.parent.unary_operation?)
227
+ node.parent&.send_type? && node.parent.unary_operation?
230
228
  end
231
229
 
232
230
  def assigned_before?(node, target)
@@ -43,24 +43,26 @@ module RuboCop
43
43
  # @!method nil_check?(node)
44
44
  def_node_matcher :nil_check?, '(send _ :nil?)'
45
45
 
46
+ # rubocop:disable Metrics/AbcSize
46
47
  def on_send(node)
47
48
  return unless node.receiver
48
49
 
49
50
  style_check?(node) do
50
51
  add_offense(node.loc.selector) do |corrector|
51
- new_code = if prefer_comparison?
52
- node.source.sub('.nil?', ' == nil')
53
- else
54
- node.source.sub(/\s*={2,3}\s*nil/, '.nil?')
55
- end
56
-
57
- corrector.replace(node, new_code)
52
+ if prefer_comparison?
53
+ range = node.loc.dot.join(node.loc.selector.end)
54
+ corrector.replace(range, ' == nil')
55
+ else
56
+ range = node.receiver.source_range.end.join(node.source_range.end)
57
+ corrector.replace(range, '.nil?')
58
+ end
58
59
 
59
60
  parent = node.parent
60
61
  corrector.wrap(node, '(', ')') if parent.respond_to?(:method?) && parent.method?(:!)
61
62
  end
62
63
  end
63
64
  end
65
+ # rubocop:enable Metrics/AbcSize
64
66
 
65
67
  private
66
68
 
@@ -55,19 +55,21 @@ module RuboCop
55
55
  include OnNormalIfUnless
56
56
  extend AutoCorrector
57
57
 
58
- MSG = 'Favor the ternary operator (`?:`) or multi-line constructs ' \
59
- 'over single-line `%<keyword>s/then/else/end` constructs.'
58
+ MSG_SUFFIX = 'over single-line `%<keyword>s/then/else/end` constructs.'
59
+ MSG_TERNARY = "Favor the ternary operator (`?:`) #{MSG_SUFFIX}"
60
+ MSG_MULTILINE = "Favor multi-line `%<keyword>s` #{MSG_SUFFIX}"
60
61
 
61
62
  def on_normal_if_unless(node)
62
63
  return unless node.single_line?
63
64
  return unless node.else_branch
64
65
  return if node.elsif? || node.if_branch&.begin_type?
65
66
 
66
- message = message(node)
67
- add_offense(node, message: message) do |corrector|
67
+ multiline = multiline?(node)
68
+
69
+ add_offense(node, message: message(node, multiline)) do |corrector|
68
70
  next if part_of_ignored_node?(node)
69
71
 
70
- autocorrect(corrector, node)
72
+ autocorrect(corrector, node, multiline)
71
73
 
72
74
  ignore_node(node)
73
75
  end
@@ -75,12 +77,18 @@ module RuboCop
75
77
 
76
78
  private
77
79
 
78
- def message(node)
79
- format(MSG, keyword: node.keyword)
80
+ def multiline?(node)
81
+ always_multiline? || cannot_replace_to_ternary?(node)
82
+ end
83
+
84
+ def message(node, multiline)
85
+ template = multiline ? MSG_MULTILINE : MSG_TERNARY
86
+
87
+ format(template, keyword: node.keyword)
80
88
  end
81
89
 
82
- def autocorrect(corrector, node)
83
- if always_multiline? || cannot_replace_to_ternary?(node)
90
+ def autocorrect(corrector, node, multiline)
91
+ if multiline
84
92
  IfThenCorrector.new(node, indentation: configured_indentation_width).call(corrector)
85
93
  else
86
94
  corrector.replace(node, ternary_correction(node))
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'tsort'
4
-
5
3
  module RuboCop
6
4
  module Cop
7
5
  module Style
@@ -29,6 +27,8 @@ module RuboCop
29
27
  MSG = 'Do not use parallel assignment.'
30
28
 
31
29
  def on_masgn(node) # rubocop:disable Metrics/AbcSize
30
+ return if part_of_ignored_node?(node)
31
+
32
32
  rhs = node.rhs
33
33
  rhs = rhs.body if rhs.rescue_type?
34
34
  rhs_elements = Array(rhs).compact # edge case for one constant
@@ -41,6 +41,7 @@ module RuboCop
41
41
  add_offense(range) do |corrector|
42
42
  autocorrect(corrector, node, rhs)
43
43
  end
44
+ ignore_node(node)
44
45
  end
45
46
 
46
47
  private
@@ -91,15 +92,9 @@ module RuboCop
91
92
  def find_valid_order(left_elements, right_elements)
92
93
  # arrange left_elements in an order such that no corresponding right
93
94
  # element refers to a left element earlier in the sequence
94
- # this can be done using an algorithm called a "topological sort"
95
- # fortunately for us, Ruby's stdlib contains an implementation
96
95
  assignments = left_elements.zip(right_elements)
97
96
 
98
- begin
99
- AssignmentSorter.new(assignments).tsort
100
- rescue TSort::Cyclic
101
- nil
102
- end
97
+ AssignmentSorter.new(assignments).tsort
103
98
  end
104
99
 
105
100
  # Converts (send nil :something) nodes to (send (:self) :something).
@@ -114,10 +109,9 @@ module RuboCop
114
109
  # @!method implicit_self_getter?(node)
115
110
  def_node_matcher :implicit_self_getter?, '(send nil? $_)'
116
111
 
117
- # Helper class necessitated by silly design of TSort prior to Ruby 2.1
118
- # Newer versions have a better API, but that doesn't help us
112
+ # Topologically sorts the assignments with Kahn's algorithm.
113
+ # https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
119
114
  class AssignmentSorter
120
- include TSort
121
115
  extend RuboCop::NodePattern::Macros
122
116
 
123
117
  # @!method var_name(node)
@@ -133,21 +127,39 @@ module RuboCop
133
127
  @assignments = assignments
134
128
  end
135
129
 
136
- def tsort_each_node(...)
137
- @assignments.each(...)
130
+ def tsort
131
+ dependencies = @assignments.to_h do |assignment|
132
+ [assignment, dependencies_for_assignment(assignment)]
133
+ end
134
+ result = []
135
+
136
+ while (matched_node, = dependencies.find { |_node, edges| edges.empty? })
137
+ dependencies.delete(matched_node)
138
+ result.push(matched_node)
139
+
140
+ dependencies.each do |node, edges|
141
+ dependencies[node].delete(matched_node) if edges.include?(matched_node)
142
+ end
143
+ end
144
+ # Cyclic dependency
145
+ return nil if dependencies.any?
146
+
147
+ result
138
148
  end
139
149
 
140
- def tsort_each_child(assignment)
141
- # yield all the assignments which must come after `assignment`
142
- # (due to dependencies on the previous value of the assigned var)
150
+ # Returns all the assignments which must come after `assignment`
151
+ # (due to dependencies on the previous value of the assigned var)
152
+ def dependencies_for_assignment(assignment)
143
153
  my_lhs, _my_rhs = *assignment
144
154
 
145
- @assignments.each do |other|
146
- _other_lhs, other_rhs = *other
155
+ @assignments.filter_map do |other|
156
+ # Exclude self, there are no dependencies in cases such as `a, b = a, b`.
157
+ next if other == assignment
147
158
 
159
+ _other_lhs, other_rhs = *other
148
160
  next unless dependency?(my_lhs, other_rhs)
149
161
 
150
- yield other
162
+ other
151
163
  end
152
164
  end
153
165
 
@@ -85,6 +85,29 @@ module RuboCop
85
85
  end
86
86
  alias on_defs on_def
87
87
 
88
+ def on_if(node)
89
+ return if node.modifier_form?
90
+
91
+ inspect_branches(node)
92
+ end
93
+
94
+ def on_case(node)
95
+ inspect_branches(node)
96
+ end
97
+ alias on_case_match on_case
98
+
99
+ def on_while(node)
100
+ return if node.modifier_form?
101
+
102
+ body = node.body
103
+
104
+ return unless body&.kwbegin_type?
105
+ return if body.rescue_node || body.ensure_node
106
+
107
+ register_offense(body)
108
+ end
109
+ alias on_until on_while
110
+
88
111
  def on_block(node)
89
112
  return if target_ruby_version < 2.5
90
113
  return if node.send_node.lambda_literal?
@@ -180,6 +203,8 @@ module RuboCop
180
203
  end
181
204
 
182
205
  def begin_block_has_multiline_statements?(node)
206
+ return false unless node.parent
207
+
183
208
  node.children.count >= 2
184
209
  end
185
210
 
@@ -199,6 +224,15 @@ module RuboCop
199
224
  def valid_begin_assignment?(node)
200
225
  node.parent&.assignment? && !node.children.one?
201
226
  end
227
+
228
+ def inspect_branches(node)
229
+ node.branches.each do |branch|
230
+ next unless branch&.kwbegin_type?
231
+ next if branch.rescue_node || branch.ensure_node
232
+
233
+ register_offense(branch)
234
+ end
235
+ end
202
236
  end
203
237
  end
204
238
  end
@@ -247,7 +247,7 @@ module RuboCop
247
247
  "#{if_branch.receiver.source} #{if_branch.method_name} (#{argument_source}"
248
248
  elsif if_branch.true_type?
249
249
  condition = if_branch.parent.condition
250
- return condition.source if condition.arguments.empty?
250
+ return condition.source if condition.arguments.empty? || condition.parenthesized?
251
251
 
252
252
  wrap_arguments_with_parens(condition)
253
253
  else
@@ -51,7 +51,7 @@ module RuboCop
51
51
  end
52
52
 
53
53
  def string_message?(message)
54
- message.type?(:str, :dstr, :xstr)
54
+ message.any_str_type?
55
55
  end
56
56
 
57
57
  def fix_compact(node)
@@ -89,7 +89,7 @@ module RuboCop
89
89
 
90
90
  def on_send(node)
91
91
  format_without_additional_args?(node) do |value|
92
- replacement = value.source
92
+ replacement = escape_control_chars(value.source)
93
93
 
94
94
  add_offense(node, message: message(node, replacement)) do |corrector|
95
95
  corrector.replace(node, replacement)
@@ -134,6 +134,7 @@ module RuboCop
134
134
  end
135
135
  end
136
136
 
137
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
137
138
  def all_fields_literal?(string, arguments)
138
139
  count = 0
139
140
  sequences = RuboCop::Cop::Utils::FormatString.new(string).format_sequences
@@ -141,29 +142,44 @@ module RuboCop
141
142
 
142
143
  sequences.each do |sequence|
143
144
  next if sequence.percent?
145
+ next if unknown_variable_width?(sequence, arguments)
144
146
 
145
147
  hash = arguments.detect(&:hash_type?)
146
148
  next unless (argument = find_argument(sequence, arguments, hash))
147
149
  next unless matching_argument?(sequence, argument)
150
+ next if (sequence.width || sequence.precision) && argument.dstr_type?
148
151
 
149
152
  count += 1
150
153
  end
151
154
 
152
155
  sequences.size == count
153
156
  end
157
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
154
158
 
159
+ # If the sequence has a variable (`*`) width, it cannot be autocorrected
160
+ # if the width is not given as a numeric literal argument
161
+ def unknown_variable_width?(sequence, arguments)
162
+ return false unless sequence.variable_width?
163
+
164
+ argument = arguments[sequence.variable_width_argument_number - 1]
165
+ !numeric?(argument)
166
+ end
167
+
168
+ # rubocop:disable Metrics/AbcSize
155
169
  def find_argument(sequence, arguments, hash)
156
170
  if hash && (sequence.annotated? || sequence.template?)
157
171
  find_hash_value_node(hash, sequence.name.to_sym).first
172
+ elsif sequence.variable_width?
173
+ # If the specifier contains `*`, the argument for the width can be ignored.
174
+ arguments.delete_at(sequence.variable_width_argument_number - 1)
175
+ arguments.shift
158
176
  elsif sequence.arg_number
159
177
  arguments[sequence.arg_number.to_i - 1]
160
178
  else
161
- # If the specifier contains `*`, the following arguments will be used
162
- # to specify the width and can be ignored.
163
- (sequence.arity - 1).times { arguments.shift }
164
179
  arguments.shift
165
180
  end
166
181
  end
182
+ # rubocop:enable Metrics/AbcSize
167
183
 
168
184
  def matching_argument?(sequence, argument)
169
185
  # Template specifiers don't give a type, any acceptable literal type is ok.
@@ -214,7 +230,12 @@ module RuboCop
214
230
  end
215
231
  end
216
232
 
217
- "#{start_delimiter}#{string}#{end_delimiter}"
233
+ "#{start_delimiter}#{escape_control_chars(string)}#{end_delimiter}"
234
+ end
235
+
236
+ # Escape any control characters in the string (eg. `\t` or `\n` become `\\t` or `\\n`)
237
+ def escape_control_chars(string)
238
+ string.gsub(/\p{Cc}/) { |s| s.dump[1..-2] }
218
239
  end
219
240
 
220
241
  def argument_values(arguments)
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Check for uses of `Object#freeze` on immutable objects.
6
+ # Checks for uses of `Object#freeze` on immutable objects.
7
7
  #
8
8
  # NOTE: `Regexp` and `Range` literals are frozen objects since Ruby 3.0.
9
9
  #
@@ -49,9 +49,10 @@ module RuboCop
49
49
  def on_dstr(node)
50
50
  return unless single_interpolation?(node)
51
51
 
52
- add_offense(node) do |corrector|
53
- embedded_node = node.children.first
52
+ embedded_node = node.children.first
53
+ return if use_match_pattern?(embedded_node)
54
54
 
55
+ add_offense(node) do |corrector|
55
56
  if variable_interpolation?(embedded_node)
56
57
  autocorrect_variable_interpolation(corrector, embedded_node, node)
57
58
  elsif single_variable_interpolation?(embedded_node)
@@ -71,6 +72,14 @@ module RuboCop
71
72
  !embedded_in_percent_array?(node)
72
73
  end
73
74
 
75
+ def use_match_pattern?(node)
76
+ return false if target_ruby_version <= 2.7
77
+
78
+ node.children.any? do |child|
79
+ child.respond_to?(:match_pattern_type?) && child.match_pattern_type?
80
+ end
81
+ end
82
+
74
83
  def single_variable_interpolation?(node)
75
84
  return false unless node.children.one?
76
85
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Check for redundant line continuation.
6
+ # Checks for redundant line continuation.
7
7
  #
8
8
  # This cop marks a line continuation as redundant if removing the backslash
9
9
  # does not result in a syntax error.
@@ -24,9 +24,6 @@ module RuboCop
24
24
  (send `{(send _recv _msg) str array hash const #variable?} :[] ...)
25
25
  PATTERN
26
26
 
27
- # @!method method_node_and_args(node)
28
- def_node_matcher :method_node_and_args, '$(call _recv _msg $...)'
29
-
30
27
  # @!method rescue?(node)
31
28
  def_node_matcher :rescue?, '{^resbody ^^resbody}'
32
29
 
@@ -149,7 +146,7 @@ module RuboCop
149
146
  return offense(begin_node, message)
150
147
  end
151
148
 
152
- check_send(begin_node, node) if node.call_type?
149
+ check_send(begin_node, node) if call_node?(node)
153
150
  end
154
151
 
155
152
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
@@ -169,6 +166,7 @@ module RuboCop
169
166
  end
170
167
  return 'an interpolated expression' if interpolation?(begin_node)
171
168
  return 'a method argument' if argument_of_parenthesized_method_call?(begin_node, node)
169
+ return 'a one-line rescue' if oneline_rescue_parentheses_required?(begin_node, node)
172
170
 
173
171
  return if begin_node.chained?
174
172
 
@@ -200,6 +198,15 @@ module RuboCop
200
198
  parent.call_type? && parent.parenthesized? && parent.receiver != begin_node
201
199
  end
202
200
 
201
+ def oneline_rescue_parentheses_required?(begin_node, node)
202
+ return false unless node.rescue_type?
203
+ return false unless (parent = begin_node.parent)
204
+ return false if parent.if_type? && parent.ternary?
205
+ return false if parent.conditional? && parent.condition == begin_node
206
+
207
+ !parent.type?(:call, :array, :pair)
208
+ end
209
+
203
210
  def method_call_parentheses_required?(node)
204
211
  return false unless node.call_type?
205
212
 
@@ -210,10 +217,16 @@ module RuboCop
210
217
  !!config.for_enabled_cop('Style/ParenthesesAroundCondition')['AllowInMultilineConditions']
211
218
  end
212
219
 
220
+ def call_node?(node)
221
+ node.call_type? || (node.any_block_type? && node.braces? && !node.lambda_or_proc?)
222
+ end
223
+
213
224
  def check_send(begin_node, node)
225
+ node = node.send_node if node.any_block_type?
226
+
214
227
  return check_unary(begin_node, node) if node.unary_operation?
215
228
 
216
- return unless method_call_with_redundant_parentheses?(node)
229
+ return unless method_call_with_redundant_parentheses?(begin_node, node)
217
230
  return if call_chain_starts_with_int?(begin_node, node) ||
218
231
  do_end_block_in_method_chain?(begin_node, node)
219
232
 
@@ -224,8 +237,7 @@ module RuboCop
224
237
  return if begin_node.chained?
225
238
 
226
239
  node = node.children.first while suspect_unary?(node)
227
-
228
- return if node.send_type? && !method_call_with_redundant_parentheses?(node)
240
+ return unless method_call_with_redundant_parentheses?(begin_node, node)
229
241
 
230
242
  offense(begin_node, 'a unary operation')
231
243
  end
@@ -287,13 +299,19 @@ module RuboCop
287
299
  end
288
300
  end
289
301
 
290
- def method_call_with_redundant_parentheses?(node)
291
- return false unless node.call_type?
302
+ def method_call_with_redundant_parentheses?(begin_node, node)
303
+ return false unless node.type?(:call, :super, :yield, :defined?)
292
304
  return false if node.prefix_not?
305
+ return true if singular_parenthesized_parent?(begin_node)
306
+
307
+ node.arguments.empty? || parentheses?(node) || square_brackets?(node)
308
+ end
293
309
 
294
- send_node, args = method_node_and_args(node)
310
+ def singular_parenthesized_parent?(begin_node)
311
+ return true unless begin_node.parent
312
+ return false if begin_node.parent.type?(:splat, :kwsplat)
295
313
 
296
- args.empty? || parentheses?(send_node) || square_brackets?(send_node)
314
+ parentheses?(begin_node) && begin_node.parent.children.one?
297
315
  end
298
316
 
299
317
  def only_begin_arg?(args)
@@ -66,6 +66,7 @@ module RuboCop
66
66
  DETERMINISTIC_REGEX.match?(regexp_node.source)
67
67
  end
68
68
 
69
+ # rubocop:disable Metrics/MethodLength
69
70
  def preferred_argument(regexp_node)
70
71
  new_argument = replacement(regexp_node)
71
72
 
@@ -73,6 +74,8 @@ module RuboCop
73
74
  new_argument.gsub!("'", "\\\\'")
74
75
  new_argument.gsub!('\"', '"')
75
76
  quote = "'"
77
+ elsif new_argument.include?("\\'")
78
+ quote = "'"
76
79
  elsif new_argument.include?('\'')
77
80
  new_argument.gsub!("'", "\\\\'")
78
81
  quote = "'"
@@ -84,6 +87,7 @@ module RuboCop
84
87
 
85
88
  "#{quote}#{new_argument}#{quote}"
86
89
  end
90
+ # rubocop:enable Metrics/MethodLength
87
91
 
88
92
  def replacement(regexp_node)
89
93
  regexp_content = regexp_node.content