@cplieger/deadset-ts 8.0.0-dev.15 → 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 (134) hide show
  1. package/dist/src/analysis.js +41 -37
  2. package/dist/src/commands/read.js +6 -7
  3. package/dist/src/conformance-record.js +1 -1
  4. package/dist/src/consumers.js +3 -1
  5. package/dist/src/contract/config.generated.js +1 -1
  6. package/dist/src/contract/contract.generated.js +119 -2
  7. package/dist/src/dependencies.js +79 -48
  8. package/dist/src/entries/build-scripts.js +222 -0
  9. package/dist/src/{configuration-files.js → entries/configuration-files.js} +20 -58
  10. package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
  11. package/dist/src/entries/conventions.js +739 -0
  12. package/dist/src/{html-entries.js → entries/html-entries.js} +3 -3
  13. package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +2 -2
  14. package/dist/src/entries/run-directories.js +77 -0
  15. package/dist/src/entries/sources.js +144 -0
  16. package/dist/src/entries/static-module.js +669 -0
  17. package/dist/src/entries/test-runners.js +684 -0
  18. package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
  19. package/dist/src/extensions.js +37 -0
  20. package/dist/src/facts.js +38 -0
  21. package/dist/src/findings/emitters.js +5 -8
  22. package/dist/src/findings/self-check.js +36 -20
  23. package/dist/src/findings-pass.js +5 -6
  24. package/dist/src/framework-lifecycle.js +1 -1
  25. package/dist/src/fs/manifest.js +15 -20
  26. package/dist/src/generated-file.js +172 -29
  27. package/dist/src/graph.js +2 -4
  28. package/dist/src/index.js +1 -1
  29. package/dist/src/injection-container.js +1 -1
  30. package/dist/src/inventory/commonjs.js +62 -17
  31. package/dist/src/inventory/enumerate.js +2 -1
  32. package/dist/src/javascript-file.js +26 -11
  33. package/dist/src/load/classify.js +28 -9
  34. package/dist/src/load/diagnostics.js +8 -1
  35. package/dist/src/load/discover.js +15 -7
  36. package/dist/src/load/plan.js +2 -2
  37. package/dist/src/load/synthesize.js +5 -5
  38. package/dist/src/load/workspace-manifests.js +1 -1
  39. package/dist/src/matrix.js +6 -13
  40. package/dist/src/outside-tests.js +1 -1
  41. package/dist/src/paths.js +16 -0
  42. package/dist/src/platform-calls.js +78 -0
  43. package/dist/src/platform-symbols.js +140 -0
  44. package/dist/src/references.js +202 -32
  45. package/dist/src/resolve.js +1 -1
  46. package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +4 -4
  47. package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
  48. package/dist/src/{manifest.js → roots/manifest.js} +4 -4
  49. package/dist/src/roots/published-reach.js +412 -0
  50. package/dist/src/roots/root-set.js +489 -0
  51. package/dist/src/roots/unseen-loads.js +208 -0
  52. package/dist/src/serialization-contract.js +1 -1
  53. package/dist/src/source-files.js +5 -9
  54. package/dist/src/suppress.js +30 -32
  55. package/dist/src/test-support.js +4 -2
  56. package/dist/src/verbs/analyze.js +13 -1
  57. package/dist/src/verbs/describe.js +5 -3
  58. package/jsr.json +1 -1
  59. package/package.json +2 -2
  60. package/src/analysis.ts +75 -52
  61. package/src/automatic-types.ts +1 -1
  62. package/src/commands/read.ts +6 -8
  63. package/src/conformance-record.ts +1 -1
  64. package/src/consumers.ts +4 -0
  65. package/src/contract/config.generated.ts +1 -1
  66. package/src/contract/contract.generated.ts +138 -2
  67. package/src/dependencies.ts +96 -49
  68. package/src/entries/build-scripts.ts +292 -0
  69. package/src/{configuration-files.ts → entries/configuration-files.ts} +23 -75
  70. package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
  71. package/src/{conventions.ts → entries/conventions.ts} +347 -522
  72. package/src/{html-entries.ts → entries/html-entries.ts} +5 -5
  73. package/src/{mdx-entries.ts → entries/mdx-entries.ts} +4 -4
  74. package/src/entries/run-directories.ts +108 -0
  75. package/src/entries/sources.ts +266 -0
  76. package/src/entries/static-module.ts +857 -0
  77. package/src/entries/test-runners.ts +868 -0
  78. package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
  79. package/src/exempt.ts +3 -2
  80. package/src/extensions.ts +45 -0
  81. package/src/facts.ts +44 -0
  82. package/src/file-facts.ts +1 -1
  83. package/src/findings/emitters.ts +14 -10
  84. package/src/findings/self-check.ts +41 -22
  85. package/src/findings/visibility-narrowing.ts +1 -1
  86. package/src/findings-pass.ts +5 -12
  87. package/src/framework-lifecycle.ts +1 -1
  88. package/src/fs/manifest.ts +25 -21
  89. package/src/generated-file.ts +240 -36
  90. package/src/graph.ts +3 -5
  91. package/src/index.ts +1 -1
  92. package/src/injection-container.ts +1 -1
  93. package/src/inventory/commonjs.ts +102 -17
  94. package/src/inventory/enumerate.ts +2 -1
  95. package/src/javascript-file.ts +29 -12
  96. package/src/load/classify.ts +49 -13
  97. package/src/load/diagnostics.ts +11 -1
  98. package/src/load/discover.ts +27 -11
  99. package/src/load/plan.ts +11 -2
  100. package/src/load/synthesize.ts +6 -6
  101. package/src/load/test-globals.ts +1 -1
  102. package/src/load/workspace-manifests.ts +1 -1
  103. package/src/load/workspace-run.ts +1 -1
  104. package/src/matrix.ts +7 -14
  105. package/src/outside-tests.ts +1 -1
  106. package/src/paths.ts +18 -0
  107. package/src/platform-calls.ts +119 -0
  108. package/src/platform-symbols.ts +205 -0
  109. package/src/references.ts +281 -34
  110. package/src/report.ts +1 -1
  111. package/src/resolve.ts +1 -1
  112. package/src/{configured-declarations.ts → roots/configured-declarations.ts} +7 -7
  113. package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
  114. package/src/{manifest.ts → roots/manifest.ts} +5 -5
  115. package/src/roots/published-reach.ts +481 -0
  116. package/src/{roots.ts → roots/root-set.ts} +203 -630
  117. package/src/roots/unseen-loads.ts +307 -0
  118. package/src/serialization-contract.ts +1 -1
  119. package/src/source-files.ts +5 -10
  120. package/src/suppress.ts +48 -33
  121. package/src/test-support.ts +8 -3
  122. package/src/verbs/analyze.ts +17 -4
  123. package/src/verbs/describe.ts +5 -2
  124. package/src/verbs/explain.ts +1 -1
  125. package/dist/src/build-scripts.js +0 -418
  126. package/dist/src/conventions.js +0 -907
  127. package/dist/src/entry-points.js +0 -444
  128. package/dist/src/own-body.js +0 -28
  129. package/dist/src/roots.js +0 -865
  130. package/dist/src/unseen-loads.js +0 -189
  131. package/src/build-scripts.ts +0 -536
  132. package/src/entry-points.ts +0 -584
  133. package/src/own-body.ts +0 -42
  134. package/src/unseen-loads.ts +0 -282
