rubocop 1.51.0 → 1.54.1

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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +62 -3
  4. data/lib/rubocop/cli/command/lsp.rb +19 -0
  5. data/lib/rubocop/cli.rb +3 -0
  6. data/lib/rubocop/config_loader_resolver.rb +4 -3
  7. data/lib/rubocop/cop/base.rb +1 -1
  8. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  9. data/lib/rubocop/cop/bundler/gem_version.rb +2 -2
  10. data/lib/rubocop/cop/gemspec/dependency_version.rb +2 -2
  11. data/lib/rubocop/cop/internal_affairs/cop_description.rb +32 -8
  12. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +3 -1
  13. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +3 -3
  14. data/lib/rubocop/cop/layout/class_structure.rb +7 -0
  15. data/lib/rubocop/cop/layout/closing_heredoc_indentation.rb +1 -2
  16. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +27 -4
  17. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +2 -0
  18. data/lib/rubocop/cop/layout/indentation_style.rb +1 -1
  19. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  20. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +2 -0
  21. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  22. data/lib/rubocop/cop/layout/space_after_comma.rb +9 -1
  23. data/lib/rubocop/cop/layout/space_around_operators.rb +3 -1
  24. data/lib/rubocop/cop/layout/space_inside_range_literal.rb +1 -1
  25. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +2 -1
  26. data/lib/rubocop/cop/lint/debugger.rb +9 -5
  27. data/lib/rubocop/cop/lint/duplicate_hash_key.rb +2 -1
  28. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +46 -19
  29. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -2
  30. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +1 -1
  31. data/lib/rubocop/cop/lint/identity_comparison.rb +0 -1
  32. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +1 -2
  33. data/lib/rubocop/cop/lint/inherit_exception.rb +9 -0
  34. data/lib/rubocop/cop/lint/missing_super.rb +34 -5
  35. data/lib/rubocop/cop/lint/mixed_case_range.rb +111 -0
  36. data/lib/rubocop/cop/lint/number_conversion.rb +5 -0
  37. data/lib/rubocop/cop/lint/ordered_magic_comments.rb +0 -1
  38. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +130 -0
  39. data/lib/rubocop/cop/lint/redundant_require_statement.rb +8 -3
  40. data/lib/rubocop/cop/lint/send_with_mixin_argument.rb +1 -2
  41. data/lib/rubocop/cop/lint/shadowed_exception.rb +5 -11
  42. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  43. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
  44. data/lib/rubocop/cop/lint/useless_assignment.rb +4 -1
  45. data/lib/rubocop/cop/lint/void.rb +12 -18
  46. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +1 -2
  47. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +30 -2
  48. data/lib/rubocop/cop/migration/department_name.rb +2 -2
  49. data/lib/rubocop/cop/mixin/allowed_receivers.rb +34 -0
  50. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  51. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -1
  52. data/lib/rubocop/cop/mixin/heredoc.rb +6 -2
  53. data/lib/rubocop/cop/mixin/percent_literal.rb +1 -1
  54. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  55. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +3 -3
  56. data/lib/rubocop/cop/naming/variable_name.rb +6 -1
  57. data/lib/rubocop/cop/style/accessor_grouping.rb +5 -1
  58. data/lib/rubocop/cop/style/begin_block.rb +1 -2
  59. data/lib/rubocop/cop/style/block_comments.rb +1 -1
  60. data/lib/rubocop/cop/style/block_delimiters.rb +3 -3
  61. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -1
  62. data/lib/rubocop/cop/style/class_equality_comparison.rb +17 -39
  63. data/lib/rubocop/cop/style/collection_compact.rb +6 -0
  64. data/lib/rubocop/cop/style/conditional_assignment.rb +3 -1
  65. data/lib/rubocop/cop/style/dir.rb +1 -1
  66. data/lib/rubocop/cop/style/dir_empty.rb +8 -14
  67. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +1 -1
  68. data/lib/rubocop/cop/style/eval_with_location.rb +4 -4
  69. data/lib/rubocop/cop/style/exact_regexp_match.rb +8 -2
  70. data/lib/rubocop/cop/style/file_read.rb +2 -2
  71. data/lib/rubocop/cop/style/hash_each_methods.rb +1 -22
  72. data/lib/rubocop/cop/style/hash_transform_keys.rb +2 -2
  73. data/lib/rubocop/cop/style/hash_transform_values.rb +2 -2
  74. data/lib/rubocop/cop/style/identical_conditional_branches.rb +6 -2
  75. data/lib/rubocop/cop/style/if_with_semicolon.rb +2 -2
  76. data/lib/rubocop/cop/style/invertible_unless_condition.rb +1 -1
  77. data/lib/rubocop/cop/style/lambda.rb +3 -3
  78. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +3 -4
  79. data/lib/rubocop/cop/style/multiple_comparison.rb +14 -0
  80. data/lib/rubocop/cop/style/numeric_literals.rb +1 -1
  81. data/lib/rubocop/cop/style/preferred_hash_methods.rb +1 -1
  82. data/lib/rubocop/cop/style/redundant_array_constructor.rb +77 -0
  83. data/lib/rubocop/cop/style/redundant_begin.rb +1 -1
  84. data/lib/rubocop/cop/style/redundant_conditional.rb +1 -1
  85. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +38 -0
  86. data/lib/rubocop/cop/style/redundant_filter_chain.rb +101 -0
  87. data/lib/rubocop/cop/style/redundant_line_continuation.rb +2 -2
  88. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
  89. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +100 -0
  90. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +46 -0
  91. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +2 -1
  92. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +3 -1
  93. data/lib/rubocop/cop/style/redundant_sort.rb +1 -1
  94. data/lib/rubocop/cop/style/redundant_string_escape.rb +2 -0
  95. data/lib/rubocop/cop/style/require_order.rb +2 -1
  96. data/lib/rubocop/cop/style/rescue_modifier.rb +1 -3
  97. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +81 -0
  98. data/lib/rubocop/cop/style/select_by_regexp.rb +15 -5
  99. data/lib/rubocop/cop/style/signal_exception.rb +1 -1
  100. data/lib/rubocop/cop/style/single_line_methods.rb +1 -1
  101. data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
  102. data/lib/rubocop/cop/style/special_global_vars.rb +1 -2
  103. data/lib/rubocop/cop/style/yaml_file_read.rb +66 -0
  104. data/lib/rubocop/cop/style/yoda_condition.rb +4 -2
  105. data/lib/rubocop/cop/util.rb +1 -1
  106. data/lib/rubocop/cop/utils/regexp_ranges.rb +100 -0
  107. data/lib/rubocop/cop/variable_force/assignment.rb +43 -4
  108. data/lib/rubocop/cop/variable_force.rb +1 -0
  109. data/lib/rubocop/cops_documentation_generator.rb +1 -1
  110. data/lib/rubocop/ext/regexp_parser.rb +4 -1
  111. data/lib/rubocop/lsp/logger.rb +22 -0
  112. data/lib/rubocop/lsp/routes.rb +231 -0
  113. data/lib/rubocop/lsp/runtime.rb +82 -0
  114. data/lib/rubocop/lsp/server.rb +66 -0
  115. data/lib/rubocop/lsp/severity.rb +27 -0
  116. data/lib/rubocop/options.rb +11 -1
  117. data/lib/rubocop/server/client_command/exec.rb +2 -1
  118. data/lib/rubocop/version.rb +8 -4
  119. data/lib/rubocop.rb +12 -0
  120. metadata +36 -5
