@cplieger/deadset-ts 8.0.0-dev.8 → 8.0.0-dev.9

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 (75) hide show
  1. package/dist/src/analysis.js +84 -288
  2. package/dist/src/configuration-files.js +2 -9
  3. package/dist/src/consumers.js +1 -28
  4. package/dist/src/contract/contract.generated.js +20 -10
  5. package/dist/src/conventions.js +3 -8
  6. package/dist/src/failure.js +36 -0
  7. package/dist/src/fs/document.js +21 -28
  8. package/dist/src/fs/manifest.js +7 -0
  9. package/dist/src/load/classify.js +236 -0
  10. package/dist/src/load/diagnostics.js +79 -0
  11. package/dist/src/load/discover.js +230 -0
  12. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  13. package/dist/src/load/expected-module.js +136 -0
  14. package/dist/src/load/load.js +22 -0
  15. package/dist/src/load/module-lookup.js +37 -0
  16. package/dist/src/load/plan.js +155 -0
  17. package/dist/src/{test-globals.js → load/test-globals.js} +51 -56
  18. package/dist/src/load/test-joins.js +82 -0
  19. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +3 -3
  20. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +37 -40
  21. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +21 -59
  22. package/dist/src/{workspace-run.js → load/workspace-run.js} +4 -3
  23. package/dist/src/{workspace.js → load/workspace.js} +21 -17
  24. package/dist/src/outside-tests.js +1 -1
  25. package/dist/src/run.js +20 -26
  26. package/dist/src/scope.js +11 -19
  27. package/dist/src/session.js +22 -28
  28. package/dist/src/source-files.js +1 -1
  29. package/dist/src/type-errors.js +11 -216
  30. package/dist/src/verbs/analyze.js +90 -109
  31. package/dist/src/verbs/describe.js +5 -12
  32. package/dist/src/verbs/version.js +12 -2
  33. package/jsr.json +1 -1
  34. package/package.json +1 -1
  35. package/src/analysis.ts +68 -437
  36. package/src/configuration-files.ts +8 -10
  37. package/src/consumers.ts +2 -53
  38. package/src/contract/contract.generated.ts +20 -10
  39. package/src/conventions.ts +6 -12
  40. package/src/failure.ts +63 -0
  41. package/src/fs/document.ts +23 -27
  42. package/src/fs/manifest.ts +8 -0
  43. package/src/load/classify.ts +352 -0
  44. package/src/load/diagnostics.ts +141 -0
  45. package/src/load/discover.ts +378 -0
  46. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  47. package/src/load/expected-module.ts +188 -0
  48. package/src/load/load.ts +61 -0
  49. package/src/load/module-lookup.ts +50 -0
  50. package/src/load/plan.ts +258 -0
  51. package/src/{test-globals.ts → load/test-globals.ts} +59 -65
  52. package/src/load/test-joins.ts +98 -0
  53. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +4 -4
  54. package/src/{workspace-programs.ts → load/workspace-programs.ts} +59 -53
  55. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +31 -78
  56. package/src/{workspace-run.ts → load/workspace-run.ts} +14 -11
  57. package/src/{workspace.ts → load/workspace.ts} +28 -21
  58. package/src/outside-tests.ts +1 -1
  59. package/src/partial.ts +1 -1
  60. package/src/run.ts +26 -25
  61. package/src/scope.ts +15 -21
  62. package/src/session.ts +52 -53
  63. package/src/source-files.ts +1 -1
  64. package/src/type-errors.ts +18 -288
  65. package/src/verbs/analyze.ts +129 -136
  66. package/src/verbs/describe.ts +4 -11
  67. package/src/verbs/version.ts +13 -2
  68. package/dist/src/base-url.js +0 -18
  69. package/dist/src/discover.js +0 -374
  70. package/dist/src/setup-failure.js +0 -19
  71. package/dist/src/test-joins.js +0 -101
  72. package/src/base-url.ts +0 -24
  73. package/src/discover.ts +0 -548
  74. package/src/setup-failure.ts +0 -37
  75. package/src/test-joins.ts +0 -118
@@ -24,7 +24,13 @@ import type { SourceFiles } from "./source-files.ts";
24
24
  import { bytewise } from "./text/compare.ts";
25
25
  import { isRecord } from "./json-document.ts";
26
26
  import { filesIn } from "./fs/tree.ts";