package/src/references.ts CHANGED
@@ -35,16 +35,18 @@ import {
35
35
  isStringLiteralLikeNode,
36
36
  isVoidExpression,
37
37
  SyntaxKind,
38
+ type Identifier,
38
39
  type Node,
39
40
  type ParameterDeclaration,
40
41
  type SourceFile,
42
+ type StringLiteralLikeNode,
41
43
  } from "@typescript/native/ast";
42
44
  import { parenthesesRemoved } from "./unwrap.ts";
43
45
  import type { Symbol as TSSymbol } from "@typescript/native/sync";
44
46
  import { aliasChains, type ChainTarget } from "./alias-chain.ts";
45
47
  import { contextualUses, type FlowAt, type LiteralAt } from "./contextual-uses.ts";
46
48
  import { destructuring, propertyReadsOf, type PropertyRead } from "./destructuring.ts";
47
- import { specifierCandidates } from "./configuration-files.ts";
49
+ import { specifierCandidates } from "./entries/configuration-files.ts";
48
50
  import { namespaceUses } from "./namespace-objects.ts";
49
51
  import { isParameterProperty, nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
50
52
  import { ambientGlobalInterfaces, augmentationReferences } from "./augmentations.ts";
@@ -54,14 +56,23 @@ import { keyofComparisonReferences } from "./keyof-comparisons.ts";
54
56
  import { unimplementedCalleeReads } from "./unimplemented-callees.ts";
55
57
  import { moduleDeclarationReferences } from "./module-declarations.ts";
56
58
  import { overrideReferences } from "./overrides.ts";
57
- import { dirnamePath, queriedModulePath, relativePath, resolvePath } from "./paths.ts";
59
+ import {
60
+ dirnamePath,
61
+ queriedModulePath,
62
+ relativePath,
63
+ relativeURLPath,
64
+ resolvePath,
65
+ } from "./paths.ts";
58
66
  import { byPosition, isComponentFile, renderPosition, type Position } from "./position.ts";
59
67
  import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED, type Answer } from "./query.ts";
