@@ -13,18 +13,35 @@ type LoadJsConfigsResult =
1313 | { Error : string } ;
1414
1515function validateConfigExtends ( root : object ) : void {
16- const visited = new Set < object > ( ) ;
17- const stack = new Set < object > ( ) ;
16+ const visited = new WeakSet < object > ( ) ;
17+ const inStack = new WeakSet < object > ( ) ;
18+ const stackObjects : object [ ] = [ ] ;
19+ const stackPaths : string [ ] = [ ] ;
20+
21+ const formatCycleError = ( refPath : string , cycleStart : string , idx : number ) : string => {
22+ const cycle =
23+ idx === - 1
24+ ? `${ cycleStart } -> ${ cycleStart } `
25+ : [ ...stackPaths . slice ( idx ) , cycleStart ] . join ( " -> " ) ;
26+
27+ return (
28+ "`extends` contains a circular reference.\n\n" +
29+ `${ refPath } points back to ${ cycleStart } \n` +
30+ `Cycle: ${ cycle } `
31+ ) ;
32+ } ;
1833
19- const visit = ( config : object ) : void => {
34+ const visit = ( config : object , path : string ) : void => {
2035 if ( visited . has ( config ) ) return ;
21- visited . add ( config ) ;
22-
23- if ( stack . has ( config ) ) {
24- // Defensive: this should never happen because we check before recursing.
25- throw new Error ( "`extends` contains a circular reference." ) ;
36+ if ( inStack . has ( config ) ) {
37+ const idx = stackObjects . indexOf ( config ) ;
38+ const cycleStart = idx === - 1 ? "<unknown>" : stackPaths [ idx ] ;
39+ throw new Error ( formatCycleError ( path , cycleStart , idx ) ) ;
2640 }
27- stack . add ( config ) ;
41+
42+ inStack . add ( config ) ;
43+ stackObjects . push ( config ) ;
44+ stackPaths . push ( path ) ;
2845
2946 const maybeExtends = ( config as Record < string , unknown > ) . extends ;
3047 if ( maybeExtends !== undefined ) {
@@ -40,17 +57,25 @@ function validateConfigExtends(root: object): void {
4057 `\`extends[${ i } ]\` must be a config object (strings/paths are not supported).` ,
4158 ) ;
4259 }
43- if ( stack . has ( item ) ) {
44- throw new Error ( "`extends` contains a circular reference." ) ;
60+
61+ const itemPath = `${ path } .extends[${ i } ]` ;
62+ if ( inStack . has ( item ) ) {
63+ const idx = stackObjects . indexOf ( item ) ;
64+ const cycleStart = idx === - 1 ? "<unknown>" : stackPaths [ idx ] ;
65+ throw new Error ( formatCycleError ( itemPath , cycleStart , idx ) ) ;
4566 }
46- visit ( item ) ;
67+
68+ visit ( item , itemPath ) ;
4769 }
4870 }
4971
50- stack . delete ( config ) ;
72+ inStack . delete ( config ) ;
73+ stackObjects . pop ( ) ;
74+ stackPaths . pop ( ) ;
75+ visited . add ( config ) ;
5176 } ;
5277
53- visit ( root ) ;
78+ visit ( root , "<root>" ) ;
5479}
5580
5681/**
0 commit comments