@cplieger/deadset-ts 7.2.0-dev.3 → 8.0.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/dist/bin/mapper-connection.js +7 -4
  2. package/dist/src/analysis.js +116 -16
  3. package/dist/src/augmentations.js +21 -8
  4. package/dist/src/automatic-types.js +189 -0
  5. package/dist/src/base-url.js +18 -0
  6. package/dist/src/component-files.js +96 -31
  7. package/dist/src/components.js +7 -6
  8. package/dist/src/config.js +1 -0
  9. package/dist/src/configuration-files.js +54 -15
  10. package/dist/src/configured-declarations.js +24 -2
  11. package/dist/src/conformance-record.js +2 -2
  12. package/dist/src/contextual-uses.js +26 -26
  13. package/dist/src/convention-rows.js +75 -0
  14. package/dist/src/conventions.js +101 -5
  15. package/dist/src/decorator.js +21 -18
  16. package/dist/src/dependencies.js +185 -30
  17. package/dist/src/destructuring.js +77 -2
  18. package/dist/src/discover.js +188 -85
  19. package/dist/src/edges.js +1 -1
  20. package/dist/src/entry-point-gaps.js +7 -10
  21. package/dist/src/entry-points.js +112 -11
  22. package/dist/src/findings/completion.js +116 -21
  23. package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
  24. package/dist/src/findings/emitters.js +1 -1
  25. package/dist/src/findings/intra-function.js +44 -13
  26. package/dist/src/findings/non-code-artifacts.js +66 -33
  27. package/dist/src/findings/unused-declarations.js +21 -1
  28. package/dist/src/findings/visibility-narrowing.js +22 -3
  29. package/dist/src/generated-file.js +11 -0
  30. package/dist/src/git-ignore.js +86 -0
  31. package/dist/src/graph.js +4 -1
  32. package/dist/src/import-bindings.js +97 -0
  33. package/dist/src/indexed-access.js +130 -28
  34. package/dist/src/interface-satisfaction.js +30 -12
  35. package/dist/src/intra-function-parts.js +313 -50
  36. package/dist/src/inventory.js +63 -8
  37. package/dist/src/json-document.js +5 -5
  38. package/dist/src/kinds.js +13 -13
  39. package/dist/src/matrix.js +18 -9
  40. package/dist/src/mdx-entries.js +197 -0
  41. package/dist/src/module-declarations.js +9 -13
  42. package/dist/src/namespace-objects.js +28 -18
  43. package/dist/src/overrides.js +45 -9
  44. package/dist/src/paths.js +9 -0
  45. package/dist/src/print.js +1 -0
  46. package/dist/src/ref.js +10 -0
  47. package/dist/src/references.js +29 -13
  48. package/dist/src/reflective-lookup.js +8 -6
  49. package/dist/src/report.js +4 -2
  50. package/dist/src/reporters/template.js +22 -13
  51. package/dist/src/resolve.js +2 -0
  52. package/dist/src/roots.js +110 -38
  53. package/dist/src/schema.js +1 -0
  54. package/dist/src/serialization-contract.js +97 -20
  55. package/dist/src/session.js +26 -1
  56. package/dist/src/source-files.js +39 -14
  57. package/dist/src/spelled-names.js +58 -0
  58. package/dist/src/suppress-file.js +12 -4
  59. package/dist/src/suppress-inline.js +21 -3
  60. package/dist/src/suppress.js +7 -5
  61. package/dist/src/sweep.js +27 -10
  62. package/dist/src/symbol-keys.js +103 -2
  63. package/dist/src/template-field.js +9 -6
  64. package/dist/src/test-support.js +121 -17
  65. package/dist/src/type-errors.js +116 -35
  66. package/dist/src/type-parameter-reads.js +172 -0
  67. package/dist/src/value-reach.js +17 -5
  68. package/dist/src/verbs/analyze.js +19 -1
  69. package/dist/src/verbs/explain.js +4 -2
  70. package/dist/src/version-range.js +10 -5
  71. package/dist/src/version.js +1 -1
  72. package/dist/src/workflow-steps.js +119 -22
  73. package/dist/src/workspace-programs.js +7 -3
  74. package/dist/src/workspace-resolution.js +72 -13
  75. package/dist/src/workspace.js +1 -1
  76. package/jsr.json +1 -1
  77. package/package.json +2 -2
  78. package/src/analysis.ts +175 -16
  79. package/src/augmentations.ts +21 -10
  80. package/src/automatic-types.ts +246 -0
  81. package/src/base-url.ts +24 -0
  82. package/src/component-files.ts +116 -31
  83. package/src/components.ts +7 -6
  84. package/src/config.ts +3 -0
  85. package/src/configuration-files.ts +80 -18
  86. package/src/configured-declarations.ts +33 -4
  87. package/src/conformance-record.ts +2 -2
  88. package/src/contextual-uses.ts +27 -27
  89. package/src/convention-rows.ts +103 -0
  90. package/src/conventions.ts +121 -7
  91. package/src/decorator.ts +28 -18
  92. package/src/dependencies.ts +249 -43
  93. package/src/destructuring.ts +93 -2
  94. package/src/discover.ts +234 -86
  95. package/src/edges.ts +1 -1
  96. package/src/entry-point-gaps.ts +8 -11
  97. package/src/entry-points.ts +133 -11
  98. package/src/finding.ts +10 -0
  99. package/src/findings/completion.ts +147 -24
  100. package/src/findings/dependencies-and-module-machinery.ts +90 -28
  101. package/src/findings/emitters.ts +1 -1
  102. package/src/findings/intra-function.ts +56 -14
  103. package/src/findings/non-code-artifacts.ts +81 -41
  104. package/src/findings/unused-declarations.ts +25 -1
  105. package/src/findings/visibility-narrowing.ts +32 -3
  106. package/src/generated-file.ts +16 -0
  107. package/src/git-ignore.ts +98 -0
  108. package/src/graph.ts +6 -0
  109. package/src/host.ts +5 -9
  110. package/src/import-bindings.ts +128 -0
  111. package/src/indexed-access.ts +152 -30
  112. package/src/interface-satisfaction.ts +33 -13
  113. package/src/intra-function-parts.ts +415 -64
  114. package/src/inventory.ts +83 -7
  115. package/src/json-document.ts +7 -7
  116. package/src/kinds.ts +13 -13
  117. package/src/matrix.ts +32 -14
  118. package/src/mdx-entries.ts +241 -0
  119. package/src/module-declarations.ts +11 -13
  120. package/src/namespace-objects.ts +34 -21
  121. package/src/overrides.ts +56 -9
  122. package/src/paths.ts +10 -0
  123. package/src/print.ts +1 -0
  124. package/src/ref.ts +12 -0
  125. package/src/references.ts +38 -17
  126. package/src/reflective-lookup.ts +9 -7
  127. package/src/report.ts +16 -3
  128. package/src/reporters/template.ts +21 -13
  129. package/src/resolve.ts +3 -0
  130. package/src/roots.ts +163 -45
  131. package/src/schema.ts +1 -0
  132. package/src/serialization-contract.ts +108 -17
  133. package/src/session.ts +29 -1
  134. package/src/source-files.ts +66 -16
  135. package/src/spelled-names.ts +102 -0
  136. package/src/suppress-file.ts +14 -3
  137. package/src/suppress-inline.ts +21 -2
  138. package/src/suppress.ts +9 -5
  139. package/src/sweep.ts +32 -11
  140. package/src/symbol-keys.ts +129 -2
  141. package/src/template-field.ts +9 -6
  142. package/src/test-support.ts +149 -16
  143. package/src/type-errors.ts +132 -41
  144. package/src/type-parameter-reads.ts +227 -0
  145. package/src/value-reach.ts +19 -6
  146. package/src/verbs/analyze.ts +28 -1
  147. package/src/verbs/explain.ts +5 -2
  148. package/src/version-range.ts +10 -5
  149. package/src/version.ts +1 -1
  150. package/src/workflow-steps.ts +127 -22
  151. package/src/workspace-programs.ts +7 -3
  152. package/src/workspace-resolution.ts +86 -14
  153. package/src/workspace.ts +1 -1
