@cplieger/deadset-ts 5.1.0 → 5.2.0

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 (73) hide show
  1. package/README.md +1 -1
  2. package/dist/src/analysis.js +79 -37
  3. package/dist/src/config.js +3 -0
  4. package/dist/src/configuration-files.js +24 -25
  5. package/dist/src/conformance-record.js +2 -2
  6. package/dist/src/contextual-uses.js +46 -18
  7. package/dist/src/convention-rows.js +7 -0
  8. package/dist/src/conventions.js +97 -33
  9. package/dist/src/dependencies.js +8 -0
  10. package/dist/src/entry-point-gaps.js +10 -26
  11. package/dist/src/entry-points.js +4 -0
  12. package/dist/src/exempt.js +23 -8
  13. package/dist/src/file-facts.js +23 -20
  14. package/dist/src/findings/completion.js +27 -11
  15. package/dist/src/findings/interfaces.js +2 -6
  16. package/dist/src/findings/intra-function.js +15 -3
  17. package/dist/src/findings/unused-declarations.js +5 -6
  18. package/dist/src/findings/visibility-narrowing.js +6 -1
  19. package/dist/src/generated-file.js +72 -12
  20. package/dist/src/graph.js +6 -5
  21. package/dist/src/html-entries.js +137 -0
  22. package/dist/src/inventory.js +13 -3
  23. package/dist/src/kinds.js +2 -2
  24. package/dist/src/manifest.js +34 -5
  25. package/dist/src/matrix.js +9 -3
  26. package/dist/src/namespace-objects.js +158 -0
  27. package/dist/src/outside-tests.js +139 -0
  28. package/dist/src/overrides.js +104 -0
  29. package/dist/src/paths.js +17 -0
  30. package/dist/src/references.js +150 -4
  31. package/dist/src/roots.js +172 -39
  32. package/dist/src/session.js +4 -3
  33. package/dist/src/sweep.js +17 -0
  34. package/dist/src/template-field.js +60 -3
  35. package/dist/src/test-support.js +35 -23
  36. package/dist/src/version.js +1 -1
  37. package/jsr.json +1 -1
  38. package/package.json +1 -1
  39. package/src/analysis.ts +122 -49
  40. package/src/config.ts +3 -0
  41. package/src/configuration-files.ts +29 -25
  42. package/src/conformance-record.ts +2 -2
  43. package/src/contextual-uses.ts +51 -20
  44. package/src/convention-rows.ts +15 -0
  45. package/src/conventions.ts +119 -30
  46. package/src/dependencies.ts +9 -0
  47. package/src/entry-point-gaps.ts +11 -28
  48. package/src/entry-points.ts +7 -1
  49. package/src/exempt.ts +29 -12
  50. package/src/file-facts.ts +25 -21
  51. package/src/findings/completion.ts +31 -11
  52. package/src/findings/interfaces.ts +2 -9
  53. package/src/findings/intra-function.ts +17 -3
  54. package/src/findings/unused-declarations.ts +5 -8
  55. package/src/findings/visibility-narrowing.ts +7 -1
  56. package/src/generated-file.ts +80 -12
  57. package/src/graph.ts +9 -8
  58. package/src/html-entries.ts +170 -0
  59. package/src/inventory.ts +15 -3
  60. package/src/kinds.ts +2 -2
  61. package/src/manifest.ts +49 -7
  62. package/src/matrix.ts +21 -3
  63. package/src/namespace-objects.ts +216 -0
  64. package/src/outside-tests.ts +191 -0
  65. package/src/overrides.ts +136 -0
  66. package/src/paths.ts +18 -0
  67. package/src/references.ts +206 -9
  68. package/src/roots.ts +210 -53
  69. package/src/session.ts +4 -2
  70. package/src/sweep.ts +19 -0
  71. package/src/template-field.ts +65 -3
  72. package/src/test-support.ts +34 -22
  73. package/src/version.ts +1 -1
