@cplieger/deadset-ts 8.0.0-dev.14 → 8.0.0-dev.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/src/alias-chain.js +2 -2
  2. package/dist/src/analysis.js +43 -38
  3. package/dist/src/commands/read.js +6 -7
  4. package/dist/src/conformance-record.js +1 -1
  5. package/dist/src/consumers.js +4 -2
  6. package/dist/src/contract/config.generated.js +1 -1
  7. package/dist/src/contract/contract.generated.js +120 -2
  8. package/dist/src/dependencies.js +81 -49
  9. package/dist/src/entries/build-scripts.js +222 -0
  10. package/dist/src/{configuration-files.js → entries/configuration-files.js} +20 -58
  11. package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
  12. package/dist/src/entries/conventions.js +739 -0
  13. package/dist/src/{html-entries.js → entries/html-entries.js} +3 -3
  14. package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +2 -2
  15. package/dist/src/entries/run-directories.js +77 -0
  16. package/dist/src/entries/sources.js +144 -0
  17. package/dist/src/entries/static-module.js +669 -0
  18. package/dist/src/entries/test-runners.js +684 -0
  19. package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
  20. package/dist/src/extensions.js +37 -0
  21. package/dist/src/facts.js +38 -0
  22. package/dist/src/file-facts.js +1 -1
  23. package/dist/src/findings/emitters.js +5 -8
  24. package/dist/src/findings/self-check.js +36 -20
  25. package/dist/src/findings-pass.js +5 -6
  26. package/dist/src/framework-lifecycle.js +1 -1
  27. package/dist/src/fs/manifest.js +15 -20
  28. package/dist/src/generated-file.js +172 -29
  29. package/dist/src/graph.js +2 -5
  30. package/dist/src/index.js +1 -1
  31. package/dist/src/injection-container.js +1 -1
  32. package/dist/src/inventory/commonjs.js +507 -0
  33. package/dist/src/inventory/enumerate.js +167 -0
  34. package/dist/src/inventory/syntax.js +236 -0
  35. package/dist/src/inventory.js +305 -705
  36. package/dist/src/javascript-file.js +26 -11
  37. package/dist/src/load/classify.js +28 -9
  38. package/dist/src/load/diagnostics.js +8 -1
  39. package/dist/src/load/discover.js +16 -8
  40. package/dist/src/load/package-scope.js +56 -0
  41. package/dist/src/load/plan.js +2 -2
  42. package/dist/src/load/synthesize.js +5 -5
  43. package/dist/src/load/workspace-manifests.js +1 -1
  44. package/dist/src/matrix.js +6 -13
  45. package/dist/src/outside-tests.js +1 -1
  46. package/dist/src/paths.js +16 -0
  47. package/dist/src/platform-calls.js +78 -0
  48. package/dist/src/platform-symbols.js +140 -0
  49. package/dist/src/position.js +41 -0
  50. package/dist/src/ref.js +11 -20
  51. package/dist/src/references.js +281 -50
  52. package/dist/src/resolve.js +1 -1
  53. package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +4 -4
  54. package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
  55. package/dist/src/{manifest.js → roots/manifest.js} +4 -4
  56. package/dist/src/roots/published-reach.js +412 -0
  57. package/dist/src/roots/root-set.js +489 -0
  58. package/dist/src/roots/unseen-loads.js +208 -0
  59. package/dist/src/serialization-contract.js +1 -1
  60. package/dist/src/session.js +3 -0
  61. package/dist/src/source-files.js +5 -9
  62. package/dist/src/suppress-inline.js +33 -81
  63. package/dist/src/suppress.js +30 -32
  64. package/dist/src/test-support.js +4 -2
  65. package/dist/src/verbs/analyze.js +13 -1
  66. package/dist/src/verbs/describe.js +5 -3
  67. package/jsr.json +1 -1
  68. package/package.json +3 -3
  69. package/src/alias-chain.ts +2 -2
  70. package/src/analysis.ts +77 -53
  71. package/src/automatic-types.ts +1 -1
  72. package/src/commands/read.ts +6 -8
  73. package/src/conformance-record.ts +1 -1
  74. package/src/consumers.ts +5 -1
  75. package/src/contract/config.generated.ts +1 -1
  76. package/src/contract/contract.generated.ts +139 -2
  77. package/src/dependencies.ts +98 -50
  78. package/src/entries/build-scripts.ts +292 -0
  79. package/src/{configuration-files.ts → entries/configuration-files.ts} +23 -75
  80. package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
  81. package/src/{conventions.ts → entries/conventions.ts} +347 -522
  82. package/src/{html-entries.ts → entries/html-entries.ts} +5 -5
  83. package/src/{mdx-entries.ts → entries/mdx-entries.ts} +4 -4
  84. package/src/entries/run-directories.ts +108 -0
  85. package/src/entries/sources.ts +266 -0
  86. package/src/entries/static-module.ts +857 -0
  87. package/src/entries/test-runners.ts +868 -0
  88. package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
  89. package/src/exempt.ts +3 -2
  90. package/src/extensions.ts +45 -0
  91. package/src/facts.ts +44 -0
  92. package/src/file-facts.ts +2 -2
  93. package/src/findings/emitters.ts +14 -10
  94. package/src/findings/self-check.ts +41 -22
  95. package/src/findings/visibility-narrowing.ts +1 -1
  96. package/src/findings-pass.ts +5 -12
  97. package/src/framework-lifecycle.ts +1 -1
  98. package/src/fs/manifest.ts +25 -21
  99. package/src/generated-file.ts +240 -36
  100. package/src/graph.ts +3 -6
  101. package/src/index.ts +1 -1
  102. package/src/injection-container.ts +1 -1
  103. package/src/inventory/commonjs.ts +696 -0
  104. package/src/inventory/enumerate.ts +203 -0
  105. package/src/inventory/syntax.ts +311 -0
  106. package/src/inventory.ts +424 -878
  107. package/src/javascript-file.ts +29 -12
  108. package/src/load/classify.ts +49 -13
  109. package/src/load/diagnostics.ts +11 -1
  110. package/src/load/discover.ts +28 -12
  111. package/src/load/package-scope.ts +74 -0
  112. package/src/load/plan.ts +11 -2
  113. package/src/load/synthesize.ts +6 -6
  114. package/src/load/test-globals.ts +1 -1
  115. package/src/load/workspace-manifests.ts +1 -1
  116. package/src/load/workspace-run.ts +1 -1
  117. package/src/matrix.ts +7 -14
  118. package/src/outside-tests.ts +1 -1
  119. package/src/paths.ts +18 -0
  120. package/src/platform-calls.ts +119 -0
  121. package/src/platform-symbols.ts +205 -0
  122. package/src/position.ts +42 -0
  123. package/src/ref.ts +11 -20
  124. package/src/references.ts +383 -73
  125. package/src/report.ts +1 -1
  126. package/src/resolve.ts +1 -1
  127. package/src/{configured-declarations.ts → roots/configured-declarations.ts} +7 -7
  128. package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
  129. package/src/{manifest.ts → roots/manifest.ts} +5 -5
  130. package/src/roots/published-reach.ts +481 -0
  131. package/src/{roots.ts → roots/root-set.ts} +203 -630
  132. package/src/roots/unseen-loads.ts +307 -0
  133. package/src/serialization-contract.ts +1 -1
  134. package/src/session.ts +17 -1
  135. package/src/source-files.ts +5 -10
  136. package/src/suppress-inline.ts +53 -89
  137. package/src/suppress.ts +48 -33
  138. package/src/test-support.ts +8 -3
  139. package/src/verbs/analyze.ts +17 -4
  140. package/src/verbs/describe.ts +5 -2
  141. package/src/verbs/explain.ts +1 -1
  142. package/dist/src/build-scripts.js +0 -418
  143. package/dist/src/conventions.js +0 -907
  144. package/dist/src/entry-points.js +0 -513
  145. package/dist/src/roots.js +0 -865
  146. package/dist/src/unseen-loads.js +0 -100
  147. package/src/build-scripts.ts +0 -536
  148. package/src/entry-points.ts +0 -661
  149. package/src/unseen-loads.ts +0 -134
