@cplieger/deadset-ts 8.0.0-dev.14 → 8.0.0-dev.16

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.
Files changed (149) hide show
  1. package/dist/src/alias-chain.js +2 -2
  2. package/dist/src/analysis.js +43 -38
  3. package/dist/src/commands/read.js +6 -7
  4. package/dist/src/conformance-record.js +1 -1
  5. package/dist/src/consumers.js +4 -2
  6. package/dist/src/contract/config.generated.js +1 -1
  7. package/dist/src/contract/contract.generated.js +120 -2
  8. package/dist/src/dependencies.js +81 -49
  9. package/dist/src/entries/build-scripts.js +222 -0
  10. package/dist/src/{configuration-files.js → entries/configuration-files.js} +20 -58
  11. package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
  12. package/dist/src/entries/conventions.js +739 -0
  13. package/dist/src/{html-entries.js → entries/html-entries.js} +3 -3
  14. package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +2 -2
  15. package/dist/src/entries/run-directories.js +77 -0
  16. package/dist/src/entries/sources.js +144 -0
  17. package/dist/src/entries/static-module.js +669 -0
  18. package/dist/src/entries/test-runners.js +684 -0
  19. package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
  20. package/dist/src/extensions.js +37 -0
  21. package/dist/src/facts.js +38 -0
  22. package/dist/src/file-facts.js +1 -1
  23. package/dist/src/findings/emitters.js +5 -8
  24. package/dist/src/findings/self-check.js +36 -20
  25. package/dist/src/findings-pass.js +5 -6
  26. package/dist/src/framework-lifecycle.js +1 -1
  27. package/dist/src/fs/manifest.js +15 -20
  28. package/dist/src/generated-file.js +172 -29
  29. package/dist/src/graph.js +2 -5
  30. package/dist/src/index.js +1 -1
  31. package/dist/src/injection-container.js +1 -1
  32. package/dist/src/inventory/commonjs.js +507 -0
  33. package/dist/src/inventory/enumerate.js +167 -0
  34. package/dist/src/inventory/syntax.js +236 -0
  35. package/dist/src/inventory.js +305 -705
  36. package/dist/src/javascript-file.js +26 -11
  37. package/dist/src/load/classify.js +28 -9
  38. package/dist/src/load/diagnostics.js +8 -1
  39. package/dist/src/load/discover.js +16 -8
  40. package/dist/src/load/package-scope.js +56 -0
  41. package/dist/src/load/plan.js +2 -2
  42. package/dist/src/load/synthesize.js +5 -5
  43. package/dist/src/load/workspace-manifests.js +1 -1
  44. package/dist/src/matrix.js +6 -13
  45. package/dist/src/outside-tests.js +1 -1
  46. package/dist/src/paths.js +16 -0
  47. package/dist/src/platform-calls.js +78 -0
  48. package/dist/src/platform-symbols.js +140 -0
  49. package/dist/src/position.js +41 -0
  50. package/dist/src/ref.js +11 -20
  51. package/dist/src/references.js +281 -50
  52. package/dist/src/resolve.js +1 -1
  53. package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +4 -4
  54. package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
  55. package/dist/src/{manifest.js → roots/manifest.js} +4 -4
  56. package/dist/src/roots/published-reach.js +412 -0
  57. package/dist/src/roots/root-set.js +489 -0
  58. package/dist/src/roots/unseen-loads.js +208 -0
  59. package/dist/src/serialization-contract.js +1 -1
  60. package/dist/src/session.js +3 -0
  61. package/dist/src/source-files.js +5 -9
  62. package/dist/src/suppress-inline.js +33 -81
  63. package/dist/src/suppress.js +30 -32
  64. package/dist/src/test-support.js +4 -2
  65. package/dist/src/verbs/analyze.js +13 -1
  66. package/dist/src/verbs/describe.js +5 -3
  67. package/jsr.json +1 -1
  68. package/package.json +3 -3
  69. package/src/alias-chain.ts +2 -2
  70. package/src/analysis.ts +77 -53
  71. package/src/automatic-types.ts +1 -1
  72. package/src/commands/read.ts +6 -8
  73. package/src/conformance-record.ts +1 -1
  74. package/src/consumers.ts +5 -1
  75. package/src/contract/config.generated.ts +1 -1
  76. package/src/contract/contract.generated.ts +139 -2
  77. package/src/dependencies.ts +98 -50
  78. package/src/entries/build-scripts.ts +292 -0
  79. package/src/{configuration-files.ts → entries/configuration-files.ts} +23 -75
  80. package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
  81. package/src/{conventions.ts → entries/conventions.ts} +347 -522
  82. package/src/{html-entries.ts → entries/html-entries.ts} +5 -5
  83. package/src/{mdx-entries.ts → entries/mdx-entries.ts} +4 -4
  84. package/src/entries/run-directories.ts +108 -0
  85. package/src/entries/sources.ts +266 -0
  86. package/src/entries/static-module.ts +857 -0
  87. package/src/entries/test-runners.ts +868 -0
  88. package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
  89. package/src/exempt.ts +3 -2
  90. package/src/extensions.ts +45 -0
  91. package/src/facts.ts +44 -0
  92. package/src/file-facts.ts +2 -2
  93. package/src/findings/emitters.ts +14 -10
  94. package/src/findings/self-check.ts +41 -22
  95. package/src/findings/visibility-narrowing.ts +1 -1
  96. package/src/findings-pass.ts +5 -12
  97. package/src/framework-lifecycle.ts +1 -1
  98. package/src/fs/manifest.ts +25 -21
  99. package/src/generated-file.ts +240 -36
  100. package/src/graph.ts +3 -6
  101. package/src/index.ts +1 -1
  102. package/src/injection-container.ts +1 -1
  103. package/src/inventory/commonjs.ts +696 -0
  104. package/src/inventory/enumerate.ts +203 -0
  105. package/src/inventory/syntax.ts +311 -0
  106. package/src/inventory.ts +424 -878
  107. package/src/javascript-file.ts +29 -12
  108. package/src/load/classify.ts +49 -13
  109. package/src/load/diagnostics.ts +11 -1
  110. package/src/load/discover.ts +28 -12
  111. package/src/load/package-scope.ts +74 -0
  112. package/src/load/plan.ts +11 -2
  113. package/src/load/synthesize.ts +6 -6
  114. package/src/load/test-globals.ts +1 -1
  115. package/src/load/workspace-manifests.ts +1 -1
  116. package/src/load/workspace-run.ts +1 -1
  117. package/src/matrix.ts +7 -14
  118. package/src/outside-tests.ts +1 -1
  119. package/src/paths.ts +18 -0
  120. package/src/platform-calls.ts +119 -0
  121. package/src/platform-symbols.ts +205 -0
  122. package/src/position.ts +42 -0
  123. package/src/ref.ts +11 -20
  124. package/src/references.ts +383 -73
  125. package/src/report.ts +1 -1
  126. package/src/resolve.ts +1 -1
  127. package/src/{configured-declarations.ts → roots/configured-declarations.ts} +7 -7
  128. package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
  129. package/src/{manifest.ts → roots/manifest.ts} +5 -5
  130. package/src/roots/published-reach.ts +481 -0
  131. package/src/{roots.ts → roots/root-set.ts} +203 -630
  132. package/src/roots/unseen-loads.ts +307 -0
  133. package/src/serialization-contract.ts +1 -1
  134. package/src/session.ts +17 -1
  135. package/src/source-files.ts +5 -10
  136. package/src/suppress-inline.ts +53 -89
  137. package/src/suppress.ts +48 -33
  138. package/src/test-support.ts +8 -3
  139. package/src/verbs/analyze.ts +17 -4
  140. package/src/verbs/describe.ts +5 -2
  141. package/src/verbs/explain.ts +1 -1
  142. package/dist/src/build-scripts.js +0 -418
  143. package/dist/src/conventions.js +0 -907
  144. package/dist/src/entry-points.js +0 -513
  145. package/dist/src/roots.js +0 -865
  146. package/dist/src/unseen-loads.js +0 -100
  147. package/src/build-scripts.ts +0 -536
  148. package/src/entry-points.ts +0 -661
  149. package/src/unseen-loads.ts +0 -134