@@ -4,6 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module Lint
6
6
  # Checks for duplicated keys in hash literals.
7
+ # This cop considers both primitive types and constants for the hash keys.
7
8
  #
8
9
  # This cop mirrors a warning in Ruby 2.2.
9
10
  #
@@ -24,7 +25,7 @@ module RuboCop
24
25
  MSG = 'Duplicated key in hash literal.'
25
26
 
26
27
  def on_hash(node)
27
- keys = node.keys.select(&:recursive_basic_literal?)
28
+ keys = node.keys.select { |key| key.recursive_basic_literal? || key.const_type? }
28
29
 
29
30
  return unless duplicates?(keys)
30
31
 
@@ -24,6 +24,8 @@ module RuboCop
24
24
 
25
25
  MSG_REPEATED_ELEMENT = 'Duplicate element inside regexp character class'
26
26
 
27
+ OCTAL_DIGITS_AFTER_ESCAPE = 2
28
+
27
29
  def on_regexp(node)
28
30
  each_repeated_character_class_element_loc(node) do |loc|
29
31
  add_offense(loc, message: MSG_REPEATED_ELEMENT) do |corrector|
@@ -32,35 +34,57 @@ module RuboCop
32
34
  end
33
35
  end
34
36
 
35
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
36
37
  def each_repeated_character_class_element_loc(node)
