@cplieger/deadset-ts 8.0.0-dev.13 → 8.0.0-dev.15

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 (46) hide show
  1. package/dist/src/alias-chain.js +2 -2
  2. package/dist/src/analysis.js +2 -1
  3. package/dist/src/conformance-record.js +1 -1
  4. package/dist/src/consumers.js +1 -1
  5. package/dist/src/contract/contract.generated.js +1 -0
  6. package/dist/src/dependencies.js +2 -1
  7. package/dist/src/entry-points.js +26 -95
  8. package/dist/src/file-facts.js +1 -1
  9. package/dist/src/graph.js +0 -1
  10. package/dist/src/inventory/commonjs.js +462 -0
  11. package/dist/src/inventory/enumerate.js +166 -0
  12. package/dist/src/inventory/syntax.js +236 -0
  13. package/dist/src/inventory.js +305 -705
  14. package/dist/src/load/discover.js +1 -1
  15. package/dist/src/load/package-scope.js +56 -0
  16. package/dist/src/own-body.js +28 -0
  17. package/dist/src/position.js +41 -0
  18. package/dist/src/ref.js +11 -20
  19. package/dist/src/references.js +91 -30
  20. package/dist/src/session.js +3 -0
  21. package/dist/src/suppress-inline.js +33 -81
  22. package/dist/src/unseen-loads.js +106 -17
  23. package/jsr.json +1 -1
  24. package/package.json +2 -2
  25. package/src/alias-chain.ts +2 -2
  26. package/src/analysis.ts +2 -1
  27. package/src/conformance-record.ts +1 -1
  28. package/src/consumers.ts +1 -1
  29. package/src/contract/contract.generated.ts +1 -0
  30. package/src/dependencies.ts +2 -1
  31. package/src/entry-points.ts +30 -107
  32. package/src/file-facts.ts +1 -1
  33. package/src/graph.ts +0 -1
  34. package/src/inventory/commonjs.ts +611 -0
  35. package/src/inventory/enumerate.ts +202 -0
  36. package/src/inventory/syntax.ts +311 -0
  37. package/src/inventory.ts +424 -878
  38. package/src/load/discover.ts +1 -1
  39. package/src/load/package-scope.ts +74 -0
  40. package/src/own-body.ts +42 -0
  41. package/src/position.ts +42 -0
  42. package/src/ref.ts +11 -20
  43. package/src/references.ts +116 -53
  44. package/src/session.ts +17 -1
  45. package/src/suppress-inline.ts +53 -89
  46. package/src/unseen-loads.ts +166 -18
@@ -2,7 +2,7 @@ import type { BuildConfiguration, ProjectConfiguration } from "../config.ts";
2
2
  import { LANGUAGE_ROWS } from "../contract/contract.generated.ts";
3
3
  import { filesBelow, filesIn } from "../fs/tree.ts";
4
4
  import type { Host } from "../host.ts";
5
- import { packageScope } from "../inventory.ts";
5
+ import { packageScope } from "./package-scope.ts";
6
6
  import { dirnamePath, joinPath, relativePath, resolvePath } from "../paths.ts";
7
7
  import { LANGUAGE, message } from "../product.ts";
8
8
  import type { Module, Scope } from "../scope.ts";
