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

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 (207) hide show
  1. package/dist/src/alias-chain.js +0 -5
  2. package/dist/src/amd.js +81 -0
  3. package/dist/src/analysis.js +94 -54
  4. package/dist/src/augmentations.js +23 -24
  5. package/dist/src/bindings.js +382 -0
  6. package/dist/src/calls.js +25 -8
  7. package/dist/src/commands/read.js +6 -7
  8. package/dist/src/commands/tools.js +5 -9
  9. package/dist/src/component-files.js +3 -2
  10. package/dist/src/conformance-record.js +1 -1
  11. package/dist/src/consumers.js +9 -10
  12. package/dist/src/contextual-uses.js +1 -17
  13. package/dist/src/contract/config.generated.js +5 -2
  14. package/dist/src/contract/contract.generated.js +119 -2
  15. package/dist/src/contract/report.generated.js +6 -2
  16. package/dist/src/dependencies.js +98 -71
  17. package/dist/src/destructuring.js +1 -19
  18. package/dist/src/element-access.js +179 -164
  19. package/dist/src/entries/build-scripts.js +222 -0
  20. package/dist/src/{configuration-files.js → entries/configuration-files.js} +22 -64
  21. package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
  22. package/dist/src/entries/conventions.js +739 -0
  23. package/dist/src/{html-entries.js → entries/html-entries.js} +9 -12
  24. package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +3 -3
  25. package/dist/src/entries/run-directories.js +77 -0
  26. package/dist/src/entries/sources.js +144 -0
  27. package/dist/src/entries/static-module.js +670 -0
  28. package/dist/src/entries/test-runners.js +684 -0
  29. package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
  30. package/dist/src/extensions.js +37 -0
  31. package/dist/src/facts.js +38 -0
  32. package/dist/src/findings/completion.js +40 -4
  33. package/dist/src/findings/emitters.js +5 -8
  34. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  35. package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
  36. package/dist/src/findings/self-check.js +36 -20
  37. package/dist/src/findings-pass.js +5 -6
  38. package/dist/src/framework-lifecycle.js +42 -15
  39. package/dist/src/fs/manifest.js +15 -20
  40. package/dist/src/generated-file.js +172 -29
  41. package/dist/src/graph.js +14 -8
  42. package/dist/src/import-bindings.js +73 -28
  43. package/dist/src/index/sites.js +557 -0
  44. package/dist/src/index.js +1 -1
  45. package/dist/src/indexed-access.js +523 -478
  46. package/dist/src/injection-container.js +1 -1
  47. package/dist/src/interface-satisfaction.js +1 -1
  48. package/dist/src/inventory/commonjs.js +62 -17
  49. package/dist/src/inventory/enumerate.js +2 -1
  50. package/dist/src/javascript-file.js +26 -11
  51. package/dist/src/keyof-comparisons.js +124 -103
  52. package/dist/src/load/classify.js +28 -9
  53. package/dist/src/load/diagnostics.js +8 -1
  54. package/dist/src/load/discover.js +15 -7
  55. package/dist/src/load/expected-module.js +2 -2
  56. package/dist/src/load/plan.js +2 -2
  57. package/dist/src/load/specifiers.js +2 -4
  58. package/dist/src/load/synthesize.js +10 -5
  59. package/dist/src/load/workspace-manifests.js +1 -1
  60. package/dist/src/load/workspace-resolution.js +2 -2
  61. package/dist/src/matrix.js +12 -17
  62. package/dist/src/module-declarations.js +70 -85
  63. package/dist/src/overrides.js +134 -112
  64. package/dist/src/paths.js +56 -1
  65. package/dist/src/platform-calls.js +78 -0
  66. package/dist/src/platform-symbols.js +134 -0
  67. package/dist/src/references/read-rule.js +1 -0
  68. package/dist/src/references/read-rules.js +48 -0
  69. package/dist/src/references/sink.js +151 -0
  70. package/dist/src/references/sources.js +450 -0
  71. package/dist/src/references.js +111 -1116
  72. package/dist/src/resolve.js +1 -1
  73. package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +7 -4
  74. package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
  75. package/dist/src/{manifest.js → roots/manifest.js} +4 -4
  76. package/dist/src/roots/published-reach.js +412 -0
  77. package/dist/src/roots/root-set.js +489 -0
  78. package/dist/src/roots/unseen-loads.js +236 -0
  79. package/dist/src/serialization-contract.js +9 -4
  80. package/dist/src/source-files.js +5 -9
  81. package/dist/src/stores.js +4 -2
  82. package/dist/src/suppress.js +30 -32
  83. package/dist/src/sweep.js +25 -13
  84. package/dist/src/symbol-keys.js +89 -83
  85. package/dist/src/tests/file-class.js +76 -0
  86. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  87. package/dist/src/{test-support.js → tests/support.js} +5 -12
  88. package/dist/src/type-parameter-reads.js +178 -139
  89. package/dist/src/unimplemented-callees.js +262 -242
  90. package/dist/src/verbs/analyze.js +15 -3
  91. package/dist/src/verbs/describe.js +5 -3
  92. package/dist/src/verbs/explain.js +50 -28
  93. package/dist/src/verbs/print-retained.js +2 -2
  94. package/jsr.json +1 -1
  95. package/package.json +3 -3
  96. package/src/alias-chain.ts +0 -7
  97. package/src/amd.ts +130 -0
  98. package/src/analysis.ts +156 -70
  99. package/src/augmentations.ts +41 -27
  100. package/src/automatic-types.ts +1 -1
  101. package/src/bindings.ts +460 -0
  102. package/src/calls.ts +25 -7
  103. package/src/commands/read.ts +6 -8
  104. package/src/commands/tools.ts +5 -11
  105. package/src/component-files.ts +4 -2
  106. package/src/conformance-record.ts +1 -1
  107. package/src/consumers.ts +12 -11
  108. package/src/contextual-uses.ts +2 -22
  109. package/src/contract/config.generated.ts +5 -2
  110. package/src/contract/contract.generated.ts +138 -2
  111. package/src/contract/report.generated.ts +6 -2
  112. package/src/dependencies.ts +117 -71
  113. package/src/destructuring.ts +1 -31
  114. package/src/element-access.ts +188 -182
  115. package/src/entries/build-scripts.ts +292 -0
  116. package/src/{configuration-files.ts → entries/configuration-files.ts} +33 -82
  117. package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
  118. package/src/{conventions.ts → entries/conventions.ts} +349 -523
  119. package/src/{html-entries.ts → entries/html-entries.ts} +19 -18
  120. package/src/{mdx-entries.ts → entries/mdx-entries.ts} +5 -5
  121. package/src/entries/run-directories.ts +108 -0
  122. package/src/entries/sources.ts +266 -0
  123. package/src/entries/static-module.ts +858 -0
  124. package/src/entries/test-runners.ts +868 -0
  125. package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
  126. package/src/exempt.ts +3 -2
  127. package/src/extensions.ts +45 -0
  128. package/src/facts.ts +46 -0
  129. package/src/file-facts.ts +1 -1
  130. package/src/findings/boundary.ts +2 -0
  131. package/src/findings/completion.ts +43 -4
  132. package/src/findings/emitter.ts +1 -1
  133. package/src/findings/emitters.ts +14 -10
  134. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  135. package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
  136. package/src/findings/self-check.ts +41 -22
  137. package/src/findings/visibility-narrowing.ts +1 -1
  138. package/src/findings-pass.ts +5 -12
  139. package/src/framework-lifecycle.ts +43 -15
  140. package/src/fs/manifest.ts +25 -21
  141. package/src/generated-file.ts +240 -36
  142. package/src/graph.ts +27 -11
  143. package/src/import-bindings.ts +98 -29
  144. package/src/index/sites.ts +781 -0
  145. package/src/index.ts +1 -1
  146. package/src/indexed-access.ts +570 -527
  147. package/src/injection-container.ts +1 -1
  148. package/src/interface-satisfaction.ts +1 -1
  149. package/src/inventory/commonjs.ts +102 -17
  150. package/src/inventory/enumerate.ts +2 -1
  151. package/src/javascript-file.ts +29 -12
  152. package/src/keyof-comparisons.ts +131 -116
  153. package/src/load/classify.ts +49 -13
  154. package/src/load/diagnostics.ts +11 -1
  155. package/src/load/discover.ts +27 -11
  156. package/src/load/expected-module.ts +8 -2
  157. package/src/load/plan.ts +11 -2
  158. package/src/load/specifiers.ts +8 -5
  159. package/src/load/synthesize.ts +12 -6
  160. package/src/load/test-globals.ts +1 -1
  161. package/src/load/workspace-manifests.ts +1 -1
  162. package/src/load/workspace-resolution.ts +9 -2
  163. package/src/load/workspace-run.ts +1 -1
  164. package/src/matrix.ts +13 -17
  165. package/src/module-declarations.ts +74 -98
  166. package/src/overrides.ts +162 -134
  167. package/src/paths.ts +65 -1
  168. package/src/platform-calls.ts +119 -0
  169. package/src/platform-symbols.ts +189 -0
  170. package/src/references/read-rule.ts +60 -0
  171. package/src/references/read-rules.ts +55 -0
  172. package/src/references/sink.ts +213 -0
  173. package/src/references/sources.ts +632 -0
  174. package/src/references.ts +174 -1492
  175. package/src/report.ts +2 -2
  176. package/src/resolve.ts +1 -1
  177. package/src/{configured-declarations.ts → roots/configured-declarations.ts} +10 -7
  178. package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
  179. package/src/{manifest.ts → roots/manifest.ts} +5 -5
  180. package/src/roots/published-reach.ts +481 -0
  181. package/src/{roots.ts → roots/root-set.ts} +203 -630
  182. package/src/roots/unseen-loads.ts +345 -0
  183. package/src/serialization-contract.ts +9 -4
  184. package/src/source-files.ts +5 -10
  185. package/src/stores.ts +13 -2
  186. package/src/suppress.ts +48 -33
  187. package/src/sweep.ts +37 -14
  188. package/src/symbol-keys.ts +102 -102
  189. package/src/tests/file-class.ts +118 -0
  190. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  191. package/src/{test-support.ts → tests/support.ts} +15 -21
  192. package/src/type-parameter-reads.ts +187 -158
  193. package/src/unimplemented-callees.ts +297 -280
  194. package/src/verbs/analyze.ts +19 -5
  195. package/src/verbs/describe.ts +5 -2
  196. package/src/verbs/explain.ts +75 -32
  197. package/src/verbs/print-retained.ts +2 -2
  198. package/dist/src/build-scripts.js +0 -418
  199. package/dist/src/conventions.js +0 -907
  200. package/dist/src/entry-points.js +0 -444
  201. package/dist/src/own-body.js +0 -28
  202. package/dist/src/roots.js +0 -865
  203. package/dist/src/unseen-loads.js +0 -189
  204. package/src/build-scripts.ts +0 -536
  205. package/src/entry-points.ts +0 -584
  206. package/src/own-body.ts +0 -42
  207. package/src/unseen-loads.ts +0 -282
