import { Quantifier , Node } from "regexpp/ast" ; import { mention , shorten } from "../format" ; import { CleanRegexRule , createRuleListener , getDocUrl } from "../rules-util" ; import { hasSomeAncestor , quantToString , Quant } from "../ast-util" ; function getCombinedQuant ( node , nested ) { if ( node . max === 0 || nested . max === 0 ) { return null ; } else if ( node . greedy === nested . greedy ) { const greedy = node . greedy ; const a = nested . min ; const b = nested . max ; const c = node . min ; const d = node . max ; const condition = b === Infinity && c === 0 ? a <= 1 : c === d || b * c + 1 >= a * ( c + 1 ) ; if ( condition ) { return { min : a * c , max : b * d , greedy , } ; } else { return null ; } } else { return null ; } } function getSimplifiedChildQuant ( parent , child ) { if ( parent . max === 0 || child . max === 0 ) { return null ; } else if ( parent . greedy !== child . greedy ) { return null ; } else { let min = child . min ; let max = child . max ; if ( min === 0 && parent . min === 0 ) { min = 1 ; } if ( parent . max === Infinity && ( min === 0 || min === 1 ) && max > 1 ) { max = 1 ; } return { min , max , greedy : child . greedy } ; } } export default { meta : { type : "suggestion" , docs : { description : "Disallow nested quantifiers that can be rewritten as one quantifier." , url : getDocUrl ( "no-trivially-nested-quantifier" ) , } , fixable : "code" , } , create ( context ) { return createRuleListener ( ( { visitAST , replaceElement } ) => { const ignore = new Set ( ) ; visitAST ( { onQuantifierEnter ( node ) { if ( hasSomeAncestor ( node , a => ignore . has ( a ) ) ) { return ; } if ( node . max === 0 ) { ignore . add ( node ) ; return ; } const element = node . element ; if ( element . type === "Group" && element . alternatives . length === 1 && element . alternatives [ 0 ] . elements . length === 1 ) { const nested = element . alternatives [ 0 ] . elements [ 0 ] ; if ( nested . type === "Quantifier" ) { const newQuant = getCombinedQuant ( node , nested ) ; if ( newQuant ) { const quantStr = quantToString ( newQuant ) ; const replacement = nested . element . raw + quantStr ; const messagePreview = shorten ( nested . element . raw , 20 , "end" ) + quantStr ; ignore . add ( node ) ; context . report ( { message : `The nested quantifiers can be rewritten as ${ mention ( messagePreview ) }.` , ... replaceElement ( node , replacement ) , } ) ; return ; } } } if ( element . type === "Group" || element . type === "CapturingGroup" ) { for ( const alternative of element . alternatives ) { const nested = alternative . elements [ 0 ] ; if ( ! ( alternative . elements . length === 1 && nested . type === "Quantifier" ) ) { continue ; } const newQuant = getSimplifiedChildQuant ( node , nested ) ; if ( ! newQuant || ( newQuant . min === nested . min && newQuant . max === nested . max ) ) { continue ; } ignore . add ( node ) ; if ( newQuant . min === 1 && newQuant . max === 1 ) { const replacement = nested . element . raw ; context . report ( { message : "The nested quantifier is unnecessary and can be replaced with its element." , ... replaceElement ( nested , replacement , { dependsOn : node } ) , } ) ; } else { const quantStr = quantToString ( newQuant ) ; const replacement = nested . element . raw + quantStr ; const messagePreview = shorten ( nested . element . raw , 20 , "end" ) + quantStr ; ignore . add ( node ) ; context . report ( { message : `The nested quantifiers can be rewritten as ${ mention ( messagePreview ) }.` , ... replaceElement ( nested , replacement , { dependsOn : node } ) , } ) ; } } } } , } ) ; } ) ; } , } as CleanRegexRule ;