@cplieger/deadset-ts 8.0.0-dev.1 → 8.0.0-dev.11

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 (276) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/mapper-connection.js +2 -1
  3. package/dist/bin/node-host.js +24 -5
  4. package/dist/src/analysis.js +181 -341
  5. package/dist/src/augmentations.js +106 -13
  6. package/dist/src/automatic-types.js +10 -29
  7. package/dist/src/build-scripts.js +425 -0
  8. package/dist/src/component-layer.js +4 -2
  9. package/dist/src/components.js +2 -3
  10. package/dist/src/config.js +6 -82
  11. package/dist/src/configuration-files.js +28 -176
  12. package/dist/src/configured-declarations.js +15 -20
  13. package/dist/src/conformance-record.js +1 -1
  14. package/dist/src/consumers.js +3 -30
  15. package/dist/src/contextual-uses.js +56 -9
  16. package/dist/src/contract/config.generated.js +558 -0
  17. package/dist/src/contract/contract.generated.js +1244 -0
  18. package/dist/src/contract/report.generated.js +862 -0
  19. package/dist/src/convention-rows.js +73 -3
  20. package/dist/src/conventions.js +36 -66
  21. package/dist/src/dependencies.js +103 -40
  22. package/dist/src/edges.js +38 -122
  23. package/dist/src/element-access.js +4 -6
  24. package/dist/src/entry-point-gaps.js +2 -8
  25. package/dist/src/entry-points.js +11 -27
  26. package/dist/src/enum-group.js +3 -10
  27. package/dist/src/erased-stores.js +4 -6
  28. package/dist/src/exempt-classes.js +1 -63
  29. package/dist/src/exempt.js +21 -40
  30. package/dist/src/failure.js +36 -0
  31. package/dist/src/file-facts.js +2 -25
  32. package/dist/src/findings/completion.js +139 -72
  33. package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
  34. package/dist/src/findings/emitters.js +5 -5
  35. package/dist/src/findings/interfaces.js +4 -5
  36. package/dist/src/findings/intra-function.js +13 -10
  37. package/dist/src/findings/non-code-artifacts.js +7 -8
  38. package/dist/src/findings/reads-and-writes.js +16 -13
  39. package/dist/src/findings/self-check.js +10 -12
  40. package/dist/src/findings/unused-declarations.js +41 -32
  41. package/dist/src/findings/visibility-narrowing.js +19 -16
  42. package/dist/src/findings-pass.js +8 -15
  43. package/dist/src/framework-lifecycle.js +7 -7
  44. package/dist/src/fs/document.js +52 -0
  45. package/dist/src/fs/manifest.js +72 -0
  46. package/dist/src/fs/tree.js +88 -0
  47. package/dist/src/generated-file.js +7 -4
  48. package/dist/src/git-ignore.js +39 -26
  49. package/dist/src/graph.js +5 -3
  50. package/dist/src/html-entries.js +6 -33
  51. package/dist/src/in-operands.js +2 -1
  52. package/dist/src/index.js +1 -1
  53. package/dist/src/injection-container.js +1 -1
  54. package/dist/src/intra-function-parts.js +63 -43
  55. package/dist/src/inventory.js +2 -21
  56. package/dist/src/javascript-file.js +33 -0
  57. package/dist/src/json-document.js +198 -67
  58. package/dist/src/keyof-comparisons.js +4 -6
  59. package/dist/src/kinds.js +3 -366
  60. package/dist/src/load/classify.js +270 -0
  61. package/dist/src/load/diagnostics.js +80 -0
  62. package/dist/src/load/discover.js +277 -0
  63. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  64. package/dist/src/load/expected-module.js +136 -0
  65. package/dist/src/load/load.js +27 -0
  66. package/dist/src/load/module-lookup.js +37 -0
  67. package/dist/src/load/plan.js +313 -0
  68. package/dist/src/load/specifiers.js +115 -0
  69. package/dist/src/load/synthesize.js +140 -0
  70. package/dist/src/load/test-globals.js +207 -0
  71. package/dist/src/load/test-joins.js +82 -0
  72. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  73. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +89 -66
  74. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +23 -65
  75. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  76. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  77. package/dist/src/manifest.js +49 -54
  78. package/dist/src/matrix.js +76 -4
  79. package/dist/src/mdx-entries.js +3 -26
  80. package/dist/src/outside-tests.js +1 -1
  81. package/dist/src/partial.js +2 -1
  82. package/dist/src/paths.js +26 -15
  83. package/dist/src/position.js +2 -1
  84. package/dist/src/print.js +11 -79
  85. package/dist/src/product.js +13 -0
  86. package/dist/src/query.js +1 -0
  87. package/dist/src/ref.js +1 -0
  88. package/dist/src/references.js +48 -30
  89. package/dist/src/report-schema.js +51 -0
  90. package/dist/src/report.js +57 -41
  91. package/dist/src/reporters/annotations.js +15 -8
  92. package/dist/src/reporters/sarif.js +16 -10
  93. package/dist/src/reporters/template.js +4 -19
  94. package/dist/src/resolve.js +245 -729
  95. package/dist/src/roots.js +273 -87
  96. package/dist/src/run.js +66 -77
  97. package/dist/src/schema.js +320 -343
  98. package/dist/src/scope.js +38 -115
  99. package/dist/src/serialization-contract.js +595 -21
  100. package/dist/src/session.js +34 -29
  101. package/dist/src/source-files.js +1 -1
  102. package/dist/src/suppress-file.js +25 -28
  103. package/dist/src/suppress-inline.js +3 -2
  104. package/dist/src/suppress.js +33 -51
  105. package/dist/src/sweep.js +7 -9
  106. package/dist/src/symbol-keys.js +3 -5
  107. package/dist/src/template-field.js +9 -19
  108. package/dist/src/test-support.js +8 -11
  109. package/dist/src/text/compare.js +26 -0
  110. package/dist/src/text/glob.js +194 -0
  111. package/dist/src/text/named-files.js +54 -0
  112. package/dist/src/text/shell.js +159 -0
  113. package/dist/src/text/yaml.js +393 -0
  114. package/dist/src/type-errors.js +11 -232
  115. package/dist/src/unseen-loads.js +100 -0
  116. package/dist/src/unwrap.js +23 -0
  117. package/dist/src/usage.js +10 -0
  118. package/dist/src/verbs/analyze.js +166 -126
  119. package/dist/src/verbs/describe.js +9 -16
  120. package/dist/src/verbs/explain.js +10 -7
  121. package/dist/src/verbs/print-config.js +2 -1
  122. package/dist/src/verbs/print-projects.js +6 -4
  123. package/dist/src/verbs/print-retained.js +4 -2
  124. package/dist/src/verbs/print-roots.js +4 -2
  125. package/dist/src/verbs/verb.js +1 -5
  126. package/dist/src/verbs/version.js +14 -3
  127. package/dist/src/workflow-steps.js +62 -64
  128. package/jsr.json +1 -1
  129. package/package.json +5 -4
  130. package/src/analysis.ts +245 -502
  131. package/src/augmentations.ts +135 -17
  132. package/src/automatic-types.ts +11 -29
  133. package/src/build-scripts.ts +543 -0
  134. package/src/component-layer.ts +4 -2
  135. package/src/components.ts +2 -4
  136. package/src/config.ts +31 -302
  137. package/src/configuration-files.ts +34 -179
  138. package/src/configured-declarations.ts +18 -23
  139. package/src/conformance-record.ts +1 -1
  140. package/src/consumers.ts +6 -57
  141. package/src/contextual-uses.ts +64 -12
  142. package/src/contract/config.generated.ts +818 -0
  143. package/src/contract/contract.generated.ts +1308 -0
  144. package/src/contract/report.generated.ts +1237 -0
  145. package/src/convention-rows.ts +85 -3
  146. package/src/conventions.ts +46 -84
  147. package/src/dependencies.ts +118 -46
  148. package/src/edges.ts +41 -140
  149. package/src/element-access.ts +4 -7
  150. package/src/entry-point-gaps.ts +3 -9
  151. package/src/entry-points.ts +12 -38
  152. package/src/enum-group.ts +2 -11
  153. package/src/erased-stores.ts +3 -7
  154. package/src/exempt-classes.ts +1 -75
  155. package/src/exempt.ts +36 -50
  156. package/src/failure.ts +63 -0
  157. package/src/file-facts.ts +9 -24
  158. package/src/finding.ts +14 -9
  159. package/src/findings/boundary.ts +2 -0
  160. package/src/findings/completion.ts +181 -84
  161. package/src/findings/dependencies-and-module-machinery.ts +4 -7
  162. package/src/findings/emitter.ts +8 -9
  163. package/src/findings/emitters.ts +7 -7
  164. package/src/findings/interfaces.ts +4 -6
  165. package/src/findings/intra-function.ts +13 -11
  166. package/src/findings/non-code-artifacts.ts +9 -11
  167. package/src/findings/reads-and-writes.ts +20 -16
  168. package/src/findings/self-check.ts +11 -17
  169. package/src/findings/unused-declarations.ts +45 -32
  170. package/src/findings/visibility-narrowing.ts +20 -17
  171. package/src/findings-pass.ts +10 -19
  172. package/src/framework-lifecycle.ts +7 -7
  173. package/src/fs/document.ts +70 -0
  174. package/src/fs/manifest.ts +92 -0
  175. package/src/fs/tree.ts +110 -0
  176. package/src/generated-file.ts +7 -4
  177. package/src/git-ignore.ts +50 -26
  178. package/src/graph.ts +5 -3
  179. package/src/host.ts +12 -5
  180. package/src/html-entries.ts +6 -33
  181. package/src/in-operands.ts +2 -1
  182. package/src/index.ts +1 -1
  183. package/src/injection-container.ts +1 -1
  184. package/src/intra-function-parts.ts +73 -44
  185. package/src/inventory.ts +2 -20
  186. package/src/javascript-file.ts +37 -0
  187. package/src/json-document.ts +241 -76
  188. package/src/keyof-comparisons.ts +3 -7
  189. package/src/kinds.ts +5 -413
  190. package/src/load/classify.ts +397 -0
  191. package/src/load/diagnostics.ts +144 -0
  192. package/src/load/discover.ts +455 -0
  193. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  194. package/src/load/expected-module.ts +188 -0
  195. package/src/load/load.ts +73 -0
  196. package/src/load/module-lookup.ts +50 -0
  197. package/src/load/plan.ts +504 -0
  198. package/src/load/specifiers.ts +168 -0
  199. package/src/load/synthesize.ts +211 -0
  200. package/src/load/test-globals.ts +251 -0
  201. package/src/load/test-joins.ts +98 -0
  202. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  203. package/src/{workspace-programs.ts → load/workspace-programs.ts} +128 -85
  204. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +33 -76
  205. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  206. package/src/{workspace.ts → load/workspace.ts} +90 -134
  207. package/src/manifest.ts +81 -75
  208. package/src/matrix.ts +122 -4
  209. package/src/mdx-entries.ts +3 -26
  210. package/src/outside-tests.ts +1 -1
  211. package/src/partial.ts +3 -2
  212. package/src/paths.ts +27 -15
  213. package/src/position.ts +2 -1
  214. package/src/print.ts +12 -92
  215. package/src/product.ts +18 -0
  216. package/src/query.ts +11 -0
  217. package/src/ref.ts +1 -0
  218. package/src/references.ts +52 -31
  219. package/src/report-schema.ts +68 -0
  220. package/src/report.ts +101 -224
  221. package/src/reporters/annotations.ts +22 -11
  222. package/src/reporters/json.ts +1 -1
  223. package/src/reporters/reporter.ts +2 -4
  224. package/src/reporters/reporters.ts +1 -1
  225. package/src/reporters/sarif.ts +29 -16
  226. package/src/reporters/template.ts +4 -20
  227. package/src/reporters/text.ts +3 -3
  228. package/src/resolve.ts +283 -1114
  229. package/src/roots.ts +339 -104
  230. package/src/run.ts +75 -91
  231. package/src/schema.ts +391 -388
  232. package/src/scope.ts +46 -138
  233. package/src/serialization-contract.ts +763 -22
  234. package/src/session.ts +70 -54
  235. package/src/source-files.ts +1 -1
  236. package/src/suppress-file.ts +24 -33
  237. package/src/suppress-inline.ts +2 -2
  238. package/src/suppress.ts +43 -58
  239. package/src/sweep.ts +23 -9
  240. package/src/symbol-keys.ts +2 -5
  241. package/src/template-field.ts +15 -25
  242. package/src/test-support.ts +17 -13
  243. package/src/text/compare.ts +27 -0
  244. package/src/text/glob.ts +210 -0
  245. package/src/text/named-files.inputs.json +1522 -0
  246. package/src/text/named-files.ts +68 -0
  247. package/src/text/shell.ts +161 -0
  248. package/src/text/yaml.ts +423 -0
  249. package/src/type-errors.ts +18 -288
  250. package/src/unseen-loads.ts +134 -0
  251. package/src/unwrap.ts +34 -0
  252. package/src/usage.ts +10 -0
  253. package/src/verbs/analyze.ts +224 -162
  254. package/src/verbs/describe.ts +14 -15
  255. package/src/verbs/explain.ts +10 -11
  256. package/src/verbs/print-config.ts +2 -1
  257. package/src/verbs/print-projects.ts +6 -4
  258. package/src/verbs/print-retained.ts +4 -2
  259. package/src/verbs/print-roots.ts +4 -2
  260. package/src/verbs/verb.ts +2 -8
  261. package/src/verbs/version.ts +15 -3
  262. package/src/workflow-steps.ts +74 -64
  263. package/dist/src/base-url.js +0 -18
  264. package/dist/src/discover.js +0 -468
  265. package/dist/src/glob.js +0 -65
  266. package/dist/src/pnpm-workspace.js +0 -120
  267. package/dist/src/setup-failure.js +0 -23
  268. package/dist/src/test-joins.js +0 -101
  269. package/dist/src/version.js +0 -7
  270. package/src/base-url.ts +0 -24
  271. package/src/discover.ts +0 -653
  272. package/src/glob.ts +0 -67
  273. package/src/pnpm-workspace.ts +0 -135
  274. package/src/setup-failure.ts +0 -39
  275. package/src/test-joins.ts +0 -120
  276. package/src/version.ts +0 -7
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The loads of a project's production files whose target the analysis cannot resolve: an
3
+ * `import()` or `require()` call whose path is computed and starts with no relative literal
4
+ * naming a directory below the calling file's. The files such a load runs are roots the
5
+ * analysis does not see, so the configuration's roots are never complete.
6
+ */
7
+ import { isBinaryExpression, isCallExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isStringLiteral, isTemplateExpression, SyntaxKind, } from "@typescript/native/ast";
8
+ import { dirnamePath, relativePath, resolvePath } from "./paths.js";
9
+ import { renderPosition } from "./position.js";
10
+ import { UNANSWERED } from "./query.js";
11
+ import { sourceFilesOf } from "./source-files.js";
12
+ import { unwrapped } from "./unwrap.js";
13
+ /** The literal text a computed path starts with: a template's head, or a concatenation's first literal. */
14
+ export function leadingLiteral(node) {
15
+ const at = unwrapped(node);
16
+ if (isTemplateExpression(at)) {
17
+ return at.head.text;
18
+ }
19
+ if (isBinaryExpression(at) && at.operatorToken.kind === SyntaxKind.PlusToken) {
20
+ const left = unwrapped(at.left);
21
+ return isStringLiteral(left) || isNoSubstitutionTemplateLiteral(left)
22
+ ? left.text
23
+ : leadingLiteral(at.left);
24
+ }
25
+ return undefined;
26
+ }
27
+ /**
28
+ * The directory below `dir` a computed load's leading literal names, which the entry
29
+ * rules root every file of, or none where it names a package, `dir` itself or a
30
+ * directory holding it, each of which would name files no rule roots.
31
+ */
32
+ export function computedLoadDirectory(dir, prefix) {
33
+ if (!(prefix.startsWith("./") || prefix.startsWith("../")) || !prefix.endsWith("/")) {
34
+ return undefined;
35
+ }
36
+ const target = resolvePath(dir, prefix);
37
+ return target === dir || relativePath(target, dir) !== undefined ? undefined : target;
38
+ }
39
+ /** Whether a computed path's leading literal names a package, whose files are none of the target's. */
40
+ function namesPackage(prefix) {
41
+ return (prefix !== undefined &&
42
+ prefix !== "" &&
43
+ !prefix.startsWith(".") &&
44
+ !prefix.startsWith("/") &&
45
+ !/^[A-Za-z][A-Za-z\d+.-]*:/u.test(prefix));
46
+ }
47
+ /**
48
+ * Every unresolved load the project's production files write, in file then source order.
49
+ * `testFiles` holds the test files by their path below the target root. A `require` call
50
+ * loads a module only where the name resolves to no declaration of the project's own
51
+ * files: one the project declares is its own function.
52
+ */
53
+ export function unseenLoadsOf(project, configuration, targetRoot, testFiles) {
54
+ const tests = new Set(testFiles);
55
+ const sites = [];
56
+ for (const [path, file] of sourceFilesOf(project, targetRoot).byPath) {
57
+ if (tests.has(path) || file.isDeclarationFile) {
58
+ continue;
59
+ }
60
+ const dir = dirnamePath(file.fileName);
61
+ const visit = (node) => {
62
+ if (isCallExpression(node)) {
63
+ const callee = node.expression;
64
+ const required = isIdentifier(callee) && callee.text === "require";
65
+ const [first] = node.arguments;
66
+ const argument = first === undefined ? undefined : unwrapped(first);
67
+ if ((callee.kind === SyntaxKind.ImportKeyword || required) &&
68
+ argument !== undefined &&
69
+ !isStringLiteral(argument) &&
70
+ !isNoSubstitutionTemplateLiteral(argument)) {
71
+ const prefix = leadingLiteral(argument);
72
+ const seen = namesPackage(prefix) ||
73
+ (prefix !== undefined && computedLoadDirectory(dir, prefix) !== undefined);
74
+ if (!seen) {
75
+ sites.push({
76
+ position: renderPosition(file, targetRoot, node.getStart()),
77
+ require: required ? callee : undefined,
78
+ });
79
+ }
80
+ }
81
+ }
82
+ node.forEachChild(visit);
83
+ };
84
+ file.forEachChild(visit);
85
+ }
86
+ const requires = sites.flatMap((site) => (site.require === undefined ? [] : [site.require]));
87
+ const own = project.ownPaths();
88
+ const local = new Set();
89
+ project.symbolsAt(requires.map((node) => project.handle(node))).forEach((symbol, at) => {
90
+ const node = requires[at];
91
+ if (node !== undefined &&
92
+ symbol !== UNANSWERED &&
93
+ symbol?.declarations.some((handle) => own.has(handle.path)) === true) {
94
+ local.add(node);
95
+ }
96
+ });
97
+ return sites
98
+ .filter((site) => site.require === undefined || !local.has(site.require))
99
+ .map((site) => ({ configuration, position: site.position }));
100
+ }
@@ -0,0 +1,23 @@
1
+ import { isAsExpression, isNonNullExpression, isParenthesizedExpression, isSatisfiesExpression, } from "@typescript/native/ast";
2
+ /** The expression inside every pair of parentheses around `node`. */
3
+ export function parenthesesRemoved(node) {
4
+ let at = node;
5
+ while (isParenthesizedExpression(at)) {
6
+ at = at.expression;
7
+ }
8
+ return at;
9
+ }
10
+ /**
11
+ * The expression inside every wrapper around `node` that changes no value it evaluates to:
12
+ * parentheses, `as` and `satisfies` assertions and the non-null assertion.
13
+ */
14
+ export function unwrapped(node) {
15
+ let at = node;
16
+ while (isParenthesizedExpression(at) ||
17
+ isAsExpression(at) ||
18
+ isSatisfiesExpression(at) ||
19
+ isNonNullExpression(at)) {
20
+ at = at.expression;
21
+ }
22
+ return at;
23
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * A refusal of the invocation: the fix is to the command line or to a document it
3
+ * names, so the run ends with the usage code before any analysis.
4
+ */
5
+ export class UsageError extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "UsageError";
9
+ }
10
+ }
@@ -1,18 +1,24 @@
1
+ import { CONTRACT_VERSION, EXIT_CLEAN } from "../contract/contract.generated.js";
1
2
  import { runAnalysis, runBaselineWrite, unansweredCounts, } from "../analysis.js";
