@cplieger/deadset-ts 4.0.1 → 5.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 +4 -4
- package/dist/bin/component-mapper.js +5 -0
- package/dist/bin/mapper-connection.js +101 -0
- package/dist/bin/node-host.js +24 -2
- package/dist/src/analysis.js +229 -36
- package/dist/src/component-files.js +612 -0
- package/dist/src/component-layer.js +132 -0
- package/dist/src/config.js +9 -2
- package/dist/src/configuration-files.js +186 -0
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/convention-rows.js +383 -0
- package/dist/src/conventions.js +456 -0
- package/dist/src/dependencies.js +30 -3
- package/dist/src/discover.js +10 -3
- package/dist/src/emit-map.js +72 -0
- package/dist/src/entry-point-gaps.js +81 -35
- package/dist/src/entry-points.js +47 -32
- package/dist/src/exempt.js +11 -3
- package/dist/src/findings/completion.js +19 -29
- package/dist/src/findings/emitters.js +22 -1
- package/dist/src/findings/unused-declarations.js +7 -2
- package/dist/src/graph.js +13 -5
- package/dist/src/inventory.js +4 -2
- package/dist/src/kinds.js +2 -2
- package/dist/src/manifest.js +37 -11
- package/dist/src/matrix.js +21 -13
- package/dist/src/partial.js +3 -1
- package/dist/src/pnpm-workspace.js +120 -0
- package/dist/src/position.js +17 -2
- package/dist/src/print.js +2 -0
- package/dist/src/references.js +209 -30
- package/dist/src/report.js +26 -6
- package/dist/src/reporters/annotations.js +10 -3
- package/dist/src/reporters/template.js +3 -0
- package/dist/src/resolve.js +31 -6
- package/dist/src/roots.js +37 -8
- package/dist/src/run.js +13 -3
- package/dist/src/schema.js +2 -0
- package/dist/src/session.js +141 -34
- package/dist/src/setup-failure.js +23 -0
- package/dist/src/source-files.js +4 -61
- package/dist/src/sweep.js +4 -2
- package/dist/src/template-field.js +38 -6
- package/dist/src/test-support.js +59 -0
- package/dist/src/type-errors.js +252 -0
- package/dist/src/type-query-alias.js +92 -0
- package/dist/src/verbs/analyze.js +24 -4
- package/dist/src/verbs/explain.js +4 -1
- package/dist/src/verbs/print-projects.js +17 -56
- package/dist/src/verbs/print-retained.js +7 -2
- package/dist/src/verbs/print-roots.js +7 -2
- package/dist/src/version-range.js +255 -0
- package/dist/src/version.js +1 -1
- package/dist/src/workspace-manifests.js +68 -0
- package/dist/src/workspace-programs.js +342 -0
- package/dist/src/workspace-resolution.js +307 -0
- package/dist/src/workspace-run.js +75 -0
- package/dist/src/workspace.js +358 -0
- package/jsr.json +1 -1
- package/package.json +2 -1
- package/src/analysis.ts +394 -70
- package/src/component-files.ts +718 -0
- package/src/component-layer.ts +160 -0
- package/src/config.ts +11 -2
- package/src/configuration-files.ts +235 -0
- package/src/conformance-record.ts +2 -2
- package/src/convention-rows.ts +440 -0
- package/src/conventions.ts +640 -0
- package/src/dependencies.ts +36 -2
- package/src/discover.ts +10 -6
- package/src/emit-map.ts +93 -0
- package/src/entry-point-gaps.ts +92 -38
- package/src/entry-points.ts +66 -36
- package/src/exempt.ts +21 -3
- package/src/findings/completion.ts +24 -33
- package/src/findings/emitters.ts +32 -1
- package/src/findings/unused-declarations.ts +9 -2
- package/src/graph.ts +15 -4
- package/src/host.ts +23 -0
- package/src/index.ts +1 -1
- package/src/inventory.ts +13 -2
- package/src/kinds.ts +2 -2
- package/src/manifest.ts +47 -14
- package/src/matrix.ts +43 -15
- package/src/partial.ts +6 -1
- package/src/pnpm-workspace.ts +135 -0
- package/src/position.ts +23 -2
- package/src/print.ts +2 -0
- package/src/references.ts +227 -28
- package/src/report.ts +56 -6
- package/src/reporters/annotations.ts +12 -3
- package/src/reporters/template.ts +3 -0
- package/src/resolve.ts +59 -6
- package/src/roots.ts +60 -9
- package/src/run.ts +15 -5
- package/src/schema.ts +2 -0
- package/src/session.ts +218 -37
- package/src/setup-failure.ts +39 -0
- package/src/source-files.ts +4 -66
- package/src/sweep.ts +9 -2
- package/src/template-field.ts +42 -7
- package/src/test-support.ts +77 -0
- package/src/type-errors.ts +340 -0
- package/src/type-query-alias.ts +133 -0
- package/src/verbs/analyze.ts +38 -4
- package/src/verbs/explain.ts +4 -1
- package/src/verbs/print-projects.ts +17 -69
- package/src/verbs/print-retained.ts +7 -2
- package/src/verbs/print-roots.ts +7 -2
- package/src/verbs/verb.ts +6 -3
- package/src/version-range.ts +309 -0
- package/src/version.ts +1 -1
- package/src/workspace-manifests.ts +100 -0
- package/src/workspace-programs.ts +486 -0
- package/src/workspace-resolution.ts +393 -0
- package/src/workspace-run.ts +148 -0
- package/src/workspace.ts +463 -0
package/dist/src/references.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isDecorator, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExternalModuleReference, isForInStatement, isForOfStatement, isImportDeclaration, isImportEqualsDeclaration, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
1
|
+
import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isDecorator, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExpressionStatement, isExternalModuleReference, isForInStatement, isForOfStatement, isForStatement, isImportDeclaration, isImportEqualsDeclaration, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, isVoidExpression, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
2
2
|
import { aliasChains } from "./alias-chain.js";
|
|
3
3
|
import { destructuring, propertyReadsOf } from "./destructuring.js";
|
|
4
4
|
import { globExpression } from "./glob.js";
|
|
5
5
|
import { declarationsByName, nodeKey } from "./inventory.js";
|
|
6
6
|
import { relativePath } from "./paths.js";
|
|
7
|
-
import { byPosition, renderPosition } from "./position.js";
|
|
7
|
+
import { byPosition, isComponentFile, renderPosition } from "./position.js";
|
|
8
8
|
import { DEFAULT_BATCH_CAP, UNANSWERED } from "./query.js";
|
|
9
|
+
import { typeQueryAliases } from "./type-query-alias.js";
|
|
9
10
|
/**
|
|
10
11
|
* The operators whose left side one assignment stores into. A compound assignment is
|
|
11
|
-
* among them: it reads its target only to write the result
|
|
12
|
-
*
|
|
13
|
-
* carries no information out of itself.
|
|
12
|
+
* among them: where its value is discarded it reads its target only to write the result
|
|
13
|
+
* back, so nothing else receives what it read.
|
|
14
14
|
*/
|
|
15
15
|
const ASSIGNMENTS = new Set([
|
|
16
16
|
SyntaxKind.EqualsToken,
|
|
@@ -30,6 +30,36 @@ const ASSIGNMENTS = new Set([
|
|
|
30
30
|
SyntaxKind.QuestionQuestionEqualsToken,
|
|
31
31
|
SyntaxKind.CaretEqualsToken,
|
|
32
32
|
]);
|
|
33
|
+
/**
|
|
34
|
+
* The assignments that read their target to decide whether to store at all, so the read
|
|
35
|
+
* carries information out whatever becomes of the result.
|
|
36
|
+
*/
|
|
37
|
+
const LOGICAL_ASSIGNMENTS = new Set([
|
|
38
|
+
SyntaxKind.BarBarEqualsToken,
|
|
39
|
+
SyntaxKind.AmpersandAmpersandEqualsToken,
|
|
40
|
+
SyntaxKind.QuestionQuestionEqualsToken,
|
|
41
|
+
]);
|
|
42
|
+
/**
|
|
43
|
+
* Whether the value of one store expression is discarded: it is a statement of its own, a
|
|
44
|
+
* clause of a `for` loop, the operand of `void`, or the left operand of a comma.
|
|
45
|
+
*/
|
|
46
|
+
function valueDiscarded(node) {
|
|
47
|
+
let child = node;
|
|
48
|
+
let parent = node.parent;
|
|
49
|
+
while (isParenthesizedExpression(parent)) {
|
|
50
|
+
child = parent;
|
|
51
|
+
parent = parent.parent;
|
|
52
|
+
}
|
|
53
|
+
if (isExpressionStatement(parent) || isVoidExpression(parent)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
if (isForStatement(parent)) {
|
|
57
|
+
return parent.initializer === child || parent.incrementor === child;
|
|
58
|
+
}
|
|
59
|
+
return (isBinaryExpression(parent) &&
|
|
60
|
+
parent.operatorToken.kind === SyntaxKind.CommaToken &&
|
|
61
|
+
parent.left === child);
|
|
62
|
+
}
|
|
33
63
|
/** Whether one node is an identifier or a private name, the two forms a name takes. */
|
|
34
64
|
function isName(node) {
|
|
35
65
|
return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PrivateIdentifier;
|
|
@@ -97,12 +127,18 @@ function markWrite(target, writes) {
|
|
|
97
127
|
* Records every store one node performs: an assignment, a compound assignment, an
|
|
98
128
|
* increment or a decrement, a `delete`, and an iteration that assigns to a name declared
|
|
99
129
|
* elsewhere. An iteration that declares its own name stores into nothing the inventory
|
|
100
|
-
* holds.
|
|
130
|
+
* holds. A store that reads its target as well records it in `reads`: a logical
|
|
131
|
+
* assignment, and a compound assignment, an increment or a decrement whose value is used.
|
|
101
132
|
*/
|
|
102
|
-
function markStores(node, writes) {
|
|
133
|
+
function markStores(node, writes, reads) {
|
|
103
134
|
if (isBinaryExpression(node)) {
|
|
104
|
-
|
|
135
|
+
const operator = node.operatorToken.kind;
|
|
136
|
+
if (ASSIGNMENTS.has(operator)) {
|
|
105
137
|
markWrite(node.left, writes);
|
|
138
|
+
if (LOGICAL_ASSIGNMENTS.has(operator) ||
|
|
139
|
+
(operator !== SyntaxKind.EqualsToken && !valueDiscarded(node))) {
|
|
140
|
+
markWrite(node.left, reads);
|
|
141
|
+
}
|
|
106
142
|
}
|
|
107
143
|
return;
|
|
108
144
|
}
|
|
@@ -110,6 +146,9 @@ function markStores(node, writes) {
|
|
|
110
146
|
if (node.operator === SyntaxKind.PlusPlusToken ||
|
|
111
147
|
node.operator === SyntaxKind.MinusMinusToken) {
|
|
112
148
|
markWrite(node.operand, writes);
|
|
149
|
+
if (!valueDiscarded(node)) {
|
|
150
|
+
markWrite(node.operand, reads);
|
|
151
|
+
}
|
|
113
152
|
}
|
|
114
153
|
return;
|
|
115
154
|
}
|
|
@@ -198,15 +237,17 @@ function linksOf(file, node, declarations, enclosing) {
|
|
|
198
237
|
* order, each with its enclosing declaration and its use, and every re-export the file
|
|
199
238
|
* writes. One walk answers all of it, because a second would have to agree with this one
|
|
200
239
|
* about which nodes are names. A declaration's own declared name is left out, which is
|
|
201
|
-
* what makes a declaration referenced only from its own site an unreferenced one
|
|
240
|
+
* what makes a declaration referenced only from its own site an unreferenced one, and so
|
|
241
|
+
* is every node of `unread`, a subtree that is no use.
|
|
202
242
|
*/
|
|
203
|
-
function sitesOf(file, declarations, fileId) {
|
|
243
|
+
function sitesOf(file, declarations, fileId, unread) {
|
|
204
244
|
const found = [];
|
|
205
245
|
const reads = [];
|
|
206
246
|
const links = [];
|
|
207
247
|
const evaluations = [];
|
|
208
248
|
const decorated = [];
|
|
209
249
|
const writes = new Set();
|
|
250
|
+
const alsoRead = new Set();
|
|
210
251
|
const declared = new Set();
|
|
211
252
|
const shorthands = new Map();
|
|
212
253
|
let enclosing = fileId;
|
|
@@ -228,19 +269,23 @@ function sitesOf(file, declarations, fileId) {
|
|
|
228
269
|
}
|
|
229
270
|
};
|
|
230
271
|
const visit = (node) => {
|
|
272
|
+
if (unread.has(node)) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
231
275
|
if (isName(node)) {
|
|
232
276
|
if (!declared.has(node.pos)) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
277
|
+
const site = { node, shorthand: shorthands.get(node.pos), from: enclosing };
|
|
278
|
+
if (writes.has(node.pos)) {
|
|
279
|
+
found.push({ ...site, use: "write" });
|
|
280
|
+
}
|
|
281
|
+
if (!writes.has(node.pos) || alsoRead.has(node.pos)) {
|
|
282
|
+
found.push({ ...site, use: "read" });
|
|
283
|
+
}
|
|
239
284
|
}
|
|
240
285
|
return;
|
|
241
286
|
}
|
|
242
287
|
markNames(node);
|
|
243
|
-
markStores(node, writes);
|
|
288
|
+
markStores(node, writes, alsoRead);
|
|
244
289
|
for (const read of propertyReadsOf(node)) {
|
|
245
290
|
reads.push({ ...read, from: enclosing });
|
|
246
291
|
}
|
|
@@ -265,6 +310,8 @@ function sitesOf(file, declarations, fileId) {
|
|
|
265
310
|
file.forEachChild(visit);
|
|
266
311
|
return { uses: found, reads, links, evaluations, decorated };
|
|
267
312
|
}
|
|
313
|
+
/** The rule a file the analysis classified as test-support code is counted under. */
|
|
314
|
+
const TEST_SUPPORT_RULE = "test-support";
|
|
268
315
|
/**
|
|
269
316
|
* The name one test-file rule takes. A rule's name is a word list the report's own
|
|
270
317
|
* vocabulary spells, which cannot carry a pattern, so a rule is named by the place of
|
|
@@ -277,17 +324,19 @@ function patternRule(index) {
|
|
|
277
324
|
* The rules over the test files of several projects at once, ordered by rule: a file two
|
|
278
325
|
* projects hold is one file, so each rule counts the distinct paths it matches.
|
|
279
326
|
*/
|
|
280
|
-
export function testFileRulesOf(testFiles, paths) {
|
|
327
|
+
export function testFileRulesOf(testFiles, paths, supportFiles = []) {
|
|
281
328
|
const distinct = [...new Set(paths)];
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
329
|
+
const support = new Set(supportFiles).size;
|
|
330
|
+
return [
|
|
331
|
+
...testFiles.map((pattern, index) => {
|
|
332
|
+
const expression = globExpression(pattern);
|
|
333
|
+
return {
|
|
334
|
+
rule: patternRule(index),
|
|
335
|
+
matched: distinct.filter((path) => expression.test(path)).length,
|
|
336
|
+
};
|
|
337
|
+
}),
|
|
338
|
+
...(support === 0 ? [] : [{ rule: TEST_SUPPORT_RULE, matched: support }]),
|
|
339
|
+
].sort((a, b) => compare(a.rule, b.rule));
|
|
291
340
|
}
|
|
292
341
|
const importedCache = new WeakMap();
|
|
293
342
|
/**
|
|
@@ -332,6 +381,37 @@ function importedNames(file) {
|
|
|
332
381
|
importedCache.set(file, names);
|
|
333
382
|
return names;
|
|
334
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* The name node of every import binding one file's top level declares, and whether it
|
|
386
|
+
* binds a whole module.
|
|
387
|
+
*/
|
|
388
|
+
function importBindings(file) {
|
|
389
|
+
const found = [];
|
|
390
|
+
for (const statement of file.statements) {
|
|
391
|
+
if (isImportEqualsDeclaration(statement)) {
|
|
392
|
+
found.push({ node: statement.name, whole: true });
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
const clause = isImportDeclaration(statement) ? statement.importClause : undefined;
|
|
396
|
+
if (clause === undefined) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (clause.name !== undefined) {
|
|
400
|
+
found.push({ node: clause.name, whole: false });
|
|
401
|
+
}
|
|
402
|
+
const bindings = clause.namedBindings;
|
|
403
|
+
if (bindings === undefined) {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (bindings.kind === SyntaxKind.NamespaceImport) {
|
|
407
|
+
found.push({ node: bindings.name, whole: true });
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
found.push(...bindings.elements.map((element) => ({ node: element.name, whole: false })));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return found;
|
|
414
|
+
}
|
|
335
415
|
/** Two strings ordered bytewise, which is the order every set of a run is read in. */
|
|
336
416
|
function compare(a, b) {
|
|
337
417
|
if (a === b) {
|
|
@@ -376,6 +456,20 @@ export function references(project, held, targetRoot, options) {
|
|
|
376
456
|
.filter((file) => relativePath(consumer.root, file.fileName) !== undefined &&
|
|
377
457
|
relativePath(targetRoot, file.fileName) === undefined);
|
|
378
458
|
const fileIds = new Set(held.symbols.filter((symbol) => symbol.kind === "file").map((symbol) => symbol.id));
|
|
459
|
+
let topLevel;
|
|
460
|
+
/** The declarations one file's top level holds. */
|
|
461
|
+
const declaredIn = (fileId) => {
|
|
462
|
+
if (topLevel === undefined) {
|
|
463
|
+
const byParent = new Map();
|
|
464
|
+
for (const symbol of held.symbols) {
|
|
465
|
+
if (fileIds.has(symbol.parent)) {
|
|
466
|
+
byParent.set(symbol.parent, [...(byParent.get(symbol.parent) ?? []), symbol]);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
topLevel = byParent;
|
|
470
|
+
}
|
|
471
|
+
return topLevel.get(fileId) ?? [];
|
|
472
|
+
};
|
|
379
473
|
/** The identifier of the declaration one node of this program is, where it is one. */
|
|
380
474
|
const declaredAt = (node) => declarations.get(nodeKey(node.getSourceFile(), node));
|
|
381
475
|
/**
|
|
@@ -405,6 +499,45 @@ export function references(project, held, targetRoot, options) {
|
|
|
405
499
|
]),
|
|
406
500
|
];
|
|
407
501
|
};
|
|
502
|
+
// A type-query alias's own type names its export without using it; a use of the
|
|
503
|
+
// global is the use, so it names every link of the export's chain as well.
|
|
504
|
+
const aliases = project.ownSourceFiles().flatMap(typeQueryAliases);
|
|
505
|
+
const unread = new Set(aliases.map((alias) => alias.type));
|
|
506
|
+
const aliased = new Map();
|
|
507
|
+
if (aliases.length > 0) {
|
|
508
|
+
const exported = project.symbolsAt(aliases.map((alias) => project.handle(alias.exported)));
|
|
509
|
+
aliases.forEach((alias, index) => {
|
|
510
|
+
const id = declaredAt(alias.declaration);
|
|
511
|
+
const symbol = exported[index];
|
|
512
|
+
if (id === undefined || symbol === undefined) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
aliased.set(id, symbol === UNANSWERED
|
|
516
|
+
? (declarationsByName(held).get(alias.name) ?? []).map((one) => ({
|
|
517
|
+
id: one,
|
|
518
|
+
stepped: true,
|
|
519
|
+
guessed: true,
|
|
520
|
+
}))
|
|
521
|
+
: chains.chainOf(symbol).map((target) => ({ ...target, stepped: true })));
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
/** One use's targets, then every link an alias global among them stands for, each once. */
|
|
525
|
+
const throughAliases = (targets) => {
|
|
526
|
+
if (aliased.size === 0) {
|
|
527
|
+
return targets;
|
|
528
|
+
}
|
|
529
|
+
const all = [...targets];
|
|
530
|
+
const seen = new Set(targets.map((target) => target.id));
|
|
531
|
+
for (const target of targets) {
|
|
532
|
+
for (const forwarded of aliased.get(target.id) ?? []) {
|
|
533
|
+
if (!seen.has(forwarded.id)) {
|
|
534
|
+
seen.add(forwarded.id);
|
|
535
|
+
all.push(forwarded);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return all;
|
|
540
|
+
};
|
|
408
541
|
/** The symbol one site resolves to, asking only the accessor the site's form names. */
|
|
409
542
|
const symbolFor = (site, answered) => {
|
|
410
543
|
if (site.shorthand !== undefined) {
|
|
@@ -424,7 +557,11 @@ export function references(project, held, targetRoot, options) {
|
|
|
424
557
|
if (test) {
|
|
425
558
|
tests.push(path);
|
|
426
559
|
}
|
|
427
|
-
const
|
|
560
|
+
const fileId = declaredAt(file) ?? "";
|
|
561
|
+
const { uses, reads, links, evaluations, decorated } = sitesOf(file, declarations, fileId, unread);
|
|
562
|
+
// A component file's markup may use any binding its blocks declare at the top level.
|
|
563
|
+
const markup = isComponentFile(file);
|
|
564
|
+
const bindings = markup ? importBindings(file) : [];
|
|
428
565
|
// A decorator is written inside the declaration it is attached to and receives it,
|
|
429
566
|
// so the declaration is used where the decorator is written, whatever the
|
|
430
567
|
// decorator does with it.
|
|
@@ -448,6 +585,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
448
585
|
...uses.filter((site) => site.shorthand === undefined).map((site) => site.node),
|
|
449
586
|
...links.map((link) => link.node),
|
|
450
587
|
...evaluations.map((evaluation) => evaluation.node),
|
|
588
|
+
...bindings.map((binding) => binding.node),
|
|
451
589
|
]),
|
|
452
590
|
];
|
|
453
591
|
const answered = new Map();
|
|
@@ -504,6 +642,47 @@ export function references(project, held, targetRoot, options) {
|
|
|
504
642
|
}
|
|
505
643
|
}
|
|
506
644
|
}
|
|
645
|
+
if (markup) {
|
|
646
|
+
for (const symbol of declaredIn(fileId)) {
|
|
647
|
+
found.push({
|
|
648
|
+
from: fileId,
|
|
649
|
+
to: symbol.id,
|
|
650
|
+
position: symbol.position,
|
|
651
|
+
use: "read",
|
|
652
|
+
resolution: "syntax",
|
|
653
|
+
test,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
for (const binding of bindings) {
|
|
657
|
+
const symbol = answered.get(binding.node);
|
|
658
|
+
if (symbol === undefined) {
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
const position = renderPosition(file, root, binding.node.getStart());
|
|
662
|
+
const chained = symbol === UNANSWERED
|
|
663
|
+
? named(binding.node).map((id) => ({ id, stepped: false, guessed: true }))
|
|
664
|
+
: chains.chainOf(symbol);
|
|
665
|
+
// The markup may name any export of a module a binding holds whole.
|
|
666
|
+
const targets = binding.whole
|
|
667
|
+
? chained.flatMap((target) => [
|
|
668
|
+
target,
|
|
669
|
+
...declaredIn(target.id)
|
|
670
|
+
.filter((one) => one.exported)
|
|
671
|
+
.map((one) => ({ ...target, id: one.id })),
|
|
672
|
+
])
|
|
673
|
+
: chained;
|
|
674
|
+
for (const target of targets) {
|
|
675
|
+
found.push({
|
|
676
|
+
from: fileId,
|
|
677
|
+
to: target.id,
|
|
678
|
+
position,
|
|
679
|
+
use: "read",
|
|
680
|
+
resolution: target.guessed ? "by-name" : target.stepped ? "alias" : "batch",
|
|
681
|
+
test,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
507
686
|
// A pattern names a property of the value it destructures, which no name node of
|
|
508
687
|
// the pattern resolves to: a shorthand's name is the local it binds.
|
|
509
688
|
destructured.resolve(reads).forEach((symbol, index) => {
|
|
@@ -543,9 +722,9 @@ export function references(project, held, targetRoot, options) {
|
|
|
543
722
|
const position = renderPosition(file, root, site.node.getStart());
|
|
544
723
|
// A re-export something imports is used and so is the declaration behind it, so
|
|
545
724
|
// the use names every link of the chain its name stands at the head of.
|
|
546
|
-
const targets = symbol === UNANSWERED
|
|
725
|
+
const targets = throughAliases(symbol === UNANSWERED
|
|
547
726
|
? named(site.node).map((id) => ({ id, stepped: false, guessed: true }))
|
|
548
|
-
: chains.chainOf(symbol);
|
|
727
|
+
: chains.chainOf(symbol));
|
|
549
728
|
for (const target of targets) {
|
|
550
729
|
found.push({
|
|
551
730
|
from: site.from,
|
package/dist/src/report.js
CHANGED
|
@@ -4,12 +4,9 @@
|
|
|
4
4
|
* renders this document, so no two renderings disagree about which findings exist.
|
|
5
5
|
*/
|
|
6
6
|
/** The version of the report schema this analyzer writes a report to. */
|
|
7
|
-
const SCHEMA_VERSION = "
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
* implements admits, each an instance of the one it writes.
|
|
11
|
-
*/
|
|
12
|
-
export const SCHEMA_VERSIONS_ACCEPTED = ["6.0.0", SCHEMA_VERSION];
|
|
7
|
+
const SCHEMA_VERSION = "7.0.0";
|
|
8
|
+
/** Every report schema version this analyzer reads: the one the Contract it implements admits. */
|
|
9
|
+
export const SCHEMA_VERSIONS_ACCEPTED = [SCHEMA_VERSION];
|
|
13
10
|
/** The name this analyzer writes every document under and mints every identifier with. */
|
|
14
11
|
export const ANALYZER_NAME = "deadset-ts";
|
|
15
12
|
/** The language this analyzer claims. */
|
|
@@ -234,6 +231,29 @@ export function buildReport(input) {
|
|
|
234
231
|
test_file_rules: [...input.testFileRules]
|
|
235
232
|
.sort((a, b) => compare(a.rule, b.rule) || a.matched - b.matched)
|
|
236
233
|
.map((one) => ({ rule: one.rule, matched: one.matched })),
|
|
234
|
+
type_error_skips: [
|
|
235
|
+
...new Map(input.typeErrorSkips.map((one) => {
|
|
236
|
+
const skip = { path: one.path, line: one.line, message: one.message };
|
|
237
|
+
return [JSON.stringify(skip), skip];
|
|
238
|
+
})),
|
|
239
|
+
]
|
|
240
|
+
.sort(([a, x], [b, y]) => compare(x.path, y.path) || x.line - y.line || compare(a, b))
|
|
241
|
+
.map(([, skip]) => skip),
|
|
242
|
+
notes: [],
|
|
243
|
+
unanswered_questions: [...input.unanswered]
|
|
244
|
+
.sort((a, b) => compare(a.configuration, b.configuration) ||
|
|
245
|
+
compare(JSON.stringify(a), JSON.stringify(b)))
|
|
246
|
+
.map((one) => ({
|
|
247
|
+
configuration: one.configuration,
|
|
248
|
+
questions: one.questions,
|
|
249
|
+
declarations: one.declarations,
|
|
250
|
+
})),
|
|
251
|
+
conventions_applied: input.conventionsApplied.map((one) => ({
|
|
252
|
+
name: one.name,
|
|
253
|
+
package: one.package,
|
|
254
|
+
version: one.version,
|
|
255
|
+
manifest: one.manifest,
|
|
256
|
+
})),
|
|
237
257
|
totals: totalsOf(findings, input.result),
|
|
238
258
|
};
|
|
239
259
|
}
|
|
@@ -22,11 +22,15 @@ function kindName(code) {
|
|
|
22
22
|
function command(level, at, endLine, title, message) {
|
|
23
23
|
return `::${level} file=${escapeProperty(at.path)},line=${String(at.line)},col=${String(at.column)},endLine=${String(endLine)},title=${escapeProperty(title)}::${escapeData(message)}`;
|
|
24
24
|
}
|
|
25
|
+
/** The title and the message prefix of a type-error skip's annotation. */
|
|
26
|
+
const SKIP_TITLE = "type error skipped";
|
|
27
|
+
const SKIP_MESSAGE = "the analysis did not evaluate the function or statement holding this type error: ";
|
|
25
28
|
/**
|
|
26
29
|
* One workflow annotation per finding, then one per stale suppression, each naming its
|
|
27
|
-
* file, line, column and last line, titled by its code and kind
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
+
* file, line, column and last line, titled by its code and kind, then one warning per
|
|
31
|
+
* type-error skip naming its file and line. A finding at or above the failing severity is
|
|
32
|
+
* an error and one below it a warning; a stale suppression is an error, because its kind
|
|
33
|
+
* is fixed on at the failing severity.
|
|
30
34
|
*/
|
|
31
35
|
export function annotations(report, options) {
|
|
32
36
|
return coalesced((function* lines() {
|
|
@@ -36,5 +40,8 @@ export function annotations(report, options) {
|
|
|
36
40
|
for (const stale of report.stale_suppressions) {
|
|
37
41
|
yield `${command("error", stale.position, stale.position.line, `${stale.code} ${kindName(stale.code)}`, stale.message)}\n`;
|
|
38
42
|
}
|
|
43
|
+
for (const skip of report.type_error_skips) {
|
|
44
|
+
yield `::warning file=${escapeProperty(skip.path)},line=${String(skip.line)},title=${escapeProperty(SKIP_TITLE)}::${escapeData(SKIP_MESSAGE + skip.message)}\n`;
|
|
45
|
+
}
|
|
39
46
|
})());
|
|
40
47
|
}
|
|
@@ -805,6 +805,9 @@ class Execution {
|
|
|
805
805
|
if (head === undefined) {
|
|
806
806
|
throw new RenderError("empty command");
|
|
807
807
|
}
|
|
808
|
+
if (head.k === "lit" && head.value === null) {
|
|
809
|
+
throw new RenderError("nil is not a command");
|
|
810
|
+
}
|
|
808
811
|
if (head.k !== "func") {
|
|
809
812
|
if (rest.length > 0 || piped !== undefined) {
|
|
810
813
|
throw new RenderError("cannot give an argument to a non-function");
|
package/dist/src/resolve.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConfigError, defaultConfig, } from "./config.js";
|
|
2
|
+
import { CONVENTION_ROWS } from "./convention-rows.js";
|
|
2
3
|
import { FIXED_SEVERITY_CODES, LIVE_CODES } from "./kinds.js";
|
|
3
4
|
import { checkDocument, malformed, nestFlags, SCHEMA_ROOT, SEVERITY_SECTION, } from "./schema.js";
|
|
4
5
|
/** Names the command-line flags in a refusal, where a document is named by its path. */
|
|
@@ -149,6 +150,26 @@ function readStrings(parent, name, path, label, minItems, allowed) {
|
|
|
149
150
|
}
|
|
150
151
|
return value;
|
|
151
152
|
}
|
|
153
|
+
/** A component file's extension: a full stop followed by letters and digits. */
|
|
154
|
+
const COMPONENT_EXTENSION = /^\.[A-Za-z0-9]+$/u;
|
|
155
|
+
function readComponentExtensions(ts, label) {
|
|
156
|
+
const extensions = readStrings(ts, "component_extensions", "ts.component_extensions", label, 0);
|
|
157
|
+
const wrong = extensions?.find((one) => !COMPONENT_EXTENSION.test(one));
|
|
158
|
+
if (wrong !== undefined) {
|
|
159
|
+
throw malformed(label, "ts.component_extensions", `${JSON.stringify(wrong)} is not a full stop followed by letters and digits`);
|
|
160
|
+
}
|
|
161
|
+
return extensions;
|
|
162
|
+
}
|
|
163
|
+
/** The names of the convention rows this analyzer's table carries. */
|
|
164
|
+
const CONVENTION_NAMES = new Set(CONVENTION_ROWS.map((row) => row.name));
|
|
165
|
+
function readDisabledConventions(ts, label) {
|
|
166
|
+
const names = readStrings(ts, "disabled_conventions", "ts.disabled_conventions", label, 0);
|
|
167
|
+
const unknown = names?.find((one) => !CONVENTION_NAMES.has(one));
|
|
168
|
+
if (unknown !== undefined) {
|
|
169
|
+
throw malformed(label, "ts.disabled_conventions", `${JSON.stringify(unknown)} names no convention row this analyzer carries`);
|
|
170
|
+
}
|
|
171
|
+
return names;
|
|
172
|
+
}
|
|
152
173
|
function requireMember(value, path, label) {
|
|
153
174
|
if (value === undefined || value === "") {
|
|
154
175
|
throw malformed(label, path, "is required and names nothing");
|
|
@@ -507,6 +528,8 @@ function readDoc(value, label) {
|
|
|
507
528
|
providers: readProviders(providers, label),
|
|
508
529
|
testFiles: readStrings(ts, "test_files", "ts.test_files", label, 1),
|
|
509
530
|
entryFiles: readStrings(ts, "entry_files", "ts.entry_files", label, 0),
|
|
531
|
+
componentExtensions: readComponentExtensions(ts, label),
|
|
532
|
+
disabledConventions: readDisabledConventions(ts, label),
|
|
510
533
|
injectionRegistrations: readDeclarations(ts, "injection_registrations", "ts.injection_registrations", label),
|
|
511
534
|
lifecycleContracts: readLifecycleContracts(ts, label),
|
|
512
535
|
serializers: readDeclarations(ts, "serializers", "ts.serializers", label),
|
|
@@ -667,13 +690,13 @@ export function resolve(inputs) {
|
|
|
667
690
|
return { config, provenance };
|
|
668
691
|
}
|
|
669
692
|
/**
|
|
670
|
-
* The
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
693
|
+
* The configuration the documents resolve to, every document read and refused as
|
|
694
|
+
* {@link resolve} refuses it, with no target kind required of them: which projects a
|
|
695
|
+
* run analyzes does not depend on the kind, so a question about the projects asks
|
|
696
|
+
* none. The kind is empty where no document names one.
|
|
674
697
|
*/
|
|
675
|
-
export function
|
|
676
|
-
return settleAll(inputs).config
|
|
698
|
+
export function resolveUnkinded(inputs) {
|
|
699
|
+
return settleAll(inputs).config;
|
|
677
700
|
}
|
|
678
701
|
/**
|
|
679
702
|
* Every setting the documents supply, each from the highest-ranked source carrying
|
|
@@ -714,6 +737,8 @@ function settleAll(inputs) {
|
|
|
714
737
|
ts: {
|
|
715
738
|
testFiles: at("ts.test_files", defaults.ts.testFiles, (doc) => doc.testFiles),
|
|
716
739
|
entryFiles: at("ts.entry_files", defaults.ts.entryFiles, (doc) => doc.entryFiles),
|
|
740
|
+
componentExtensions: at("ts.component_extensions", defaults.ts.componentExtensions, (doc) => doc.componentExtensions),
|
|
741
|
+
disabledConventions: at("ts.disabled_conventions", defaults.ts.disabledConventions, (doc) => doc.disabledConventions),
|
|
717
742
|
injectionRegistrations: at("ts.injection_registrations", defaults.ts.injectionRegistrations, (doc) => doc.injectionRegistrations),
|
|
718
743
|
lifecycleContracts: at("ts.lifecycle_contracts", defaults.ts.lifecycleContracts, (doc) => doc.lifecycleContracts),
|
|
719
744
|
serializers: at("ts.serializers", defaults.ts.serializers, (doc) => doc.serializers),
|
package/dist/src/roots.js
CHANGED
|
@@ -8,12 +8,20 @@
|
|
|
8
8
|
* under reachability and still a candidate under reference counting.
|
|
9
9
|
*/
|
|
10
10
|
import { aliasChains } from "./alias-chain.js";
|
|
11
|
+
import { conventionEntries } from "./conventions.js";
|
|
11
12
|
import { entryPoints } from "./entry-points.js";
|
|
12
13
|
import { globExpression } from "./glob.js";
|
|
13
14
|
import { nodeKey } from "./inventory.js";
|
|
14
15
|
import { byPosition } from "./position.js";
|
|
15
16
|
import { UNANSWERED } from "./query.js";
|
|
16
17
|
import { sourceFilesOf } from "./source-files.js";
|
|
18
|
+
import { typeQueryAliases } from "./type-query-alias.js";
|
|
19
|
+
/** The kind of root each role of a manifest entry makes. */
|
|
20
|
+
const ENTRY_KINDS = {
|
|
21
|
+
import: "manifest-entry",
|
|
22
|
+
run: "manifest-binary",
|
|
23
|
+
script: "script",
|
|
24
|
+
};
|
|
17
25
|
/** Whether a configured string carries either special character. */
|
|
18
26
|
function isPattern(text) {
|
|
19
27
|
return text.includes("*") || text.includes("?");
|
|
@@ -117,18 +125,23 @@ export function roots(project, held, targetRoot, options) {
|
|
|
117
125
|
entered.push({ file, kind, source, exports });
|
|
118
126
|
};
|
|
119
127
|
const published = [];
|
|
120
|
-
const
|
|
121
|
-
entry
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
128
|
+
const entries = [
|
|
129
|
+
...options.manifest.entries.map((entry) => ({
|
|
130
|
+
entry,
|
|
131
|
+
publishes: entry.published &&
|
|
132
|
+
(options.manifest.declaresExports ? entry.member.startsWith("exports") : true),
|
|
133
|
+
})),
|
|
134
|
+
...(options.workspaceEntries ?? []).map((entry) => ({ entry, publishes: entry.published })),
|
|
135
|
+
];
|
|
136
|
+
for (const { entry, publishes } of entries) {
|
|
137
|
+
if (options.publishedAPI && publishes) {
|
|
125
138
|
published.push(...files.named(entry.path));
|
|
126
139
|
}
|
|
127
140
|
}
|
|
128
141
|
const publishedNames = new Set(published.map((file) => file.fileName));
|
|
129
|
-
for (const entry of
|
|
142
|
+
for (const { entry } of entries) {
|
|
130
143
|
for (const file of files.named(entry.path)) {
|
|
131
|
-
const kind = entry.role
|
|
144
|
+
const kind = ENTRY_KINDS[entry.role];
|
|
132
145
|
enter(file, kind, entry.member, !publishedNames.has(file.fileName));
|
|
133
146
|
}
|
|
134
147
|
}
|
|
@@ -140,9 +153,25 @@ export function roots(project, held, targetRoot, options) {
|
|
|
140
153
|
}
|
|
141
154
|
}
|
|
142
155
|
}
|
|
143
|
-
for (const
|
|
156
|
+
for (const { file, row } of options.conventions === undefined
|
|
157
|
+
? []
|
|
158
|
+
: conventionEntries(options.conventions, files)) {
|
|
159
|
+
enter(file, "convention", row, true);
|
|
160
|
+
}
|
|
161
|
+
for (const point of entryPoints(project, files, options.testFiles, options.host, targetRoot)) {
|
|
144
162
|
enter(point.file, point.rule, point.source, point.exports);
|
|
145
163
|
}
|
|
164
|
+
for (const file of files.byPath.values()) {
|
|
165
|
+
for (const alias of typeQueryAliases(file)) {
|
|
166
|
+
const source = alias.type.getText().replace(/\s+/gu, " ");
|
|
167
|
+
for (const node of [alias.declaration, ...alias.containers]) {
|
|
168
|
+
const id = declared.get(nodeKey(file, node));
|
|
169
|
+
if (id !== undefined) {
|
|
170
|
+
add(id, "type-query-alias", source);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
146
175
|
const { tables: exportsOf, answered } = exportTables(project, held, [
|
|
147
176
|
...entered.filter((entry) => entry.exports).map((entry) => entry.file),
|
|
148
177
|
...published,
|
package/dist/src/run.js
CHANGED
|
@@ -3,6 +3,7 @@ import { DiscoveryError, renderDiagnostic } from "./discover.js";
|
|
|
3
3
|
import { EMITTERS } from "./findings/emitters.js";
|
|
4
4
|
import { joinPath, resolvePath } from "./paths.js";
|
|
5
5
|
import { readScope, ScopeError, scopeForDir } from "./scope.js";
|
|
6
|
+
import { SetupError, setupLine } from "./setup-failure.js";
|
|
6
7
|
import { openEngine } from "./session.js";
|
|
7
8
|
import { printConfigVerb } from "./verbs/print-config.js";
|
|
8
9
|
import { printProjectsVerb } from "./verbs/print-projects.js";
|
|
@@ -158,7 +159,7 @@ function scopeOf(host, options) {
|
|
|
158
159
|
* `openClient` opens the compiler client; a caller that watches what a run reads
|
|
159
160
|
* supplies its own of either.
|
|
160
161
|
*/
|
|
161
|
-
export function run(args, out, err, host, openClient =
|
|
162
|
+
export function run(args, out, err, host, openClient = openEngine) {
|
|
162
163
|
const editing = sourceEditOption(args);
|
|
163
164
|
if (editing !== undefined) {
|
|
164
165
|
err.write(`deadset-ts: ${editing} is not supported: deadset-ts reports and never edits a source file\n`);
|
|
@@ -193,8 +194,11 @@ function invoke(verb, own, args, streams, openClient) {
|
|
|
193
194
|
scope: () => scopeOf(streams.host, read()),
|
|
194
195
|
option: (name) => read().named.get(name),
|
|
195
196
|
repeated: (name) => read().repeated.get(name) ?? [],
|
|
196
|
-
openClient: (
|
|
197
|
-
opened = openClient(
|
|
197
|
+
openClient: (config) => {
|
|
198
|
+
opened = openClient({
|
|
199
|
+
collectTiming: false,
|
|
200
|
+
components: { extensions: config.ts.componentExtensions, host: streams.host },
|
|
201
|
+
});
|
|
198
202
|
return opened;
|
|
199
203
|
},
|
|
200
204
|
emitters: EMITTERS,
|
|
@@ -219,6 +223,12 @@ function refuse(error, err) {
|
|
|
219
223
|
err.write(`deadset-ts: ${error.message}\n`);
|
|
220
224
|
return EXIT_FAILURE;
|
|
221
225
|
}
|
|
226
|
+
if (error instanceof SetupError) {
|
|
227
|
+
for (const failure of error.failures) {
|
|
228
|
+
err.write(`${setupLine(failure)}\n`);
|
|
229
|
+
}
|
|
230
|
+
return EXIT_FAILURE;
|
|
231
|
+
}
|
|
222
232
|
if (error instanceof ScopeError) {
|
|
223
233
|
err.write(`deadset-ts: ${error.message}\n`);
|
|
224
234
|
return EXIT_FAILURE;
|
package/dist/src/schema.js
CHANGED