package/src/references.ts CHANGED
@@ -32,26 +32,23 @@ import {
32
32
  isSpreadAssignment,
33
33
  isSpreadElement,
34
34
  isStringLiteral,
35
+ isStringLiteralLikeNode,
35
36
  isVoidExpression,
36
37
  SyntaxKind,
38
+ type Identifier,
37
39
  type Node,
38
40
  type ParameterDeclaration,
39
41
  type SourceFile,
42
+ type StringLiteralLikeNode,
40
43
  } from "@typescript/native/ast";
41
44
  import { parenthesesRemoved } from "./unwrap.ts";
42
45
  import type { Symbol as TSSymbol } from "@typescript/native/sync";
43
46
  import { aliasChains, type ChainTarget } from "./alias-chain.ts";
44
47
  import { contextualUses, type FlowAt, type LiteralAt } from "./contextual-uses.ts";
45
48
  import { destructuring, propertyReadsOf, type PropertyRead } from "./destructuring.ts";
46
- import { specifierCandidates } from "./configuration-files.ts";
49
+ import { specifierCandidates } from "./entries/configuration-files.ts";
47
50
  import { namespaceUses } from "./namespace-objects.ts";
48
- import {
49
- declarationsByName,
50
- isParameterProperty,
51
- nodeKey,
52
- type Inventory,
53
- type InventorySymbol,
54
- } from "./inventory.ts";
51
+ import { isParameterProperty, nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
55
52
  import { ambientGlobalInterfaces, augmentationReferences } from "./augmentations.ts";
56
53
  import { elementAccessReferences } from "./element-access.ts";
57
54
  import { indexedAccessReferences } from "./indexed-access.ts";
@@ -59,12 +56,23 @@ import { keyofComparisonReferences } from "./keyof-comparisons.ts";
59
56
  import { unimplementedCalleeReads } from "./unimplemented-callees.ts";
60
57
  import { moduleDeclarationReferences } from "./module-declarations.ts";
61
58
  import { overrideReferences } from "./overrides.ts";
62
- import { dirnamePath, queriedModulePath, relativePath, resolvePath } from "./paths.ts";
59
+ import {
60
+ dirnamePath,
61
+ queriedModulePath,
62
+ relativePath,
63
+ relativeURLPath,
64
+ resolvePath,
65
+ } from "./paths.ts";
63
66
  import { byPosition, isComponentFile, renderPosition, type Position } from "./position.ts";
64
67
  import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED, type Answer } from "./query.ts";