2
- import { ConfigError, kindOf } from "../config.js";
3
+ import { kindOf } from "../config.js";
3
4
  import { CONFORMANCE, DECLARED_GAPS } from "../conformance.js";
4
- import { moduleIdentity } from "../consumers.js";
5
5
  import { analyzerProvenance, verdictOf } from "../findings-pass.js";
6
- import { joinPath, normalizePath, relativePath, resolvePath } from "../paths.js";
6
+ import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "../paths.js";
7
+ import { moduleIdentity } from "../load/discover.js";
7
8
  import { partialNotes } from "../partial.js";
8
- import { buildReport, LANGUAGE, capFindings, sortFindings, } from "../report.js";
9
+ import { buildReport, capFindings, sortFindings, } from "../report.js";
10
+ import { reportRefusals } from "../report-schema.js";
9
11
  import { json } from "../reporters/json.js";
10
12
  import { RENDERINGS } from "../reporters/reporters.js";
11
13
  import { parseTemplate, TemplateError } from "../reporters/template.js";
12
14
  import { resolve } from "../resolve.js";
13
15
  import { otherSchemeRows, rowReason, writeBaseline } from "../suppress-file.js";
14
- import { CONTRACT_VERSION } from "../version.js";
15
- import { EXIT_CLEAN, EXIT_FAILURE } from "./verb.js";
16
+ import {} from "./verb.js";
17
+ import { UsageError } from "../usage.js";
18
+ import { bytewise } from "../text/compare.js";
19
+ import { productFault, failureExit } from "../failure.js";
20
+ import { diagnose, message } from "../product.js";
21
+ import { ownVersion } from "./version.js";
16
22
  /** The options `analyze` takes beside the ones every verb takes. */