27
- import { holdsManifest, inInstalledPackages, manifestAt, readJSON } from "./fs/manifest.ts";
27
+ import {
28
+ DEPENDENCY_SECTIONS,
29
+ holdsManifest,
30
+ inInstalledPackages,
31
+ manifestAt,
32
+ readJSON,
33
+ } from "./fs/manifest.ts";
28
34
  import { shellWords } from "./text/shell.ts";
29
35
 
30
36
  /** A configuration module's name, each form with the spelling a root reports it by. */
@@ -40,14 +46,6 @@ const MODULE_FORMS: readonly { readonly name: RegExp; readonly form: string }[]
40
46
  /** A JSON configuration file's name. */
41
47
  const DOCUMENT_NAME = /^(?:[\w-]+(?:\.[\w-]+)?\.config\.json|\.[\w-]+rc(?:\.json)?)$/u;
42
48
 
43
- /** The manifest members a dependency is declared in. */
44
- const DEPENDENCY_MEMBERS = [
45
- "dependencies",
46
- "devDependencies",
47
- "peerDependencies",
48
- "optionalDependencies",
49
- ] as const;
50
-
51
49
  /** The prefixes a string names a file with, relative to the file that writes it. */
52
50
  const RELATIVE_PREFIXES = ["./", "../"] as const;
53
51
 
@@ -474,7 +472,7 @@ export function declaredIn(host: Host, dir: string): readonly string[] {
474
472
  return [];
475
473
  }
476
474
  const names = new Set<string>();
