@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
package/src/references.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
isArrayLiteralExpression,
|
|
3
3
|
isBinaryExpression,
|
|
4
4
|
isCallExpression,
|
|
5
|
+
isDecorator,
|
|
5
6
|
isDeleteExpression,
|
|
6
7
|
isElementAccessExpression,
|
|
7
8
|
isExportDeclaration,
|
|
@@ -53,9 +54,11 @@ import type { ProjectView } from "./session.ts";
|
|
|
53
54
|
/**
|
|
54
55
|
* How one reference uses the declaration it names. An `evaluation` names a file: the
|
|
55
56
|
* place it is written imports the module, which runs the module's top level and reads
|
|
56
|
-
* none of what it exports. A `read` of a file reads the module's namespace.
|
|
57
|
+
* none of what it exports. A `read` of a file reads the module's namespace. A
|
|
58
|
+
* `decorator` names the declaration a decorator expression is attached to, which the
|
|
59
|
+
* decorator receives when the class is defined.
|
|
57
60
|
*/
|
|
58
|
-
export type Use = "read" | "write" | "evaluation";
|
|
61
|
+
export type Use = "read" | "write" | "evaluation" | "decorator";
|
|
59
62
|
|
|
60
63
|
/**
|
|
61
64
|
* Which accessor answered for one reference, so that the cost of a run is attributable
|
|
@@ -67,8 +70,9 @@ export type Use = "read" | "write" | "evaluation";
|
|
|
67
70
|
* - `shorthand` is the per-node lookup for `{ a }`, whose name resolves to the property
|
|
68
71
|
* the literal declares rather than to the declaration that name reads.
|
|
69
72
|
* - `alias` is a step along an import or export chain, from one link to what it names.
|
|
73
|
+
* - `syntax` is the tree alone, which says what a decorator is attached to.
|
|
70
74
|
*/
|
|
71
|
-
export type Resolution = "batch" | "resolved-symbol" | "shorthand" | "alias";
|
|
75
|
+
export type Resolution = "batch" | "resolved-symbol" | "shorthand" | "alias" | "syntax";
|
|
72
76
|
|
|
73
77
|
/** One use of one declaration by one declaration. */
|
|
74
78
|
export interface Reference {
|
|
@@ -318,11 +322,18 @@ interface Evaluation {
|
|
|
318
322
|
readonly from: string;
|
|
319
323
|
}
|
|
320
324
|
|
|
321
|
-
/**
|
|
325
|
+
/** One decorator expression, and the declaration it is attached to. */
|
|
326
|
+
interface Decorated {
|
|
327
|
+
readonly decorator: Node;
|
|
328
|
+
readonly declaration: string;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Everything one file's walk found to resolve, and the decorators it needs no resolution for. */
|
|
322
332
|
interface FileSites {
|
|
323
333
|
readonly uses: readonly Site[];
|
|
324
334
|
readonly links: readonly Link[];
|
|
325
335
|
readonly evaluations: readonly Evaluation[];
|
|
336
|
+
readonly decorated: readonly Decorated[];
|
|
326
337
|
}
|
|
327
338
|
|
|
328
339
|
/** Whether one node is a string literal or a template literal with no substitution. */
|
|
@@ -418,6 +429,7 @@ function sitesOf(
|
|
|
418
429
|
const found: Site[] = [];
|
|
419
430
|
const links: Link[] = [];
|
|
420
431
|
const evaluations: Evaluation[] = [];
|
|
432
|
+
const decorated: Decorated[] = [];
|
|
421
433
|
const writes = new Set<number>();
|
|
422
434
|
const declared = new Set<number>();
|
|
423
435
|
const shorthands = new Map<number, Node>();
|
|
@@ -464,13 +476,18 @@ function sitesOf(
|
|
|
464
476
|
const outer = enclosing;
|
|
465
477
|
if (held !== undefined) {
|
|
466
478
|
enclosing = held;
|
|
479
|
+
for (const modifier of (node as { readonly modifiers?: readonly Node[] }).modifiers ?? []) {
|
|
480
|
+
if (isDecorator(modifier)) {
|
|
481
|
+
decorated.push({ decorator: modifier, declaration: held });
|
|
482
|
+
}
|
|
483
|
+
}
|
|
467
484
|
}
|
|
468
485
|
node.forEachChild(visit);
|
|
469
486
|
enclosing = outer;
|
|
470
487
|
};
|
|
471
488
|
|
|
472
489
|
file.forEachChild(visit);
|
|
473
|
-
return { uses: found, links, evaluations };
|
|
490
|
+
return { uses: found, links, evaluations, decorated };
|
|
474
491
|
}
|
|
475
492
|
|
|
476
493
|
/**
|
|
@@ -567,7 +584,25 @@ export function references<Brand>(
|
|
|
567
584
|
if (test) {
|
|
568
585
|
tests.push(path);
|
|
569
586
|
}
|
|
570
|
-
const { uses, links, evaluations } = sitesOf(
|
|
587
|
+
const { uses, links, evaluations, decorated } = sitesOf(
|
|
588
|
+
file,
|
|
589
|
+
declarations,
|
|
590
|
+
declaredAt(file) ?? "",
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
// A decorator is written inside the declaration it is attached to and receives it,
|
|
594
|
+
// so the declaration is used where the decorator is written, whatever the
|
|
595
|
+
// decorator does with it.
|
|
596
|
+
for (const { decorator, declaration } of decorated) {
|
|
597
|
+
found.push({
|
|
598
|
+
from: declaration,
|
|
599
|
+
to: declaration,
|
|
600
|
+
position: renderPosition(file, targetRoot, decorator.getStart()),
|
|
601
|
+
use: "decorator",
|
|
602
|
+
resolution: "syntax",
|
|
603
|
+
test,
|
|
604
|
+
});
|
|
605
|
+
}
|
|
571
606
|
|
|
572
607
|
// One batch per capped run of the file's name nodes, the re-exports' nodes after
|
|
573
608
|
// the uses and the evaluated modules' specifiers last, each node once: a bare star
|
|
@@ -0,0 +1,127 @@
|
|
|
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
|
+
|
|
14
|
+
import {
|
|
15
|
+
isCallExpression,
|
|
16
|
+
isElementAccessExpression,
|
|
17
|
+
isIdentifier,
|
|
18
|
+
isNoSubstitutionTemplateLiteral,
|
|
19
|
+
isPropertyAccessExpression,
|
|
20
|
+
isStringLiteral,
|
|
21
|
+
type Node,
|
|
22
|
+
type SourceFile,
|
|
23
|
+
} from "@typescript/native/unstable/ast";
|
|
24
|
+
import type { DetectorInput, Evidence } from "./exempt.ts";
|
|
25
|
+
import { memberNames } from "./member-names.ts";
|
|
26
|
+
import { renderPosition } from "./position.ts";
|
|
27
|
+
|
|
28
|
+
/** The `Reflect` methods whose second argument is a property key. */
|
|
29
|
+
const REFLECT_KEYED: ReadonlySet<string> = new Set(["get", "set", "has"]);
|
|
30
|
+
|
|
31
|
+
/** One literal key at one lookup, and how the lookup is spelled in a record. */
|
|
32
|
+
interface Lookup {
|
|
33
|
+
readonly key: Node;
|
|
34
|
+
readonly name: string;
|
|
35
|
+
readonly callee: string;
|
|
36
|
+
/** The `Reflect` the call names, where the lookup is a `Reflect` call. */
|
|
37
|
+
readonly reflect: Node | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The text one key denotes, where it is literal text. */
|
|
41
|
+
function literalText(node: Node | undefined): string | undefined {
|
|
42
|
+
if (node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node))) {
|
|
43
|
+
return node.text;
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Every lookup one file writes with a literal key, in source order. */
|
|
49
|
+
function lookupsOf(file: SourceFile): readonly Lookup[] {
|
|
50
|
+
const found: Lookup[] = [];
|
|
51
|
+
const visit = (node: Node): void => {
|
|
52
|
+
if (isElementAccessExpression(node)) {
|
|
53
|
+
const name = literalText(node.argumentExpression);
|
|
54
|
+
if (name !== undefined) {
|
|
55
|
+
found.push({
|
|
56
|
+
key: node.argumentExpression,
|
|
57
|
+
name,
|
|
58
|
+
callee: `[${JSON.stringify(name)}]`,
|
|
59
|
+
reflect: undefined,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
} else if (isCallExpression(node) && isPropertyAccessExpression(node.expression)) {
|
|
63
|
+
const target = node.expression.expression;
|
|
64
|
+
const method = node.expression.name;
|
|
65
|
+
const key = node.arguments[1];
|
|
66
|
+
const name = literalText(key);
|
|
67
|
+
if (
|
|
68
|
+
isIdentifier(target) &&
|
|
69
|
+
target.text === "Reflect" &&
|
|
70
|
+
isIdentifier(method) &&
|
|
71
|
+
REFLECT_KEYED.has(method.text) &&
|
|
72
|
+
key !== undefined &&
|
|
73
|
+
name !== undefined
|
|
74
|
+
) {
|
|
75
|
+
found.push({ key, name, callee: `Reflect.${method.text}`, reflect: target });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
node.forEachChild(visit);
|
|
79
|
+
};
|
|
80
|
+
file.forEachChild(visit);
|
|
81
|
+
return found;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The reflective-lookup detector: every member whose name a literal key of a lookup
|
|
86
|
+
* spells, recorded at the key.
|
|
87
|
+
*/
|
|
88
|
+
export function reflectiveLookup<Brand>(input: DetectorInput<Brand>): readonly Evidence[] {
|
|
89
|
+
const { project, held, targetRoot } = input;
|
|
90
|
+
const files = project.ownSourceFiles();
|
|
91
|
+
const lookups = files.flatMap(lookupsOf);
|
|
92
|
+
if (lookups.length === 0) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Whether each written `Reflect` is the global one, asked in one batch: a
|
|
97
|
+
// declaration of that name in one of the project's own files shadows it.
|
|
98
|
+
const ownFiles = new Set(files.map((file) => file.fileName));
|
|
99
|
+
const reflects = lookups.flatMap((lookup) =>
|
|
100
|
+
lookup.reflect === undefined ? [] : [lookup.reflect],
|
|
101
|
+
);
|
|
102
|
+
const global = new Set<Node>();
|
|
103
|
+
if (reflects.length > 0) {
|
|
104
|
+
project.symbolsAt(reflects.map((node) => project.handle(node))).forEach((symbol, index) => {
|
|
105
|
+
const node = reflects[index];
|
|
106
|
+
const declaredHere = symbol?.declarations.some((declaration) =>
|
|
107
|
+
ownFiles.has(declaration.path),
|
|
108
|
+
);
|
|
109
|
+
if (node !== undefined && declaredHere === false) {
|
|
110
|
+
global.add(node);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const members = memberNames(files, held);
|
|
116
|
+
const found: Evidence[] = [];
|
|
117
|
+
for (const lookup of lookups) {
|
|
118
|
+
if (lookup.reflect !== undefined && !global.has(lookup.reflect)) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const site = renderPosition(lookup.key.getSourceFile(), targetRoot, lookup.key.getStart());
|
|
122
|
+
for (const member of members.get(lookup.name) ?? []) {
|
|
123
|
+
found.push({ id: member.id, detail: `looked up by ${lookup.callee}`, site });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return found;
|
|
127
|
+
}
|
package/src/run.ts
CHANGED
|
@@ -1,43 +1,38 @@
|
|
|
1
|
-
import { runRoots, runSweep, type RunRoot } from "./analysis.ts";
|
|
2
1
|
import { ConfigError, REPOSITORY_DOCUMENT, type Inputs } from "./config.ts";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
discoverProjects,
|
|
6
|
-
DiscoveryError,
|
|
7
|
-
renderDiagnostic,
|
|
8
|
-
} from "./discover.ts";
|
|
9
|
-
import { retainedLines } from "./exempt.ts";
|
|
2
|
+
import { DiscoveryError, renderDiagnostic } from "./discover.ts";
|
|
3
|
+
import { EMITTERS } from "./findings/emitters.ts";
|
|
10
4
|
import type { Host } from "./host.ts";
|
|
11
5
|
import { joinPath, resolvePath } from "./paths.ts";
|
|
12
|
-
import { printConfig } from "./print.ts";
|
|
13
|
-
import { resolve, resolveMatrix } from "./resolve.ts";
|
|
14
6
|
import { readScope, ScopeError, scopeForDir, type Scope } from "./scope.ts";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
7
|
+
import { openEngine, type Engine } from "./session.ts";
|
|
8
|
+
import { printConfigVerb } from "./verbs/print-config.ts";
|
|
9
|
+
import { printProjectsVerb } from "./verbs/print-projects.ts";
|
|
10
|
+
import { printRetainedVerb } from "./verbs/print-retained.ts";
|
|
11
|
+
import { printRootsVerb } from "./verbs/print-roots.ts";
|
|
12
|
+
import { EXIT_FAILURE, EXIT_USAGE, type Verb } from "./verbs/verb.ts";
|
|
13
|
+
import { versionVerb } from "./verbs/version.ts";
|
|
17
14
|
|
|
18
15
|
/** One output stream of the command line. `process.stdout` and `process.stderr` satisfy it. */
|
|
19
16
|
export interface Writer {
|
|
20
17
|
write(text: string): void;
|
|
21
18
|
}
|
|
22
19
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"print-
|
|
33
|
-
"print-
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
"version",
|
|
38
|
-
] as const;
|
|
20
|
+
/**
|
|
21
|
+
* Every verb this command answers, in the order the usage text lists them. A verb
|
|
22
|
+
* with no `run` is listed, and invoking it is a usage error.
|
|
23
|
+
*/
|
|
24
|
+
const VERBS: readonly { readonly name: string; readonly run?: Verb }[] = [
|
|
25
|
+
{ name: "analyze" },
|
|
26
|
+
{ name: "explain" },
|
|
27
|
+
{ name: "print-config", run: printConfigVerb },
|
|
28
|
+
{ name: "print-projects", run: printProjectsVerb },
|
|
29
|
+
{ name: "print-roots", run: printRootsVerb },
|
|
30
|
+
{ name: "print-retained", run: printRetainedVerb },
|
|
31
|
+
{ name: "describe" },
|
|
32
|
+
{ name: "version", run: versionVerb },
|
|
33
|
+
];
|
|
39
34
|
|
|
40
|
-
const USAGE = `usage: deadset-ts <verb> [options]\nverbs: ${VERBS.join(", ")}\n`;
|
|
35
|
+
const USAGE = `usage: deadset-ts <verb> [options]\nverbs: ${VERBS.map((verb) => verb.name).join(", ")}\n`;
|
|
41
36
|
|
|
42
37
|
/**
|
|
43
38
|
* The tokens whose presence in an option's name makes that option a request to
|
|
@@ -47,15 +42,11 @@ const USAGE = `usage: deadset-ts <verb> [options]\nverbs: ${VERBS.join(", ")}\n`
|
|
|
47
42
|
const SOURCE_EDIT_TOKENS = ["fix", "edit", "delete", "rewrite"] as const;
|
|
48
43
|
|
|
49
44
|
/**
|
|
50
|
-
* Maps an option name to the setting
|
|
51
|
-
* option, supplies the document resolution reads, and gives
|
|
52
|
-
* setting's provenance names.
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* command line, and its path is the setting of the Contract's configuration schema
|
|
56
|
-
* that option supplies. The two are mapped here and never derived from one
|
|
57
|
-
* another, so this table is the authority for both: a setting reaches the command
|
|
58
|
-
* line only by an entry, and a path names a key the schema declares.
|
|
45
|
+
* Maps an option name to the setting of the Contract's configuration schema it supplies,
|
|
46
|
+
* so one entry registers the option, supplies the document resolution reads, and gives
|
|
47
|
+
* the spelling the setting's provenance names. The name is this command's vocabulary and
|
|
48
|
+
* is never derived from the path: a setting reaches the command line only by an entry,
|
|
49
|
+
* and a path names a key the schema declares.
|
|
59
50
|
*/
|
|
60
51
|
export const SETTING_OPTIONS: ReadonlyMap<string, string> = new Map([
|
|
61
52
|
["min-confidence", "analysis.min_confidence"],
|
|
@@ -177,20 +168,13 @@ function scopeOf(host: Host, options: Options): Scope {
|
|
|
177
168
|
}
|
|
178
169
|
|
|
179
170
|
/**
|
|
180
|
-
* Runs the
|
|
181
|
-
*
|
|
182
|
-
* error or a configuration
|
|
183
|
-
* answer. It writes nothing outside `out` and `err` and never exits the
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
* analyzer reports and never edits a source file.
|
|
188
|
-
*
|
|
189
|
-
* `host` is the platform the run reads, the version it reports included;
|
|
190
|
-
* `openClient` opens the compiler client a verb that reads projects needs.
|
|
191
|
-
* Both are parameters because both are the platform, which no module below this one
|
|
192
|
-
* names: the command entry binds them, and a caller that wants to watch what a run
|
|
193
|
-
* reads supplies its own.
|
|
171
|
+
* Runs the command line over `args`, the arguments after the program name, and returns
|
|
172
|
+
* the exit code: 0 for a completed verb, 1 for a finding that fails the run, 2 for a
|
|
173
|
+
* usage error, a requested source edit or a refused configuration, 3 for a failure that
|
|
174
|
+
* produced no answer. It writes nothing outside `out` and `err` and never exits the
|
|
175
|
+
* process. `host` is the platform the run reads, the version it reports included, and
|
|
176
|
+
* `openClient` opens the compiler client; a caller that watches what a run reads
|
|
177
|
+
* supplies its own of either.
|
|
194
178
|
*/
|
|
195
179
|
export function run(
|
|
196
180
|
args: readonly string[],
|
|
@@ -208,170 +192,50 @@ export function run(
|
|
|
208
192
|
return EXIT_USAGE;
|
|
209
193
|
}
|
|
210
194
|
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
return
|
|
215
|
-
}
|
|
216
|
-
if (verb === "print-config") {
|
|
217
|
-
return printConfigVerb(args.slice(1), out, err, host);
|
|
218
|
-
}
|
|
219
|
-
if (verb === "print-projects") {
|
|
220
|
-
return printProjectsVerb(args.slice(1), out, err, host, openClient);
|
|
221
|
-
}
|
|
222
|
-
if (verb === "print-roots") {
|
|
223
|
-
return printRootsVerb(args.slice(1), out, err, host, openClient);
|
|
224
|
-
}
|
|
225
|
-
if (verb === "print-retained") {
|
|
226
|
-
return printRetainedVerb(args.slice(1), out, err, host, openClient);
|
|
195
|
+
const name = args[0];
|
|
196
|
+
const verb = VERBS.find((listed) => listed.name === name);
|
|
197
|
+
if (verb?.run !== undefined) {
|
|
198
|
+
return invoke(verb.run, args.slice(1), { out, err, host }, openClient);
|
|
227
199
|
}
|
|
228
|
-
if (verb !== undefined
|
|
229
|
-
err.write(`deadset-ts: ${verb} is not implemented\n`);
|
|
200
|
+
if (verb !== undefined) {
|
|
201
|
+
err.write(`deadset-ts: ${verb.name} is not implemented\n`);
|
|
230
202
|
return EXIT_USAGE;
|
|
231
203
|
}
|
|
232
|
-
if (
|
|
233
|
-
err.write(`deadset-ts: unknown verb ${JSON.stringify(
|
|
204
|
+
if (name !== undefined) {
|
|
205
|
+
err.write(`deadset-ts: unknown verb ${JSON.stringify(name)}\n`);
|
|
234
206
|
}
|
|
235
207
|
err.write(USAGE);
|
|
236
208
|
return EXIT_USAGE;
|
|
237
209
|
}
|
|
238
210
|
|
|
239
|
-
/** Writes the resolved configuration with the source of every setting. */
|
|
240
|
-
function printConfigVerb(args: readonly string[], out: Writer, err: Writer, host: Host): number {
|
|
241
|
-
try {
|
|
242
|
-
const options = readOptions(args);
|
|
243
|
-
const { config, provenance } = resolve(inputsOf(host, options));
|
|
244
|
-
out.write(printConfig(config, provenance));
|
|
245
|
-
return EXIT_CLEAN;
|
|
246
|
-
} catch (error: unknown) {
|
|
247
|
-
return refuse(error, err);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Writes the name of every project one run analyzes, one per line, having read each
|
|
253
|
-
* project's diagnostics first: the identifier the build matrix declares for it, or,
|
|
254
|
-
* where discovery derived the project, its configuration file's path below the
|
|
255
|
-
* target root.
|
|
256
|
-
*/
|
|
257
|
-
function printProjectsVerb(
|
|
258
|
-
args: readonly string[],
|
|
259
|
-
out: Writer,
|
|
260
|
-
err: Writer,
|
|
261
|
-
host: Host,
|
|
262
|
-
openClient: (collectTiming: boolean) => Engine,
|
|
263
|
-
): number {
|
|
264
|
-
let engine: Engine | undefined;
|
|
265
|
-
try {
|
|
266
|
-
const options = readOptions(args);
|
|
267
|
-
const matrix = resolveMatrix(inputsOf(host, options));
|
|
268
|
-
const scope = scopeOf(host, options);
|
|
269
|
-
engine = openClient(false);
|
|
270
|
-
const discovered = discoverProjects(engine, host, scope, matrix);
|
|
271
|
-
const ids = new Map(discovered.projects.map((project) => [project.configFile, project.id]));
|
|
272
|
-
const failures: string[] = [];
|
|
273
|
-
const { projects } = runSession(engine, discovered.configFiles, (project) => {
|
|
274
|
-
const errors = diagnosticErrors(diagnosticsOf(project));
|
|
275
|
-
failures.push(...errors.map(renderDiagnostic));
|
|
276
|
-
return ids.get(project.configFile) ?? project.configFile;
|
|
277
|
-
});
|
|
278
|
-
if (failures.length > 0) {
|
|
279
|
-
for (const line of failures) {
|
|
280
|
-
err.write(`${line}\n`);
|
|
281
|
-
}
|
|
282
|
-
err.write(`deadset-ts: ${String(failures.length)} error(s); no answer was produced\n`);
|
|
283
|
-
return EXIT_FAILURE;
|
|
284
|
-
}
|
|
285
|
-
for (const id of projects) {
|
|
286
|
-
out.write(`${id}\n`);
|
|
287
|
-
}
|
|
288
|
-
return EXIT_CLEAN;
|
|
289
|
-
} catch (error: unknown) {
|
|
290
|
-
engine?.close();
|
|
291
|
-
return refuse(error, err);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* One root as a line: the declaration's reference, why it is a root, and the string
|
|
297
|
-
* that named it, separated by tabs. A run of several configurations adds the ones
|
|
298
|
-
* whose project holds the root as a fourth field, and writes the third present and
|
|
299
|
-
* empty where no string named the root, so every line of one run holds the same
|
|
300
|
-
* fields.
|
|
301
|
-
*/
|
|
302
|
-
function rootLine(root: RunRoot, several: boolean): string {
|
|
303
|
-
const line = `${root.ref}\t${root.kind}`;
|
|
304
|
-
if (several) {
|
|
305
|
-
return `${line}\t${root.source}\t${root.configurations.join(" ")}`;
|
|
306
|
-
}
|
|
307
|
-
return root.source === "" ? line : `${line}\t${root.source}`;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
211
|
/**
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
* Contract format, so that sort and cut read it.
|
|
212
|
+
* Runs one verb over the arguments after its name. The options are read the first
|
|
213
|
+
* time the verb asks for what they name, and a refusal the verb throws is written
|
|
214
|
+
* and turned into its exit code once the client the verb opened is closed.
|
|
315
215
|
*/
|
|
316
|
-
function
|
|
216
|
+
function invoke(
|
|
217
|
+
verb: Verb,
|
|
317
218
|
args: readonly string[],
|
|
318
|
-
out: Writer,
|
|
319
|
-
err: Writer,
|
|
320
|
-
host: Host,
|
|
219
|
+
streams: { readonly out: Writer; readonly err: Writer; readonly host: Host },
|
|
321
220
|
openClient: (collectTiming: boolean) => Engine,
|
|
322
221
|
): number {
|
|
323
|
-
let
|
|
222
|
+
let options: Options | undefined;
|
|
223
|
+
const read = (): Options => (options ??= readOptions(args));
|
|
224
|
+
let opened: Engine | undefined;
|
|
324
225
|
try {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
for (const finding of answer.findings) {
|
|
335
|
-
err.write(`${finding.code}: roots.patterns names nothing: ${finding.symbol.ref}\n`);
|
|
336
|
-
}
|
|
337
|
-
return answer.findings.length > 0 ? EXIT_FINDINGS : EXIT_CLEAN;
|
|
338
|
-
} catch (error: unknown) {
|
|
339
|
-
engine?.close();
|
|
340
|
-
return refuse(error, err);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Writes every declaration an exemption held back in the production sweep a report is
|
|
346
|
-
* built from, one per line, and nothing where no exemption held one back. One fact
|
|
347
|
-
* several configurations found is one record, so the line names no configuration. The
|
|
348
|
-
* line shape is this command's own rather than a Contract format, so that sort and cut
|
|
349
|
-
* read it.
|
|
350
|
-
*/
|
|
351
|
-
function printRetainedVerb(
|
|
352
|
-
args: readonly string[],
|
|
353
|
-
out: Writer,
|
|
354
|
-
err: Writer,
|
|
355
|
-
host: Host,
|
|
356
|
-
openClient: (collectTiming: boolean) => Engine,
|
|
357
|
-
): number {
|
|
358
|
-
let engine: Engine | undefined;
|
|
359
|
-
try {
|
|
360
|
-
const options = readOptions(args);
|
|
361
|
-
const { config } = resolve(inputsOf(host, options));
|
|
362
|
-
const scope = scopeOf(host, options);
|
|
363
|
-
engine = openClient(false);
|
|
364
|
-
const swept = runSweep(engine, host, scope, config, {
|
|
365
|
-
marked: [],
|
|
366
|
-
mode: { production: true },
|
|
226
|
+
return verb({
|
|
227
|
+
...streams,
|
|
228
|
+
inputs: () => inputsOf(streams.host, read()),
|
|
229
|
+
scope: () => scopeOf(streams.host, read()),
|
|
230
|
+
openClient: (collectTiming) => {
|
|
231
|
+
opened = openClient(collectTiming);
|
|
232
|
+
return opened;
|
|
233
|
+
},
|
|
234
|
+
emitters: EMITTERS,
|
|
367
235
|
});
|
|
368
|
-
for (const line of retainedLines(swept.matrix.union.symbols, swept.retained)) {
|
|
369
|
-
out.write(`${line}\n`);
|
|
370
|
-
}
|
|
371
|
-
return EXIT_CLEAN;
|
|
372
236
|
} catch (error: unknown) {
|
|
373
|
-
|
|
374
|
-
return refuse(error, err);
|
|
237
|
+
opened?.close();
|
|
238
|
+
return refuse(error, streams.err);
|
|
375
239
|
}
|
|
376
240
|
}
|
|
377
241
|
|