@cplieger/deadset-ts 3.3.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +1 -1
  2. package/dist/src/analysis.js +8 -1
  3. package/dist/src/calls.js +154 -0
  4. package/dist/src/class-members.js +92 -0
  5. package/dist/src/config.js +3 -0
  6. package/dist/src/configured-declarations.js +232 -0
  7. package/dist/src/decorator.js +3 -10
  8. package/dist/src/findings/dependencies-and-module-machinery.js +2 -0
  9. package/dist/src/findings/emitter.js +1 -0
  10. package/dist/src/findings/emitters.js +19 -0
  11. package/dist/src/findings/interfaces.js +2 -0
  12. package/dist/src/findings/intra-function.js +2 -0
  13. package/dist/src/findings/non-code-artifacts.js +2 -0
  14. package/dist/src/findings/reads-and-writes.js +2 -0
  15. package/dist/src/findings/self-check.js +2 -0
  16. package/dist/src/findings/unused-declarations.js +2 -0
  17. package/dist/src/findings/visibility-narrowing.js +2 -0
  18. package/dist/src/framework-lifecycle.js +192 -0
  19. package/dist/src/injection-container.js +101 -0
  20. package/dist/src/print.js +18 -0
  21. package/dist/src/resolve.js +98 -0
  22. package/dist/src/run.js +60 -176
  23. package/dist/src/schema.js +18 -1
  24. package/dist/src/serialization-contract.js +315 -0
  25. package/dist/src/value-reach.js +120 -0
  26. package/dist/src/verbs/print-config.js +9 -0
  27. package/dist/src/verbs/print-projects.js +34 -0
  28. package/dist/src/verbs/print-retained.js +23 -0
  29. package/dist/src/verbs/print-roots.js +36 -0
  30. package/dist/src/verbs/verb.js +5 -0
  31. package/dist/src/verbs/version.js +7 -0
  32. package/dist/src/version.js +1 -1
  33. package/jsr.json +1 -1
  34. package/package.json +2 -2
  35. package/src/analysis.ts +8 -1
  36. package/src/calls.ts +224 -0
  37. package/src/class-members.ts +136 -0
  38. package/src/config.ts +31 -0
  39. package/src/configured-declarations.ts +308 -0
  40. package/src/decorator.ts +4 -16
  41. package/src/exempt.ts +5 -1
  42. package/src/findings/dependencies-and-module-machinery.ts +4 -0
  43. package/src/findings/emitter.ts +19 -0
  44. package/src/findings/emitters.ts +21 -0
  45. package/src/findings/interfaces.ts +4 -0
  46. package/src/findings/intra-function.ts +4 -0
  47. package/src/findings/non-code-artifacts.ts +4 -0
  48. package/src/findings/reads-and-writes.ts +4 -0
  49. package/src/findings/self-check.ts +4 -0
  50. package/src/findings/unused-declarations.ts +4 -0
  51. package/src/findings/visibility-narrowing.ts +4 -0
  52. package/src/framework-lifecycle.ts +280 -0
  53. package/src/injection-container.ts +141 -0
  54. package/src/print.ts +20 -1
  55. package/src/resolve.ts +155 -0
  56. package/src/run.ts +64 -200
  57. package/src/schema.ts +19 -1
  58. package/src/serialization-contract.ts +492 -0
  59. package/src/value-reach.ts +152 -0
  60. package/src/verbs/print-config.ts +10 -0
  61. package/src/verbs/print-projects.ts +35 -0
  62. package/src/verbs/print-retained.ts +24 -0
  63. package/src/verbs/print-roots.ts +38 -0
  64. package/src/verbs/verb.ts +38 -0
  65. package/src/verbs/version.ts +8 -0
  66. package/src/version.ts +1 -1
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
- diagnosticErrors,
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 { diagnosticsOf, openEngine, runSession, type Engine } from "./session.ts";
16
- import { CONTRACT_VERSION } from "./version.ts";
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
- /** The exit codes the Contract's exit-code table names. */
24
- const EXIT_CLEAN = 0;
25
- const EXIT_FINDINGS = 1;
26
- const EXIT_USAGE = 2;
27
- const EXIT_FAILURE = 3;
28
-
29
- const VERBS = [
30
- "analyze",
31
- "explain",
32
- "print-config",
33
- "print-projects",
34
- "print-roots",
35
- "print-retained",
36
- "describe",
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 it supplies, so one entry registers the
51
- * option, supplies the document resolution reads, and gives the spelling the
52
- * setting's provenance names.
53
- *
54
- * An option's name is this command's own vocabulary, chosen for how it reads on a
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 deadset-ts command line over `args` (the arguments after the program
181
- * name) and returns the process exit code: 0 for a completed verb, 2 for a usage
182
- * error or a configuration this analyzer refuses, 3 for a failure that produced no
183
- * answer. It writes nothing outside `out` and `err` and never exits the process,
184
- * so a caller decides what the code means.
185
- *
186
- * An option asking for a source edit is refused before the verb is read: this
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 verb = args[0];
212
- if (verb === "version") {
213
- out.write(`deadset-ts ${host.analyzerVersion()}\ncontract ${CONTRACT_VERSION}\n`);
214
- return EXIT_CLEAN;
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 && (VERBS as readonly string[]).includes(verb)) {
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 (verb !== undefined) {
233
- err.write(`deadset-ts: unknown verb ${JSON.stringify(verb)}\n`);
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
- * Writes the root set of the run, one root per line, and then names on the error
312
- * stream, by its issue kind, every configured root or pattern that named nothing;
313
- * one such string fails the run. The line shape is this command's own rather than a
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 printRootsVerb(
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 engine: Engine | undefined;
222
+ let options: Options | undefined;
223
+ const read = (): Options => (options ??= readOptions(args));
224
+ let opened: Engine | undefined;
324
225
  try {
325
- const options = readOptions(args);
326
- const { config, provenance } = resolve(inputsOf(host, options));
327
- const scope = scopeOf(host, options);
328
- engine = openClient(false);
329
- const answer = runRoots(engine, host, scope, config, provenance);
330
- const several = answer.configurations.length > 1;
331
- for (const root of answer.roots) {
332
- out.write(`${rootLine(root, several)}\n`);
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
- engine?.close();
374
- return refuse(error, err);
237
+ opened?.close();
238
+ return refuse(error, streams.err);
375
239
  }
376
240
  }
377
241
 
package/src/schema.ts CHANGED
@@ -28,6 +28,12 @@ export interface KeyNode {
28
28
 
29
29
  const leaf: KeyNode = { kind: "leaf" };
30
30
 
31
+ /** A list of declarations, each entry in one of the three shapes the `ts` section states. */
32
+ const declarations: KeyNode = {
33
+ kind: "list",
34
+ members: { symbol: leaf, module: leaf, name: leaf, global: leaf },
35
+ };
36
+
31
37
  /**
32
38
  * The closed key list the Contract's configuration schema declares, derived from
33
39
  * that schema key by key. A test pins the two equal, so a Contract change that
@@ -72,7 +78,19 @@ export const SCHEMA_ROOT: KeyNode = {
72
78
  },
73
79
  },
74
80
  go: { kind: "section", members: {} },
75
- ts: { kind: "section", members: { test_files: leaf, entry_files: leaf } },
81
+ ts: {
82
+ kind: "section",
83
+ members: {
84
+ test_files: leaf,
85
+ entry_files: leaf,
86
+ injection_registrations: declarations,
87
+ lifecycle_contracts: {
88
+ kind: "list",
89
+ members: { components: declarations, bases: declarations, members: leaf },
90
+ },
91
+ serializers: declarations,
92
+ },
93
+ },
76
94
  provenance: { kind: "map" },
77
95
  },
78
96
  };