37
38
  node.parsed_tree&.each_expression do |expr|
38
39
  next if skip_expression?(expr)
39
40
 
40
41
  seen = Set.new
41
- enum = expr.expressions.to_enum
42
- expression_count = expr.expressions.count
42
+ group_expressions(node, expr.expressions) do |group|
43
+ group_source = group.map(&:to_s).join
43
44
 
44
- expression_count.times do |current_number|
45
- current_child = enum.next
46
- next if within_interpolation?(node, current_child)
45
+ yield source_range(group) if seen.include?(group_source)
47
46
 
48
- current_child_source = current_child.to_s
49
- next_child = enum.peek if current_number + 1 < expression_count
47
+ seen << group_source
48
+ end
49
+ end
50
+ end
50
51
 
51
- if seen.include?(current_child_source)
52
- next if start_with_escaped_zero_number?(current_child_source, next_child.to_s)
52
+ private
53
53
 
54
- yield current_child.expression
55
- end
54
+ def group_expressions(node, expressions)
55
+ # Create a mutable list to simplify state tracking while we iterate.
56
+ expressions = expressions.to_a
56
57
 
57
- seen << current_child_source
58
- end
58
+ until expressions.empty?
59
+ # With we may need to compose a group of multiple expressions.
60
+ group = [expressions.shift]
61
+ next if within_interpolation?(node, group.first)
62
+
63
+ # With regexp_parser < 2.7 escaped octal sequences may be up to 3
64
+ # separate expressions ("\\0", "0", "1").
65
+ pop_octal_digits(group, expressions) if escaped_octal?(group.first.to_s)
66
+
67
+ yield(group)
59
68
  end
60
69
  end
61
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
62
70
 
63
- private
71
+ def pop_octal_digits(current_child, expressions)
72
+ OCTAL_DIGITS_AFTER_ESCAPE.times do
73
+ next_child = expressions.first
74
+ break unless octal?(next_child.to_s)
75
+
76
+ current_child << expressions.shift
77
+ end
78
+ end
79
+
80
+ def source_range(children)
81
+ return children.first.expression if children.size == 1
82
+
83
+ range_between(
84
+ children.first.expression.begin_pos,
85
+ children.last.expression.begin_pos + children.last.to_s.length
86
+ )
87
+ end
64
88
 
65
89
  def skip_expression?(expr)
66
90
  expr.type != :set || expr.token == :intersection
@@ -75,9 +99,12 @@ module RuboCop
75
99
  interpolation_locs(node).any? { |il| il.overlaps?(parse_tree_child_loc) }
76
100
  end
77
101
 
78
- def start_with_escaped_zero_number?(current_child, next_child)
79
- # Represents escaped code from `"\00"` (`"\u0000"`) to `"\07"` (`"\a"`).
80
- current_child == '\\0' && next_child.match?(/[0-7]/)
102
+ def escaped_octal?(string)
103
+ string.length == 2 && string[0] == '\\' && octal?(string[1])
104
+ end
105
+
106
+ def octal?(char)
107
+ ('0'..'7').cover?(char)
81
108
  end
82
109
 
83
110
  def interpolation_locs(node)
@@ -3,8 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- #
7
- # This cop emulates the following Ruby warnings in Ruby 2.6.
6
+ # Emulates the following Ruby warnings in Ruby 2.6.
8
7
  #
9
8
  # [source,console]
10
9
  # ----
@@ -65,7 +65,7 @@ module RuboCop
65
65
  end
66
66
 
67
67
  def send_node?(node)
68
- return nil unless node
68
+ return false unless node
69
69
 
70
70
  node.call_type?
71
71
  end
@@ -3,7 +3,6 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- #
7
6
  # Prefer `equal?` over `==` when comparing `object_id`.
8
7
  #
9
8
  # `Object#equal?` is provided to compare objects for identity, and in contrast
@@ -3,8 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- #
7
- # This cop checks for `IO.select` that is incompatible with Fiber Scheduler since Ruby 3.0.
6
+ # Checks for `IO.select` that is incompatible with Fiber Scheduler since Ruby 3.0.
8
7
  #
9
8
  # When an array of IO objects waiting for an exception (the third argument of `IO.select`)
10
9
  # is used as an argument, there is no alternative API, so offenses are not registered.
@@ -58,6 +58,7 @@ module RuboCop
58
58
 
