@cplieger/deadset-ts 3.3.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/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/run.js +60 -176
- 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/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/run.ts +64 -200
- 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 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { dependenciesAndModuleMachinery } from "./dependencies-and-module-machinery.js";
|
|
2
|
+
import { interfaces } from "./interfaces.js";
|
|
3
|
+
import { intraFunction } from "./intra-function.js";
|
|
4
|
+
import { nonCodeArtifacts } from "./non-code-artifacts.js";
|
|
5
|
+
import { readsAndWrites } from "./reads-and-writes.js";
|
|
6
|
+
import { selfCheck } from "./self-check.js";
|
|
7
|
+
import { unusedDeclarations } from "./unused-declarations.js";
|
|
8
|
+
import { visibilityNarrowing } from "./visibility-narrowing.js";
|
|
9
|
+
/** Every kind family this analyzer reports, each by its emitter, in code order. */
|
|
10
|
+
export const EMITTERS = new Map([
|
|
11
|
+
["unused-declarations", unusedDeclarations],
|
|
12
|
+
["visibility-narrowing", visibilityNarrowing],
|
|
13
|
+
["interfaces", interfaces],
|
|
14
|
+
["reads-and-writes", readsAndWrites],
|
|
15
|
+
["non-code-artifacts", nonCodeArtifacts],
|
|
16
|
+
["dependencies-and-module-machinery", dependenciesAndModuleMachinery],
|
|
17
|
+
["self-check", selfCheck],
|
|
18
|
+
["intra-function", intraFunction],
|
|
19
|
+
]);
|
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. */
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { printConfig } from "../print.js";
|
|
2
|
+
import { resolve } from "../resolve.js";
|
|
3
|
+
import { EXIT_CLEAN } from "./verb.js";
|
|
4
|
+
/** Writes the resolved configuration with the source of every setting. */
|
|
5
|
+
export const printConfigVerb = ({ out, inputs }) => {
|
|
6
|
+
const { config, provenance } = resolve(inputs());
|
|
7
|
+
out.write(printConfig(config, provenance));
|
|
8
|
+
return EXIT_CLEAN;
|
|
9
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { diagnosticErrors, discoverProjects, renderDiagnostic } from "../discover.js";
|
|
2
|
+
import { resolveMatrix } from "../resolve.js";
|
|
3
|
+
import { diagnosticsOf, runSession } from "../session.js";
|
|
4
|
+
import { EXIT_CLEAN, EXIT_FAILURE } from "./verb.js";
|
|
5
|
+
/**
|
|
6
|
+
* Writes the name of every project one run analyzes, one per line, having read each
|
|
7
|
+
* project's diagnostics first: the identifier the build matrix declares for it, or,
|
|
8
|
+
* where discovery derived the project, its configuration file's path below the
|
|
9
|
+
* target root.
|
|
10
|
+
*/
|
|
11
|
+
export const printProjectsVerb = ({ out, err, host, inputs, scope, openClient }) => {
|
|
12
|
+
const matrix = resolveMatrix(inputs());
|
|
13
|
+
const scoped = scope();
|
|
14
|
+
const engine = openClient(false);
|
|
15
|
+
const discovered = discoverProjects(engine, host, scoped, matrix);
|
|
16
|
+
const ids = new Map(discovered.projects.map((project) => [project.configFile, project.id]));
|
|
17
|
+
const failures = [];
|
|
18
|
+
const { projects } = runSession(engine, discovered.configFiles, (project) => {
|
|
19
|
+
const errors = diagnosticErrors(diagnosticsOf(project));
|
|
20
|
+
failures.push(...errors.map(renderDiagnostic));
|
|
21
|
+
return ids.get(project.configFile) ?? project.configFile;
|
|
22
|
+
});
|
|
23
|
+
if (failures.length > 0) {
|
|
24
|
+
for (const line of failures) {
|
|
25
|
+
err.write(`${line}\n`);
|
|
26
|
+
}
|
|
27
|
+
err.write(`deadset-ts: ${String(failures.length)} error(s); no answer was produced\n`);
|
|
28
|
+
return EXIT_FAILURE;
|
|
29
|
+
}
|
|
30
|
+
for (const id of projects) {
|
|
31
|
+
out.write(`${id}\n`);
|
|
32
|
+
}
|
|
33
|
+
return EXIT_CLEAN;
|
|
34
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { runSweep } from "../analysis.js";
|
|
2
|
+
import { retainedLines } from "../exempt.js";
|
|
3
|
+
import { resolve } from "../resolve.js";
|
|
4
|
+
import { EXIT_CLEAN } from "./verb.js";
|
|
5
|
+
/**
|
|
6
|
+
* Writes every declaration an exemption held back in the production sweep a report is
|
|
7
|
+
* built from, one per line, and nothing where no exemption held one back. One fact
|
|
8
|
+
* several configurations found is one record, so the line names no configuration. The
|
|
9
|
+
* line shape is this command's own rather than a Contract format, so that sort and cut
|
|
10
|
+
* read it.
|
|
11
|
+
*/
|
|
12
|
+
export const printRetainedVerb = ({ out, host, inputs, scope, openClient }) => {
|
|
13
|
+
const { config } = resolve(inputs());
|
|
14
|
+
const scoped = scope();
|
|
15
|
+
const swept = runSweep(openClient(false), host, scoped, config, {
|
|
16
|
+
marked: [],
|
|
17
|
+
mode: { production: true },
|
|
18
|
+
});
|
|
19
|
+
for (const line of retainedLines(swept.matrix.union.symbols, swept.retained)) {
|
|
20
|
+
out.write(`${line}\n`);
|
|
21
|
+
}
|
|
22
|
+
return EXIT_CLEAN;
|
|
23
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { runRoots } from "../analysis.js";
|
|
2
|
+
import { resolve } from "../resolve.js";
|
|
3
|
+
import { EXIT_CLEAN, EXIT_FINDINGS } from "./verb.js";
|
|
4
|
+
/**
|
|
5
|
+
* One root as a line: the declaration's reference, why it is a root, and the string
|
|
6
|
+
* that named it, separated by tabs. A run of several configurations adds the ones
|
|
7
|
+
* whose project holds the root as a fourth field, and writes the third present and
|
|
8
|
+
* empty where no string named the root, so every line of one run holds the same
|
|
9
|
+
* fields.
|
|
10
|
+
*/
|
|
11
|
+
function rootLine(root, several) {
|
|
12
|
+
const line = `${root.ref}\t${root.kind}`;
|
|
13
|
+
if (several) {
|
|
14
|
+
return `${line}\t${root.source}\t${root.configurations.join(" ")}`;
|
|
15
|
+
}
|
|
16
|
+
return root.source === "" ? line : `${line}\t${root.source}`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Writes the root set of the run, one root per line, and then names on the error
|
|
20
|
+
* stream, by its issue kind, every configured root or pattern that named nothing;
|
|
21
|
+
* one such string fails the run. The line shape is this command's own rather than a
|
|
22
|
+
* Contract format, so that sort and cut read it.
|
|
23
|
+
*/
|
|
24
|
+
export const printRootsVerb = ({ out, err, host, inputs, scope, openClient }) => {
|
|
25
|
+
const { config, provenance } = resolve(inputs());
|
|
26
|
+
const scoped = scope();
|
|
27
|
+
const answer = runRoots(openClient(false), host, scoped, config, provenance);
|
|
28
|
+
const several = answer.configurations.length > 1;
|
|
29
|
+
for (const root of answer.roots) {
|
|
30
|
+
out.write(`${rootLine(root, several)}\n`);
|
|
31
|
+
}
|
|
32
|
+
for (const finding of answer.findings) {
|
|
33
|
+
err.write(`${finding.code}: roots.patterns names nothing: ${finding.symbol.ref}\n`);
|
|
34
|
+
}
|
|
35
|
+
return answer.findings.length > 0 ? EXIT_FINDINGS : EXIT_CLEAN;
|
|
36
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CONTRACT_VERSION } from "../version.js";
|
|
2
|
+
import { EXIT_CLEAN } from "./verb.js";
|
|
3
|
+
/** Writes the version the host reports and the Contract version this analyzer implements. */
|
|
4
|
+
export const versionVerb = ({ out, host }) => {
|
|
5
|
+
out.write(`deadset-ts ${host.analyzerVersion()}\ncontract ${CONTRACT_VERSION}\n`);
|
|
6
|
+
return EXIT_CLEAN;
|
|
7
|
+
};
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RunSweep } from "../analysis.ts";
|
|
2
|
+
import type { Config } from "../config.ts";
|
|
3
|
+
import type { Finding } from "../finding.ts";
|
|
4
|
+
|
|
5
|
+
/** What every emitter reads: the configuration the run resolved and the run's sweep. */
|
|
6
|
+
export interface EmitterInput {
|
|
7
|
+
readonly config: Config;
|
|
8
|
+
/** The production sweep a report is built from, beside the matrix it judged. */
|
|
9
|
+
readonly swept: RunSweep;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* One kind family's rule over one run: the findings of the codes in its family's range
|
|
14
|
+
* of the Contract's code space, in no particular order.
|
|
15
|
+
*/
|
|
16
|
+
export type Emitter = (input: EmitterInput) => readonly Finding[];
|
|
17
|
+
|
|
18
|
+
/** The emitter of each kind family, by the family's name in the Contract, in code order. */
|
|
19
|
+
export type Emitters = ReadonlyMap<string, Emitter>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { dependenciesAndModuleMachinery } from "./dependencies-and-module-machinery.ts";
|
|
2
|
+
import type { Emitter, Emitters } from "./emitter.ts";
|
|
3
|
+
import { interfaces } from "./interfaces.ts";
|
|
4
|
+
import { intraFunction } from "./intra-function.ts";
|
|
5
|
+
import { nonCodeArtifacts } from "./non-code-artifacts.ts";
|
|
6
|
+
import { readsAndWrites } from "./reads-and-writes.ts";
|
|
7
|
+
import { selfCheck } from "./self-check.ts";
|
|
8
|
+
import { unusedDeclarations } from "./unused-declarations.ts";
|
|
9
|
+
import { visibilityNarrowing } from "./visibility-narrowing.ts";
|
|
10
|
+
|
|
11
|
+
/** Every kind family this analyzer reports, each by its emitter, in code order. */
|
|
12
|
+
export const EMITTERS: Emitters = new Map<string, Emitter>([
|
|
13
|
+
["unused-declarations", unusedDeclarations],
|
|
14
|
+
["visibility-narrowing", visibilityNarrowing],
|
|
15
|
+
["interfaces", interfaces],
|
|
16
|
+
["reads-and-writes", readsAndWrites],
|
|
17
|
+
["non-code-artifacts", nonCodeArtifacts],
|
|
18
|
+
["dependencies-and-module-machinery", dependenciesAndModuleMachinery],
|
|
19
|
+
["self-check", selfCheck],
|
|
20
|
+
["intra-function", intraFunction],
|
|
21
|
+
]);
|
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
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { printConfig } from "../print.ts";
|
|
2
|
+
import { resolve } from "../resolve.ts";
|
|
3
|
+
import { EXIT_CLEAN, type Verb } from "./verb.ts";
|
|
4
|
+
|
|
5
|
+
/** Writes the resolved configuration with the source of every setting. */
|
|
6
|
+
export const printConfigVerb: Verb = ({ out, inputs }) => {
|
|
7
|
+
const { config, provenance } = resolve(inputs());
|
|
8
|
+
out.write(printConfig(config, provenance));
|
|
9
|
+
return EXIT_CLEAN;
|
|
10
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { diagnosticErrors, discoverProjects, renderDiagnostic } from "../discover.ts";
|
|
2
|
+
import { resolveMatrix } from "../resolve.ts";
|
|
3
|
+
import { diagnosticsOf, runSession } from "../session.ts";
|
|
4
|
+
import { EXIT_CLEAN, EXIT_FAILURE, type Verb } from "./verb.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Writes the name of every project one run analyzes, one per line, having read each
|
|
8
|
+
* project's diagnostics first: the identifier the build matrix declares for it, or,
|
|
9
|
+
* where discovery derived the project, its configuration file's path below the
|
|
10
|
+
* target root.
|
|
11
|
+
*/
|
|
12
|
+
export const printProjectsVerb: Verb = ({ out, err, host, inputs, scope, openClient }) => {
|
|
13
|
+
const matrix = resolveMatrix(inputs());
|
|
14
|
+
const scoped = scope();
|
|
15
|
+
const engine = openClient(false);
|
|
16
|
+
const discovered = discoverProjects(engine, host, scoped, matrix);
|
|
17
|
+
const ids = new Map(discovered.projects.map((project) => [project.configFile, project.id]));
|
|
18
|
+
const failures: string[] = [];
|
|
19
|
+
const { projects } = runSession(engine, discovered.configFiles, (project) => {
|
|
20
|
+
const errors = diagnosticErrors(diagnosticsOf(project));
|
|
21
|
+
failures.push(...errors.map(renderDiagnostic));
|
|
22
|
+
return ids.get(project.configFile) ?? project.configFile;
|
|
23
|
+
});
|
|
24
|
+
if (failures.length > 0) {
|
|
25
|
+
for (const line of failures) {
|
|
26
|
+
err.write(`${line}\n`);
|
|
27
|
+
}
|
|
28
|
+
err.write(`deadset-ts: ${String(failures.length)} error(s); no answer was produced\n`);
|
|
29
|
+
return EXIT_FAILURE;
|
|
30
|
+
}
|
|
31
|
+
for (const id of projects) {
|
|
32
|
+
out.write(`${id}\n`);
|
|
33
|
+
}
|
|
34
|
+
return EXIT_CLEAN;
|
|
35
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { runSweep } from "../analysis.ts";
|
|
2
|
+
import { retainedLines } from "../exempt.ts";
|
|
3
|
+
import { resolve } from "../resolve.ts";
|
|
4
|
+
import { EXIT_CLEAN, type Verb } from "./verb.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Writes every declaration an exemption held back in the production sweep a report is
|
|
8
|
+
* built from, one per line, and nothing where no exemption held one back. One fact
|
|
9
|
+
* several configurations found is one record, so the line names no configuration. The
|
|
10
|
+
* line shape is this command's own rather than a Contract format, so that sort and cut
|
|
11
|
+
* read it.
|
|
12
|
+
*/
|
|
13
|
+
export const printRetainedVerb: Verb = ({ out, host, inputs, scope, openClient }) => {
|
|
14
|
+
const { config } = resolve(inputs());
|
|
15
|
+
const scoped = scope();
|
|
16
|
+
const swept = runSweep(openClient(false), host, scoped, config, {
|
|
17
|
+
marked: [],
|
|
18
|
+
mode: { production: true },
|
|
19
|
+
});
|
|
20
|
+
for (const line of retainedLines(swept.matrix.union.symbols, swept.retained)) {
|
|
21
|
+
out.write(`${line}\n`);
|
|
22
|
+
}
|
|
23
|
+
return EXIT_CLEAN;
|
|
24
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { runRoots, type RunRoot } from "../analysis.ts";
|
|
2
|
+
import { resolve } from "../resolve.ts";
|
|
3
|
+
import { EXIT_CLEAN, EXIT_FINDINGS, type Verb } from "./verb.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One root as a line: the declaration's reference, why it is a root, and the string
|
|
7
|
+
* that named it, separated by tabs. A run of several configurations adds the ones
|
|
8
|
+
* whose project holds the root as a fourth field, and writes the third present and
|
|
9
|
+
* empty where no string named the root, so every line of one run holds the same
|
|
10
|
+
* fields.
|
|
11
|
+
*/
|
|
12
|
+
function rootLine(root: RunRoot, several: boolean): string {
|
|
13
|
+
const line = `${root.ref}\t${root.kind}`;
|
|
14
|
+
if (several) {
|
|
15
|
+
return `${line}\t${root.source}\t${root.configurations.join(" ")}`;
|
|
16
|
+
}
|
|
17
|
+
return root.source === "" ? line : `${line}\t${root.source}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Writes the root set of the run, one root per line, and then names on the error
|
|
22
|
+
* stream, by its issue kind, every configured root or pattern that named nothing;
|
|
23
|
+
* one such string fails the run. The line shape is this command's own rather than a
|
|
24
|
+
* Contract format, so that sort and cut read it.
|
|
25
|
+
*/
|
|
26
|
+
export const printRootsVerb: Verb = ({ out, err, host, inputs, scope, openClient }) => {
|
|
27
|
+
const { config, provenance } = resolve(inputs());
|
|
28
|
+
const scoped = scope();
|
|
29
|
+
const answer = runRoots(openClient(false), host, scoped, config, provenance);
|
|
30
|
+
const several = answer.configurations.length > 1;
|
|
31
|
+
for (const root of answer.roots) {
|
|
32
|
+
out.write(`${rootLine(root, several)}\n`);
|
|
33
|
+
}
|
|
34
|
+
for (const finding of answer.findings) {
|
|
35
|
+
err.write(`${finding.code}: roots.patterns names nothing: ${finding.symbol.ref}\n`);
|
|
36
|
+
}
|
|
37
|
+
return answer.findings.length > 0 ? EXIT_FINDINGS : EXIT_CLEAN;
|
|
38
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Inputs } from "../config.ts";
|
|
2
|
+
import type { Emitters } from "../findings/emitter.ts";
|
|
3
|
+
import type { Host } from "../host.ts";
|
|
4
|
+
import type { Writer } from "../run.ts";
|
|
5
|
+
import type { Scope } from "../scope.ts";
|
|
6
|
+
import type { Engine } from "../session.ts";
|
|
7
|
+
|
|
8
|
+
/** The exit codes the Contract's exit-code table names. */
|
|
9
|
+
export const EXIT_CLEAN = 0;
|
|
10
|
+
export const EXIT_FINDINGS = 1;
|
|
11
|
+
export const EXIT_USAGE = 2;
|
|
12
|
+
export const EXIT_FAILURE = 3;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* What one verb is handed: the streams it writes, the platform it reads, and the
|
|
16
|
+
* options that follow its name, read only when the verb asks for what they name, so
|
|
17
|
+
* a verb that asks for nothing refuses no option.
|
|
18
|
+
*/
|
|
19
|
+
export interface Invocation {
|
|
20
|
+
readonly out: Writer;
|
|
21
|
+
readonly err: Writer;
|
|
22
|
+
readonly host: Host;
|
|
23
|
+
/** The configuration documents the options name, and the settings they supply. */
|
|
24
|
+
readonly inputs: () => Inputs;
|
|
25
|
+
/** The scope the options name: the document `--scope` names, or the target. */
|
|
26
|
+
readonly scope: () => Scope;
|
|
27
|
+
/** Opens the compiler client. A client the verb opened is closed if the verb throws. */
|
|
28
|
+
readonly openClient: (collectTiming: boolean) => Engine;
|
|
29
|
+
/** The emitter of every kind family, which a verb that reports findings runs. */
|
|
30
|
+
readonly emitters: Emitters;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* One verb of the command line. It returns the exit code, and refuses by throwing a
|
|
35
|
+
* configuration, discovery or scope error, which the command line writes and turns
|
|
36
|
+
* into the code the Contract's table gives it.
|
|
37
|
+
*/
|
|
38
|
+
export type Verb = (invocation: Invocation) => number;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { CONTRACT_VERSION } from "../version.ts";
|
|
2
|
+
import { EXIT_CLEAN, type Verb } from "./verb.ts";
|
|
3
|
+
|
|
4
|
+
/** Writes the version the host reports and the Contract version this analyzer implements. */
|
|
5
|
+
export const versionVerb: Verb = ({ out, host }) => {
|
|
6
|
+
out.write(`deadset-ts ${host.analyzerVersion()}\ncontract ${CONTRACT_VERSION}\n`);
|
|
7
|
+
return EXIT_CLEAN;
|
|
8
|
+
};
|