class RuboCop::Cop::Lint::RedundantRegexpQuantifiers

/(?:x*)/
# good
/(?:x)*/
# good
/(?:x+)?/
# bad
/(?:x+)/
# good
/(?:x)+/
# good
/(?:x+)+/
# bad
@example
—-
/(?:a*#{interpolation})?/x
—-
[source,ruby]

because it’s unknown what kind of string will be expanded as a result:
It is always allowed when interpolation is used in a regexp literal,
Checks for redundant quantifiers inside ‘Regexp` literals.

def character_set?(expr)

def character_set?(expr)
  expr.is?(:character, :set)
end

def each_redundantly_quantified_pair(node)

def each_redundantly_quantified_pair(node)
  seen = Set.new
  node.parsed_tree&.each_expression do |(expr)|
    next if seen.include?(expr) || !redundant_group?(expr) || !mergeable_quantifier(expr)
    expr.each_expression do |(subexp)|
      seen << subexp
      break unless redundantly_quantifiable?(subexp)
      yield(expr, subexp) if mergeable_quantifier(subexp)
    end
  end
end

def mergeable_quantifier(expr)

def mergeable_quantifier(expr)
  # Merging reluctant or possessive quantifiers would be more complex,
  # and Ruby does not emit warnings for these cases.
  return unless expr.quantifier&.greedy?
  # normalize quantifiers, e.g. "{1,}" => "+"
  case expr.quantity
  when [0, -1]
    '*'
  when [0, 1]
    '?'
  when [1, -1]
    '+'
  end
end

def merged_quantifier(exp1, exp2)

def merged_quantifier(exp1, exp2)
  quantifier1 = mergeable_quantifier(exp1)
  quantifier2 = mergeable_quantifier(exp2)
  if quantifier1 == quantifier2
    # (?:a+)+ equals (?:a+) ; (?:a*)* equals (?:a*) ; # (?:a?)? equals (?:a?)
    quantifier1
  else
    # (?:a+)*, (?:a+)?, (?:a*)+, (?:a*)?, (?:a?)+, (?:a?)* - all equal (?:a*)
    '*'
  end
end

def message(group, child, replacement)

def message(group, child, replacement)
  format(
    MSG_REDUNDANT_QUANTIFIER,
    inner_quantifier: child.quantifier.to_s,
    outer_quantifier: group.quantifier.to_s,
    replacement: replacement
  )
end

def on_regexp(node)

def on_regexp(node)
  return if node.interpolation?
  each_redundantly_quantified_pair(node) do |group, child|
    replacement = merged_quantifier(group, child)
    add_offense(
      quantifier_range(group, child),
      message: message(group, child, replacement)
    ) do |corrector|
      # drop outer quantifier
      corrector.replace(group.loc.quantifier, '')
      # replace inner quantifier
      corrector.replace(child.loc.quantifier, replacement)
    end
  end
end

def quantifier_range(group, child)

def quantifier_range(group, child)
  range_between(child.loc.quantifier.begin_pos, group.loc.quantifier.end_pos)
end

def redundant_group?(expr)

def redundant_group?(expr)
  expr.is?(:passive, :group) && expr.count { |child| child.type != :free_space } == 1
end

def redundantly_quantifiable?(node)

def redundantly_quantifiable?(node)
  redundant_group?(node) || character_set?(node) || node.terminal?
end