@@ -0,0 +1,74 @@
1
+ import { manifestAt } from "../fs/manifest.ts";
2
+ import type { Host } from "../host.ts";
3
+ import { dirnamePath, joinPath } from "../paths.ts";
4
+ import type { Module } from "../ref.ts";
5
+
6
+ /** One manifest that names a package, and the directory below the root that holds it. */
7
+ interface Named {
8
+ readonly name: string;
9
+ readonly at: string;
10
+ }
11
+
12
+ /**
13
+ * Resolves each file's package scope: the name of the nearest manifest at or above its
14
+ * directory up to the target root, and the file's path below that manifest. Where no
15
+ * manifest up to the root carries a name, the package is a single dot and the path is read
16
+ * from the root, which cannot collide, because a package name never starts with a dot. One
17
+ * directory is read at most once.
18
+ */
19
+ export function packageScope(host: Host, root: string): (path: string) => Module {
20
+ const named = new Map<string, Named | undefined>();
21
+
22
+ const manifestName = (dir: string): string | undefined => {
23
+ const name = manifestAt(host, joinPath(root, dir))?.["name"];
24
+ return typeof name === "string" && name !== "" ? name : undefined;
25
+ };
26
+
27
+ const nearest = (dir: string): Named | undefined => {
28
+ if (named.has(dir)) {
29
+ return named.get(dir);
30
+ }
31
+ const name = manifestName(dir);
32
+ const found =
33
+ name !== undefined ? { name, at: dir } : dir === "" ? undefined : nearest(parentOf(dir));
34
+ named.set(dir, found);
35
+ return found;
36
+ };
37
+
38
+ return (path) => {
39
+ const found = nearest(parentOf(path));
40
+ if (found === undefined) {
41
+ return { package: ".", path };
42
+ }
43
+ return { package: found.name, path: found.at === "" ? path : path.slice(found.at.length + 1) };
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Whether Node reads each file's `.js` siblings as ECMAScript modules: the `type` of the
49
+ * nearest manifest at or above its directory up to the target root is `module`
50
+ * (https://nodejs.org/api/packages.html#type). One directory is read at most once.
51
+ */
52
+ export function packageTypeIsModule(host: Host, root: string): (path: string) => boolean {
53
+ const typed = new Map<string, boolean>();
54
+
55
+ const nearest = (dir: string): boolean => {
56
+ const known = typed.get(dir);
57
+ if (known !== undefined) {
58
+ return known;
59
+ }
60
+ const manifest = manifestAt(host, joinPath(root, dir));
61
+ const found =
62
+ manifest !== undefined ? manifest["type"] === "module" : dir !== "" && nearest(parentOf(dir));
63
+ typed.set(dir, found);
64
+ return found;
65
+ };
66
+
67
+ return (path) => nearest(parentOf(path));
68
+ }
69
+
70
+ /** The directory one path below the root sits in, and the root itself as the empty path. */
71
+ function parentOf(path: string): string {
72
+ const dir = dirnamePath(path);
73
+ return dir === "." ? "" : dir;
74
+ }
@@ -0,0 +1,42 @@
1
+ import {
2
+ isArrowFunction,
3
+ isClassDeclaration,
4
+ isClassExpression,
5
+ isFunctionDeclaration,
6
+ isFunctionExpression,
7
+ isVariableDeclaration,
8
+ } from "@typescript/native/ast";
9
+ import { SymbolFlags, type Symbol as TSSymbol } from "@typescript/native/sync";
10
+ import { isAmbient } from "./inventory.ts";
11
+ import type { ProjectView } from "./session.ts";
12
+ import { unwrapped } from "./unwrap.ts";
13
+
14
+ /**
15
+ * Whether `symbol`, through its aliases, names a function or class the target writes with a
16
+ * body, which a call or construction runs in place of the platform's `require` or `URL`. Any
17
+ * other answer, an unresolved one included, is the platform's, whose call loads a file.
18
+ */
19
+ export function hasOwnBody<Brand>(project: ProjectView<Brand>, symbol: TSSymbol): boolean {
20
+ const named = (symbol.flags & SymbolFlags.Alias) === 0 ? symbol : project.queries.aliased(symbol);
21
+ const own = project.ownPaths();
22
+ return (
23
+ typeof named === "object" &&
24
+ named.declarations.some((handle) => {
25
+ const node = own.has(handle.path) ? project.declarationAt(handle)?.node : undefined;
26
+ const value =
27
+ node !== undefined && isVariableDeclaration(node) && node.initializer !== undefined
28
+ ? unwrapped(node.initializer)
29
+ : node;
30
+ return (
31
+ value !== undefined &&
32
+ (isFunctionDeclaration(value)
33
+ ? value.body !== undefined
34
+ : isFunctionExpression(value) ||
35
+ isArrowFunction(value) ||
36
+ isClassDeclaration(value) ||
37
+ isClassExpression(value)) &&
38
+ !isAmbient(value.getSourceFile(), value)
39
+ );
40
+ })
41
+ );
42
+ }
package/src/position.ts CHANGED
@@ -48,6 +48,48 @@ export function writtenLength(file: SourceFile): number {
48
48
  return isComponentFile(file) ? file.originalText.length : file.text.length;
49
49
  }
50
50
 
51
+ /** Whether one character ends a line, under the language's line terminators. */
52
+ function endsLine(char: string | undefined): boolean {
53
+ return char === "\n" || char === "\r" || char === "\u2028" || char === "\u2029";
54
+ }
55
+
56
+ /** The offset where the line holding `from` ends. */
57
+ export function lineEnd(text: string, from: number): number {
58
+ let at = from;
59
+ while (at < text.length && !endsLine(text[at])) {
60
+ at += 1;
61
+ }
62
+ return at;
63
+ }
64
+
65
+ /**
66
+ * The offset of the first token at or after `from`: whitespace, comments and a leading
67
+ * hashbang are trivia.
68
+ */
69
+ export function tokenStart(text: string, from: number): number {
70
+ let at = from;
71
+ if (at === 0 && text.startsWith("#!")) {
72
+ at = lineEnd(text, 0);
73
+ }
74
+ for (;;) {
75
+ if (/\s/u.test(text[at] ?? "")) {
76
+ at += 1;
77
+ } else if (text.startsWith("//", at)) {
78
+ at = lineEnd(text, at);
79
+ } else if (text.startsWith("/*", at)) {
80
+ const close = text.indexOf("*/", at + 2);
81
+ at = close < 0 ? text.length : close + 2;
82
+ } else {
83
+ return at;
84
+ }
85
+ }
86
+ }
87
+
88
+ /** The line, counted from one, of the first token at or after one offset of one file. */
89
+ export function startLine(file: SourceFile, offset: number): number {
90
+ return file.getLineAndCharacterOfPosition(tokenStart(file.text, offset)).line + 1;
91
+ }
92
+
51
93
  /**
52
94
  * The position of one offset of one file, rendered against the target root.
53
95
  *
package/src/ref.ts CHANGED
@@ -1,13 +1,9 @@
1
1
  /**
2
- * The stable symbol reference: the identifier that names one declaration across
3
- * runs, across reports, in an ignore entry, in a configured root and in a
4
- * cross-language edge. It is built from names and containers only, so an edit above
5
- * a declaration leaves it unchanged, and it is compared as bytes, so there is one
6
- * canonical spelling per declaration and this module is where that spelling is
7
- * written.
8
- *
9
- * The position key of {@link ./position.ts} is the other identifier and has the
10
- * other job: it locates a finding inside one run, and the two never mix.
2
+ * The stable symbol reference: the identifier that names one declaration across runs,
3
+ * reports, ignore entries, configured roots and cross-language edges. It is built from
4
+ * names and containers only, so an edit above a declaration leaves it unchanged, and it
5
+ * is compared as bytes, so this module writes the one canonical spelling. The position
6
+ * key of {@link ./position.ts} locates a finding inside one run; the two never mix.
11
7
  */
12
8
 
13
9
  /** The language prefix, which namespaces every reference this analyzer emits. */
@@ -34,16 +30,10 @@ const SCOPE = `${PREFIX}${TS_PACKAGE}/${TS_FILE}`;
34
30
  const MANIFEST_SCOPE = `${PREFIX}${TS_PACKAGE}/${TS_DIRS}package\\.json`;
35
31
 
36
32
  /**
37
- * One expression per form the grammar defines, each anchored and each the whole of
38
- * a reference. Several forms share one expression because they differ in what the
39
- * symbol is rather than in how the reference is spelled, so the list is the set of
40
- * spellings and not the set of subjects. A reference is valid when it matches one.
41
- *
42
- * They are written here rather than read from the Contract at run time: the Contract
43
- * is data a reader outside this program consults, and a product that read it while
44
- * running would need it installed beside itself. A test compares this list against
45
- * the release this analyzer is written against, so a grammar that grows a form
46
- * arrives here as a failing test.
33
+ * One anchored expression per spelling the grammar defines; a reference is valid when it
34
+ * matches one. They are written here rather than read from the Contract at run time, so
35
+ * the product needs no Contract installed beside it; a test compares this list against
36
+ * the release this analyzer implements, so a grammar that grows a form fails it.
47
37
  */
48
38
  export const REF_EXPRESSIONS: readonly string[] = [
49
39
  `^${SCOPE}#$`,
@@ -170,7 +160,8 @@ export function aliasFragment(name: string, block?: string): string {
170
160
  return `${head}${nameComponent(name)}:alias`;
171
161
  }
172
162
 
173
- function spell(component: Component): string {
163
+ /** The canonical spelling of one component: a name, or a computed key. */
164
+ export function spell(component: Component): string {
174
165
  return component.computed ? computedComponent(component.text) : nameComponent(component.text);
175
166
  }
176
167
 
package/src/references.ts CHANGED
@@ -32,6 +32,7 @@ import {
32
32
  isSpreadAssignment,
33
33
  isSpreadElement,
34
34
  isStringLiteral,
35
+ isStringLiteralLikeNode,
35
36
  isVoidExpression,
36
37
  SyntaxKind,
37
38
  type Node,
@@ -45,13 +46,7 @@ import { contextualUses, type FlowAt, type LiteralAt } from "./contextual-uses.t
45
46
  import { destructuring, propertyReadsOf, type PropertyRead } from "./destructuring.ts";
46
47
  import { specifierCandidates } from "./configuration-files.ts";
47
48
  import { namespaceUses } from "./namespace-objects.ts";
48
- import {
49
- declarationsByName,
50
- isParameterProperty,
51
- nodeKey,
52
- type Inventory,
53
- type InventorySymbol,
54
- } from "./inventory.ts";
49
+ import { isParameterProperty, nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
55
50
  import { ambientGlobalInterfaces, augmentationReferences } from "./augmentations.ts";
56
51
  import { elementAccessReferences } from "./element-access.ts";
57
52
  import { indexedAccessReferences } from "./indexed-access.ts";
@@ -65,6 +60,8 @@ import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED, type Answer } from "./qu
65
60
  import type { ProjectView } from "./session.ts";
66
61
  import { typeParameterReads } from "./type-parameter-reads.ts";
67
62
  import { typeQueryAliases } from "./type-query-alias.ts";
63
+ import { hasOwnBody } from "./own-body.ts";
64
+ import { computedLoadsOf, isLoadCall, type LoadCall } from "./unseen-loads.ts";
68
65
  import {
69
66
  computedKeyNames,
70
67
  iterationReferences,
@@ -77,20 +74,12 @@ import { bytewise } from "./text/compare.ts";
77
74
  import { globExpression } from "./text/glob.ts";
78
75
 
79
76
  /**
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.
77
+ * Every use one project's own files make of the project's own declarations: one local walk
78
+ * and one batched resolution per file, never a reference query per symbol, whose answers
79
+ * would depend on the order the symbols are asked in. A reference carries its use, its
80
+ * position, the declaration enclosing it and whether a test file made it; every import of
81
+ * one of the project's modules is an evaluation of that module's file. It carries no mode:
82
+ * which references a run counts is the sweep's reading of this set.
94
83
  */
95
84
 
96
85
  /**
@@ -468,11 +457,16 @@ interface Evaluation {
468
457
  */
469
458
  interface ModuleURL {
470
459
  readonly specifier: string;
471
- /** The `URL` the constructor names, which must name no declaration of the target. */
460
+ /** The `URL` the constructor names; {@link hasOwnBody} owns which one loads nothing. */
472
461
  readonly constructor: Node | undefined;
473
462
  readonly at: Node;
474
463
  }
475
464
 
465
+ /** One module URL, and the declaration whose code builds it. */
466
+ interface ModuleURLAt extends ModuleURL {
467
+ readonly from: string;
468
+ }
469
+
476
470
  /** One use of a module namespace object that may read every export of the module. */
477
471
  interface WholeRead {
478
472
  /** The module specifier, whose symbol is the module read. */
@@ -509,7 +503,8 @@ interface FileSites {
509
503
  readonly links: readonly Link[];
510
504
  readonly evaluations: readonly Evaluation[];
511
505
  readonly wholes: readonly WholeRead[];
512
- readonly urls: readonly ModuleURL[];
506
+ readonly urls: readonly ModuleURLAt[];
507
+ readonly loadCalls: readonly LoadCall[];
513
508
  readonly decorated: readonly Decorated[];
514
509
  readonly parameterWrites: readonly ParameterWrite[];
515
510
  readonly keyed: readonly KeyedAccess[];
@@ -559,6 +554,17 @@ function moduleURLOf(node: Node): ModuleURL | undefined {
559
554
  return undefined;
560
555
  }
561
556
 
557
+ /** The file of the target a relative specifier written in `file` names, by its source or output name. */
558
+ function fileNamed(
559
+ file: SourceFile,
560
+ specifier: string,
561
+ fileIdByName: ReadonlyMap<string, string>,
562
+ ): string | undefined {
563
+ return specifierCandidates(dirnamePath(file.fileName), specifier)
564
+ .map((path) => fileIdByName.get(path))
565
+ .find((id) => id !== undefined);
566
+ }
567
+
562
568
  /** Whether one node is a string literal or a template literal with no substitution. */
563
569
  function isLiteralText(node: Node | undefined): node is Node {
564
570
  return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
@@ -698,7 +704,8 @@ function sitesOf(
698
704
  const links: Link[] = [];
699
705
  const evaluations: Evaluation[] = [];
700
706
  const wholes: WholeRead[] = [];
701
- const urls: ModuleURL[] = [];
707
+ const urls: ModuleURLAt[] = [];
708
+ const loadCalls: LoadCall[] = [];
702
709
  const decorated: Decorated[] = [];
703
710
  const parameterWrites: ParameterWrite[] = [];
704
711
  const namespaces = namespaceUses(file);
@@ -781,7 +788,10 @@ function sitesOf(
781
788
  }
782
789
  const url = moduleURLOf(node);
783
790
  if (url !== undefined) {
784
- urls.push(url);
791
+ urls.push({ ...url, from: enclosing });
792
+ }
793
+ if (isLoadCall(node)) {
794
+ loadCalls.push({ call: node, from: enclosing });
785
795
  }
786
796
  const held = declarations.get(nodeKey(file, node));
787
797
  const outer = enclosing;
@@ -815,6 +825,7 @@ function sitesOf(
815
825
  evaluations,
816
826
  wholes,
817
827
  urls,
828
+ loadCalls,
818
829
  decorated,
819
830
  parameterWrites,
820
831
  keyed,
@@ -1005,20 +1016,12 @@ function aloneIn(
1005
1016
  }
1006
1017
 
1007
1018
  /**
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.
1019
+ * Every reference one project's own files make to its own declarations, and the files the
1020
+ * test-file rules classified. `held` is the same project's inventory, where a resolved symbol
1021
+ * is looked up; positions render against `targetRoot`, or a consumer's root as
1022
+ * {@link ReferenceOptions.consumer} states. The order is by position, then the declaration
1023
+ * named, the referencing declaration and the use: an alias chain or a bare star re-export
1024
+ * names several at one position, and one name references one declaration once.
1022
1025
  */
1023
1026
  export function references<Brand>(
1024
1027
  project: ProjectView<Brand>,
@@ -1132,7 +1135,7 @@ export function references<Brand>(
1132
1135
  * live. A name a namespace import binds may be a use of any file.
1133
1136
  */
1134
1137
  const named = (node: Node): readonly string[] => {
1135
- const byName = declarationsByName(held);
1138
+ const byName = held.byName;
1136
1139
  const text = node.getText();
1137
1140
  const imported = importedNames(node.getSourceFile()).get(text) ?? [];
1138
1141
  return [
@@ -1161,7 +1164,7 @@ export function references<Brand>(
1161
1164
  aliased.set(
1162
1165
  id,
1163
1166
  symbol === UNANSWERED
1164
- ? (declarationsByName(held).get(alias.name) ?? []).map((one) => ({
1167
+ ? (held.byName.get(alias.name) ?? []).map((one) => ({
1165
1168
  id: one,
1166
1169
  stepped: true,
1167
1170
  guessed: true,
@@ -1212,6 +1215,9 @@ export function references<Brand>(
1212
1215
  const fileIdByName = new Map<string, string>(
1213
1216
  walked.filter((one) => fileIds.has(one.fileId)).map((one) => [one.file.fileName, one.fileId]),
1214
1217
  );
1218
+ const loadable = walked
1219
+ .filter((one) => fileIds.has(one.fileId))
1220
+ .map(({ file, fileId }) => ({ file, fileId }));
1215
1221
  const contextual = contextualUses(
1216
1222
  project,
1217
1223
  held,
@@ -1311,11 +1317,13 @@ export function references<Brand>(
1311
1317
  evaluations,
1312
1318
  wholes,
1313
1319
  urls,
1320
+ loadCalls,
1314
1321
  decorated,
1315
1322
  parameterWrites,
1316
1323
  keyed,
1317
1324
  } of walked) {
1318
1325
  const test = testFile.get(file) === true;
1326
+ const loads = computedLoadsOf(file, loadCalls, loadable);
1319
1327
  // A component file's markup may use any binding its blocks declare at the top level.
1320
1328
  const markup = isComponentFile(file);
1321
1329
  const bindings = markup ? importBindings(file) : [];
@@ -1359,6 +1367,10 @@ export function references<Brand>(
1359
1367
  ...wholes.map((whole) => whole.specifier),
1360
1368
  ...urls.flatMap((url) => url.constructor ?? []),
1361
1369
  ...bindings.map((binding) => binding.node),
1370
+ ...loads.flatMap((load) => [
1371
+ ...(load.require === undefined ? [] : [load.require]),
1372
+ ...(load.asset ? [] : load.files.map((loaded) => loaded.file)),
1373
+ ]),
1362
1374
  ]),
1363
1375
  ];
1364
1376
  const answered = new Map<Node, Answer<TSSymbol>>();
@@ -1400,6 +1412,22 @@ export function references<Brand>(
1400
1412
  }
1401
1413
  }
1402
1414
 
1415
+ // A script declares no module, so the checker names nothing for a specifier of one;
1416
+ // the compiler's module resolution still names its file.
1417
+ const unnamed = evaluations.flatMap(({ node }) =>
1418
+ answered.get(node) === undefined &&
1419
+ isStringLiteralLikeNode(node) &&
1420
+ queriedFile(file, node, fileIdByName) === undefined
1421
+ ? [node]
1422
+ : [],
1423
+ );
1424
+ const resolved = project.resolvedFileNames(unnamed);
1425
+ const scripts = new Map<Node, string | undefined>(
1426
+ unnamed.map((node, at) => {
1427
+ const name = resolved[at];
1428
+ return [node, name === undefined ? undefined : fileIdByName.get(name)];
1429
+ }),
1430
+ );
1403
1431
  for (const evaluation of evaluations) {
1404
1432
  const queried = queriedFile(file, evaluation.node, fileIdByName);
1405
1433
  if (queried !== undefined) {
@@ -1413,6 +1441,17 @@ export function references<Brand>(
1413
1441
  });
1414
1442
  }
1415
1443
  const module = answered.get(evaluation.node);
1444
+ const script = scripts.get(evaluation.node);
1445
+ if (script !== undefined) {
1446
+ found.push({
1447
+ from: evaluation.from,
1448
+ to: script,
1449
+ position: renderPosition(file, root, evaluation.node.getStart()),
1450
+ use: "evaluation",
1451
+ resolution: "syntax",
1452
+ test,
1453
+ });
1454
+ }
1416
1455
  if (module === undefined) {
1417
1456
  continue;
1418
1457
  }
@@ -1435,21 +1474,13 @@ export function references<Brand>(
1435
1474
 
1436
1475
  for (const url of urls) {
1437
1476
  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
- if (
1441
- typeof constructor === "object" &&
1442
- constructor.declarations.length > 0 &&
1443
- constructor.declarations.every((handle) => ownPaths.has(handle.path))
1444
- ) {
1477
+ if (typeof constructor === "object" && hasOwnBody(project, constructor)) {
1445
1478
  continue;
1446
1479
  }
1447
- const loaded = specifierCandidates(dirnamePath(file.fileName), url.specifier)
1448
- .map((path) => fileIdByName.get(path))
1449
- .find((id) => id !== undefined);
1480
+ const loaded = fileNamed(file, url.specifier, fileIdByName);
1450
1481
  if (loaded !== undefined) {
1451
1482
  found.push({
1452
- from: fileId,
1483
+ from: url.from,
1453
1484
  to: loaded,
1454
1485
  position: renderPosition(file, root, url.at.getStart()),
1455
1486
  use: "evaluation",
@@ -1459,6 +1490,38 @@ export function references<Brand>(
1459
1490
  }
1460
1491
  }
1461
1492
 
1493
+ // 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.
1496
+ for (const load of loads) {
1497
+ const callee = load.require === undefined ? undefined : answered.get(load.require);
1498
+ if (typeof callee === "object" && hasOwnBody(project, callee)) {
1499
+ continue;
1500
+ }
1501
+ const position = renderPosition(file, root, load.call.getStart());
1502
+ for (const loaded of load.files) {
1503
+ found.push({
1504
+ from: load.eager ? fileId : load.from,
1505
+ to: loaded.fileId,
1506
+ position,
1507
+ use: "evaluation",
1508
+ resolution: "syntax",
1509
+ test,
1510
+ });
1511
+ const module = load.asset ? undefined : answered.get(loaded.file);
1512
+ for (const target of module === undefined ? [] : exportsRead(module)) {
1513
+ found.push({
1514
+ from: load.from,
1515
+ to: target.id,
1516
+ position,
1517
+ use: "read",
1518
+ resolution: target.guessed ? "by-name" : "batch",
1519
+ test,
1520
+ });
1521
+ }
1522
+ }
1523
+ }
1524
+
1462
1525
  for (const whole of wholes) {
1463
1526
  const module = answered.get(whole.specifier);
1464
1527
  if (module === undefined) {
@@ -1529,7 +1592,7 @@ export function references<Brand>(
1529
1592
  const position = renderPosition(file, root, read.key.getStart());
1530
1593
  const targets =
1531
1594
  symbol === UNANSWERED
1532
- ? (declarationsByName(held).get(read.name) ?? []).map((id) => ({
1595
+ ? (held.byName.get(read.name) ?? []).map((id) => ({
1533
1596
  id,
1534
1597
  stepped: false,
1535
1598
  guessed: true,
package/src/session.ts CHANGED
@@ -13,7 +13,7 @@ import type {
13
13
  Symbol as TSSymbol,
14
14
  TimingInfo,
15
15
  } from "@typescript/native/sync";
16
- import type { Node, SourceFile } from "@typescript/native/ast";
16
+ import type { Node, SourceFile, StringLiteralLikeNode } from "@typescript/native/ast";
17
17
  import { componentLayer, mapperManifest } from "./component-layer.ts";
18
18
  import type { Host, TemporaryDirectory } from "./host.ts";
19
19
  import { joinPath } from "./paths.ts";
@@ -242,6 +242,11 @@ export interface ProjectView<Brand> {
242
242
  * file it is in.
243
243
  */
244
244
  ownPaths(): ReadonlySet<string>;
245
+ /**
246
+ * The file the compiler's module resolution names for each module specifier of this
247
+ * project's files, in order, or `undefined` where it names none.
248
+ */
249
+ resolvedFileNames(specifiers: readonly StringLiteralLikeNode[]): (string | undefined)[];
245
250
  /** A handle to one node of this project's program. */
246
251
  handle(node: Node): Handle<Brand>;
247
252
  /**
@@ -347,6 +352,17 @@ function viewOf<Brand>(
347
352
  ownKeys ??= new Set(ownSourceFiles().map((file) => file.path));
348
353
  return ownKeys;
349
354
  },
355
+ resolvedFileNames: (specifiers) =>
356
+ batched(engine, specifiers, (node) =>
357
+ project.program.getResolvedModuleFromModuleSpecifier.gen(
358
+ node,
359
+ node.getSourceFile().fileName,
360
+ ),
361
+ ).map((resolved) =>
362
+ resolved === undefined || resolved.resolvedFileName === ""
363
+ ? undefined
364
+ : resolved.resolvedFileName,
365
+ ),
350
366
  handle: (node) => ({ node }),
351
367
  symbolsAt: (handles, cap) =>
352
368
  queries.symbolsAt(