59
59
  def on_class(node)
60
60
  return unless node.parent_class && exception_class?(node.parent_class)
61
+ return if inherit_exception_class_with_omitted_namespace?(node)
61
62
 
62
63
  message = message(node.parent_class)
63
64
 
@@ -87,6 +88,14 @@ module RuboCop
87
88
  class_node.const_name == 'Exception'
88
89
  end
89
90
 
91
+ def inherit_exception_class_with_omitted_namespace?(class_node)
92
+ return false if class_node.parent_class.namespace&.cbase_type?
93
+
94
+ class_node.left_siblings.any? do |sibling|
95
+ sibling.respond_to?(:identifier) && exception_class?(sibling.identifier)
96
+ end
97
+ end
98
+
90
99
  def preferred_base_class
91
100
  PREFERRED_BASE_CLASS[style]
92
101
  end
@@ -11,6 +11,16 @@ module RuboCop
11
11
  # missing method. In other cases, the theoretical ideal handling could be
12
12
  # challenging or verbose for no actual gain.
13
13
  #
14
+ # Autocorrection is not supported because the position of `super` cannot be
15
+ # determined automatically.
16
+ #
17
+ # `Object` and `BasicObject` are allowed by this cop because of their
18
+ # stateless nature. However, sometimes you might want to allow other parent
19
+ # classes from this cop, for example in the case of an abstract class that is
20
+ # not meant to be called with `super`. In those cases, you can use the
21
+ # `AllowedParentClasses` option to specify which classes should be allowed
22
+ # *in addition to* `Object` and `BasicObject`.
23
+ #
14
24
  # @example
15
25
  # # bad
16
26
  # class Employee < Person
@@ -57,6 +67,21 @@ module RuboCop
57
67
  # end
58
68
  # end
59
69
  #
70
+ # # good
71
+ # class ClassWithNoParent
72
+ # def initialize
73
+ # do_something
74
+ # end
75
+ # end
76
+ #
77
+ # @example AllowedParentClasses: [MyAbstractClass]
78
+ # # good
79
+ # class MyConcreteClass < MyAbstractClass
80
+ # def initialize
81
+ # do_something
82
+ # end
83
+ # end
84
+ #
60
85
  class MissingSuper < Base
61
86
  CONSTRUCTOR_MSG = 'Call `super` to initialize state of the parent class.'
62
87
  CALLBACK_MSG = 'Call `super` to invoke callback defined in the parent class.'
@@ -100,7 +125,7 @@ module RuboCop
100
125
  end
101
126
 
102
127
  def callback_method_def?(node)
103
- return unless CALLBACKS.include?(node.method_name)
128
+ return false unless CALLBACKS.include?(node.method_name)
104
129
 
105
130
  node.each_ancestor(:class, :sclass, :module).first
106
131
  end
@@ -113,16 +138,20 @@ module RuboCop
113
138
  if (block_node = node.each_ancestor(:block, :numblock).first)
114
139
  return false unless (super_class = class_new_block(block_node))
115
140
 
116
- !stateless_class?(super_class)
141
+ !allowed_class?(super_class)
117
142
  elsif (class_node = node.each_ancestor(:class).first)
118
- class_node.parent_class && !stateless_class?(class_node.parent_class)
143
+ class_node.parent_class && !allowed_class?(class_node.parent_class)
119
144
  else
120
145
  false
121
146
  end
122
147
  end
123
148
 
124
- def stateless_class?(node)
125
- STATELESS_CLASSES.include?(node.const_name)
149
+ def allowed_class?(node)
150
+ allowed_classes.include?(node.const_name)
151
+ end
152
+
153
+ def allowed_classes
154
+ @allowed_classes ||= STATELESS_CLASSES + cop_config.fetch('AllowedParentClasses', [])
126
155
  end
127
156
  end
128
157
  end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for mixed-case character ranges since they include likely unintended characters.