65
68
  import type { ProjectView } from "./session.ts";
69
+ import { sourceFilesOf, type SourceFiles } from "./source-files.ts";
66
70
  import { typeParameterReads } from "./type-parameter-reads.ts";
67
71
  import { typeQueryAliases } from "./type-query-alias.ts";
72
+ import { callsPlatform, isPlatformGlobal, startsThread } from "./platform-symbols.ts";
73
+ import { writtenName } from "./platform-calls.ts";
74
+ import type { RunDirectories } from "./entries/run-directories.ts";
75
+ import { computedLoadsOf, isLoadCall, type LoadCall } from "./roots/unseen-loads.ts";
68
76
  import {
69
77
  computedKeyNames,
70
78
  iterationReferences,
@@ -77,20 +85,12 @@ import { bytewise } from "./text/compare.ts";
77
85
  import { globExpression } from "./text/glob.ts";
78
86
 
79
87
  /**
80
- * Every use one project's own files make of the project's own declarations.
81
- *
82
- * The pass is one local walk per file and one batched resolution per file, never a
83
- * query per symbol: a per-symbol reference query is the shape whose answers depend on
84
- * the order the symbols are asked about, so no answer here depends on that order. The
85
- * tree is on this side of the client boundary and the resolution is not, which is why a
86
- * file's name nodes are gathered whole and handed over together.
87
- *
88
- * What the pass records and what it does not: a reference carries its use, its
89
- * position, the declaration that encloses it and the classification of the file that
90
- * made it, and every place that imports one of the project's own modules is an
91
- * evaluation of that module's file. It carries no mode. Which of the references a run
92
- * counts is the reading the sweep makes of this set, so the mode is one value the run
93
- * decides once and this pass never sees it.
88
+ * Every use one project's own files make of the project's own declarations: one local walk
89
+ * and one batched resolution per file, never a reference query per symbol, whose answers
90
+ * would depend on the order the symbols are asked in. A reference carries its use, its
91
+ * position, the declaration enclosing it and whether a test file made it; every import of
92
+ * one of the project's modules is an evaluation of that module's file. It carries no mode:
93
+ * which references a run counts is the sweep's reading of this set.
94
94
  */
95
95
 
96
96
  /**
@@ -190,6 +190,12 @@ export interface ReferenceCost {
190
190
  readonly shorthandLookups: number;
191
191
  /** Per-symbol lookups that step from one alias to what it names, one per alias. */
192
192
  readonly aliasSteps: number;
193
+ /**
194
+ * Per-node and per-symbol lookups that decide whether a load call is the platform's
195
+ * `require`, a construction its `URL`, or a `Worker` Node's, for a callee the pass's own
196
+ * chains leave open.
197
+ */
198
+ readonly platformLookups: number;
193
199
  /**
194
200
  * Batched type lookups for the object patterns: per file, the patterns and the values
195
201
  * assigned to destructuring targets, divided by the batch cap, rounded up.
@@ -225,6 +231,11 @@ export interface References {
225
231
  * classification that marks the references the file makes.
226
232
  */
227
233
  readonly testFilePaths: readonly string[];
234
+ /**
235
+ * Each load by a computed path a production file of the target writes whose files the
236
+ * analysis cannot name (confidence.26), in file then source order; none for a consumer.
237
+ */
238
+ readonly unseenLoads: readonly Position[];
228
239
  readonly cost: ReferenceCost;
229
240
  }
230
241
 
@@ -252,6 +263,8 @@ export interface ReferenceOptions {
252
263
  readonly consumer?: ConsumerSide;
253
264
  /** The client's running request count, where the caller measures the client. */
254
265
  readonly requestCount?: () => number;
266
+ /** The directories a thread's path and a glob's root are read against. */
267
+ readonly directories: RunDirectories;
255
268
  }
256
269
 
257
270
  /**
@@ -459,6 +472,10 @@ interface Evaluation {
459
472
  /** The module specifier, whose symbol is the module evaluated. */
460
473
  readonly node: Node;
461
474
  readonly from: string;
475
+ /** The name a `require()` call is written with; {@link callsPlatform} owns which one loads. */
476
+ readonly require?: Identifier;
477
+ /** Whether the call's value is used, which reads the loaded file's export table. */
478
+ readonly whole?: boolean;
462
479
  }
463
480
 
464
481
  /**
@@ -468,11 +485,29 @@ interface Evaluation {
468
485
  */
469
486
  interface ModuleURL {
470
487
  readonly specifier: string;
471
- /** The `URL` the constructor names, which must name no declaration of the target. */
472
- readonly constructor: Node | undefined;
488
+ /** The name the constructor is written with; {@link callsPlatform} owns which one is `URL`. */
489
+ readonly constructor: Identifier | undefined;
473
490
  readonly at: Node;
474
491
  }
475
492
 
493
+ /** One module URL, and the declaration whose code builds it. */
494
+ interface ModuleURLAt extends ModuleURL {
495
+ readonly from: string;
496
+ }
497
+
498
+ /** One worker a call starts by a literal address (references-ts.2), and the declaration whose code starts it. */
499
+ interface WorkerSite {
500
+ readonly address: StringLiteralLikeNode;
501
+ /** The name the constructor or `navigator` is written with. */
502
+ readonly constructor: Identifier;
503
+ /** The namespace the constructor is read through, as `threads` in `threads.Worker`. */
504
+ readonly holder: Identifier | undefined;
505
+ /** Whether an options literal writes an `eval` other than `false`, which makes a thread's address code. */
506
+ readonly evaluated: boolean;
507
+ readonly at: Node;
508
+ readonly from: string;
509
+ }
510
+
476
511
  /** One use of a module namespace object that may read every export of the module. */
