import { CleanRegexRule , createRuleListener , getDocUrl } from "../rules-util" ; import { areEqual , isEscapeSequence , isOctalEscapeSequence , negateCharacterSetRaw } from "../ast-util" ; const SPECIAL_CHARACTERS = new Set ( ".*+?|()[]{}^$/" ) ; export default { meta : { type : "suggestion" , docs : { description : "Disallow unnecessary character classes." , url : getDocUrl ( "no-unnecessary-character-class" ) , } , fixable : "code" , schema : [ { type : "object" , properties : { avoidEscape : { type : "boolean" , } , } , additionalProperties : false , } , ] , } , create ( context ) { const avoidEscape = ! ! ( context . options [ 0 ] || { } ) . avoidEscape ; return createRuleListener ( ( { visitAST , pattern , parseExpression , replaceElement } ) => { visitAST ( { onCharacterClassEnter ( node ) { if ( node . elements . length !== 1 ) { return ; } const element = node . elements [ 0 ] ; if ( element . type === "CharacterSet" ) { const set = node . negate ? negateCharacterSetRaw ( element ) : element . raw ; context . report ( { message : "Unnecessary character class." , ... replaceElement ( node , set ) , } ) ; } else if ( element . type === "Character" ) { if ( node . negate ) { return ; } if ( element . value > 0 && isOctalEscapeSequence ( element ) ) { return ; } if ( element . raw === "\\b" ) { if ( ! avoidEscape ) { context . report ( { message : "Unnecessary character class." , ... replaceElement ( node , "\\x08" ) , } ) ; } return ; } if ( SPECIAL_CHARACTERS . has ( element . raw ) ) { if ( ! avoidEscape ) { context . report ( { message : "Unnecessary character class." , ... replaceElement ( node , "\\" + element . raw ) , } ) ; } return ; } if ( isEscapeSequence ( element ) ) { if ( ! avoidEscape ) { context . report ( { message : "Unnecessary character class." , ... replaceElement ( node , element . raw ) , } ) ; } return ; } if ( /^\\[^kbBpP]$/ . test ( element . raw ) ) { context . report ( { message : "Unnecessary character class." , ... replaceElement ( node , element . raw ) , } ) ; return ; } const parent = node . parent ; const before = pattern . raw . substr ( 0 , node . start - pattern . start ) ; const after = pattern . raw . substr ( node . end - pattern . start ) ; const withoutCharacterClass = before + element . raw + after ; const ast = parseExpression ( withoutCharacterClass ) ; 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 ) , element , ... parent . elements . slice ( parentIndex + 1 ) , ] ; parent . elements = newElements ; try { equal = areEqual ( pattern , ast ) ; } finally { parent . elements = oldElements ; } } else { parent . element = element ; try { equal = areEqual ( pattern , ast ) ; } finally { parent . element = node ; } } if ( equal ) { context . report ( { message : "Unnecessary character class." , ... replaceElement ( node , element . raw ) , } ) ; } } } } , } ) ; } ) ; } , } as CleanRegexRule ;