@cplieger/deadset-ts 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -5
- package/{bin/deadset-ts.ts → dist/bin/deadset-ts.js} +2 -3
- package/dist/bin/node-host.js +74 -0
- package/dist/src/config.js +63 -0
- package/dist/src/discover.js +171 -0
- package/dist/src/host.js +1 -0
- package/dist/src/index.js +6 -0
- package/dist/src/inventory.js +676 -0
- package/dist/src/kinds.js +12 -0
- package/dist/src/paths.js +86 -0
- package/dist/src/position.js +52 -0
- package/dist/src/print.js +56 -0
- package/dist/src/ref.js +117 -0
- package/dist/src/references.js +420 -0
- package/dist/src/resolve.js +482 -0
- package/dist/src/run.js +249 -0
- package/dist/src/schema.js +335 -0
- package/dist/src/scope.js +116 -0
- package/dist/src/session.js +101 -0
- package/dist/src/version.js +7 -0
- package/jsr.json +1 -1
- package/package.json +10 -4
- package/src/host.ts +10 -2
- package/src/index.ts +39 -1
- package/src/inventory.ts +964 -0
- package/src/paths.ts +20 -0
- package/src/position.ts +75 -0
- package/src/ref.ts +179 -0
- package/src/references.ts +606 -0
- package/src/run.ts +4 -4
- package/src/session.ts +57 -22
- package/src/version.ts +3 -18
- package/bin/node-host.ts +0 -28
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
import { isClassDeclaration, isComputedPropertyName, isEnumDeclaration, isEnumMember, isExportAssignment, isExportDeclaration, isFunctionDeclaration, isGetAccessorDeclaration, isIdentifier, isInterfaceDeclaration, isIntersectionTypeNode, isMethodDeclaration, isMethodSignatureDeclaration, isModuleBlock, isModuleDeclaration, isNamespaceExport, isPrivateIdentifier, isPropertyDeclaration, isPropertySignatureDeclaration, isSetAccessorDeclaration, isTypeAliasDeclaration, isTypeLiteralNode, isUnionTypeNode, isVariableStatement, ModifierFlags, } from "@typescript/native/unstable/ast";
|
|
2
|
+
import { dirnamePath, joinPath, relativePath } from "./paths.js";
|
|
3
|
+
import { byPosition, positionKey, renderPosition } from "./position.js";
|
|
4
|
+
import { computedComponent, nameComponent, renderRef } from "./ref.js";
|
|
5
|
+
const MANIFEST = "package.json";
|
|
6
|
+
/**
|
|
7
|
+
* Resolves each file's package scope: the name of the nearest manifest at or above
|
|
8
|
+
* its directory up to the target root, and the file's path below that manifest.
|
|
9
|
+
* Where no manifest up to the root carries a name, the package is a single dot and
|
|
10
|
+
* the path is read from the root, which cannot collide, because a package name never
|
|
11
|
+
* starts with a dot.
|
|
12
|
+
*
|
|
13
|
+
* One directory is read at most once, because a package holds many files and a
|
|
14
|
+
* manifest read is a filesystem call.
|
|
15
|
+
*/
|
|
16
|
+
function packages(host, root) {
|
|
17
|
+
const named = new Map();
|
|
18
|
+
const manifestName = (dir) => {
|
|
19
|
+
let text;
|
|
20
|
+
try {
|
|
21
|
+
text = host.readFile(joinPath(root, dir, MANIFEST));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
let value;
|
|
27
|
+
try {
|
|
28
|
+
value = JSON.parse(text);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// A manifest this program cannot read names no package, which is the same
|
|
32
|
+
// answer as an absent one: the file is then scoped by the target root.
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
if (typeof value !== "object" || value === null) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const name = value["name"];
|
|
39
|
+
return typeof name === "string" && name !== "" ? name : undefined;
|
|
40
|
+
};
|
|
41
|
+
const nearest = (dir) => {
|
|
42
|
+
if (named.has(dir)) {
|
|
43
|
+
return named.get(dir);
|
|
44
|
+
}
|
|
45
|
+
const name = manifestName(dir);
|
|
46
|
+
const found = name !== undefined ? { name, at: dir } : dir === "" ? undefined : nearest(parentOf(dir));
|
|
47
|
+
named.set(dir, found);
|
|
48
|
+
return found;
|
|
49
|
+
};
|
|
50
|
+
return (path) => {
|
|
51
|
+
const found = nearest(parentOf(path));
|
|
52
|
+
if (found === undefined) {
|
|
53
|
+
return { package: ".", path };
|
|
54
|
+
}
|
|
55
|
+
return { package: found.name, path: found.at === "" ? path : path.slice(found.at.length + 1) };
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** The directory one path below the root sits in, and the root itself as the empty path. */
|
|
59
|
+
function parentOf(path) {
|
|
60
|
+
const dir = dirnamePath(path);
|
|
61
|
+
return dir === "." ? "" : dir;
|
|
62
|
+
}
|
|
63
|
+
/** The modifier flags one node carries, and zero for a node that carries none. */
|
|
64
|
+
function modifiersOf(node) {
|
|
65
|
+
return node.modifierFlags ?? 0;
|
|
66
|
+
}
|
|
67
|
+
/** The type parameters one node declares, and none for a node that declares none. */
|
|
68
|
+
function typeParametersOf(node) {
|
|
69
|
+
if (isFunctionDeclaration(node) ||
|
|
70
|
+
isMethodDeclaration(node) ||
|
|
71
|
+
isMethodSignatureDeclaration(node)) {
|
|
72
|
+
return node.typeParameters ?? [];
|
|
73
|
+
}
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The reference component one declaration name node spells, and undefined for a
|
|
78
|
+
* name the grammar does not represent: a binding pattern, and a computed key whose
|
|
79
|
+
* source text carries a closing bracket.
|
|
80
|
+
*/
|
|
81
|
+
function componentOf(name) {
|
|
82
|
+
if (name === undefined) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
if (isComputedPropertyName(name)) {
|
|
86
|
+
const text = name.expression.getText();
|
|
87
|
+
return text.includes("]") ? undefined : { text, computed: true };
|
|
88
|
+
}
|
|
89
|
+
if (isIdentifier(name) || isPrivateIdentifier(name)) {
|
|
90
|
+
return { text: name.getText(), computed: false };
|
|
91
|
+
}
|
|
92
|
+
if (isStringOrNumericName(name)) {
|
|
93
|
+
// A string or numeric key's own text carries its quotes and its numeric
|
|
94
|
+
// spelling; the reference carries the value, which the component then quotes.
|
|
95
|
+
return { text: literalTextOf(name), computed: false };
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
function isStringOrNumericName(name) {
|
|
100
|
+
const text = name.getText();
|
|
101
|
+
return (text.startsWith('"') || text.startsWith("'") || text.startsWith("`") || /^[0-9]/u.test(text));
|
|
102
|
+
}
|
|
103
|
+
/** One member's identity inside its container: its component, and its side of a class. */
|
|
104
|
+
function memberKey(component, isStatic) {
|
|
105
|
+
return `${component.computed ? "[" : ""}${component.text}${isStatic ? ":static" : ""}`;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The members one type alias declares: the members of the object type it writes, and
|
|
109
|
+
* of every object type that is a direct constituent of a union or an intersection it
|
|
110
|
+
* writes. A constituent naming another type declares nothing here, because that
|
|
111
|
+
* declaration carries its own members.
|
|
112
|
+
*
|
|
113
|
+
* A name two constituents both declare is left out of all of them. The alias stands
|
|
114
|
+
* for such a member, because the two declarations are one member of the type and
|
|
115
|
+
* neither of them is the one a reference to it would name.
|
|
116
|
+
*/
|
|
117
|
+
function aliasMembers(type) {
|
|
118
|
+
const literals = isTypeLiteralNode(type)
|
|
119
|
+
? [type]
|
|
120
|
+
: isUnionTypeNode(type) || isIntersectionTypeNode(type)
|
|
121
|
+
? type.types.filter(isTypeLiteralNode)
|
|
122
|
+
: [];
|
|
123
|
+
if (literals.length < 2) {
|
|
124
|
+
return literals[0]?.members ?? [];
|
|
125
|
+
}
|
|
126
|
+
const declaring = new Map();
|
|
127
|
+
for (const literal of literals) {
|
|
128
|
+
for (const key of new Set(literal.members.map(keyOf))) {
|
|
129
|
+
if (key !== undefined) {
|
|
130
|
+
declaring.set(key, (declaring.get(key) ?? 0) + 1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return literals.flatMap((literal) => literal.members.filter((member) => {
|
|
135
|
+
const key = keyOf(member);
|
|
136
|
+
return key !== undefined && declaring.get(key) === 1;
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
/** One type member's identity, and undefined for a member the grammar cannot name. */
|
|
140
|
+
function keyOf(member) {
|
|
141
|
+
const component = componentOf(nameNodeOf(member));
|
|
142
|
+
return component === undefined ? undefined : memberKey(component, false);
|
|
143
|
+
}
|
|
144
|
+
/** The value one string or numeric key denotes, which is what the reference names. */
|
|
145
|
+
function literalTextOf(name) {
|
|
146
|
+
const text = name.getText();
|
|
147
|
+
if (text.startsWith('"') || text.startsWith("'") || text.startsWith("`")) {
|
|
148
|
+
return text.slice(1, -1);
|
|
149
|
+
}
|
|
150
|
+
return text;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The component one declaration's reference carries, and its declared name where the
|
|
154
|
+
* two differ.
|
|
155
|
+
*
|
|
156
|
+
* A declaration its module exports as the default is named `default`, because that is
|
|
157
|
+
* its export name and a module has at most one, so the spelling is unambiguous and it
|
|
158
|
+
* holds for an anonymous declaration too. Its declared name is a fact about the source
|
|
159
|
+
* rather than about the export, so it travels beside the component as the display name.
|
|
160
|
+
*/
|
|
161
|
+
function exportedAs(node, name) {
|
|
162
|
+
const declared = componentOf(name);
|
|
163
|
+
if ((modifiersOf(node) & ModifierFlags.Default) === 0) {
|
|
164
|
+
return { component: declared, localName: undefined };
|
|
165
|
+
}
|
|
166
|
+
return { component: { text: "default", computed: false }, localName: declared?.text };
|
|
167
|
+
}
|
|
168
|
+
/** The visibility one member declaration's modifiers and name give it. */
|
|
169
|
+
function visibilityOf(node, name) {
|
|
170
|
+
if (name !== undefined && isPrivateIdentifier(name)) {
|
|
171
|
+
return "private-name";
|
|
172
|
+
}
|
|
173
|
+
const flags = modifiersOf(node);
|
|
174
|
+
if ((flags & ModifierFlags.Private) !== 0) {
|
|
175
|
+
return "private";
|
|
176
|
+
}
|
|
177
|
+
if ((flags & ModifierFlags.Protected) !== 0) {
|
|
178
|
+
return "protected";
|
|
179
|
+
}
|
|
180
|
+
return "public";
|
|
181
|
+
}
|
|
182
|
+
/** The name node of one declaration, for the kinds that carry one. */
|
|
183
|
+
function nameNodeOf(node) {
|
|
184
|
+
return node.name;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* One node's identity inside one program, which is how two readings of that program
|
|
188
|
+
* name one declaration.
|
|
189
|
+
*
|
|
190
|
+
* The two mechanisms behind the member set resolve a member separately and meet here;
|
|
191
|
+
* so does the reference pass, which starts from a resolved symbol's declaration and
|
|
192
|
+
* needs the declaration this enumeration made for it.
|
|
193
|
+
*/
|
|
194
|
+
export function nodeKey(file, node) {
|
|
195
|
+
return `${file.fileName}:${String(node.pos)}:${String(node.end)}`;
|
|
196
|
+
}
|
|
197
|
+
/** The kind a class element takes: a method, or a property or accessor. */
|
|
198
|
+
function classMemberKind(node) {
|
|
199
|
+
return isMethodDeclaration(node) ? "method" : "class-member";
|
|
200
|
+
}
|
|
201
|
+
/** The kind a type element takes: a method of an interface, or any other member. */
|
|
202
|
+
function typeMemberKind(node, inInterface) {
|
|
203
|
+
return inInterface && isMethodSignatureDeclaration(node) ? "interface-method" : "type-member";
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Every declaration one project's own files hold.
|
|
207
|
+
*
|
|
208
|
+
* `targetRoot` is the absolute path every position is rendered against; a file the
|
|
209
|
+
* program holds that is not below it ends the enumeration rather than being passed
|
|
210
|
+
* over. `host` is the filesystem the manifests that name each file's package are
|
|
211
|
+
* read through, and nothing else is read from it.
|
|
212
|
+
*
|
|
213
|
+
* The order is by rendered position, so two enumerations of one project return the
|
|
214
|
+
* same list whatever order the program reported its files in.
|
|
215
|
+
*/
|
|
216
|
+
export function inventory(project, host, targetRoot) {
|
|
217
|
+
const files = project.ownSourceFiles();
|
|
218
|
+
const ownFiles = new Set(files.map((file) => file.fileName));
|
|
219
|
+
const moduleOf = packages(host, targetRoot);
|
|
220
|
+
const building = [];
|
|
221
|
+
const byNode = new Map();
|
|
222
|
+
const exportScopes = [];
|
|
223
|
+
const containers = [];
|
|
224
|
+
const starReExports = [];
|
|
225
|
+
let outsideOwnFiles = 0;
|
|
226
|
+
const keep = (record) => {
|
|
227
|
+
building.push(record);
|
|
228
|
+
byNode.set(nodeKey(record.file, record.node), record);
|
|
229
|
+
return record;
|
|
230
|
+
};
|
|
231
|
+
for (const file of files) {
|
|
232
|
+
const path = relativePath(targetRoot, file.fileName);
|
|
233
|
+
if (path === undefined) {
|
|
234
|
+
// renderPosition states the refusal, with the root it was rendered against.
|
|
235
|
+
renderPosition(file, targetRoot, 0);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const module = moduleOf(path);
|
|
239
|
+
const fileRecord = keep({
|
|
240
|
+
file,
|
|
241
|
+
node: file,
|
|
242
|
+
module,
|
|
243
|
+
position: renderPosition(file, targetRoot, 0),
|
|
244
|
+
endLine: renderPosition(file, targetRoot, Math.max(file.end - 1, 0)).line,
|
|
245
|
+
kind: "file",
|
|
246
|
+
parentChain: [],
|
|
247
|
+
// A file's reference names its module, so no component of it is ever spelled;
|
|
248
|
+
// the path is what a text line renders for it.
|
|
249
|
+
component: { text: path, computed: false },
|
|
250
|
+
localName: path,
|
|
251
|
+
parent: undefined,
|
|
252
|
+
visibility: "public",
|
|
253
|
+
static: false,
|
|
254
|
+
exported: false,
|
|
255
|
+
typeParameter: undefined,
|
|
256
|
+
});
|
|
257
|
+
exportScopes.push({ owner: fileRecord, nameNode: file });
|
|
258
|
+
walkStatements(file.statements, fileRecord);
|
|
259
|
+
}
|
|
260
|
+
/** One declaration of a module, a namespace or a container body. */
|
|
261
|
+
function walkStatements(statements, owner) {
|
|
262
|
+
for (const statement of statements) {
|
|
263
|
+
walkStatement(statement, owner);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function declare(node, owner, kind, name, named) {
|
|
267
|
+
const at = name ?? node;
|
|
268
|
+
const record = keep({
|
|
269
|
+
file: owner.file,
|
|
270
|
+
node,
|
|
271
|
+
module: owner.module,
|
|
272
|
+
position: renderPosition(owner.file, targetRoot, at.getStart()),
|
|
273
|
+
endLine: renderPosition(owner.file, targetRoot, Math.max(node.end - 1, 0)).line,
|
|
274
|
+
kind,
|
|
275
|
+
parentChain: owner.kind === "file" ? [] : chainOf(owner),
|
|
276
|
+
component: named.component,
|
|
277
|
+
localName: named.localName,
|
|
278
|
+
parent: owner,
|
|
279
|
+
visibility: visibilityOf(node, name),
|
|
280
|
+
static: (modifiersOf(node) & ModifierFlags.Static) !== 0,
|
|
281
|
+
exported: false,
|
|
282
|
+
typeParameter: undefined,
|
|
283
|
+
});
|
|
284
|
+
declareTypeParameters(node, record);
|
|
285
|
+
return record;
|
|
286
|
+
}
|
|
287
|
+
function declareTypeParameters(node, owner) {
|
|
288
|
+
for (const parameter of typeParametersOf(node)) {
|
|
289
|
+
const name = nameNodeOf(parameter);
|
|
290
|
+
if (name === undefined) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
keep({
|
|
294
|
+
file: owner.file,
|
|
295
|
+
node: parameter,
|
|
296
|
+
module: owner.module,
|
|
297
|
+
position: renderPosition(owner.file, targetRoot, name.getStart()),
|
|
298
|
+
endLine: renderPosition(owner.file, targetRoot, Math.max(parameter.end - 1, 0)).line,
|
|
299
|
+
kind: "type-parameter",
|
|
300
|
+
parentChain: owner.parentChain,
|
|
301
|
+
component: owner.component,
|
|
302
|
+
localName: undefined,
|
|
303
|
+
parent: owner,
|
|
304
|
+
visibility: "public",
|
|
305
|
+
static: owner.static,
|
|
306
|
+
exported: false,
|
|
307
|
+
typeParameter: name.getText(),
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function walkStatement(node, owner) {
|
|
312
|
+
if (isFunctionDeclaration(node)) {
|
|
313
|
+
const named = exportedAs(node, node.name);
|
|
314
|
+
if (named.component !== undefined) {
|
|
315
|
+
declare(node, owner, "function", node.name, { ...named, component: named.component });
|
|
316
|
+
}
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (isClassDeclaration(node)) {
|
|
320
|
+
const named = exportedAs(node, node.name);
|
|
321
|
+
if (named.component === undefined) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const record = declare(node, owner, "class", node.name, {
|
|
325
|
+
...named,
|
|
326
|
+
component: named.component,
|
|
327
|
+
});
|
|
328
|
+
containers.push({
|
|
329
|
+
owner: record,
|
|
330
|
+
nameNode: node.name,
|
|
331
|
+
members: node.members,
|
|
332
|
+
memberKind: "class-member",
|
|
333
|
+
isClass: true,
|
|
334
|
+
membersAreExports: false,
|
|
335
|
+
});
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (isInterfaceDeclaration(node)) {
|
|
339
|
+
const named = exportedAs(node, node.name);
|
|
340
|
+
if (named.component === undefined) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const record = declare(node, owner, "interface", node.name, {
|
|
344
|
+
...named,
|
|
345
|
+
component: named.component,
|
|
346
|
+
});
|
|
347
|
+
containers.push({
|
|
348
|
+
owner: record,
|
|
349
|
+
nameNode: node.name,
|
|
350
|
+
members: node.members,
|
|
351
|
+
memberKind: "interface-method",
|
|
352
|
+
isClass: false,
|
|
353
|
+
membersAreExports: false,
|
|
354
|
+
});
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (isTypeAliasDeclaration(node)) {
|
|
358
|
+
const named = exportedAs(node, node.name);
|
|
359
|
+
if (named.component === undefined) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const record = declare(node, owner, "type", node.name, {
|
|
363
|
+
...named,
|
|
364
|
+
component: named.component,
|
|
365
|
+
});
|
|
366
|
+
// A type alias keeps no member table of its own: the members belong to the
|
|
367
|
+
// object types its declaration writes, so the declaration is what carries them.
|
|
368
|
+
const members = aliasMembers(node.type);
|
|
369
|
+
if (members.length > 0) {
|
|
370
|
+
containers.push({
|
|
371
|
+
owner: record,
|
|
372
|
+
nameNode: undefined,
|
|
373
|
+
members,
|
|
374
|
+
memberKind: "type-member",
|
|
375
|
+
isClass: false,
|
|
376
|
+
membersAreExports: false,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (isEnumDeclaration(node)) {
|
|
382
|
+
const named = exportedAs(node, node.name);
|
|
383
|
+
if (named.component === undefined) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const record = declare(node, owner, "enum", node.name, {
|
|
387
|
+
...named,
|
|
388
|
+
component: named.component,
|
|
389
|
+
});
|
|
390
|
+
containers.push({
|
|
391
|
+
owner: record,
|
|
392
|
+
nameNode: node.name,
|
|
393
|
+
members: node.members,
|
|
394
|
+
memberKind: "enum-member",
|
|
395
|
+
isClass: false,
|
|
396
|
+
membersAreExports: true,
|
|
397
|
+
});
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
if (isModuleDeclaration(node)) {
|
|
401
|
+
const component = componentOf(node.name);
|
|
402
|
+
if (component === undefined) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const record = declare(node, owner, "namespace", node.name, {
|
|
406
|
+
component,
|
|
407
|
+
localName: undefined,
|
|
408
|
+
});
|
|
409
|
+
exportScopes.push({ owner: record, nameNode: node.name });
|
|
410
|
+
const body = node.body;
|
|
411
|
+
if (body === undefined) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (isModuleBlock(body)) {
|
|
415
|
+
walkStatements(body.statements, record);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
walkStatement(body, record);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (isVariableStatement(node)) {
|
|
422
|
+
for (const declaration of node.declarationList.declarations) {
|
|
423
|
+
const component = componentOf(declaration.name);
|
|
424
|
+
if (component === undefined) {
|
|
425
|
+
// A destructuring pattern declares binding elements, which the reference
|
|
426
|
+
// grammar does not represent; the statement stands for them.
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
declare(declaration, owner, "variable", declaration.name, {
|
|
430
|
+
component,
|
|
431
|
+
localName: undefined,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (isExportDeclaration(node)) {
|
|
437
|
+
walkExportDeclaration(node, owner);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (isExportAssignment(node) && !node.isExportEquals) {
|
|
441
|
+
declare(node, owner, "export-alias", undefined, {
|
|
442
|
+
component: { text: "default", computed: false },
|
|
443
|
+
localName: undefined,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function walkExportDeclaration(node, owner) {
|
|
448
|
+
const clause = node.exportClause;
|
|
449
|
+
if (clause === undefined) {
|
|
450
|
+
const specifier = node.moduleSpecifier;
|
|
451
|
+
starReExports.push(specifier === undefined ? "" : literalTextOf(specifier));
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (isNamespaceExport(clause)) {
|
|
455
|
+
const component = componentOf(clause.name);
|
|
456
|
+
if (component !== undefined) {
|
|
457
|
+
declare(clause, owner, "export-alias", clause.name, { component, localName: undefined });
|
|
458
|
+
}
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
for (const element of clause.elements ?? []) {
|
|
462
|
+
const name = nameNodeOf(element);
|
|
463
|
+
const component = componentOf(name);
|
|
464
|
+
if (component !== undefined) {
|
|
465
|
+
declare(element, owner, "export-alias", name, { component, localName: undefined });
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// One batch for every module and every container the walk found, because a
|
|
470
|
+
// checker accessor with an array overload costs one round trip for the batch and
|
|
471
|
+
// one per node without it.
|
|
472
|
+
const batched = [
|
|
473
|
+
...exportScopes.map((scope) => scope.nameNode),
|
|
474
|
+
...containers.map((container) => container.nameNode).filter((node) => node !== undefined),
|
|
475
|
+
];
|
|
476
|
+
const handles = batched.map((node) => project.handle(node));
|
|
477
|
+
const resolved = project.symbolsAt(handles);
|
|
478
|
+
const symbolOf = new Map();
|
|
479
|
+
batched.forEach((node, index) => {
|
|
480
|
+
const symbol = resolved[index];
|
|
481
|
+
if (symbol !== undefined) {
|
|
482
|
+
symbolOf.set(node, symbol);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
let exportTables = 0;
|
|
486
|
+
for (const scope of exportScopes) {
|
|
487
|
+
const symbol = symbolOf.get(scope.nameNode);
|
|
488
|
+
if (symbol === undefined) {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
exportTables += 1;
|
|
492
|
+
for (const [, exported] of symbol.getExports()) {
|
|
493
|
+
for (const declaration of exported.declarations) {
|
|
494
|
+
const held = project.declarationAt(declaration);
|
|
495
|
+
if (held === undefined) {
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const record = byNode.get(nodeKey(held.node.getSourceFile(), held.node));
|
|
499
|
+
if (record === undefined) {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
record.exported = true;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
let memberTables = 0;
|
|
507
|
+
for (const container of containers) {
|
|
508
|
+
const symbol = container.nameNode === undefined ? undefined : symbolOf.get(container.nameNode);
|
|
509
|
+
const seen = new Map();
|
|
510
|
+
if (symbol !== undefined) {
|
|
511
|
+
if (!container.membersAreExports) {
|
|
512
|
+
memberTables += 1;
|
|
513
|
+
outsideOwnFiles += readTable(container, symbol.getMembers(), seen, false);
|
|
514
|
+
}
|
|
515
|
+
if (container.membersAreExports || container.isClass) {
|
|
516
|
+
memberTables += 1;
|
|
517
|
+
outsideOwnFiles += readTable(container, symbol.getExports(), seen, container.isClass);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
completeFromTree(container, seen);
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* One container's members as its symbol table holds them, each positioned at the
|
|
524
|
+
* declaration the table names. A member with no declaration is the language's own
|
|
525
|
+
* (a class's `prototype`), and a member declared outside the target's own files
|
|
526
|
+
* belongs to another program; neither is a declaration of the target.
|
|
527
|
+
*/
|
|
528
|
+
function readTable(container, table, seen, areStatic) {
|
|
529
|
+
let outside = 0;
|
|
530
|
+
for (const [, member] of table) {
|
|
531
|
+
const declarations = member.declarations
|
|
532
|
+
.map((handle) => project.declarationAt(handle))
|
|
533
|
+
.filter((held) => held !== undefined);
|
|
534
|
+
if (declarations.length === 0) {
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
const own = declarations.filter((held) => ownFiles.has(held.node.getSourceFile().fileName));
|
|
538
|
+
if (own.length === 0) {
|
|
539
|
+
outside += declarations.length;
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
for (const held of own) {
|
|
543
|
+
keepMember(container, held.node, seen, areStatic);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return outside;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* One container's members as its own declaration writes them, for the members its
|
|
550
|
+
* symbol table does not hold: a member whose key is computed is bound to the
|
|
551
|
+
* container's type later than the table is built.
|
|
552
|
+
*/
|
|
553
|
+
function completeFromTree(container, seen) {
|
|
554
|
+
for (const member of container.members) {
|
|
555
|
+
keepMember(container, member, seen, (modifiersOf(member) & ModifierFlags.Static) !== 0);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
function keepMember(container, node, seen, areStatic) {
|
|
559
|
+
const kind = memberKindOf(container, node);
|
|
560
|
+
if (kind === undefined) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const name = nameNodeOf(node);
|
|
564
|
+
const component = componentOf(name);
|
|
565
|
+
if (component === undefined) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
const file = node.getSourceFile();
|
|
569
|
+
const key = memberKey(component, areStatic);
|
|
570
|
+
const held = seen.get(key);
|
|
571
|
+
const endLine = renderPosition(file, targetRoot, Math.max(node.end - 1, 0)).line;
|
|
572
|
+
if (held !== undefined) {
|
|
573
|
+
// A getter and a setter of one name are one symbol and one reference, so the
|
|
574
|
+
// second declaration widens the first record rather than making a second. Both
|
|
575
|
+
// nodes name that record, so a reading that starts from either one finds it.
|
|
576
|
+
held.endLine = Math.max(held.endLine, endLine);
|
|
577
|
+
byNode.set(nodeKey(file, node), held);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
const at = name ?? node;
|
|
581
|
+
const record = keep({
|
|
582
|
+
file,
|
|
583
|
+
node,
|
|
584
|
+
module: container.owner.module,
|
|
585
|
+
position: renderPosition(file, targetRoot, at.getStart()),
|
|
586
|
+
endLine,
|
|
587
|
+
kind,
|
|
588
|
+
parentChain: chainOf(container.owner),
|
|
589
|
+
component,
|
|
590
|
+
localName: undefined,
|
|
591
|
+
parent: container.owner,
|
|
592
|
+
visibility: visibilityOf(node, name),
|
|
593
|
+
static: areStatic,
|
|
594
|
+
exported: false,
|
|
595
|
+
typeParameter: undefined,
|
|
596
|
+
});
|
|
597
|
+
seen.set(key, record);
|
|
598
|
+
declareTypeParameters(node, record);
|
|
599
|
+
}
|
|
600
|
+
function memberKindOf(container, node) {
|
|
601
|
+
if (container.isClass) {
|
|
602
|
+
if (isPropertyDeclaration(node) ||
|
|
603
|
+
isMethodDeclaration(node) ||
|
|
604
|
+
isGetAccessorDeclaration(node) ||
|
|
605
|
+
isSetAccessorDeclaration(node)) {
|
|
606
|
+
return classMemberKind(node);
|
|
607
|
+
}
|
|
608
|
+
return undefined;
|
|
609
|
+
}
|
|
610
|
+
if (container.memberKind === "enum-member") {
|
|
611
|
+
return isEnumMember(node) ? "enum-member" : undefined;
|
|
612
|
+
}
|
|
613
|
+
if (isPropertySignatureDeclaration(node) || isMethodSignatureDeclaration(node)) {
|
|
614
|
+
return typeMemberKind(node, container.memberKind === "interface-method");
|
|
615
|
+
}
|
|
616
|
+
if (isGetAccessorDeclaration(node) || isSetAccessorDeclaration(node)) {
|
|
617
|
+
return "type-member";
|
|
618
|
+
}
|
|
619
|
+
return undefined;
|
|
620
|
+
}
|
|
621
|
+
const symbols = building
|
|
622
|
+
.map(render)
|
|
623
|
+
.sort((a, b) => byPosition(a.position, b.position) || (a.ref < b.ref ? -1 : 1));
|
|
624
|
+
return {
|
|
625
|
+
configFile: project.configFile,
|
|
626
|
+
symbols,
|
|
627
|
+
declarations: new Map([...byNode].map(([key, record]) => [key, positionKey(record.position)])),
|
|
628
|
+
cost: { batches: 1, exportTables, memberTables },
|
|
629
|
+
starReExports,
|
|
630
|
+
outsideOwnFiles,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
/** The chain of components that names one declaration, read from the module inward. */
|
|
634
|
+
function chainOf(record) {
|
|
635
|
+
return [...record.parentChain, record.component];
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* One declaration as the inventory reports it. The display name is the chain in the
|
|
639
|
+
* spelling a reference gives it, except where the declaration carries a name of its
|
|
640
|
+
* own the reference does not: a file, whose reference names its module, and a default
|
|
641
|
+
* export, whose reference carries its export name.
|
|
642
|
+
*/
|
|
643
|
+
function render(record) {
|
|
644
|
+
const chain = chainOf(record);
|
|
645
|
+
const spelled = chain
|
|
646
|
+
.map((component) => component.computed ? computedComponent(component.text) : nameComponent(component.text))
|
|
647
|
+
.join(".");
|
|
648
|
+
const name = record.localName ??
|
|
649
|
+
(record.typeParameter === undefined ? spelled : `${spelled}<${record.typeParameter}>`);
|
|
650
|
+
return {
|
|
651
|
+
id: positionKey(record.position),
|
|
652
|
+
ref: refOf(record, chain),
|
|
653
|
+
name,
|
|
654
|
+
kind: record.kind,
|
|
655
|
+
position: record.position,
|
|
656
|
+
endLine: record.endLine,
|
|
657
|
+
parent: record.parent === undefined ? "" : positionKey(record.parent.position),
|
|
658
|
+
exported: record.exported,
|
|
659
|
+
visibility: record.visibility,
|
|
660
|
+
static: record.static,
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
function refOf(record, chain) {
|
|
664
|
+
if (record.kind === "file") {
|
|
665
|
+
return renderRef(record.module, { of: "module" });
|
|
666
|
+
}
|
|
667
|
+
if (record.kind === "export-alias") {
|
|
668
|
+
return renderRef(record.module, { of: "alias", name: record.component.text });
|
|
669
|
+
}
|
|
670
|
+
return renderRef(record.module, {
|
|
671
|
+
of: "declaration",
|
|
672
|
+
chain,
|
|
673
|
+
static: record.static,
|
|
674
|
+
...(record.typeParameter === undefined ? {} : { typeParameter: record.typeParameter }),
|
|
675
|
+
});
|
|
676
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The issue-kind codes whose severity the Contract fixes. A severity key naming
|
|
3
|
+
* one, or a family prefix whose range holds one, is an unimplemented key rather
|
|
4
|
+
* than a setting.
|
|
5
|
+
*
|
|
6
|
+
* The codes are written out rather than read from the Contract at run time: the
|
|
7
|
+
* Contract is data a reader outside this program consults, and a product that
|
|
8
|
+
* read it while running would need it installed beside itself. A test compares
|
|
9
|
+
* this list against the Contract release the analyzer is written against, so a
|
|
10
|
+
* code the Contract fixes arrives here as a failing test.
|
|
11
|
+
*/
|
|
12
|
+
export const FIXED_SEVERITY_CODES = ["DS1703", "DS1704"];
|