import { AnyCharacterSet , CharacterClass , Flags , Node } from "regexpp/ast" ; import { CleanRegexRule , createRuleListener , getDocUrl } from "../rules-util" ; import { many , mention } from "../format" ; import { assertNever } from "../util" ; import { isMatchAll } from "../char-util" ; function removeDescendantNodes < T extends { node : Node ; } > ( nodes ) { return nodes . filter ( ( { node } ) => { return ! nodes . some ( ( { node : n } ) => n != node && n . start <= node . start && n . end >= node . end ) ; } ) ; } type Mode = "dot" | "dot-if-dotAll" | "char-class" ; const modes = [ "dot" , "dot-if-dotAll" , "char-class" ] ; export default { meta : { type : "suggestion" , docs : { description : "Use one character class consistently whenever all characters have to be matched." , url : getDocUrl ( "consistent-match-all-characters" ) , } , fixable : "code" , schema : [ { type : "object" , properties : { mode : { type : "string" , enum : modes , } , charClass : { type : "string" , } , } , additionalProperties : false , } , ] , } , create ( context ) { const options = context . options [ 0 ] || { } ; const mode = options . mode || "dot-if-dotAll" ; const charClass = options . charClass || "[\\s\\S]" ; function getReplacement ( flags ) { switch ( mode ) { case "dot" : return "." ; case "dot-if-dotAll" : if ( flags . dotAll ) { return "." ; } else { return charClass ; } case "char-class" : return charClass ; default : throw assertNever ( mode ) ; } } return createRuleListener ( ( { visitAST , pattern , flags , replaceElement , replaceLiteral } ) => { const replacement = getReplacement ( flags ) ; let nodesToReplace = [ ] ; visitAST ( { onCharacterClassEnter ( node ) { if ( node . raw !== replacement && isMatchAll ( node , flags ) ) { nodesToReplace . push ( { node , message : `Replace this character class with ${ mention ( replacement ) }.` , replacement , } ) ; } } , onCharacterSetEnter ( node ) { if ( "." !== replacement && node . kind === "any" && flags . dotAll ) { nodesToReplace . push ( { node , message : `Replace this dot with ${ mention ( replacement ) }.` , replacement , } ) ; } } , } ) ; nodesToReplace = removeDescendantNodes ( nodesToReplace ) ; if ( nodesToReplace . length === 0 ) { return ; } if ( ( replacement === "." ) === flags . dotAll ) { nodesToReplace . forEach ( ( { node , message , replacement } ) => { context . report ( { message : message , ... replaceElement ( node , replacement , { dependsOnFlags : true } ) , } ) ; } ) ; } else { let newFlags ; if ( replacement === "." ) { newFlags = flags . raw + "s" ; if ( flags . raw === [ ... flags . raw ] . sort ( ( a , b ) => a . charCodeAt ( 0 ) - b . charCodeAt ( 0 ) ) . join ( "" ) ) { newFlags = [ ... newFlags ] . sort ( ( a , b ) => a . charCodeAt ( 0 ) - b . charCodeAt ( 0 ) ) . join ( "" ) ; } } else { newFlags = flags . raw . replace ( /s/ , "" ) ; } nodesToReplace . sort ( ( a , b ) => a . node . start - b . node . start ) ; const oldPattern = pattern . raw ; let lastEndIndex = 0 ; let newPattern = "" ; for ( const { node , replacement } of nodesToReplace ) { newPattern += oldPattern . substr ( lastEndIndex , node . start ) ; newPattern += replacement ; lastEndIndex = node . end ; } newPattern += oldPattern . substr ( lastEndIndex ) ; const manyElements = many ( nodesToReplace . length , "element" ) ; context . report ( { message : `${ manyElements } in the pattern will be replaced with ${ mention ( replacement ) }` + ` and the s flag will be ${ replacement === "." ? "added" : "removed" }.` , ... replaceLiteral ( newPattern , newFlags ) , } ) ; } } ) ; } , } as CleanRegexRule ;