17
23
  export const ANALYZE_OPTIONS = {
18
24
  plain: ["report", "baseline-write", "exit-code", "template"],
@@ -21,16 +27,9 @@ export const ANALYZE_OPTIONS = {
21
27
  /** The two values `--exit-code` takes; on, the default, puts the run's verdict in the exit code. */
22
28
  const EXIT_CODE_ON = "on";
23
29
  const EXIT_CODE_OFF = "off";
24
- /** A path of the run no report can name, which ends the run with the failure code. */
25
- class ReportPathError extends Error {
26
- constructor(message) {
27
- super(message);
28
- this.name = "ReportPathError";
29
- }
30
- }
31
30
  /** The formats this analyzer renders, for a message that names them. */
32
31
  function formatNames() {
33
- return [...RENDERINGS.keys()].sort().join(", ");
32
+ return [...RENDERINGS.keys()].sort(bytewise).join(", ");
34
33
  }
35
34
  /** The renderings one invocation writes: the ones it named, else the configuration's. */
36
35
  function formatsOf(named, configured) {
@@ -38,10 +37,10 @@ function formatsOf(named, configured) {
38
37
  const seen = new Set();
39
38
  for (const format of formats) {
40
39
  if (!RENDERINGS.has(format)) {
41
- throw new ConfigError("malformed", "", `${JSON.stringify(format)} is not a format this analyzer renders: the formats are ${formatNames()}`);
40
+ throw new UsageError(`${JSON.stringify(format)} is not a format this analyzer renders: the formats are ${formatNames()}`);
42
41
  }
43
42
  if (seen.has(format)) {
44
- throw new ConfigError("malformed", "", `the format ${JSON.stringify(format)} is named twice`);
43
+ throw new UsageError(`the format ${JSON.stringify(format)} is named twice`);
45
44
  }
46
45
  seen.add(format);
47
46
  }
@@ -58,7 +57,7 @@ function runRelative(invoked, path) {
58
57
  }
59
58
  const below = relativePath(invoked, path);
60
59
  if (below === undefined) {
61
- throw new ReportPathError(`a report names its target relative to the directory the run was invoked from, and ${invoked} does not contain ${path}: run from a directory that does`);
60
+ throw failureExit("unreportable-path", `a report names its target relative to the directory the run was invoked from, and ${invoked} does not contain ${path}: run from a directory that does`);
62
61
  }
63
62
  return below;
64
63
  }
@@ -77,12 +76,61 @@ function unmappedNotes(entries, targetRoot) {
77
76
  };
78
77
  });
79
78
  }
