metro 0.71.0 → 0.71.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,16 +7,44 @@
7
7
  *
8
8
  * @format
9
9
  */
10
+
11
+ /**
12
+ * Portions of this code are based on the Synchronous Cycle Collection
13
+ * algorithm described in:
14
+ *
15
+ * David F. Bacon and V. T. Rajan. 2001. Concurrent Cycle Collection in
16
+ * Reference Counted Systems. In Proceedings of the 15th European Conference on
17
+ * Object-Oriented Programming (ECOOP '01). Springer-Verlag, Berlin,
18
+ * Heidelberg, 207–235.
19
+ *
20
+ * Notable differences from the algorithm in the paper:
21
+ * 1. Our implementation uses the inverseDependencies set (which we already
22
+ * have to maintain) instead of a separate refcount variable. A module's
23
+ * reference count is equal to the size of its inverseDependencies set, plus
24
+ * 1 if it's an entry point of the graph.
25
+ * 2. We keep the "root buffer" (possibleCycleRoots) free of duplicates by
26
+ * making it a Set, instead of storing a "buffered" flag on each node.
27
+ * 3. On top of tracking edges between nodes, we also count references between
28
+ * nodes and entries in the importBundleNames set.
29
+ */
10
30
  "use strict";
11
31
 
12
- const nullthrows = require("nullthrows"); // Private state for the graph that persists between operations.
32
+ const invariant = require("invariant");
33
+
34
+ const nullthrows = require("nullthrows"); // TODO: Convert to a Flow enum
13
35
 
14
36
  function createGraph(options) {
15
37
  return {
16
38
  ...options,
17
39
  dependencies: new Map(),
18
40
  importBundleNames: new Set(),
19
- privateState: {},
41
+ privateState: {
42
+ gc: {
43
+ color: new Map(),
44
+ possibleCycleRoots: new Set(),
45
+ importBundleRefs: new Map(),
46
+ },
47
+ },
20
48
  };
21
49
  }
22
50
 
@@ -55,7 +83,7 @@ async function traverseDependencies(paths, graph, options) {
55
83
  added: new Set(),
56
84
  modified: new Set(),
57
85
  deleted: new Set(),
58
- inverseDependencies: new Map(),
86
+ earlyInverseDependencies: new Map(),
59
87
  };
60
88
  const internalOptions = getInternalOptions(options);
61
89
 
@@ -72,6 +100,7 @@ async function traverseDependencies(paths, graph, options) {
72
100
  }
73
101
  }
74
102
 
103
+ collectCycles(graph, delta);
75
104
  const added = new Map();
76
105
 