60
68
  import type { ProjectView } from "./session.ts";
69
+ import { sourceFilesOf, type SourceFiles } from "./source-files.ts";
61
70
  import { typeParameterReads } from "./type-parameter-reads.ts";
62
71
  import { typeQueryAliases } from "./type-query-alias.ts";
63
- import { hasOwnBody } from "./own-body.ts";
64
- import { computedLoadsOf, isLoadCall, type LoadCall } from "./unseen-loads.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";
65
76
  import {
66
77
  computedKeyNames,
67
78
  iterationReferences,
@@ -179,6 +190,12 @@ export interface ReferenceCost {
179
190
  readonly shorthandLookups: number;
180
191
  /** Per-symbol lookups that step from one alias to what it names, one per alias. */
181
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;
182
199
  /**
183
200
  * Batched type lookups for the object patterns: per file, the patterns and the values
184
201
  * assigned to destructuring targets, divided by the batch cap, rounded up.
@@ -214,6 +231,11 @@ export interface References {
214
231
  * classification that marks the references the file makes.
215
232
  */
216
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[];
217
239
  readonly cost: ReferenceCost;
218
240
  }
219
241
 
@@ -241,6 +263,8 @@ export interface ReferenceOptions {
241
263
  readonly consumer?: ConsumerSide;
242
264
  /** The client's running request count, where the caller measures the client. */
243
265
  readonly requestCount?: () => number;
266
+ /** The directories a thread's path and a glob's root are read against. */
267
+ readonly directories: RunDirectories;
244
268
  }
245
269
 
246
270
  /**
@@ -448,6 +472,10 @@ interface Evaluation {
448
472
  /** The module specifier, whose symbol is the module evaluated. */
449
473
  readonly node: Node;
450
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;
451
479
  }
452
480
 
453
481
  /**
@@ -457,8 +485,8 @@ interface Evaluation {
457
485
  */
458
486
  interface ModuleURL {
459
487
  readonly specifier: string;
460
- /** The `URL` the constructor names; {@link hasOwnBody} owns which one loads nothing. */
461
- readonly constructor: Node | undefined;
488
+ /** The name the constructor is written with; {@link callsPlatform} owns which one is `URL`. */
489
+ readonly constructor: Identifier | undefined;
462
490
  readonly at: Node;
463
491
  }
464
492
 
@@ -467,6 +495,19 @@ interface ModuleURLAt extends ModuleURL {
467
495
  readonly from: string;
468
496
  }
469
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
+
470
511
  /** One use of a module namespace object that may read every export of the module. */