79
+ /**
80
+ * One note per load of a production file the run cannot resolve, each site once: a file
81
+ * it loads is a root the run does not see, so `ts.roots_complete` lifts no cap there.
82
+ */
83
+ function unseenLoadNotes(loads) {
84
+ return [
85
+ ...new Map(loads.map((load) => {
86
+ const { path, line, column } = load.position;
87
+ const site = `${path}:${String(line)}:${String(column)}`;
88
+ const note = {
89
+ kind: "computed-load",
90
+ path: dirnamePath(path),
91
+ message: `${site} loads a module by a computed path the analysis cannot resolve, so the configurations compiling it report what no root reaches at probable at most, whatever ts.roots_complete says`,
92
+ };
93
+ return [site, note];
94
+ })).values(),
95
+ ];
96
+ }
97
+ /** One note per package whose command strings name files a configuration holds, naming every such file. */
98
+ function heldScriptNotes(held, targetRoot) {
99
+ const files = new Map();
100
+ for (const one of held) {
101
+ files.set(one.dir, new Set([...(files.get(one.dir) ?? []), ...one.files]));
102
+ }
103
+ return [...files].map(([dir, paths]) => ({
104
+ kind: "unread-command",
105
+ path: relativePath(targetRoot, dir) ?? ".",
106
+ message: `commands the analysis does not read name ${[...paths].sort(bytewise).join(", ")}, so no finding is reported about ${paths.size === 1 ? "that file itself or the names it exports" : "those files themselves or the names they export"}`,
107
+ }));
108
+ }
109
+ function unresolvedNotes(specifiers) {
110
+ return specifiers.map(({ specifier, position }) => ({
111
+ kind: "unresolved-specifier",
112
+ path: dirnamePath(position.path),
113
+ message: `${position.path}:${String(position.line)}:${String(position.column)} loads ${JSON.stringify(specifier)}, which resolves to no file, so it references nothing`,
114
+ }));
115
+ }
116
+ function unreadLine(input, targetRoot) {
117
+ switch (input.kind) {
118
+ case "manifest": {
119
+ const path = relativePath(targetRoot, input.manifest) ?? input.manifest;
120
+ return input.reason === undefined
121
+ ? `${path} does not parse, so the manifest facts of the program formed for its package are unanswered: correct the manifest`
122
+ : `${path} cannot be read, so the manifest facts of the program formed for its package are unanswered: give the run read access to it: ${input.reason}`;
123
+ }
124
+ case "ignore-file":
125
+ return `${relativePath(targetRoot, input.path) ?? input.path} cannot be read, so the JavaScript files it would ignore are analyzed: ${input.reason}`;
126
+ }
127
+ }
80
128
  /** Each project the run dropped as a report names it, by its configuration file below the target. */