77
106
  for (const path of delta.added) {
@@ -99,9 +128,26 @@ async function initialTraverseDependencies(graph, options) {
99
128
  added: new Set(),
100
129
  modified: new Set(),
101
130
  deleted: new Set(),
102
- inverseDependencies: new Map(),
131
+ earlyInverseDependencies: new Map(),
103
132
  };
104
133
  const internalOptions = getInternalOptions(options);
134
+ invariant(
135
+ graph.dependencies.size === 0,
136
+ "initialTraverseDependencies called on nonempty graph"
137
+ );
138
+ invariant(
139
+ graph.importBundleNames.size === 0,
140
+ "initialTraverseDependencies called on nonempty graph"
141
+ );
142
+ graph.privateState.gc.color.clear();
143
+ graph.privateState.gc.possibleCycleRoots.clear();
144
+ graph.privateState.gc.importBundleRefs.clear();
145
+
146
+ for (const path of graph.entryPoints) {
147
+ // Each entry point implicitly has a refcount of 1, so mark them all black.
148
+ graph.privateState.gc.color.set(path, "black");
149
+ }
150
+
105
151
  await Promise.all(
106
152
  [...graph.entryPoints].map((path) =>
107
153
  traverseDependenciesForSingleFile(path, graph, delta, internalOptions)
@@ -135,264 +181,181 @@ async function processModule(path, graph, delta, options) {
135
181
  options
136
182
  );
137
183
  const previousModule = graph.dependencies.get(path) || {
138
- inverseDependencies: delta.inverseDependencies.get(path) || new Set(),
184
+ inverseDependencies: delta.earlyInverseDependencies.get(path) || new Set(),
139
185
  path,
140
186
  };
141
187
  const previousDependencies = previousModule.dependencies || new Map(); // Update the module information.
142
188
 
143
189
  const module = {
144
190
  ...previousModule,
145
- dependencies: new Map(),
191
+ dependencies: new Map(previousDependencies),
146
192
  getSource: result.getSource,
147
193
  output: result.output,
148
194
  };
149
- graph.dependencies.set(module.path, module);
150
-
151
- for (const [relativePath, dependency] of currentDependencies) {
152
- module.dependencies.set(relativePath, dependency);
153
- }
195
+ graph.dependencies.set(module.path, module); // Diff dependencies (1/2): remove dependencies that have changed or been removed.
154
196
 
155
- Array.from(previousDependencies.entries())
156
- .filter(
157
- ([relativePath, dependency]) =>
158
- !currentDependencies.has(relativePath) ||
159
- nullthrows(currentDependencies.get(relativePath)).absolutePath !==
160
- dependency.absolutePath
161
- )
162
- .forEach(([relativePath, dependency]) =>
163
- removeDependency(module, dependency.absolutePath, graph, delta, new Set())
164
- ); // Check all the module dependencies and start traversing the tree from each
165
- // added and removed dependency, to get all the modules that have to be added
166
- // and removed from the dependency graph.
167
-
168
- const promises = [];
197
+ for (const [relativePath, prevDependency] of previousDependencies) {
198
+ const curDependency = currentDependencies.get(relativePath);
169
199
 
170
- for (const [relativePath, dependency] of currentDependencies) {
171
- if (!options.shallow) {
172
- if (
173
- options.experimentalImportBundleSupport &&
174
- dependency.data.data.asyncType != null
175
- ) {
176
- graph.importBundleNames.add(dependency.absolutePath);
177
- } else if (
178
- !previousDependencies.has(relativePath) ||
179
- nullthrows(previousDependencies.get(relativePath)).absolutePath !==
180
- dependency.absolutePath
181
- ) {
182
- promises.push(
183
- addDependency(module, dependency.absolutePath, graph, delta, options)
184
- );
185
- }
200
+ if (
201
+ !curDependency ||
202
+ curDependency.absolutePath !== prevDependency.absolutePath ||
203
+ (options.experimentalImportBundleSupport &&
204
+ curDependency.data.data.asyncType !==
205
+ prevDependency.data.data.asyncType)
206
+ ) {
207
+ removeDependency(
208
+ module,
209
+ relativePath,
210
+ prevDependency,
211
+ graph,
212
+ delta,
213
+ options
214
+ );
186
215
  }
187
- }
188
-
189
- try {
190
- await Promise.all(promises);
191
- } catch (err) {
192
- // If there is an error, restore the previous dependency list.
193
- // This ensures we don't skip over them during the next traversal attempt.
194
- // $FlowFixMe[cannot-write]
195
- module.dependencies = previousDependencies;
196
- throw err;
197
- }
198
-
199
- return module;
200
- }
201
-
202
- async function addDependency(parentModule, path, graph, delta, options) {
203
- // The new dependency was already in the graph, we don't need to do anything.
204
- const existingModule = graph.dependencies.get(path);
205
-
206
- if (existingModule) {
207
- existingModule.inverseDependencies.add(parentModule.path);
208
- return;
209
- } // This module is being transformed at the moment in parallel, so we should
210
- // only mark its parent as an inverse dependency.
211
-
212
- const inverse = delta.inverseDependencies.get(path);
213
-
214
- if (inverse) {
215
- inverse.add(parentModule.path);
216
- return;
217
- }
216
+ } // Diff dependencies (2/2): add dependencies that have changed or been added.
218
217
 
219
- if (delta.deleted.has(path)) {
220
- // Mark the addition by clearing a prior deletion.
221
- delta.deleted.delete(path);
222
- } else {
223
- // Mark the addition in the added set.
224
- delta.added.add(path);
225
- delta.modified.delete(path);
226
- }
227
-
228
- delta.inverseDependencies.set(path, new Set([parentModule.path]));
229
- options.onDependencyAdd();
230
- const module = await processModule(path, graph, delta, options);
231
- graph.dependencies.set(module.path, module);
232
- module.inverseDependencies.add(parentModule.path);
233
- options.onDependencyAdded();
234
- }
235
- /**
236
- * Recursively look up `inverseDependencies` until it is empty,
237
- * returning a set of paths for the last module that does not have
238
- * `inverseDependencies`.
239
- */
240
-
241
- function getAllTopLevelInverseDependencies(
242
- inverseDependencies,
243
- graph,
244
- currModule,
245
- visited
246
- ) {
247
- if (visited.has(currModule)) {
248
- return new Set();
249
- }
218
+ const promises = [];
250
219
 
251
- visited.add(currModule);
220
+ for (const [relativePath, curDependency] of currentDependencies) {
221
+ const prevDependency = previousDependencies.get(relativePath);
252
222
 
253
- if (!inverseDependencies.size) {
254
- return new Set([currModule]);
223
+ if (
224
+ !prevDependency ||
225
+ prevDependency.absolutePath !== curDependency.absolutePath ||
226
+ (options.experimentalImportBundleSupport &&
227
+ prevDependency.data.data.asyncType !==
228
+ curDependency.data.data.asyncType)
229
+ ) {
230
+ promises.push(
231
+ addDependency(
232
+ module,
233
+ relativePath,
234
+ curDependency,
235
+ graph,
236
+ delta,
237
+ options
238
+ )
239
+ );
240
+ }
255
241
  }
256
242
 
257
- return Array.from(inverseDependencies)
258
- .filter((inverseDep) => graph.dependencies.has(inverseDep))
259
- .reduce((acc, inverseDep) => {
260
- const mod = graph.dependencies.get(inverseDep);
243
+ await Promise.all(promises); // Replace dependencies with the correctly-ordered version. As long as all
244
+ // the above promises have resolved, this will be the same map but without
245
+ // the added nondeterminism of promise resolution order. Because this
246
+ // assignment does not add or remove edges, it does NOT invalidate any of the
247
+ // garbage collection state.
248
+ // Catch obvious errors with a cheap assertion.
261
249
 
262
- if (!mod) {
263
- return acc;
264
- }
250
+ invariant(
251
+ module.dependencies.size === currentDependencies.size,
252
+ "Failed to add the correct dependencies"
253
+ ); // $FlowFixMe[cannot-write]
265
254
 
266
- getAllTopLevelInverseDependencies(
267
- mod.inverseDependencies,
268
- graph,
269
- inverseDep,
270
- visited
271
- ).forEach((x) => {
272
- acc.add(x);
273
- });
274
- return acc;
275
- }, new Set());
255
+ module.dependencies = currentDependencies;
256
+ return module;
276
257
  }
277
- /**
278
- * Given `inverseDependencies`, tracing back inverse dependencies to
279
- * see if it only leads back to `parentModule`.
280
- */
281
258
 
282
- function canSafelyRemoveFromParentModule(
283
- inverseDependencies,
259
+ async function addDependency(
284
260
  parentModule,
261
+ relativePath,
262
+ dependency,
285
263
  graph,
286
- canBeRemovedSafely,
287
- delta
264
+ delta,
265
+ options
288
266
  ) {
289
- const visited = new Set();
290
- const topInverseDependencies = getAllTopLevelInverseDependencies(
291
- inverseDependencies,
292
- graph,
293
- "", // current module name
294
- visited
295
- );
296
-
297
- if (!topInverseDependencies.size) {
298
- /**
299
- * This happens when parentModule and inverseDependencies have a circular dependency.
300
- * This will eventually become an empty set due to the `visited` Set being the
301
- * base case for the recursive call.
302
- */
303
- return true;
304
- }
267
+ const path = dependency.absolutePath; // The module may already exist, in which case we just need to update some
268
+ // bookkeeping instead of adding a new node to the graph.
269
+
270
+ let module = graph.dependencies.get(path);
271
+
272
+ if (options.shallow) {
273
+ // Don't add a node for the module if the graph is shallow (single-module).
274
+ } else if (
275
+ options.experimentalImportBundleSupport &&
276
+ dependency.data.data.asyncType != null
277
+ ) {
278
+ // Don't add a node for the module if we are traversing async dependencies
279
+ // lazily (and this is an async dependency). Instead, record it in
280
+ // importBundleNames.
281
+ incrementImportBundleReference(dependency, graph);
282
+ } else {
283
+ if (!module) {
284
+ // Add a new node to the graph.
285
+ const earlyInverseDependencies = delta.earlyInverseDependencies.get(path);
286
+
287
+ if (earlyInverseDependencies) {
288
+ // This module is being transformed at the moment in parallel, so we
289
+ // should only mark its parent as an inverse dependency.
290
+ earlyInverseDependencies.add(parentModule.path);
291
+ } else {
292
+ if (delta.deleted.has(path)) {
293
+ // Mark the addition by clearing a prior deletion.
294
+ delta.deleted.delete(path);
295
+ } else {
296
+ // Mark the addition in the added set.
297
+ delta.added.add(path);
298
+ delta.modified.delete(path);
299
+ }
300
+
301
+ delta.earlyInverseDependencies.set(path, new Set([parentModule.path]));
302
+ options.onDependencyAdd();
303
+ module = await processModule(path, graph, delta, options);
304
+ options.onDependencyAdded();
305
+ graph.dependencies.set(module.path, module);
306
+ }
307
+ }
305
308
 
306
- const undeletedInverseDependencies = Array.from(
307
- topInverseDependencies
308
- ).filter((x) => graph.dependencies.has(x));
309
- /**
310
- * We can only mark the `visited` Set of modules to be safely removable if
311
- * 1. We do not have top a level module to compare with parentModule.
312
- * This can happen when trying to see if we can safely remove from
313
- * a module that was deleted. This is why we filtered them out with `delta.deleted`
314
- * 2. We have one top module and it is parentModule
315
- */
316
-
317
- const canSafelyRemove =
318
- !undeletedInverseDependencies.length ||
319
- (undeletedInverseDependencies.length === 1 &&
320
- undeletedInverseDependencies[0] === parentModule);
321
-
322
- if (canSafelyRemove) {
323
- visited.forEach((mod) => {
324
- canBeRemovedSafely.add(mod);
325
- });
326
- }
309
+ if (module) {
310
+ // We either added a new node to the graph, or we're updating an existing one.
311
+ module.inverseDependencies.add(parentModule.path);
312
+ markModuleInUse(module, graph);
313
+ }
314
+ } // Always update the parent's dependency map.
315
+ // This means the parent's dependencies can get desynced from
316
+ // inverseDependencies and the other fields in the case of lazy edges.
317
+ // Not an optimal representation :(
327
318
 
328
- return canSafelyRemove;
319
+ parentModule.dependencies.set(relativePath, dependency);
329
320
  }
330
321
 
331
322
  function removeDependency(
332
323
  parentModule,
333
- absolutePath,
324
+ relativePath,
325
+ dependency,
334
326
  graph,
335
- delta, // We use `canBeRemovedSafely` set to keep track of visited
336
- // module(s) that we're sure can be removed. This will skip expensive
337
- // inverse dependency traversals.
338
- canBeRemovedSafely = new Set()
327
+ delta,
328
+ options
339
329
  ) {
330
+ parentModule.dependencies.delete(relativePath);
331
+ const { absolutePath } = dependency;
332
+
333
+ if (
334
+ options.experimentalImportBundleSupport &&
335
+ dependency.data.data.asyncType != null
336
+ ) {
337
+ decrementImportBundleReference(dependency, graph);
338
+ }
339
+
340
340
  const module = graph.dependencies.get(absolutePath);
341
341
 
342
342
  if (!module) {
343
343
  return;
344
344
  }
345
345
 
346
- module.inverseDependencies.delete(parentModule.path); // Even if there are modules still using parentModule, we want to ensure
347
- // there is no circular dependency. Thus, we check if it can be safely removed
348
- // by tracing back the inverseDependencies.
349
-
350
- if (!canBeRemovedSafely.has(module.path)) {
351
- if (
352
- module.inverseDependencies.size &&
353
- !canSafelyRemoveFromParentModule(
354
- module.inverseDependencies,
355
- module.path,
356
- graph,
357
- canBeRemovedSafely,
358
- delta
359
- )
360
- ) {
361
- return;
362
- }
363
- }
346
+ module.inverseDependencies.delete(parentModule.path);
364
347
 
365
- if (delta.added.has(module.path)) {
366
- // Mark the deletion by clearing a prior addition.
367
- delta.added.delete(module.path);
348
+ if (
349
+ module.inverseDependencies.size > 0 ||
350
+ graph.entryPoints.has(absolutePath)
351
+ ) {
352
+ // The reference count has decreased, but not to zero.
353
+ // NOTE: Each entry point implicitly has a refcount of 1.
354
+ markAsPossibleCycleRoot(module, graph);
368
355
  } else {
369
- // Mark the deletion in the deleted set.
370
- delta.deleted.add(module.path);
371
- delta.modified.delete(module.path);
372
- } // This module is not used anywhere else! We can clear it from the bundle.
373
- // Clean up all the state associated with this module in order to correctly
374
- // re-add it if we encounter it again.
375
-
376
- graph.dependencies.delete(module.path);
377
- delta.inverseDependencies.delete(module.path); // Now we need to iterate through the module dependencies in order to
378
- // clean up everything (we cannot read the module because it may have
379
- // been deleted).
380
-
381
- Array.from(module.dependencies.values())
382
- .filter(
383
- (dependency) =>
384
- graph.dependencies.has(dependency.absolutePath) &&
385
- dependency.absolutePath !== parentModule.path
386
- )
387
- .forEach((dependency) =>
388
- removeDependency(
389
- module,
390
- dependency.absolutePath,
391
- graph,
392
- delta,
393
- canBeRemovedSafely
394
- )
395
- );
356
+ // The reference count has decreased to zero.
357
+ releaseModule(module, graph, delta, options);
358
+ }
396
359
  }
397
360
 
398
361
  function resolveDependencies(parentPath, dependencies, options) {
@@ -472,6 +435,196 @@ function reorderDependencies(graph, module, orderedDependencies, options) {
472
435
  reorderDependencies(graph, childModule, orderedDependencies, options);
473
436
  });
474
437
  }
438
+ /** Garbage collection functions */
439
+ // Add an entry to importBundleNames (or increase the reference count of an existing one)
440
+
441
+ function incrementImportBundleReference(dependency, graph) {
442
+ var _graph$privateState$g;
443
+
444
+ const { absolutePath } = dependency;
445
+ graph.privateState.gc.importBundleRefs.set(
446
+ absolutePath,
447
+ ((_graph$privateState$g =
448
+ graph.privateState.gc.importBundleRefs.get(absolutePath)) !== null &&
449
+ _graph$privateState$g !== void 0
450
+ ? _graph$privateState$g
451
+ : 0) + 1
452
+ );
453
+ graph.importBundleNames.add(absolutePath);
454
+ } // Decrease the reference count of an entry in importBundleNames (and delete it if necessary)
455
+
456
+ function decrementImportBundleReference(dependency, graph) {
457
+ const { absolutePath } = dependency;
458
+ const prevRefCount = nullthrows(
459
+ graph.privateState.gc.importBundleRefs.get(absolutePath)
460
+ );
461
+ invariant(
462
+ prevRefCount > 0,
463
+ "experimentalImportBundleSupport: import bundle refcount not valid"
464
+ );
465
+ graph.privateState.gc.importBundleRefs.set(absolutePath, prevRefCount - 1);
466
+
467
+ if (prevRefCount === 1) {
468
+ graph.privateState.gc.importBundleRefs.delete(absolutePath);
469
+ graph.importBundleNames.delete(absolutePath);
470
+ }
471
+ } // Mark a module as in use (ref count >= 1)
472
+
473
+ function markModuleInUse(module, graph) {
474
+ graph.privateState.gc.color.set(module.path, "black");
475
+ } // Delete an unreachable module from the graph immediately, unless it's queued
476
+ // for later deletion as a potential cycle root. Delete the module's outbound
477
+ // edges.
478
+ // Called when the reference count of a module has reached 0.
479
+
480
+ function releaseModule(module, graph, delta, options) {
481
+ for (const [relativePath, dependency] of module.dependencies) {
482
+ removeDependency(module, relativePath, dependency, graph, delta, options);
483
+ }
484
+
485
+ graph.privateState.gc.color.set(module.path, "black");
486
+
487
+ if (!graph.privateState.gc.possibleCycleRoots.has(module.path)) {
488
+ freeModule(module, graph, delta);
489
+ }
490
+ } // Delete an unreachable module from the graph.
491
+
492
+ function freeModule(module, graph, delta) {
493
+ if (delta.added.has(module.path)) {
494
+ // Mark the deletion by clearing a prior addition.
495
+ delta.added.delete(module.path);
496
+ } else {
497
+ // Mark the deletion in the deleted set.
498
+ delta.deleted.add(module.path);
499
+ delta.modified.delete(module.path);
500
+ } // This module is not used anywhere else! We can clear it from the bundle.
501
+ // Clean up all the state associated with this module in order to correctly
502
+ // re-add it if we encounter it again.
503
+
504
+ graph.dependencies.delete(module.path);
505
+ delta.earlyInverseDependencies.delete(module.path);
506
+ graph.privateState.gc.possibleCycleRoots.delete(module.path);
507
+ graph.privateState.gc.color.delete(module.path);
508
+ } // Mark a module as a possible cycle root
509
+
510
+ function markAsPossibleCycleRoot(module, graph) {
511
+ if (nullthrows(graph.privateState.gc.color.get(module.path)) !== "purple") {
512
+ graph.privateState.gc.color.set(module.path, "purple");
513
+ graph.privateState.gc.possibleCycleRoots.add(module.path);
514
+ }
515
+ } // Collect any unreachable cycles in the graph.
516
+
517
+ function collectCycles(graph, delta) {
518
+ // Mark recursively from roots (trial deletion)
519
+ for (const path of graph.privateState.gc.possibleCycleRoots) {
520
+ const module = nullthrows(graph.dependencies.get(path));
521
+ const color = nullthrows(graph.privateState.gc.color.get(path));
522
+
523
+ if (color === "purple") {
524
+ markGray(module, graph);
525
+ } else {
526
+ graph.privateState.gc.possibleCycleRoots.delete(path);
527
+
528
+ if (
529
+ color === "black" &&
530
+ module.inverseDependencies.size === 0 &&
531
+ !graph.entryPoints.has(path)
532
+ ) {
533
+ freeModule(module, graph, delta);
534
+ }
535
+ }
536
+ } // Scan recursively from roots (undo unsuccessful trial deletions)
537
+
538
+ for (const path of graph.privateState.gc.possibleCycleRoots) {
539
+ const module = nullthrows(graph.dependencies.get(path));
540
+ scan(module, graph);
541
+ } // Collect recursively from roots (free unreachable cycles)
542
+
543
+ for (const path of graph.privateState.gc.possibleCycleRoots) {
544
+ graph.privateState.gc.possibleCycleRoots.delete(path);
545
+ const module = nullthrows(graph.dependencies.get(path));
546
+ collectWhite(module, graph, delta);
547
+ }
548
+ }
549
+
550
+ function markGray(module, graph) {
551
+ const color = nullthrows(graph.privateState.gc.color.get(module.path));
552
+
553
+ if (color !== "gray") {
554
+ graph.privateState.gc.color.set(module.path, "gray");
555
+
556
+ for (const dependency of module.dependencies.values()) {
557
+ const childModule = nullthrows(
558
+ graph.dependencies.get(dependency.absolutePath)
559
+ ); // The inverse dependency will be restored during the scan phase if this module remains live.
560
+
561
+ childModule.inverseDependencies.delete(module.path);
562
+ markGray(childModule, graph);
563
+ }
564
+ }
565
+ }
566
+
567
+ function scan(module, graph) {
568
+ const color = nullthrows(graph.privateState.gc.color.get(module.path));
569
+
570
+ if (color === "gray") {
571
+ if (
572
+ module.inverseDependencies.size > 0 ||
573
+ graph.entryPoints.has(module.path)
574
+ ) {
575
+ scanBlack(module, graph);
576
+ } else {
577
+ graph.privateState.gc.color.set(module.path, "white");
578
+
579
+ for (const dependency of module.dependencies.values()) {
580
+ const childModule = nullthrows(
581
+ graph.dependencies.get(dependency.absolutePath)
582
+ );
583
+ scan(childModule, graph);
584
+ }
585
+ }
586
+ }
587
+ }
588
+
589
+ function scanBlack(module, graph) {
590
+ graph.privateState.gc.color.set(module.path, "black");
591
+
592
+ for (const dependency of module.dependencies.values()) {
593
+ const childModule = nullthrows(
594
+ graph.dependencies.get(dependency.absolutePath)
595
+ ); // The inverse dependency must have been deleted during the mark phase.
596
+
597
+ childModule.inverseDependencies.add(module.path);
598
+ const childColor = nullthrows(
599
+ graph.privateState.gc.color.get(childModule.path)
600
+ );
601
+
602
+ if (childColor !== "black") {
603
+ scanBlack(childModule, graph);
604
+ }
605
+ }
606
+ }
607
+
608
+ function collectWhite(module, graph, delta) {
609
+ const color = nullthrows(graph.privateState.gc.color.get(module.path));
610
+
611
+ if (
612
+ color === "white" &&
613
+ !graph.privateState.gc.possibleCycleRoots.has(module.path)
614
+ ) {
615
+ graph.privateState.gc.color.set(module.path, "black");
616
+
617
+ for (const dependency of module.dependencies.values()) {
618
+ const childModule = nullthrows(
619
+ graph.dependencies.get(dependency.absolutePath)
620
+ );
621
+ collectWhite(childModule, graph, delta);
622
+ }
623
+
624
+ freeModule(module, graph, delta);
625
+ }
626
+ }
627
+ /** End of garbage collection functions */
475
628
 
476
629
  module.exports = {
477
630
  createGraph,