471
512
  interface WholeRead {
472
513
  /** The module specifier, whose symbol is the module read. */
@@ -504,6 +545,7 @@ interface FileSites {
504
545
  readonly evaluations: readonly Evaluation[];
505
546
  readonly wholes: readonly WholeRead[];
506
547
  readonly urls: readonly ModuleURLAt[];
548
+ readonly workers: readonly WorkerSite[];
507
549
  readonly loadCalls: readonly LoadCall[];
508
550
  readonly decorated: readonly Decorated[];
509
551
  readonly parameterWrites: readonly ParameterWrite[];
@@ -523,26 +565,24 @@ function isImportMetaURL(node: Node): boolean {
523
565
 
524
566
  /** The module URL one node builds, where it builds one. */
525
567
  function moduleURLOf(node: Node): ModuleURL | undefined {
526
- const relative = (argument: Node | undefined): string | undefined => {
568
+ const literal = (argument: Node | undefined): string | undefined => {
527
569
  const at = argument === undefined ? undefined : parenthesesRemoved(argument);
528
- return at !== undefined &&
529
- (isStringLiteral(at) || isNoSubstitutionTemplateLiteral(at)) &&
530
- /^\.\.?\//u.test(at.text)
531
- ? at.text
532
- : undefined;
570
+ return isLiteralText(at) ? at.text : undefined;
533
571
  };
534
- 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) {
535
574
  const [reference, base] = node.arguments ?? [];
536
- const specifier = relative(reference);
575
+ const specifier = literal(reference);
537
576
  return specifier !== undefined && base !== undefined && isImportMetaURL(base)
538
- ? { specifier, constructor: node.expression, at: node }
577
+ ? { specifier, constructor, at: node }
539
578
  : undefined;
540
579
  }
541
580
  if (isCallExpression(node)) {
542
581
  const callee = parenthesesRemoved(node.expression);
543
- const specifier = relative(node.arguments[0]);
582
+ const specifier = literal(node.arguments[0]);
544
583
  if (
545
584
  specifier !== undefined &&
585
+ /^\.\.?\//u.test(specifier) &&
546
586
  isPropertyAccessExpression(callee) &&
547
587
  isMetaProperty(callee.expression) &&
548
588
  callee.expression.keywordToken === SyntaxKind.ImportKeyword &&
@@ -554,6 +594,75 @@ function moduleURLOf(node: Node): ModuleURL | undefined {
554
594
  return undefined;
555
595
  }
556
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
+
557
666
  /** The file of the target a relative specifier written in `file` names, by its source or output name. */
558
667
  function fileNamed(
559
668
  file: SourceFile,
@@ -566,17 +675,44 @@ function fileNamed(
566
675
  }
567
676
 
568
677
  /** Whether one node is a string literal or a template literal with no substitution. */
569
- function isLiteralText(node: Node | undefined): node is Node {
678
+ function isLiteralText(node: Node | undefined): node is StringLiteralLikeNode {
570
679
  return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
571
680
  }
572
681
 
573
682
  /**
574
683
  * The module specifier of the module one node evaluates, if it evaluates one: an import
575
684
  * declaration, an import-equals declaration of an external module, an export
576
- * declaration that names a module, and an `import()` call whose argument is literal
577
- * text. A type-only form evaluates nothing. An `import()` of a computed argument names
578
- * 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.
579
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
+
580
716
  function evaluatedBy(node: Node): Node | undefined {
581
717
  if (isImportDeclaration(node)) {
582
718
  return node.importClause?.phaseModifier === SyntaxKind.TypeKeyword
@@ -705,6 +841,7 @@ function sitesOf(
705
841
  const evaluations: Evaluation[] = [];
706
842
  const wholes: WholeRead[] = [];
707
843
  const urls: ModuleURLAt[] = [];
844
+ const workers: WorkerSite[] = [];
708
845
  const loadCalls: LoadCall[] = [];
709
846
  const decorated: Decorated[] = [];
710
847
  const parameterWrites: ParameterWrite[] = [];
@@ -782,14 +919,18 @@ function sitesOf(
782
919
  reads.push({ ...read, from: enclosing });
783
920
  }
784
921
  links.push(...linksOf(file, node, declarations, enclosing));
785
- const evaluated = evaluatedBy(node);
922
+ const evaluated = evaluationOf(node);
786
923
  if (evaluated !== undefined) {
787
- evaluations.push({ node: evaluated, from: enclosing });
924
+ evaluations.push({ ...evaluated, from: enclosing });
788
925
  }
789
926
  const url = moduleURLOf(node);
790
927
  if (url !== undefined) {
791
928
  urls.push({ ...url, from: enclosing });
792
929
  }
930
+ const worker = workerOf(node);
931
+ if (worker !== undefined) {
932
+ workers.push({ ...worker, from: enclosing });
933
+ }
793
934
  if (isLoadCall(node)) {
794
935
  loadCalls.push({ call: node, from: enclosing });
795
936
  }
@@ -825,6 +966,7 @@ function sitesOf(
825
966
  evaluations,
826
967
  wholes,
827
968
  urls,
969
+ workers,
828
970
  loadCalls,
829
971
  decorated,
830
972
  parameterWrites,
@@ -1040,6 +1182,10 @@ export function references<Brand>(
1040
1182
  let batched = 0;
1041
1183
  let fileBatches = 0;
1042
1184
  let residueFallbacks = 0;
1185
+ let platformLookups = 0;
1186
+ const asked = (): void => {
1187
+ platformLookups += 1;
1188
+ };
1043
1189
  let shorthandLookups = 0;
1044
1190
 
1045
1191
  const consumer = options.consumer;
@@ -1082,6 +1228,8 @@ export function references<Brand>(
1082
1228
  return moduleBodies.get(moduleId) ?? [];
1083
1229
  };
1084
1230
 
1231
+ let ownFiles: SourceFiles | undefined;
1232
+ const unseenLoads: Position[] = [];
1085
1233
  const exportsCache = new Map<number, readonly ChainTarget[]>();
1086
1234
  /**
1087
1235
  * Every declaration one module exports, each re-export walked along its chain. A module
@@ -1218,6 +1366,7 @@ export function references<Brand>(
1218
1366
  const loadable = walked
1219
1367
  .filter((one) => fileIds.has(one.fileId))
1220
1368
  .map(({ file, fileId }) => ({ file, fileId }));
1369
+ const loadableById = new Map(loadable.map((one) => [one.fileId, one.file]));
1221
1370
  const contextual = contextualUses(
1222
1371
  project,
1223
1372
  held,
@@ -1317,13 +1466,16 @@ export function references<Brand>(
1317
1466
  evaluations,
1318
1467
  wholes,
1319
1468
  urls,
1469
+ workers,
1320
1470
  loadCalls,
1321
1471
  decorated,
1322
1472
  parameterWrites,
1323
1473
  keyed,
1324
1474
  } of walked) {
1325
1475
  const test = testFile.get(file) === true;
1326
- const loads = computedLoadsOf(file, loadCalls, loadable);
1476
+ const loads = computedLoadsOf(file, loadCalls, loadable, (fileName) =>
1477
+ options.directories.bundlerRootOf(fileName),
1478
+ );
1327
1479
  // A component file's markup may use any binding its blocks declare at the top level.
1328
1480
  const markup = isComponentFile(file);
1329
1481
  const bindings = markup ? importBindings(file) : [];
@@ -1363,9 +1515,19 @@ export function references<Brand>(
1363
1515
  ...new Set([
1364
1516
  ...uses.filter((site) => site.shorthand === undefined).map((site) => site.node),
1365
1517
  ...links.map((link) => link.node),
1366
- ...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
+ ),
1367
1526
  ...wholes.map((whole) => whole.specifier),
1368
1527
  ...urls.flatMap((url) => url.constructor ?? []),
1528
+ ...workers.flatMap((worker) =>
1529
+ worker.holder === undefined ? [worker.constructor] : [worker.constructor, worker.holder],
1530
+ ),
1369
1531
  ...bindings.map((binding) => binding.node),
1370
1532
  ...loads.flatMap((load) => [
1371
1533
  ...(load.require === undefined ? [] : [load.require]),
@@ -1412,9 +1574,14 @@ export function references<Brand>(
1412
1574
  }
1413
1575
  }
1414
1576
 
1577
+ const loading = evaluations.filter(
1578
+ (one) =>
1579
+ one.require === undefined ||
1580
+ callsPlatform(project, "require", one.require, answered.get(one.require), asked),
1581
+ );
1415
1582
  // A script declares no module, so the checker names nothing for a specifier of one;
1416
1583
  // the compiler's module resolution still names its file.
1417
- const unnamed = evaluations.flatMap(({ node }) =>
1584
+ const unnamed = loading.flatMap(({ node }) =>
1418
1585
  answered.get(node) === undefined &&
1419
1586
  isStringLiteralLikeNode(node) &&
1420
1587
  queriedFile(file, node, fileIdByName) === undefined
@@ -1428,7 +1595,8 @@ export function references<Brand>(
1428
1595
  return [node, name === undefined ? undefined : fileIdByName.get(name)];
1429
1596
  }),
1430
1597
  );
1431
- for (const evaluation of evaluations) {
1598
+ const wholeReads: { readonly evaluation: Evaluation; readonly fileId: string }[] = [];
1599
+ for (const evaluation of loading) {
1432
1600
  const queried = queriedFile(file, evaluation.node, fileIdByName);
1433
1601
  if (queried !== undefined) {
1434
1602
  found.push({
@@ -1441,7 +1609,19 @@ export function references<Brand>(
1441
1609
  });
1442
1610
  }
1443
1611
  const module = answered.get(evaluation.node);
1444
- const script = scripts.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
+ }
1445
1625
  if (script !== undefined) {
1446
1626
  found.push({
1447
1627
  from: evaluation.from,
@@ -1472,12 +1652,40 @@ export function references<Brand>(
1472
1652
  }
1473
1653
  }
1474
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
+
1475
1680
  for (const url of urls) {
1476
- const constructor = url.constructor === undefined ? undefined : answered.get(url.constructor);
1477
- if (typeof constructor === "object" && hasOwnBody(project, constructor)) {
1681
+ if (
1682
+ url.constructor !== undefined &&
1683
+ !callsPlatform(project, "URL", url.constructor, answered.get(url.constructor), asked)
1684
+ ) {
1478
1685
  continue;
1479
1686
  }
1480
- const loaded = fileNamed(file, url.specifier, fileIdByName);
1687
+ const path = relativeURLPath(dirnamePath(file.fileName), url.specifier);
1688
+ const loaded = path === undefined ? undefined : fileNamed(file, path, fileIdByName);
1481
1689
  if (loaded !== undefined) {
1482
1690
  found.push({
1483
1691
  from: url.from,
@@ -1490,15 +1698,52 @@ export function references<Brand>(
1490
1698
  }
1491
1699
  }
1492
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
+
1493
1733
  // A computed load imports each file it loads where the call is written, reading what
1494
- // the file exports unless it reads the file as text or a URL; hasOwnBody owns which
1495
- // `require` loads nothing.
1734
+ // the file exports unless it reads the file as text or a URL; callsPlatform owns which
1735
+ // call is a `require`.
1496
1736
  for (const load of loads) {
1497
- const callee = load.require === undefined ? undefined : answered.get(load.require);
1498
- if (typeof callee === "object" && hasOwnBody(project, callee)) {
1737
+ if (
1738
+ load.require !== undefined &&
1739
+ !callsPlatform(project, "require", load.require, answered.get(load.require), asked)
1740
+ ) {
1499
1741
  continue;
1500
1742
  }
1501
1743
  const position = renderPosition(file, root, load.call.getStart());
1744
+ if (!load.resolved && !test && consumer === undefined && !file.isDeclarationFile) {
1745
+ unseenLoads.push(position);
1746
+ }
1502
1747
  for (const loaded of load.files) {
1503
1748
  found.push({
1504
1749
  from: load.eager ? fileId : load.from,
@@ -1736,12 +1981,14 @@ export function references<Brand>(
1736
1981
  ? found
1737
1982
  : found.map((reference) => ({ ...reference, consumer: consumer.id })),
1738
1983
  testFilePaths: tests.sort(bytewise),
1984
+ unseenLoads,
1739
1985
  cost: {
1740
1986
  batched,
1741
1987
  fileBatches,
1742
1988
  residueFallbacks,
1743
1989
  shorthandLookups,
1744
1990
  aliasSteps: chains.steps,
1991
+ platformLookups,
1745
1992
  patternBatches: destructured.cost.patternBatches,
1746
1993
  patternLookups: destructured.cost.patternLookups,
1747
1994
  contextualBatches: contextual.batches,
package/src/report.ts CHANGED
@@ -15,7 +15,7 @@ import type {
15
15
  FindingPosition,
16
16
  PositionedSymbol,
17
17
  } from "./finding.ts";
18
- import type { AppliedConvention } from "./conventions.ts";
18
+ import type { AppliedConvention } from "./entries/conventions.ts";
19
19
  import type { PassResult } from "./findings-pass.ts";
20
20
  import type { StaleSuppression } from "./findings/self-check.ts";
21
21
  import type { TestFileRule } from "./references.ts";
package/src/resolve.ts CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  type ResolvedConfig,
10
10
  type Source,
11
11
  } from "./config.ts";
12
- import { CONVENTION_ROWS } from "./convention-rows.ts";
12
+ import { CONVENTION_ROWS } from "./entries/convention-rows.ts";
13
13
  import { refuseOverBound } from "./fs/document.ts";
14
14
  import {
15
15
  dotted,
@@ -17,13 +17,13 @@ import {
17
17
  type Node,
18
18
  } from "@typescript/native/ast";
19
19
  import { SymbolFlags, type NodeHandle, type Symbol as TSSymbol } from "@typescript/native/sync";
20
- import type { AliasChains } from "./alias-chain.ts";
21
- import { resolvedTargets } from "./calls.ts";
22
- import type { DeclarationEntry, ResolvedConfig } from "./config.ts";
23
- import { declaredComponent, type Inventory } from "./inventory.ts";
24
- import { must, Unanswerable } from "./query.ts";
25
- import { nameComponent } from "./ref.ts";
26
- import type { ProjectView } from "./session.ts";
20
+ import type { AliasChains } from "../alias-chain.ts";
21
+ import { resolvedTargets } from "../calls.ts";
22
+ import type { DeclarationEntry, ResolvedConfig } from "../config.ts";
23
+ import { declaredComponent, type Inventory } from "../inventory.ts";
24
+ import { must, Unanswerable } from "../query.ts";
25
+ import { nameComponent } from "../ref.ts";
26
+ import type { ProjectView } from "../session.ts";
27
27
 
28
28
  /** The meanings a name of the global scope is looked up under: any of them. */
29
29
  const ANY_MEANING: SymbolFlags = SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace;
@@ -1,14 +1,11 @@
1
1
  /**
2
- * Every entry-point convention this analyzer declines, each with the tool it belongs
3
- * to and why. A file only a declined convention enters is reported as never
4
- * imported, so a project that relies on one names the file in `ts.entry_files` or
5
- * `roots.patterns`, and this list is what tells it to.
6
- *
7
- * Every row carries a reason, because the list is read where the rows are shown
8
- * one at a time: beside a finding, or in a report's declared gaps.
2
+ * Every entry-point convention this analyzer declines, each with its tool and why. A file only
3
+ * a declined convention enters is reported as never imported, so a project relying on one names
4
+ * the file in `ts.entry_files` or `roots.patterns`, and this list tells it to. Every row carries
5
+ * a reason, since rows are shown one at a time, beside a finding or in a report's declared gaps.
9
6
  */
10
7
 
11
- import { bytewise } from "./text/compare.ts";
8
+ import { bytewise } from "../text/compare.ts";
12
9
 
13
10
  /** One declined entry-point convention. */
14
11
  export interface DeclinedConvention {
@@ -5,11 +5,11 @@
5
5
  * directory's manifest, {@link packageEntries} those of the packages below the target root.
6
6
  */
7
7
 
8
- import type { Host } from "./host.ts";
9
- import { isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.ts";
10
- import { isRecord } from "./json-document.ts";
11
- import { MANIFEST, manifestAt, readJSON } from "./fs/manifest.ts";
12
- import { pathsNamed, readCommands } from "./commands/read.ts";
8
+ import type { Host } from "../host.ts";
9
+ import { isAbsolutePath, joinPath, relativePath, resolvePath } from "../paths.ts";
10
+ import { isRecord } from "../json-document.ts";
11
+ import { MANIFEST, manifestAt, readJSON } from "../fs/manifest.ts";
12
+ import { pathsNamed, readCommands } from "../commands/read.ts";
13
13
 
14
14
  /** The three members that name an entry point beside `exports` and `bin`. */
15
15
  const LEGACY_MEMBERS = ["main", "module", "types"] as const;