@@ -0,0 +1,241 @@
1
+ /**
2
+ * The files the import declarations of the target's Markdown and MDX documents load. A
3
+ * document is read as text: a line outside a fenced code block that starts with `import`
4
+ * is an import declaration, which an MDX compiler hoists into the module it builds.
5
+ */
6
+
7
+ import type { SourceFile } from "@typescript/native/ast";
8
+ import { resolveRelativeSpecifier } from "./configuration-files.ts";
9
+ import type { Host } from "./host.ts";
10
+ import { dirnamePath, joinPath, resolvePath } from "./paths.ts";
11
+ import type { SourceFiles } from "./source-files.ts";
12
+
13
+ /** One file a document imports, the specifier that named it, and the export names it reads. */
14
+ interface DocumentEntry {
15
+ readonly file: SourceFile;
16
+ readonly source: string;
17
+ /** The export names the import reads, or undefined for a namespace import, which reads them all. */
18
+ readonly names: readonly string[] | undefined;
19
+ }
20
+
21
+ /** The directories no document is read below. */
22
+ const SKIPPED: ReadonlySet<string> = new Set(["node_modules", ".git"]);
23
+
24
+ /** The extensions of a document whose imports an MDX compiler reads. */
25
+ const DOCUMENT = /\.mdx?$/u;
26
+
27
+ /** A fence line, its marker run and what follows it captured. */
28
+ const FENCE = /^ {0,3}(`{3,}|~{3,})(.*)$/u;
29
+
30
+ /** A string literal in either quote, its escapes included. */
31
+ const STRING = String.raw`"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'`;
32
+
33
+ /** An import declaration at the start of a line, its clause and its specifier captured. */
34
+ const IMPORT_LINE = new RegExp(
35
+ String.raw`^import\s+(?:((?:[^"'\n]|${STRING})*?)\s+from\s*)?["']([^"'\n]+)["']`,
36
+ "u",
37
+ );
38
+
39
+ /** The braces of an import clause, what they hold captured. */
40
+ const BRACES = new RegExp(String.raw`\{((?:[^}"']|${STRING})*)\}`, "u");
41
+
42
+ /** One comma-separated part of a clause's braces. */
43
+ const PART = new RegExp(String.raw`(?:[^,"']|${STRING})+`, "gu");
44
+
45
+ /** A named import of a clause's braces: the exported name and an optional local alias. */
46
+ const NAMED_IMPORT = new RegExp(String.raw`^([\w$]+|${STRING})(?:\s+as\s+[\w$]+)?$`, "u");
47
+
48
+ /** The single-character escapes of a string literal and the character each stands for. */
49
+ const SINGLE_ESCAPES: ReadonlyMap<string, string> = new Map([
50
+ ["b", "\b"],
51
+ ["f", "\f"],
52
+ ["n", "\n"],
53
+ ["r", "\r"],
54
+ ["t", "\t"],
55
+ ["v", "\v"],
56
+ ["0", "\0"],
57
+ ["'", "'"],
58
+ ['"', '"'],
59
+ ["\\", "\\"],
60
+ ]);
61
+
62
+ /**
63
+ * One escape of a string literal: two hexadecimal digits, a code unit, a code point, or one
64
+ * character and the digit after it.
65
+ */
66
+ const ESCAPE = /\\(?:x([\da-fA-F]{2})|u([\da-fA-F]{4})|u\{([\da-fA-F]+)\}|(.)(\d?))/gu;
67
+
68
+ /** What one escape stands for, or undefined for one a module cannot hold. */
69
+ function unescaped([, hex, unit, point, single, digit]: RegExpExecArray): string | undefined {
70
+ if (single === undefined) {
71
+ const code = Number.parseInt(hex ?? unit ?? point ?? "", 16);
72
+ return code <= 0x10ffff ? String.fromCodePoint(code) : undefined;
73
+ }
74
+ return /[1-9xu]/u.test(single) || (single === "0" && digit !== "")
75
+ ? undefined
76
+ : `${SINGLE_ESCAPES.get(single) ?? single}${digit ?? ""}`;
77
+ }
78
+
79
+ /**
80
+ * The value of a quoted export name, its escapes decoded, or undefined for an escape this
81
+ * reading does not decode. A module is strict code, so no legacy octal escape is valid.
82
+ */
83
+ function quotedName(literal: string): string | undefined {
84
+ const body = literal.slice(1, -1);
85
+ let value = "";
86
+ let from = 0;
87
+ for (const escape of body.matchAll(ESCAPE)) {
88
+ const decoded = unescaped(escape);
89
+ if (decoded === undefined) {
90
+ return undefined;
91
+ }
92
+ value += body.slice(from, escape.index) + decoded;
93
+ from = escape.index + escape[0].length;
94
+ }
95
+ return value + body.slice(from);
96
+ }
97
+
98
+ /**
99
+ * The export names one import clause reads: `default` for a default binding and each name
100
+ * its braces list, or undefined for a namespace import, which reads every export, and for
101
+ * a clause this reading does not parse, which may read any.
102
+ */
103
+ function clauseNames(clause: string): readonly string[] | undefined {
104
+ const braces = BRACES.exec(clause);
105
+ const head = (braces === null ? clause : clause.slice(0, braces.index))
106
+ .replace(/,\s*$/u, "")
107
+ .trim();
108
+ if (head.includes("*")) {
109
+ return undefined;
110
+ }
111
+ const names = head === "" ? [] : ["default"];
112
+ if (head !== "" && !/^[\w$]+$/u.test(head)) {
113
+ return undefined;
114
+ }
115
+ for (const part of (braces?.[1]?.match(PART) ?? []).map((one) => one.trim())) {
116
+ const named = NAMED_IMPORT.exec(part);
117
+ if (part === "") {
118
+ continue;
119
+ }
120
+ if (named?.[1] === undefined) {
121
+ return undefined;
122
+ }
123
+ const name = /^["']/u.test(named[1]) ? quotedName(named[1]) : named[1];
124
+ if (name === undefined) {
125
+ return undefined;
126
+ }
127
+ names.push(name);
128
+ }
129
+ return names;
130
+ }
131
+
132
+ /** Every document below `dir`, outside every skipped directory, by absolute path. */
133
+ function documents(host: Host, dir: string): readonly string[] {
134
+ let entries;
135
+ try {
136
+ entries = host.readDirectory(dir);
137
+ } catch {
138
+ return [];
139
+ }
140
+ const found: string[] = [];
141
+ for (const entry of entries) {
142
+ const path = joinPath(dir, entry.name);
143
+ if (entry.directory) {
144
+ if (!SKIPPED.has(entry.name)) {
145
+ found.push(...documents(host, path));
146
+ }
147
+ } else if (DOCUMENT.test(entry.name)) {
148
+ found.push(path);
149
+ }
150
+ }
151
+ return found.sort();
152
+ }
153
+
154
+ /** The relative specifiers one document's import lines name, its fenced code blocks skipped. */
155
+ function importedBy(
156
+ text: string,
157
+ ): readonly { readonly specifier: string; readonly names: readonly string[] | undefined }[] {
158
+ const found: { specifier: string; names: readonly string[] | undefined }[] = [];
159
+ let open: string | undefined;
160
+ for (const line of text.split(/\r?\n/u)) {
161
+ const fence = FENCE.exec(line);
162
+ const marker = fence?.[1];
163
+ if (marker !== undefined && open === undefined) {
164
+ open = marker;
165
+ continue;
166
+ }
167
+ // https://spec.commonmark.org/0.31.2/#fenced-code-blocks: only a run of the opening
168
+ // character, at least as long and followed by nothing, closes a fence.
169
+ if (
170
+ marker !== undefined &&
171
+ open !== undefined &&
172
+ marker.startsWith(open.slice(0, 1)) &&
173
+ marker.length >= open.length &&
174
+ fence?.[2]?.trim() === ""
175
+ ) {
176
+ open = undefined;
177
+ continue;
178
+ }
179
+ const matched = open === undefined ? IMPORT_LINE.exec(line) : null;
180
+ const specifier = matched?.[2];
181
+ if (specifier?.startsWith("./") === true || specifier?.startsWith("../") === true) {
182
+ found.push({ specifier, names: clauseNames(matched?.[1] ?? "") });
183
+ }
184
+ }
185
+ return found;
186
+ }
187
+
188
+ /** One document below a target root, by absolute path, and what its import lines name. */
189
+ interface DocumentImports {
190
+ readonly path: string;
191
+ readonly imports: ReturnType<typeof importedBy>;
192
+ }
193
+
194
+ /**
195
+ * The documents each host's target roots hold, which every project of a run reads alike,
196
+ * kept for the host's life as {@link Host} allows.
197
+ */
198
+ const documentsOf = new WeakMap<Host, Map<string, readonly DocumentImports[]>>();
199
+
200
+ /** Every readable document below `root` and the import lines it writes, read once per host. */
201
+ function documentImports(host: Host, root: string): readonly DocumentImports[] {
202
+ const byRoot = documentsOf.get(host) ?? new Map<string, readonly DocumentImports[]>();
203
+ documentsOf.set(host, byRoot);
204
+ const known = byRoot.get(root);
205
+ if (known !== undefined) {
206
+ return known;
207
+ }
208
+ const found = documents(host, root).flatMap((path) => {
209
+ try {
210
+ return [{ path, imports: importedBy(host.readFile(path)) }];
211
+ } catch {
212
+ return [];
213
+ }
214
+ });
215
+ byRoot.set(root, found);
216
+ return found;
217
+ }
218
+
219
+ /**
220
+ * Every own file one document below the target root imports by a relative specifier,
221
+ * read against the document's directory, on disk or through the emit mapping.
222
+ */
223
+ export function documentEntries(
224
+ host: Host,
225
+ targetRoot: string,
226
+ files: SourceFiles,
227
+ ): readonly DocumentEntry[] {
228
+ const root = resolvePath(host.workingDirectory(), targetRoot);
229
+ const found: DocumentEntry[] = [];
230
+ for (const { path, imports } of documentImports(host, root)) {
231
+ const dir = dirnamePath(path);
232
+ for (const { specifier, names } of imports) {
233
+ const reached = resolveRelativeSpecifier(host, dir, specifier);
234
+ const held = reached === undefined ? undefined : files.byName.get(reached);
235
+ for (const file of held === undefined ? files.named(resolvePath(dir, specifier)) : [held]) {
236
+ found.push({ file, source: specifier, names });
237
+ }
238
+ }
239
+ }
240
+ return found;
241
+ }
@@ -104,6 +104,8 @@ function namesModule(specifier: string, name: string): boolean {
104
104
  interface Named {
105
105
  readonly id: string;
106
106
  readonly members: readonly string[];
107
+ /** Whether the declaration's name is a pattern, which a specifier names by matching it. */
108
+ readonly wildcard: boolean;
107
109
  }
108
110
 
109
111
  /**
@@ -140,7 +142,7 @@ function namedBy(
140
142
  ]),
141
143
  );
142
144
  const members = (children.get(id) ?? []).filter((child) => wanted.has(child.ref));
143
- return [{ id, members: members.map((child) => child.id) }];
145
+ return [{ id, members: members.map((child) => child.id), wildcard: name.includes("*") }];
144
146
  });
145
147
  }
146
148
 
@@ -208,28 +210,24 @@ export function moduleDeclarationReferences<Brand>(
208
210
  imports.forEach((one, index) => {
209
211
  const resolved = moduleOf(declared.length + index);
210
212
  const position = renderPosition(one.file, root, one.specifier.getStart());
211
- const read = (to: string, resolution: Reference["resolution"]): void => {
212
- found.push({
213
- from: one.from,
214
- to,
215
- position,
216
- use: "read",
217
- resolution,
218
- test: testFiles.has(position.path),
219
- });
213
+ const read = (to: string, resolution: Reference["resolution"], test: boolean): void => {
214
+ found.push({ from: one.from, to, position, use: "read", resolution, test });
220
215
  };
216
+ const test = testFiles.has(position.path);
221
217
  const same = new Set(
222
218
  declared
223
219
  .filter((_, at) => resolved !== undefined && resolved === moduleOf(at))
224
220
  .map((declaration) => declaration.id),
225
221
  );
226
222
  for (const id of same) {
227
- read(id, "batch");
223
+ read(id, "batch", test);
228
224
  }
229
225
  for (const declaration of named(one.specifier.text, one.names)) {
230
- if (!same.has(declaration.id)) {
226
+ // A wildcard declaration is how the compiler types the file a matching specifier
227
+ // names, so the import uses it whoever writes it, a test included.
228
+ if (declaration.wildcard || !same.has(declaration.id)) {
231
229
  for (const to of [declaration.id, ...declaration.members]) {
232
- read(to, "syntax");
230
+ read(to, "syntax", test && !declaration.wildcard);
233
231
  }
234
232
  }
235
233
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Module namespace objects: the binding of a namespace import, the value an `await` of a
3
3
  * dynamic `import()` produces, the first parameter of the first callback passed to `then`
4
- * on one, and a `const` binding initialized with one of these. A use reads the exports it
4
+ * on one, and a variable binding initialized with one of these. A use reads the exports it
5
5
  * names and no other: a property access by name or by a literal index, or a destructured
6
6
  * property, names one export, and any other use may read every export.
7
7
  */
@@ -9,22 +9,26 @@
9
9
  import {
10
10
  isArrowFunction,
11
11
  isAwaitExpression,
12
- isBindingElement,
13
12
  isCallExpression,
13
+ isClassStaticBlockDeclaration,
14
14
  isElementAccessExpression,
15
15
  isExpressionStatement,
16
16
  isFunctionExpression,
17
+ isFunctionLikeDeclaration,
17
18
  isIdentifier,
18
19
  isImportDeclaration,
20
+ isModuleBlock,
19
21
  isNamespaceImport,
20
22
  isNoSubstitutionTemplateLiteral,
21
23
  isObjectBindingPattern,
22
24
  isParenthesizedExpression,
23
25
  isPropertyAccessExpression,
24
26
  isQualifiedName,
27
+ isSourceFile,
25
28
  isStringLiteral,
26
29
  isVariableDeclaration,
27
30
  isVariableDeclarationList,
31
+ isVariableStatement,
28
32
  isVoidExpression,
29
33
  NodeFlags,
30
34
  SyntaxKind,
@@ -32,6 +36,7 @@ import {
32
36
  type Node,
33
37
  type SourceFile,
34
38
  } from "@typescript/native/ast";
39
+ import { spellsNoBinding } from "./spelled-names.ts";
35
40
 
36
41
  /** What one use of a namespace object reads beyond the names the checker resolves. */
37
42
  type NamespaceUse =
@@ -62,23 +67,33 @@ function isLiteralImport(node: Node): boolean {
62
67
  return argument !== undefined && isLiteralText(argument);
63
68
  }
64
69
 
65
- /** The node a `const` binding's name is visible below: the block or file its statement is in. */
66
- function scopeOf(declaration: Node): Node {
67
- const statement = declaration.parent.parent;
68
- return statement.parent;
70
+ /**
71
+ * The node the binding one declaration list declares is visible below: for `let` and
72
+ * `const`, the loop whose head declares it, or else the block or file its statement is
73
+ * in; for `var`, the function, namespace body, static block or file that holds it.
74
+ */
75
+ function scopeOf(list: Node): Node {
76
+ if ((list.flags & (NodeFlags.Let | NodeFlags.Const | NodeFlags.Using)) !== 0) {
77
+ return isVariableStatement(list.parent) ? list.parent.parent : list.parent;
78
+ }
79
+ let at = list.parent;
80
+ while (
81
+ !isSourceFile(at) &&
82
+ !isFunctionLikeDeclaration(at) &&
83
+ !isModuleBlock(at) &&
84
+ !isClassStaticBlockDeclaration(at)
85
+ ) {
86
+ at = at.parent;
87
+ }
88
+ return at;
69
89
  }
70
90
 
71
- /** Every identifier below `scope` that spells `name`, other than `declared` and a member's name. */
91
+ /** Every identifier below `scope` that spells `name` as a reference, other than `declared`. */
72
92
  function occurrences(scope: Node, name: string, declared: Node): readonly Identifier[] {
73
93
  const found: Identifier[] = [];
74
94
  const visit = (node: Node): void => {
75
95
  if (isIdentifier(node)) {
76
- const parent = node.parent;
77
- const member =
78
- (isPropertyAccessExpression(parent) && parent.name === node) ||
79
- (isQualifiedName(parent) && parent.right === node) ||
80
- (isBindingElement(parent) && parent.propertyName === node);
81
- if (node !== declared && node.text === name && !member) {
96
+ if (node !== declared && node.text === name && !spellsNoBinding(node)) {
82
97
  found.push(node);
83
98
  }
84
99
  return;
@@ -92,8 +107,8 @@ function occurrences(scope: Node, name: string, declared: Node): readonly Identi
92
107
  /**
93
108
  * Every use of the namespace objects one file holds, keyed by the node the use is written
94
109
  * at, that reads more than a name the checker resolves: an indexed read by a literal, and
95
- * any use that may read every export. A shadowing declaration of a binding's name inside
96
- * its scope is read as the binding, which can only keep an export live.
110
+ * any use that may read every export. A use of a shadowing declaration of a binding's
111
+ * name inside its scope is read as a use of the binding, which can only keep an export live.
97
112
  */
98
113
  export function namespaceUses(file: SourceFile): ReadonlyMap<Node, NamespaceUse> {
99
114
  const uses = new Map<Node, NamespaceUse>();
@@ -142,13 +157,11 @@ export function namespaceUses(file: SourceFile): ReadonlyMap<Node, NamespaceUse>
142
157
  }
143
158
  return;
144
159
  }
160
+ // A binding that is not `const` is followed too: each assignment to it is an
161
+ // occurrence `value` cannot narrow, so a reassigned binding still reads every export.
145
162
  const list = parent.parent;
146
- if (
147
- isIdentifier(pattern) &&
148
- isVariableDeclarationList(list) &&
149
- (list.flags & NodeFlags.Const) !== 0
150
- ) {
151
- binding(pattern, scopeOf(parent), specifier, seen);
163
+ if (isIdentifier(pattern) && isVariableDeclarationList(list)) {
164
+ binding(pattern, scopeOf(list), specifier, seen);
152
165
  return;
153
166
  }
154
167
  }
package/src/overrides.ts CHANGED
@@ -1,10 +1,9 @@
1
1
  /**
2
- * The overrides of the target's class members. A call through the base class names the
3
- * base's member and runs the override of whichever subclass the value is, so the base
4
- * member's declaration references every override. A method, an accessor or a property,
5
- * static or not, overriding a member of a class declared outside the target, at any depth
6
- * of its `extends` chain, is used by outside code holding the instance or the class
7
- * through that class, so its own class references it.
2
+ * The overrides of the target's class members. A call through the base class runs the
3
+ * override of whichever subclass the value is, so the base member references every
4
+ * override. A member, static or not, overriding one of a class declared outside the target
5
+ * at any depth of its `extends` chain, a library's interface-and-variable class such as
6
+ * `Error` included, is used by outside code holding the value, so its own class references it.
8
7
  */
9
8
 
10
9
  import {
@@ -26,6 +25,11 @@ import { nameComponent } from "./ref.ts";
26
25
  import type { Reference } from "./references.ts";
27
26
  import type { ProjectView } from "./session.ts";
28
27
 
28
+ const CLASS_DECLARATIONS: ReadonlySet<SyntaxKind> = new Set([
29
+ SyntaxKind.ClassDeclaration,
30
+ SyntaxKind.ClassExpression,
31
+ ]);
32
+
29
33
  /** The member kinds a subclass overrides. */
30
34
  const OVERRIDABLE: ReadonlySet<string> = new Set(["method", "class-member"]);
31
35
 
@@ -110,9 +114,16 @@ export function overrideReferences<Brand>(
110
114
  if (
111
115
  inTarget.length === 0 &&
112
116
  declared !== UNANSWERED &&
113
- (declared.flags & SymbolFlags.Class) !== 0 &&
114
- declared.declarations.length > 0 &&
115
- declared.declarations.every((handle) => !own.has(handle.path))
117
+ (((declared.flags & SymbolFlags.Class) !== 0 &&
118
+ // The target's augmentation of an outside class merges an interface into it,
119
+ // so only a class declaration says where the class is.
120
+ declared.declarations.some(
121
+ (handle) => CLASS_DECLARATIONS.has(handle.kind) && !own.has(handle.path),
122
+ )) ||
123
+ ((declared.flags & SymbolFlags.Interface) !== 0 &&
124
+ declared.declarations.some(
125
+ (handle) => handle.kind === SyntaxKind.VariableDeclaration && !own.has(handle.path),
126
+ )))
116
127
  ) {
117
128
  outsideBases.set(id, declared);
118
129
  }
@@ -177,11 +188,13 @@ function outsideMemberNames<Brand>(
177
188
  outsideBases: ReadonlyMap<string, TSSymbol>,
178
189
  ): ReadonlyMap<string, ReadonlySet<string>> {
179
190
  const direct = new Map<string, ReadonlySet<string>>();
191
+ const error = errorType(project);
180
192
  for (const [classId, base] of outsideBases) {
181
193
  const instance = project.queries.declaredTypeOf(base);
182
194
  const statics = project.queries.typeOfSymbol(base);
183
195
  const components = [
184
196
  ...namesOf(project, instance === UNANSWERED ? undefined : instance).map(nameComponent),
197
+ ...(instance !== UNANSWERED && reaches(project, instance, error) ? ["toString"] : []),
185
198
  ...namesOf(project, statics === UNANSWERED ? undefined : statics).map(
186
199
  (name) => `${nameComponent(name)}:static`,
187
200
  ),
@@ -210,6 +223,40 @@ function outsideMemberNames<Brand>(
210
223
  return names;
211
224
  }
212
225
 
226
+ /** The global `Error` instance type's identifier, where the program's library declares one. */
227
+ function errorType<Brand>(project: ProjectView<Brand>): number | undefined {
228
+ const symbol = project.queries.resolveName("Error", SymbolFlags.Type, undefined, false);
229
+ if (symbol === UNANSWERED || symbol === undefined) {
230
+ return undefined;
231
+ }
232
+ const type = project.queries.declaredTypeOf(symbol);
233
+ return type === UNANSWERED ? undefined : type.id;
234
+ }
235
+
236
+ /**
237
+ * Whether one type is the `Error` instance type or extends it at any depth: the runtime
238
+ * prints an error through its `toString`, a member the library's `Error` interface omits.
239
+ */
240
+ function reaches<Brand>(
241
+ project: ProjectView<Brand>,
242
+ type: Type,
243
+ target: number | undefined,
244
+ ): boolean {
245
+ const seen = new Set<number>();
246
+ const pending = [type];
247
+ for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
248
+ if (next.id === target) {
249
+ return true;
250
+ }
251
+ if (!seen.has(next.id)) {
252
+ seen.add(next.id);
253
+ const bases = project.queries.baseTypes(next);
254
+ pending.push(...(bases === UNANSWERED ? [] : bases));
255
+ }
256
+ }
257
+ return false;
258
+ }
259
+
213
260
  /** The names of a type's properties; none where the type or its properties are unanswered. */
214
261
  function namesOf<Brand>(project: ProjectView<Brand>, type: Type | undefined): readonly string[] {
215
262
  const properties = type === undefined ? UNANSWERED : project.queries.propertiesOf(type);
package/src/paths.ts CHANGED
@@ -67,6 +67,16 @@ export function dirnamePath(path: string): string {
67
67
  return cut === 0 ? SEPARATOR : normalized.slice(0, cut);
68
68
  }
69
69
 
70
+ /**
71
+ * The extension of one path's last segment, from its last full stop, or the empty string
72
+ * where it has none. A segment that only starts with a full stop has none.
73
+ */
74
+ export function extensionOf(path: string): string {
75
+ const name = path.slice(path.lastIndexOf(SEPARATOR) + 1);
76
+ const dot = name.lastIndexOf(".");
77
+ return dot <= 0 ? "" : name.slice(dot);
78
+ }
79
+
70
80
  /** One path resolved against a directory, absolute where that directory is. */
71
81
  export function resolvePath(against: string, path: string): string {
72
82
  return isAbsolutePath(path) ? normalizePath(path) : joinPath(against, path);
package/src/print.ts CHANGED
@@ -84,6 +84,7 @@ export function printConfig(config: Config, provenance: Provenance): string {
84
84
  members: entry.members,
85
85
  })),
86
86
  serializers: config.ts.serializers.map(declarationOf),
87
+ roots_complete: config.ts.rootsComplete,
87
88
  },
88
89
  provenance: Object.fromEntries(
89
90
  [...provenance]
package/src/ref.ts CHANGED
@@ -49,9 +49,11 @@ export const REF_EXPRESSIONS: readonly string[] = [
49
49
  `^${SCOPE}#$`,
50
50
  `^${SCOPE}#${TS_HEAD}$`,
51
51
  `^${SCOPE}#(?:${TS_QUOTED}\\.)?${TS_HEAD}:alias$`,
52
+ `^${SCOPE}#${TS_HEAD}:import$`,
52
53
  `^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})+$`,
53
54
  `^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})+:static$`,
54
55
  `^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})*(?::static)?<${TS_IDENT}>$`,
56
+ `^${SCOPE}#(?:${TS_HEAD}(?:\\.${TS_MEMBER})*(?::static)?)?\\(${TS_IDENT}\\)$`,
55
57
  `^${MANIFEST_SCOPE}#${TS_PACKAGE}:(?:dependency|dev-dependency|peer-dependency)$`,
56
58
  ];
57
59
 
@@ -151,6 +153,16 @@ export function computedComponent(text: string): string {
151
153
  return `[${text.replaceAll(ASCII_WHITESPACE, "")}]`;
152
154
  }
153
155
 
156
+ /** The reference of an import binding, read against the reference of the module that holds it. */
157
+ export function importRef(moduleRef: string, name: string): string {
158
+ return `${moduleRef}${nameComponent(name)}:import`;
159
+ }
160
+
161
+ /** The reference of a parameter, read against the reference of the declaration that holds it. */
162
+ export function parameterRef(holderRef: string, name: string): string {
163
+ return `${holderRef}(${name})`;
164
+ }
165
+
154
166
  /** The fragment of an export alias, inside the `declare module` block named, if one is. */
155
167
  export function aliasFragment(name: string, block?: string): string {
156
168
  const head = block === undefined ? "" : `${quoted(block)}.`;
package/src/references.ts CHANGED
@@ -60,8 +60,15 @@ import { dirnamePath, queriedModulePath, relativePath, resolvePath } from "./pat
60
60
  import { byPosition, isComponentFile, renderPosition, type Position } from "./position.ts";
61
61
  import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED, type Answer } from "./query.ts";
62
62
  import type { ProjectView } from "./session.ts";
63
+ import { typeParameterReads } from "./type-parameter-reads.ts";
63
64
  import { typeQueryAliases } from "./type-query-alias.ts";
64
- import { computedKeyNames, keyNameOf, keyedUses, type KeyedAccess } from "./symbol-keys.ts";
65
+ import {
66
+ computedKeyNames,
67
+ iterationReferences,
68
+ keyNameOf,
69
+ keyedUses,
70
+ type KeyedAccess,
71
+ } from "./symbol-keys.ts";
65
72
  import { handedOn, valuesOf } from "./value-flow.ts";
66
73
 
67
74
  /**
@@ -196,10 +203,10 @@ export interface ReferenceCost {
196
203
  /** Per-type lookups for the contextual uses: each distinct type's constituents, properties and symbol. */
197
204
  readonly contextualLookups: number;
198
205
  /**
199
- * The requests the passes that read members through the checker's types make: element
200
- * accesses, key comparisons, instantiated signatures and values handed to unimplemented
201
- * callees. They are
202
- * counted where the caller measures the client, through `requestCount`, and are 0 otherwise.
206
+ * The requests the passes after the walk make: the bases overrides are read against,
207
+ * element accesses, key comparisons, instantiated signatures, values handed to
208
+ * unimplemented callees and the modules a `declare module` names. They are counted
209
+ * where the caller measures the client, through `requestCount`, and are 0 otherwise.
203
210
  */
204
211
  readonly typedLookups: number;
205
212
  }
@@ -1060,19 +1067,28 @@ export function references<Brand>(
1060
1067
  const fileIds = new Set(
1061
1068
  held.symbols.filter((symbol) => symbol.kind === "file").map((symbol) => symbol.id),
1062
1069
  );
1063
- let topLevel: ReadonlyMap<string, readonly InventorySymbol[]> | undefined;
1064
- /** The declarations one file's top level holds. */
1065
- const declaredIn = (fileId: string): readonly InventorySymbol[] => {
1066
- if (topLevel === undefined) {
1067
- const byParent = new Map<string, InventorySymbol[]>();
1070
+ let moduleBodies: ReadonlyMap<string, readonly InventorySymbol[]> | undefined;
1071
+ /**
1072
+ * The declarations one module holds directly: a file's top level, or the body of a
1073
+ * `declare module` whose name is a string. Any other declaration holds none here.
1074
+ */
1075
+ const declaredIn = (moduleId: string): readonly InventorySymbol[] => {
1076
+ if (moduleBodies === undefined) {
1077
+ const byModule = new Map<string, InventorySymbol[]>();
1068
1078
  for (const symbol of held.symbols) {
1069
- if (fileIds.has(symbol.parent)) {
1070
- byParent.set(symbol.parent, [...(byParent.get(symbol.parent) ?? []), symbol]);
1079
+ if (!fileIds.has(symbol.parent) && !held.modules.has(symbol.parent)) {
1080
+ continue;
1081
+ }
1082
+ const siblings = byModule.get(symbol.parent);
1083
+ if (siblings === undefined) {
1084
+ byModule.set(symbol.parent, [symbol]);
1085
+ } else {
1086
+ siblings.push(symbol);
1071
1087
  }
1072
1088
  }
1073
- topLevel = byParent;
1089
+ moduleBodies = byModule;
1074
1090
  }
1075
- return topLevel.get(fileId) ?? [];
1091
+ return moduleBodies.get(moduleId) ?? [];
1076
1092
  };
1077
1093
 
1078
1094
  const exportsCache = new Map<number, readonly ChainTarget[]>();
@@ -1431,9 +1447,12 @@ export function references<Brand>(
1431
1447
 
1432
1448
  for (const url of urls) {
1433
1449
  const constructor = url.constructor === undefined ? undefined : answered.get(url.constructor);
1450
+ // An augmentation of the global `URL` merges into it, which keeps the
1451
+ // library's declaration; a `URL` the target declares has no other.
1434
1452
  if (
1435
1453
  typeof constructor === "object" &&
1436
- constructor.declarations.some((handle) => ownPaths.has(handle.path))
1454
+ constructor.declarations.length > 0 &&
1455
+ constructor.declarations.every((handle) => ownPaths.has(handle.path))
1437
1456
  ) {
1438
1457
  continue;
1439
1458
  }
@@ -1604,14 +1623,16 @@ export function references<Brand>(
1604
1623
 
1605
1624
  let typedLookups = 0;
1606
1625
  if (consumer === undefined) {
1607
- found.push(...overrideReferences(project, held, chains, new Set(tests)));
1608
1626
  const before = options.requestCount?.() ?? 0;
1627
+ found.push(...overrideReferences(project, held, chains, new Set(tests)));
1609
1628
  found.push(...indexedAccessReferences(project, held, chains, root, new Set(tests)));
1610
1629
  found.push(...elementAccessReferences(project, held, chains, root, new Set(tests)));
1611
1630
  found.push(...keyofComparisonReferences(project, held, chains, root, new Set(tests)));
1631
+ found.push(...iterationReferences(project, held, chains, root, new Set(tests)));
1632
+ found.push(...typeParameterReads(project, held, chains, root, new Set(tests)));
1612
1633
  found.push(...unimplementedCalleeReads(project, held, chains, root, new Set(tests)));
1613
- typedLookups = (options.requestCount?.() ?? 0) - before;
1614
1634
  found.push(...moduleDeclarationReferences(project, held, root, new Set(tests)));
1635
+ typedLookups = (options.requestCount?.() ?? 0) - before;
1615
1636
  }
1616
1637
  const scripts = new Set(
1617
1638
  walked.filter((one) => one.file.externalModuleIndicator === undefined).map((one) => one.fileId),