package/src/inventory.ts CHANGED
@@ -1,76 +1,38 @@
1
1
  import {
2
- isArrayBindingPattern,
3
- isBindingElement,
4
2
  isClassDeclaration,
5
3
  isComputedPropertyName,
6
4
  isConstructorDeclaration,
7
- isEnumDeclaration,
8
5
  isEnumMember,
9
- isExportAssignment,
10
- isExportDeclaration,
11
6
  isFunctionDeclaration,
12
7
  isGetAccessorDeclaration,
13
8
  isIdentifier,
14
9
  isInterfaceDeclaration,
15
- isIntersectionTypeNode,
16
10
  isMethodDeclaration,
17
11
  isMethodSignatureDeclaration,
18
- isModuleBlock,
19
- isModuleDeclaration,
20
- isNamespaceExport,
21
- isObjectBindingPattern,
22
12
  isParameterDeclaration,
23
- isParenthesizedTypeNode,
24
13
  isPrivateIdentifier,
25
14
  isPropertyDeclaration,
26
15
  isPropertySignatureDeclaration,
27
16
  isSetAccessorDeclaration,
28
- isStringLiteral,
29
17
  isTypeAliasDeclaration,
30
- isTypeLiteralNode,
31
- isUnionTypeNode,
32
- isVariableStatement,
33
18
  ModifierFlags,
34
- type BindingElement,
35
- type ClassDeclaration,
36
- type Identifier,
19
+ SyntaxKind,
37
20
  type Node,
38
21
  type SourceFile,
39
22
  } from "@typescript/native/ast";
40
- import type { Symbol as TSSymbol } from "@typescript/native/sync";
41
- import type { Host } from "./host.ts";
42
- import { dirnamePath, joinPath, relativePath } from "./paths.ts";
43
- import {
44
- byPosition,
45
- positionKey,
46
- renderPosition,
47
- writtenLength,
48
- type Position,
49
- } from "./position.ts";
50
- import { computedComponent, nameComponent, renderRef, type Component, type Module } from "./ref.ts";
51
- import { isAnswered, UNANSWERED } from "./query.ts";
52
- import type { Handle, ProjectView } from "./session.ts";
53
- import { manifestAt } from "./fs/manifest.ts";
23
+ import { byPosition, positionKey, renderPosition, startLine, type Position } from "./position.ts";
24
+ import { renderRef, spell, type Component, type Module } from "./ref.ts";
25
+ import { signatureOf } from "./signatures.ts";
54
26
 
55
27
  /**
56
- * The symbol inventory of one project: every declaration the target's own files
57
- * hold, at its own position, with the container it belongs to and the reference
58
- * that survives an edit above it.
59
- *
60
- * Two mechanisms produce it and neither is redundant. A container's symbol table is
61
- * the binder's own answer, so it holds a member of a declaration that merges with
62
- * another and it holds a member whatever its visibility, a private name included;
63
- * a container's syntactic member list holds a member whose key is computed, which
64
- * the binder resolves later and does not put in the table. The set is the union,
65
- * keyed by the member's own reference component, so a member both sources name
66
- * appears once.
28
+ * The symbol inventory of one project: every declaration the target's own files hold, at
29
+ * its own position, with the container it belongs to and the reference that survives an
30
+ * edit above it. A container's members are the union of its symbol table, which holds a
31
+ * member of a merged declaration and a private name, and its syntactic member list, which
32
+ * holds a computed key the binder resolves later; a member both sources name appears once.
67
33
  */
68
34
 
69
- /**
70
- * What one declaration is, from the vocabulary the Contract's finding schema
71
- * closes. A Go rendering of the same vocabulary names other members of it; these
72
- * are the ones a TypeScript declaration takes.
73
- */
35
+ /** What one declaration is, from the vocabulary the Contract's finding schema closes. */
74
36
  export type SymbolKind =
75
37
  | "file"
76
38
  | "function"
@@ -89,15 +51,8 @@ export type SymbolKind =
89
51
  | "type-parameter";
90
52
 
91
53
  /**
92
- * How a member may be named from outside the container that declares it.
93
- *
94
- * The last two are two facts rather than one. A `private` modifier is a
95
- * compile-time constraint only, so the member is still reachable at run time by its
96
- * name through a string index, a decorator, a container or a serializer; a private
97
- * name is not reachable by any of them, because the name does not exist outside the
98
- * class body. An exemption class that finds its evidence in a name therefore
99
- * applies to the first and never to the second, and the Contract's exemption
100
- * vocabulary records that per class.
54
+ * How a member may be named from outside its container. A `private` member is still
55
+ * reachable at run time by its name; a private name does not exist outside the class body.
101
56
  */
102
57
  export type Visibility = "public" | "protected" | "private" | "private-name";
103
58
 