81
129
  function notBuiltOf(dropped, targetRoot) {
82
130
  return dropped.map((one) => {
83
131
  const project = relativePath(targetRoot, one.configFile);
84
132
  if (project === undefined) {
85
- throw new ReportPathError(`the project ${one.id} would be opened from ${one.configFile}, which is not below the target ${targetRoot}, so no report can name it`);
133
+ throw failureExit("unreportable-path", `the project ${one.id} would be opened from ${one.configFile}, which is not below the target ${targetRoot}, so no report can name it: analyze a target that holds it`);
86
134
  }
87
135
  return { id: one.id, project, error: one.error };
88
136
  });
@@ -92,11 +140,24 @@ function configurationsOf(projects, targetRoot) {
92
140
  return projects.map((one) => {
93
141
  const project = relativePath(targetRoot, one.configFile);
94
142
  if (project === undefined) {
95
- throw new ReportPathError(`the project ${one.id} is opened from ${one.configFile}, which is not below the target ${targetRoot}, so no report can name it`);
143
+ throw failureExit("unreportable-path", `the project ${one.id} is opened from ${one.configFile}, which is not below the target ${targetRoot}, so no report can name it: analyze a target that holds it`);
96
144
  }
97
145
  return { id: one.id, project };
98
146
  });
99
147
  }
148
+ /**
149
+ * Writes one document the run was asked for. A document that cannot be produced or written
150
+ * ends the run with the run failure that names it and `fix`, the documents written before it
151
+ * kept.
152
+ */
153
+ function writeDocument(host, path, fix, produce) {
154
+ try {
155
+ host.writeDocument(path, produce());
156
+ }
157
+ catch (error) {
158
+ throw failureExit("unwritten-output", `${path}: ${message(error)}: ${fix}`);
159
+ }
160
+ }
100
161
  /** A count with its noun, so a message reads for one record as well as for several. */
101
162
  function counted(n, noun) {
102
163
  return `${String(n)} ${noun}${n === 1 ? "" : "s"}`;
@@ -109,7 +170,7 @@ function counted(n, noun) {
109
170
  function templateOf(host, invoked, named, formats) {
110
171
  if (named === undefined || named === "") {
111
172
  if (formats.includes("template")) {
112
- throw new ConfigError("malformed", "", "the template format renders the template --template names, and none was named");
173
+ throw new UsageError("the template format renders the template --template names, and none was named");
113
174
  }
114
175
  return undefined;
115
176
  }
@@ -118,7 +179,7 @@ function templateOf(host, invoked, named, formats) {
118
179
  source = host.readFile(resolvePath(invoked, named));
119
180
  }
120
181
  catch (error) {
121
- throw new ConfigError("malformed", "", `--template=${named}: ${error instanceof Error ? error.message : String(error)}`);
182
+ throw new UsageError(`--template=${named}: ${message(error)}`);
122
183
  }
123
184
  try {
124
185
  return parseTemplate(source);
@@ -127,7 +188,7 @@ function templateOf(host, invoked, named, formats) {
127
188
  if (!(error instanceof TemplateError)) {
128
189
  throw error;
129
190
  }
130
- throw new ConfigError("malformed", "", `--template=${named}: ${error.message}`);
191
+ throw new UsageError(`--template=${named}: ${error.message}`);
131
192
  }
132
193
  }
133
194
  /**
@@ -141,17 +202,13 @@ function templateOf(host, invoked, named, formats) {
141
202
  export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repeated }) => {
142
203
  const reportPath = option("report");
143
204
  if (reportPath === undefined || reportPath === "") {
144
- throw new ConfigError("malformed", "", "analyze writes its report to the path --report names, and no path was named");
205
+ throw new UsageError("analyze writes its report to the path --report names, and no path was named");
145
206
  }
146
207
  const exitCode = option("exit-code") ?? EXIT_CODE_ON;
147
208
  if (exitCode !== EXIT_CODE_ON && exitCode !== EXIT_CODE_OFF) {
148
- throw new ConfigError("malformed", "", `--exit-code=${JSON.stringify(exitCode)}: the values are ${EXIT_CODE_ON} and ${EXIT_CODE_OFF}`);
209
+ throw new UsageError(`--exit-code=${JSON.stringify(exitCode)}: the values are ${EXIT_CODE_ON} and ${EXIT_CODE_OFF}`);
149
210
  }
150
211
  const { config, provenance } = resolve(inputs());
151
- const kind = config.targetKind;
152
- if (kind === "") {
153
- throw new ConfigError("missing-target-kind", "target.kind", "target.kind is not set");
154
- }
155
212
  const invoked = host.workingDirectory();
156
213
  const baseline = option("baseline-write");
157
214
  const documents = {
@@ -162,123 +219,106 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
162
219
  const template = templateOf(host, invoked, option("template"), documents.formats);
163
220
  const scoped = scope();
164
221
  const targetRoot = scoped.target.path;
165
- let version;
166
- try {
167
- version = host.analyzerVersion();
168
- }
169
- catch (error) {
170
- err.write(`deadset-ts: ${error instanceof Error ? error.message : String(error)}\n`);
171
- return EXIT_FAILURE;
172
- }
173
- let report;
222
+ const version = ownVersion(host);
174
223
  let recorded;
175
- let verdict;
176
- let partial;
177
- let warnings;
178
- let unread;
179
- let unfound;
180
- try {
181
- const root = runRelative(invoked, targetRoot);
182
- const mode = { production: true };
183
- let analysis;
184
- if (documents.baseline === undefined) {
185
- analysis = runAnalysis(openClient(config), host, scoped, config, provenance, mode);
186
- recorded = [];
187
- }
188
- else {
189
- const reason = rowReason(analyzerProvenance(version));
190
- const write = runBaselineWrite(openClient(config), host, scoped, config, provenance, mode, reason);
191
- analysis = write.first;
192
- recorded = write.rows;
193
- }
194
- const { result, run } = analysis;
195
- warnings = run.componentWarnings;
196
- unread = run.componentsUnread.map(({ configFile, reason }) => `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: its component files are not read: ${reason}`);
197
- unfound = run.librariesUnfound.map(({ configFile, error }) => `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: ${error}: the configuration is analyzed without that library`);
198
- partial = partialNotes({
199
- notBuilt: run.notBuilt,
200
- unanswered: run.unanswered,
201
- targetRoot: resolvePath(invoked, targetRoot),
202
- });
203
- const built = buildReport({
204
- contractVersion: CONTRACT_VERSION,
205
- version,
206
- conformance: CONFORMANCE,
207
- declaredGaps: DECLARED_GAPS,
208
- target: {
209
- kind: kindOf(kind, LANGUAGE),
210
- root,
211
- identity: moduleIdentity(host, scoped.target),
212
- },
213
- configurations: configurationsOf(run.projects, targetRoot),
214
- notBuilt: notBuiltOf(run.notBuilt, targetRoot),
215
- loaded: run.consumers.map((consumer) => ({
216
- id: consumer.id,
217
- path: runRelative(invoked, consumer.path),
218
- })),
219
- unavailable: [],
220
- result,
221
- testFileRules: run.testFileRules,
222
- typeErrorSkips: run.typeErrorSkips,
223
- unanswered: unansweredCounts(analysis),
224
- conventionsApplied: run.conventionsApplied,
225
- notes: unmappedNotes(run.unmappedEntries, resolvePath(invoked, targetRoot)),
226
- });
227
- report = capFindings(sortFindings(built, config.reporters.sort), config.reporters.maxFindings);
228
- verdict = verdictOf(result, config);
224
+ const root = runRelative(invoked, targetRoot);
225
+ const mode = { production: true };
226
+ let analysis;
227
+ if (documents.baseline === undefined) {
228
+ analysis = runAnalysis(openClient(config), host, scoped, config, provenance, mode);
229
+ recorded = [];
229
230
  }
230
- catch (error) {
231
- if (!(error instanceof ReportPathError)) {
232
- throw error;
233
- }
234
- err.write(`deadset-ts: ${error.message}\n`);
235
- return EXIT_FAILURE;
231
+ else {
232
+ const reason = rowReason(analyzerProvenance(version));
233
+ const write = runBaselineWrite(openClient(config), host, scoped, config, provenance, mode, reason);
234
+ analysis = write.first;
235
+ recorded = write.rows;
236
236
  }
237
- try {
238
- host.writeDocument(documents.report, json(report));
239
- const options = {
240
- failOn: config.reporters.failOn,
241
- readSource: (path) => host.readFile(joinPath(targetRoot, path)),
242
- template,
243
- };
244
- for (const format of documents.formats) {
245
- const rendering = RENDERINGS.get(format);
246
- if (rendering !== undefined) {
247
- host.writeDocument(documents.report + rendering.suffix, rendering.render(report, options));
248
- }
249
- }
250
- if (documents.baseline !== undefined) {
251
- const kept = host.kindOf(documents.baseline) === "file"
252
- ? otherSchemeRows(host.readFile(documents.baseline))
253
- : [];
254
- host.writeDocument(documents.baseline, writeBaseline(recorded, analyzerProvenance(version), kept));
237
+ const { result, run } = analysis;
238
+ const warnings = run.componentWarnings;
239
+ const unread = run.componentsUnread.map(({ configFile, reason }) => `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: its component files are not read: ${reason}`);
240
+ const unfound = run.librariesUnfound.map(({ configFile, error }) => `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: ${error}: the configuration is analyzed without that library`);
241
+ const unreadInputs = run.unread.map((one) => unreadLine(one, resolvePath(invoked, targetRoot)));
242
+ const partial = partialNotes({
243
+ notBuilt: run.notBuilt,
244
+ unanswered: run.unanswered,
245
+ targetRoot: resolvePath(invoked, targetRoot),
246
+ });
247
+ const built = buildReport({
248
+ contractVersion: CONTRACT_VERSION,
249
+ version,
250
+ conformance: CONFORMANCE,
251
+ declaredGaps: DECLARED_GAPS,
252
+ target: {
253
+ kind: kindOf(config.target.kind),
254
+ root,
255
+ identity: moduleIdentity(host, scoped.target),
256
+ },
257
+ configurations: configurationsOf(run.projects, targetRoot),
258
+ notBuilt: notBuiltOf(run.notBuilt, targetRoot),
259
+ loaded: run.consumers.map((consumer) => ({
260
+ id: consumer.id,
261
+ path: runRelative(invoked, consumer.path),
262
+ })),
263
+ unavailable: [],
264
+ result,
265
+ testFileRules: run.testFileRules,
266
+ typeErrorSkips: run.typeErrorSkips,
267
+ unanswered: unansweredCounts(analysis),
268
+ conventionsApplied: run.conventionsApplied,
269
+ listsComponents: config.reporters.cascade === "full",
270
+ notes: [
271
+ ...unmappedNotes(run.unmappedEntries, resolvePath(invoked, targetRoot)),
272
+ ...unseenLoadNotes(run.unseenLoads),
273
+ ...unresolvedNotes(run.unresolvedSpecifiers),
274
+ ...heldScriptNotes(run.heldScripts, resolvePath(invoked, targetRoot)),
275
+ ],
276
+ });
277
+ const report = capFindings(sortFindings(built, config.reporters.sort), config.reporters.max_findings);
278
+ const verdict = verdictOf(result, config);
279
+ const [refused] = reportRefusals(report);
280
+ if (refused !== undefined) {
281
+ throw productFault(`built a report that breaks the report schema at ${refused.pointer || "the document"}: ${refused.keyword}: ${refused.reason}`);
282
+ }
283
+ writeDocument(host, documents.report, "name a path the run can write", () => json(report));
284
+ const options = {
285
+ readSource: (path) => host.readFile(joinPath(targetRoot, path)),
286
+ template,
287
+ };
288
+ for (const format of documents.formats) {
289
+ const rendering = RENDERINGS.get(format);
290
+ if (rendering !== undefined) {
291
+ writeDocument(host, documents.report + rendering.suffix, "name a path the run can write, or correct the template that renders it", () => rendering.render(report, options));
255
292
  }
256
293
  }
257
- catch (error) {
258
- err.write(`deadset-ts: ${error instanceof Error ? error.message : String(error)}\n`);
259
- return EXIT_FAILURE;
294
+ const baselinePath = documents.baseline;
295
+ if (baselinePath !== undefined) {
296
+ writeDocument(host, baselinePath, "name a path the run can write, or repair the rows of the baseline the run would keep", () => {
297
+ const kept = host.kindOf(baselinePath) === "file" ? otherSchemeRows(host.readFile(baselinePath)) : [];
298
+ return writeBaseline(recorded, analyzerProvenance(version), kept);
299
+ });
260
300
  }
261
301
  for (const note of partial) {
262
- err.write(`deadset-ts: ${note}\n`);
302
+ diagnose(err, note);
263
303
  }
264
- for (const line of [...unread, ...unfound]) {
265
- err.write(`deadset-ts: ${line}\n`);
304
+ for (const line of [...unread, ...unfound, ...unreadInputs]) {
305
+ diagnose(err, line);
266
306
  }
267
307
  for (const warning of warnings) {
268
- err.write(`deadset-ts: ${warning.path}:${String(warning.line)}: a <script> start tag that begins a line is not read as a block: ${warning.reason}\n`);
308
+ diagnose(err, `${warning.path}:${String(warning.line)}: a <script> start tag that begins a line is not read as a block: ${warning.reason}`);
269
309
  }
270
310
  for (const skip of report.type_error_skips) {
271
- err.write(`deadset-ts: ${skip.path}:${String(skip.line)}: ${skip.message}: the function or statement holding this type error is not evaluated\n`);
311
+ diagnose(err, `${skip.path}:${String(skip.line)}: ${skip.message}: the function or statement holding this type error is not evaluated`);
272
312
  }
273
313
  if (report.totals.pending > 0) {
274
- err.write(`deadset-ts: ${counted(report.totals.pending, "pending finding")}: the report names a declared cross-language edge, and no merge has resolved it\n`);
314
+ diagnose(err, `${counted(report.totals.pending, "pending finding")}: the report names a declared cross-language edge, and no merge has resolved it`);
275
315
  }
276
316
  if (report.totals.stale_suppressions > 0) {
277
- err.write(`deadset-ts: ${counted(report.totals.stale_suppressions, "stale suppression")}\n`);
317
+ diagnose(err, counted(report.totals.stale_suppressions, "stale suppression"));
278
318
  }
279
319
  if (exitCode === EXIT_CODE_OFF) {
280
320
  if (verdict !== EXIT_CLEAN) {
281
- err.write(`deadset-ts: the exit code is configured off: the verdict of this run is ${String(verdict)}\n`);
321
+ diagnose(err, `the exit code is configured off: the verdict of this run is ${String(verdict)}`);
282
322
  }
283
323
  return EXIT_CLEAN;
284
324
  }
@@ -1,7 +1,8 @@
1
+ import { CONTRACT_VERSION, EXIT_CLEAN, EXIT_USAGE, SCHEMA_VERSIONS, } from "../contract/contract.generated.js";
1
2
  import { CONFORMANCE } from "../conformance.js";
2
- import { ANALYZER_NAME, LANGUAGE, SCHEMA_VERSIONS_ACCEPTED } from "../report.js";
3
- import { CONTRACT_VERSION } from "../version.js";
4
- import { EXIT_CLEAN, EXIT_FAILURE, EXIT_USAGE } from "./verb.js";
3
+ import {} from "./verb.js";
4
+ import { diagnose, LANGUAGE, PRODUCT } from "../product.js";
5
+ import { ownVersion } from "./version.js";
5
6
  /**
6
7
  * The document `describe` writes: the analyzer, the Contract version it implements, the
7
8
  * report schema versions it reads, the languages it claims, and its recorded result over
@@ -9,10 +10,10 @@ import { EXIT_CLEAN, EXIT_FAILURE, EXIT_USAGE } from "./verb.js";
9
10
  */
10
11
  export function describeDocument(version, recorded) {
11
12
  const document = {
12
- name: ANALYZER_NAME,
13
+ name: PRODUCT,
13
14
  version,
14
15
  contract_version: CONTRACT_VERSION,
15
- schema_versions_accepted: SCHEMA_VERSIONS_ACCEPTED,
16
+ schema_versions_accepted: SCHEMA_VERSIONS,
16
17
  languages: [LANGUAGE],
17
18
  ...(recorded === undefined
18
19
  ? {}
@@ -26,20 +27,12 @@ export function describeDocument(version, recorded) {
26
27
  };
27
28
  return `${JSON.stringify(document, null, 2)}\n`;
28
29
  }
29
- /** Writes this analyzer's description to the output stream, and nothing to the error stream. */
30
+ /** Writes this analyzer's description to the output stream. */
30
31
  export const describeVerb = ({ out, err, host, args }) => {
31
32
  if (args.length > 0) {
32
- err.write(`deadset-ts: describe takes no argument, got ${JSON.stringify(args[0])}\n`);
33
+ diagnose(err, `describe takes no argument, got ${JSON.stringify(args[0])}`);
33
34
  return EXIT_USAGE;
34
35
  }
35
- let version;
36
- try {
37
- version = host.analyzerVersion();
38
- }
39
- catch (error) {
40
- err.write(`deadset-ts: describe: ${error instanceof Error ? error.message : String(error)}\n`);
41
- return EXIT_FAILURE;
42
- }
43
- out.write(describeDocument(version, CONFORMANCE));
36
+ out.write(describeDocument(ownVersion(host), CONFORMANCE));
44
37
  return EXIT_CLEAN;
45
38
  };
@@ -1,13 +1,16 @@
1
+ import { EXIT_CLEAN, EXIT_USAGE } from "../contract/contract.generated.js";
1
2
  import { runAnalysis } from "../analysis.js";
2
3
  import { listing } from "../components.js";
3
- import { ConfigError } from "../config.js";
4
4
  import { OUTSIDE, swept as judged } from "../graph.js";
5
5
  import { referenceKey } from "../matrix.js";
6
6
  import { partialNotes } from "../partial.js";
7
7
  import { resolvePath } from "../paths.js";
8
8
  import { positionKey } from "../position.js";
9
9
  import { resolve } from "../resolve.js";
10
- import { EXIT_CLEAN, EXIT_USAGE } from "./verb.js";
10
+ import {} from "./verb.js";
11
+ import { UsageError } from "../usage.js";
12
+ import { bytewise } from "../text/compare.js";
13
+ import { diagnose } from "../product.js";
11
14
  /**
12
15
  * The three spellings of an explanation request, each naming the symbol to explain. They are
13
16
  * one request, because exactly one state holds for a symbol and the answer is that state.
@@ -33,10 +36,10 @@ function namedSymbol(option) {
33
36
  });
34
37
  const [only, ...more] = named;
35
38
  if (only === undefined) {
36
- throw new ConfigError("malformed", "", "explain explains the symbol --why, --why-live or --why-not names, and none was named");
39
+ throw new UsageError("explain explains the symbol --why, --why-live or --why-not names, and none was named");
37
40
  }
38
41
  if (more.length > 0) {
39
- throw new ConfigError("malformed", "", `explain explains one symbol, and ${named.map((one) => `--${one.name}`).join(", ")} each name one`);
42
+ throw new UsageError(`explain explains one symbol, and ${named.map((one) => `--${one.name}`).join(", ")} each name one`);
40
43
  }
41
44
  return only.value;
42
45
  }
@@ -73,7 +76,7 @@ function subjectOf(symbols, named) {
73
76
  const partial = symbols
74
77
  .filter((symbol) => symbol.ref.toLowerCase().includes(folded) || symbol.name.toLowerCase().includes(folded))
75
78
  .map(described)
76
- .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
79
+ .sort(bytewise);
77
80
  return { candidates: partial.slice(0, MAX_PARTIAL_MATCHES) };
78
81
  }
79
82
  /** Every reference of the run once, a reference several configurations make counted once. */
@@ -317,7 +320,7 @@ export const explainVerb = ({ out, err, host, inputs, scope, openClient, option
317
320
  targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
318
321
  });
319
322
  if (subject === undefined) {
320
- err.write(`deadset-ts: ${JSON.stringify(named)} names no one symbol of the target\n`);
323
+ diagnose(err, `${JSON.stringify(named)} names no one symbol of the target`);
321
324
  for (const candidate of candidates) {
322
325
  err.write(` ${candidate}\n`);
323
326
  }
@@ -329,7 +332,7 @@ export const explainVerb = ({ out, err, host, inputs, scope, openClient, option
329
332
  out.write(explanation(analysis, subject, config.reporters.cascade));
330
333
  }
331
334
  for (const note of notes) {
332
- err.write(`deadset-ts: ${note}\n`);
335
+ diagnose(err, note);
333
336
  }
334
337
  return subject === undefined ? EXIT_USAGE : EXIT_CLEAN;
335
338
  };