@@ -15,20 +15,18 @@ import {
15
15
  isImportTypeNode,
16
16
  isLiteralTypeNode,
17
17
  isModuleDeclaration,
18
- isNamespaceExport,
19
- isNamespaceImport,
20
18
  isNoSubstitutionTemplateLiteral,
21
19
  isStringLiteral,
22
20
  SyntaxKind,
23
21
  type Node,
24
22
  type SourceFile,
25
23
  } from "@typescript/native/ast";
24
+ import { namesImported } from "./import-bindings.ts";
26
25
  import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
27
- import { renderPosition } from "./position.ts";
28
26
  import { UNANSWERED } from "./query.ts";
29
27
  import { aliasFragment, nameComponent } from "./ref.ts";
30
- import type { Reference } from "./references.ts";
31
- import type { ProjectView } from "./session.ts";
28
+ import type { Read, ReadRule } from "./references/read-rule.ts";
29
+ import { isPathSpecifier } from "./paths.ts";
32
30
 
33
31
  /** Whether a node is a specifier's text: a string literal or a template with no substitution. */
34
32
  function literalText(node: Node | undefined): node is Node & { readonly text: string } {
@@ -58,33 +56,12 @@ function specifierOf(node: Node): (Node & { readonly text: string }) | undefined
58
56
  return undefined;
59
57
  }