7
+ #
8
+ # Offenses are registered for regexp character classes like `/[A-z]/`
9
+ # as well as range objects like `('A'..'z')`.
10
+ #
11
+ # NOTE: Range objects cannot be autocorrected.
12
+ #
13
+ # @safety
14
+ # The cop autocorrects regexp character classes
15
+ # by replacing one character range with two: `A-z` becomes `A-Za-z`.
16
+ # In most cases this is probably what was originally intended
17
+ # but it changes the regexp to no longer match symbols it used to include.
18
+ # For this reason, this cop's autocorrect is unsafe (it will
19
+ # change the behavior of the code).
20
+ #
21
+ # @example
22
+ #
23
+ # # bad
24
+ # r = /[A-z]/
25
+ #
26
+ # # good
27
+ # r = /[A-Za-z]/
28
+ class MixedCaseRange < Base
29
+ extend AutoCorrector
30
+ include RangeHelp
31
+
32
+ MSG = 'Ranges from upper to lower case ASCII letters may include unintended ' \
33
+ 'characters. Instead of `A-z` (which also includes several symbols) ' \
34
+ 'specify each range individually: `A-Za-z` and individually specify any symbols.'
35
+ RANGES = [('a'..'z').freeze, ('A'..'Z').freeze].freeze
36
+
37
+ def on_irange(node)
38
+ return unless node.children.compact.all?(&:str_type?)
39
+
40
+ range_start, range_end = node.children
41
+
42
+ return if range_start.nil? || range_end.nil?
43
+
44
+ add_offense(node) if unsafe_range?(range_start.value, range_end.value)
45
+ end
46
+ alias on_erange on_irange
47
+
48
+ def on_regexp(node)
49
+ each_unsafe_regexp_range(node) do |loc|
50
+ add_offense(loc) do |corrector|
51
+ corrector.replace(loc, rewrite_regexp_range(loc.source))
52
+ end
53
+ end
54
+ end
55
+
56
+ def each_unsafe_regexp_range(node)
57
+ node.parsed_tree&.each_expression do |expr|
58
+ next if skip_expression?(expr)
59
+
60
+ range_pairs(expr).reject do |range_start, range_end|
61
+ next if skip_range?(range_start, range_end)
62
+
63
+ next unless unsafe_range?(range_start.text, range_end.text)
64
+
65
+ yield(build_source_range(range_start, range_end))
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def build_source_range(range_start, range_end)
73
+ range_between(range_start.expression.begin_pos, range_end.expression.end_pos)
74
+ end
75
+
76
+ def range_for(char)
77
+ RANGES.detect do |range|
78
+ range.include?(char)
79
+ end
80
+ end
81
+
82
+ def range_pairs(expr)
83
+ RuboCop::Cop::Utils::RegexpRanges.new(expr).pairs
84
+ end
85
+
86
+ def unsafe_range?(range_start, range_end)
87
+ return false if range_start.length != 1 || range_end.length != 1
88
+
89
+ range_for(range_start) != range_for(range_end)
90
+ end
91
+
92
+ def skip_expression?(expr)
93
+ !(expr.type == :set && expr.token == :character)
94
+ end
95
+
96
+ def skip_range?(range_start, range_end)
97
+ [range_start, range_end].any? do |bound|
98
+ bound.type == :escape
99
+ end
100
+ end
101
+
102
+ def rewrite_regexp_range(source)
103
+ open, close = source.split('-')
104
+ first = [open, range_for(open).end]
105
+ second = [range_for(close).begin, close]
106
+ "#{first.uniq.join('-')}#{second.uniq.join('-')}"
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -74,6 +74,7 @@ module RuboCop
74
74
  extend AutoCorrector
75
75
  include AllowedMethods
76
76
  include AllowedPattern
77
+ include IgnoredNode
77
78
 
78
79
  CONVERSION_METHOD_CLASS_MAPPING = {
79
80
  to_i: "#{Integer.name}(%<number_object>s, 10)",
@@ -116,7 +117,11 @@ module RuboCop
116
117
  corrected_method: correct_method(node, receiver)
117
118
  )
118
119
  add_offense(node, message: message) do |corrector|
120
+ next if part_of_ignored_node?(node)
121
+
119
122
  corrector.replace(node, correct_method(node, node.receiver))
123
+
124
+ ignore_node(node)
120
125
  end
121
126
  end
122
127
  end
@@ -3,7 +3,6 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- #
7
6
  # Checks the proper ordering of magic comments and whether
8
7
  # a magic comment is not placed before a shebang.
