import { Group , QuantifiableElement } from "regexpp/ast" ; import { CleanRegexRule , createRuleListener , getDocUrl } from "../rules-util" ; import { areEqual } from "../ast-util" ; /**
 * Returns whether the given group is the top-level group of its pattern.
 *
 * A pattern with a top-level groups is of the form `/(?:...)/flags`.
 */ function isTopLevel ( group ) { const parent = group . parent ; if ( parent . type === "Alternative" && parent . elements . length === 1 ) { const parentParent = parent . parent ; if ( parentParent . type === "Pattern" && parentParent . alternatives . length === 1 ) { return true ; } } return false ; } export default { meta : { type : "suggestion" , docs : { description : "Disallow unnecessary non-capturing groups." , url : getDocUrl ( "no-unnecessary-group" ) , } , fixable : "code" , } , create ( context ) { const options = context . options [ 0 ] || { } ; const allowTop = ! ! options . allowTop ; return createRuleListener ( ( { visitAST , pattern , parseExpression , replaceElement } ) => { visitAST ( { onGroupEnter ( node ) { if ( allowTop && isTopLevel ( node ) ) { return ; } if ( pattern . raw === "(?:)" ) { return ; } const groupContent = node . raw . substr ( "(?:" . length , node . raw . length - "(?:)" . length ) ; if ( node . parent . type === "Alternative" && node . parent . elements . length === 1 ) { context . report ( { message : "Unnecessary non-capturing group." , ... replaceElement ( node , groupContent ) , } ) ; return ; } if ( node . alternatives . length !== 1 ) { return ; } const elements = node . alternatives [ 0 ] . elements ; const parent = node . parent ; if ( parent . type === "Quantifier" ) { if ( elements . length !== 1 ) { return ; } const type = elements [ 0 ] . type ; if ( type === "Assertion" || type === "Quantifier" ) { return ; } } const beforeGroup = pattern . raw . substr ( 0 , node . start - pattern . start ) ; const afterGroup = pattern . raw . substr ( node . end - pattern . start ) ; const withoutGroup = beforeGroup + groupContent + afterGroup ; const ast = parseExpression ( withoutGroup ) ; if ( ast ) { let equal ; if ( parent . type === "Alternative" ) { const parentIndex = parent . elements . indexOf ( node ) ; const oldElements = parent . elements ; const newElements = [ ... parent . elements . slice ( 0 , parentIndex ) , ... elements , ... parent . elements . slice ( parentIndex + 1 ) , ] ; parent . elements = newElements ; try { equal = areEqual ( pattern , ast ) ; } finally { parent . elements = oldElements ; } } else { parent . element = elements [ 0 ] as QuantifiableElement ; try { equal = areEqual ( pattern , ast ) ; } finally { parent . element = node ; } } if ( equal ) { context . report ( { message : "Unnecessary non-capturing group." , ... replaceElement ( node , groupContent ) , } ) ; } } } , } ) ; } ) ; } , } as CleanRegexRule ;