477
512
  interface WholeRead {
478
513
  /** The module specifier, whose symbol is the module read. */
@@ -509,7 +544,9 @@ interface FileSites {
509
544
  readonly links: readonly Link[];
510
545
  readonly evaluations: readonly Evaluation[];
511
546
  readonly wholes: readonly WholeRead[];
512
- readonly urls: readonly ModuleURL[];
547
+ readonly urls: readonly ModuleURLAt[];
548
+ readonly workers: readonly WorkerSite[];
549
+ readonly loadCalls: readonly LoadCall[];
513
550
  readonly decorated: readonly Decorated[];
514
551
  readonly parameterWrites: readonly ParameterWrite[];
515
552
  readonly keyed: readonly KeyedAccess[];
@@ -528,26 +565,24 @@ function isImportMetaURL(node: Node): boolean {
528
565
 
529
566
  /** The module URL one node builds, where it builds one. */
530
567
  function moduleURLOf(node: Node): ModuleURL | undefined {
531
- const relative = (argument: Node | undefined): string | undefined => {
568
+ const literal = (argument: Node | undefined): string | undefined => {
532
569
  const at = argument === undefined ? undefined : parenthesesRemoved(argument);
533
- return at !== undefined &&
534
- (isStringLiteral(at) || isNoSubstitutionTemplateLiteral(at)) &&
535
- /^\.\.?\//u.test(at.text)
536
- ? at.text
537
- : undefined;
570
+ return isLiteralText(at) ? at.text : undefined;
538
571
  };
539
- if (isNewExpression(node) && isIdentifier(node.expression) && node.expression.text === "URL") {
572
+ const constructor = isNewExpression(node) ? writtenName(node.expression) : undefined;
573
+ if (isNewExpression(node) && constructor !== undefined) {
540
574
  const [reference, base] = node.arguments ?? [];
541
- const specifier = relative(reference);
575
+ const specifier = literal(reference);
542
576
  return specifier !== undefined && base !== undefined && isImportMetaURL(base)
543
- ? { specifier, constructor: node.expression, at: node }
577
+ ? { specifier, constructor, at: node }
544
578
  : undefined;
545
579
  }
546
580
  if (isCallExpression(node)) {
547
581
  const callee = parenthesesRemoved(node.expression);
548
- const specifier = relative(node.arguments[0]);
582
+ const specifier = literal(node.arguments[0]);
549
583
  if (
550
584
  specifier !== undefined &&
585
+ /^\.\.?\//u.test(specifier) &&
551
586
  isPropertyAccessExpression(callee) &&
552
587
  isMetaProperty(callee.expression) &&
553
588
  callee.expression.keywordToken === SyntaxKind.ImportKeyword &&
@@ -559,18 +594,125 @@ function moduleURLOf(node: Node): ModuleURL | undefined {
559
594
  return undefined;
560
595
  }
561
596
 
597
+ /** The constructors whose first argument addresses a worker. */
598
+ const WORKER_CONSTRUCTORS: ReadonlySet<string> = new Set(["Worker", "SharedWorker"]);
599
+
600
+ /** Whether an options argument is an object literal whose `eval` member is written as anything but `false`. */
601
+ function evaluatesCode(options: Node | undefined): boolean {
602
+ const at = options === undefined ? undefined : parenthesesRemoved(options);
603
+ return (
604
+ at !== undefined &&
605
+ isObjectLiteralExpression(at) &&
606
+ at.properties.some(
607
+ (property) =>
608
+ (isShorthandPropertyAssignment(property) &&
609
+ isIdentifier(property.name) &&
610
+ property.name.text === "eval") ||
611
+ (isPropertyAssignment(property) &&
612
+ (isIdentifier(property.name) || isStringLiteral(property.name)) &&
613
+ property.name.text === "eval" &&
614
+ parenthesesRemoved(property.initializer).kind !== SyntaxKind.FalseKeyword),
615
+ )
616
+ );
617
+ }
618
+
619
+ /** The worker one node starts by a literal address, where it starts one. */
620
+ function workerOf(node: Node): Omit<WorkerSite, "from"> | undefined {
621
+ if (isNewExpression(node)) {
622
+ const constructor = writtenName(node.expression);
623
+ const [address, options] = node.arguments ?? [];
624
+ const literal = address === undefined ? undefined : parenthesesRemoved(address);
625
+ // A thread's constructor may be imported under another name, whose path is relative.
626
+ if (
627
+ constructor === undefined ||
628
+ !isLiteralText(literal) ||
629
+ (!WORKER_CONSTRUCTORS.has(constructor.text) && !/^\.\.?\//u.test(literal.text))
630
+ ) {
631
+ return undefined;
632
+ }
633
+ const callee = parenthesesRemoved(node.expression);
634
+ const holder = isPropertyAccessExpression(callee)
635
+ ? parenthesesRemoved(callee.expression)
636
+ : undefined;
637
+ return {
638
+ address: literal,
639
+ constructor,
640
+ holder: holder !== undefined && isIdentifier(holder) ? holder : undefined,
641
+ evaluated: evaluatesCode(options),
642
+ at: node,
643
+ };
644
+ }
645
+ if (!isCallExpression(node)) {
646
+ return undefined;
647
+ }
648
+ const callee = parenthesesRemoved(node.expression);
649
+ const registry =
650
+ isPropertyAccessExpression(callee) && callee.name.text === "register"
651
+ ? parenthesesRemoved(callee.expression)
652
+ : undefined;
653
+ const navigator =
654
+ registry !== undefined &&
655
+ isPropertyAccessExpression(registry) &&
656
+ registry.name.text === "serviceWorker"
657
+ ? writtenName(registry.expression)
658
+ : undefined;
659
+ const [address] = node.arguments;
660
+ const literal = address === undefined ? undefined : parenthesesRemoved(address);
661
+ return navigator?.text === "navigator" && isLiteralText(literal)
662
+ ? { address: literal, constructor: navigator, holder: undefined, evaluated: false, at: node }
663
+ : undefined;
664
+ }
665
+
666
+ /** The file of the target a relative specifier written in `file` names, by its source or output name. */
667
+ function fileNamed(
668
+ file: SourceFile,
669
+ specifier: string,
670
+ fileIdByName: ReadonlyMap<string, string>,
671
+ ): string | undefined {
672
+ return specifierCandidates(dirnamePath(file.fileName), specifier)
673
+ .map((path) => fileIdByName.get(path))
674
+ .find((id) => id !== undefined);
675
+ }
676
+
562
677
  /** Whether one node is a string literal or a template literal with no substitution. */
563
- function isLiteralText(node: Node | undefined): node is Node {
678
+ function isLiteralText(node: Node | undefined): node is StringLiteralLikeNode {
564
679
  return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
565
680
  }
566
681
 
567
682
  /**
568
683
  * The module specifier of the module one node evaluates, if it evaluates one: an import
569
684
  * declaration, an import-equals declaration of an external module, an export
570
- * declaration that names a module, and an `import()` call whose argument is literal
571
- * text. A type-only form evaluates nothing. An `import()` of a computed argument names
572
- * no module this pass can know.
685
+ * declaration that names a module, an `import()` call whose argument is literal text, and
686
+ * a call that may be a `require()` of literal text: one written as the name `require`, or
687
+ * one of a relative specifier through another name. A type-only form evaluates nothing.
688
+ * An `import()` of a computed argument names no module this pass can know.
573
689
  */
690
+ function evaluationOf(node: Node): Omit<Evaluation, "from"> | undefined {
691
+ if (isCallExpression(node) && node.expression.kind !== SyntaxKind.ImportKeyword) {
692
+ const [argument] = node.arguments;
693
+ const require = writtenName(node.expression);
694
+ if (
695
+ require === undefined ||
696
+ !isLiteralText(argument) ||
697
+ (require.text !== "require" && !/^\.\.?\//u.test(argument.text))
698
+ ) {
699
+ return undefined;
700
+ }
701
+ return { node: argument, require, whole: !discarded(node) };
702
+ }
703
+ const specifier = evaluatedBy(node);
704
+ return specifier === undefined ? undefined : { node: specifier };
705
+ }
706
+
707
+ /** Whether an expression's value is thrown away: it is a statement of its own, or `void`'s operand. */
708
+ function discarded(expression: Node): boolean {
709
+ let at = expression.parent;
710
+ while (isParenthesizedExpression(at)) {
711
+ at = at.parent;
712
+ }
713
+ return isExpressionStatement(at) || isVoidExpression(at);
714
+ }
715
+
574
716
  function evaluatedBy(node: Node): Node | undefined {
575
717
  if (isImportDeclaration(node)) {
576
718
  return node.importClause?.phaseModifier === SyntaxKind.TypeKeyword
@@ -698,7 +840,9 @@ function sitesOf(
698
840
  const links: Link[] = [];
699
841
  const evaluations: Evaluation[] = [];
700
842
  const wholes: WholeRead[] = [];
701
- const urls: ModuleURL[] = [];
843
+ const urls: ModuleURLAt[] = [];
844
+ const workers: WorkerSite[] = [];
845
+ const loadCalls: LoadCall[] = [];
702
846
  const decorated: Decorated[] = [];
703
847
  const parameterWrites: ParameterWrite[] = [];
704
848
  const namespaces = namespaceUses(file);
@@ -775,13 +919,20 @@ function sitesOf(
775
919
  reads.push({ ...read, from: enclosing });
776
920
  }
777
921
  links.push(...linksOf(file, node, declarations, enclosing));
778
- const evaluated = evaluatedBy(node);
922
+ const evaluated = evaluationOf(node);
779
923
  if (evaluated !== undefined) {
780
- evaluations.push({ node: evaluated, from: enclosing });
924
+ evaluations.push({ ...evaluated, from: enclosing });
781
925
  }
782
926
  const url = moduleURLOf(node);
783
927
  if (url !== undefined) {
784
- urls.push(url);
928
+ urls.push({ ...url, from: enclosing });
929
+ }
930
+ const worker = workerOf(node);
931
+ if (worker !== undefined) {
932
+ workers.push({ ...worker, from: enclosing });
933
+ }
934
+ if (isLoadCall(node)) {
935
+ loadCalls.push({ call: node, from: enclosing });
785
936
  }
786
937
  const held = declarations.get(nodeKey(file, node));
787
938
  const outer = enclosing;
@@ -815,6 +966,8 @@ function sitesOf(
815
966
  evaluations,
816
967
  wholes,
817
968
  urls,
969
+ workers,
970
+ loadCalls,
818
971
  decorated,
819
972
  parameterWrites,
820
973
  keyed,
@@ -1005,20 +1158,12 @@ function aloneIn(
1005
1158
  }
1006
1159
 
1007
1160
  /**
1008
- * Every reference one project's own files make to the project's own declarations, in
1009
- * position order, and the files the test-file rules classified.
1010
- *
1011
- * `held` is the inventory of the same project, whose declaration map is what a resolved
1012
- * symbol is looked up in; `targetRoot` is the absolute path every position is rendered
1013
- * against, which is the root the inventory was read against. A pass over a consumer
1014
- * walks the consumer's files instead and renders against its root, as
1015
- * {@link ReferenceOptions.consumer} states.
1016
- *
1017
- * The order is by the referencing position, then by the declaration named, the
1018
- * declaration referencing it and the use. That is a total order because one name
1019
- * references one declaration once from one declaration, while a chain of aliases reaches
1020
- * several declarations at one position and a bare star re-export's specifier both reads
1021
- * and evaluates the module it names.
1161
+ * Every reference one project's own files make to its own declarations, and the files the
1162
+ * test-file rules classified. `held` is the same project's inventory, where a resolved symbol
1163
+ * is looked up; positions render against `targetRoot`, or a consumer's root as
1164
+ * {@link ReferenceOptions.consumer} states. The order is by position, then the declaration
1165
+ * named, the referencing declaration and the use: an alias chain or a bare star re-export
1166
+ * names several at one position, and one name references one declaration once.
1022
1167
  */
1023
1168
  export function references<Brand>(
1024
1169
  project: ProjectView<Brand>,
@@ -1037,6 +1182,10 @@ export function references<Brand>(
1037
1182
  let batched = 0;
1038
1183
  let fileBatches = 0;
1039
1184
  let residueFallbacks = 0;
1185
+ let platformLookups = 0;
1186
+ const asked = (): void => {
1187
+ platformLookups += 1;
1188
+ };
1040
1189
  let shorthandLookups = 0;
1041
1190
 
1042
1191
  const consumer = options.consumer;
@@ -1079,6 +1228,8 @@ export function references<Brand>(
1079
1228
  return moduleBodies.get(moduleId) ?? [];
1080
1229
  };
1081
1230
 
1231
+ let ownFiles: SourceFiles | undefined;
1232
+ const unseenLoads: Position[] = [];
1082
1233
  const exportsCache = new Map<number, readonly ChainTarget[]>();
1083
1234
  /**
1084
1235
  * Every declaration one module exports, each re-export walked along its chain. A module
@@ -1132,7 +1283,7 @@ export function references<Brand>(
1132
1283
  * live. A name a namespace import binds may be a use of any file.
1133
1284
  */
1134
1285
  const named = (node: Node): readonly string[] => {
1135
- const byName = declarationsByName(held);
1286
+ const byName = held.byName;
1136
1287
  const text = node.getText();
1137
1288
  const imported = importedNames(node.getSourceFile()).get(text) ?? [];
1138
1289
  return [
@@ -1161,7 +1312,7 @@ export function references<Brand>(
1161
1312
  aliased.set(
1162
1313
  id,
1163
1314
  symbol === UNANSWERED
1164
- ? (declarationsByName(held).get(alias.name) ?? []).map((one) => ({
1315
+ ? (held.byName.get(alias.name) ?? []).map((one) => ({
1165
1316
  id: one,
1166
1317
  stepped: true,
1167
1318
  guessed: true,
@@ -1212,6 +1363,10 @@ export function references<Brand>(
1212
1363
  const fileIdByName = new Map<string, string>(
1213
1364
  walked.filter((one) => fileIds.has(one.fileId)).map((one) => [one.file.fileName, one.fileId]),
1214
1365
  );
1366
+ const loadable = walked
1367
+ .filter((one) => fileIds.has(one.fileId))
1368
+ .map(({ file, fileId }) => ({ file, fileId }));
1369
+ const loadableById = new Map(loadable.map((one) => [one.fileId, one.file]));
1215
1370
  const contextual = contextualUses(
1216
1371
  project,
1217
1372
  held,
@@ -1311,11 +1466,16 @@ export function references<Brand>(
1311
1466
  evaluations,
1312
1467
  wholes,
1313
1468
  urls,
1469
+ workers,
1470
+ loadCalls,
1314
1471
  decorated,
1315
1472
  parameterWrites,
1316
1473
  keyed,
1317
1474
  } of walked) {
1318
1475
  const test = testFile.get(file) === true;
1476
+ const loads = computedLoadsOf(file, loadCalls, loadable, (fileName) =>
1477
+ options.directories.bundlerRootOf(fileName),
1478
+ );
1319
1479
  // A component file's markup may use any binding its blocks declare at the top level.
1320
1480
  const markup = isComponentFile(file);
1321
1481
  const bindings = markup ? importBindings(file) : [];
@@ -1355,10 +1515,24 @@ export function references<Brand>(
1355
1515
  ...new Set([
1356
1516
  ...uses.filter((site) => site.shorthand === undefined).map((site) => site.node),
1357
1517
  ...links.map((link) => link.node),
1358
- ...evaluations.map((evaluation) => evaluation.node),
1518
+ ...evaluations.flatMap((evaluation) =>
1519
+ evaluation.require === undefined
1520
+ ? [evaluation.node]
1521
+ : [
1522
+ evaluation.require,
1523
+ ...(evaluation.require.text === "require" ? [evaluation.node] : []),
1524
+ ],
1525
+ ),
1359
1526
  ...wholes.map((whole) => whole.specifier),
1360
1527
  ...urls.flatMap((url) => url.constructor ?? []),
1528
+ ...workers.flatMap((worker) =>
1529
+ worker.holder === undefined ? [worker.constructor] : [worker.constructor, worker.holder],
1530
+ ),
1361
1531
  ...bindings.map((binding) => binding.node),
1532
+ ...loads.flatMap((load) => [
1533
+ ...(load.require === undefined ? [] : [load.require]),
1534
+ ...(load.asset ? [] : load.files.map((loaded) => loaded.file)),
1535
+ ]),
1362
1536
  ]),
1363
1537
  ];
1364
1538
  const answered = new Map<Node, Answer<TSSymbol>>();
@@ -1400,7 +1574,29 @@ export function references<Brand>(
1400
1574
  }
1401
1575
  }
1402
1576
 
1403
- for (const evaluation of evaluations) {
1577
+ const loading = evaluations.filter(
1578
+ (one) =>
1579
+ one.require === undefined ||
1580
+ callsPlatform(project, "require", one.require, answered.get(one.require), asked),
1581
+ );
1582
+ // A script declares no module, so the checker names nothing for a specifier of one;
1583
+ // the compiler's module resolution still names its file.
1584
+ const unnamed = loading.flatMap(({ node }) =>
1585
+ answered.get(node) === undefined &&
1586
+ isStringLiteralLikeNode(node) &&
1587
+ queriedFile(file, node, fileIdByName) === undefined
1588
+ ? [node]
1589
+ : [],
1590
+ );
1591
+ const resolved = project.resolvedFileNames(unnamed);
1592
+ const scripts = new Map<Node, string | undefined>(
1593
+ unnamed.map((node, at) => {
1594
+ const name = resolved[at];
1595
+ return [node, name === undefined ? undefined : fileIdByName.get(name)];
1596
+ }),
1597
+ );
1598
+ const wholeReads: { readonly evaluation: Evaluation; readonly fileId: string }[] = [];
1599
+ for (const evaluation of loading) {
1404
1600
  const queried = queriedFile(file, evaluation.node, fileIdByName);
1405
1601
  if (queried !== undefined) {
1406
1602
  found.push({
@@ -1413,6 +1609,29 @@ export function references<Brand>(
1413
1609
  });
1414
1610
  }
1415
1611
  const module = answered.get(evaluation.node);
1612
+ // Node's require resolves a relative specifier the program records no resolution for,
1613
+ // as in a TypeScript file, by its own extension search.
1614
+ const script =
1615
+ scripts.get(evaluation.node) ??
1616
+ (evaluation.require !== undefined &&
1617
+ module === undefined &&
1618
+ isLiteralText(evaluation.node) &&
1619
+ /^\.\.?\//u.test(evaluation.node.text)
1620
+ ? fileNamed(file, evaluation.node.text, fileIdByName)
1621
+ : undefined);
1622
+ if (script !== undefined && evaluation.whole === true && module === undefined) {
1623
+ wholeReads.push({ evaluation, fileId: script });
1624
+ }
1625
+ if (script !== undefined) {
1626
+ found.push({
1627
+ from: evaluation.from,
1628
+ to: script,
1629
+ position: renderPosition(file, root, evaluation.node.getStart()),
1630
+ use: "evaluation",
1631
+ resolution: "syntax",
1632
+ test,
1633
+ });
1634
+ }
1416
1635
  if (module === undefined) {
1417
1636
  continue;
1418
1637
  }
@@ -1433,23 +1652,43 @@ export function references<Brand>(
1433
1652
  }
1434
1653
  }
1435
1654
 
1655
+ // A required file the checker names no module for is read whole where the call's value
1656
+ // is used: the value is the file's exports object, which this pass does not follow.
1657
+ const required = wholeReads.flatMap((one) => {
1658
+ const loaded = loadableById.get(one.fileId);
1659
+ return loaded === undefined ? [] : [{ ...one, loaded }];
1660
+ });
1661
+ const modules = project.symbolsAt(
1662
+ required.map((one) => project.handle(one.loaded)),
1663
+ cap,
1664
+ );
1665
+ required.forEach(({ evaluation }, at) => {
1666
+ const module = modules[at];
1667
+ const position = renderPosition(file, root, evaluation.node.getStart());
1668
+ for (const target of module === undefined ? [] : exportsRead(module)) {
1669
+ found.push({
1670
+ from: evaluation.from,
1671
+ to: target.id,
1672
+ position,
1673
+ use: "read",
1674
+ resolution: target.guessed ? "by-name" : "batch",
1675
+ test,
1676
+ });
1677
+ }
1678
+ });
1679
+
1436
1680
  for (const url of urls) {
1437
- const constructor = url.constructor === undefined ? undefined : answered.get(url.constructor);
1438
- // An augmentation of the global `URL` merges into it, which keeps the
1439
- // library's declaration; a `URL` the target declares has no other.
1440
1681
  if (
1441
- typeof constructor === "object" &&
1442
- constructor.declarations.length > 0 &&
1443
- constructor.declarations.every((handle) => ownPaths.has(handle.path))
1682
+ url.constructor !== undefined &&
1683
+ !callsPlatform(project, "URL", url.constructor, answered.get(url.constructor), asked)
1444
1684
  ) {
1445
1685
  continue;
1446
1686
  }
1447
- const loaded = specifierCandidates(dirnamePath(file.fileName), url.specifier)
1448
- .map((path) => fileIdByName.get(path))
1449
- .find((id) => id !== undefined);
1687
+ const path = relativeURLPath(dirnamePath(file.fileName), url.specifier);
1688
+ const loaded = path === undefined ? undefined : fileNamed(file, path, fileIdByName);
1450
1689
  if (loaded !== undefined) {
1451
1690
  found.push({
1452
- from: fileId,
1691
+ from: url.from,
1453
1692
  to: loaded,
1454
1693
  position: renderPosition(file, root, url.at.getStart()),
1455
1694
  use: "evaluation",
@@ -1459,6 +1698,75 @@ export function references<Brand>(
1459
1698
  }
1460
1699
  }
1461
1700
 
1701
+ for (const worker of workers) {
1702
+ const symbol = answered.get(worker.constructor);
1703
+ const text = worker.address.text;
1704
+ let path: string | undefined;
1705
+ if (startsThread(project, symbol, worker.holder && answered.get(worker.holder), asked)) {
1706
+ path =
1707
+ worker.evaluated || !/^\.\.?\//u.test(text)
1708
+ ? undefined
1709
+ : resolvePath(options.directories.workingDirectoryOf(file.fileName), text);
1710
+ } else if (
1711
+ (WORKER_CONSTRUCTORS.has(worker.constructor.text) ||
1712
+ worker.constructor.text === "navigator") &&
1713
+ isPlatformGlobal(project, symbol)
1714
+ ) {
1715
+ path = relativeURLPath(dirnamePath(file.fileName), text);
1716
+ }
1717
+ ownFiles ??= sourceFilesOf(project, targetRoot);
1718
+ for (const started of path === undefined ? [] : ownFiles.named(path)) {
1719
+ const to = declaredAt(started);
1720
+ if (to !== undefined && fileIds.has(to)) {
1721
+ found.push({
1722
+ from: worker.from,
1723
+ to,
1724
+ position: renderPosition(file, root, worker.at.getStart()),
1725
+ use: "evaluation",
1726
+ resolution: "syntax",
1727
+ test,
1728
+ });
1729
+ }
1730
+ }
1731
+ }
1732
+
1733
+ // A computed load imports each file it loads where the call is written, reading what
1734
+ // the file exports unless it reads the file as text or a URL; callsPlatform owns which
1735
+ // call is a `require`.
1736
+ for (const load of loads) {
1737
+ if (
1738
+ load.require !== undefined &&
1739
+ !callsPlatform(project, "require", load.require, answered.get(load.require), asked)
1740
+ ) {
1741
+ continue;
1742
+ }
1743
+ const position = renderPosition(file, root, load.call.getStart());
1744
+ if (!load.resolved && !test && consumer === undefined && !file.isDeclarationFile) {
1745
+ unseenLoads.push(position);
1746
+ }
1747
+ for (const loaded of load.files) {
1748
+ found.push({
1749
+ from: load.eager ? fileId : load.from,
1750
+ to: loaded.fileId,
1751
+ position,
1752
+ use: "evaluation",
1753
+ resolution: "syntax",
1754
+ test,
1755
+ });
1756
+ const module = load.asset ? undefined : answered.get(loaded.file);
1757
+ for (const target of module === undefined ? [] : exportsRead(module)) {
1758
+ found.push({
1759
+ from: load.from,
1760
+ to: target.id,
1761
+ position,
1762
+ use: "read",
1763
+ resolution: target.guessed ? "by-name" : "batch",
1764
+ test,
1765
+ });
1766
+ }
1767
+ }
1768
+ }
1769
+
1462
1770
  for (const whole of wholes) {
1463
1771
  const module = answered.get(whole.specifier);
1464
1772
  if (module === undefined) {
@@ -1529,7 +1837,7 @@ export function references<Brand>(
1529
1837
  const position = renderPosition(file, root, read.key.getStart());
1530
1838
  const targets =
1531
1839
  symbol === UNANSWERED
1532
- ? (declarationsByName(held).get(read.name) ?? []).map((id) => ({
1840
+ ? (held.byName.get(read.name) ?? []).map((id) => ({
1533
1841
  id,
1534
1842
  stepped: false,
1535
1843
  guessed: true,
@@ -1673,12 +1981,14 @@ export function references<Brand>(
1673
1981
  ? found
1674
1982
  : found.map((reference) => ({ ...reference, consumer: consumer.id })),
1675
1983
  testFilePaths: tests.sort(bytewise),
1984
+ unseenLoads,
1676
1985
  cost: {
1677
1986
  batched,
1678
1987
  fileBatches,
1679
1988
  residueFallbacks,
1680
1989
  shorthandLookups,
1681
1990
  aliasSteps: chains.steps,
1991
+ platformLookups,
1682
1992
  patternBatches: destructured.cost.patternBatches,
1683
1993
  patternLookups: destructured.cost.patternLookups,
1684
1994
  contextualBatches: contextual.batches,