@cplieger/deadset-ts 8.0.0-dev.5 → 8.0.0-dev.7

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 (201) hide show
  1. package/dist/bin/mapper-connection.js +2 -1
  2. package/dist/bin/node-host.js +3 -2
  3. package/dist/src/analysis.js +28 -46
  4. package/dist/src/automatic-types.js +10 -29
  5. package/dist/src/build-scripts.js +17 -29
  6. package/dist/src/component-layer.js +4 -2
  7. package/dist/src/components.js +2 -3
  8. package/dist/src/config.js +3 -2
  9. package/dist/src/configuration-files.js +27 -168
  10. package/dist/src/conformance-record.js +1 -1
  11. package/dist/src/contract/contract.generated.js +1084 -0
  12. package/dist/src/contract/report.generated.js +796 -0
  13. package/dist/src/conventions.js +20 -60
  14. package/dist/src/dependencies.js +25 -43
  15. package/dist/src/discover.js +13 -39
  16. package/dist/src/edges.js +7 -15
  17. package/dist/src/element-access.js +4 -6
  18. package/dist/src/entry-point-gaps.js +2 -8
  19. package/dist/src/entry-points.js +3 -10
  20. package/dist/src/enum-group.js +3 -10
  21. package/dist/src/erased-stores.js +4 -6
  22. package/dist/src/exempt-classes.js +3 -63
  23. package/dist/src/exempt.js +13 -40
  24. package/dist/src/file-facts.js +2 -25
  25. package/dist/src/findings/completion.js +11 -20
  26. package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
  27. package/dist/src/findings/emitters.js +4 -4
  28. package/dist/src/findings/interfaces.js +4 -5
  29. package/dist/src/findings/intra-function.js +8 -7
  30. package/dist/src/findings/non-code-artifacts.js +5 -6
  31. package/dist/src/findings/reads-and-writes.js +11 -9
  32. package/dist/src/findings/self-check.js +10 -12
  33. package/dist/src/findings/unused-declarations.js +28 -27
  34. package/dist/src/findings/visibility-narrowing.js +9 -11
  35. package/dist/src/findings-pass.js +7 -14
  36. package/dist/src/fs/manifest.js +43 -0
  37. package/dist/src/fs/tree.js +88 -0
  38. package/dist/src/git-ignore.js +34 -25
  39. package/dist/src/html-entries.js +6 -33
  40. package/dist/src/in-operands.js +2 -1
  41. package/dist/src/index.js +1 -1
  42. package/dist/src/intra-function-parts.js +18 -26
  43. package/dist/src/inventory.js +2 -21
  44. package/dist/src/json-document.js +4 -0
  45. package/dist/src/keyof-comparisons.js +4 -6
  46. package/dist/src/kinds.js +4 -360
  47. package/dist/src/manifest.js +5 -43
  48. package/dist/src/mdx-entries.js +3 -26
  49. package/dist/src/partial.js +2 -1
  50. package/dist/src/paths.js +26 -15
  51. package/dist/src/position.js +2 -1
  52. package/dist/src/print.js +3 -2
  53. package/dist/src/product.js +13 -0
  54. package/dist/src/references.js +14 -27
  55. package/dist/src/report-schema.js +272 -0
  56. package/dist/src/report.js +39 -42
  57. package/dist/src/reporters/sarif.js +4 -2
  58. package/dist/src/reporters/template.js +4 -19
  59. package/dist/src/resolve.js +12 -26
  60. package/dist/src/roots.js +7 -17
  61. package/dist/src/run.js +10 -8
  62. package/dist/src/schema.js +4 -3
  63. package/dist/src/scope.js +4 -5
  64. package/dist/src/serialization-contract.js +4 -8
  65. package/dist/src/session.js +2 -1
  66. package/dist/src/setup-failure.js +2 -6
  67. package/dist/src/suppress-file.js +12 -13
  68. package/dist/src/suppress-inline.js +3 -2
  69. package/dist/src/suppress.js +29 -48
  70. package/dist/src/symbol-keys.js +3 -5
  71. package/dist/src/template-field.js +5 -15
  72. package/dist/src/test-globals.js +6 -28
  73. package/dist/src/test-joins.js +6 -6
  74. package/dist/src/text/compare.js +26 -0
  75. package/dist/src/text/glob.js +194 -0
  76. package/dist/src/text/shell.js +159 -0
  77. package/dist/src/{yaml-document.js → text/yaml.js} +38 -0
  78. package/dist/src/type-errors.js +14 -38
  79. package/dist/src/unseen-loads.js +2 -9
  80. package/dist/src/unwrap.js +23 -0
  81. package/dist/src/verbs/analyze.js +22 -15
  82. package/dist/src/verbs/describe.js +7 -7
  83. package/dist/src/verbs/explain.js +7 -4
  84. package/dist/src/verbs/print-config.js +2 -1
  85. package/dist/src/verbs/print-projects.js +4 -2
  86. package/dist/src/verbs/print-retained.js +4 -2
  87. package/dist/src/verbs/print-roots.js +4 -2
  88. package/dist/src/verbs/verb.js +1 -5
  89. package/dist/src/verbs/version.js +4 -3
  90. package/dist/src/workflow-steps.js +9 -46
  91. package/dist/src/workspace-manifests.js +2 -2
  92. package/dist/src/workspace-programs.js +5 -3
  93. package/dist/src/workspace-resolution.js +13 -17
  94. package/dist/src/workspace.js +28 -116
  95. package/jsr.json +1 -1
  96. package/package.json +2 -1
  97. package/src/analysis.ts +29 -54
  98. package/src/automatic-types.ts +11 -29
  99. package/src/build-scripts.ts +17 -38
  100. package/src/component-layer.ts +4 -2
  101. package/src/components.ts +2 -4
  102. package/src/config.ts +11 -15
  103. package/src/configuration-files.ts +27 -170
  104. package/src/conformance-record.ts +1 -1
  105. package/src/contract/contract.generated.ts +1148 -0
  106. package/src/contract/report.generated.ts +1157 -0
  107. package/src/conventions.ts +26 -74
  108. package/src/dependencies.ts +32 -50
  109. package/src/discover.ts +14 -39
  110. package/src/edges.ts +7 -21
  111. package/src/element-access.ts +4 -7
  112. package/src/entry-point-gaps.ts +3 -9
  113. package/src/entry-points.ts +2 -13
  114. package/src/enum-group.ts +2 -11
  115. package/src/erased-stores.ts +3 -7
  116. package/src/exempt-classes.ts +5 -74
  117. package/src/exempt.ts +17 -47
  118. package/src/file-facts.ts +6 -24
  119. package/src/finding.ts +9 -9
  120. package/src/findings/completion.ts +22 -23
  121. package/src/findings/dependencies-and-module-machinery.ts +4 -7
  122. package/src/findings/emitter.ts +6 -7
  123. package/src/findings/emitters.ts +6 -6
  124. package/src/findings/interfaces.ts +4 -6
  125. package/src/findings/intra-function.ts +8 -8
  126. package/src/findings/non-code-artifacts.ts +5 -7
  127. package/src/findings/reads-and-writes.ts +15 -12
  128. package/src/findings/self-check.ts +11 -17
  129. package/src/findings/unused-declarations.ts +28 -27
  130. package/src/findings/visibility-narrowing.ts +9 -12
  131. package/src/findings-pass.ts +7 -16
  132. package/src/fs/manifest.ts +52 -0
  133. package/src/fs/tree.ts +110 -0
  134. package/src/git-ignore.ts +38 -25
  135. package/src/html-entries.ts +6 -33
  136. package/src/in-operands.ts +2 -1
  137. package/src/index.ts +1 -1
  138. package/src/intra-function-parts.ts +18 -28
  139. package/src/inventory.ts +2 -20
  140. package/src/json-document.ts +5 -0
  141. package/src/keyof-comparisons.ts +3 -7
  142. package/src/kinds.ts +6 -406
  143. package/src/manifest.ts +5 -46
  144. package/src/mdx-entries.ts +3 -26
  145. package/src/partial.ts +2 -1
  146. package/src/paths.ts +27 -15
  147. package/src/position.ts +2 -1
  148. package/src/print.ts +3 -2
  149. package/src/product.ts +18 -0
  150. package/src/references.ts +14 -29
  151. package/src/report-schema.ts +364 -0
  152. package/src/report.ts +66 -229
  153. package/src/reporters/annotations.ts +2 -2
  154. package/src/reporters/json.ts +1 -1
  155. package/src/reporters/reporter.ts +2 -2
  156. package/src/reporters/reporters.ts +1 -1
  157. package/src/reporters/sarif.ts +17 -12
  158. package/src/reporters/template.ts +4 -20
  159. package/src/reporters/text.ts +3 -3
  160. package/src/resolve.ts +31 -38
  161. package/src/roots.ts +7 -20
  162. package/src/run.ts +10 -14
  163. package/src/schema.ts +4 -3
  164. package/src/scope.ts +4 -10
  165. package/src/serialization-contract.ts +4 -9
  166. package/src/session.ts +2 -1
  167. package/src/setup-failure.ts +5 -7
  168. package/src/suppress-file.ts +11 -15
  169. package/src/suppress-inline.ts +2 -2
  170. package/src/suppress.ts +39 -55
  171. package/src/symbol-keys.ts +2 -5
  172. package/src/template-field.ts +5 -18
  173. package/src/test-globals.ts +9 -27
  174. package/src/test-joins.ts +6 -8
  175. package/src/text/compare.ts +27 -0
  176. package/src/text/glob.ts +210 -0
  177. package/src/text/shell.ts +161 -0
  178. package/src/{yaml-document.ts → text/yaml.ts} +46 -0
  179. package/src/type-errors.ts +25 -36
  180. package/src/unseen-loads.ts +1 -12
  181. package/src/unwrap.ts +34 -0
  182. package/src/verbs/analyze.ts +38 -25
  183. package/src/verbs/describe.ts +13 -7
  184. package/src/verbs/explain.ts +7 -4
  185. package/src/verbs/print-config.ts +2 -1
  186. package/src/verbs/print-projects.ts +4 -2
  187. package/src/verbs/print-retained.ts +4 -2
  188. package/src/verbs/print-roots.ts +4 -2
  189. package/src/verbs/verb.ts +0 -6
  190. package/src/verbs/version.ts +4 -3
  191. package/src/workflow-steps.ts +9 -44
  192. package/src/workspace-manifests.ts +2 -2
  193. package/src/workspace-programs.ts +5 -3
  194. package/src/workspace-resolution.ts +21 -17
  195. package/src/workspace.ts +35 -121
  196. package/dist/src/glob.js +0 -65
  197. package/dist/src/pnpm-workspace.js +0 -120
  198. package/dist/src/version.js +0 -7
  199. package/src/glob.ts +0 -67
  200. package/src/pnpm-workspace.ts +0 -135
  201. package/src/version.ts +0 -7
