@cplieger/deadset-ts 3.13.0 → 4.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 +3 -3
- package/dist/bin/node-host.js +10 -7
- package/dist/src/alias-chain.js +59 -7
- package/dist/src/analysis.js +119 -23
- package/dist/src/calls.js +23 -22
- package/dist/src/class-members.js +10 -11
- package/dist/src/configured-declarations.js +22 -9
- package/dist/src/dependencies.js +34 -17
- package/dist/src/deprecation.js +35 -24
- package/dist/src/destructuring.js +37 -29
- package/dist/src/discover.js +105 -28
- package/dist/src/entry-points.js +7 -0
- package/dist/src/enum-group.js +46 -40
- package/dist/src/exempt.js +42 -1
- package/dist/src/findings/emitters.js +15 -1
- package/dist/src/findings/interfaces.js +7 -1
- package/dist/src/framework-lifecycle.js +8 -6
- package/dist/src/implementations.js +45 -23
- package/dist/src/interface-satisfaction.js +30 -27
- package/dist/src/intra-function-parts.js +37 -20
- package/dist/src/inventory.js +85 -4
- package/dist/src/json-chunks.js +76 -0
- package/dist/src/partial.js +23 -0
- package/dist/src/query.js +207 -0
- package/dist/src/references.js +92 -20
- package/dist/src/reflective-lookup.js +18 -9
- package/dist/src/report.js +3 -1
- package/dist/src/reporters/annotations.js +9 -5
- package/dist/src/reporters/json.js +3 -2
- package/dist/src/reporters/sarif.js +2 -1
- package/dist/src/reporters/template.js +48 -7
- package/dist/src/reporters/text.js +8 -6
- package/dist/src/roots.js +27 -11
- package/dist/src/serialization-contract.js +12 -10
- package/dist/src/session.js +79 -39
- package/dist/src/suppress-file.js +2 -1
- package/dist/src/sweep.js +4 -1
- package/dist/src/value-reach.js +14 -19
- package/dist/src/verbs/analyze.js +20 -3
- package/dist/src/verbs/explain.js +22 -9
- package/dist/src/verbs/print-projects.js +40 -7
- package/dist/src/verbs/print-retained.js +6 -2
- package/dist/src/verbs/print-roots.js +5 -1
- package/jsr.json +1 -1
- package/package.json +3 -3
- package/src/alias-chain.ts +75 -11
- package/src/analysis.ts +223 -61
- package/src/calls.ts +10 -9
- package/src/class-members.ts +10 -11
- package/src/configured-declarations.ts +29 -13
- package/src/dependencies.ts +42 -20
- package/src/deprecation.ts +53 -27
- package/src/destructuring.ts +50 -39
- package/src/discover.ts +177 -33
- package/src/entry-points.ts +7 -0
- package/src/enum-group.ts +56 -42
- package/src/exempt.ts +50 -2
- package/src/findings/emitters.ts +18 -1
- package/src/findings/interfaces.ts +7 -1
- package/src/framework-lifecycle.ts +10 -6
- package/src/host.ts +5 -3
- package/src/implementations.ts +59 -23
- package/src/interface-satisfaction.ts +30 -27
- package/src/intra-function-parts.ts +43 -25
- package/src/inventory.ts +97 -4
- package/src/json-chunks.ts +87 -0
- package/src/partial.ts +40 -0
- package/src/query.ts +522 -0
- package/src/references.ts +119 -38
- package/src/reflective-lookup.ts +21 -11
- package/src/report.ts +14 -2
- package/src/reporters/annotations.ts +23 -21
- package/src/reporters/json.ts +3 -2
- package/src/reporters/reporter.ts +2 -2
- package/src/reporters/sarif.ts +3 -2
- package/src/reporters/template.ts +50 -8
- package/src/reporters/text.ts +12 -12
- package/src/roots.ts +34 -12
- package/src/serialization-contract.ts +12 -10
- package/src/session.ts +146 -58
- package/src/suppress-file.ts +6 -2
- package/src/sweep.ts +9 -4
- package/src/value-reach.ts +20 -23
- package/src/verbs/analyze.ts +25 -3
- package/src/verbs/explain.ts +20 -9
- package/src/verbs/print-projects.ts +53 -7
- package/src/verbs/print-retained.ts +6 -2
- package/src/verbs/print-roots.ts +5 -1
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { isCallExpression, isExportDeclaration, isExternalModuleReference, isImportDeclaration, isImportEqualsDeclaration, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
8
8
|
import { SymbolFlags, } from "@typescript/native/unstable/sync";
|
|
9
9
|
import { resolvedTargets } from "./calls.js";
|
|
10
|
+
import { must, Unanswerable } from "./query.js";
|
|
10
11
|
import { nameComponent } from "./ref.js";
|
|
11
12
|
/** The meanings a name of the global scope is looked up under: any of them. */
|
|
12
13
|
const ANY_MEANING = SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace;
|
|
@@ -55,7 +56,7 @@ function unaliased(project, symbol) {
|
|
|
55
56
|
if ((symbol.flags & SymbolFlags.Alias) === 0) {
|
|
56
57
|
return symbol;
|
|
57
58
|
}
|
|
58
|
-
const target = project.
|
|
59
|
+
const target = must(project.queries.aliased(symbol));
|
|
59
60
|
return target.declarations.length === 0 ? undefined : target;
|
|
60
61
|
}
|
|
61
62
|
/** Whether one symbol is a member a class declares: a property, a method or an accessor. */
|
|
@@ -68,20 +69,20 @@ function isClassMember(symbol) {
|
|
|
68
69
|
* with the static suffix names one of the first and a component without it names an
|
|
69
70
|
* instance member or one of the second.
|
|
70
71
|
*/
|
|
71
|
-
function candidatesOf(container, isStatic) {
|
|
72
|
-
const exported = [...
|
|
72
|
+
function candidatesOf(project, container, isStatic) {
|
|
73
|
+
const exported = [...must(project.queries.exportsOf(container)).values()];
|
|
73
74
|
if (isStatic) {
|
|
74
75
|
return exported.filter(isClassMember);
|
|
75
76
|
}
|
|
76
77
|
const isClass = (container.flags & SymbolFlags.Class) !== 0;
|
|
77
78
|
return [
|
|
78
|
-
...
|
|
79
|
+
...must(project.queries.membersOf(container)).values(),
|
|
79
80
|
...exported.filter((member) => !isClass || !isClassMember(member)),
|
|
80
81
|
];
|
|
81
82
|
}
|
|
82
83
|
/** The member of one container a path component spells, followed through any alias. */
|
|
83
84
|
function memberOf(project, container, component, isStatic) {
|
|
84
|
-
const member = candidatesOf(container, isStatic).find((candidate) => nameComponent(candidate.name) === component);
|
|
85
|
+
const member = candidatesOf(project, container, isStatic).find((candidate) => nameComponent(candidate.name) === component);
|
|
85
86
|
return member === undefined ? undefined : unaliased(project, member);
|
|
86
87
|
}
|
|
87
88
|
/** The declaration a path names from one starting symbol, component by component. */
|
|
@@ -131,7 +132,7 @@ function modulesNamed(project, specifiers) {
|
|
|
131
132
|
file.forEachChild(visit);
|
|
132
133
|
}
|
|
133
134
|
const specified = [...written];
|
|
134
|
-
const symbols = project.symbolsAt(specified.map(([, node]) => project.handle(node)));
|
|
135
|
+
const symbols = project.symbolsAt(specified.map(([, node]) => project.handle(node))).map(must);
|
|
135
136
|
const modules = new Map();
|
|
136
137
|
specified.forEach(([name], index) => {
|
|
137
138
|
const symbol = symbols[index];
|
|
@@ -171,7 +172,7 @@ export function resolveEntries(project, held, entries) {
|
|
|
171
172
|
start = module === undefined ? undefined : memberOf(project, module, first, false);
|
|
172
173
|
}
|
|
173
174
|
else if (nameComponent(first) === first) {
|
|
174
|
-
const global = project.
|
|
175
|
+
const global = must(project.queries.resolveName(first, ANY_MEANING, undefined, false));
|
|
175
176
|
start = global === undefined ? undefined : unaliased(project, global);
|
|
176
177
|
}
|
|
177
178
|
const declared = walkPath(project, start, rest, isStatic);
|
|
@@ -247,9 +248,21 @@ export function configuredDeclarations(config) {
|
|
|
247
248
|
...config.ts.serializers.map((entry) => ({ key: "ts.serializers", entry })),
|
|
248
249
|
];
|
|
249
250
|
}
|
|
250
|
-
/**
|
|
251
|
+
/**
|
|
252
|
+
* Whether each configured entry names a declaration in one project, in the entries'
|
|
253
|
+
* order. Where the checker leaves a question of the resolution unanswered, every entry
|
|
254
|
+
* counts as naming one, so no entry is reported as naming nothing on a gap.
|
|
255
|
+
*/
|
|
251
256
|
export function declarationsNamed(project, held, configured) {
|
|
252
|
-
|
|
257
|
+
try {
|
|
258
|
+
return resolveEntries(project, held, configured.map((one) => one.entry)).map(namesADeclaration);
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
if (!(error instanceof Unanswerable)) {
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
return configured.map(() => true);
|
|
265
|
+
}
|
|
253
266
|
}
|
|
254
267
|
/**
|
|
255
268
|
* The reference a finding about a configured entry names it by, the entry as the
|
package/dist/src/dependencies.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { isExportDeclaration, isExternalModuleReference, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isNamespaceExport, isShorthandPropertyAssignment, isStringLiteral, isNoSubstitutionTemplateLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
9
9
|
import { nodeKey, packageScope } from "./inventory.js";
|
|
10
10
|
import { dirnamePath, isAbsolutePath, joinPath, resolvePath } from "./paths.js";
|
|
11
|
-
import {
|
|
11
|
+
import { UNANSWERED } from "./query.js";
|
|
12
12
|
const MANIFEST = "package.json";
|
|
13
13
|
const MODULES_DIR = "node_modules";
|
|
14
14
|
const MODULES_SEGMENT = `/${MODULES_DIR}/`;
|
|
@@ -207,10 +207,11 @@ export function projectNeeds(project, held, host) {
|
|
|
207
207
|
}
|
|
208
208
|
};
|
|
209
209
|
use([PROJECT_OWNER], packages);
|
|
210
|
+
let unanswered = false;
|
|
210
211
|
for (const file of project.ownSourceFiles()) {
|
|
211
|
-
fileNeeds(project, held, file, programPackages, use);
|
|
212
|
+
unanswered = fileNeeds(project, held, file, programPackages, use) || unanswered;
|
|
212
213
|
}
|
|
213
|
-
return { packages, uses };
|
|
214
|
+
return { packages, uses, unanswered };
|
|
214
215
|
}
|
|
215
216
|
/** The declarations of the inventory a use written at one node belongs to. */
|
|
216
217
|
function ownersOf(file, held, node) {
|
|
@@ -235,6 +236,7 @@ function ownersOf(file, held, node) {
|
|
|
235
236
|
}
|
|
236
237
|
return [PROJECT_OWNER];
|
|
237
238
|
}
|
|
239
|
+
/** Records the needs of one file, and answers whether a specifier of it went unanswered. */
|
|
238
240
|
function fileNeeds(project, held, file, programPackages, use) {
|
|
239
241
|
const specifiers = [...file.imports, ...file.moduleAugmentations].filter(isLiteralText);
|
|
240
242
|
const bindings = [];
|
|
@@ -262,13 +264,8 @@ function fileNeeds(project, held, file, programPackages, use) {
|
|
|
262
264
|
visit(file);
|
|
263
265
|
}
|
|
264
266
|
const asked = [...specifiers, ...bindings.map(({ node }) => node), ...candidates];
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
const handles = asked
|
|
268
|
-
.slice(at, at + DEFAULT_BATCH_CAP)
|
|
269
|
-
.map((node) => project.handle(node));
|
|
270
|
-
resolved.push(...project.symbolsAt(handles));
|
|
271
|
-
}
|
|
267
|
+
const handles = asked.map((node) => project.handle(node));
|
|
268
|
+
const resolved = project.symbolsAt(handles);
|
|
272
269
|
const packagesOf = new Map();
|
|
273
270
|
specifiers.forEach((specifier, index) => {
|
|
274
271
|
const named = new Set();
|
|
@@ -276,7 +273,8 @@ function fileNeeds(project, held, file, programPackages, use) {
|
|
|
276
273
|
if (byName !== undefined) {
|
|
277
274
|
named.add(byName);
|
|
278
275
|
}
|
|
279
|
-
|
|
276
|
+
const symbol = resolved[index];
|
|
277
|
+
for (const declaration of typeof symbol === "object" ? symbol.declarations : []) {
|
|
280
278
|
const byPath = packageOfPath(declaration.path);
|
|
281
279
|
if (byPath !== undefined) {
|
|
282
280
|
named.add(byPath);
|
|
@@ -284,11 +282,20 @@ function fileNeeds(project, held, file, programPackages, use) {
|
|
|
284
282
|
}
|
|
285
283
|
packagesOf.set(specifier, [...named]);
|
|
286
284
|
});
|
|
285
|
+
// A name the checker could not resolve counts as a use of every package a binding of
|
|
286
|
+
// that name brings in, so a gap never makes a dependency read as unused.
|
|
287
287
|
const bySymbol = new Map();
|
|
288
|
-
|
|
288
|
+
const byName = new Map();
|
|
289
|
+
const unresolvedNames = new Set();
|
|
290
|
+
bindings.forEach(({ node, specifier }, index) => {
|
|
291
|
+
const packages = packagesOf.get(specifier) ?? [];
|
|
292
|
+
byName.set(node.text, [...(byName.get(node.text) ?? []), ...packages]);
|
|
289
293
|
const symbol = resolved[specifiers.length + index];
|
|
290
|
-
if (symbol
|
|
291
|
-
|
|
294
|
+
if (symbol === UNANSWERED) {
|
|
295
|
+
unresolvedNames.add(node.text);
|
|
296
|
+
}
|
|
297
|
+
else if (symbol !== undefined) {
|
|
298
|
+
bySymbol.set(symbol.id, packages);
|
|
292
299
|
}
|
|
293
300
|
});
|
|
294
301
|
for (const specifier of specifiers) {
|
|
@@ -304,7 +311,12 @@ function fileNeeds(project, held, file, programPackages, use) {
|
|
|
304
311
|
if (parent !== undefined && isShorthandPropertyAssignment(parent) && parent.name === node) {
|
|
305
312
|
symbol = project.shorthandValueAt(project.handle(node));
|
|
306
313
|
}
|
|
307
|
-
const named = symbol ===
|
|
314
|
+
const named = symbol === UNANSWERED || (unresolvedNames.has(node.text) && symbol === undefined)
|
|
315
|
+
? byName.get(node.text)
|
|
316
|
+
: symbol === undefined
|
|
317
|
+
? undefined
|
|
318
|
+
: (bySymbol.get(symbol.id) ??
|
|
319
|
+
(unresolvedNames.has(node.text) ? byName.get(node.text) : undefined));
|
|
308
320
|
if (named !== undefined) {
|
|
309
321
|
use(ownersOf(file, held, node), named);
|
|
310
322
|
}
|
|
@@ -312,6 +324,7 @@ function fileNeeds(project, held, file, programPackages, use) {
|
|
|
312
324
|
for (const reference of file.typeReferenceDirectives) {
|
|
313
325
|
use(ownersOf(file, held, file), typePackages(reference.fileName, programPackages));
|
|
314
326
|
}
|
|
327
|
+
return resolved.slice(0, specifiers.length).includes(UNANSWERED);
|
|
315
328
|
}
|
|
316
329
|
/** The offset just past the JSON value that starts at `at` in text the parser has accepted. */
|
|
317
330
|
function valueEnd(text, at) {
|
|
@@ -470,12 +483,16 @@ export function dependenciesOf(host, targetRoot, perProject, candidates) {
|
|
|
470
483
|
const needed = new Set();
|
|
471
484
|
const uses = new Map();
|
|
472
485
|
for (const project of perProject) {
|
|
473
|
-
|
|
474
|
-
|
|
486
|
+
// A specifier the checker left unanswered may resolve to any declared dependency,
|
|
487
|
+
// and what the configuration needs is never deleted, so it is needed there.
|
|
488
|
+
const unknown = project.unanswered ? declared.map((dependency) => dependency.name) : [];
|
|
489
|
+
const held = unknown.length === 0 ? [] : [[PROJECT_OWNER, new Set(unknown)]];
|
|
490
|
+
for (const [id, used] of [...project.uses, ...held]) {
|
|
475
491
|
const merged = uses.get(id) ?? new Set();
|
|
476
492
|
used.forEach((one) => merged.add(one));
|
|
477
493
|
uses.set(id, merged);
|
|
478
494
|
}
|
|
495
|
+
[...project.packages, ...unknown].forEach((one) => needed.add(one));
|
|
479
496
|
}
|
|
480
497
|
const users = new Map();
|
|
481
498
|
for (const used of uses.values()) {
|
package/dist/src/deprecation.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { isVariableStatement, SyntaxKind } from "@typescript/native/unstable/ast";
|
|
10
10
|
import { nodeKey } from "./inventory.js";
|
|
11
|
-
import {
|
|
11
|
+
import { isAnswered, UNANSWERED } from "./query.js";
|
|
12
12
|
/** The tag the TypeScript tools recognize as marking a declaration deprecated. */
|
|
13
13
|
const DEPRECATED_TAG = "deprecated";
|
|
14
14
|
/** Whether one node's own documentation carries the deprecation tag. */
|
|
@@ -21,15 +21,15 @@ function nameNode(node) {
|
|
|
21
21
|
return node.name ?? node;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* The
|
|
25
|
-
* marker, in the inventory's order.
|
|
24
|
+
* The declarations of one project that carry the deprecation marker.
|
|
26
25
|
*
|
|
27
26
|
* The checker is asked only for a symbol with a declaration in a file whose tree holds
|
|
28
27
|
* the tag, so a project whose files hold none costs no round trip, and one that does
|
|
29
|
-
* costs one batch per file and one
|
|
28
|
+
* costs one batch per file and one for the tags of the symbols that could carry it.
|
|
30
29
|
*/
|
|
31
30
|
export function deprecatedDeclarations(project, held) {
|
|
32
31
|
const marked = new Set();
|
|
32
|
+
const unknown = new Set();
|
|
33
33
|
const markedPaths = new Set();
|
|
34
34
|
const declared = [];
|
|
35
35
|
for (const file of project.ownSourceFiles()) {
|
|
@@ -56,28 +56,39 @@ export function deprecatedDeclarations(project, held) {
|
|
|
56
56
|
declared.push(named);
|
|
57
57
|
}
|
|
58
58
|
if (markedPaths.size > 0) {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
const pairs = declared.flatMap((named) => {
|
|
60
|
+
const symbols = project.symbolsAt(named.map((one) => one.handle));
|
|
61
|
+
return named.flatMap((one, at) => {
|
|
62
|
+
const symbol = symbols[at];
|
|
63
|
+
if (symbol === UNANSWERED) {
|
|
64
|
+
unknown.add(one.id);
|
|
65
|
+
}
|
|
66
|
+
return typeof symbol === "object" &&
|
|
67
|
+
symbol.declarations.some((declaration) => markedPaths.has(declaration.path))
|
|
68
|
+
? [{ id: one.id, symbol }]
|
|
69
|
+
: [];
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
const distinct = [...new Map(pairs.map((one) => [one.symbol.id, one.symbol])).values()];
|
|
73
|
+
const tags = project.queries.jsDocTags(distinct);
|
|
74
|
+
const unread = new Set(distinct.filter((_, at) => tags[at] === UNANSWERED).map((one) => one.id));
|
|
75
|
+
const carries = new Set(distinct.flatMap((symbol, at) => {
|
|
76
|
+
const read = tags[at];
|
|
77
|
+
return read !== undefined &&
|
|
78
|
+
isAnswered(read) &&
|
|
79
|
+
read.some((tag) => tag.name === DEPRECATED_TAG)
|
|
80
|
+
? [symbol.id]
|
|
81
|
+
: [];
|
|
82
|
+
}));
|
|
83
|
+
for (const one of pairs) {
|
|
84
|
+
if (carries.has(one.symbol.id)) {
|
|
85
|
+
marked.add(one.id);
|
|
64
86
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
asked.set(symbol.id, carries);
|
|
68
|
-
return carries;
|
|
69
|
-
};
|
|
70
|
-
for (const named of declared) {
|
|
71
|
-
for (let start = 0; start < named.length; start += DEFAULT_BATCH_CAP) {
|
|
72
|
-
const batch = named.slice(start, start + DEFAULT_BATCH_CAP);
|
|
73
|
-
project.symbolsAt(batch.map((one) => one.handle)).forEach((symbol, at) => {
|
|
74
|
-
const one = batch[at];
|
|
75
|
-
if (symbol !== undefined && one !== undefined && carriesTag(symbol)) {
|
|
76
|
-
marked.add(one.id);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
87
|
+
if (unread.has(one.symbol.id)) {
|
|
88
|
+
unknown.add(one.id);
|
|
79
89
|
}
|
|
80
90
|
}
|
|
81
91
|
}
|
|
82
|
-
|
|
92
|
+
const inOrder = (ids) => held.symbols.filter((symbol) => ids.has(symbol.id)).map((symbol) => symbol.id);
|
|
93
|
+
return { deprecated: inOrder(marked), unanswered: inOrder(unknown) };
|
|
83
94
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { isArrayLiteralExpression, isBinaryExpression, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParameterDeclaration, isParenthesizedExpression, isPropertyAssignment, isShorthandPropertyAssignment, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
9
9
|
import { TypeFlags } from "@typescript/native/unstable/sync";
|
|
10
|
+
import { isAnswered, UNANSWERED } from "./query.js";
|
|
10
11
|
/** The text a property name spells, or `undefined` for a computed name. */
|
|
11
12
|
function nameText(node) {
|
|
12
13
|
if (node === undefined) {
|
|
@@ -151,29 +152,22 @@ function sourceOf(target) {
|
|
|
151
152
|
* places and a table answers every name of it.
|
|
152
153
|
*/
|
|
153
154
|
export function destructuring(project, cap) {
|
|
154
|
-
const
|
|
155
|
+
const queries = project.queries;
|
|
155
156
|
const tables = new Map();
|
|
156
157
|
let patternBatches = 0;
|
|
157
158
|
let patternLookups = 0;
|
|
158
|
-
const typesAt = (nodes) => {
|
|
159
|
-
const types = [];
|
|
160
|
-
for (let from = 0; from < nodes.length; from += cap) {
|
|
161
|
-
types.push(...checker.getTypeAtLocation(nodes.slice(from, from + cap)));
|
|
162
|
-
patternBatches += 1;
|
|
163
|
-
}
|
|
164
|
-
return types;
|
|
165
|
-
};
|
|
166
159
|
const tableOf = (type) => {
|
|
167
160
|
const known = tables.get(type.id);
|
|
168
161
|
if (known !== undefined) {
|
|
169
162
|
return known;
|
|
170
163
|
}
|
|
171
|
-
|
|
164
|
+
let table = new Map();
|
|
172
165
|
if ((type.flags & TypeFlags.AnyOrUnknown) === 0) {
|
|
173
166
|
patternLookups += 1;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
167
|
+
const properties = queries.propertiesOf(type);
|
|
168
|
+
table = isAnswered(properties)
|
|
169
|
+
? new Map(properties.map((property) => [property.name, property]))
|
|
170
|
+
: UNANSWERED;
|
|
177
171
|
}
|
|
178
172
|
tables.set(type.id, table);
|
|
179
173
|
return table;
|
|
@@ -183,7 +177,7 @@ export function destructuring(project, cap) {
|
|
|
183
177
|
return type;
|
|
184
178
|
}
|
|
185
179
|
patternLookups += 1;
|
|
186
|
-
return
|
|
180
|
+
return queries.nonNullable(type);
|
|
187
181
|
};
|
|
188
182
|
/**
|
|
189
183
|
* The type one element of an array or tuple value has: a tuple's element at `index`,
|
|
@@ -193,33 +187,42 @@ export function destructuring(project, cap) {
|
|
|
193
187
|
if (!type.isTypeReference()) {
|
|
194
188
|
return undefined;
|
|
195
189
|
}
|
|
196
|
-
|
|
197
|
-
if (checker.isTupleType(type)) {
|
|
190
|
+
if (type.isTupleType()) {
|
|
198
191
|
if (index === undefined) {
|
|
199
192
|
return undefined;
|
|
200
193
|
}
|
|
201
194
|
patternLookups += 1;
|
|
202
|
-
|
|
195
|
+
const elements = queries.typeArguments(type);
|
|
196
|
+
return isAnswered(elements) ? elements[index] : UNANSWERED;
|
|
203
197
|
}
|
|
204
198
|
patternLookups += 1;
|
|
205
|
-
|
|
199
|
+
const array = queries.isArray(type);
|
|
200
|
+
if (!isAnswered(array)) {
|
|
201
|
+
return UNANSWERED;
|
|
202
|
+
}
|
|
203
|
+
if (!array) {
|
|
206
204
|
return undefined;
|
|
207
205
|
}
|
|
208
206
|
patternLookups += 1;
|
|
209
|
-
|
|
207
|
+
const elements = queries.typeArguments(type);
|
|
208
|
+
return isAnswered(elements) ? elements[0] : UNANSWERED;
|
|
210
209
|
};
|
|
211
210
|
const step = (type, next) => {
|
|
212
211
|
const defined = definedOf(type);
|
|
213
|
-
if (defined === undefined) {
|
|
214
|
-
return
|
|
212
|
+
if (defined === undefined || defined === UNANSWERED) {
|
|
213
|
+
return defined;
|
|
215
214
|
}
|
|
216
215
|
if (next.kind === "property") {
|
|
217
|
-
const
|
|
216
|
+
const table = tableOf(defined);
|
|
217
|
+
if (!isAnswered(table)) {
|
|
218
|
+
return UNANSWERED;
|
|
219
|
+
}
|
|
220
|
+
const property = table.get(next.name);
|
|
218
221
|
if (property === undefined) {
|
|
219
222
|
return undefined;
|
|
220
223
|
}
|
|
221
224
|
patternLookups += 1;
|
|
222
|
-
return
|
|
225
|
+
return queries.typeOfSymbol(property);
|
|
223
226
|
}
|
|
224
227
|
return elementOf(defined, next.kind === "element" ? next.index : undefined);
|
|
225
228
|
};
|
|
@@ -238,11 +241,12 @@ export function destructuring(project, cap) {
|
|
|
238
241
|
asking.add(source.expression);
|
|
239
242
|
}
|
|
240
243
|
}
|
|
241
|
-
const answered = new Map();
|
|
242
244
|
const asked = [...asking];
|
|
243
|
-
|
|
245
|
+
patternBatches += Math.ceil(asked.length / cap);
|
|
246
|
+
const answered = new Map();
|
|
247
|
+
queries.typesAt(asked, cap).forEach((type, index) => {
|
|
244
248
|
const node = asked[index];
|
|
245
|
-
if (
|
|
249
|
+
if (node !== undefined) {
|
|
246
250
|
answered.set(node, type);
|
|
247
251
|
}
|
|
248
252
|
});
|
|
@@ -255,13 +259,17 @@ export function destructuring(project, cap) {
|
|
|
255
259
|
}
|
|
256
260
|
let type = answered.get(source.expression);
|
|
257
261
|
for (const next of source.steps) {
|
|
258
|
-
type = type === undefined ?
|
|
262
|
+
type = type === undefined || type === UNANSWERED ? type : step(type, next);
|
|
259
263
|
}
|
|
260
|
-
patternTypes.set(pattern, type === undefined ?
|
|
264
|
+
patternTypes.set(pattern, type === undefined || type === UNANSWERED ? type : definedOf(type));
|
|
261
265
|
}
|
|
262
266
|
return reads.map((read) => {
|
|
263
267
|
const type = patternTypes.get(read.pattern);
|
|
264
|
-
|
|
268
|
+
if (type === undefined || type === UNANSWERED) {
|
|
269
|
+
return type;
|
|
270
|
+
}
|
|
271
|
+
const table = tableOf(type);
|
|
272
|
+
return isAnswered(table) ? table.get(read.name) : UNANSWERED;
|
|
265
273
|
});
|
|
266
274
|
};
|
|
267
275
|
return {
|
package/dist/src/discover.js
CHANGED
|
@@ -19,14 +19,35 @@ export class DiscoveryError extends Error {
|
|
|
19
19
|
this.diagnostics = diagnostics;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
/** The category an error diagnostic carries, which the protocol sends as a number. */
|
|
23
|
+
const ERROR_CATEGORY = DiagnosticCategory.Error;
|
|
22
24
|
/** Whether one diagnostic is an error, which is what fails a run closed. */
|
|
23
25
|
export function isError(diagnostic) {
|
|
24
|
-
return diagnostic.category ===
|
|
26
|
+
return diagnostic.category === ERROR_CATEGORY;
|
|
25
27
|
}
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* One diagnostic as a line naming its file, its line and column, its code and its
|
|
30
|
+
* message: `path:line:column: TScode: message`, the position left out where the
|
|
31
|
+
* diagnostic names none and the file where it names none. `display` spells the
|
|
32
|
+
* file's path; it answers the path itself where it is not given.
|
|
33
|
+
*/
|
|
34
|
+
export function renderDiagnostic(located, display = (path) => path) {
|
|
35
|
+
const { diagnostic, at } = located;
|
|
36
|
+
const message = `TS${String(diagnostic.code)}: ${diagnostic.text}`;
|
|
37
|
+
if (diagnostic.fileName === undefined) {
|
|
38
|
+
return message;
|
|
39
|
+
}
|
|
40
|
+
const where = display(diagnostic.fileName);
|
|
41
|
+
return at === undefined
|
|
42
|
+
? `${where}: ${message}`
|
|
43
|
+
: `${where}:${String(at.line)}:${String(at.column)}: ${message}`;
|
|
44
|
+
}
|
|
45
|
+
/** Each diagnostic with the line and column its offset falls on. */
|
|
46
|
+
function locatedIn(diagnostics, locate) {
|
|
47
|
+
return diagnostics.map((diagnostic) => ({
|
|
48
|
+
diagnostic,
|
|
49
|
+
at: diagnostic.fileName === undefined ? undefined : locate(diagnostic.fileName, diagnostic.pos),
|
|
50
|
+
}));
|
|
30
51
|
}
|
|
31
52
|
/**
|
|
32
53
|
* Every error a project's three diagnostic sets carry, in the order the compiler
|
|
@@ -38,6 +59,32 @@ export function renderDiagnostic(diagnostic) {
|
|
|
38
59
|
export function diagnosticErrors(sets) {
|
|
39
60
|
return [...sets.configParsing, ...sets.syntactic, ...sets.semantic].filter(isError);
|
|
40
61
|
}
|
|
62
|
+
/** The errors one open project carries, each located in the file it names. */
|
|
63
|
+
export function projectErrors(sets) {
|
|
64
|
+
const load = sets.configParsing.filter(isError);
|
|
65
|
+
return {
|
|
66
|
+
load: locatedIn(load, sets.locate),
|
|
67
|
+
check: load.length > 0 ? [] : locatedIn(diagnosticErrors(sets), sets.locate),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The first line of one error about the target, every mention of the target root
|
|
72
|
+
* spelled relative to it: a path below the root loses the root and its separator, and
|
|
73
|
+
* the root itself is `.`.
|
|
74
|
+
*/
|
|
75
|
+
export function targetRelative(text, targetRoot) {
|
|
76
|
+
const root = targetRoot.endsWith("/") ? targetRoot.slice(0, -1) : targetRoot;
|
|
77
|
+
const [first = ""] = text.split(/\r?\n/u);
|
|
78
|
+
const spelled = first.split(`${root}/`).join("").split(root).join(".").trim();
|
|
79
|
+
return spelled === "" ? "the configuration could not be built" : spelled;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Whether one configuration is the analyzer's own guess: discovery derived it and the
|
|
83
|
+
* scope did not name it. A guess that does not load is dropped; any other ends the run.
|
|
84
|
+
*/
|
|
85
|
+
export function isGuess(discovery, configFile) {
|
|
86
|
+
return discovery.derived && !discovery.fromScope.includes(configFile);
|
|
87
|
+
}
|
|
41
88
|
function isRecord(value) {
|
|
42
89
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
43
90
|
}
|
|
@@ -130,9 +177,11 @@ function scopeConfigFiles(host, scope) {
|
|
|
130
177
|
/**
|
|
131
178
|
* Every configuration file reached from `pending`, in the order discovery reads them:
|
|
132
179
|
* each once, each followed by the configurations it references, transitively. A
|
|
133
|
-
* configuration the compiler cannot read ends the run naming it
|
|
180
|
+
* configuration the compiler cannot read ends the run naming it, unless `unreadable`
|
|
181
|
+
* is given, which is then told the configuration and why, and the walk goes on
|
|
182
|
+
* without it.
|
|
134
183
|
*/
|
|
135
|
-
function derivedFrom(engine, host, pending) {
|
|
184
|
+
function derivedFrom(engine, host, pending, unreadable) {
|
|
136
185
|
const seen = new Set();
|
|
137
186
|
const found = [];
|
|
138
187
|
while (pending.length > 0) {
|
|
@@ -145,7 +194,14 @@ function derivedFrom(engine, host, pending) {
|
|
|
145
194
|
continue;
|
|
146
195
|
}
|
|
147
196
|
seen.add(path);
|
|
148
|
-
|
|
197
|
+
const reason = unreadableReason(engine, path);
|
|
198
|
+
if (reason !== undefined) {
|
|
199
|
+
if (unreadable === undefined) {
|
|
200
|
+
throw new DiscoveryError(`${path} cannot be read as a compiler configuration: ${reason}`, []);
|
|
201
|
+
}
|
|
202
|
+
unreadable(path, reason);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
149
205
|
found.push(path);
|
|
150
206
|
pending.push(...referencesOf(host, path));
|
|
151
207
|
}
|
|
@@ -179,18 +235,25 @@ export function consumerProjects(engine, host, root) {
|
|
|
179
235
|
function derivedId(targetRoot, configFile) {
|
|
180
236
|
return relativePath(targetRoot, configFile) ?? configFile;
|
|
181
237
|
}
|
|
238
|
+
/** Why the compiler cannot read one configuration file, or undefined where it can. */
|
|
239
|
+
function unreadableReason(engine, configFile) {
|
|
240
|
+
try {
|
|
241
|
+
engine.parseConfigFile(configFile);
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
return error instanceof Error ? error.message : String(error);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
182
248
|
/**
|
|
183
249
|
* Resolves one compiler configuration file, or ends the run naming it: a project
|
|
184
250
|
* opened from a configuration the compiler cannot read would be analyzed with no
|
|
185
251
|
* type information.
|
|
186
252
|
*/
|
|
187
253
|
function parseOrRefuse(engine, configFile, named) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
catch (error) {
|
|
192
|
-
throw new DiscoveryError(`${named} cannot be read as a compiler configuration: ` +
|
|
193
|
-
(error instanceof Error ? error.message : String(error)), []);
|
|
254
|
+
const reason = unreadableReason(engine, configFile);
|
|
255
|
+
if (reason !== undefined) {
|
|
256
|
+
throw new DiscoveryError(`${named} cannot be read as a compiler configuration: ${reason}`, []);
|
|
194
257
|
}
|
|
195
258
|
}
|
|
196
259
|
/**
|
|
@@ -232,25 +295,41 @@ function declaredProjects(engine, host, targetRoot, entries) {
|
|
|
232
295
|
"into the program that references it, so the matrix names it too", []);
|
|
233
296
|
}
|
|
234
297
|
}
|
|
235
|
-
return {
|
|
298
|
+
return {
|
|
299
|
+
projects,
|
|
300
|
+
derived: false,
|
|
301
|
+
notBuilt: [],
|
|
302
|
+
configFiles: projects.map((project) => project.configFile),
|
|
303
|
+
fromScope: [],
|
|
304
|
+
};
|
|
236
305
|
}
|
|
237
306
|
/**
|
|
238
307
|
* The projects a run derives where the matrix declares none, in the order discovery
|
|
239
308
|
* reads them: the paths the scope names, then every compiler configuration under
|
|
240
309
|
* the target root that is not inside an ignored directory, then the configurations
|
|
241
|
-
* each of those references, transitively. Each configuration appears once.
|
|
310
|
+
* each of those references, transitively. Each configuration appears once. A
|
|
311
|
+
* configuration the scope names directly is the caller's own and ends the run where
|
|
312
|
+
* it cannot be read; one the walk found is dropped and named.
|
|
242
313
|
*/
|
|
243
314
|
function derivedProjects(engine, host, scope) {
|
|
315
|
+
const targetRoot = scope.target.path;
|
|
244
316
|
const fromScope = scopeConfigFiles(host, scope);
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
317
|
+
const notBuilt = [];
|
|
318
|
+
const configFiles = derivedFrom(engine, host, [...fromScope, ...configFilesUnder(host, targetRoot)], (configFile, reason) => {
|
|
319
|
+
if (fromScope.includes(configFile)) {
|
|
320
|
+
throw new DiscoveryError(`${configFile} cannot be read as a compiler configuration: ${reason}`, []);
|
|
321
|
+
}
|
|
322
|
+
notBuilt.push({
|
|
323
|
+
id: derivedId(targetRoot, configFile),
|
|
324
|
+
configFile,
|
|
325
|
+
error: targetRelative(reason, resolvePath(host.workingDirectory(), targetRoot)),
|
|
326
|
+
});
|
|
327
|
+
});
|
|
249
328
|
const projects = configFiles.map((configFile) => ({
|
|
250
|
-
id: derivedId(
|
|
329
|
+
id: derivedId(targetRoot, configFile),
|
|
251
330
|
configFile,
|
|
252
331
|
}));
|
|
253
|
-
return { projects, configFiles, fromScope };
|
|
332
|
+
return { projects, derived: true, notBuilt, configFiles, fromScope };
|
|
254
333
|
}
|
|
255
334
|
/**
|
|
256
335
|
* Every project one run analyzes.
|
|
@@ -262,12 +341,10 @@ function derivedProjects(engine, host, scope) {
|
|
|
262
341
|
* scope and the target tree. A platform entry names a configuration of another
|
|
263
342
|
* language and plays no part here.
|
|
264
343
|
*
|
|
265
|
-
* A configuration file the compiler cannot read
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
* {@link diagnosticErrors} over the three sets a program reports, because a
|
|
270
|
-
* resolution carries no diagnostics.
|
|
344
|
+
* A configuration file the compiler cannot read is never analyzed. One the matrix or
|
|
345
|
+
* the scope names ends the run with the failure code; a guess is dropped and named in
|
|
346
|
+
* {@link Discovery.notBuilt}. What a configuration's own contents say is read once its
|
|
347
|
+
* project is open, by {@link diagnosticErrors}, because a resolution carries none.
|
|
271
348
|
*
|
|
272
349
|
* Nothing reads a build output: a project is the files its configuration names, so a
|
|
273
350
|
* package whose sources ship as TypeScript is analyzed as it stands.
|
package/dist/src/entry-points.js
CHANGED
|
@@ -14,6 +14,7 @@ import { isArrayLiteralExpression, isAsExpression, isCallExpression, isExportAss
|
|
|
14
14
|
import { SymbolFlags } from "@typescript/native/unstable/sync";
|
|
15
15
|
import { globExpression } from "./glob.js";
|
|
16
16
|
import { dirnamePath, relativePath, resolvePath } from "./paths.js";
|
|
17
|
+
import { UNANSWERED } from "./query.js";
|
|
17
18
|
/** The extensions the test runner and the browser-test runner load a configuration from. */
|
|
18
19
|
const MODULE_EXTENSIONS = ["js", "mjs", "cjs", "ts", "mts", "cts"];
|
|
19
20
|
/** The extensions the linter's flat configuration and the mutation tester's are loaded from. */
|
|
@@ -331,8 +332,14 @@ function workersOf(project, file, files) {
|
|
|
331
332
|
const names = sites.flatMap((site) => site.platformNames);
|
|
332
333
|
const symbols = project.symbolsAt(names.map((name) => project.handle(name)));
|
|
333
334
|
const platform = new Set();
|
|
335
|
+
// A name the checker could not resolve is read as the platform's, so a worker it
|
|
336
|
+
// stands on is rooted rather than reported.
|
|
334
337
|
names.forEach((name, index) => {
|
|
335
338
|
const symbol = symbols[index];
|
|
339
|
+
if (symbol === UNANSWERED) {
|
|
340
|
+
platform.add(name);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
336
343
|
if (symbol !== undefined &&
|
|
337
344
|
(symbol.flags & SymbolFlags.Alias) === 0 &&
|
|
338
345
|
symbol.declarations.length > 0 &&
|