@cplieger/deadset-ts 8.0.0-dev.13 → 8.0.0-dev.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/alias-chain.js +2 -2
- package/dist/src/analysis.js +2 -1
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +1 -1
- package/dist/src/contract/contract.generated.js +1 -0
- package/dist/src/dependencies.js +2 -1
- package/dist/src/entry-points.js +26 -95
- package/dist/src/file-facts.js +1 -1
- package/dist/src/graph.js +0 -1
- package/dist/src/inventory/commonjs.js +462 -0
- package/dist/src/inventory/enumerate.js +166 -0
- package/dist/src/inventory/syntax.js +236 -0
- package/dist/src/inventory.js +305 -705
- package/dist/src/load/discover.js +1 -1
- package/dist/src/load/package-scope.js +56 -0
- package/dist/src/own-body.js +28 -0
- package/dist/src/position.js +41 -0
- package/dist/src/ref.js +11 -20
- package/dist/src/references.js +91 -30
- package/dist/src/session.js +3 -0
- package/dist/src/suppress-inline.js +33 -81
- package/dist/src/unseen-loads.js +106 -17
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/alias-chain.ts +2 -2
- package/src/analysis.ts +2 -1
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +1 -1
- package/src/contract/contract.generated.ts +1 -0
- package/src/dependencies.ts +2 -1
- package/src/entry-points.ts +30 -107
- package/src/file-facts.ts +1 -1
- package/src/graph.ts +0 -1
- package/src/inventory/commonjs.ts +611 -0
- package/src/inventory/enumerate.ts +202 -0
- package/src/inventory/syntax.ts +311 -0
- package/src/inventory.ts +424 -878
- package/src/load/discover.ts +1 -1
- package/src/load/package-scope.ts +74 -0
- package/src/own-body.ts +42 -0
- package/src/position.ts +42 -0
- package/src/ref.ts +11 -20
- package/src/references.ts +116 -53
- package/src/session.ts +17 -1
- package/src/suppress-inline.ts +53 -89
- package/src/unseen-loads.ts +166 -18
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
|
-
|
|
35
|
-
type ClassDeclaration,
|
|
36
|
-
type Identifier,
|
|
19
|
+
SyntaxKind,
|
|
37
20
|
type Node,
|
|
38
21
|
type SourceFile,
|
|
39
22
|
} from "@typescript/native/ast";
|
|
40
|
-
import
|
|
41
|
-
import type
|
|
42
|
-
import {
|
|
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
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
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
|
|
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
|
-
*
|
|
125
|
-
*
|
|
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
|
-
*
|
|
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
|
|
155
|
-
*
|
|
156
|
-
*
|
|
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
|
|
166
|
-
* answer
|
|
167
|
-
*
|
|
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.
|
|
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
|
|
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
|
-
/**
|
|
240
|
+
/** The name a declaration is declared under: its component, and its declared name beside it. */
|
|
238
241
|
interface Named {
|
|
239
|
-
readonly
|
|
240
|
-
readonly
|
|
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
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
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
|
|
254
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
};
|
|
266
|
+
constructor(targetRoot: string) {
|
|
267
|
+
this.#targetRoot = targetRoot;
|
|
268
|
+
}
|
|
260
269
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
/**
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
/**
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
|
306
|
-
function
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
484
|
+
record.startLines.add(startLine(file, list.parent.pos));
|
|
316
485
|
}
|
|
317
|
-
|
|
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
|
-
*
|
|
323
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
|
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
|
-
/**
|
|
524
|
-
function
|
|
525
|
-
|
|
526
|
-
node
|
|
527
|
-
|
|
528
|
-
node
|
|
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
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
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 =
|
|
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
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
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(
|
|
692
|
+
function namesOf(records: readonly Building[]): ReadonlyMap<string, readonly string[]> {
|
|
1132
693
|
const byName = new Map<string, string[]>();
|
|
1133
|
-
for (const record of
|
|
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
|
-
*
|
|
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
|
-
}
|