477
- for (const member of DEPENDENCY_MEMBERS) {
475
+ for (const member of DEPENDENCY_SECTIONS) {
478
476
  const section = value[member];
479
477
  if (isRecord(section)) {
480
478
  Object.keys(section).forEach((name) => names.add(name));
package/src/consumers.ts CHANGED
@@ -1,60 +1,9 @@
1
- /**
2
- * The consumers a scope declares, loaded beside the target: each a directory on the
3
- * local filesystem whose compiler configurations open as projects of the run's one
4
- * snapshot, and whose references to the target's declarations count against them.
5
- */
6
-
7
1
  import type { PartialConfig } from "./config.ts";
8
- import { consumerProjects } from "./discover.ts";
9
2
  import type { Host } from "./host.ts";
10
- import { inventory, packageScope } from "./inventory.ts";
3
+ import { inventory } from "./inventory.ts";
11
4
  import { relativePath } from "./paths.ts";
12
5
  import { references, type ConsumerSide, type Reference } from "./references.ts";
13
- import type { Module, Scope } from "./scope.ts";
14
- import { narrowedTo, type Engine, type ProjectView } from "./session.ts";
15
-
16
- /** One consumer the run loaded. */
17
- export interface LoadedConsumer {
18
- /** The name the consumer publishes itself under, which every finding names it by. */
19
- readonly id: string;
20
- /** The consumer's directory, absolute. */
21
- readonly path: string;
22
- }
23
-
24
- /** One declared consumer and the compiler configurations it opens as. */
25
- interface ConsumerLoad {
26
- readonly consumer: LoadedConsumer;
27
- /** The configuration files, absolute, in discovery order, none a target project's. */
28
- readonly configFiles: readonly string[];
29
- }
30
-
31
- /** The name one module publishes itself under: the scope's, else its manifest's. */
32
- export function moduleIdentity(host: Host, module: Module): string {
33
- return module.id === "" ? packageScope(host, module.path)("index.ts").package : module.id;
34
- }
35
-
36
- /**
37
- * Every consumer the scope declares, in its order, with the projects it opens as. A
38
- * configuration that is also one of the target's projects, or an earlier consumer's, is
39
- * that project's alone. A consumer whose directory is absent, is no directory or holds
40
- * no configuration ends the run naming it, before any project opens.
41
- */
42
- export function consumerLoads(
43
- engine: Engine,
44
- host: Host,
45
- scope: Scope,
46
- targetConfigFiles: readonly string[],
47
- ): readonly ConsumerLoad[] {
48
- const opened = new Set(targetConfigFiles);
49
- return scope.consumers.map((module) => {
50
- const configFiles = consumerProjects(engine, host, module.path).filter((configFile) => {
51
- const fresh = !opened.has(configFile);
52
- opened.add(configFile);
53
- return fresh;
54
- });
55
- return { consumer: { id: moduleIdentity(host, module), path: module.path }, configFiles };
56
- });
57
- }
6
+ import { narrowedTo, type ProjectView } from "./session.ts";
58
7
 
59
8
  /**
60
9
  * Every reference one consumer project's own files make to a declaration of the target.
@@ -1135,14 +1135,24 @@ export const EVIDENCE = [
1135
1135
  },
1136
1136
  ] as const;
1137
1137
 
1138
- export const SETUP_CLASSES = [
1139
- { class: "missing-module", languages: ["go", "ts"] },
1140
- { class: "incomplete-module-sum", languages: ["go"] },
1141
- { class: "missing-consumer", languages: ["go", "ts"] },
1142
- { class: "workspace-member-without-source", languages: ["ts"] },
1143
- { class: "convention-not-literal", languages: ["ts"] },
1144
- { class: "no-program", languages: ["go", "ts"] },
1145
- ] as const;
1138
+ /** Each group of failure classes, and the text opening the line that names one of its failures. */
1139
+ export const FAILURE_PREFIXES = { setup: "setup failure: ", run: "run failure: " } as const;
1146
1140
 
1147
- /** The text opening the line of the error stream that names one setup failure. */
1148
- export const SETUP_FAILURE_PREFIX = "setup failure: ";
1141
+ /** Each failure class the Contract names, and its group. */
1142
+ export const FAILURE_CLASSES = {
1143
+ "missing-module": "setup",
1144
+ "incomplete-module-sum": "setup",
1145
+ "missing-consumer": "setup",
1146
+ "workspace-member-without-source": "setup",
1147
+ "convention-not-literal": "setup",
1148
+ "no-program": "setup",
1149
+ "unreadable-document": "setup",
1150
+ "unwritten-output": "run",
1151
+ "unreportable-path": "run",
1152
+ "memory-exhausted": "run",
1153
+ "missing-analyzer": "run",
1154
+ "unadmitted-analyzer": "run",
1155
+ "unresolved-edge": "run",
1156
+ "interrupted-run": "run",
1157
+ "product-fault": "run",
1158
+ } as const;
@@ -61,14 +61,14 @@ import {
61
61
  relativePath,
62
62
  resolvePath,
63
63
  } from "./paths.ts";
64
- import type { SetupFailure } from "./setup-failure.ts";
64
+ import type { Failure } from "./failure.ts";
65
65
  import type { SourceFiles } from "./source-files.ts";
66
66
  import { satisfies } from "./version-range.ts";
67
67
  import { bytewise } from "./text/compare.ts";
68
68
  import { isRecord } from "./json-document.ts";
69
69
  import { unwrapped } from "./unwrap.ts";
70
70
  import { filesIn } from "./fs/tree.ts";
71
- import { INSTALLED_PACKAGES, MANIFEST, readJSON } from "./fs/manifest.ts";
71
+ import { DEPENDENCY_SECTIONS, INSTALLED_PACKAGES, MANIFEST, readJSON } from "./fs/manifest.ts";
72
72
  import { globExpression } from "./text/glob.ts";
73
73
  import { yamlDocument } from "./text/yaml.ts";
74
74
 
@@ -115,7 +115,7 @@ interface GeneratedDirectory {
115
115
  /** A setup failure a row met, at the directory of the manifest that declared it. */
116
116
  interface ConventionFailure {
117
117
  readonly dir: string;
118
- readonly failure: SetupFailure;
118
+ readonly failure: Failure;
119
119
  }
120
120
 
121
121
  /** What the rows decided for one run. */
@@ -169,12 +169,6 @@ const JSON_RC = /^\.[\w-]+rc$/u;
169
169
  /** The sections an installed package is required by, so its absence fails the run. */
170
170
  const REQUIRED_SECTIONS = ["dependencies", "devDependencies"] as const;
171
171
 
172
- const DEPENDENCY_SECTIONS = [
173
- ...REQUIRED_SECTIONS,
174
- "peerDependencies",
175
- "optionalDependencies",
176
- ] as const;
177
-
178
172
  function declares(
179
173
  manifest: unknown,
180
174
  name: string,
@@ -1094,7 +1088,7 @@ export function readConventions(
1094
1088
  failures.push({
1095
1089
  dir: manifest.dir,
1096
1090
  failure: {
1097
- setupClass: "missing-module",
1091
+ failureClass: "missing-module",
1098
1092
  detail:
1099
1093
  `${manifest.path} declares ${row.package}, which no node_modules at or ` +
1100
1094
  `above its directory holds: run the package manager's install`,
@@ -1120,7 +1114,7 @@ export function readConventions(
1120
1114
  failures.push({
1121
1115
  dir: manifest.dir,
1122
1116
  failure: {
1123
- setupClass: "convention-not-literal",
1117
+ failureClass: "convention-not-literal",
1124
1118
  detail: where === "." ? failed : `${where}/${failed}`,
1125
1119
  },
1126
1120
  });
@@ -1241,7 +1235,7 @@ export function conventionFailuresFor(
1241
1235
  conventions: Conventions,
1242
1236
  configFile: string,
1243
1237
  ownFiles: readonly string[],
1244
- ): readonly SetupFailure[] {
1238
+ ): readonly Failure[] {
1245
1239
  return conventions.failures
1246
1240
  .filter(
1247
1241
  ({ dir }) =>
package/src/failure.ts ADDED
@@ -0,0 +1,63 @@
1
+ import { FAILURE_CLASSES, FAILURE_PREFIXES } from "./contract/contract.generated.ts";
2
+ import type { LocatedDiagnostic } from "./load/diagnostics.ts";
3
+ import { PRODUCT } from "./product.ts";
4
+
5
+ /** A class of failure the Contract's exit-code table names, of either group. */
6
+ type FailureClass = keyof typeof FAILURE_CLASSES;
7
+
8
+ /** One cause the run ends with the failure code for. */
9
+ export interface Failure {
10
+ readonly failureClass: FailureClass;
11
+ /** What fails and the fix, on one line. */
12
+ readonly detail: string;
13
+ }
14
+
15
+ /** The failure as a class line of the error stream, opened by its group's prefix. */
16
+ export function failureLine(failure: Failure): string {
17
+ return `${FAILURE_PREFIXES[FAILURE_CLASSES[failure.failureClass]]}${failure.failureClass}: ${failure.detail}`;
18
+ }
19
+
20
+ /**
21
+ * The lines of the error stream that name the failures: the first one's class line, the run's
22
+ * only one, then each further failure as its class and detail with no group prefix.
23
+ */
24
+ export function failureLines([first, ...further]: readonly [Failure, ...Failure[]]): string[] {
25
+ return [failureLine(first), ...further.map((one) => `${one.failureClass}: ${one.detail}`)];
26
+ }
27
+
28
+ /**
29
+ * A run the analysis cannot answer. It ends the run with the failure code and no finding
30
+ * list, printing the compiler's located errors and then the failures' lines.
31
+ */
32
+ export class FailureExit extends Error {
33
+ readonly failures: readonly [Failure, ...Failure[]];
34
+ /** The compiler's errors behind the failures, in the order it reported them. */
35
+ readonly diagnostics: readonly LocatedDiagnostic[];
36
+
37
+ constructor(
38
+ failures: readonly [Failure, ...Failure[]],
39
+ diagnostics: readonly LocatedDiagnostic[] = [],
40
+ ) {
41
+ super(failureLines(failures).join("\n"));
42
+ this.name = "FailureExit";
43
+ this.failures = failures;
44
+ this.diagnostics = diagnostics;
45
+ }
46
+ }
47
+
48
+ /** A failure exit of one failure. */
49
+ export function failureExit(
50
+ failureClass: FailureClass,
51
+ detail: string,
52
+ diagnostics: readonly LocatedDiagnostic[] = [],
53
+ ): FailureExit {
54
+ return new FailureExit([{ failureClass, detail }], diagnostics);
55
+ }
56
+
57
+ /** A fault of this analyzer itself, `fault` saying what it cannot do. */
58
+ export function productFault(fault: string): FailureExit {
59
+ return failureExit(
60
+ "product-fault",
61
+ `${PRODUCT} ${fault}: reinstall ${PRODUCT}, and report the fault to its maintainers if a reinstalled ${PRODUCT} fails the same way`,
62
+ );
63
+ }
@@ -7,21 +7,17 @@
7
7
 
8
8
  import { CONFIG_MAX_BYTES } from "../contract/config.generated.ts";
9
9
  import type { Host } from "../host.ts";
10
+ import { failureExit } from "../failure.ts";
10
11
  import { joinPath, relativePath } from "../paths.ts";
11
12
  import { message } from "../product.ts";
12
13
  import { utf8Bytes } from "../reporters/sha256.ts";
13
14
  import { UsageError } from "../usage.ts";
14
15
 
15
- /**
16
- * A document at the target root that is not one to read: its name resolves outside the
17
- * target root or to anything but a regular file, or the read fails. Nothing is read from
18
- * it, and the run ends with the failure code.
19
- */
20
- export class TargetDocumentError extends Error {
21
- constructor(label: string, reason: string) {
22
- super(`${label}: ${reason}`);
23
- this.name = "TargetDocumentError";
24
- }
16
+ function unreadable(label: string, reason: string): Error {
17
+ return failureExit(
18
+ "unreadable-document",
19
+ `${label}: ${reason}: make it a regular file inside the target root that the run can read, or remove it`,
20
+ );
25
21
  }
26
22
 
27
23
  /** Refuses a document past the Contract's bound with the usage code, naming it `label`. */
@@ -31,44 +27,44 @@ export function refuseOverBound(label: string, text: string): void {
31
27
  }
32
28
  }
33
29
 
34
- /** What `read` returns, or its throw as the document's {@link TargetDocumentError}. */
35
- function attempt<T>(label: string, read: () => T): T {
36
- try {
37
- return read();
38
- } catch (error: unknown) {
39
- throw new TargetDocumentError(label, message(error));
40
- }
41
- }
42
-
43
30
  /**
44
31
  * The text of the document `name` at the absolute `targetRoot`, undefined where nothing is
45
- * there, each refusal naming it `label`. A document past the bound is read one byte past
46
- * it and refused as {@link refuseOverBound} refuses it.
32
+ * there. One whose name resolves outside the target root or to anything but a regular file,
33
+ * or whose read fails, is refused by `refuse` naming it `label`, nothing read from it. A
34
+ * document past the bound is read one byte past it and refused as {@link refuseOverBound} does.
47
35
  */
48
36
  export function readTargetDocument(
49
37
  host: Host,
50
38
  targetRoot: string,
51
39
  name: string,
52
40
  label = name,
41
+ refuse: (label: string, reason: string) => Error = unreadable,
53
42
  ): string | undefined {
43
+ const attempt = <T>(read: () => T): T => {
44
+ try {
45
+ return read();
46
+ } catch (error: unknown) {
47
+ throw refuse(label, message(error));
48
+ }
49
+ };
54
50
  const path = joinPath(targetRoot, name);
55
- const kind = attempt(label, () => host.kindOf(path));
51
+ const kind = attempt(() => host.kindOf(path));
56
52
  if (kind === "absent") {
57
53
  return undefined;
58
54
  }
59
- const real = attempt(label, () => host.realPath(path));
55
+ const real = attempt(() => host.realPath(path));
60
56
  if (
61
57
  relativePath(
62
- attempt(label, () => host.realPath(targetRoot)),
58
+ attempt(() => host.realPath(targetRoot)),
63
59
  real,
64
60
  ) === undefined
65
61
  ) {
66
- throw new TargetDocumentError(label, `resolves to ${real}, outside the target root`);
62
+ throw refuse(label, `resolves to ${real}, outside the target root`);
67
63
  }
68
64
  if (kind !== "file") {
69
- throw new TargetDocumentError(label, "is not a regular file");
65
+ throw refuse(label, "is not a regular file");
70
66
  }
71
- const text = attempt(label, () => host.readFile(real, CONFIG_MAX_BYTES + 1));
67
+ const text = attempt(() => host.readFile(real, CONFIG_MAX_BYTES + 1));
72
68
  refuseOverBound(label, text);
73
69
  return text;
74
70
  }
@@ -8,6 +8,14 @@ export const MANIFEST = "package.json";
8
8
  /** The directory a package manager installs packages into, whose files are no project's own. */
9
9
  export const INSTALLED_PACKAGES = "node_modules";
10
10
 
11
+ /** The manifest members a package declares the packages it depends on under. */
12
+ export const DEPENDENCY_SECTIONS = [
13
+ "dependencies",
14
+ "devDependencies",
15
+ "peerDependencies",
16
+ "optionalDependencies",
17
+ ] as const;
18
+
11
19
  /** Whether one path passes through an installed-packages directory. */
12
20
  export function inInstalledPackages(path: string): boolean {
13
21
  return path.split("/").includes(INSTALLED_PACKAGES);