60
58
 
61
- /**
62
- * The export names an import or a re-export reads from the module it names: none for a
63
- * namespace, a star or a bare form.
64
- */
65
- export function namesImported(node: Node): readonly string[] {
66
- if (isImportDeclaration(node)) {
67
- const clause = node.importClause;
68
- const bindings = clause?.namedBindings;
69
- return [
70
- ...(clause?.name === undefined ? [] : ["default"]),
71
- ...(bindings === undefined || isNamespaceImport(bindings)
72
- ? []
73
- : bindings.elements.map((one) => (one.propertyName ?? one.name).text)),
74
- ];
75
- }
76
- const clause = isExportDeclaration(node) ? node.exportClause : undefined;
77
- return clause === undefined || isNamespaceExport(clause)
78
- ? []
79
- : clause.elements.map((one) => (one.propertyName ?? one.name).text);
80
- }
81
-
82
59
  /**
83
60
  * Whether a specifier matches a declaration's name, the name's one `*` standing for any run.
84
61
  * A relative name is resolved against its own file, so no specifier names it by its text.
85
62
  */
86
63
  function namesModule(specifier: string, name: string): boolean {
87
- if (/^(?:\.{1,2}(?:\/|$)|\/)/u.test(name)) {
64
+ if (isPathSpecifier(name)) {
88
65
  return false;
89
66
  }
90
67
  const star = name.indexOf("*");
@@ -161,76 +138,75 @@ interface Declared {
161
138
  }
162
139
 
163
140
  /**
164
- * The references the imports of one project's own files make to the module declarations
165
- * they name, each at the import's specifier.
141
+ * The reads the imports of a side's files make of the module declarations of the target they
142
+ * name, each at the import's specifier. A specifier the checker leaves unanswered still names
143
+ * a declaration by its name or pattern.
166
144
  */
167
- export function moduleDeclarationReferences<Brand>(
168
- project: ProjectView<Brand>,
169
- held: Inventory,
170
- root: string,
171
- testFiles: ReadonlySet<string>,
172
- ): readonly Reference[] {
173
- const imports: Import[] = [];
174
- const declared: Declared[] = [];
175
- for (const file of project.ownSourceFiles()) {
176
- const fileId = held.declarations.get(nodeKey(file, file));
177
- if (fileId === undefined) {
178
- continue;
179
- }
180
- const visit = (node: Node, from: string): void => {
181
- const own = held.declarations.get(nodeKey(file, node));
182
- if (isModuleDeclaration(node) && literalText(node.name) && own !== undefined) {
183
- declared.push({ id: own, name: node.name });
184
- }
185
- const specifier = specifierOf(node);
186
- if (specifier !== undefined) {
187
- imports.push({ file, from, specifier, names: namesImported(node) });
188
- }
189
- node.forEachChild((child) => {
190
- visit(child, own ?? from);
191
- });
192
- };
193
- file.forEachChild((child) => {
194
- visit(child, fileId);
195
- });
196
- }
197
- if (declared.length === 0 || imports.length === 0) {
198
- return [];
199
- }
200
-
201
- const asked = [...declared.map((one) => one.name), ...imports.map((one) => one.specifier)];
202
- const answers = project.symbolsAt(asked.map((node) => project.handle(node)));
203
- const moduleOf = (at: number): number | undefined => {
204
- const symbol = answers[at];
205
- return symbol === undefined || symbol === UNANSWERED ? undefined : symbol.id;
206
- };
207
-
208
- const named = namedBy(held);
209
- const found: Reference[] = [];
210
- imports.forEach((one, index) => {
211
- const resolved = moduleOf(declared.length + index);
212
- const position = renderPosition(one.file, root, one.specifier.getStart());
213
- const read = (to: string, resolution: Reference["resolution"], test: boolean): void => {
214
- found.push({ from: one.from, to, position, use: "read", resolution, test });
215
- };
216
- const test = testFiles.has(position.path);
217
- const same = new Set(
218
- declared
219
- .filter((_, at) => resolved !== undefined && resolved === moduleOf(at))
220
- .map((declaration) => declaration.id),
221
- );
222
- for (const id of same) {
223
- read(id, "batch", test);
224
- }
225
- for (const declaration of named(one.specifier.text, one.names)) {
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)) {
229
- for (const to of [declaration.id, ...declaration.members]) {
230
- read(to, "syntax", test && !declaration.wildcard);
145
+ export const moduleDeclarations: ReadRule = {
146
+ start() {
147
+ const imports: Import[] = [];
148
+ return {
149
+ visit(node, { file, from }) {
150
+ const specifier = specifierOf(node);
151
+ if (specifier !== undefined) {
152
+ imports.push({ file, from, specifier, names: namesImported(node) });
231
153
  }
232
- }
233
- }
234
- });
235
- return found;
236
- }
154
+ },
155
+ reads({ project, held }) {
156
+ const found: Read[] = [];
157
+ // A module declaration whose name is a string is written at a file's top level.
158
+ const declared: Declared[] = project.ownSourceFiles().flatMap((file) =>
159
+ file.statements.flatMap((statement) => {
160
+ const id = held.declarations.get(nodeKey(file, statement));
161
+ return isModuleDeclaration(statement) && literalText(statement.name) && id !== undefined
162
+ ? [{ id, name: statement.name }]
163
+ : [];
164
+ }),
165
+ );
166
+ if (declared.length === 0 || imports.length === 0) {
167
+ return found;
168
+ }
169
+ const asked = [...declared.map((one) => one.name), ...imports.map((one) => one.specifier)];
170
+ const answers = project.symbolsAt(asked.map((node) => project.handle(node)));
171
+ const moduleOf = (at: number): number | undefined => {
172
+ const symbol = answers[at];
173
+ return symbol === undefined || symbol === UNANSWERED ? undefined : symbol.id;
174
+ };
175
+ const named = namedBy(held);
176
+ imports.forEach((one, index) => {
177
+ const resolved = moduleOf(declared.length + index);
178
+ const read = (to: string, via: "batch" | "syntax", production: boolean): void => {
179
+ found.push({
180
+ kind: "named",
181
+ from: one.from,
182
+ to,
183
+ file: one.file,
184
+ at: one.specifier,
185
+ use: "read",
186
+ via,
187
+ ...(production ? { production: true as const } : {}),
188
+ });
189
+ };
190
+ const same = new Set(
191
+ declared
192
+ .filter((_, at) => resolved !== undefined && resolved === moduleOf(at))
193
+ .map((declaration) => declaration.id),
194
+ );
195
+ for (const id of same) {
196
+ read(id, "batch", false);
197
+ }
198
+ for (const declaration of named(one.specifier.text, one.names)) {
199
+ // A wildcard declaration is how the compiler types the file a matching specifier
200
+ // names, so the import uses it whoever writes it, a test included.
201
+ if (declaration.wildcard || !same.has(declaration.id)) {
202
+ for (const to of [declaration.id, ...declaration.members]) {
203
+ read(to, "syntax", declaration.wildcard);
204
+ }
205
+ }
206
+ }
207
+ });
208
+ return found;
209
+ },
210
+ };
211
+ },
212
+ };
package/src/overrides.ts CHANGED
@@ -15,14 +15,14 @@ import {
15
15
  SyntaxKind,
16
16
  type HeritageClauseElement,
17
17
  type Node,
18
+ type SourceFile,
18
19
  } from "@typescript/native/ast";
19
- import type { AliasChains } from "./alias-chain.ts";
20
20
  import { memberComponent } from "./class-members.ts";
21
- import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
21
+ import { nodeKey, type InventorySymbol } from "./inventory.ts";
22
22
  import { SymbolFlags, type Symbol as TSSymbol, type Type } from "@typescript/native/sync";
23
23
  import { UNANSWERED } from "./query.ts";
24
24
  import { nameComponent } from "./ref.ts";
25
- import type { Reference } from "./references.ts";
25
+ import type { Read, ReadRule } from "./references/read-rule.ts";
26
26
  import type { ProjectView } from "./session.ts";
27
27
 
28
28
  const CLASS_DECLARATIONS: ReadonlySet<SyntaxKind> = new Set([
@@ -41,141 +41,157 @@ function nameOf(entry: HeritageClauseElement): Node {
41
41
  return isQualifiedName(entry.typeName) ? entry.typeName.right : entry.typeName;
42
42
  }
43
43
 
44
- /** Each class of the target whose `extends` clause names a class, with the name node. */
45
- function extending<Brand>(
46
- project: ProjectView<Brand>,
47
- held: Inventory,
48
- ): readonly { readonly id: string; readonly base: Node }[] {
49
- const found: { id: string; base: Node }[] = [];
50
- for (const file of project.ownSourceFiles()) {
51
- const visit = (node: Node): void => {
52
- if (isClassDeclaration(node) || isClassExpression(node)) {
53
- const id = held.declarations.get(nodeKey(file, node));
54
- const clause = node.heritageClauses?.find((one) => one.token === SyntaxKind.ExtendsKeyword);
55
- const base = clause?.types[0];
56
- if (id !== undefined && base !== undefined) {
57
- found.push({ id, base: nameOf(base) });
58
- }
59
- }
60
- node.forEachChild(visit);
61
- };
62
- file.forEachChild(visit);
63
- }
64
- return found;
44
+ /** One class whose `extends` clause names a class, with the name node and its file. */
45
+ interface Extending {
46
+ readonly node: Node;
47
+ readonly file: SourceFile;
48
+ readonly base: Node;
65
49
  }
66
50
 
67
51
  /**
68
- * The references each base class member makes to its overrides, for the classes of one
69
- * project that extend another class of the target, and each class makes to its members
70
- * that override a member of a class declared outside the target. A member a nearer base
71
- * already overrides is reached through that override. A private member is not
72
- * overridden. Each reference is placed at the override and made by its file.
52
+ * The reads each base class member makes of its overrides, for the classes of the target that
53
+ * extend another class of the target, and each class makes of its members that override a
54
+ * member of a class declared outside the target. A member a nearer base already overrides is
55
+ * reached through that override. A private member is not overridden. Each read is placed at
56
+ * the override. A class whose base the checker leaves unanswered holds every member it could
57
+ * override.
73
58
  */
74
- export function overrideReferences<Brand>(
75
- project: ProjectView<Brand>,
76
- held: Inventory,
77
- chains: AliasChains,
78
- testFiles: ReadonlySet<string>,
79
- ): readonly Reference[] {
80
- const classes = extending(project, held);
81
- if (classes.length === 0) {
82
- return [];
83
- }
84
- const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
85
- const members = new Map<string, Map<string, InventorySymbol>>();
86
- for (const symbol of held.symbols) {
87
- const component = memberComponent(symbol, byId);
88
- if (
89
- component === undefined ||
90
- !OVERRIDABLE.has(symbol.kind) ||
91
- symbol.visibility === "private" ||
92
- symbol.visibility === "private-name"
93
- ) {
94
- continue;
95
- }
96
- const own = members.get(symbol.parent) ?? new Map<string, InventorySymbol>();
97
- own.set(component, symbol);
98
- members.set(symbol.parent, own);
99
- }
100
- const bases = new Map<string, readonly string[]>();
101
- const outsideBases = new Map<string, TSSymbol>();
102
- const own = project.ownPaths();
103
- const symbols = project.symbolsAt(classes.map((one) => project.handle(one.base)));
104
- classes.forEach(({ id }, index) => {
105
- const symbol = symbols[index];
106
- if (symbol !== undefined && symbol !== UNANSWERED) {
107
- const inTarget = chains
108
- .chainOf(symbol)
109
- .map((target) => target.id)
110
- .filter((target) => byId.get(target)?.kind === "class" && target !== id);
111
- bases.set(id, inTarget);
112
- const declared =
113
- (symbol.flags & SymbolFlags.Alias) === 0 ? symbol : project.queries.aliased(symbol);
114
- if (
115
- inTarget.length === 0 &&
116
- declared !== UNANSWERED &&
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
- )))
127
- ) {
128
- outsideBases.set(id, declared);
129
- }
130
- }
131
- });
132
-
133
- /** The nearest member of one component the class or a class it extends declares. */
134
- const declaredAbove = (classId: string, component: string, seen: Set<string>): string[] => {
135
- if (seen.has(classId)) {
136
- return [];
137
- }
138
- seen.add(classId);
139
- const own = members.get(classId)?.get(component);
140
- if (own !== undefined) {
141
- return [own.id];
142
- }
143
- return (bases.get(classId) ?? []).flatMap((base) => declaredAbove(base, component, seen));
144
- };
145
-
146
- const found: Reference[] = [];
147
- const outsideNames = outsideMemberNames(project, bases, outsideBases);
148
- for (const [classId, names] of outsideNames) {
149
- for (const [component, member] of members.get(classId) ?? []) {
150
- if (names.has(component)) {
151
- found.push({
152
- from: classId,
153
- to: member.id,
154
- position: member.position,
155
- use: "read",
156
- resolution: "override",
157
- test: testFiles.has(member.position.path),
59
+ export const overrides: ReadRule = {
60
+ start() {
61
+ const extending: Extending[] = [];
62
+ return {
63
+ visit(node, { file }) {
64
+ if (isClassDeclaration(node) || isClassExpression(node)) {
65
+ const clause = node.heritageClauses?.find(
66
+ (one) => one.token === SyntaxKind.ExtendsKeyword,
67
+ );
68
+ const base = clause?.types[0];
69
+ if (base !== undefined) {
70
+ extending.push({ node, file, base: nameOf(base) });
71
+ }
72
+ }
73
+ },
74
+ reads({ project, held, chains }) {
75
+ const found: Read[] = [];
76
+ const classes = extending.flatMap((one) => {
77
+ const id = held.declarations.get(nodeKey(one.file, one.node));
78
+ return id === undefined ? [] : [{ ...one, id }];
158
79
  });
159
- }
160
- }
161
- }
162
- for (const [classId, baseIds] of bases) {
163
- for (const [component, member] of members.get(classId) ?? []) {
164
- const seen = new Set([classId]);
165
- for (const base of baseIds.flatMap((one) => declaredAbove(one, component, seen))) {
166
- found.push({
167
- from: base,
168
- to: member.id,
169
- position: member.position,
170
- use: "read",
171
- resolution: "override",
172
- test: testFiles.has(member.position.path),
80
+ if (classes.length === 0) {
81
+ return found;
82
+ }
83
+ const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
84
+ const members = new Map<string, Map<string, InventorySymbol>>();
85
+ for (const symbol of held.symbols) {
86
+ const component = memberComponent(symbol, byId);
87
+ if (
88
+ component === undefined ||
89
+ !OVERRIDABLE.has(symbol.kind) ||
90
+ symbol.visibility === "private" ||
91
+ symbol.visibility === "private-name"
92
+ ) {
93
+ continue;
94
+ }
95
+ const own = members.get(symbol.parent) ?? new Map<string, InventorySymbol>();
96
+ own.set(component, symbol);
97
+ members.set(symbol.parent, own);
98
+ }
99
+ const bases = new Map<string, readonly string[]>();
100
+ const outsideBases = new Map<string, TSSymbol>();
101
+ const fileOf = new Map(classes.map((one) => [one.id, one.file]));
102
+ const own = project.ownPaths();
103
+ const unanswered = new Set<string>();
104
+ const symbols = project.symbolsAt(classes.map((one) => project.handle(one.base)));
105
+ classes.forEach(({ id }, index) => {
106
+ const symbol = symbols[index];
107
+ if (symbol === UNANSWERED) {
108
+ unanswered.add(id);
109
+ return;
110
+ }
111
+ if (symbol === undefined) {
112
+ return;
113
+ }
114
+ const inTarget = chains
115
+ .chainOf(symbol)
116
+ .map((target) => target.id)
117
+ .filter((target) => byId.get(target)?.kind === "class" && target !== id);
118
+ bases.set(id, inTarget);
119
+ const declared =
120
+ (symbol.flags & SymbolFlags.Alias) === 0 ? symbol : project.queries.aliased(symbol);
121
+ if (declared === UNANSWERED) {
122
+ unanswered.add(id);
123
+ return;
124
+ }
125
+ if (
126
+ inTarget.length === 0 &&
127
+ (((declared.flags & SymbolFlags.Class) !== 0 &&
128
+ // The target's augmentation of an outside class merges an interface into it,
129
+ // so only a class declaration says where the class is.
130
+ declared.declarations.some(
131
+ (handle) => CLASS_DECLARATIONS.has(handle.kind) && !own.has(handle.path),
132
+ )) ||
133
+ ((declared.flags & SymbolFlags.Interface) !== 0 &&
134
+ declared.declarations.some(
135
+ (handle) =>
136
+ handle.kind === SyntaxKind.VariableDeclaration && !own.has(handle.path),
137
+ )))
138
+ ) {
139
+ outsideBases.set(id, declared);
140
+ }
173
141
  });
174
- }
175
- }
176
- }
177
- return found;
178
- }
142
+
143
+ /** The nearest member of one component the class or a class it extends declares. */
144
+ const declaredAbove = (classId: string, component: string, seen: Set<string>): string[] => {
145
+ if (seen.has(classId)) {
146
+ return [];
147
+ }
148
+ seen.add(classId);
149
+ const member = members.get(classId)?.get(component);
150
+ if (member !== undefined) {
151
+ return [member.id];
152
+ }
153
+ return (bases.get(classId) ?? []).flatMap((base) => declaredAbove(base, component, seen));
154
+ };
155
+
156
+ const read = (classId: string, from: string, member: InventorySymbol): void => {
157
+ const file = fileOf.get(classId);
158
+ if (file !== undefined) {
159
+ found.push({
160
+ kind: "named",
161
+ from,
162
+ to: member.id,
163
+ file,
164
+ at: member.position,
165
+ use: "read",
166
+ via: "override",
167
+ });
168
+ }
169
+ };
170
+ const outside = outsideMemberNames(project, bases, outsideBases);
171
+ for (const classId of [...unanswered, ...outside.unanswered]) {
172
+ const ids = [...(members.get(classId)?.values() ?? [])].map((member) => member.id);
173
+ found.push({ kind: "held", from: classId, ids });
174
+ }
175
+ for (const [classId, names] of outside.names) {
176
+ for (const [component, member] of members.get(classId) ?? []) {
177
+ if (names.has(component)) {
178
+ read(classId, classId, member);
179
+ }
180
+ }
181
+ }
182
+ for (const [classId, baseIds] of bases) {
183
+ for (const [component, member] of members.get(classId) ?? []) {
184
+ const seen = new Set([classId]);
185
+ for (const base of baseIds.flatMap((one) => declaredAbove(one, component, seen))) {
186
+ read(classId, base, member);
187
+ }
188
+ }
189
+ }
190
+ return found;
191
+ },
192
+ };
193
+ },
194
+ };
179
195
 
180
196
  /**
181
197
  * Per class of the target with a class declared outside the target in its `extends`
@@ -186,12 +202,24 @@ function outsideMemberNames<Brand>(
186
202
  project: ProjectView<Brand>,
187
203
  bases: ReadonlyMap<string, readonly string[]>,
188
204
  outsideBases: ReadonlyMap<string, TSSymbol>,
189
- ): ReadonlyMap<string, ReadonlySet<string>> {
205
+ ): {
206
+ readonly names: ReadonlyMap<string, ReadonlySet<string>>;
207
+ readonly unanswered: readonly string[];
208
+ } {
190
209
  const direct = new Map<string, ReadonlySet<string>>();
210
+ const unanswered: string[] = [];
191
211
  const error = errorType(project);
192
212
  for (const [classId, base] of outsideBases) {
193
213
  const instance = project.queries.declaredTypeOf(base);
194
214
  const statics = project.queries.typeOfSymbol(base);
215
+ if (
216
+ instance === UNANSWERED ||
217
+ statics === UNANSWERED ||
218
+ project.queries.propertiesOf(instance) === UNANSWERED ||
219
+ (statics !== undefined && project.queries.propertiesOf(statics) === UNANSWERED)
220
+ ) {
221
+ unanswered.push(classId);
222
+ }
195
223
  const components = [
196
224
  ...namesOf(project, instance === UNANSWERED ? undefined : instance).map(nameComponent),
197
225
  ...(instance !== UNANSWERED && reaches(project, instance, error) ? ["toString"] : []),
@@ -220,7 +248,7 @@ function outsideMemberNames<Brand>(
220
248
  names.set(classId, found);
221
249
  }
222
250
  }
223
- return names;
251
+ return { names, unanswered };
224
252
  }
225
253
 
226
254
  /** The global `Error` instance type's identifier, where the program's library declares one. */
package/src/paths.ts CHANGED
@@ -11,6 +11,58 @@ export function isAbsolutePath(path: string): boolean {
11
11
  return path.startsWith(SEPARATOR);
12
12
  }
13
13
 
14
+ /** Whether a specifier starts a path relative to the file that writes it: `./` or `../`. */
15
+ export function startsRelativePath(specifier: string): boolean {
16
+ return specifier.startsWith("./") || specifier.startsWith("../");
17
+ }
18
+
19
+ /** Whether a specifier names a path relative to the file that writes it: `.`, `..`, `./` or `../` first. */
20
+ export function isRelativeSpecifier(specifier: string): boolean {
21
+ return specifier === "." || specifier === ".." || startsRelativePath(specifier);
22
+ }
23
+
24
+ /**
25
+ * Whether a module specifier names a path rather than a package, as the compiler reads one:
26
+ * a relative specifier or an absolute path.
27
+ */
28
+ export function isPathSpecifier(specifier: string): boolean {
29
+ return isRelativeSpecifier(specifier) || isAbsolutePath(specifier);
30
+ }
31
+
32
+ /**
33
+ * Whether a string starts with `./`, `../` or `/`, the specifiers a browser reads as a URL
34
+ * rather than a bare name, so a bare `.` or `..` stays a request:
35
+ * https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-url-like-module-specifier
36
+ */
37
+ export function isExplicitPath(text: string): boolean {
38
+ return startsRelativePath(text) || isAbsolutePath(text);
39
+ }
40
+
41
+ /**
42
+ * Whether a string starts with `.` or `/`, which no npm package name does, so a reader that
43
+ * takes any other string as a package's takes it as a path:
44
+ * https://docs.npmjs.com/cli/v11/configuring-npm/package-json#name
45
+ */
46
+ export function cannotStartPackageName(text: string): boolean {
47
+ return text.startsWith(".") || isAbsolutePath(text);
48
+ }
49
+
50
+ /** A URL scheme, `file:` and `node:` among them: https://url.spec.whatwg.org/#url-scheme-string */
51
+ const URL_SCHEME = /^[a-z][a-z\d+.-]*:/iu;
52
+
53
+ /** Whether a string starts with a URL scheme. */
54
+ export function hasURLScheme(text: string): boolean {
55
+ return URL_SCHEME.test(text);
56
+ }
57
+
58
+ /**
59
+ * Whether a URL reference is a relative one that does not start with a solidus, the bare
60
+ * name included: https://url.spec.whatwg.org/#relative-url-string
61
+ */
62
+ export function isPathRelativeURL(reference: string): boolean {
63
+ return reference !== "" && !reference.startsWith(SEPARATOR) && !hasURLScheme(reference);
64
+ }
65
+
14
66
  /**
15
67
  * One path with `.` removed, `..` applied to the segment before it, and repeated
16
68
  * separators collapsed. A `..` that walks past the root of an absolute path is
@@ -97,6 +149,18 @@ export function resolvePath(against: string, path: string): string {
97
149
  return isAbsolutePath(path) ? normalizePath(path) : joinPath(against, path);
98
150
  }
99
151
 
152
+ /**
153
+ * The path a relative URL reference names against `dir`, its query and its fragment
154
+ * removed, which name no part of the file; undefined where the reference is not relative.
155
+ */
156
+ export function relativeURLPath(dir: string, reference: string): string | undefined {
157
+ if (!isPathRelativeURL(reference)) {
158
+ return undefined;
159
+ }
160
+ const cut = reference.search(/[?#]/u);
161
+ return resolvePath(dir, cut < 0 ? reference : reference.slice(0, cut));
162
+ }
163
+
100
164
  /**
101
165
  * The relative path before the query of a relative module specifier such as
102
166
  * `./worker.ts?worker`, the module a bundler loads; undefined for a specifier with no
@@ -105,7 +169,7 @@ export function resolvePath(against: string, path: string): string {
105
169
  */
106
170
  export function queriedModulePath(specifier: string): string | undefined {
107
171
  const query = specifier.indexOf("?");
108
- if (query < 0 || !(specifier.startsWith("./") || specifier.startsWith("../"))) {
172
+ if (query < 0 || !startsRelativePath(specifier)) {
109
173
  return undefined;
110
174
  }
111
175
  const raw = specifier