@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.10

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 (288) 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 +245 -309
  5. package/dist/src/augmentations.js +124 -18
  6. package/dist/src/automatic-types.js +170 -0
  7. package/dist/src/build-scripts.js +425 -0
  8. package/dist/src/component-layer.js +4 -2
  9. package/dist/src/components.js +9 -9
  10. package/dist/src/config.js +6 -81
  11. package/dist/src/configuration-files.js +78 -188
  12. package/dist/src/configured-declarations.js +15 -20
  13. package/dist/src/conformance-record.js +2 -2
  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 +146 -1
  20. package/dist/src/conventions.js +136 -70
  21. package/dist/src/decorator.js +21 -18
  22. package/dist/src/dependencies.js +280 -62
  23. package/dist/src/edges.js +38 -122
  24. package/dist/src/element-access.js +4 -6
  25. package/dist/src/entry-point-gaps.js +9 -18
  26. package/dist/src/entry-points.js +106 -21
  27. package/dist/src/enum-group.js +3 -10
  28. package/dist/src/erased-stores.js +4 -6
  29. package/dist/src/exempt-classes.js +1 -63
  30. package/dist/src/exempt.js +21 -40
  31. package/dist/src/failure.js +36 -0
  32. package/dist/src/file-facts.js +2 -25
  33. package/dist/src/findings/completion.js +225 -63
  34. package/dist/src/findings/dependencies-and-module-machinery.js +72 -27
  35. package/dist/src/findings/emitters.js +6 -6
  36. package/dist/src/findings/interfaces.js +4 -5
  37. package/dist/src/findings/intra-function.js +53 -19
  38. package/dist/src/findings/non-code-artifacts.js +71 -39
  39. package/dist/src/findings/reads-and-writes.js +16 -13
  40. package/dist/src/findings/self-check.js +10 -12
  41. package/dist/src/findings/unused-declarations.js +57 -28
  42. package/dist/src/findings/visibility-narrowing.js +40 -18
  43. package/dist/src/findings-pass.js +8 -15
  44. package/dist/src/framework-lifecycle.js +7 -7
  45. package/dist/src/fs/document.js +52 -0
  46. package/dist/src/fs/manifest.js +72 -0
  47. package/dist/src/fs/tree.js +88 -0
  48. package/dist/src/generated-file.js +18 -4
  49. package/dist/src/git-ignore.js +99 -0
  50. package/dist/src/graph.js +9 -4
  51. package/dist/src/html-entries.js +6 -33
  52. package/dist/src/import-bindings.js +97 -0
  53. package/dist/src/in-operands.js +2 -1
  54. package/dist/src/index.js +1 -1
  55. package/dist/src/indexed-access.js +130 -28
  56. package/dist/src/injection-container.js +1 -1
  57. package/dist/src/interface-satisfaction.js +30 -12
  58. package/dist/src/intra-function-parts.js +341 -69
  59. package/dist/src/inventory.js +54 -29
  60. package/dist/src/javascript-file.js +33 -0
  61. package/dist/src/json-document.js +198 -67
  62. package/dist/src/keyof-comparisons.js +4 -6
  63. package/dist/src/kinds.js +3 -366
  64. package/dist/src/load/classify.js +270 -0
  65. package/dist/src/load/diagnostics.js +80 -0
  66. package/dist/src/load/discover.js +277 -0
  67. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  68. package/dist/src/load/expected-module.js +136 -0
  69. package/dist/src/load/load.js +27 -0
  70. package/dist/src/load/module-lookup.js +37 -0
  71. package/dist/src/load/plan.js +313 -0
  72. package/dist/src/load/specifiers.js +115 -0
  73. package/dist/src/load/synthesize.js +140 -0
  74. package/dist/src/load/test-globals.js +207 -0
  75. package/dist/src/load/test-joins.js +82 -0
  76. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  77. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +88 -61
  78. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +60 -59
  79. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  80. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  81. package/dist/src/manifest.js +49 -54
  82. package/dist/src/matrix.js +94 -13
  83. package/dist/src/mdx-entries.js +174 -0
  84. package/dist/src/module-declarations.js +9 -13
  85. package/dist/src/outside-tests.js +1 -1
  86. package/dist/src/overrides.js +41 -10
  87. package/dist/src/partial.js +2 -1
  88. package/dist/src/paths.js +26 -15
  89. package/dist/src/position.js +2 -1
  90. package/dist/src/print.js +11 -78
  91. package/dist/src/product.js +13 -0
  92. package/dist/src/query.js +1 -0
  93. package/dist/src/ref.js +11 -0
  94. package/dist/src/references.js +52 -31
  95. package/dist/src/report-schema.js +51 -0
  96. package/dist/src/report.js +59 -41
  97. package/dist/src/reporters/annotations.js +15 -8
  98. package/dist/src/reporters/sarif.js +16 -10
  99. package/dist/src/reporters/template.js +4 -19
  100. package/dist/src/resolve.js +245 -727
  101. package/dist/src/roots.js +374 -116
  102. package/dist/src/run.js +66 -77
  103. package/dist/src/schema.js +320 -342
  104. package/dist/src/scope.js +38 -115
  105. package/dist/src/serialization-contract.js +649 -29
  106. package/dist/src/session.js +41 -29
  107. package/dist/src/source-files.js +40 -15
  108. package/dist/src/suppress-file.js +34 -29
  109. package/dist/src/suppress-inline.js +21 -4
  110. package/dist/src/suppress.js +40 -56
  111. package/dist/src/sweep.js +32 -17
  112. package/dist/src/symbol-keys.js +105 -6
  113. package/dist/src/template-field.js +9 -19
  114. package/dist/src/test-support.js +119 -18
  115. package/dist/src/text/compare.js +26 -0
  116. package/dist/src/text/glob.js +194 -0
  117. package/dist/src/text/named-files.js +54 -0
  118. package/dist/src/text/shell.js +159 -0
  119. package/dist/src/text/yaml.js +393 -0
  120. package/dist/src/type-errors.js +27 -221
  121. package/dist/src/type-parameter-reads.js +172 -0
  122. package/dist/src/unseen-loads.js +100 -0
  123. package/dist/src/unwrap.js +23 -0
  124. package/dist/src/usage.js +10 -0
  125. package/dist/src/verbs/analyze.js +181 -125
  126. package/dist/src/verbs/describe.js +9 -16
  127. package/dist/src/verbs/explain.js +14 -9
  128. package/dist/src/verbs/print-config.js +2 -1
  129. package/dist/src/verbs/print-projects.js +6 -4
  130. package/dist/src/verbs/print-retained.js +4 -2
  131. package/dist/src/verbs/print-roots.js +4 -2
  132. package/dist/src/verbs/verb.js +1 -5
  133. package/dist/src/verbs/version.js +14 -3
  134. package/dist/src/workflow-steps.js +128 -33
  135. package/jsr.json +1 -1
  136. package/package.json +5 -4
  137. package/src/analysis.ts +333 -457
  138. package/src/augmentations.ts +152 -23
  139. package/src/automatic-types.ts +228 -0
  140. package/src/build-scripts.ts +543 -0
  141. package/src/component-layer.ts +4 -2
  142. package/src/components.ts +9 -10
  143. package/src/config.ts +31 -299
  144. package/src/configuration-files.ts +100 -191
  145. package/src/configured-declarations.ts +18 -23
  146. package/src/conformance-record.ts +2 -2
  147. package/src/consumers.ts +6 -57
  148. package/src/contextual-uses.ts +64 -12
  149. package/src/contract/config.generated.ts +818 -0
  150. package/src/contract/contract.generated.ts +1308 -0
  151. package/src/contract/report.generated.ts +1237 -0
  152. package/src/convention-rows.ts +186 -1
  153. package/src/conventions.ts +166 -90
  154. package/src/decorator.ts +28 -18
  155. package/src/dependencies.ts +356 -78
  156. package/src/edges.ts +41 -140
  157. package/src/element-access.ts +4 -7
  158. package/src/entry-point-gaps.ts +11 -20
  159. package/src/entry-points.ts +124 -28
  160. package/src/enum-group.ts +2 -11
  161. package/src/erased-stores.ts +3 -7
  162. package/src/exempt-classes.ts +1 -75
  163. package/src/exempt.ts +36 -50
  164. package/src/failure.ts +63 -0
  165. package/src/file-facts.ts +9 -24
  166. package/src/finding.ts +24 -9
  167. package/src/findings/boundary.ts +2 -0
  168. package/src/findings/completion.ts +297 -77
  169. package/src/findings/dependencies-and-module-machinery.ts +90 -31
  170. package/src/findings/emitter.ts +8 -9
  171. package/src/findings/emitters.ts +8 -8
  172. package/src/findings/interfaces.ts +4 -6
  173. package/src/findings/intra-function.ts +64 -20
  174. package/src/findings/non-code-artifacts.ts +88 -50
  175. package/src/findings/reads-and-writes.ts +20 -16
  176. package/src/findings/self-check.ts +11 -17
  177. package/src/findings/unused-declarations.ts +65 -28
  178. package/src/findings/visibility-narrowing.ts +51 -19
  179. package/src/findings-pass.ts +10 -19
  180. package/src/framework-lifecycle.ts +7 -7
  181. package/src/fs/document.ts +70 -0
  182. package/src/fs/manifest.ts +92 -0
  183. package/src/fs/tree.ts +110 -0
  184. package/src/generated-file.ts +23 -4
  185. package/src/git-ignore.ts +122 -0
  186. package/src/graph.ts +11 -3
  187. package/src/host.ts +17 -14
  188. package/src/html-entries.ts +6 -33
  189. package/src/import-bindings.ts +128 -0
  190. package/src/in-operands.ts +2 -1
  191. package/src/index.ts +1 -1
  192. package/src/indexed-access.ts +152 -30
  193. package/src/injection-container.ts +1 -1
  194. package/src/interface-satisfaction.ts +33 -13
  195. package/src/intra-function-parts.ts +449 -85
  196. package/src/inventory.ts +73 -27
  197. package/src/javascript-file.ts +37 -0
  198. package/src/json-document.ts +241 -76
  199. package/src/keyof-comparisons.ts +3 -7
  200. package/src/kinds.ts +5 -413
  201. package/src/load/classify.ts +397 -0
  202. package/src/load/diagnostics.ts +144 -0
  203. package/src/load/discover.ts +455 -0
  204. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  205. package/src/load/expected-module.ts +188 -0
  206. package/src/load/load.ts +73 -0
  207. package/src/load/module-lookup.ts +50 -0
  208. package/src/load/plan.ts +504 -0
  209. package/src/load/specifiers.ts +168 -0
  210. package/src/load/synthesize.ts +211 -0
  211. package/src/load/test-globals.ts +251 -0
  212. package/src/load/test-joins.ts +98 -0
  213. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  214. package/src/{workspace-programs.ts → load/workspace-programs.ts} +127 -80
  215. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +73 -64
  216. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  217. package/src/{workspace.ts → load/workspace.ts} +90 -134
  218. package/src/manifest.ts +81 -75
  219. package/src/matrix.ts +154 -18
  220. package/src/mdx-entries.ts +218 -0
  221. package/src/module-declarations.ts +11 -13
  222. package/src/outside-tests.ts +1 -1
  223. package/src/overrides.ts +51 -12
  224. package/src/partial.ts +3 -2
  225. package/src/paths.ts +27 -15
  226. package/src/position.ts +2 -1
  227. package/src/print.ts +12 -91
  228. package/src/product.ts +18 -0
  229. package/src/query.ts +11 -0
  230. package/src/ref.ts +13 -0
  231. package/src/references.ts +62 -32
  232. package/src/report-schema.ts +68 -0
  233. package/src/report.ts +105 -215
  234. package/src/reporters/annotations.ts +22 -11
  235. package/src/reporters/json.ts +1 -1
  236. package/src/reporters/reporter.ts +2 -4
  237. package/src/reporters/reporters.ts +1 -1
  238. package/src/reporters/sarif.ts +29 -16
  239. package/src/reporters/template.ts +4 -20
  240. package/src/reporters/text.ts +3 -3
  241. package/src/resolve.ts +283 -1111
  242. package/src/roots.ts +490 -137
  243. package/src/run.ts +75 -91
  244. package/src/schema.ts +391 -387
  245. package/src/scope.ts +46 -138
  246. package/src/serialization-contract.ts +818 -26
  247. package/src/session.ts +77 -54
  248. package/src/source-files.ts +67 -17
  249. package/src/suppress-file.ts +36 -34
  250. package/src/suppress-inline.ts +20 -3
  251. package/src/suppress.ts +52 -63
  252. package/src/sweep.ts +53 -18
  253. package/src/symbol-keys.ts +131 -7
  254. package/src/template-field.ts +15 -25
  255. package/src/test-support.ts +155 -18
  256. package/src/text/compare.ts +27 -0
  257. package/src/text/glob.ts +210 -0
  258. package/src/text/named-files.inputs.json +1522 -0
  259. package/src/text/named-files.ts +68 -0
  260. package/src/text/shell.ts +161 -0
  261. package/src/text/yaml.ts +423 -0
  262. package/src/type-errors.ts +35 -273
  263. package/src/type-parameter-reads.ts +227 -0
  264. package/src/unseen-loads.ts +134 -0
  265. package/src/unwrap.ts +34 -0
  266. package/src/usage.ts +10 -0
  267. package/src/verbs/analyze.ts +245 -161
  268. package/src/verbs/describe.ts +14 -15
  269. package/src/verbs/explain.ts +15 -13
  270. package/src/verbs/print-config.ts +2 -1
  271. package/src/verbs/print-projects.ts +6 -4
  272. package/src/verbs/print-retained.ts +4 -2
  273. package/src/verbs/print-roots.ts +4 -2
  274. package/src/verbs/verb.ts +2 -8
  275. package/src/verbs/version.ts +15 -3
  276. package/src/workflow-steps.ts +149 -34
  277. package/dist/src/discover.js +0 -347
  278. package/dist/src/glob.js +0 -65
  279. package/dist/src/pnpm-workspace.js +0 -120
  280. package/dist/src/setup-failure.js +0 -23
  281. package/dist/src/test-joins.js +0 -101
  282. package/dist/src/version.js +0 -7
  283. package/src/discover.ts +0 -510
  284. package/src/glob.ts +0 -67
  285. package/src/pnpm-workspace.ts +0 -135
  286. package/src/setup-failure.ts +0 -39
  287. package/src/test-joins.ts +0 -120
  288. package/src/version.ts +0 -7
