@cplieger/deadset-ts 3.2.0 → 3.3.1
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 +2 -2
- package/dist/src/analysis.js +14 -2
- package/dist/src/decorator.js +64 -0
- package/dist/src/exempt-classes.js +57 -10
- package/dist/src/exempt.js +23 -3
- package/dist/src/findings/dependencies-and-module-machinery.js +2 -0
- package/dist/src/findings/emitter.js +1 -0
- package/dist/src/findings/emitters.js +19 -0
- package/dist/src/findings/interfaces.js +2 -0
- package/dist/src/findings/intra-function.js +2 -0
- package/dist/src/findings/non-code-artifacts.js +2 -0
- package/dist/src/findings/reads-and-writes.js +2 -0
- package/dist/src/findings/self-check.js +2 -0
- package/dist/src/findings/unused-declarations.js +2 -0
- package/dist/src/findings/visibility-narrowing.js +2 -0
- package/dist/src/interface-satisfaction.js +310 -0
- package/dist/src/member-names.js +76 -0
- package/dist/src/references.js +22 -3
- package/dist/src/reflective-lookup.js +96 -0
- package/dist/src/run.js +60 -176
- package/dist/src/template-field.js +173 -0
- package/dist/src/verbs/print-config.js +9 -0
- package/dist/src/verbs/print-projects.js +34 -0
- package/dist/src/verbs/print-retained.js +23 -0
- package/dist/src/verbs/print-roots.js +36 -0
- package/dist/src/verbs/verb.js +5 -0
- package/dist/src/verbs/version.js +7 -0
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +24 -6
- package/src/decorator.ts +74 -0
- package/src/exempt-classes.ts +75 -12
- package/src/exempt.ts +29 -3
- package/src/findings/dependencies-and-module-machinery.ts +4 -0
- package/src/findings/emitter.ts +19 -0
- package/src/findings/emitters.ts +21 -0
- package/src/findings/interfaces.ts +4 -0
- package/src/findings/intra-function.ts +4 -0
- package/src/findings/non-code-artifacts.ts +4 -0
- package/src/findings/reads-and-writes.ts +4 -0
- package/src/findings/self-check.ts +4 -0
- package/src/findings/unused-declarations.ts +4 -0
- package/src/findings/visibility-narrowing.ts +4 -0
- package/src/interface-satisfaction.ts +417 -0
- package/src/member-names.ts +89 -0
- package/src/references.ts +41 -6
- package/src/reflective-lookup.ts +127 -0
- package/src/run.ts +64 -200
- package/src/template-field.ts +214 -0
- package/src/verbs/print-config.ts +10 -0
- package/src/verbs/print-projects.ts +35 -0
- package/src/verbs/print-retained.ts +24 -0
- package/src/verbs/print-roots.ts +38 -0
- package/src/verbs/verb.ts +38 -0
- package/src/verbs/version.ts +8 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The members of one project's inventory by the name each is written with, for the
|
|
3
|
+
* classes whose evidence is a name rather than a type relation: a template and a
|
|
4
|
+
* string key both hold a name and nothing else, so each holds back every member of
|
|
5
|
+
* that name its own mechanism can reach.
|
|
6
|
+
*/
|
|
7
|
+
import { isIdentifier, isNumericLiteral, isPrivateIdentifier, isStringLiteral, } from "@typescript/native/unstable/ast";
|
|
8
|
+
import { nodeKey } from "./inventory.js";
|
|
9
|
+
/** The kinds of declaration a member name can name. */
|
|
10
|
+
const MEMBER_KINDS = new Set([
|
|
11
|
+
"method",
|
|
12
|
+
"class-member",
|
|
13
|
+
"interface-method",
|
|
14
|
+
"type-member",
|
|
15
|
+
"enum-member",
|
|
16
|
+
]);
|
|
17
|
+
/**
|
|
18
|
+
* The name a member declaration is written with: an identifier's text, a private
|
|
19
|
+
* name's text without its `#`, a string key's value and a numeric key's text. A
|
|
20
|
+
* private name is indexed under the name a template or a string would spell, so the
|
|
21
|
+
* rule that no such evidence holds it back is the framework's alone. A computed key
|
|
22
|
+
* names nothing a string can be equal to before the program runs.
|
|
23
|
+
*/
|
|
24
|
+
function writtenName(name) {
|
|
25
|
+
if (name === undefined) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
if (isPrivateIdentifier(name)) {
|
|
29
|
+
return name.text.replace(/^#/u, "");
|
|
30
|
+
}
|
|
31
|
+
if (isIdentifier(name)) {
|
|
32
|
+
return name.text;
|
|
33
|
+
}
|
|
34
|
+
if (isStringLiteral(name) || isNumericLiteral(name)) {
|
|
35
|
+
return name.text;
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Every member the inventory holds, keyed by the name it is written with, each
|
|
41
|
+
* member once and in inventory order. A getter and a setter of one name are one
|
|
42
|
+
* member.
|
|
43
|
+
*/
|
|
44
|
+
export function memberNames(files, held) {
|
|
45
|
+
const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
|
|
46
|
+
const named = new Map();
|
|
47
|
+
for (const file of files) {
|
|
48
|
+
const visit = (node) => {
|
|
49
|
+
const id = held.declarations.get(nodeKey(file, node));
|
|
50
|
+
const symbol = id === undefined ? undefined : byId.get(id);
|
|
51
|
+
if (symbol !== undefined && MEMBER_KINDS.has(symbol.kind)) {
|
|
52
|
+
const name = writtenName(node.name);
|
|
53
|
+
if (name !== undefined) {
|
|
54
|
+
named.set(symbol.id, name);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
node.forEachChild(visit);
|
|
58
|
+
};
|
|
59
|
+
visit(file);
|
|
60
|
+
}
|
|
61
|
+
const index = new Map();
|
|
62
|
+
for (const symbol of held.symbols) {
|
|
63
|
+
const name = named.get(symbol.id);
|
|
64
|
+
if (name === undefined) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const members = index.get(name);
|
|
68
|
+
if (members === undefined) {
|
|
69
|
+
index.set(name, [symbol]);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
members.push(symbol);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return index;
|
|
76
|
+
}
|
package/dist/src/references.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, 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, isExternalModuleReference, isForInStatement, isForOfStatement, isImportDeclaration, isImportEqualsDeclaration, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
2
2
|
import { aliasChains } from "./alias-chain.js";
|
|
3
3
|
import { globExpression } from "./glob.js";
|
|
4
4
|
import { nodeKey } from "./inventory.js";
|
|
@@ -207,6 +207,7 @@ function sitesOf(file, declarations, fileId) {
|
|
|
207
207
|
const found = [];
|
|
208
208
|
const links = [];
|
|
209
209
|
const evaluations = [];
|
|
210
|
+
const decorated = [];
|
|
210
211
|
const writes = new Set();
|
|
211
212
|
const declared = new Set();
|
|
212
213
|
const shorthands = new Map();
|
|
@@ -251,12 +252,17 @@ function sitesOf(file, declarations, fileId) {
|
|
|
251
252
|
const outer = enclosing;
|
|
252
253
|
if (held !== undefined) {
|
|
253
254
|
enclosing = held;
|
|
255
|
+
for (const modifier of node.modifiers ?? []) {
|
|
256
|
+
if (isDecorator(modifier)) {
|
|
257
|
+
decorated.push({ decorator: modifier, declaration: held });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
254
260
|
}
|
|
255
261
|
node.forEachChild(visit);
|
|
256
262
|
enclosing = outer;
|
|
257
263
|
};
|
|
258
264
|
file.forEachChild(visit);
|
|
259
|
-
return { uses: found, links, evaluations };
|
|
265
|
+
return { uses: found, links, evaluations, decorated };
|
|
260
266
|
}
|
|
261
267
|
/**
|
|
262
268
|
* The name one test-file rule takes. A rule's name is a word list the report's own
|
|
@@ -336,7 +342,20 @@ export function references(project, held, targetRoot, options) {
|
|
|
336
342
|
if (test) {
|
|
337
343
|
tests.push(path);
|
|
338
344
|
}
|
|
339
|
-
const { uses, links, evaluations } = sitesOf(file, declarations, declaredAt(file) ?? "");
|
|
345
|
+
const { uses, links, evaluations, decorated } = sitesOf(file, declarations, declaredAt(file) ?? "");
|
|
346
|
+
// A decorator is written inside the declaration it is attached to and receives it,
|
|
347
|
+
// so the declaration is used where the decorator is written, whatever the
|
|
348
|
+
// decorator does with it.
|
|
349
|
+
for (const { decorator, declaration } of decorated) {
|
|
350
|
+
found.push({
|
|
351
|
+
from: declaration,
|
|
352
|
+
to: declaration,
|
|
353
|
+
position: renderPosition(file, targetRoot, decorator.getStart()),
|
|
354
|
+
use: "decorator",
|
|
355
|
+
resolution: "syntax",
|
|
356
|
+
test,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
340
359
|
// One batch per capped run of the file's name nodes, the re-exports' nodes after
|
|
341
360
|
// the uses and the evaluated modules' specifiers last, each node once: a bare star
|
|
342
361
|
// re-export's specifier is a link and an evaluation both. A shorthand is left out
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reflective-lookup class: a member is held back, at the lowest confidence, when
|
|
3
|
+
* a string literal equal to its name is the key of an element access or the property
|
|
4
|
+
* key of a `Reflect.get`, `Reflect.set` or `Reflect.has` call, because the member is
|
|
5
|
+
* then read by a name the program computes nowhere the reference pass can follow.
|
|
6
|
+
*
|
|
7
|
+
* The evidence is a string beside a lookup rather than a type relation, so the
|
|
8
|
+
* string reaches every member of that name on any type, static members included,
|
|
9
|
+
* and every record names the string's own position. A key that is not literal text
|
|
10
|
+
* holds nothing back: a computed name is the case no exact rule serves. `Reflect` is
|
|
11
|
+
* the global one only: a local declaration of that name is a different object.
|
|
12
|
+
*/
|
|
13
|
+
import { isCallExpression, isElementAccessExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isPropertyAccessExpression, isStringLiteral, } from "@typescript/native/unstable/ast";
|
|
14
|
+
import { memberNames } from "./member-names.js";
|
|
15
|
+
import { renderPosition } from "./position.js";
|
|
16
|
+
/** The `Reflect` methods whose second argument is a property key. */
|
|
17
|
+
const REFLECT_KEYED = new Set(["get", "set", "has"]);
|
|
18
|
+
/** The text one key denotes, where it is literal text. */
|
|
19
|
+
function literalText(node) {
|
|
20
|
+
if (node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node))) {
|
|
21
|
+
return node.text;
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
/** Every lookup one file writes with a literal key, in source order. */
|
|
26
|
+
function lookupsOf(file) {
|
|
27
|
+
const found = [];
|
|
28
|
+
const visit = (node) => {
|
|
29
|
+
if (isElementAccessExpression(node)) {
|
|
30
|
+
const name = literalText(node.argumentExpression);
|
|
31
|
+
if (name !== undefined) {
|
|
32
|
+
found.push({
|
|
33
|
+
key: node.argumentExpression,
|
|
34
|
+
name,
|
|
35
|
+
callee: `[${JSON.stringify(name)}]`,
|
|
36
|
+
reflect: undefined,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else if (isCallExpression(node) && isPropertyAccessExpression(node.expression)) {
|
|
41
|
+
const target = node.expression.expression;
|
|
42
|
+
const method = node.expression.name;
|
|
43
|
+
const key = node.arguments[1];
|
|
44
|
+
const name = literalText(key);
|
|
45
|
+
if (isIdentifier(target) &&
|
|
46
|
+
target.text === "Reflect" &&
|
|
47
|
+
isIdentifier(method) &&
|
|
48
|
+
REFLECT_KEYED.has(method.text) &&
|
|
49
|
+
key !== undefined &&
|
|
50
|
+
name !== undefined) {
|
|
51
|
+
found.push({ key, name, callee: `Reflect.${method.text}`, reflect: target });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
node.forEachChild(visit);
|
|
55
|
+
};
|
|
56
|
+
file.forEachChild(visit);
|
|
57
|
+
return found;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The reflective-lookup detector: every member whose name a literal key of a lookup
|
|
61
|
+
* spells, recorded at the key.
|
|
62
|
+
*/
|
|
63
|
+
export function reflectiveLookup(input) {
|
|
64
|
+
const { project, held, targetRoot } = input;
|
|
65
|
+
const files = project.ownSourceFiles();
|
|
66
|
+
const lookups = files.flatMap(lookupsOf);
|
|
67
|
+
if (lookups.length === 0) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
// Whether each written `Reflect` is the global one, asked in one batch: a
|
|
71
|
+
// declaration of that name in one of the project's own files shadows it.
|
|
72
|
+
const ownFiles = new Set(files.map((file) => file.fileName));
|
|
73
|
+
const reflects = lookups.flatMap((lookup) => lookup.reflect === undefined ? [] : [lookup.reflect]);
|
|
74
|
+
const global = new Set();
|
|
75
|
+
if (reflects.length > 0) {
|
|
76
|
+
project.symbolsAt(reflects.map((node) => project.handle(node))).forEach((symbol, index) => {
|
|
77
|
+
const node = reflects[index];
|
|
78
|
+
const declaredHere = symbol?.declarations.some((declaration) => ownFiles.has(declaration.path));
|
|
79
|
+
if (node !== undefined && declaredHere === false) {
|
|
80
|
+
global.add(node);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const members = memberNames(files, held);
|
|
85
|
+
const found = [];
|
|
86
|
+
for (const lookup of lookups) {
|
|
87
|
+
if (lookup.reflect !== undefined && !global.has(lookup.reflect)) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const site = renderPosition(lookup.key.getSourceFile(), targetRoot, lookup.key.getStart());
|
|
91
|
+
for (const member of members.get(lookup.name) ?? []) {
|
|
92
|
+
found.push({ id: member.id, detail: `looked up by ${lookup.callee}`, site });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return found;
|
|
96
|
+
}
|
package/dist/src/run.js
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
import { runRoots, runSweep } from "./analysis.js";
|
|
2
1
|
import { ConfigError, REPOSITORY_DOCUMENT } from "./config.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { DiscoveryError, renderDiagnostic } from "./discover.js";
|
|
3
|
+
import { EMITTERS } from "./findings/emitters.js";
|
|
5
4
|
import { joinPath, resolvePath } from "./paths.js";
|
|
6
|
-
import { printConfig } from "./print.js";
|
|
7
|
-
import { resolve, resolveMatrix } from "./resolve.js";
|
|
8
5
|
import { readScope, ScopeError, scopeForDir } from "./scope.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
import { openEngine } from "./session.js";
|
|
7
|
+
import { printConfigVerb } from "./verbs/print-config.js";
|
|
8
|
+
import { printProjectsVerb } from "./verbs/print-projects.js";
|
|
9
|
+
import { printRetainedVerb } from "./verbs/print-retained.js";
|
|
10
|
+
import { printRootsVerb } from "./verbs/print-roots.js";
|
|
11
|
+
import { EXIT_FAILURE, EXIT_USAGE } from "./verbs/verb.js";
|
|
12
|
+
import { versionVerb } from "./verbs/version.js";
|
|
13
|
+
/**
|
|
14
|
+
* Every verb this command answers, in the order the usage text lists them. A verb
|
|
15
|
+
* with no `run` is listed, and invoking it is a usage error.
|
|
16
|
+
*/
|
|
16
17
|
const VERBS = [
|
|
17
|
-
"analyze",
|
|
18
|
-
"explain",
|
|
19
|
-
"print-config",
|
|
20
|
-
"print-projects",
|
|
21
|
-
"print-roots",
|
|
22
|
-
"print-retained",
|
|
23
|
-
"describe",
|
|
24
|
-
"version",
|
|
18
|
+
{ name: "analyze" },
|
|
19
|
+
{ name: "explain" },
|
|
20
|
+
{ name: "print-config", run: printConfigVerb },
|
|
21
|
+
{ name: "print-projects", run: printProjectsVerb },
|
|
22
|
+
{ name: "print-roots", run: printRootsVerb },
|
|
23
|
+
{ name: "print-retained", run: printRetainedVerb },
|
|
24
|
+
{ name: "describe" },
|
|
25
|
+
{ name: "version", run: versionVerb },
|
|
25
26
|
];
|
|
26
|
-
const USAGE = `usage: deadset-ts <verb> [options]\nverbs: ${VERBS.join(", ")}\n`;
|
|
27
|
+
const USAGE = `usage: deadset-ts <verb> [options]\nverbs: ${VERBS.map((verb) => verb.name).join(", ")}\n`;
|
|
27
28
|
/**
|
|
28
29
|
* The tokens whose presence in an option's name makes that option a request to
|
|
29
30
|
* edit a source file. The set is closed, and a token is matched in any position of
|
|
@@ -31,15 +32,11 @@ const USAGE = `usage: deadset-ts <verb> [options]\nverbs: ${VERBS.join(", ")}\n`
|
|
|
31
32
|
*/
|
|
32
33
|
const SOURCE_EDIT_TOKENS = ["fix", "edit", "delete", "rewrite"];
|
|
33
34
|
/**
|
|
34
|
-
* Maps an option name to the setting
|
|
35
|
-
* option, supplies the document resolution reads, and gives
|
|
36
|
-
* setting's provenance names.
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* command line, and its path is the setting of the Contract's configuration schema
|
|
40
|
-
* that option supplies. The two are mapped here and never derived from one
|
|
41
|
-
* another, so this table is the authority for both: a setting reaches the command
|
|
42
|
-
* line only by an entry, and a path names a key the schema declares.
|
|
35
|
+
* Maps an option name to the setting of the Contract's configuration schema it supplies,
|
|
36
|
+
* so one entry registers the option, supplies the document resolution reads, and gives
|
|
37
|
+
* the spelling the setting's provenance names. The name is this command's vocabulary and
|
|
38
|
+
* is never derived from the path: a setting reaches the command line only by an entry,
|
|
39
|
+
* and a path names a key the schema declares.
|
|
43
40
|
*/
|
|
44
41
|
export const SETTING_OPTIONS = new Map([
|
|
45
42
|
["min-confidence", "analysis.min_confidence"],
|
|
@@ -139,20 +136,13 @@ function scopeOf(host, options) {
|
|
|
139
136
|
return scopeForDir(host, options.named.get("target") ?? ".");
|
|
140
137
|
}
|
|
141
138
|
/**
|
|
142
|
-
* Runs the
|
|
143
|
-
*
|
|
144
|
-
* error or a configuration
|
|
145
|
-
* answer. It writes nothing outside `out` and `err` and never exits the
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* analyzer reports and never edits a source file.
|
|
150
|
-
*
|
|
151
|
-
* `host` is the platform the run reads, the version it reports included;
|
|
152
|
-
* `openClient` opens the compiler client a verb that reads projects needs.
|
|
153
|
-
* Both are parameters because both are the platform, which no module below this one
|
|
154
|
-
* names: the command entry binds them, and a caller that wants to watch what a run
|
|
155
|
-
* reads supplies its own.
|
|
139
|
+
* Runs the command line over `args`, the arguments after the program name, and returns
|
|
140
|
+
* the exit code: 0 for a completed verb, 1 for a finding that fails the run, 2 for a
|
|
141
|
+
* usage error, a requested source edit or a refused configuration, 3 for a failure that
|
|
142
|
+
* produced no answer. It writes nothing outside `out` and `err` and never exits the
|
|
143
|
+
* process. `host` is the platform the run reads, the version it reports included, and
|
|
144
|
+
* `openClient` opens the compiler client; a caller that watches what a run reads
|
|
145
|
+
* supplies its own of either.
|
|
156
146
|
*/
|
|
157
147
|
export function run(args, out, err, host, openClient = (collectTiming) => openEngine({ collectTiming })) {
|
|
158
148
|
const editing = sourceEditOption(args);
|
|
@@ -161,151 +151,45 @@ export function run(args, out, err, host, openClient = (collectTiming) => openEn
|
|
|
161
151
|
err.write(USAGE);
|
|
162
152
|
return EXIT_USAGE;
|
|
163
153
|
}
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return
|
|
168
|
-
}
|
|
169
|
-
if (verb === "print-config") {
|
|
170
|
-
return printConfigVerb(args.slice(1), out, err, host);
|
|
171
|
-
}
|
|
172
|
-
if (verb === "print-projects") {
|
|
173
|
-
return printProjectsVerb(args.slice(1), out, err, host, openClient);
|
|
174
|
-
}
|
|
175
|
-
if (verb === "print-roots") {
|
|
176
|
-
return printRootsVerb(args.slice(1), out, err, host, openClient);
|
|
154
|
+
const name = args[0];
|
|
155
|
+
const verb = VERBS.find((listed) => listed.name === name);
|
|
156
|
+
if (verb?.run !== undefined) {
|
|
157
|
+
return invoke(verb.run, args.slice(1), { out, err, host }, openClient);
|
|
177
158
|
}
|
|
178
|
-
if (verb
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
if (verb !== undefined && VERBS.includes(verb)) {
|
|
182
|
-
err.write(`deadset-ts: ${verb} is not implemented\n`);
|
|
159
|
+
if (verb !== undefined) {
|
|
160
|
+
err.write(`deadset-ts: ${verb.name} is not implemented\n`);
|
|
183
161
|
return EXIT_USAGE;
|
|
184
162
|
}
|
|
185
|
-
if (
|
|
186
|
-
err.write(`deadset-ts: unknown verb ${JSON.stringify(
|
|
163
|
+
if (name !== undefined) {
|
|
164
|
+
err.write(`deadset-ts: unknown verb ${JSON.stringify(name)}\n`);
|
|
187
165
|
}
|
|
188
166
|
err.write(USAGE);
|
|
189
167
|
return EXIT_USAGE;
|
|
190
168
|
}
|
|
191
|
-
/** Writes the resolved configuration with the source of every setting. */
|
|
192
|
-
function printConfigVerb(args, out, err, host) {
|
|
193
|
-
try {
|
|
194
|
-
const options = readOptions(args);
|
|
195
|
-
const { config, provenance } = resolve(inputsOf(host, options));
|
|
196
|
-
out.write(printConfig(config, provenance));
|
|
197
|
-
return EXIT_CLEAN;
|
|
198
|
-
}
|
|
199
|
-
catch (error) {
|
|
200
|
-
return refuse(error, err);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
169
|
/**
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* target root.
|
|
170
|
+
* Runs one verb over the arguments after its name. The options are read the first
|
|
171
|
+
* time the verb asks for what they name, and a refusal the verb throws is written
|
|
172
|
+
* and turned into its exit code once the client the verb opened is closed.
|
|
208
173
|
*/
|
|
209
|
-
function
|
|
210
|
-
let
|
|
174
|
+
function invoke(verb, args, streams, openClient) {
|
|
175
|
+
let options;
|
|
176
|
+
const read = () => (options ??= readOptions(args));
|
|
177
|
+
let opened;
|
|
211
178
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
failures.push(...errors.map(renderDiagnostic));
|
|
222
|
-
return ids.get(project.configFile) ?? project.configFile;
|
|
179
|
+
return verb({
|
|
180
|
+
...streams,
|
|
181
|
+
inputs: () => inputsOf(streams.host, read()),
|
|
182
|
+
scope: () => scopeOf(streams.host, read()),
|
|
183
|
+
openClient: (collectTiming) => {
|
|
184
|
+
opened = openClient(collectTiming);
|
|
185
|
+
return opened;
|
|
186
|
+
},
|
|
187
|
+
emitters: EMITTERS,
|
|
223
188
|
});
|
|
224
|
-
if (failures.length > 0) {
|
|
225
|
-
for (const line of failures) {
|
|
226
|
-
err.write(`${line}\n`);
|
|
227
|
-
}
|
|
228
|
-
err.write(`deadset-ts: ${String(failures.length)} error(s); no answer was produced\n`);
|
|
229
|
-
return EXIT_FAILURE;
|
|
230
|
-
}
|
|
231
|
-
for (const id of projects) {
|
|
232
|
-
out.write(`${id}\n`);
|
|
233
|
-
}
|
|
234
|
-
return EXIT_CLEAN;
|
|
235
|
-
}
|
|
236
|
-
catch (error) {
|
|
237
|
-
engine?.close();
|
|
238
|
-
return refuse(error, err);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* One root as a line: the declaration's reference, why it is a root, and the string
|
|
243
|
-
* that named it, separated by tabs. A run of several configurations adds the ones
|
|
244
|
-
* whose project holds the root as a fourth field, and writes the third present and
|
|
245
|
-
* empty where no string named the root, so every line of one run holds the same
|
|
246
|
-
* fields.
|
|
247
|
-
*/
|
|
248
|
-
function rootLine(root, several) {
|
|
249
|
-
const line = `${root.ref}\t${root.kind}`;
|
|
250
|
-
if (several) {
|
|
251
|
-
return `${line}\t${root.source}\t${root.configurations.join(" ")}`;
|
|
252
|
-
}
|
|
253
|
-
return root.source === "" ? line : `${line}\t${root.source}`;
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Writes the root set of the run, one root per line, and then names on the error
|
|
257
|
-
* stream, by its issue kind, every configured root or pattern that named nothing;
|
|
258
|
-
* one such string fails the run. The line shape is this command's own rather than a
|
|
259
|
-
* Contract format, so that sort and cut read it.
|
|
260
|
-
*/
|
|
261
|
-
function printRootsVerb(args, out, err, host, openClient) {
|
|
262
|
-
let engine;
|
|
263
|
-
try {
|
|
264
|
-
const options = readOptions(args);
|
|
265
|
-
const { config, provenance } = resolve(inputsOf(host, options));
|
|
266
|
-
const scope = scopeOf(host, options);
|
|
267
|
-
engine = openClient(false);
|
|
268
|
-
const answer = runRoots(engine, host, scope, config, provenance);
|
|
269
|
-
const several = answer.configurations.length > 1;
|
|
270
|
-
for (const root of answer.roots) {
|
|
271
|
-
out.write(`${rootLine(root, several)}\n`);
|
|
272
|
-
}
|
|
273
|
-
for (const finding of answer.findings) {
|
|
274
|
-
err.write(`${finding.code}: roots.patterns names nothing: ${finding.symbol.ref}\n`);
|
|
275
|
-
}
|
|
276
|
-
return answer.findings.length > 0 ? EXIT_FINDINGS : EXIT_CLEAN;
|
|
277
|
-
}
|
|
278
|
-
catch (error) {
|
|
279
|
-
engine?.close();
|
|
280
|
-
return refuse(error, err);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* Writes every declaration an exemption held back in the production sweep a report is
|
|
285
|
-
* built from, one per line, and nothing where no exemption held one back. One fact
|
|
286
|
-
* several configurations found is one record, so the line names no configuration. The
|
|
287
|
-
* line shape is this command's own rather than a Contract format, so that sort and cut
|
|
288
|
-
* read it.
|
|
289
|
-
*/
|
|
290
|
-
function printRetainedVerb(args, out, err, host, openClient) {
|
|
291
|
-
let engine;
|
|
292
|
-
try {
|
|
293
|
-
const options = readOptions(args);
|
|
294
|
-
const { config } = resolve(inputsOf(host, options));
|
|
295
|
-
const scope = scopeOf(host, options);
|
|
296
|
-
engine = openClient(false);
|
|
297
|
-
const swept = runSweep(engine, host, scope, config, {
|
|
298
|
-
marked: [],
|
|
299
|
-
mode: { production: true },
|
|
300
|
-
});
|
|
301
|
-
for (const line of retainedLines(swept.matrix.union.symbols, swept.retained)) {
|
|
302
|
-
out.write(`${line}\n`);
|
|
303
|
-
}
|
|
304
|
-
return EXIT_CLEAN;
|
|
305
189
|
}
|
|
306
190
|
catch (error) {
|
|
307
|
-
|
|
308
|
-
return refuse(error, err);
|
|
191
|
+
opened?.close();
|
|
192
|
+
return refuse(error, streams.err);
|
|
309
193
|
}
|
|
310
194
|
}
|
|
311
195
|
/** Turns one refusal into the exit code the Contract's table gives it. */
|