9
8
  #
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for redundant quantifiers inside Regexp literals.
7
+ #
8
+ # It is always allowed when interpolation is used in a regexp literal,
9
+ # because it's unknown what kind of string will be expanded as a result:
10
+ #
11
+ # [source,ruby]
12
+ # ----
13
+ # /(?:a*#{interpolation})?/x
14
+ # ----
15
+ #
16
+ # @example
17
+ # # bad
18
+ # /(?:x+)+/
19
+ #
20
+ # # good
21
+ # /(?:x)+/
22
+ #
23
+ # # good
24
+ # /(?:x+)/
25
+ #
26
+ # # bad
27
+ # /(?:x+)?/
28
+ #
29
+ # # good
30
+ # /(?:x)*/
31
+ #
32
+ # # good
33
+ # /(?:x*)/
34
+ class RedundantRegexpQuantifiers < Base
35
+ include RangeHelp
36
+ extend AutoCorrector
37
+
38
+ MSG_REDUNDANT_QUANTIFIER = 'Replace redundant quantifiers ' \
39
+ '`%<inner_quantifier>s` and `%<outer_quantifier>s` ' \
40
+ 'with a single `%<replacement>s`.'
41
+
42
+ def on_regexp(node)
43
+ return if node.interpolation?
44
+
45
+ each_redundantly_quantified_pair(node) do |group, child|
46
+ replacement = merged_quantifier(group, child)
47
+ add_offense(
48
+ quantifier_range(group, child),
49
+ message: message(group, child, replacement)
50
+ ) do |corrector|
51
+ # drop outer quantifier
52
+ corrector.replace(group.loc.quantifier, '')
53
+ # replace inner quantifier
54
+ corrector.replace(child.loc.quantifier, replacement)
55
+ end
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def each_redundantly_quantified_pair(node)
62
+ seen = Set.new
63
+ node.parsed_tree&.each_expression do |(expr)|
64
+ next if seen.include?(expr) || !redundant_group?(expr) || !mergeable_quantifier(expr)
65
+
66
+ expr.each_expression do |(subexp)|
67
+ seen << subexp
68
+ break unless redundantly_quantifiable?(subexp)
69
+
70
+ yield(expr, subexp) if mergeable_quantifier(subexp)
71
+ end
72
+ end
73
+ end
74
+
75
+ def redundant_group?(expr)
76
+ expr.is?(:passive, :group) && expr.count { |child| child.type != :free_space } == 1
77
+ end
78
+
79
+ def redundantly_quantifiable?(node)
80
+ redundant_group?(node) || character_set?(node) || node.terminal?
81
+ end
82
+
83
+ def character_set?(expr)
84
+ expr.is?(:character, :set)
85
+ end
86
+
87
+ def mergeable_quantifier(expr)
88
+ # Merging reluctant or possessive quantifiers would be more complex,
89
+ # and Ruby does not emit warnings for these cases.
90
+ return unless expr.quantifier&.greedy?
91
+
92
+ # normalize quantifiers, e.g. "{1,}" => "+"
93
+ case expr.quantity
94
+ when [0, -1]
95
+ '*'
96
+ when [0, 1]
97
+ '?'
98
+ when [1, -1]
99
+ '+'
100
+ end
101
+ end
102
+
103
+ def merged_quantifier(exp1, exp2)
104
+ quantifier1 = mergeable_quantifier(exp1)
105
+ quantifier2 = mergeable_quantifier(exp2)
106
+ if quantifier1 == quantifier2
107
+ # (?:a+)+ equals (?:a+) ; (?:a*)* equals (?:a*) ; # (?:a?)? equals (?:a?)
108
+ quantifier1
109
+ else
110
+ # (?:a+)*, (?:a+)?, (?:a*)+, (?:a*)?, (?:a?)+, (?:a?)* - all equal (?:a*)
111
+ '*'
112
+ end
113
+ end
114
+
115
+ def quantifier_range(group, child)
116
+ range_between(child.loc.quantifier.begin_pos, group.loc.quantifier.end_pos)
117
+ end
118
+
119
+ def message(group, child, replacement)
120
+ format(
121
+ MSG_REDUNDANT_QUANTIFIER,
122
+ inner_quantifier: child.quantifier.to_s,
123
+ outer_quantifier: group.quantifier.to_s,
124
+ replacement: replacement
125
+ )
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -49,6 +49,11 @@ module RuboCop
49
49
  (str #redundant_feature?))
50
50
  PATTERN
51
51
 
52
+ # @!method pp_const?(node)
53
+ def_node_matcher :pp_const?, <<~PATTERN
54
+ (const {nil? cbase} :PP)
55
+ PATTERN
56
+
52
57
  def on_send(node)
53
58
  return unless redundant_require_statement?(node)
54
59
 
@@ -72,16 +77,16 @@ module RuboCop
72
77
  feature_name == 'enumerator' ||
73
78
  (target_ruby_version >= 2.1 && feature_name == 'thread') ||
74
79
  (target_ruby_version >= 2.2 && RUBY_22_LOADED_FEATURES.include?(feature_name)) ||
75
- (target_ruby_version >= 2.5 && feature_name == 'pp' && !use_pretty_print_method?) ||
80
+ (target_ruby_version >= 2.5 && feature_name == 'pp' && !need_to_require_pp?) ||
76
81
  (target_ruby_version >= 2.7 && feature_name == 'ruby2_keywords') ||
77
82
  (target_ruby_version >= 3.1 && feature_name == 'fiber') ||
78
83
  (target_ruby_version >= 3.2 && feature_name == 'set')
79
84
  end
80
85
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
81
86
 
82
- def use_pretty_print_method?
87
+ def need_to_require_pp?
83
88
  processed_source.ast.each_descendant(:send).any? do |node|
84
- PRETTY_PRINT_METHODS.include?(node.method_name)
89
+ pp_const?(node.receiver) || PRETTY_PRINT_METHODS.include?(node.method_name)
85
90
  end
86
91
  end
87
92
  end
@@ -3,8 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- #
7
- # This cop checks for `send`, `public_send`, and `__send__` methods
6
+ # Checks for `send`, `public_send`, and `__send__` methods
8
7
  # when using mix-in.
9
8
  #
10
9
  # `include` and `prepend` methods were private methods until Ruby 2.0,
@@ -121,18 +121,12 @@ module RuboCop
121
121
 
122
122
  if rescued_exceptions.any?
123
123
  rescued_exceptions.each_with_object([]) do |exception, converted|
124
- # FIXME: Workaround `rubocop:disable` comment for JRuby.
125
- # https://github.com/jruby/jruby/issues/6642
126
- # rubocop:disable Style/RedundantBegin
127
- begin
128
- RuboCop::Util.silence_warnings do
129
- # Avoid printing deprecation warnings about constants
130
- converted << Kernel.const_get(exception.source)
131
- end
132
- rescue NameError
133
- converted << nil
124
+ RuboCop::Util.silence_warnings do
125
+ # Avoid printing deprecation warnings about constants
126
+ converted << Kernel.const_get(exception.source)
134
127
  end
135
- # rubocop:enable Style/RedundantBegin
128
+ rescue NameError
129
+ converted << nil
136
130
  end
137
131
  else
138
132
  # treat an empty `rescue` as `rescue StandardError`
@@ -117,7 +117,7 @@ module RuboCop
117
117
 
118
118
  def comment_between_rescue_and_end?(node)
119
119
  ancestor = node.each_ancestor(:kwbegin, :def, :defs, :block, :numblock).first
120
- return unless ancestor
120
+ return false unless ancestor
121
121
 
122
122
  end_line = ancestor.loc.end.line
123
123
  processed_source[node.first_line...end_line].any? { |line| comment_line?(line) }
@@ -124,7 +124,7 @@ module RuboCop
124
124
  source == value ||
125
125
  # `Symbol#inspect` uses double quotes, but allow single-quoted
126
126
  # symbols to work as well.
127
- source.tr("'", '"') == value
127
+ source.gsub('"', '\"').tr("'", '"') == value
128
128
  end
129
129
 
130
130
  def requires_quotes?(sym_node)
@@ -132,10 +132,12 @@ module RuboCop
132
132
  node.receiver.nil? && !node.arguments?
133
133
  end
134
134
 
135
+ # rubocop:disable Metrics/AbcSize
135
136
  def autocorrect(corrector, assignment)
136
137
  if assignment.exception_assignment?
137
138
  remove_exception_assignment_part(corrector, assignment.node)
138
- elsif assignment.multiple_assignment?
139
+ elsif assignment.multiple_assignment? || assignment.rest_assignment? ||
140
+ assignment.for_assignment?
139
141
  rename_variable_with_underscore(corrector, assignment.node)
140
142
  elsif assignment.operator_assignment?
141
143
  remove_trailing_character_from_operator(corrector, assignment.node)
@@ -146,6 +148,7 @@ module RuboCop
146
148
  remove_local_variable_assignment_part(corrector, assignment.node)
147
149
  end
148
150
  end
151
+ # rubocop:enable Metrics/AbcSize
149
152
 
150
153
  def remove_exception_assignment_part(corrector, node)
151
154
  corrector.remove(