@@ -1,7 +1,7 @@
1
+ import { EXIT_CLEAN, EXIT_USAGE } from "../contract/contract.generated.ts";
1
2
  import { runAnalysis, type RunAnalysis } from "../analysis.ts";
2
3
  import { listing } from "../components.ts";
3
4
  import type { Cascade } from "../config.ts";
4
- import { ConfigError } from "../config.ts";
5
5
  import type { CompletedFinding } from "../finding.ts";
6
6
  import { OUTSIDE, swept as judged } from "../graph.ts";
7
7
  import type { InventorySymbol } from "../inventory.ts";
@@ -12,7 +12,10 @@ import { positionKey } from "../position.ts";
12
12
  import type { Reference } from "../references.ts";
13
13
  import { resolve } from "../resolve.ts";
14
14
  import type { Root } from "../roots.ts";
15
- import { EXIT_CLEAN, EXIT_USAGE, type Verb, type VerbOptions } from "./verb.ts";
15
+ import { type Verb, type VerbOptions } from "./verb.ts";
16
+ import { UsageError } from "../usage.ts";
17
+ import { bytewise } from "../text/compare.ts";
18
+ import { diagnose } from "../product.ts";
16
19
 
17
20
  /**
18
21
  * The three spellings of an explanation request, each naming the symbol to explain. They are
@@ -44,16 +47,12 @@ function namedSymbol(option: (name: string) => string | undefined): string {
44
47
  });
45
48
  const [only, ...more] = named;
46
49
  if (only === undefined) {
47
- throw new ConfigError(
48
- "malformed",
49
- "",
50
+ throw new UsageError(
50
51
  "explain explains the symbol --why, --why-live or --why-not names, and none was named",
51
52
  );
52
53
  }
53
54
  if (more.length > 0) {
54
- throw new ConfigError(
55
- "malformed",
56
- "",
55
+ throw new UsageError(
57
56
  `explain explains one symbol, and ${named.map((one) => `--${one.name}`).join(", ")} each name one`,
58
57
  );
59
58
  }
@@ -101,7 +100,7 @@ function subjectOf(
101
100
  symbol.ref.toLowerCase().includes(folded) || symbol.name.toLowerCase().includes(folded),
102
101
  )
103
102
  .map(described)
104
- .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
103
+ .sort(bytewise);
105
104
  return { candidates: partial.slice(0, MAX_PARTIAL_MATCHES) };
106
105
  }
107
106
 
@@ -315,12 +314,15 @@ function reported(found: CompletedFinding): string {
315
314
  /**
316
315
  * Every finding of the run that names the declaration by its reference and reports at
317
316
  * another position: a part of it, or a suppression record naming it. Each is published under
318
- * the reference asked about, so an explanation leaving it out would disagree with the report.
317
+ * the reference asked about, or a parameter's under one it starts, so an explanation leaving
318
+ * it out would disagree with the report.
319
319
  */