@@ -120,11 +75,9 @@ export interface InventorySymbol {
120
75
  /** The container's id: the file for a module-level declaration, empty for a file. */
121
76
  readonly parent: string;
122
77
  /**
123
- * Whether the export table of the module or namespace that holds the declaration
124
- * names it. It is a fact about that one table, so it is false for every member of
125
- * a class, an interface, a type or an enum and for every type parameter, whose
126
- * reach outside its container is {@link InventorySymbol.visibility} and the state
127
- * of the container the parent names.
78
+ * Whether the export table of the module or namespace that holds the declaration names
79
+ * it: false for every member of a class, an interface, a type or an enum and for every
80
+ * type parameter, whose reach is its visibility and its container's.
128
81
  */
129
82
  readonly exported: boolean;
130
83
  readonly visibility: Visibility;
@@ -132,6 +85,23 @@ export interface InventorySymbol {
132
85
  readonly static: boolean;
133
86
  }
134
87
 
88
+ /** Where a directive written above a declaration binds it. */
89
+ interface Placement {
90
+ /**
91
+ * The lines a directive written above binds the declaration on: the line of the first
92
+ * token of each node that declares it, of a variable's statement and of each parameter
93
+ * of the function it declares. Ascending.
94
+ */
95
+ readonly startLines: readonly number[];
96
+ /** The id of the declaration a directive for a part kind binds on those lines. */
97
+ readonly holder: string;
98
+ /**
99
+ * The lines a directive binds only the declaration's parts on: a class's constructors
100
+ * and their parameters that declare no member. Ascending.
101
+ */
102
+ readonly partLines: readonly number[];
103
+ }
104
+
135
105
  /** What one project's inventory cost at the client boundary. */
136
106
  export interface InventoryCost {
137
107
  /** Batched symbol lookups. One pass over every module and container of the project. */
@@ -139,9 +109,8 @@ export interface InventoryCost {
139
109
  /** Export-table reads: one per module and one per namespace. */
140
110
  readonly exportTables: number;
141
111
  /**
142
- * Member-table reads: one per interface and enum, and two per class, because a
143
- * class keeps its instance members and its static members in two tables. Never
144
- * one per member.
112
+ * Member-table reads: one per interface and enum, and two per class, because a class
113
+ * keeps its instance members and its static members in two tables.
145
114
  */
146
115
  readonly memberTables: number;
147
116
  }
@@ -151,20 +120,16 @@ export interface Inventory {
151
120
  readonly symbols: readonly InventorySymbol[];
152
121
  /**
153
122
  * Each declaring node's {@link nodeKey} mapped to the identifier of the declaration
154
- * enumerated there. A reading that starts from a node of the same program, rather
155
- * than from a position, resolves the declaration through this map instead of
156
- * rendering the position a second time, so the rule that decides a declaration's
157
- * identifier has one owner.
158
- *
159
- * A getter and a setter of one name are one declaration and two nodes, so both nodes
160
- * name it.
123
+ * enumerated there, so a reading that starts from a node resolves the declaration
124
+ * without rendering its position again. A getter and a setter of one name are one
125
+ * declaration and two nodes, so both nodes name it.
161
126
  */
162
127
  readonly declarations: ReadonlyMap<string, string>;
163
128
  readonly cost: InventoryCost;
164
129
  /**
165
- * The declarations whose module, namespace or container table the checker did not
166
- * answer, in position order: whether each is exported, and which members a table
167
- * would have added, are unknown.
130
+ * The declarations whose export or member table is unknown, in position order: the
131
+ * checker did not answer a table of theirs or of a container holding them, or they are
132
+ * entries of a CommonJS export table, whose readers no reference source models.
168
133
  */
169
134
  readonly unanswered: readonly string[];
170
135
  /**
@@ -176,20 +141,57 @@ export interface Inventory {
176
141
  readonly modules: ReadonlyMap<string, string>;
177
142
  /**
178
143
  * The ids of the ambient declarations: those of a declaration file, and those a `declare`
179
- * modifier carries or a declaration carrying one holds. They describe code the target
180
- * does not implement.
144
+ * modifier carries or a declaration carrying one holds.
181
145
  */
182
146
  readonly ambient: ReadonlySet<string>;
183
147
  /**
184
148
  * Each enum member's dependents, by id: the later members of its declaration with no
185
149
  * initializer, up to the first one with an initializer, whose values its deletion moves.
186
- * A member with none has no entry.
187
150
  */
188
151
  readonly dependents: ReadonlyMap<string, readonly string[]>;
152
+ /** Each declaration but a file, by id, to where a directive binds it. */
153
+ readonly placement: ReadonlyMap<string, Placement>;
154
+ /**
155
+ * The ids of the CommonJS files that write their exports in a form their table does not
156
+ * model, so an import of one may read anything it exports.
157
+ */
158
+ // deadset:ignore DS1301 -- only tests read open tables
159
+ readonly openTables: ReadonlySet<string>;
160
+ }
161
+
162
+ /**
163
+ * One node's identity inside one program, which is how two readings of that program name
164
+ * one declaration: the binder's table and the tree meet here, and so does the reference
165
+ * pass, which starts from a resolved symbol's declaration.
166
+ */
167
+ export function nodeKey(file: SourceFile, node: Node): string {
168
+ return `${file.fileName}:${String(node.pos)}:${String(node.end)}:${String(node.kind)}`;
169
+ }
170
+
171
+ /**
172
+ * Whether one node is a parameter property: a constructor parameter carrying an
173
+ * accessibility, `readonly` or `override` modifier, which also declares an instance member
174
+ * of the class, with the parameter's node as the member's declaration.
175
+ */
176
+ export function isParameterProperty(node: Node): boolean {
177
+ return (
178
+ isParameterDeclaration(node) &&
179
+ isConstructorDeclaration(node.parent) &&
180
+ (modifiersOf(node) & ModifierFlags.ParameterPropertyModifier) !== 0
181
+ );
182
+ }
183
+
184
+ /**
185
+ * The fragment component one declaration's name spells in a reference, as the inventory
186
+ * spells it, or undefined for a name the grammar does not represent.
187
+ */
188
+ export function declaredComponent(node: Node): string | undefined {
189
+ const component = componentOf(nameNodeOf(node));
190
+ return component === undefined ? undefined : spell(component);
189
191
  }
190
192
 
191
193
  /** A declaration under construction, before its reference is rendered. */
192
- interface Building {
194
+ export interface Building {
193
195
  readonly file: SourceFile;
194
196
  readonly node: Node;
195
197
  readonly module: Module;
@@ -198,10 +200,7 @@ interface Building {
198
200
  readonly kind: SymbolKind;
199
201
  /** The chain of the container, without this declaration's own component. */
200
202
  readonly parentChain: readonly Component[];
201
- /**
202
- * This declaration's own component. A default export's is its export name, which
203
- * is where the component and the declared name part company.
204
- */
203
+ /** This declaration's own component: a default export's is its export name. */
205
204
  readonly component: Component;
206
205
  /** The declared name, where it is not what the component spells. */
207
206
  readonly localName: string | undefined;
@@ -211,21 +210,25 @@ interface Building {
211
210
  exported: boolean;
212
211
  /** The type parameter this declaration is, where it is one. */
213
212
  readonly typeParameter: string | undefined;
213
+ /** The declaration that holds this one's parts: a parameter property's class, else itself. */
214
+ readonly holder: Building | undefined;
215
+ readonly startLines: Set<number>;
216
+ readonly partLines: Set<number>;
214
217
  }
215
218
 
216
219
  /** A container whose members are read from a symbol table, from the tree, or both. */
217
- interface Container {
220
+ export interface Container {
218
221
  readonly owner: Building;
219
222
  /** The declaration that writes the container, which owns the members written inside it. */
220
223
  readonly declaration: Node;
221
- /** The container's name node, which is what a batch resolves to its symbol. */
224
+ /** The container's name node, which a batch resolves to its symbol; none reads no table. */
222
225
  readonly nameNode: Node | undefined;
223
226
  readonly members: readonly Node[];
227
+ /**
228
+ * `class-member` for a class, whose static members are a second table, and `enum-member`
229
+ * for an enum, which keeps its members in its export table.
230
+ */
224
231
  readonly memberKind: SymbolKind;
225
- /** Whether the container is a class, whose static members are a second table. */
226
- readonly isClass: boolean;
227
- /** Whether the container keeps its members in the export table, as an enum does. */
228
- readonly membersAreExports: boolean;
229
232
  }
230
233
 
231
234
  /** A module or namespace whose export table says which of its declarations it exports. */
@@ -234,95 +237,270 @@ interface ExportScope {
234
237
  readonly nameNode: Node;
235
238
  }
236
239
 
237
- /** One manifest that names a package, and the directory below the root that holds it. */
240
+ /** The name a declaration is declared under: its component, and its declared name beside it. */
238
241
  interface Named {
239
- readonly name: string;
240
- readonly at: string;
242
+ readonly component: Component;
243
+ readonly localName: string | undefined;
244
+ }
245
+
246
+ /** One mechanism that finds the declarations a file writes, run per file in list order. */
247
+ export interface DeclarationSource {
248
+ declare(file: SourceFile, owner: Building, into: Builder): void;
241
249
  }
242
250
 
243
251
  /**
244
- * Resolves each file's package scope: the name of the nearest manifest at or above
245
- * its directory up to the target root, and the file's path below that manifest.
246
- * Where no manifest up to the root carries a name, the package is a single dot and
247
- * the path is read from the root, which cannot collide, because a package name never
248
- * starts with a dot.
249
- *
250
- * One directory is read at most once, because a package holds many files and a
251
- * manifest read is a filesystem call.
252
+ * Collects one project's declarations, from every {@link DeclarationSource} and from the
253
+ * binder's tables, and renders them. A member is kept once per container, by its
254
+ * component and its side of a class, whichever mechanism names it first.
252
255
  */
253
- export function packageScope(host: Host, root: string): (path: string) => Module {
254
- const named = new Map<string, Named | undefined>();
256
+ export class Builder {
257
+ readonly #targetRoot: string;
258
+ readonly #records: Building[] = [];
259
+ readonly #byNode = new Map<string, Building>();
260
+ readonly #exportScopes: ExportScope[] = [];
261
+ readonly #containers: Container[] = [];
262
+ readonly #modules: (readonly [Building, string])[] = [];
263
+ readonly #unansweredOwners = new Set<Building>();
264
+ readonly #openTables = new Set<Building>();
255
265
 
256
- const manifestName = (dir: string): string | undefined => {
257
- const name = manifestAt(host, joinPath(root, dir))?.["name"];
258
- return typeof name === "string" && name !== "" ? name : undefined;
259
- };
266
+ constructor(targetRoot: string) {
267
+ this.#targetRoot = targetRoot;
268
+ }
260
269
 
261
- const nearest = (dir: string): Named | undefined => {
262
- if (named.has(dir)) {
263
- return named.get(dir);
264
- }
265
- const name = manifestName(dir);
266
- const found =
267
- name !== undefined ? { name, at: dir } : dir === "" ? undefined : nearest(parentOf(dir));
268
- named.set(dir, found);
269
- return found;
270
- };
270
+ get exportScopes(): readonly ExportScope[] {
271
+ return this.#exportScopes;
272
+ }
273
+
274
+ get containers(): readonly Container[] {
275
+ return this.#containers;
276
+ }
277
+
278
+ /** One file's own record, which is also the export scope of its module. */
279
+ file(file: SourceFile, module: Module, path: string): Building {
280
+ const record = this.#keep(file, file, 0, {
281
+ module,
282
+ kind: "file",
283
+ parentChain: [],
284
+ // A file's reference names its module, so no component of it is ever spelled; the
285
+ // path is what a text line renders for it.
286
+ component: { text: path, computed: false },
287
+ localName: path,
288
+ parent: undefined,
289
+ });
290
+ this.#exportScopes.push({ owner: record, nameNode: file });
291
+ return record;
292
+ }
293
+
294
+ /** One declaration written under `owner`, placed at its name, and its type parameters. */
295
+ declare(
296
+ node: Node,
297
+ owner: Building,
298
+ kind: SymbolKind,
299
+ name: Node | undefined,
300
+ named: Named,
301
+ ): Building {
302
+ const record = this.#keep(owner.file, node, name?.getStart() ?? unnamedStart(node), {
303
+ module: owner.module,
304
+ kind,
305
+ parentChain: owner.kind === "file" ? [] : chainOf(owner),
306
+ ...named,
307
+ parent: owner,
308
+ visibility: visibilityOf(node, name),
309
+ static: (modifiersOf(node) & ModifierFlags.Static) !== 0,
310
+ });
311
+ this.#declareTypeParameters(node, record);
312
+ return record;
313
+ }
271
314
 
272
- return (path) => {
273
- const found = nearest(parentOf(path));
274
- if (found === undefined) {
275
- return { package: ".", path };
315
+ /** A container of members; a class's constructors are where its parts are bound. */
316
+ container(container: Container): void {
317
+ this.#containers.push(container);
318
+ if (container.memberKind !== "class-member") {
319
+ return;
276
320
  }
277
- return { package: found.name, path: found.at === "" ? path : path.slice(found.at.length + 1) };
278
- };
279
- }
321
+ for (const member of container.members.filter(isConstructorDeclaration)) {
322
+ for (const at of [member, ...member.parameters.filter((one) => !isParameterProperty(one))]) {
323
+ container.owner.partLines.add(startLine(container.owner.file, at.pos));
324
+ }
325
+ }
326
+ }
280
327
 
281
- /** The directory one path below the root sits in, and the root itself as the empty path. */
282
- function parentOf(path: string): string {
283
- const dir = dirnamePath(path);
284
- return dir === "." ? "" : dir;
285
- }
328
+ /** A namespace whose export table says which of its declarations it exports. */
329
+ exportScope(owner: Building, nameNode: Node): void {
330
+ this.#exportScopes.push({ owner, nameNode });
331
+ }
286
332
 
287
- /** The modifier flags one node carries, and zero for a node that carries none. */
288
- function modifiersOf(node: Node): number {
289
- return (node as { readonly modifierFlags?: number }).modifierFlags ?? 0;
290
- }
333
+ /** A `declare module` declaration named by a string literal. */
334
+ module(record: Building, name: string): void {
335
+ this.#modules.push([record, name]);
336
+ }
291
337
 
292
- /** Whether a declaration is ambient: in a declaration file, or under a `declare` modifier. */
293
- function isAmbient(file: SourceFile, node: Node): boolean {
294
- if (file.isDeclarationFile) {
295
- return true;
338
+ /** The declaration enumerated at one node, where one is. */
339
+ recordAt(node: Node): Building | undefined {
340
+ return this.#byNode.get(nodeKey(node.getSourceFile(), node));
296
341
  }
297
- for (let at: Node | undefined = node; at !== undefined; at = at.parent as Node | undefined) {
298
- if ((modifiersOf(at) & ModifierFlags.Ambient) !== 0) {
299
- return true;
342
+
343
+ /** Records that a table of `owner` is unknown, which leaves everything under it unknown. */
344
+ unanswered(owner: Building): void {
345
+ this.#unansweredOwners.add(owner);
346
+ }
347
+
348
+ /** Records that one file writes its exports in a form its table does not model. */
349
+ openTable(file: Building): void {
350
+ this.#openTables.add(file);
351
+ }
352
+
353
+ /**
354
+ * One member of a container. A getter and a setter of one name are one symbol and one
355
+ * reference, so the second declaration widens the first record rather than making a
356
+ * second, and both nodes name that record.
357
+ */
358
+ member(container: Container, node: Node, seen: Map<string, Building>, areStatic: boolean): void {
359
+ const kind = memberKindOf(container, node);
360
+ if (kind === undefined) {
361
+ return;
300
362
  }
363
+ const name = nameNodeOf(node);
364
+ const component = componentOf(name);
365
+ if (component === undefined) {
366
+ return;
367
+ }
368
+ const file = node.getSourceFile();
369
+ const key = memberKey(component, areStatic);
370
+ const held = seen.get(key);
371
+ const endLine = this.#endOf(file, node);
372
+ if (held !== undefined) {
373
+ held.endLine = Math.max(held.endLine, endLine);
374
+ this.#byNode.set(nodeKey(file, node), held);
375
+ placeAt(held, node);
376
+ return;
377
+ }
378
+ const record = this.#keep(file, node, (name ?? node).getStart(), {
379
+ module: container.owner.module,
380
+ kind,
381
+ parentChain: chainOf(container.owner),
382
+ component,
383
+ parent: container.owner,
384
+ visibility: visibilityOf(node, name),
385
+ static: areStatic,
386
+ holder: isParameterProperty(node) ? container.owner : undefined,
387
+ });
388
+ seen.set(key, record);
389
+ this.#declareTypeParameters(node, record);
390
+ }
391
+
392
+ /** The inventory of everything collected. */
393
+ build(cost: InventoryCost): Inventory {
394
+ const byId = new Map(this.#records.map((record) => [positionKey(record.position), record]));
395
+ const symbols = this.#records.map(render).sort((a, b) => byPosition(a.position, b.position));
396
+ return {
397
+ symbols,
398
+ declarations: new Map(
399
+ [...this.#byNode].map(([key, record]) => [key, positionKey(record.position)] as const),
400
+ ),
401
+ cost,
402
+ unanswered: symbols
403
+ .filter((symbol) => underUnanswered(byId.get(symbol.id), this.#unansweredOwners))
404
+ .map((symbol) => symbol.id),
405
+ byName: namesOf(this.#records),
406
+ modules: new Map(this.#modules.map(([record, name]) => [positionKey(record.position), name])),
407
+ ambient: new Set(
408
+ this.#records
409
+ .filter((record) => isAmbient(record.file, record.node))
410
+ .map((record) => positionKey(record.position)),
411
+ ),
412
+ dependents: dependentsOf(this.#containers, (node) => this.recordAt(node)),
413
+ placement: placementOf(this.#records),
414
+ openTables: new Set([...this.#openTables].map((file) => positionKey(file.position))),
415
+ };
416
+ }
417
+
418
+ #keep(
419
+ file: SourceFile,
420
+ node: Node,
421
+ at: number,
422
+ fields: Pick<Building, "module" | "kind" | "parentChain" | "component" | "parent"> &
423
+ Partial<Pick<Building, "localName" | "visibility" | "static" | "typeParameter" | "holder">>,
424
+ ): Building {
425
+ const kept: Building = {
426
+ file,
427
+ node,
428
+ position: renderPosition(file, this.#targetRoot, at),
429
+ endLine: this.#endOf(file, node),
430
+ localName: undefined,
431
+ visibility: "public",
432
+ static: false,
433
+ exported: false,
434
+ typeParameter: undefined,
435
+ holder: undefined,
436
+ ...fields,
437
+ startLines: new Set(),
438
+ partLines: new Set(),
439
+ };
440
+ this.#records.push(kept);
441
+ this.#byNode.set(nodeKey(kept.file, kept.node), kept);
442
+ if (kept.kind !== "file") {
443
+ placeAt(kept, kept.node);
444
+ }
445
+ return kept;
446
+ }
447
+
448
+ #declareTypeParameters(node: Node, owner: Building): void {
449
+ for (const parameter of typeParametersOf(node)) {
450
+ const name = nameNodeOf(parameter);
451
+ if (name === undefined) {
452
+ continue;
453
+ }
454
+ this.#keep(owner.file, parameter, name.getStart(), {
455
+ module: owner.module,
456
+ kind: "type-parameter",
457
+ parentChain: owner.parentChain,
458
+ component: owner.component,
459
+ parent: owner,
460
+ static: owner.static,
461
+ typeParameter: name.getText(),
462
+ });
463
+ }
464
+ }
465
+
466
+ #endOf(file: SourceFile, node: Node): number {
467
+ return renderPosition(file, this.#targetRoot, Math.max(node.end - 1, 0)).line;
301
468
  }
302
- return false;
303
469
  }
304
470
 
305
- /** The type parameters one node declares, and none for a node that declares none. */
306
- function typeParametersOf(node: Node): readonly Node[] {
471
+ /** The lines one declaring node binds its declaration on. */
472
+ function placeAt(record: Building, node: Node): void {
473
+ const file = record.file;
474
+ record.startLines.add(startLine(file, node.pos));
475
+ for (const parameter of signatureOf(node)?.parameters ?? []) {
476
+ record.startLines.add(startLine(file, parameter.pos));
477
+ }
478
+ const list = node.parent as Node | undefined;
307
479
  if (
308
- isFunctionDeclaration(node) ||
309
- isMethodDeclaration(node) ||
310
- isMethodSignatureDeclaration(node) ||
311
- isClassDeclaration(node) ||
312
- isInterfaceDeclaration(node) ||
313
- isTypeAliasDeclaration(node)
480
+ node.kind === SyntaxKind.VariableDeclaration &&
481
+ list?.kind === SyntaxKind.VariableDeclarationList &&
482
+ list.parent.kind === SyntaxKind.VariableStatement
314
483
  ) {
315
- return node.typeParameters ?? [];
484
+ record.startLines.add(startLine(file, list.parent.pos));
316
485
  }
317
- return [];
486
+ }
487
+
488
+ /** The modifier flags one node carries, and zero for a node that carries none. */
489
+ export function modifiersOf(node: Node): number {
490
+ return (node as { readonly modifierFlags?: number }).modifierFlags ?? 0;
491
+ }
492
+
493
+ /** The name node of one declaration, for the kinds that carry one. */
494
+ export function nameNodeOf(node: Node): Node | undefined {
495
+ return (node as { readonly name?: Node }).name;
318
496
  }
319
497
 
320
498
  /**
321
- * The reference component one declaration name node spells, and undefined for a
322
- * name the grammar does not represent: a binding pattern, and a computed key whose
323
- * source text carries a closing bracket.
499
+ * The reference component one declaration name node spells, and undefined for a name the
500
+ * grammar does not represent: a binding pattern, and a computed key whose source text
501
+ * carries a closing bracket.
324
502
  */
325
- function componentOf(name: Node | undefined): Component | undefined {
503
+ export function componentOf(name: Node | undefined): Component | undefined {
326
504
  if (name === undefined) {
327
505
  return undefined;
328
506
  }
@@ -333,108 +511,20 @@ function componentOf(name: Node | undefined): Component | undefined {
333
511
  if (isIdentifier(name) || isPrivateIdentifier(name)) {
334
512
  return { text: name.getText(), computed: false };
335
513
  }
336
- if (isStringOrNumericName(name)) {
337
- // A string or numeric key's own text carries its quotes and its numeric
338
- // spelling; the reference carries the value, which the component then quotes.
339
- return { text: literalTextOf(name), computed: false };
340
- }
341
- return undefined;
342
- }
343
-
344
- function isStringOrNumericName(name: Node): boolean {
345
- const text = name.getText();
346
- return (
347
- text.startsWith('"') || text.startsWith("'") || text.startsWith("`") || /^[0-9]/u.test(text)
348
- );
349
- }
350
-
351
- /** One member's identity inside its container: its component, and its side of a class. */
352
- function memberKey(component: Component, isStatic: boolean): string {
353
- return `${component.computed ? "[" : ""}${component.text}${isStatic ? ":static" : ""}`;
354
- }
355
-
356
- /** The type one node is, through however many parentheses are written around it. */
357
- function unparenthesizedType(type: Node): Node {
358
- let held = type;
359
- while (isParenthesizedTypeNode(held)) {
360
- held = held.type;
361
- }
362
- return held;
363
- }
364
-
365
- /**
366
- * The direct constituents of one type. Parentheses are syntax and are not a
367
- * constituent, so a parenthesized type contributes the constituents it holds: the
368
- * constituents of `(A | B) & C` are `A`, `B` and `C`. A union or an intersection
369
- * written without them stays one constituent.
370
- */
371
- function constituentsOf(type: Node): readonly Node[] {
372
- const held = unparenthesizedType(type);
373
- if (!isUnionTypeNode(held) && !isIntersectionTypeNode(held)) {
374
- return [held];
375
- }
376
- return held.types.flatMap((member) =>
377
- isParenthesizedTypeNode(member) ? constituentsOf(member) : [member],
378
- );
379
- }
380
-
381
- /**
382
- * The members one type alias declares: the members of the object type it writes, and
383
- * of every object type that is a direct constituent of a union or an intersection it
384
- * writes. A constituent naming another type declares nothing here, because that
385
- * declaration carries its own members.
386
- *
387
- * A name two constituents both declare is left out of all of them. The alias stands
388
- * for such a member, because the two declarations are one member of the type and
389
- * neither of them is the one a reference to it would name.
390
- */
391
- function aliasMembers(type: Node): readonly Node[] {
392
- const literals = constituentsOf(type).filter(isTypeLiteralNode);
393
- if (literals.length < 2) {
394
- return literals[0]?.members ?? [];
395
- }
396
-
397
- const declaring = new Map<string, number>();
398
- for (const literal of literals) {
399
- for (const key of new Set(literal.members.map(keyOf))) {
400
- if (key !== undefined) {
401
- declaring.set(key, (declaring.get(key) ?? 0) + 1);
402
- }
403
- }
404
- }
405
- return literals.flatMap((literal) =>
406
- literal.members.filter((member) => {
407
- const key = keyOf(member);
408
- return key !== undefined && declaring.get(key) === 1;
409
- }),
410
- );
411
- }
412
-
413
- /** One type member's identity, and undefined for a member the grammar cannot name. */
414
- function keyOf(member: Node): string | undefined {
415
- const component = componentOf(nameNodeOf(member));
416
- return component === undefined ? undefined : memberKey(component, false);
417
- }
418
-
419
- /** The value one string or numeric key denotes, which is what the reference names. */
420
- function literalTextOf(name: Node): string {
421
514
  const text = name.getText();
422
515
  if (text.startsWith('"') || text.startsWith("'") || text.startsWith("`")) {
423
- return text.slice(1, -1);
516
+ // A string key's own text carries its quotes; the reference carries the value.
517
+ return { text: text.slice(1, -1), computed: false };
424
518
  }
425
- return text;
519
+ return /^[0-9]/u.test(text) ? { text, computed: false } : undefined;
426
520
  }
427
521
 
428
522
  /**
429
- * The component one declaration's reference carries, and its declared name where the
430
- * two differ.
431
- *
432
- * A declaration its module exports as the default is named `default`, because that is
433
- * its export name and a module has at most one, so the spelling is unambiguous and it
434
- * holds for an anonymous declaration too. Its declared name is a fact about the source
435
- * rather than about the export, so it travels beside the component as the display name.
523
+ * The component one declaration's reference carries, and its declared name where the two
524
+ * differ. A declaration its module exports as the default is named `default`, its export
525
+ * name, which a module has at most one of and an anonymous declaration has too.
436
526
  */
437
- function exportedAs(
527
+ export function exportedAs(
438
528
  node: Node,
439
529
  name: Node | undefined,
440
530
  ): { readonly component: Component | undefined; readonly localName: string | undefined } {
@@ -445,6 +535,11 @@ function exportedAs(
445
535
  return { component: { text: "default", computed: false }, localName: declared?.text };
446
536
  }
447
537
 
538
+ /** One member's identity inside its container: its component, and its side of a class. */
539
+ export function memberKey(component: Component, isStatic: boolean): string {
540
+ return `${component.computed ? "[" : ""}${component.text}${isStatic ? ":static" : ""}`;
541
+ }
542
+
448
543
  /** The visibility one member declaration's modifiers and name give it. */
449
544
  function visibilityOf(node: Node, name: Node | undefined): Visibility {
450
545
  if (name !== undefined && isPrivateIdentifier(name)) {
@@ -460,73 +555,32 @@ function visibilityOf(node: Node, name: Node | undefined): Visibility {
460
555
  return "public";
461
556
  }
462
557
 
463
- /**
464
- * The binding elements of a destructuring declaration that each declare a name of their
465
- * own, at any depth, but one with a default, whose removal skips the default's
466
- * evaluation, and one beside a rest element of its object pattern, whose removal
467
- * changes what the rest holds.
468
- */
469
- function patternElements(
470
- name: Node,
471
- ): readonly { readonly element: BindingElement; readonly name: Identifier }[] {
472
- if (!isObjectBindingPattern(name) && !isArrayBindingPattern(name)) {
473
- return [];
558
+ /** Whether a declaration is ambient: in a declaration file, or under a `declare` modifier. */
559
+ export function isAmbient(file: SourceFile, node: Node): boolean {
560
+ if (file.isDeclarationFile) {
561
+ return true;
474
562
  }
475
- const rest =
476
- isObjectBindingPattern(name) &&
477
- name.elements.some((element) => element.dotDotDotToken !== undefined);
478
- return name.elements.flatMap((element) => {
479
- if (
480
- !isBindingElement(element) ||
481
- element.initializer !== undefined ||
482
- (rest && element.dotDotDotToken === undefined)
483
- ) {
484
- return [];
485
- }
486
- const bound = element.name;
487
- if (bound === undefined) {
488
- return [];
563
+ for (let at: Node | undefined = node; at !== undefined; at = at.parent as Node | undefined) {
564
+ if ((modifiersOf(at) & ModifierFlags.Ambient) !== 0) {
565
+ return true;
489
566
  }
490
- return isIdentifier(bound) ? [{ element, name: bound }] : patternElements(bound);
491
- });
492
- }
493
-
494
- /** The name node of one declaration, for the kinds that carry one. */
495
- function nameNodeOf(node: Node): Node | undefined {
496
- return (node as { readonly name?: Node }).name;
497
- }
498
-
499
- /**
500
- * The fragment component one declaration's name spells in a reference, as the inventory
501
- * spells it, or undefined for a name the grammar does not represent.
502
- */
503
- export function declaredComponent(node: Node): string | undefined {
504
- const component = componentOf(nameNodeOf(node));
505
- if (component === undefined) {
506
- return undefined;
507
567
  }
508
- return component.computed ? computedComponent(component.text) : nameComponent(component.text);
509
- }
510
-
511
- /**
512
- * One node's identity inside one program, which is how two readings of that program
513
- * name one declaration.
514
- *
515
- * The two mechanisms behind the member set resolve a member separately and meet here;
516
- * so does the reference pass, which starts from a resolved symbol's declaration and
517
- * needs the declaration this enumeration made for it.
518
- */
519
- export function nodeKey(file: SourceFile, node: Node): string {
520
- return `${file.fileName}:${String(node.pos)}:${String(node.end)}:${String(node.kind)}`;
568
+ return false;
521
569
  }
522
570
 
523
- /** Whether `node` is written inside `declaration`, compared by file and range. */
524
- function writtenInside(declaration: Node, node: Node): boolean {
525
- return (
526
- node.getSourceFile().fileName === declaration.getSourceFile().fileName &&
527
- declaration.pos <= node.pos &&
528
- node.end <= declaration.end
529
- );
571
+ /** The type parameters one node declares, and none for a node that declares none. */
572
+ function typeParametersOf(node: Node): readonly Node[] {
573
+ if (
574
+ isFunctionDeclaration(node) ||
575
+ isMethodDeclaration(node) ||
576
+ isMethodSignatureDeclaration(node) ||
577
+ isClassDeclaration(node) ||
578
+ isInterfaceDeclaration(node) ||
579
+ isTypeAliasDeclaration(node)
580
+ ) {
581
+ return node.typeParameters ?? [];
582
+ }
583
+ return [];
530
584
  }
531
585
 
532
586
  /** `export default`, up to the keyword that stands where an unnamed default export's name would. */
@@ -542,537 +596,47 @@ function unnamedStart(node: Node): number {
542
596
  return prefix === null ? start : start + prefix[0].length;
543
597
  }
544
598
 
545
- /**
546
- * Whether one node is a parameter property: a constructor parameter carrying an
547
- * accessibility, `readonly` or `override` modifier, which also declares an instance
548
- * member of the class, with the parameter's node as the member's declaration.
549
- */
550
- export function isParameterProperty(node: Node): boolean {
551
- return (
552
- isParameterDeclaration(node) &&
553
- isConstructorDeclaration(node.parent) &&
554
- (modifiersOf(node) & ModifierFlags.ParameterPropertyModifier) !== 0
555
- );
556
- }
557
-
558
- /** The parameter properties every constructor of one class declares. */
559
- function parameterPropertiesOf(node: ClassDeclaration): readonly Node[] {
560
- return node.members.flatMap((member) =>
561
- isConstructorDeclaration(member) ? member.parameters.filter(isParameterProperty) : [],
562
- );
563
- }
564
-
565
- /** The kind a class element takes: a method, or a property or accessor. */
566
- function classMemberKind(node: Node): SymbolKind {
567
- return isMethodDeclaration(node) ? "method" : "class-member";
568
- }
569
-
570
- /** The kind a type element takes: a method of an interface, or any other member. */
571
- function typeMemberKind(node: Node, inInterface: boolean): SymbolKind {
572
- return inInterface && isMethodSignatureDeclaration(node) ? "interface-method" : "type-member";
573
- }
574
-
575
- /**
576
- * Every declaration one project's own files hold.
577
- *
578
- * `targetRoot` is the absolute path every position is rendered against; a file the
579
- * program holds that is not below it ends the enumeration rather than being passed
580
- * over. `host` is the filesystem the manifests that name each file's package are
581
- * read through, and nothing else is read from it.
582
- *
583
- * The order is by rendered position, so two enumerations of one project return the
584
- * same list whatever order the program reported its files in.
585
- */
586
- export function inventory<Brand>(
587
- project: ProjectView<Brand>,
588
- host: Host,
589
- targetRoot: string,
590
- ): Inventory {
591
- const files = project.ownSourceFiles();
592
- const ownFiles = new Set(files.map((file) => file.fileName));
593
- const moduleOf = packageScope(host, targetRoot);
594
-
595
- const building: Building[] = [];
596
- const byNode = new Map<string, Building>();
597
- const exportScopes: ExportScope[] = [];
598
- const containers: Container[] = [];
599
- const modules: (readonly [Building, string])[] = [];
600
-
601
- const keep = (record: Building): Building => {
602
- building.push(record);
603
- byNode.set(nodeKey(record.file, record.node), record);
604
- return record;
605
- };
606
-
607
- for (const file of files) {
608
- const path = relativePath(targetRoot, file.fileName);
609
- if (path === undefined) {
610
- // renderPosition states the refusal, with the root it was rendered against.
611
- renderPosition(file, targetRoot, 0);
612
- continue;
613
- }
614
- const module = moduleOf(path);
615
- const fileRecord = keep({
616
- file,
617
- node: file,
618
- module,
619
- position: renderPosition(file, targetRoot, 0),
620
- endLine: renderPosition(file, targetRoot, Math.max(file.end - 1, 0)).line,
621
- kind: "file",
622
- parentChain: [],
623
- // A file's reference names its module, so no component of it is ever spelled;
624
- // the path is what a text line renders for it.
625
- component: { text: path, computed: false },
626
- localName: path,
627
- parent: undefined,
628
- visibility: "public",
629
- static: false,
630
- exported: false,
631
- typeParameter: undefined,
632
- });
633
- exportScopes.push({ owner: fileRecord, nameNode: file });
634
- // A statement a component file's module appends is not written in the file.
635
- const written = writtenLength(file);
636
- walkStatements(
637
- file.statements.filter((statement) => statement.end <= written),
638
- fileRecord,
639
- );
640
- }
641
-
642
- /** One declaration of a module, a namespace or a container body. */
643
- function walkStatements(statements: readonly Node[], owner: Building): void {
644
- for (const statement of statements) {
645
- walkStatement(statement, owner);
646
- }
647
- }
648
-
649
- function declare(
650
- node: Node,
651
- owner: Building,
652
- kind: SymbolKind,
653
- name: Node | undefined,
654
- named: { readonly component: Component; readonly localName: string | undefined },
655
- ): Building {
656
- const record = keep({
657
- file: owner.file,
658
- node,
659
- module: owner.module,
660
- position: renderPosition(owner.file, targetRoot, name?.getStart() ?? unnamedStart(node)),
661
- endLine: renderPosition(owner.file, targetRoot, Math.max(node.end - 1, 0)).line,
662
- kind,
663
- parentChain: owner.kind === "file" ? [] : chainOf(owner),
664
- component: named.component,
665
- localName: named.localName,
666
- parent: owner,
667
- visibility: visibilityOf(node, name),
668
- static: (modifiersOf(node) & ModifierFlags.Static) !== 0,
669
- exported: false,
670
- typeParameter: undefined,
671
- });
672
- declareTypeParameters(node, record);
673
- return record;
674
- }
675
-
676
- function declareTypeParameters(node: Node, owner: Building): void {
677
- for (const parameter of typeParametersOf(node)) {
678
- const name = nameNodeOf(parameter);
679
- if (name === undefined) {
680
- continue;
681
- }
682
- keep({
683
- file: owner.file,
684
- node: parameter,
685
- module: owner.module,
686
- position: renderPosition(owner.file, targetRoot, name.getStart()),
687
- endLine: renderPosition(owner.file, targetRoot, Math.max(parameter.end - 1, 0)).line,
688
- kind: "type-parameter",
689
- parentChain: owner.parentChain,
690
- component: owner.component,
691
- localName: undefined,
692
- parent: owner,
693
- visibility: "public",
694
- static: owner.static,
695
- exported: false,
696
- typeParameter: name.getText(),
697
- });
698
- }
699
- }
700
-
701
- function walkStatement(node: Node, owner: Building): void {
702
- if (isFunctionDeclaration(node)) {
703
- const named = exportedAs(node, node.name);
704
- if (named.component !== undefined) {
705
- declare(node, owner, "function", node.name, { ...named, component: named.component });
706
- }
707
- return;
708
- }
709
- if (isClassDeclaration(node)) {
710
- const named = exportedAs(node, node.name);
711
- if (named.component === undefined) {
712
- return;
713
- }
714
- const record = declare(node, owner, "class", node.name, {
715
- ...named,
716
- component: named.component,
717
- });
718
- containers.push({
719
- owner: record,
720
- declaration: node,
721
- nameNode: node.name,
722
- members: [...node.members, ...parameterPropertiesOf(node)],
723
- memberKind: "class-member",
724
- isClass: true,
725
- membersAreExports: false,
726
- });
727
- return;
728
- }
729
- if (isInterfaceDeclaration(node)) {
730
- const named = exportedAs(node, node.name);
731
- if (named.component === undefined) {
732
- return;
733
- }
734
- const record = declare(node, owner, "interface", node.name, {
735
- ...named,
736
- component: named.component,
737
- });
738
- containers.push({
739
- owner: record,
740
- declaration: node,
741
- nameNode: node.name,
742
- members: node.members,
743
- memberKind: "interface-method",
744
- isClass: false,
745
- membersAreExports: false,
746
- });
747
- return;
748
- }
749
- if (isTypeAliasDeclaration(node)) {
750
- const named = exportedAs(node, node.name);
751
- if (named.component === undefined) {
752
- return;
753
- }
754
- const record = declare(node, owner, "type", node.name, {
755
- ...named,
756
- component: named.component,
757
- });
758
- // A type alias keeps no member table of its own: the members belong to the
759
- // object types its declaration writes, so the declaration is what carries them.
760
- const members = aliasMembers(node.type);
761
- if (members.length > 0) {
762
- containers.push({
763
- owner: record,
764
- declaration: node,
765
- nameNode: undefined,
766
- members,
767
- memberKind: "type-member",
768
- isClass: false,
769
- membersAreExports: false,
770
- });
771
- }
772
- return;
773
- }
774
- if (isEnumDeclaration(node)) {
775
- const named = exportedAs(node, node.name);
776
- if (named.component === undefined) {
777
- return;
778
- }
779
- const record = declare(node, owner, "enum", node.name, {
780
- ...named,
781
- component: named.component,
782
- });
783
- containers.push({
784
- owner: record,
785
- declaration: node,
786
- nameNode: node.name,
787
- members: node.members,
788
- memberKind: "enum-member",
789
- isClass: false,
790
- membersAreExports: true,
791
- });
792
- return;
793
- }
794
- if (isModuleDeclaration(node)) {
795
- const component = componentOf(node.name);
796
- if (component === undefined) {
797
- return;
798
- }
799
- const record = declare(node, owner, "namespace", node.name, {
800
- component,
801
- localName: undefined,
802
- });
803
- exportScopes.push({ owner: record, nameNode: node.name });
804
- if (isStringLiteral(node.name)) {
805
- modules.push([record, node.name.text]);
806
- }
807
- const body = node.body;
808
- if (body === undefined) {
809
- return;
810
- }
811
- if (isModuleBlock(body)) {
812
- walkStatements(body.statements, record);
813
- return;
814
- }
815
- walkStatement(body, record);
816
- return;
817
- }
818
- if (isVariableStatement(node)) {
819
- for (const declaration of node.declarationList.declarations) {
820
- const component = componentOf(declaration.name);
821
- if (component !== undefined) {
822
- declare(declaration, owner, "variable", declaration.name, {
823
- component,
824
- localName: undefined,
825
- });
826
- continue;
827
- }
828
- for (const { element, name } of patternElements(declaration.name)) {
829
- declare(element, owner, "variable", name, {
830
- component: { text: name.text, computed: false },
831
- localName: undefined,
832
- });
833
- }
834
- }
835
- return;
836
- }
837
- if (isExportDeclaration(node)) {
838
- walkExportDeclaration(node, owner);
839
- return;
840
- }
841
- if (isExportAssignment(node) && !node.isExportEquals) {
842
- declare(node, owner, "export-alias", undefined, {
843
- component: { text: "default", computed: false },
844
- localName: undefined,
845
- });
846
- }
847
- }
848
-
849
- function walkExportDeclaration(node: { readonly exportClause?: Node }, owner: Building): void {
850
- const clause = node.exportClause;
851
- // A bare star re-export declares nothing: the names it carries are the named module's.
852
- if (clause === undefined) {
853
- return;
854
- }
855
- if (isNamespaceExport(clause)) {
856
- const component = componentOf(clause.name);
857
- if (component !== undefined) {
858
- declare(clause, owner, "export-alias", clause.name, { component, localName: undefined });
859
- }
860
- return;
861
- }
862
- for (const element of (clause as { readonly elements?: readonly Node[] }).elements ?? []) {
863
- const name = nameNodeOf(element);
864
- const component = componentOf(name);
865
- if (component !== undefined) {
866
- declare(element, owner, "export-alias", name, { component, localName: undefined });
867
- }
868
- }
869
- }
870
-
871
- // One batch for every module and every container the walk found, because a
872
- // checker accessor with an array overload costs one round trip for the batch and
873
- // one per node without it.
874
- const batched: Node[] = [
875
- ...exportScopes.map((scope) => scope.nameNode),
876
- ...containers.map((container) => container.nameNode).filter((node) => node !== undefined),
877
- ];
878
- const handles: Handle<Brand>[] = batched.map((node) => project.handle(node));
879
- const resolved = project.symbolsAt(handles);
880
- const symbolOf = new Map<Node, TSSymbol>();
881
- // A scope or a container whose symbol or table went unanswered keeps what its syntax
882
- // declares, and everything under it is recorded unanswered.
883
- const unansweredOwners = new Set<Building>();
884
- batched.forEach((node, index) => {
885
- const symbol = resolved[index];
886
- if (typeof symbol === "object") {
887
- symbolOf.set(node, symbol);
888
- }
889
- });
890
- resolved.slice(0, exportScopes.length).forEach((symbol, index) => {
891
- const scope = exportScopes[index];
892
- if (symbol === UNANSWERED && scope !== undefined) {
893
- unansweredOwners.add(scope.owner);
894
- }
895
- });
896
- containers
897
- .filter((container) => container.nameNode !== undefined)
898
- .forEach((container, index) => {
899
- if (resolved[exportScopes.length + index] === UNANSWERED) {
900
- unansweredOwners.add(container.owner);
901
- }
902
- });
903
-
904
- let exportTables = 0;
905
- for (const scope of exportScopes) {
906
- const symbol = symbolOf.get(scope.nameNode);
907
- if (symbol === undefined) {
908
- continue;
909
- }
910
- exportTables += 1;
911
- const table = project.queries.exportsOf(symbol);
912
- if (!isAnswered(table)) {
913
- unansweredOwners.add(scope.owner);
914
- }
915
- for (const [, exported] of isAnswered(table) ? table : []) {
916
- for (const declaration of exported.declarations) {
917
- const held = project.declarationAt(declaration);
918
- if (held === undefined) {
919
- continue;
920
- }
921
- const record = byNode.get(nodeKey(held.node.getSourceFile(), held.node));
922
- if (record === undefined) {
923
- continue;
924
- }
925
- record.exported = true;
926
- }
927
- }
928
- }
929
-
930
- let memberTables = 0;
931
- for (const container of containers) {
932
- const symbol = container.nameNode === undefined ? undefined : symbolOf.get(container.nameNode);
933
- const seen = new Map<string, Building>();
934
- if (symbol !== undefined) {
935
- if (!container.membersAreExports) {
936
- memberTables += 1;
937
- const table = project.queries.membersOf(symbol);
938
- if (isAnswered(table)) {
939
- readTable(container, table, seen, false);
940
- } else {
941
- unansweredOwners.add(container.owner);
942
- }
943
- }
944
- if (container.membersAreExports || container.isClass) {
945
- memberTables += 1;
946
- const table = project.queries.exportsOf(symbol);
947
- if (isAnswered(table)) {
948
- readTable(container, table, seen, container.isClass);
949
- } else {
950
- unansweredOwners.add(container.owner);
951
- }
952
- }
953
- }
954
- completeFromTree(container, seen);
955
- }
956
-
957
- /**
958
- * One container's members as its symbol table holds them, each positioned at the
959
- * declaration the table names. A member with no declaration is the language's own
960
- * (a class's `prototype`), and a member declared outside the target's own files
961
- * belongs to another program; neither is a declaration of the target.
962
- */
963
- function readTable(
964
- container: Container,
965
- table: ReadonlyMap<string, TSSymbol>,
966
- seen: Map<string, Building>,
967
- areStatic: boolean,
968
- ): void {
969
- for (const [, member] of table) {
970
- const declarations = member.declarations
971
- .map((handle) => project.declarationAt(handle))
972
- .filter((held): held is Handle<Brand> => held !== undefined);
973
- if (declarations.length === 0) {
974
- continue;
975
- }
976
- const own = declarations.filter((held) => ownFiles.has(held.node.getSourceFile().fileName));
977
- if (own.length === 0) {
978
- continue;
979
- }
980
- // A merged symbol's table holds the members every one of its declarations writes,
981
- // and each member is kept once, by the declaration that writes it.
982
- for (const held of own.filter((one) => writtenInside(container.declaration, one.node))) {
983
- keepMember(container, held.node, seen, areStatic);
984
- }
599
+ /** The kind one member of a container takes, and undefined for a node that is no member. */
600
+ function memberKindOf(container: Container, node: Node): SymbolKind | undefined {
601
+ if (container.memberKind === "class-member") {
602
+ if (
603
+ isPropertyDeclaration(node) ||
604
+ isGetAccessorDeclaration(node) ||
605
+ isSetAccessorDeclaration(node) ||
606
+ isParameterProperty(node)
607
+ ) {
608
+ return "class-member";
985
609
  }
610
+ return isMethodDeclaration(node) ? "method" : undefined;
986
611
  }
987
-
988
- /**
989
- * One container's members as its own declaration writes them, for the members its
990
- * symbol table does not hold: a member whose key is computed is bound to the
991
- * container's type later than the table is built.
992
- */
993
- function completeFromTree(container: Container, seen: Map<string, Building>): void {
994
- for (const member of container.members) {
995
- keepMember(container, member, seen, (modifiersOf(member) & ModifierFlags.Static) !== 0);
996
- }
612
+ if (container.memberKind === "enum-member") {
613
+ return isEnumMember(node) ? "enum-member" : undefined;
997
614
  }
998
-
999
- function keepMember(
1000
- container: Container,
1001
- node: Node,
1002
- seen: Map<string, Building>,
1003
- areStatic: boolean,
1004
- ): void {
1005
- const kind = memberKindOf(container, node);
1006
- if (kind === undefined) {
1007
- return;
1008
- }
1009
- const name = nameNodeOf(node);
1010
- const component = componentOf(name);
1011
- if (component === undefined) {
1012
- return;
1013
- }
1014
- const file = node.getSourceFile();
1015
- const key = memberKey(component, areStatic);
1016
- const held = seen.get(key);
1017
- const endLine = renderPosition(file, targetRoot, Math.max(node.end - 1, 0)).line;
1018
- if (held !== undefined) {
1019
- // A getter and a setter of one name are one symbol and one reference, so the
1020
- // second declaration widens the first record rather than making a second. Both
1021
- // nodes name that record, so a reading that starts from either one finds it.
1022
- held.endLine = Math.max(held.endLine, endLine);
1023
- byNode.set(nodeKey(file, node), held);
1024
- return;
1025
- }
1026
- const at = name ?? node;
1027
- const record = keep({
1028
- file,
1029
- node,
1030
- module: container.owner.module,
1031
- position: renderPosition(file, targetRoot, at.getStart()),
1032
- endLine,
1033
- kind,
1034
- parentChain: chainOf(container.owner),
1035
- component,
1036
- localName: undefined,
1037
- parent: container.owner,
1038
- visibility: visibilityOf(node, name),
1039
- static: areStatic,
1040
- exported: false,
1041
- typeParameter: undefined,
1042
- });
1043
- seen.set(key, record);
1044
- declareTypeParameters(node, record);
615
+ if (isMethodSignatureDeclaration(node)) {
616
+ return container.memberKind === "interface-method" ? "interface-method" : "type-member";
1045
617
  }
1046
-
1047
- function memberKindOf(container: Container, node: Node): SymbolKind | undefined {
1048
- if (container.isClass) {
1049
- if (
1050
- isPropertyDeclaration(node) ||
1051
- isMethodDeclaration(node) ||
1052
- isGetAccessorDeclaration(node) ||
1053
- isSetAccessorDeclaration(node) ||
1054
- isParameterProperty(node)
1055
- ) {
1056
- return classMemberKind(node);
1057
- }
1058
- return undefined;
1059
- }
1060
- if (container.memberKind === "enum-member") {
1061
- return isEnumMember(node) ? "enum-member" : undefined;
1062
- }
1063
- if (isPropertySignatureDeclaration(node) || isMethodSignatureDeclaration(node)) {
1064
- return typeMemberKind(node, container.memberKind === "interface-method");
1065
- }
1066
- if (isGetAccessorDeclaration(node) || isSetAccessorDeclaration(node)) {
1067
- return "type-member";
1068
- }
1069
- return undefined;
618
+ if (
619
+ isPropertySignatureDeclaration(node) ||
620
+ isGetAccessorDeclaration(node) ||
621
+ isSetAccessorDeclaration(node)
622
+ ) {
623
+ return "type-member";
1070
624
  }
625
+ return undefined;
626
+ }
1071
627
 
628
+ /**
629
+ * Each enum member's dependents: the later members of its declaration with no initializer,
630
+ * up to the first one with an initializer.
631
+ */
632
+ function dependentsOf(
633
+ containers: readonly Container[],
634
+ recordAt: (node: Node) => Building | undefined,
635
+ ): ReadonlyMap<string, readonly string[]> {
1072
636
  const dependents = new Map<string, readonly string[]>();
1073
637
  for (const container of containers.filter((one) => one.memberKind === "enum-member")) {
1074
638
  const ids = container.members.map((member) => {
1075
- const record = byNode.get(nodeKey(member.getSourceFile(), member));
639
+ const record = recordAt(member);
1076
640
  return record === undefined ? undefined : positionKey(record.position);
1077
641
  });
1078
642
  container.members.forEach((_member, index) => {
@@ -1093,28 +657,25 @@ export function inventory<Brand>(
1093
657
  }
1094
658
  });
1095
659
  }
1096
-
1097
- const byId = new Map(building.map((record) => [positionKey(record.position), record]));
1098
- const symbols = building.map(render).sort((a, b) => byPosition(a.position, b.position));
1099
-
1100
- return {
1101
- symbols,
1102
- declarations: new Map(
1103
- [...byNode].map(([key, record]) => [key, positionKey(record.position)] as const),
1104
- ),
1105
- cost: { batches: 1, exportTables, memberTables },
1106
- unanswered: symbols
1107
- .filter((symbol) => underUnanswered(byId.get(symbol.id), unansweredOwners))
1108
- .map((symbol) => symbol.id),
1109
- byName: namesOf(building),
1110
- modules: new Map(modules.map(([record, name]) => [positionKey(record.position), name])),
1111
- ambient: new Set(
1112
- building
1113
- .filter((record) => isAmbient(record.file, record.node))
1114
- .map((record) => positionKey(record.position)),
1115
- ),
1116
- dependents,
1117
- };
660
+ return dependents;
661
+ }
662
+
663
+ /** Each declaration but a file to where a directive binds it. */
664
+ function placementOf(records: readonly Building[]): ReadonlyMap<string, Placement> {
665
+ const ascending = (lines: ReadonlySet<number>): readonly number[] =>
666
+ [...lines].sort((a, b) => a - b);
667
+ return new Map(
668
+ records
669
+ .filter((record) => record.kind !== "file")
670
+ .map((record) => [
671
+ positionKey(record.position),
672
+ {
673
+ startLines: ascending(record.startLines),
674
+ holder: positionKey((record.holder ?? record).position),
675
+ partLines: ascending(record.partLines),
676
+ },
677
+ ]),
678
+ );
1118
679
  }
1119
680
 
1120
681
  /** Whether one declaration is, or is declared under, a scope or container left unanswered. */
@@ -1128,9 +689,9 @@ function underUnanswered(record: Building | undefined, owners: ReadonlySet<Build
1128
689
  }
1129
690
 
1130
691
  /** Every declaration but a file, by each name a use may spell it with. */
1131
- function namesOf(building: readonly Building[]): ReadonlyMap<string, readonly string[]> {
692
+ function namesOf(records: readonly Building[]): ReadonlyMap<string, readonly string[]> {
1132
693
  const byName = new Map<string, string[]>();
1133
- for (const record of building) {
694
+ for (const record of records) {
1134
695
  if (record.kind === "file") {
1135
696
  continue;
1136
697
  }
@@ -1161,24 +722,18 @@ function chainOf(record: Building): readonly Component[] {
1161
722
 
1162
723
  /**
1163
724
  * One declaration as the inventory reports it. The display name is the chain in the
1164
- * spelling a reference gives it, except where the declaration carries a name of its
1165
- * own the reference does not: a file, whose reference names its module, and a default
1166
- * export, whose reference carries its export name.
725
+ * spelling a reference gives it, except where the declaration carries a name of its own
726
+ * the reference does not: a file, whose reference names its module, and a default export.
1167
727
  */
1168
728
  function render(record: Building): InventorySymbol {
1169
729
  const chain = chainOf(record);
1170
- const spelled = chain
1171
- .map((component) =>
1172
- component.computed ? computedComponent(component.text) : nameComponent(component.text),
1173
- )
1174
- .join(".");
1175
- const name =
1176
- record.localName ??
1177
- (record.typeParameter === undefined ? spelled : `${spelled}<${record.typeParameter}>`);
730
+ const spelled = chain.map(spell).join(".");
1178
731
  return {
1179
732
  id: positionKey(record.position),
1180
733
  ref: refOf(record, chain),
1181
- name,
734
+ name:
735
+ record.localName ??
736
+ (record.typeParameter === undefined ? spelled : `${spelled}<${record.typeParameter}>`),
1182
737
  kind: record.kind,
1183
738
  position: record.position,
1184
739
  endLine: record.endLine,
@@ -1208,12 +763,3 @@ function refOf(record: Building, chain: readonly Component[]): string {
1208
763
  ...(record.typeParameter === undefined ? {} : { typeParameter: record.typeParameter }),
1209
764
  });
1210
765
  }
1211
-
1212
- /**
1213
- * Every declaration of an inventory other than a file, by each name a use may spell it
1214
- * with. It is how a reference the checker could not resolve is still counted, as a
1215
- * reference to every declaration that bears the name it spells.
1216
- */
1217
- export function declarationsByName(held: Inventory): ReadonlyMap<string, readonly string[]> {
1218
- return held.byName;
1219
- }