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