320
320
  function namedElsewhere(findings: readonly CompletedFinding[], subject: InventorySymbol): string {
321
321
  return findings
322
322
  .filter(
323
- (found) => found.symbol.ref === subject.ref && positionKey(found.position) !== subject.id,
323
+ (found) =>
324
+ (found.symbol.holder ?? found.symbol.ref) === subject.ref &&
325
+ positionKey(found.position) !== subject.id,
324
326
  )
325
327
  .map(
326
328
  (found) =>
@@ -377,7 +379,7 @@ export const explainVerb: Verb = ({ out, err, host, inputs, scope, openClient, o
377
379
  targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
378
380
  });
379
381
  if (subject === undefined) {
380
- err.write(`deadset-ts: ${JSON.stringify(named)} names no one symbol of the target\n`);
382
+ diagnose(err, `${JSON.stringify(named)} names no one symbol of the target`);
381
383
  for (const candidate of candidates) {
382
384
  err.write(` ${candidate}\n`);
383
385
  }
@@ -388,7 +390,7 @@ export const explainVerb: Verb = ({ out, err, host, inputs, scope, openClient, o
388
390
  out.write(explanation(analysis, subject, config.reporters.cascade));
389
391
  }
390
392
  for (const note of notes) {
391
- err.write(`deadset-ts: ${note}\n`);
393
+ diagnose(err, note);
392
394
  }
393
395
  return subject === undefined ? EXIT_USAGE : EXIT_CLEAN;
394
396
  };
@@ -1,6 +1,7 @@
1
+ import { EXIT_CLEAN } from "../contract/contract.generated.ts";
1
2
  import { printConfig } from "../print.ts";
2
3
  import { resolve } from "../resolve.ts";
3
- import { EXIT_CLEAN, type Verb } from "./verb.ts";
4
+ import { type Verb } from "./verb.ts";
4
5
 
5
6
  /** Writes the resolved configuration with the source of every setting. */
6
7
  export const printConfigVerb: Verb = ({ out, inputs }) => {
@@ -1,8 +1,10 @@
1
+ import { EXIT_CLEAN } from "../contract/contract.generated.ts";
1
2
  import { runProjects } from "../analysis.ts";
2
3
  import { partialNotes } from "../partial.ts";
3
4
  import { resolvePath } from "../paths.ts";
4
- import { resolveUnkinded } from "../resolve.ts";
5
- import { EXIT_CLEAN, type Verb } from "./verb.ts";
5
+ import { resolvePartial } from "../resolve.ts";
6
+ import { type Verb } from "./verb.ts";
7
+ import { diagnose } from "../product.ts";
6
8
 
7
9
  /**
8
10
  * Writes the name of every project one run analyzes, one per line: the identifier the
@@ -12,7 +14,7 @@ import { EXIT_CLEAN, type Verb } from "./verb.ts";
12
14
  * instead, and one that fails the analysis fails this command the same way.
13
15
  */
14
16
  export const printProjectsVerb: Verb = ({ out, err, host, inputs, scope, openClient }) => {
15
- const config = resolveUnkinded(inputs());
17
+ const config = resolvePartial(inputs());
16
18
  const scoped = scope();
17
19
  const answer = runProjects(openClient(config), host, scoped, config);
18
20
  for (const id of answer.configurations) {
@@ -23,7 +25,7 @@ export const printProjectsVerb: Verb = ({ out, err, host, inputs, scope, openCli
23
25
  unanswered: answer.unanswered,
24
26
  targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
25
27
  })) {
26
- err.write(`deadset-ts: ${note}\n`);
28
+ diagnose(err, note);
27
29
  }
28
30
  return EXIT_CLEAN;
29
31
  };
@@ -1,9 +1,11 @@
1
+ import { EXIT_CLEAN } from "../contract/contract.generated.ts";
1
2
  import { runSweep } from "../analysis.ts";
2
3
  import { retainedLines } from "../exempt.ts";
3
4
  import { partialNotes } from "../partial.ts";
4
5
  import { resolvePath } from "../paths.ts";
5
6
  import { resolve } from "../resolve.ts";
6
- import { EXIT_CLEAN, type Verb } from "./verb.ts";
7
+ import { type Verb } from "./verb.ts";
8
+ import { diagnose } from "../product.ts";
7
9
 
8
10
  /**
9
11
  * Writes every declaration an exemption held back in the production sweep a report is
@@ -27,7 +29,7 @@ export const printRetainedVerb: Verb = ({ out, err, host, inputs, scope, openCli
27
29
  unanswered: swept.unanswered,
28
30
  targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
29
31
  })) {
30
- err.write(`deadset-ts: ${note}\n`);
32
+ diagnose(err, note);
31
33
  }
32
34
  return EXIT_CLEAN;
33
35
  };
@@ -1,8 +1,10 @@
1
+ import { EXIT_CLEAN, EXIT_FINDINGS } from "../contract/contract.generated.ts";
1
2
  import { runRoots, type RunRoot } from "../analysis.ts";
2
3
  import { partialNotes } from "../partial.ts";
3
4
  import { resolvePath } from "../paths.ts";
4
5
  import { resolve } from "../resolve.ts";
5
- import { EXIT_CLEAN, EXIT_FINDINGS, type Verb } from "./verb.ts";
6
+ import { type Verb } from "./verb.ts";
7
+ import { diagnose } from "../product.ts";
6
8
 
7
9
  /**
8
10
  * One root as a line: the declaration's reference, why it is a root, and the string
@@ -41,7 +43,7 @@ export const printRootsVerb: Verb = ({ out, err, host, inputs, scope, openClient
41
43
  unanswered: answer.unanswered,
42
44
  targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
43
45
  })) {
44
- err.write(`deadset-ts: ${note}\n`);
46
+ diagnose(err, note);
45
47
  }
46
48
  return answer.findings.length > 0 ? EXIT_FINDINGS : EXIT_CLEAN;
47
49
  };
package/src/verbs/verb.ts CHANGED
@@ -1,15 +1,9 @@
1
- import type { Config, Inputs } from "../config.ts";
1
+ import type { Inputs, PartialConfig } from "../config.ts";
2
2
  import type { Host } from "../host.ts";
3
3
  import type { Writer } from "../run.ts";
4
4
  import type { Scope } from "../scope.ts";
5
5
  import type { Engine } from "../session.ts";
6
6
 
7
- /** The exit codes the Contract's exit-code table names. */
8
- export const EXIT_CLEAN = 0;
9
- export const EXIT_FINDINGS = 1;
10
- export const EXIT_USAGE = 2;
11
- export const EXIT_FAILURE = 3;
12
-
13
7
  /**
14
8
  * What one verb is handed: the streams it writes, the platform it reads, and the
15
9
  * options that follow its name, read only when the verb asks for what they name, so
@@ -27,7 +21,7 @@ interface Invocation {
27
21
  * Opens the compiler client for a run under `config`. A client the verb opened is
28
22
  * closed if the verb throws.
29
23
  */
30
- readonly openClient: (config: Config) => Engine;
24
+ readonly openClient: (config: PartialConfig) => Engine;
31
25
  /** The arguments after the verb's name, as written. */
32
26
  readonly args: readonly string[];
33
27
  /** The last value one of the verb's own options was given, undefined where none was. */
@@ -1,8 +1,20 @@
1
- import { CONTRACT_VERSION } from "../version.ts";
2
- import { EXIT_CLEAN, type Verb } from "./verb.ts";
1
+ import { CONTRACT_VERSION, EXIT_CLEAN } from "../contract/contract.generated.ts";
2
+ import { productFault } from "../failure.ts";
3
+ import type { Host } from "../host.ts";
4
+ import { type Verb } from "./verb.ts";
5
+ import { message, PRODUCT } from "../product.ts";
6
+
7
+ /** The version the host reports for this analyzer; one it cannot read is a fault of the analyzer. */
8
+ export function ownVersion(host: Host): string {
9
+ try {
10
+ return host.analyzerVersion();
11
+ } catch (error: unknown) {
12
+ throw productFault(`cannot read its own version: ${message(error)}`);
13
+ }
14
+ }
3
15
 
4
16
  /** Writes the version the host reports and the Contract version this analyzer implements. */
5
17
  export const versionVerb: Verb = ({ out, host }) => {
6
- out.write(`deadset-ts ${host.analyzerVersion()}\ncontract ${CONTRACT_VERSION}\n`);
18
+ out.write(`${PRODUCT} ${ownVersion(host)}\ncontract ${CONTRACT_VERSION}\n`);
7
19
  return EXIT_CLEAN;
8
20
  };
@@ -1,13 +1,15 @@
1
1
  /**
2
- * The source files a CI workflow's `run:` steps name by their path from the target root.
3
- * A workflow file is read as text, line by line, with no YAML parsed.
2
+ * The source files a CI workflow's `run:` steps and the repository's git hooks name by their
3
+ * path from the target root. A workflow or hook file is read as text, line by line, with no
4
+ * YAML parsed.
4
5
  */
5
6
 
6
7
  import type { SourceFile } from "@typescript/native/ast";
7
8
  import type { Host } from "./host.ts";
8
- import { scriptTokens } from "./manifest.ts";
9
- import { joinPath, resolvePath } from "./paths.ts";
9
+ import { joinPath, relativePath, resolvePath } from "./paths.ts";
10
10
  import type { SourceFiles } from "./source-files.ts";
11
+ import { filesIn } from "./fs/tree.ts";
12
+ import { commandsOf, scriptTokens, unquoted, withoutComment } from "./text/shell.ts";
11
13
 
12
14
  /** One file a workflow step names. */
13
15
  interface WorkflowEntry {
@@ -21,39 +23,57 @@ const WORKFLOWS = ".github/workflows";
21
23
  /** A line holding a run command: indentation and one sequence marker before `run:`. */
22
24
  const RUN_LINE = /^(\s*(?:- )?)run:(.*)$/u;
23
25
 
24
- const BLOCK_SCALAR = /^[|>][+-]?$/u;
26
+ /** The directories whose every file is a git hook script, run as the shell runs it. */
27
+ const HOOK_DIRS = [".husky", ".githooks"] as const;
28
+
29
+ /** A line setting the directory a workflow step, or each step a `defaults.run` covers, runs in. */
30
+ const WORKING_DIRECTORY = /^(\s*(?:- )?)working-directory:(.*)$/u;
31
+
32
+ const LEFTHOOK_FILES = ["lefthook.yml", "lefthook.yaml", ".lefthook.yml"] as const;
33
+
34
+ /** A line setting the directory a lefthook command runs in. */
35
+ const LEFTHOOK_ROOT = /^(\s*(?:- )?)root:(.*)$/u;
36
+
37
+ /** The hook manager files whose commands are written under a key, with that key. */
38
+ const HOOK_MANAGERS: readonly (readonly [string, RegExp])[] = [
39
+ [".pre-commit-config.yaml", /^(\s*(?:- )?)entry:(.*)$/u],
40
+ ...LEFTHOOK_FILES.map((name) => [name, RUN_LINE] as const),
41
+ ];
42
+
43
+ /** The names GNU make reads a makefile under, in the order it tries them. */
44
+ const MAKEFILES = ["GNUmakefile", "makefile", "Makefile"] as const;
25
45
 
26
- const QUOTED = /^(["'])(.*)\1$/su;
46
+ const BLOCK_SCALAR = /^[|>][+-]?$/u;
27
47
 
28
48
  /** A token holding one of these is computed or a pattern, so it names no file. */
29
49
  const COMPUTED = /[${*?[]/u;
30
50
 
31
51
  const INDENT = /^\s*/u;
32
52
 
33
- function workflowFiles(host: Host, dir: string): readonly string[] {
34
- let entries;
53
+ /** The files directly in one directory whose name `accepts`, by path in name order. */
54
+ function pathsIn(host: Host, dir: string, accepts: (name: string) => boolean): readonly string[] {
55
+ return filesIn(host, dir, accepts).map((name) => joinPath(dir, name));
56
+ }
57
+
58
+ function readText(host: Host, path: string): string | undefined {
35
59
  try {
36
- entries = host.readDirectory(dir);
60
+ return host.readFile(path);
37
61
  } catch {
38
- return [];
62
+ return undefined;
39
63
  }
40
- return entries
41
- .filter((entry) => !entry.directory && /\.ya?ml$/u.test(entry.name))
42
- .map((entry) => joinPath(dir, entry.name))
43
- .sort();
44
64
  }
45
65
 
46
- /** Every command line one workflow file's `run:` steps hold. */
47
- export function runCommands(text: string): readonly string[] {
66
+ /** Every line one workflow file's `run:` steps hold, or every line of the values written under `key`. */
67
+ export function runCommands(text: string, key: RegExp = RUN_LINE): readonly string[] {
48
68
  const lines = text.split(/\r?\n/u);
49
69
  const found: string[] = [];
50
70
  for (let at = 0; at < lines.length; at += 1) {
51
- const match = RUN_LINE.exec(lines[at] ?? "");
71
+ const match = key.exec(lines[at] ?? "");
52
72
  if (match === null) {
53
73
  continue;
54
74
  }
55
75
  const column = (match[1] ?? "").length;
56
- const value = (match[2] ?? "").trim().replace(QUOTED, "$2");
76
+ const value = unquoted((match[2] ?? "").trim());
57
77
  if (!BLOCK_SCALAR.test(value)) {
58
78
  found.push(value);
59
79
  continue;
@@ -69,28 +89,112 @@ export function runCommands(text: string): readonly string[] {
69
89
  return found;
70
90
  }
71
91
 
72
- /** Every token the `run:` steps of the workflow files in the target root's `.github/workflows` hold. */
73
- export function workflowTokens(host: Host, targetRoot: string): readonly string[] {
74
- const dir = joinPath(resolvePath(host.workingDirectory(), targetRoot), WORKFLOWS);
75
- const found: string[] = [];
76
- for (const path of workflowFiles(host, dir)) {
77
- let text: string;
78
- try {
79
- text = host.readFile(path);
80
- } catch {
81
- continue;
82
- }
83
- for (const command of runCommands(text)) {
84
- found.push(...scriptTokens(command));
92
+ /**
93
+ * The commands of the recipe lines of the target root's makefile, each line's in order: a
94
+ * recipe line is one that starts with a tab, joined with the lines a trailing `\`
95
+ * continues it onto.
96
+ */
97
+ function recipeLines(host: Host, root: string): readonly (readonly string[])[] {
98
+ const path = MAKEFILES.map((name) => joinPath(root, name)).find(
99
+ (candidate) => host.kindOf(candidate) === "file",
100
+ );
101
+ const text = path === undefined ? undefined : readText(host, path);
102
+ if (text === undefined) {
103
+ return [];
104
+ }
105
+ return text
106
+ .replace(/\\\r?\n/gu, " ")
107
+ .split(/\r?\n/u)
108
+ .filter((line) => line.startsWith("\t"))
109
+ .map((line) => commandsOf(withoutComment(line.slice(1).replace(/^[@+-]+/u, ""))));
110
+ }
111
+
112
+ /**
113
+ * Every token the recipe lines of the target root's makefile hold, each with the directory
114
+ * below the root it is read against: make runs each line in a shell of its own, so a `cd`
115
+ * moves the directory of the later commands of its line alone.
116
+ */
117
+ function recipeTokens(host: Host, root: string): readonly { token: string; dir: string }[] {
118
+ const found: { token: string; dir: string }[] = [];
119
+ for (const line of recipeLines(host, root)) {
120
+ let dir = root;
121
+ for (const command of line) {
122
+ const tokens = scriptTokens(command);
123
+ const [first, target, ...rest] = tokens;
124
+ if (first === "cd" && target !== undefined && rest.length === 0 && !COMPUTED.test(target)) {
125
+ dir = resolvePath(dir, target);
126
+ continue;
127
+ }
128
+ for (const token of tokens) {
129
+ found.push({ token, dir: relativePath(root, dir) ?? "" });
130
+ }
85
131
  }
86
132
  }
87
133
  return found;
88
134
  }
89
135
 
90
136
  /**
91
- * The own files the `run:` steps of every workflow file in the target root's
92
- * `.github/workflows` name: a token equal to a file's path below the target root, with or
93
- * without a leading `./`, whatever directory the step runs in.
137
+ * Every command the target root's workflow steps, git hooks and makefile run: the `run:`
138
+ * steps of the workflow files in `.github/workflows`, every line of each file in `.husky`
139
+ * and `.githooks`, the `entry:` commands of `.pre-commit-config.yaml`, the `run:` commands
140
+ * of a lefthook file and the makefile's recipe lines.
141
+ */
142
+ export function workflowCommands(host: Host, targetRoot: string): readonly string[] {
143
+ const root = resolvePath(host.workingDirectory(), targetRoot);
144
+ return [...stepCommands(host, root), ...recipeLines(host, root).flat()];
145
+ }
146
+
147
+ /**
148
+ * Every directory the target root's workflow steps and lefthook commands are set to run in, as
149
+ * written: each `working-directory` of a workflow file and each `root` of a lefthook file.
150
+ */
151
+ export function commandDirectories(host: Host, targetRoot: string): readonly string[] {
152
+ const root = resolvePath(host.workingDirectory(), targetRoot);
153
+ return [
154
+ ...workflowFiles(host, root).flatMap((path) =>
155
+ runCommands(readText(host, path) ?? "", WORKING_DIRECTORY),
156
+ ),
157
+ ...LEFTHOOK_FILES.flatMap((name) =>
158
+ runCommands(readText(host, joinPath(root, name)) ?? "", LEFTHOOK_ROOT),
159
+ ),
160
+ ];
161
+ }
162
+
163
+ /** The workflow files in `.github/workflows` at one root, by path in name order. */
164
+ function workflowFiles(host: Host, root: string): readonly string[] {
165
+ return pathsIn(host, joinPath(root, WORKFLOWS), (name) => /\.ya?ml$/u.test(name));
166
+ }
167
+
168
+ /** Every token the commands {@link workflowCommands} reads hold. */
169
+ export function workflowTokens(host: Host, targetRoot: string): readonly string[] {
170
+ const root = resolvePath(host.workingDirectory(), targetRoot);
171
+ return [
172
+ ...stepCommands(host, root).flatMap((command) => scriptTokens(command)),
173
+ ...recipeTokens(host, root).map(({ token }) => token),
174
+ ];
175
+ }
176
+
177
+ /** The commands of the workflow steps, git hooks and hook managers at one root. */
178
+ function stepCommands(host: Host, root: string): readonly string[] {
179
+ const commands: string[] = [];
180
+ for (const path of workflowFiles(host, root)) {
181
+ commands.push(...runCommands(readText(host, path) ?? ""));
182
+ }
183
+ for (const dir of HOOK_DIRS) {
184
+ for (const path of pathsIn(host, joinPath(root, dir), () => true)) {
185
+ commands.push(...(readText(host, path) ?? "").split(/\r?\n/u).map(withoutComment));
186
+ }
187
+ }
188
+ for (const [name, key] of HOOK_MANAGERS) {
189
+ commands.push(...runCommands(readText(host, joinPath(root, name)) ?? "", key));
190
+ }
191
+ return commands;
192
+ }
193
+
194
+ /**
195
+ * The own files the target root's workflow steps and git hooks name, as
196
+ * {@link workflowTokens} reads them: a token equal to a file's path below the target root,
197
+ * with or without a leading `./`, whatever directory the step runs in.
94
198
  */
95
199
  export function workflowEntries(
96
200
  host: Host,
@@ -107,5 +211,16 @@ export function workflowEntries(
107
211
  found.push({ file, source: token });
108
212
  }
109
213
  }
214
+ const root = resolvePath(host.workingDirectory(), targetRoot);
215
+ for (const { token, dir } of recipeTokens(host, root)) {
216
+ const below =
217
+ COMPUTED.test(token) || dir === ""
218
+ ? undefined
219
+ : relativePath(root, resolvePath(joinPath(root, dir), token));
220
+ const file = below === undefined ? undefined : files.byPath.get(below);
221
+ if (file !== undefined) {
222
+ found.push({ file, source: token });
223
+ }
224
+ }
110
225
  return found;
111
226
  }