@@ -9,7 +9,6 @@ const NO_MARKER = 0;
99const IN_PROGRESS_MARKER = 1 ;
1010const DONE_MARKER = 2 ;
1111const DONE_MAYBE_ROOT_CYCLE_MARKER = 3 ;
12- const DONE_AND_ROOT_MARKER = 4 ;
1312
1413/**
1514 * @template T
@@ -32,9 +31,9 @@ class Node {
3231 this . item = item ;
3332 /** @type {Nodes<T> } */
3433 this . dependencies = new Set ( ) ;
35- this . marker = NO_MARKER ;
36- /** @type { Cycle<T> | undefined } */
37- this . cycle = undefined ;
34+ /** @type { Cycle<T> } */
35+ this . cycle = new Cycle ( ) ;
36+ this . cycle . nodes . add ( this ) ;
3837 this . incoming = 0 ;
3938 }
4039}
@@ -46,6 +45,7 @@ class Cycle {
4645 constructor ( ) {
4746 /** @type {Nodes<T> } */
4847 this . nodes = new Set ( ) ;
48+ this . marker = NO_MARKER ;
4949 }
5050}
5151
@@ -83,11 +83,6 @@ module.exports = (items, getDependencies) => {
8383 }
8484 }
8585
86- // Set of current root modules
87- // items will be removed if a new reference to it has been found
88- /** @type {Nodes<T> } */
89- const roots = new Set ( ) ;
90-
9186 // Set of current cycles without references to it
9287 // cycles will be removed if a new reference to it has been found
9388 // that is not part of the cycle
@@ -96,12 +91,12 @@ module.exports = (items, getDependencies) => {
9691
9792 // For all non-marked nodes
9893 for ( const selectedNode of itemToNode . values ( ) ) {
99- if ( selectedNode . marker === NO_MARKER ) {
94+ if ( selectedNode . cycle . marker === NO_MARKER ) {
10095 // deep-walk all referenced modules
10196 // in a non-recursive way
10297
10398 // start by entering the selected node
104- selectedNode . marker = IN_PROGRESS_MARKER ;
99+ selectedNode . cycle . marker = IN_PROGRESS_MARKER ;
105100
106101 // keep a stack to avoid recursive walk
107102 /** @type {StackEntry<T>[] } */
@@ -122,97 +117,85 @@ module.exports = (items, getDependencies) => {
122117 const dependency =
123118 /** @type {Node<T> } */
124119 ( topOfStack . openEdges . pop ( ) ) ;
125- switch ( dependency . marker ) {
120+ const cycle = dependency . cycle ;
121+ switch ( cycle . marker ) {
126122 case NO_MARKER :
127123 // dependency has not be visited yet
128124 // mark it as in-progress and recurse
129125 stack . push ( {
130126 node : dependency ,
131127 openEdges : [ ...dependency . dependencies ]
132128 } ) ;
133- dependency . marker = IN_PROGRESS_MARKER ;
129+ cycle . marker = IN_PROGRESS_MARKER ;
134130 break ;
135131 case IN_PROGRESS_MARKER : {
136132 // It's a in-progress cycle
137- let cycle = dependency . cycle ;
138- if ( ! cycle ) {
139- cycle = new Cycle ( ) ;
140- cycle . nodes . add ( dependency ) ;
141- dependency . cycle = cycle ;
142- }
143133 // set cycle property for each node in the cycle
144134 // if nodes are already part of a cycle
145135 // we merge the cycles to a shared cycle
136+ /** @type {Set<Cycle<T>> } */
137+ const toMerge = new Set ( ) ;
146138 for (
147139 let i = stack . length - 1 ;
148- stack [ i ] . node !== dependency ;
140+ stack [ i ] . node . cycle !== cycle ;
149141 i --
150142 ) {
151- const node = stack [ i ] . node ;
152- if ( node . cycle ) {
153- if ( node . cycle !== cycle ) {
154- // merge cycles
155- for ( const cycleNode of node . cycle . nodes ) {
156- cycleNode . cycle = cycle ;
157- cycle . nodes . add ( cycleNode ) ;
158- }
159- }
160- } else {
161- node . cycle = cycle ;
162- cycle . nodes . add ( node ) ;
143+ toMerge . add ( stack [ i ] . node . cycle ) ;
144+ }
145+ for ( const cycleToMerge of toMerge ) {
146+ // merge cycles
147+ for ( const cycleNode of cycleToMerge . nodes ) {
148+ cycleNode . cycle = cycle ;
149+ cycle . nodes . add ( cycleNode ) ;
163150 }
164151 }
165152 // don't recurse into dependencies
166153 // these are already on the stack
167154 break ;
168155 }
169- case DONE_AND_ROOT_MARKER :
170- // This node has be visited yet and is currently a root node
171- // But as this is a new reference to the node
172- // it's not really a root
173- // so we have to convert it to a normal node
174- dependency . marker = DONE_MARKER ;
175- roots . delete ( dependency ) ;
176- break ;
177156 case DONE_MAYBE_ROOT_CYCLE_MARKER :
178157 // This node has be visited yet and
179158 // is maybe currently part of a completed root cycle
180159 // we found a new reference to the cycle
181160 // so it's not really a root cycle
182161 // remove the cycle from the root cycles
183162 // and convert it to a normal node
184- rootCycles . delete ( /** @type {Cycle<T> } */ ( dependency . cycle ) ) ;
185- dependency . marker = DONE_MARKER ;
163+ rootCycles . delete ( /** @type {Cycle<T> } */ ( cycle ) ) ;
164+ cycle . marker = DONE_MARKER ;
186165 break ;
187166 // DONE_MARKER: nothing to do, don't recurse into dependencies
188167 }
189168 } else {
190169 // All dependencies of the current node has been visited
191170 // we leave the node
192171 stack . pop ( ) ;
193- topOfStack . node . marker = DONE_MARKER ;
172+ // Only mark the cycle as done when we leave the outermost
173+ // node of the cycle on the stack
174+ if (
175+ stack . length === 0 ||
176+ topOfStack . node . cycle !== stack [ stack . length - 1 ] . node . cycle
177+ ) {
178+ topOfStack . node . cycle . marker = DONE_MARKER ;
179+ }
194180 }
195181 }
196182 const cycle = selectedNode . cycle ;
197- if ( cycle ) {
198- for ( const node of cycle . nodes ) {
199- node . marker = DONE_MAYBE_ROOT_CYCLE_MARKER ;
200- }
201- rootCycles . add ( cycle ) ;
202- } else {
203- selectedNode . marker = DONE_AND_ROOT_MARKER ;
204- roots . add ( selectedNode ) ;
205- }
183+ cycle . marker = DONE_MAYBE_ROOT_CYCLE_MARKER ;
184+ rootCycles . add ( cycle ) ;
206185 }
207186 }
208187
188+ // Set of root modules
189+ /** @type {T[] } */
190+ const roots = [ ] ;
191+
209192 // Extract roots from root cycles
210193 // We take the nodes with most incoming edges
211194 // inside of the cycle
212195 for ( const cycle of rootCycles ) {
213196 let max = 0 ;
214197 /** @type {Nodes<T> } */
215- const cycleRoots = new Set ( ) ;
198+ const cycleRoots = new Set ( cycle . nodes ) ;
216199 const nodes = cycle . nodes ;
217200 for ( const node of nodes ) {
218201 for ( const dep of node . dependencies ) {
@@ -228,14 +211,12 @@ module.exports = (items, getDependencies) => {
228211 }
229212 }
230213 for ( const cycleRoot of cycleRoots ) {
231- roots . add ( cycleRoot ) ;
214+ roots . push ( cycleRoot . item ) ;
232215 }
233216 }
234217
235218 // When roots were found, return them
236- if ( roots . size > 0 ) {
237- return Array . from ( roots , ( r ) => r . item ) ;
238- }
219+ if ( roots . length > 0 ) return roots ;
239220
240221 throw new Error ( "Implementation of findGraphRoots is broken" ) ;
241222} ;
0 commit comments