@@ -0,0 +1,139 @@
1
+ /**
2
+ * The test files no compiler configuration of a run holds. The runner executes them all
3
+ * the same, so each module one names is a test reference: the own file a relative
4
+ * specifier names, and the declarations that file exports under the names read from it.
5
+ * Such a file is read without a program, so what its body references beyond the modules
6
+ * it names is not known.
7
+ */
8
+ import { isCallExpression, isExportDeclaration, isImportDeclaration, isNamespaceExport, isNamespaceImport, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
9
+ import { RESOLVED_EXTENSIONS } from "./configuration-files.js";
10
+ import { emittedFrom } from "./emit-map.js";
11
+ import { dirnamePath, joinPath, normalizePath, queriedModulePath } from "./paths.js";
12
+ import { renderPosition } from "./position.js";
13
+ import { nameComponent } from "./ref.js";
14
+ /** The paths below the target root one relative specifier may name, written path first. */
15
+ function candidates(dir, specifier) {
16
+ if (!specifier.startsWith("./") && !specifier.startsWith("../")) {
17
+ return [];
18
+ }
19
+ const path = normalizePath(dir === "" ? specifier : `${dir}/${specifier}`);
20
+ return [
21
+ path,
22
+ ...emittedFrom(path, { sourceDirs: [] }),
23
+ ...RESOLVED_EXTENSIONS.map((extension) => path + extension),
24
+ ...RESOLVED_EXTENSIONS.map((extension) => `${path}/index${extension}`),
25
+ ];
26
+ }
27
+ /** The text of a specifier written as literal text, or undefined for any other expression. */
28
+ function literalText(node) {
29
+ return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node))
30
+ ? node.text
31
+ : undefined;
32
+ }
33
+ /**
34
+ * Every module one file names: an import declaration, a re-export, and an `import()` call
35
+ * of literal text anywhere in the file. A namespace form, a star re-export, a bare import
36
+ * and an `import()` evaluate the module.
37
+ */
38
+ function modulesNamed(file) {
39
+ const found = [];
40
+ const name = (specifier, names, evaluates) => {
41
+ const text = literalText(specifier);
42
+ if (text !== undefined) {
43
+ found.push({ specifier, text, names, evaluates });
44
+ }
45
+ };
46
+ for (const statement of file.statements) {
47
+ if (isImportDeclaration(statement)) {
48
+ const clause = statement.importClause;
49
+ const bindings = clause?.namedBindings;
50
+ name(statement.moduleSpecifier, [
51
+ ...(clause?.name === undefined ? [] : ["default"]),
52
+ ...(bindings === undefined || isNamespaceImport(bindings)
53
+ ? []
54
+ : bindings.elements.map((one) => (one.propertyName ?? one.name).text)),
55
+ ], bindings === undefined ? clause?.name === undefined : isNamespaceImport(bindings));
56
+ }
57
+ else if (isExportDeclaration(statement) && statement.moduleSpecifier !== undefined) {
58
+ const clause = statement.exportClause;
59
+ const star = clause === undefined || isNamespaceExport(clause);
60
+ name(statement.moduleSpecifier, star ? [] : clause.elements.map((one) => (one.propertyName ?? one.name).text), star);
61
+ }
62
+ }
63
+ const visit = (node) => {
64
+ if (isCallExpression(node) && node.expression.kind === SyntaxKind.ImportKeyword) {
65
+ const [argument] = node.arguments;
66
+ if (argument !== undefined) {
67
+ name(argument, [], true);
68
+ }
69
+ }
70
+ node.forEachChild(visit);
71
+ };
72
+ file.forEachChild(visit);
73
+ return found;
74
+ }
75
+ /** Each of the given paths below the target root, parsed; one that cannot be read is skipped. */
76
+ export function testFilesAt(host, targetRoot, paths, parse) {
77
+ return paths.flatMap((path) => {
78
+ const fileName = joinPath(targetRoot, path);
79
+ let text;
80
+ try {
81
+ text = host.readFile(fileName);
82
+ }
83
+ catch {
84
+ return [];
85
+ }
86
+ return [{ path, file: parse(fileName, text) }];
87
+ });
88
+ }
89
+ /**
90
+ * The references the given test files make to one project's own files through the
91
+ * modules they name ({@link modulesNamed}). A name read from a module references each
92
+ * top-level declaration the file exports under it; an evaluating form references the
93
+ * file. A query after the path names the file as {@link queriedModulePath} reads it.
94
+ */
95
+ export function outsideTestReferences(tests, held, targetRoot) {
96
+ const fileAt = new Map(held.symbols
97
+ .filter((symbol) => symbol.kind === "file")
98
+ .map((symbol) => [symbol.position.path, symbol.id]));
99
+ // Keyed on the reference's fragment, which spells the export name where the display
100
+ // name of a default export is its declared name.
101
+ const exported = new Map();
102
+ for (const symbol of held.symbols) {
103
+ if (symbol.exported) {
104
+ const fragment = symbol.ref.slice(symbol.ref.lastIndexOf("#") + 1).replace(/:alias$/u, "");
105
+ const key = `${symbol.parent}\u0000${fragment}`;
106
+ exported.set(key, [...(exported.get(key) ?? []), symbol.id]);
107
+ }
108
+ }
109
+ const found = [];
110
+ for (const { path, file } of tests) {
111
+ const from = `${path}#`;
112
+ const dir = dirnamePath(path) === "." ? "" : dirnamePath(path);
113
+ for (const named of modulesNamed(file)) {
114
+ const written = named.text.includes("?") ? queriedModulePath(named.text) : named.text;
115
+ if (written === undefined) {
116
+ continue;
117
+ }
118
+ const fileId = candidates(dir, written)
119
+ .map((one) => fileAt.get(one))
120
+ .find((one) => one !== undefined);
121
+ if (fileId === undefined) {
122
+ continue;
123
+ }
124
+ const position = renderPosition(file, targetRoot, named.specifier.getStart());
125
+ const read = (to, use) => {
126
+ found.push({ from, to, position, use, resolution: "syntax", test: true, unheld: true });
127
+ };
128
+ if (named.evaluates) {
129
+ read(fileId, "evaluation");
130
+ }
131
+ for (const name of named.names) {
132
+ for (const id of exported.get(`${fileId}\u0000${nameComponent(name)}`) ?? []) {
133
+ read(id, "read");
134
+ }
135
+ }
136
+ }
137
+ }
138
+ return found;
139
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The overrides of the target's class members. A call through the base class, `this`
3
+ * in the base's own body or a value typed as the base, names the base's member and
4
+ * runs the override of whichever subclass the value is, so the base member's
5
+ * declaration references every override as a use of it.
6
+ */
7
+ import { isClassDeclaration, isClassExpression, isExpressionWithTypeArguments, isPropertyAccessExpression, isQualifiedName, SyntaxKind, } from "@typescript/native/unstable/ast";
8
+ import { memberComponent } from "./class-members.js";
9
+ import { nodeKey } from "./inventory.js";
10
+ import { UNANSWERED } from "./query.js";
11
+ /** The member kinds a subclass overrides. */
12
+ const OVERRIDABLE = new Set(["method", "class-member"]);
13
+ /** The node naming the class one `extends` entry names. */
14
+ function nameOf(entry) {
15
+ if (isExpressionWithTypeArguments(entry)) {
16
+ return isPropertyAccessExpression(entry.expression) ? entry.expression.name : entry.expression;
17
+ }
18
+ return isQualifiedName(entry.typeName) ? entry.typeName.right : entry.typeName;
19
+ }
20
+ /** Each class of the target whose `extends` clause names a class, with the name node. */
21
+ function extending(project, held) {
22
+ const found = [];
23
+ for (const file of project.ownSourceFiles()) {
24
+ const visit = (node) => {
25
+ if (isClassDeclaration(node) || isClassExpression(node)) {
26
+ const id = held.declarations.get(nodeKey(file, node));
27
+ const clause = node.heritageClauses?.find((one) => one.token === SyntaxKind.ExtendsKeyword);
28
+ const base = clause?.types[0];
29
+ if (id !== undefined && base !== undefined) {
30
+ found.push({ id, base: nameOf(base) });
31
+ }
32
+ }
33
+ node.forEachChild(visit);
34
+ };
35
+ file.forEachChild(visit);
36
+ }
37
+ return found;
38
+ }
39
+ /**
40
+ * The references each base class member makes to its overrides, for the classes of one
41
+ * project that extend another class of the target. A member a nearer base already
42
+ * overrides is reached through that override. A private member is not overridden.
43
+ * Each reference is placed at the override and made by its file.
44
+ */
45
+ export function overrideReferences(project, held, chains, testFiles) {
46
+ const classes = extending(project, held);
47
+ if (classes.length === 0) {
48
+ return [];
49
+ }
50
+ const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
51
+ const members = new Map();
52
+ for (const symbol of held.symbols) {
53
+ const component = memberComponent(symbol, byId);
54
+ if (component === undefined ||
55
+ !OVERRIDABLE.has(symbol.kind) ||
56
+ symbol.visibility === "private" ||
57
+ symbol.visibility === "private-name") {
58
+ continue;
59
+ }
60
+ const own = members.get(symbol.parent) ?? new Map();
61
+ own.set(component, symbol);
62
+ members.set(symbol.parent, own);
63
+ }
64
+ const bases = new Map();
65
+ const symbols = project.symbolsAt(classes.map((one) => project.handle(one.base)));
66
+ classes.forEach(({ id }, index) => {
67
+ const symbol = symbols[index];
68
+ if (symbol !== undefined && symbol !== UNANSWERED) {
69
+ bases.set(id, chains
70
+ .chainOf(symbol)
71
+ .map((target) => target.id)
72
+ .filter((target) => byId.get(target)?.kind === "class" && target !== id));
73
+ }
74
+ });
75
+ /** The nearest member of one component the class or a class it extends declares. */
76
+ const declaredAbove = (classId, component, seen) => {
77
+ if (seen.has(classId)) {
78
+ return [];
79
+ }
80
+ seen.add(classId);
81
+ const own = members.get(classId)?.get(component);
82
+ if (own !== undefined) {
83
+ return [own.id];
84
+ }
85
+ return (bases.get(classId) ?? []).flatMap((base) => declaredAbove(base, component, seen));
86
+ };
87
+ const found = [];
88
+ for (const [classId, baseIds] of bases) {
89
+ for (const [component, member] of members.get(classId) ?? []) {
90
+ const seen = new Set([classId]);
91
+ for (const base of baseIds.flatMap((one) => declaredAbove(one, component, seen))) {
92
+ found.push({
93
+ from: base,
94
+ to: member.id,
95
+ position: member.position,
96
+ use: "read",
97
+ resolution: "override",
98
+ test: testFiles.has(member.position.path),
99
+ });
100
+ }
101
+ }
102
+ }
103
+ return found;
104
+ }
package/dist/src/paths.js CHANGED
@@ -65,6 +65,23 @@ export function dirnamePath(path) {
65
65
  export function resolvePath(against, path) {
66
66
  return isAbsolutePath(path) ? normalizePath(path) : joinPath(against, path);
67
67
  }
68
+ /**
69
+ * The relative path before the query of a relative module specifier such as
70
+ * `./worker.ts?worker`, the module a bundler loads; undefined for a specifier with no
71
+ * query, one that is not relative, and one whose query has a `raw` parameter, which
72
+ * reads the file as text and so evaluates nothing.
73
+ */
74
+ export function queriedModulePath(specifier) {
75
+ const query = specifier.indexOf("?");
76
+ if (query < 0 || !(specifier.startsWith("./") || specifier.startsWith("../"))) {
77
+ return undefined;
78
+ }
79
+ const raw = specifier
80
+ .slice(query + 1)
81
+ .split("&")
82
+ .some((parameter) => parameter.split("=")[0] === "raw");
83
+ return raw ? undefined : specifier.slice(0, query);
84
+ }
68
85
  /**
69
86
  * One path as it reads below a root, or undefined when it is not below the root.
70
87
  * Both are normalized first, so a root written with a trailing separator or a `.`
@@ -1,10 +1,13 @@
1
- import { isArrayLiteralExpression, isAsExpression, isBinaryExpression, isCallExpression, isDecorator, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExpressionStatement, isExternalModuleReference, isForInStatement, isForOfStatement, isForStatement, isImportDeclaration, isImportEqualsDeclaration, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, isVoidExpression, SyntaxKind, } from "@typescript/native/unstable/ast";
1
+ import { isArrayLiteralExpression, isAsExpression, isBinaryExpression, isCallExpression, isDecorator, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExpressionStatement, isExternalModuleReference, isForInStatement, isForOfStatement, isForStatement, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isMetaProperty, isNewExpression, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, isVoidExpression, SyntaxKind, } from "@typescript/native/unstable/ast";
2
2
  import { aliasChains } from "./alias-chain.js";
3
3
  import { contextualUses } from "./contextual-uses.js";
4
4
  import { destructuring, propertyReadsOf } from "./destructuring.js";
5
+ import { specifierCandidates } from "./configuration-files.js";
6
+ import { namespaceUses } from "./namespace-objects.js";
5
7
  import { globExpression } from "./glob.js";
6
8
  import { declarationsByName, nodeKey } from "./inventory.js";
7
- import { relativePath } from "./paths.js";
9
+ import { overrideReferences } from "./overrides.js";
10
+ import { dirnamePath, queriedModulePath, relativePath, resolvePath } from "./paths.js";
8
11
  import { byPosition, isComponentFile, renderPosition } from "./position.js";
9
12
  import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED } from "./query.js";
10
13
  import { typeQueryAliases } from "./type-query-alias.js";
@@ -168,6 +171,44 @@ function markStores(node, writes, reads) {
168
171
  }
169
172
  }
170
173
  }
174
+ /** Whether one node is `import.meta.url`. */
175
+ function isImportMetaURL(node) {
176
+ const at = unparenthesized(node);
177
+ return (isPropertyAccessExpression(at) &&
178
+ isMetaProperty(at.expression) &&
179
+ at.expression.keywordToken === SyntaxKind.ImportKeyword &&
180
+ at.name.text === "url");
181
+ }
182
+ /** The module URL one node builds, where it builds one. */
183
+ function moduleURLOf(node) {
184
+ const relative = (argument) => {
185
+ const at = argument === undefined ? undefined : unparenthesized(argument);
186
+ return at !== undefined &&
187
+ (isStringLiteral(at) || isNoSubstitutionTemplateLiteral(at)) &&
188
+ /^\.\.?\//u.test(at.text)
189
+ ? at.text
190
+ : undefined;
191
+ };
192
+ if (isNewExpression(node) && isIdentifier(node.expression) && node.expression.text === "URL") {
193
+ const [reference, base] = node.arguments ?? [];
194
+ const specifier = relative(reference);
195
+ return specifier !== undefined && base !== undefined && isImportMetaURL(base)
196
+ ? { specifier, constructor: node.expression, at: node }
197
+ : undefined;
198
+ }
199
+ if (isCallExpression(node)) {
200
+ const callee = unparenthesized(node.expression);
201
+ const specifier = relative(node.arguments[0]);
202
+ if (specifier !== undefined &&
203
+ isPropertyAccessExpression(callee) &&
204
+ isMetaProperty(callee.expression) &&
205
+ callee.expression.keywordToken === SyntaxKind.ImportKeyword &&
206
+ callee.name.text === "resolve") {
207
+ return { specifier, constructor: undefined, at: node };
208
+ }
209
+ }
210
+ return undefined;
211
+ }
171
212
  /** Whether one node is a string literal or a template literal with no substitution. */
172
213
  function isLiteralText(node) {
173
214
  return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
@@ -200,6 +241,19 @@ function evaluatedBy(node) {
200
241
  }
201
242
  return undefined;
202
243
  }
244
+ /**
245
+ * The own file a module specifier with a query evaluates ({@link queriedModulePath}): the
246
+ * checker resolves the whole specifier to whatever module declaration matches it.
247
+ */
248
+ function queriedFile(file, specifier, fileIdByName) {
249
+ if (!isStringLiteral(specifier) && !isNoSubstitutionTemplateLiteral(specifier)) {
250
+ return undefined;
251
+ }
252
+ const path = queriedModulePath(specifier.text);
253
+ return path === undefined
254
+ ? undefined
255
+ : fileIdByName.get(resolvePath(dirnamePath(file.fileName), path));
256
+ }
203
257
  /**
204
258
  * The re-exports one node writes, if it is an export declaration. A specifier and a
205
259
  * namespace export are declarations whose names are where they are written, so no use is
@@ -272,7 +326,10 @@ function sitesOf(file, declarations, fileId, unread) {
272
326
  const reads = [];
273
327
  const links = [];
274
328
  const evaluations = [];
329
+ const wholes = [];
330
+ const urls = [];
275
331
  const decorated = [];
332
+ const namespaces = namespaceUses(file);
276
333
  const literals = [];
277
334
  const flows = [];
278
335
  const writes = new Set();
@@ -301,6 +358,13 @@ function sitesOf(file, declarations, fileId, unread) {
301
358
  if (unread.has(node)) {
302
359
  return;
303
360
  }
361
+ const namespace = namespaces.get(node);
362
+ if (namespace?.kind === "whole") {
363
+ wholes.push({ specifier: namespace.specifier, at: node, from: enclosing });
364
+ }
365
+ else if (namespace?.kind === "indexed") {
366
+ found.push({ node: namespace.literal, shorthand: undefined, from: enclosing, use: "read" });
367
+ }
304
368
  if (isName(node)) {
305
369
  if (!declared.has(node.pos)) {
306
370
  const site = { node, shorthand: shorthands.get(node.pos), from: enclosing };
@@ -330,6 +394,10 @@ function sitesOf(file, declarations, fileId, unread) {
330
394
  if (evaluated !== undefined) {
331
395
  evaluations.push({ node: evaluated, from: enclosing });
332
396
  }
397
+ const url = moduleURLOf(node);
398
+ if (url !== undefined) {
399
+ urls.push(url);
400
+ }
333
401
  const held = declarations.get(nodeKey(file, node));
334
402
  const outer = enclosing;
335
403
  if (held !== undefined) {
@@ -344,7 +412,7 @@ function sitesOf(file, declarations, fileId, unread) {
344
412
  enclosing = outer;
345
413
  };
346
414
  file.forEachChild(visit);
347
- return { uses: found, literals, flows, reads, links, evaluations, decorated };
415
+ return { uses: found, literals, flows, reads, links, evaluations, wholes, urls, decorated };
348
416
  }
349
417
  /** The declaration kinds of a property an object literal declares. */
350
418
  const LITERAL_PROPERTY_KINDS = new Set([
@@ -569,6 +637,30 @@ export function references(project, held, targetRoot, options) {
569
637
  }
570
638
  return topLevel.get(fileId) ?? [];
571
639
  };
640
+ const exportsCache = new Map();
641
+ /**
642
+ * Every declaration one module exports, each re-export walked along its chain. A module
643
+ * the checker left unanswered may be any file, so it reads every file's exports.
644
+ */
645
+ const exportsRead = (module) => {
646
+ if (module === UNANSWERED) {
647
+ return [...fileIds].flatMap((id) => declaredIn(id)
648
+ .filter((symbol) => symbol.exported)
649
+ .map((symbol) => ({ id: symbol.id, stepped: false, guessed: true })));
650
+ }
651
+ const known = exportsCache.get(module.id);
652
+ if (known !== undefined) {
653
+ return known;
654
+ }
655
+ const table = project.queries.exportsOfModule(module);
656
+ const read = table === UNANSWERED
657
+ ? chains.declarationsOf(module).flatMap((id) => declaredIn(id)
658
+ .filter((symbol) => symbol.exported)
659
+ .map((symbol) => ({ id: symbol.id, stepped: false, guessed: true })))
660
+ : table.flatMap((exported) => chains.chainOf(exported));
661
+ exportsCache.set(module.id, read);
662
+ return read;
663
+ };
572
664
  /** The identifier of the declaration one node of this program is, where it is one. */
573
665
  const declaredAt = (node) => declarations.get(nodeKey(node.getSourceFile(), node));
574
666
  /**
@@ -654,6 +746,7 @@ export function references(project, held, targetRoot, options) {
654
746
  const fileId = declaredAt(file) ?? "";
655
747
  return { file, fileId, ...sitesOf(file, declarations, fileId, unread) };
656
748
  });
749
+ const fileIdByName = new Map(walked.filter((one) => fileIds.has(one.fileId)).map((one) => [one.file.fileName, one.fileId]));
657
750
  const contextual = contextualUses(project, held, chains, walked.flatMap((one) => one.literals), walked.flatMap((one) => one.flows), cap, tables);
658
751
  const writtenBy = new Map();
659
752
  for (const write of contextual.writes) {
@@ -731,7 +824,7 @@ export function references(project, held, targetRoot, options) {
731
824
  }
732
825
  }
733
826
  }
734
- for (const { file, fileId, uses, reads, links, evaluations, decorated } of walked) {
827
+ for (const { file, fileId, uses, reads, links, evaluations, wholes, urls, decorated } of walked) {
735
828
  const test = testFile.get(file) === true;
736
829
  // A component file's markup may use any binding its blocks declare at the top level.
737
830
  const markup = isComponentFile(file);
@@ -759,6 +852,8 @@ export function references(project, held, targetRoot, options) {
759
852
  ...uses.filter((site) => site.shorthand === undefined).map((site) => site.node),
760
853
  ...links.map((link) => link.node),
761
854
  ...evaluations.map((evaluation) => evaluation.node),
855
+ ...wholes.map((whole) => whole.specifier),
856
+ ...urls.flatMap((url) => url.constructor ?? []),
762
857
  ...bindings.map((binding) => binding.node),
763
858
  ]),
764
859
  ];
@@ -796,6 +891,17 @@ export function references(project, held, targetRoot, options) {
796
891
  }
797
892
  }
798
893
  for (const evaluation of evaluations) {
894
+ const queried = queriedFile(file, evaluation.node, fileIdByName);
895
+ if (queried !== undefined) {
896
+ found.push({
897
+ from: evaluation.from,
898
+ to: queried,
899
+ position: renderPosition(file, root, evaluation.node.getStart()),
900
+ use: "evaluation",
901
+ resolution: "syntax",
902
+ test,
903
+ });
904
+ }
799
905
  const module = answered.get(evaluation.node);
800
906
  if (module === undefined) {
801
907
  continue;
@@ -816,6 +922,43 @@ export function references(project, held, targetRoot, options) {
816
922
  }
817
923
  }
818
924
  }
925
+ for (const url of urls) {
926
+ const constructor = url.constructor === undefined ? undefined : answered.get(url.constructor);
927
+ if (typeof constructor === "object" &&
928
+ constructor.declarations.some((handle) => ownPaths.has(handle.path))) {
929
+ continue;
930
+ }
931
+ const loaded = specifierCandidates(dirnamePath(file.fileName), url.specifier)
932
+ .map((path) => fileIdByName.get(path))
933
+ .find((id) => id !== undefined);
934
+ if (loaded !== undefined) {
935
+ found.push({
936
+ from: fileId,
937
+ to: loaded,
938
+ position: renderPosition(file, root, url.at.getStart()),
939
+ use: "evaluation",
940
+ resolution: "syntax",
941
+ test,
942
+ });
943
+ }
944
+ }
945
+ for (const whole of wholes) {
946
+ const module = answered.get(whole.specifier);
947
+ if (module === undefined) {
948
+ continue;
949
+ }
950
+ const position = renderPosition(file, root, whole.at.getStart());
951
+ for (const target of exportsRead(module)) {
952
+ found.push({
953
+ from: whole.from,
954
+ to: target.id,
955
+ position,
956
+ use: "read",
957
+ resolution: target.guessed ? "by-name" : "batch",
958
+ test,
959
+ });
960
+ }
961
+ }
819
962
  if (markup) {
820
963
  for (const symbol of declaredIn(fileId)) {
821
964
  found.push({
@@ -924,6 +1067,9 @@ export function references(project, held, targetRoot, options) {
924
1067
  }
925
1068
  }
926
1069
  }
1070
+ if (consumer === undefined) {
1071
+ found.push(...overrideReferences(project, held, chains, new Set(tests)));
1072
+ }
927
1073
  found.sort((a, b) => byPosition(a.position, b.position) ||
928
1074
  compare(a.to, b.to) ||
929
1075
  compare(a.from, b.from) ||