@@ -497,7 +497,6 @@ defineColorAlias('inverse', 'swapColors');
497497defineColorAlias ( 'inverse' , 'swapcolors' ) ;
498498defineColorAlias ( 'doubleunderline' , 'doubleUnderline' ) ;
499499
500- // TODO(BridgeAR): Add function style support for more complex styles.
501500// Don't use 'blue' not visible on cmd.exe
502501inspect . styles = ObjectAssign ( { __proto__ : null } , {
503502 special : 'cyan' ,
@@ -510,11 +509,264 @@ inspect.styles = ObjectAssign({ __proto__: null }, {
510509 symbol : 'green' ,
511510 date : 'magenta' ,
512511 // "name": intentionally not styling
513- // TODO(BridgeAR): Highlight regular expressions properly.
514- regexp : 'red' ,
512+ regexp : highlightRegExp ,
515513 module : 'underline' ,
516514} ) ;
517515
516+ // Define the palette for RegExp group depth highlighting. Can be changed by users.
517+ inspect . styles . regexp . colors = [ 'green' , 'red' , 'yellow' , 'cyan' , 'magenta' ] ;
518+
519+ const highlightRegExpColors = inspect . styles . regexp . colors . slice ( ) ;
520+
521+ function findSequence ( regexpString , i , start , end , write ) {
522+ // TODO(BridgeAR): Improve the output by using a different depth for the content.
523+ let seq = start ;
524+ i ++ ;
525+ while ( i < regexpString . length && regexpString [ i ] !== end ) {
526+ seq += regexpString [ i ++ ] ;
527+ }
528+ if ( i < regexpString . length ) {
529+ seq += end ;
530+ i ++ ;
531+ }
532+ return seq ;
533+ }
534+
535+ /**
536+ * Tokenize and colorize a JavaScript RegExp pattern per ECMAScript grammar.
537+ * This is a tolerant single-pass highlighter, not a validator.
538+ * It supports: groups (named/unnamed, lookaround), assertions, alternation,
539+ * quantifiers, escapes (incl. Unicode properties), character classes and
540+ * backreferences. Character class set-ops in /v are highlighted heuristically.
541+ * @param {string } regexpString
542+ * @returns {string }
543+ */
544+ function highlightRegExp ( regexpString ) {
545+ let out = '' ;
546+ let i = 0 ;
547+ let depth = 0 ;
548+ let inClass = false ;
549+
550+ // TODO(BridgeAR): Add group type tracking. That allows to increase the depth
551+ // in case the same type is next to each other.
552+ // let groupType = 0;
553+
554+ // Verify palette and update cache if user changed colors
555+ const paletteNames = highlightRegExp . colors ?. length > 0 ?
556+ highlightRegExp . colors :
557+ highlightRegExpColors ;
558+
559+ const palette = paletteNames . reduce ( ( acc , name ) => {
560+ const color = inspect . colors [ name ] ;
561+ if ( color ) acc . push ( [ `\u001b[${ color [ 0 ] } m` , `\u001b[${ color [ 1 ] } m` ] ) ;
562+ return acc ;
563+ } , [ ] ) ;
564+
565+ const write = ( str ) => {
566+ const idx = depth % palette . length ;
567+ const color = palette [ idx ] ;
568+ out += color [ 0 ] + str + color [ 1 ] ;
569+ return idx ;
570+ } ;
571+
572+ // Opening '/'. Input always starts with '/'
573+ write ( '/' ) ;
574+ depth ++ ;
575+ i = 1 ;
576+
577+ // Parse pattern until next unescaped '/'
578+ while ( i < regexpString . length ) {
579+ const ch = regexpString [ i ] ;
580+
581+ if ( inClass ) {
582+ if ( ch === '\\' ) {
583+ let seq = '\\' ;
584+ i ++ ;
585+ if ( i < regexpString . length ) {
586+ seq += regexpString [ i ++ ] ;
587+ if ( i < regexpString . length && seq [ 1 ] === 'u' && regexpString [ i ] === '{' ) {
588+ const str = findSequence ( regexpString , i , '{' , '}' ) ;
589+ seq += str ;
590+ i += str . length ;
591+ } else if ( seq [ 1 ] === 'x' ) {
592+ seq += regexpString . slice ( i , i + 2 ) ;
593+ i += 2 ;
594+ }
595+ }
596+ write ( seq ) ;
597+ } else if ( ch === ']' ) {
598+ depth -- ;
599+ write ( ']' ) ;
600+ i ++ ;
601+ inClass = false ;
602+ } else if ( ch === '-' && regexpString [ i - 1 ] !== '[' &&
603+ i + 1 < regexpString . length && regexpString [ i + 1 ] !== ']' ) {
604+ depth ++ ;
605+ write ( '-' ) ;
606+ depth -- ;
607+ i ++ ;
608+ } else {
609+ write ( ch ) ;
610+ i ++ ;
611+ }
612+ } else if ( ch === '[' ) {
613+ // Enter class
614+ write ( '[' ) ;
615+ depth ++ ;
616+ i ++ ;
617+ inClass = true ;
618+ } else if ( ch === '(' ) {
619+ write ( '(' ) ;
620+ depth ++ ;
621+ i ++ ;
622+ if ( i < regexpString . length && regexpString [ i ] === '?' ) {
623+ // Assertions and named groups
624+ i ++ ;
625+ const a = i < regexpString . length ? regexpString [ i ] : '' ;
626+ if ( a === ':' || a === '=' || a === '!' ) {
627+ depth -- ;
628+ write ( '?' ) ;
629+ write ( a ) ;
630+ depth ++ ;
631+ i ++ ;
632+ } else {
633+ const b = i + 1 < regexpString . length ? regexpString [ i + 1 ] : '' ;
634+ if ( a === '<' && ( b === '=' || b === '!' ) ) {
635+ depth -- ;
636+ write ( '?<' ) ;
637+ depth ++ ;
638+ write ( b ) ;
639+ i += 2 ;
640+ } else if ( a === '<' ) {
641+ // Named capture: write '?<name>' as a single colored token
642+ i ++ ; // consume '<'
643+ const start = i ;
644+ while ( i < regexpString . length && regexpString [ i ] !== '>' ) {
645+ i ++ ;
646+ }
647+ const name = regexpString . slice ( start , i ) ;
648+ if ( i < regexpString . length && regexpString [ i ] === '>' ) {
649+ depth -- ;
650+ write ( '?<' ) ;
651+ depth ++ ;
652+ write ( name ) ;
653+ depth -- ;
654+ write ( '>' ) ;
655+ depth ++ ;
656+ i ++ ;
657+ } else {
658+ depth -- ;
659+ write ( '?<' ) ;
660+ depth ++ ;
661+ write ( name ) ;
662+ }
663+ } else {
664+ write ( '?' ) ;
665+ }
666+ }
667+ }
668+ } else if ( ch === ')' ) {
669+ depth -- ;
670+ write ( ')' ) ;
671+ i ++ ;
672+ } else if ( ch === '\\' ) {
673+ let seq = '\\' ;
674+ i ++ ;
675+ if ( i < regexpString . length ) {
676+ seq += regexpString [ i ++ ] ;
677+ const next = seq [ 1 ] ;
678+ if ( i < regexpString . length ) {
679+ if ( next === 'u' && regexpString [ i ] === '{' ) {
680+ const str = findSequence ( regexpString , i , '{' , '}' ) ;
681+ seq += str ;
682+ i += str . length ;
683+ } else if ( next === 'x' ) {
684+ seq += regexpString . slice ( i , i + 2 ) ;
685+ i += 2 ;
686+ } else if ( next >= '0' && next <= '9' ) {
687+ while ( i < regexpString . length && regexpString [ i ] >= '0' && regexpString [ i ] <= '9' ) {
688+ seq += regexpString [ i ++ ] ;
689+ }
690+ } else if ( next === 'k' && regexpString [ i ] === '<' ) {
691+ const str = findSequence ( regexpString , i , '<' , '>' ) ;
692+ seq += str ;
693+ i += str . length ;
694+ } else if ( ( next === 'p' || next === 'P' ) && regexpString [ i ] === '{' ) {
695+ const str = findSequence ( regexpString , i , '{' , '}' ) ;
696+ seq += str ;
697+ i += str . length ;
698+ }
699+ }
700+ }
701+ depth ++ ;
702+ write ( seq ) ;
703+ depth -- ;
704+ } else if ( ch === '|' || ch === '+' || ch === '*' || ch === '?' || ch === ',' || ch === '^' || ch === '$' ) {
705+ depth += 3 ;
706+ write ( ch ) ;
707+ depth -= 3 ;
708+ i ++ ;
709+ } else if ( ch === '{' ) {
710+ write ( '{' ) ;
711+ depth ++ ;
712+ i ++ ;
713+ let digits = '' ;
714+ while ( i < regexpString . length && regexpString [ i ] >= '0' && regexpString [ i ] <= '9' ) {
715+ digits += regexpString [ i ++ ] ;
716+ }
717+ if ( digits ) {
718+ depth ++ ;
719+ write ( digits ) ;
720+ depth -- ;
721+ }
722+ if ( i < regexpString . length && regexpString [ i ] === ',' ) {
723+ write ( ',' ) ;
724+ i ++ ;
725+ }
726+ let digits2 = '' ;
727+ while ( i < regexpString . length && regexpString [ i ] >= '0' && regexpString [ i ] <= '9' ) {
728+ digits2 += regexpString [ i ++ ] ;
729+ }
730+ if ( digits2 ) {
731+ depth ++ ;
732+ write ( digits2 ) ;
733+ depth -- ;
734+ }
735+ if ( i < regexpString . length && regexpString [ i ] === '}' ) {
736+ depth -- ;
737+ write ( '}' ) ;
738+ i ++ ;
739+ }
740+ if ( i < regexpString . length && regexpString [ i ] === '?' ) {
741+ // depth++;
742+ write ( '?' ) ;
743+ // depth--;
744+ i ++ ;
745+ }
746+ } else if ( ch === '/' ) {
747+ // Stop at closing delimiter (unescaped, outside of character class)
748+ break ;
749+ } else {
750+ depth ++ ;
751+ write ( ch ) ;
752+ depth -- ;
753+ i ++ ;
754+ }
755+ }
756+
757+ // Closing delimiter and flags
758+ if ( i < regexpString . length && regexpString [ i ] === '/' ) {
759+ depth -- ;
760+ write ( '/' ) ;
761+ i ++ ;
762+ if ( i < regexpString . length ) {
763+ depth ++ ;
764+ write ( regexpString . slice ( i ) ) ;
765+ }
766+ }
767+ return out ;
768+ }
769+
518770function addQuotes ( str , quotes ) {
519771 if ( quotes === - 1 ) {
520772 return `"${ str } "` ;
@@ -601,8 +853,12 @@ function stylizeWithColor(str, styleType) {
601853 const style = inspect . styles [ styleType ] ;
602854 if ( style !== undefined ) {
603855 const color = inspect . colors [ style ] ;
604- if ( color !== undefined )
856+ if ( color !== undefined ) {
605857 return `\u001b[${ color [ 0 ] } m${ str } \u001b[${ color [ 1 ] } m` ;
858+ }
859+ if ( typeof style === 'function' ) {
860+ return style ( str ) ;
861+ }
606862 }
607863 return str ;
608864}
@@ -1038,9 +1294,10 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
10381294 const prefix = getPrefix ( constructor , tag , 'RegExp' ) ;
10391295 if ( prefix !== 'RegExp ' )
10401296 base = `${ prefix } ${ base } ` ;
1297+ base = ctx . stylize ( base , 'regexp' ) ;
10411298 if ( ( keys . length === 0 && protoProps === undefined ) ||
10421299 ( recurseTimes > ctx . depth && ctx . depth !== null ) ) {
1043- return ctx . stylize ( base , 'regexp' ) ;
1300+ return base ;
10441301 }
10451302 } else if ( isDate ( value ) ) {
10461303 // Make dates with properties first say the date
0 commit comments