package/src/resolve.ts CHANGED
@@ -1,21 +1,28 @@
1
+ import {
2
+ SEVERITIES,
3
+ LANGUAGES,
4
+ CONFIDENCES,
5
+ FORMATS,
6
+ type Confidence,
7
+ type Format,
8
+ type Language,
9
+ type Severity,
10
+ FAMILY_KEY_LENGTH,
11
+ } from "./contract/contract.generated.ts";
1
12
  import {
2
13
  ConfigError,
3
14
  defaultConfig,
4
15
  type BuildConfiguration,
5
16
  type Cascade,
6
17
  type Config,
7
- type Confidence,
8
18
  type ConsumerTests,
9
19
  type DeclarationEntry,
10
- type Format,
11
20
  type GeneratedFiles,
12
21
  type Inputs,
13
- type Language,
14
22
  type LifecycleContract,
15
23
  type Origin,
16
24
  type Provenance,
17
25
  type Provider,
18
- type Severity,
19
26
  type Sort,
20
27
  type Source,
21
28
  type TargetKind,
@@ -23,6 +30,7 @@ import {
23
30
  type TemplateDelimiters,
24
31
  } from "./config.ts";
25
32
  import { CONVENTION_ROWS } from "./convention-rows.ts";
33
+ import { EXEMPTION_CLASS_NAMES, type ExemptionClass } from "./exempt-classes.ts";
26
34
  import { FIXED_SEVERITY_CODES, LIVE_CODES } from "./kinds.ts";
27
35
  import {
28
36
  checkDocument,
@@ -32,15 +40,14 @@ import {
32
40
  SEVERITY_SECTION,
33
41
  type KeyNode,
34
42
  } from "./schema.ts";
43
+ import { bytewise } from "./text/compare.ts";
44
+ import { isRecord } from "./json-document.ts";
45
+ import { message } from "./product.ts";
35
46
 
36
47
  /** Names the command-line flags in a refusal, where a document is named by its path. */
37
48
  const FLAGS_LABEL = "the command-line flags";
38
49
 
39
- /** The length of a severity key naming a whole family: the prefix and two digits. */
40
- export const FAMILY_KEY_LENGTH = 4;
41
-
42
50
  const SEVERITY_KEY = /^DS[0-9]{2}([0-9]{2})?$/u;
43
- const EXEMPTION_CLASS = /^[a-z][a-z0-9-]*$/u;
44
51
  const CONTRACT_VERSION_PATTERN = /^[0-9]+\.[0-9]+\.[0-9]+$/u;
45
52
  const ANALYZER_NAME = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/u;
46
53
  const PROVIDER_SOURCE = /^(go|npm):[^ \t\r\n]+$/u;
@@ -69,12 +76,8 @@ const PROJECT_SEGMENT = String.raw`(?:[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.[^/
69
76
  const PROJECT_PATH = new RegExp(`^${PROJECT_SEGMENT}(?:/${PROJECT_SEGMENT})*$`, "u");
70
77
 
71
78
  const TARGET_KINDS: readonly TargetKind[] = ["application", "library"];
72
- const CONFIDENCES: readonly Confidence[] = ["certain", "probable", "possible"];
73
79
  const GENERATED_FILES: readonly GeneratedFiles[] = ["exclude", "include"];
74
80
  const CONSUMER_TESTS: readonly ConsumerTests[] = ["test", "production"];
75
- const LANGUAGES: readonly Language[] = ["go", "ts"];
76
- const SEVERITIES: readonly Severity[] = ["allow", "warn", "deny"];
77
- const FORMATS: readonly Format[] = ["text", "json", "github", "sarif", "template"];
78
81
  const SORTS: readonly Sort[] = ["position", "size"];
79
82
  const CASCADES: readonly Cascade[] = ["roots", "full"];
80
83
 
@@ -98,7 +101,7 @@ interface Doc {
98
101
  readonly consumersComplete: boolean | undefined;
99
102
  readonly rootPatterns: readonly string[] | undefined;
100
103
  readonly severity: ReadonlyMap<string, Severity> | undefined;
101
- readonly exemptionsDisabled: readonly string[] | undefined;
104
+ readonly exemptionsDisabled: readonly ExemptionClass[] | undefined;
102
105
  readonly formats: readonly Format[] | undefined;
103
106
  readonly sort: Sort | undefined;
104
107
  readonly cascade: Cascade | undefined;
@@ -134,10 +137,6 @@ function originOf(source: DocSource, path: string): Origin {
134
137
  return { source: source.kind, label: source.label };
135
138
  }
136
139
 
137
- function isRecord(value: unknown): value is Record<string, unknown> {
138
- return typeof value === "object" && value !== null && !Array.isArray(value);
139
- }
140
-
141
140
  function member(parent: Record<string, unknown> | undefined, name: string): unknown {
142
141
  if (parent === undefined || !Object.hasOwn(parent, name)) {
143
142
  return undefined;
@@ -505,7 +504,7 @@ function fixedByContract(code: string): string[] {
505
504
  return FIXED_SEVERITY_CODES.filter(
506
505
  (candidate) =>
507
506
  code === candidate || (code.length === FAMILY_KEY_LENGTH && candidate.startsWith(code)),
508
- ).sort();
507
+ ).sort(bytewise);
509
508
  }
510
509
 
511
510
  /** The codes a refusal names: one on its own, and several with the last joined by and. */
@@ -543,7 +542,7 @@ function readSeverity(
543
542
  return undefined;
544
543
  }
545
544
  const severity = new Map<string, Severity>();
546
- for (const code of Object.keys(object).sort()) {
545
+ for (const code of Object.keys(object).sort(bytewise)) {
547
546
  const path = `${SEVERITY_SECTION}.${code}`;
548
547
  if (!SEVERITY_KEY.test(code)) {
549
548
  throw unimplementedSeverityKey(
@@ -577,26 +576,20 @@ function readSeverity(
577
576
  return severity;
578
577
  }
579
578
 
580
- /** The exemption classes named for switching off. */
579
+ /** The exemption classes named for switching off, each one the vocabulary holds. */
581
580
  function readExemptions(
582
581
  document: Record<string, unknown> | undefined,
583
582
  label: string,
584
- ): readonly string[] | undefined {
583
+ ): readonly ExemptionClass[] | undefined {
585
584
  const exemptions = section(document, "exemptions", "exemptions", label);
586
- const disabled = readStrings(exemptions, "disabled", "exemptions.disabled", label, 0);
587
- if (disabled === undefined) {
588
- return undefined;
589
- }
590
- for (const entry of disabled) {
591
- if (!EXEMPTION_CLASS.test(entry)) {
592
- throw malformed(
593
- label,
594
- "exemptions.disabled",
595
- `${JSON.stringify(entry)} is not an exemption class name`,
596
- );
597
- }
598
- }
599
- return disabled;
585
+ return readStrings(
586
+ exemptions,
587
+ "disabled",
588
+ "exemptions.disabled",
589
+ label,
590
+ 0,
591
+ EXEMPTION_CLASS_NAMES,
592
+ );
600
593
  }
601
594
 
602
595
  /** The members that name a declaration, each belonging to exactly one shape. */
@@ -905,7 +898,7 @@ function parseDocument(text: string, node: KeyNode, label: string): unknown {
905
898
  try {
906
899
  value = JSON.parse(text);
907
900
  } catch (error: unknown) {
908
- throw malformed(label, "", error instanceof Error ? error.message : String(error));
901
+ throw malformed(label, "", message(error));
909
902
  }
910
903
  checkDocument(text, node, label);
911
904
  return value;
@@ -1002,7 +995,7 @@ function settleSeverity(
1002
995
  }
1003
996
  }
1004
997
  const severity = new Map<string, Severity>();
1005
- for (const code of [...codes].sort()) {
998
+ for (const code of [...codes].sort(bytewise)) {
1006
999
  const path = `${SEVERITY_SECTION}.${code}`;
1007
1000
  for (const source of sources) {
1008
1001
  const value = source.doc.severity?.get(code);
@@ -1044,7 +1037,7 @@ function describeSource(
1044
1037
  * print a provenance value the closed key list refuses to read back.
1045
1038
  */
1046
1039
  function checkLabels(provenance: ReadonlyMap<string, Origin>): void {
1047
- for (const path of [...provenance.keys()].sort()) {
1040
+ for (const path of [...provenance.keys()].sort(bytewise)) {
1048
1041
  const origin = provenance.get(path);
1049
1042
  if (origin !== undefined && origin.source !== "default" && origin.label === "") {
1050
1043
  throw malformed(
package/src/roots.ts CHANGED
@@ -43,17 +43,19 @@ import {
43
43
  } from "./configuration-files.ts";
44
44
  import { conventionEntries, type Conventions } from "./conventions.ts";
45
45
  import { entryPoints, type EntryRule } from "./entry-points.ts";
46
- import { globExpression } from "./glob.ts";
47
46
  import type { Host } from "./host.ts";
48
47
  import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
49
48
  import type { Manifest, ManifestEntry } from "./manifest.ts";
50
- import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
49
+ import { ancestors, dirnamePath, relativePath, resolvePath } from "./paths.ts";
51
50
  import { byPosition } from "./position.ts";
52
51
  import { UNANSWERED } from "./query.ts";
53
52
  import type { ProjectView } from "./session.ts";
54
53
  import type { Symbol as TSSymbol, Type } from "@typescript/native/sync";
55
54
  import { sourceFilesOf, type SourceFiles } from "./source-files.ts";
56
55
  import { typeQueryAliases } from "./type-query-alias.ts";
56
+ import { bytewise } from "./text/compare.ts";
57
+ import { holdsManifest } from "./fs/manifest.ts";
58
+ import { globExpression } from "./text/glob.ts";
57
59
 
58
60
  /** Why one declaration is a root. */
59
61
  export type RootKind =
@@ -197,15 +199,8 @@ const SOURCE_EXTENSION = /\.(?:d\.)?[cm]?[jt]sx?$/u;
197
199
 
198
200
  /** Whether no `package.json` sits between one file and the manifest's directory, which holds it. */
199
201
  function ofPackage(host: Host, dir: string, file: string): boolean {
200
- for (let at = dirnamePath(file); at !== dir; at = dirnamePath(at)) {
201
- if (
202
- relativePath(dir, at) === undefined ||
203
- host.kindOf(joinPath(at, "package.json")) === "file"
204
- ) {
205
- return false;
206
- }
207
- }
208
- return true;
202
+ const between = ancestors(dirnamePath(file), dir);
203
+ return between.length > 0 && !between.slice(0, -1).some((at) => holdsManifest(host, at));
209
204
  }
210
205
 
211
206
  /**
@@ -331,14 +326,6 @@ export function matchRef(pattern: string, ref: string): boolean {
331
326
  return matchGlob(Array.from(pattern), Array.from(ref));
332
327
  }
333
328
 
334
- /** Two strings ordered bytewise, which is the order every set of a run is read in. */
335
- function compare(a: string, b: string): number {
336
- if (a === b) {
337
- return 0;
338
- }
339
- return a < b ? -1 : 1;
340
- }
341
-
342
329
  /** The members of one declaration, indexed by the container that holds them. */
343
330
  function childrenOf(held: Inventory): ReadonlyMap<string, readonly InventorySymbol[]> {
344
331
  const byParent = new Map<string, InventorySymbol[]>();
@@ -561,7 +548,7 @@ export function roots<Brand>(
561
548
  const left = sites.get(a.id);
562
549
  const right = sites.get(b.id);
563
550
  const site = left === undefined || right === undefined ? 0 : byPosition(left, right);
564
- return site || compare(a.kind, b.kind) || compare(a.source, b.source);
551
+ return site || bytewise(a.kind, b.kind) || bytewise(a.source, b.source);
565
552
  });
566
553
 
567
554
  return {
package/src/run.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { EXIT_FAILURE, EXIT_USAGE } from "./contract/contract.generated.ts";
1
2
  import { ConfigError, REPOSITORY_DOCUMENT, type Inputs } from "./config.ts";
2
3
  import { DiscoveryError, renderDiagnostic } from "./discover.ts";
3
4
  import type { Host } from "./host.ts";
@@ -12,8 +13,9 @@ import { printRootsVerb } from "./verbs/print-roots.ts";
12
13
  import { ANALYZE_OPTIONS, analyzeVerb } from "./verbs/analyze.ts";
13
14
  import { describeVerb } from "./verbs/describe.ts";
14
15
  import { EXPLAIN_OPTIONS, explainVerb } from "./verbs/explain.ts";
15
- import { EXIT_FAILURE, EXIT_USAGE, type Verb, type VerbOptions } from "./verbs/verb.ts";
16
+ import { type Verb, type VerbOptions } from "./verbs/verb.ts";
16
17
  import { versionVerb } from "./verbs/version.ts";
18
+ import { diagnose, message, PRODUCT } from "./product.ts";
17
19
 
18
20
  /** One output stream of the command line. `process.stdout` and `process.stderr` satisfy it. */
19
21
  export interface Writer {
@@ -36,7 +38,7 @@ const VERBS: readonly {
36
38
  { name: "version", run: versionVerb },
37
39
  ];
38
40
 
39
- const USAGE = `usage: deadset-ts <verb> [options]\nverbs: ${VERBS.map((verb) => verb.name).join(", ")}\n`;
41
+ const USAGE = `usage: ${PRODUCT} <verb> [options]\nverbs: ${VERBS.map((verb) => verb.name).join(", ")}\n`;
40
42
 
41
43
  /**
42
44
  * The tokens whose presence in an option's name makes that option a request to
@@ -140,11 +142,7 @@ function readFileOr(host: Host, path: string, orUndefined: boolean): string | un
140
142
  if (orUndefined) {
141
143
  return undefined;
142
144
  }
143
- throw new ConfigError(
144
- "malformed",
145
- "",
146
- `${path}: ${error instanceof Error ? error.message : String(error)}`,
147
- );
145
+ throw new ConfigError("malformed", "", `${path}: ${message(error)}`);
148
146
  }
149
147
  }
150
148
 
@@ -208,9 +206,7 @@ export function run(
208
206
  ): number {
209
207
  const editing = sourceEditOption(args);
210
208
  if (editing !== undefined) {
211
- err.write(
212
- `deadset-ts: ${editing} is not supported: deadset-ts reports and never edits a source file\n`,
213
- );
209
+ diagnose(err, `${editing} is not supported: ${PRODUCT} reports and never edits a source file`);
214
210
  err.write(USAGE);
215
211
  return EXIT_USAGE;
216
212
  }
@@ -227,7 +223,7 @@ export function run(
227
223
  );
228
224
  }
229
225
  if (name !== undefined) {
230
- err.write(`deadset-ts: unknown verb ${JSON.stringify(name)}\n`);
226
+ diagnose(err, `unknown verb ${JSON.stringify(name)}`);
231
227
  }
232
228
  err.write(USAGE);
233
229
  return EXIT_USAGE;
@@ -273,7 +269,7 @@ function invoke(
273
269
  /** Turns one refusal into the exit code the Contract's table gives it. */
274
270
  function refuse(error: unknown, err: Writer): number {
275
271
  if (error instanceof ConfigError) {
276
- err.write(`deadset-ts: ${error.message}\n`);
272
+ diagnose(err, error.message);
277
273
  err.write(USAGE);
278
274
  return EXIT_USAGE;
279
275
  }
@@ -281,7 +277,7 @@ function refuse(error: unknown, err: Writer): number {
281
277
  for (const diagnostic of error.diagnostics) {
282
278
  err.write(`${renderDiagnostic(diagnostic)}\n`);
283
279
  }
284
- err.write(`deadset-ts: ${error.message}\n`);
280
+ diagnose(err, error.message);
285
281
  return EXIT_FAILURE;
286
282
  }
287
283
  if (error instanceof SetupError) {
@@ -291,7 +287,7 @@ function refuse(error: unknown, err: Writer): number {
291
287
  return EXIT_FAILURE;
292
288
  }
293
289
  if (error instanceof ScopeError) {
294
- err.write(`deadset-ts: ${error.message}\n`);
290
+ diagnose(err, error.message);
295
291
  return EXIT_FAILURE;
296
292
  }
297
293
  throw error;
package/src/schema.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ConfigError } from "./config.ts";
2
+ import { bytewise } from "./text/compare.ts";
2
3
 
3
4
  /**
4
5
  * How one node of the closed key list is written: `leaf`, a scalar or an array of
@@ -157,7 +158,7 @@ function collect(node: KeyNode, at: string, keys: string[], settingsOnly: boolea
157
158
  export function declaredKeys(): string[] {
158
159
  const keys: string[] = [];
159
160
  collect(SCHEMA_ROOT, "", keys, false);
160
- return keys.sort();
161
+ return keys.sort(bytewise);
161
162
  }
162
163
 
163
164
  /**
@@ -169,7 +170,7 @@ export function declaredKeys(): string[] {
169
170
  export function settingPaths(): string[] {
170
171
  const paths: string[] = [];
171
172
  collect(SCHEMA_ROOT, "", paths, true);
172
- return paths.sort();
173
+ return paths.sort(bytewise);
173
174
  }
174
175
 
175
176
  /**
@@ -445,7 +446,7 @@ export function checkDocument(text: string, node: KeyNode, label: string): void
445
446
  */
446
447
  export function nestFlags(flat: Readonly<Record<string, unknown>>, label: string): unknown {
447
448
  const nested: Record<string, unknown> = {};
448
- for (const path of Object.keys(flat).sort()) {
449
+ for (const path of Object.keys(flat).sort(bytewise)) {
449
450
  if (!declaresSetting(path)) {
450
451
  throw unimplementedKey(label, path);
451
452
  }
package/src/scope.ts CHANGED
@@ -2,6 +2,8 @@ import { ConfigError } from "./config.ts";
2
2
  import type { Host } from "./host.ts";
3
3
  import { dirnamePath, isAbsolutePath, joinPath, resolvePath } from "./paths.ts";
4
4
  import { checkDocument, type KeyNode } from "./schema.ts";
5
+ import { isRecord } from "./json-document.ts";
6
+ import { message } from "./product.ts";
5
7
 
6
8
  /** The roles a scope document declares for a module. */
7
9
  const ROLE_TARGET = "target";
@@ -60,10 +62,6 @@ const SCOPE_ROOT: KeyNode = {
60
62
  },
61
63
  };
62
64
 
63
- function isRecord(value: unknown): value is Record<string, unknown> {
64
- return typeof value === "object" && value !== null && !Array.isArray(value);
65
- }
66
-
67
65
  /**
68
66
  * One string member, or undefined where the document omits it. A member the
69
67
  * document writes is a string holding at least one character, so `null` and `""`
@@ -145,9 +143,7 @@ export function readScope(host: Host, file: string): Scope {
145
143
  try {
146
144
  text = host.readFile(absolute);
147
145
  } catch (error: unknown) {
148
- throw new ScopeError(
149
- `scope: ${file}: ${error instanceof Error ? error.message : String(error)}`,
150
- );
146
+ throw new ScopeError(`scope: ${file}: ${message(error)}`);
151
147
  }
152
148
  if (text.length > MAX_DOCUMENT_LENGTH) {
153
149
  throw new ScopeError(`scope: ${file} is longer than ${String(MAX_DOCUMENT_LENGTH)} characters`);
@@ -156,9 +152,7 @@ export function readScope(host: Host, file: string): Scope {
156
152
  try {
157
153
  value = JSON.parse(text);
158
154
  } catch (error: unknown) {
159
- throw new ScopeError(
160
- `scope: ${file}: ${error instanceof Error ? error.message : String(error)}`,
161
- );
155
+ throw new ScopeError(`scope: ${file}: ${message(error)}`);
162
156
  }
163
157
  if (!isRecord(value)) {
164
158
  throw new ScopeError(`scope: ${file} is not one JSON object`);
@@ -53,6 +53,7 @@ import {
53
53
  type SourceFile,
54
54
  type SpreadAssignment,
55
55
  } from "@typescript/native/ast";
56
+ import { parenthesesRemoved } from "./unwrap.ts";
56
57
  import {
57
58
  SignatureKind,
58
59
  SymbolFlags,
@@ -227,12 +228,6 @@ interface MethodForm {
227
228
  readonly invocation: Invocation;
228
229
  }
229
230
 
230
- function unparenthesized(expression: Expression): Expression {
231
- return isParenthesizedExpression(expression)
232
- ? unparenthesized(expression.expression)
233
- : expression;
234
- }
235
-
236
231
  /**
237
232
  * The invocation a call writes through a method of `Function.prototype`, read from
238
233
  * the syntax alone: `f.call(self, a)`, `f.apply(self, [a])`, `f.bind(self, a)`, which
@@ -241,12 +236,12 @@ function unparenthesized(expression: Expression): Expression {
241
236
  * carries. Whether the method is the prototype's is resolution's to say.
242
237
  */
243
238
  function throughMethod(call: CallExpression): MethodForm | undefined {
244
- const expression = unparenthesized(call.expression);
239
+ const expression = parenthesesRemoved(call.expression);
245
240
  if (isPropertyAccessExpression(expression) && FUNCTION_METHODS.has(expression.name.text)) {
246
241
  const [, ...rest] = call.arguments;
247
242
  let passed: readonly Expression[] = rest;
248
243
  if (expression.name.text === "apply") {
249
- const list = rest[0] === undefined ? undefined : unparenthesized(rest[0]);
244
+ const list = rest[0] === undefined ? undefined : parenthesesRemoved(rest[0]);
250
245
  passed = list === undefined ? [] : isArrayLiteralExpression(list) ? list.elements : [list];
251
246
  }
252
247
  return {
@@ -255,7 +250,7 @@ function throughMethod(call: CallExpression): MethodForm | undefined {
255
250
  };
256
251
  }
257
252
  if (isCallExpression(expression)) {
258
- const bound = unparenthesized(expression.expression);
253
+ const bound = parenthesesRemoved(expression.expression);
259
254
  if (isPropertyAccessExpression(bound) && bound.name.text === "bind") {
260
255
  return {
261
256
  method: bound.name,
package/src/session.ts CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  type Queries,
27
27
  type UnansweredQuestion,
28
28
  } from "./query.ts";
29
+ import { MANIFEST } from "./fs/manifest.ts";
29
30
 
30
31
  /**
31
32
  * The compiler the analysis reads through: a client over a protocol to a separate
@@ -115,7 +116,7 @@ export function openEngine(options: EngineOptions): Engine {
115
116
  if (components !== undefined && components.extensions.length > 0) {
116
117
  const made = components.host.temporaryDirectory();
117
118
  try {
118
- components.host.writeDocument(joinPath(made.path, "package.json"), [
119
+ components.host.writeDocument(joinPath(made.path, MANIFEST), [
119
120
  mapperManifest(components.host.componentMapperCommand()),
120
121
  ]);
121
122
  } catch (error: unknown) {
@@ -1,15 +1,13 @@
1
+ import { SETUP_FAILURE_PREFIX, type SETUP_CLASSES } from "./contract/contract.generated.ts";
2
+
1
3
  /**
2
4
  * A setup failure: a file or a component the analysis needs and the project does not
3
5
  * provide. Each one names what is missing and the fix, on one line of the error stream
4
6
  * that starts with its class.
5
7
  */
6
8
 
7
- /** The classes of setup failure this analyzer meets. */
8
- type SetupClass =
9
- | "missing-module"
10
- | "missing-consumer"
11
- | "workspace-member-without-source"
12
- | "convention-not-literal";
9
+ /** A class of setup failure the Contract names. */
10
+ type SetupClass = (typeof SETUP_CLASSES)[number]["class"];
13
11
 
14
12
  export interface SetupFailure {
15
13
  readonly setupClass: SetupClass;
@@ -19,7 +17,7 @@ export interface SetupFailure {
19
17
 
20
18
  /** The failure as the one line of the error stream that names it. */
21
19
  export function setupLine(failure: SetupFailure): string {
22
- return `setup failure: ${failure.setupClass}: ${failure.detail}`;
20
+ return `${SETUP_FAILURE_PREFIX}${failure.setupClass}: ${failure.detail}`;
23
21
  }
24
22
 
25
23
  /**
@@ -5,18 +5,17 @@
5
5
  * ignore file by gaining a maintainer's reason.
6
6
  */
7
7
 
8
+ import { CODE } from "./contract/contract.generated.ts";
8
9
  import type { Host } from "./host.ts";
9
10
  import type { InventorySymbol } from "./inventory.ts";
10
11
  import { jsonChunks } from "./json-chunks.ts";
11
12
  import { joinPath } from "./paths.ts";
12
- import { positionAt, walkDocument } from "./json-document.ts";
13
+ import { isRecord, positionAt, walkDocument } from "./json-document.ts";
13
14
  import type { Position } from "./position.ts";
14
15
  import { declarationKey } from "./ref.ts";
15
16
  import {
16
17
  CODE_FORM,
17
- SUPPRESSION_WITHOUT_REASON,
18
18
  SuppressionError,
19
- UNSCOPED_ENTRY,
20
19
  blankReason,
21
20
  isEntryPath,
22
21
  isPartKind,
@@ -27,6 +26,7 @@ import {
27
26
  type SuppressionRecord,
28
27
  type Suppressions,
29
28
  } from "./suppress.ts";
29
+ import { message } from "./product.ts";
30
30
 
31
31
  /** The name and the location the grammar fixes for the ignore file. */
32
32
  export const IGNORE_FILE = "deadset-ignore.json";
@@ -80,10 +80,6 @@ function documentSite(shape: Shape): Position {
80
80
  return { path: shape.file, line: 0, column: 0 };
81
81
  }
82
82
 
83
- function isObject(value: unknown): value is Record<string, unknown> {
84
- return typeof value === "object" && value !== null && !Array.isArray(value);
85
- }
86
-
87
83
  /** What one entry or row resolves to. */
88
84
  interface Read {
89
85
  readonly records: readonly SuppressionRecord[];
@@ -101,7 +97,7 @@ function readRecord(value: unknown, site: Position, byReference: ByReference, sh
101
97
  const refuse = (text: string, want: string): never => {
102
98
  throw new SuppressionError(shape.mechanism, siteText(site), text, want);
103
99
  };
104
- if (!isObject(value)) {
100
+ if (!isRecord(value)) {
105
101
  return refuse(JSON.stringify(value), shape.recordWant);
106
102
  }
107
103
  for (const [key, held] of Object.entries(value)) {
@@ -148,10 +144,10 @@ function readRecord(value: unknown, site: Position, byReference: ByReference, sh
148
144
  };
149
145
  const refusals: Refusal[] = [];
150
146
  if (blankReason(reason)) {
151
- refusals.push({ ...written, reported: SUPPRESSION_WITHOUT_REASON });
147
+ refusals.push({ ...written, reported: CODE.suppressionWithoutReason });
152
148
  }
153
149
  if (written.path === "") {
154
- refusals.push({ ...written, reported: UNSCOPED_ENTRY });
150
+ refusals.push({ ...written, reported: CODE.unscopedIgnoreEntry });
155
151
  }
156
152
  if (refusals.length > 0) {
157
153
  return { records: [], refusals };
@@ -201,9 +197,9 @@ function readDocument(
201
197
  try {
202
198
  parsed = JSON.parse(text);
203
199
  } catch (error: unknown) {
204
- return malformed(`${shape.file}: ${error instanceof Error ? error.message : String(error)}`);
200
+ return malformed(`${shape.file}: ${message(error)}`);
205
201
  }
206
- if (!isObject(parsed)) {
202
+ if (!isRecord(parsed)) {
207
203
  return malformed(shape.file);
208
204
  }
209
205
  const walk = walkDocument(text, shape.array);
@@ -329,9 +325,9 @@ export function otherSchemeRows(text: string, own: string = OWN_SCHEME): readonl
329
325
  try {
330
326
  parsed = JSON.parse(text);
331
327
  } catch (error: unknown) {
332
- return refuse(error instanceof Error ? error.message : String(error));
328
+ return refuse(message(error));
333
329
  }
334
- const held = isObject(parsed) ? parsed[BASELINE_SHAPE.array] : undefined;
330
+ const held = isRecord(parsed) ? parsed[BASELINE_SHAPE.array] : undefined;
335
331
  if (!Array.isArray(held)) {
336
332
  return refuse(BASELINE_SHAPE.documentWant);
337
333
  }
@@ -339,7 +335,7 @@ export function otherSchemeRows(text: string, own: string = OWN_SCHEME): readonl
339
335
  try {
340
336
  readRecord(value, documentSite(BASELINE_SHAPE), new Map(), BASELINE_SHAPE);
341
337
  } catch (error: unknown) {
342
- return refuse(error instanceof Error ? error.message : String(error));
338
+ return refuse(message(error));
343
339
  }
344
340
  // readRecord has checked every member is a string and the code and symbol are present.
345
341
  const { code, symbol, path, reason } = value as Readonly<Record<string, string>>;
@@ -4,6 +4,7 @@
4
4
  * below it, or on its own line where it trails a token there.
5
5
  */
6
6
 
7
+ import { CODE } from "./contract/contract.generated.ts";
7
8
  import {
8
9
  isConstructorDeclaration,
9
10
  SyntaxKind,
@@ -18,7 +19,6 @@ import type { ProjectView } from "./session.ts";
18
19
  import { signatureOf } from "./signatures.ts";
19
20
  import {
20
21
  isPartKind,
21
- SUPPRESSION_WITHOUT_REASON,
22
22
  SuppressionError,
23
23
  siteText,
24
24
  type Refusal,
@@ -354,7 +354,7 @@ export function inlineSuppressions(
354
354
  case "no-reason":
355
355
  for (const code of directive.codes) {
356
356
  refusals.push({
357
- reported: SUPPRESSION_WITHOUT_REASON,
357
+ reported: CODE.suppressionWithoutReason,
358
358
  code,
359
359
  symbol: "",
360
360
  path: site.path,