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

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 (193) hide show
  1. package/dist/bin/mapper-connection.js +2 -1
  2. package/dist/bin/node-host.js +24 -5
  3. package/dist/src/analysis.js +37 -56
  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 +6 -82
  9. package/dist/src/configuration-files.js +27 -168
  10. package/dist/src/configured-declarations.js +15 -20
  11. package/dist/src/consumers.js +2 -2
  12. package/dist/src/contract/config.generated.js +544 -0
  13. package/dist/src/contract/contract.generated.js +13 -0
  14. package/dist/src/contract/report.generated.js +3 -2
  15. package/dist/src/conventions.js +20 -60
  16. package/dist/src/dependencies.js +25 -43
  17. package/dist/src/discover.js +14 -40
  18. package/dist/src/edges.js +38 -122
  19. package/dist/src/element-access.js +4 -6
  20. package/dist/src/entry-point-gaps.js +2 -8
  21. package/dist/src/entry-points.js +3 -10
  22. package/dist/src/enum-group.js +3 -10
  23. package/dist/src/erased-stores.js +4 -6
  24. package/dist/src/exempt-classes.js +0 -4
  25. package/dist/src/exempt.js +6 -22
  26. package/dist/src/file-facts.js +2 -25
  27. package/dist/src/findings/completion.js +5 -4
  28. package/dist/src/findings/emitters.js +1 -1
  29. package/dist/src/findings/intra-function.js +1 -1
  30. package/dist/src/findings/non-code-artifacts.js +2 -2
  31. package/dist/src/findings/reads-and-writes.js +2 -1
  32. package/dist/src/findings/self-check.js +3 -2
  33. package/dist/src/findings/unused-declarations.js +1 -1
  34. package/dist/src/findings/visibility-narrowing.js +2 -2
  35. package/dist/src/findings-pass.js +7 -12
  36. package/dist/src/framework-lifecycle.js +7 -7
  37. package/dist/src/fs/document.js +59 -0
  38. package/dist/src/fs/manifest.js +43 -0
  39. package/dist/src/fs/tree.js +88 -0
  40. package/dist/src/git-ignore.js +34 -25
  41. package/dist/src/html-entries.js +6 -33
  42. package/dist/src/in-operands.js +2 -1
  43. package/dist/src/injection-container.js +1 -1
  44. package/dist/src/intra-function-parts.js +10 -13
  45. package/dist/src/inventory.js +2 -21
  46. package/dist/src/json-document.js +198 -67
  47. package/dist/src/keyof-comparisons.js +4 -6
  48. package/dist/src/kinds.js +0 -7
  49. package/dist/src/manifest.js +5 -43
  50. package/dist/src/mdx-entries.js +3 -26
  51. package/dist/src/partial.js +2 -1
  52. package/dist/src/paths.js +26 -15
  53. package/dist/src/position.js +2 -1
  54. package/dist/src/print.js +11 -79
  55. package/dist/src/product.js +13 -0
  56. package/dist/src/references.js +14 -27
  57. package/dist/src/report-schema.js +3 -225
  58. package/dist/src/report.js +22 -31
  59. package/dist/src/reporters/sarif.js +4 -2
  60. package/dist/src/reporters/template.js +4 -19
  61. package/dist/src/resolve.js +245 -724
  62. package/dist/src/roots.js +7 -17
  63. package/dist/src/run.js +55 -61
  64. package/dist/src/schema.js +320 -343
  65. package/dist/src/scope.js +34 -103
  66. package/dist/src/serialization-contract.js +5 -9
  67. package/dist/src/session.js +2 -1
  68. package/dist/src/suppress-file.js +21 -25
  69. package/dist/src/suppress.js +3 -3
  70. package/dist/src/symbol-keys.js +3 -5
  71. package/dist/src/template-field.js +9 -19
  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/usage.js +10 -0
  82. package/dist/src/verbs/analyze.js +25 -26
  83. package/dist/src/verbs/describe.js +4 -4
  84. package/dist/src/verbs/explain.js +8 -6
  85. package/dist/src/verbs/print-projects.js +4 -3
  86. package/dist/src/verbs/print-retained.js +2 -1
  87. package/dist/src/verbs/print-roots.js +2 -1
  88. package/dist/src/verbs/version.js +2 -1
  89. package/dist/src/workflow-steps.js +9 -46
  90. package/dist/src/workspace-manifests.js +2 -2
  91. package/dist/src/workspace-programs.js +5 -3
  92. package/dist/src/workspace-resolution.js +13 -17
  93. package/dist/src/workspace.js +28 -116
  94. package/jsr.json +1 -1
  95. package/package.json +1 -1
  96. package/src/analysis.ts +55 -76
  97. package/src/automatic-types.ts +11 -29
  98. package/src/build-scripts.ts +17 -38
  99. package/src/component-layer.ts +4 -2
  100. package/src/components.ts +2 -4
  101. package/src/config.ts +31 -296
  102. package/src/configuration-files.ts +27 -170
  103. package/src/configured-declarations.ts +18 -23
  104. package/src/consumers.ts +4 -4
  105. package/src/contract/config.generated.ts +798 -0
  106. package/src/contract/contract.generated.ts +25 -0
  107. package/src/contract/report.generated.ts +3 -2
  108. package/src/conventions.ts +26 -74
  109. package/src/dependencies.ts +32 -50
  110. package/src/discover.ts +15 -40
  111. package/src/edges.ts +41 -140
  112. package/src/element-access.ts +4 -7
  113. package/src/entry-point-gaps.ts +3 -9
  114. package/src/entry-points.ts +2 -13
  115. package/src/enum-group.ts +2 -11
  116. package/src/erased-stores.ts +3 -7
  117. package/src/exempt-classes.ts +0 -5
  118. package/src/exempt.ts +8 -30
  119. package/src/file-facts.ts +6 -24
  120. package/src/findings/completion.ts +5 -6
  121. package/src/findings/emitter.ts +2 -2
  122. package/src/findings/emitters.ts +1 -1
  123. package/src/findings/intra-function.ts +1 -1
  124. package/src/findings/non-code-artifacts.ts +4 -4
  125. package/src/findings/reads-and-writes.ts +2 -1
  126. package/src/findings/self-check.ts +3 -2
  127. package/src/findings/unused-declarations.ts +1 -1
  128. package/src/findings/visibility-narrowing.ts +2 -2
  129. package/src/findings-pass.ts +9 -15
  130. package/src/framework-lifecycle.ts +7 -7
  131. package/src/fs/document.ts +74 -0
  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/host.ts +12 -5
  136. package/src/html-entries.ts +6 -33
  137. package/src/in-operands.ts +2 -1
  138. package/src/injection-container.ts +1 -1
  139. package/src/intra-function-parts.ts +10 -14
  140. package/src/inventory.ts +2 -20
  141. package/src/json-document.ts +241 -76
  142. package/src/keyof-comparisons.ts +3 -7
  143. package/src/kinds.ts +0 -8
  144. package/src/manifest.ts +5 -46
  145. package/src/mdx-entries.ts +3 -26
  146. package/src/partial.ts +2 -1
  147. package/src/paths.ts +27 -15
  148. package/src/position.ts +2 -1
  149. package/src/print.ts +12 -92
  150. package/src/product.ts +18 -0
  151. package/src/references.ts +14 -29
  152. package/src/report-schema.ts +3 -301
  153. package/src/report.ts +24 -34
  154. package/src/reporters/sarif.ts +4 -4
  155. package/src/reporters/template.ts +4 -20
  156. package/src/resolve.ts +283 -1114
  157. package/src/roots.ts +7 -20
  158. package/src/run.ts +57 -75
  159. package/src/schema.ts +391 -388
  160. package/src/scope.ts +40 -126
  161. package/src/serialization-contract.ts +5 -10
  162. package/src/session.ts +2 -1
  163. package/src/suppress-file.ts +21 -29
  164. package/src/suppress.ts +3 -3
  165. package/src/symbol-keys.ts +2 -5
  166. package/src/template-field.ts +15 -25
  167. package/src/test-globals.ts +9 -27
  168. package/src/test-joins.ts +6 -8
  169. package/src/text/compare.ts +27 -0
  170. package/src/text/glob.ts +210 -0
  171. package/src/text/shell.ts +161 -0
  172. package/src/{yaml-document.ts → text/yaml.ts} +46 -0
  173. package/src/type-errors.ts +25 -36
  174. package/src/unseen-loads.ts +1 -12
  175. package/src/unwrap.ts +34 -0
  176. package/src/usage.ts +10 -0
  177. package/src/verbs/analyze.ts +32 -42
  178. package/src/verbs/describe.ts +4 -4
  179. package/src/verbs/explain.ts +8 -10
  180. package/src/verbs/print-projects.ts +4 -3
  181. package/src/verbs/print-retained.ts +2 -1
  182. package/src/verbs/print-roots.ts +2 -1
  183. package/src/verbs/verb.ts +2 -2
  184. package/src/verbs/version.ts +2 -1
  185. package/src/workflow-steps.ts +9 -44
  186. package/src/workspace-manifests.ts +2 -2
  187. package/src/workspace-programs.ts +5 -3
  188. package/src/workspace-resolution.ts +21 -17
  189. package/src/workspace.ts +35 -121
  190. package/dist/src/glob.js +0 -65
  191. package/dist/src/pnpm-workspace.js +0 -120
  192. package/src/glob.ts +0 -67
  193. package/src/pnpm-workspace.ts +0 -135
@@ -1,12 +1,12 @@
1
1
  import { CONTRACT_VERSION, EXIT_CLEAN, EXIT_FAILURE, } from "../contract/contract.generated.js";
2
2
  import { runAnalysis, runBaselineWrite, unansweredCounts, } from "../analysis.js";
3
- import { ConfigError, kindOf } from "../config.js";
3
+ import { kindOf } from "../config.js";
4
4
  import { CONFORMANCE, DECLARED_GAPS } from "../conformance.js";
5
5
  import { moduleIdentity } from "../consumers.js";
6
6
  import { analyzerProvenance, verdictOf } from "../findings-pass.js";
7
7
  import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "../paths.js";
8
8
  import { partialNotes } from "../partial.js";
9
- import { buildReport, LANGUAGE, capFindings, sortFindings, } from "../report.js";
9
+ import { buildReport, capFindings, sortFindings, } from "../report.js";
10
10
  import { reportRefusals } from "../report-schema.js";
11
11
  import { json } from "../reporters/json.js";
12
12
  import { RENDERINGS } from "../reporters/reporters.js";
@@ -14,6 +14,9 @@ import { parseTemplate, TemplateError } from "../reporters/template.js";
14
14
  import { resolve } from "../resolve.js";
15
15
  import { otherSchemeRows, rowReason, writeBaseline } from "../suppress-file.js";
16
16
  import {} from "./verb.js";
17
+ import { UsageError } from "../usage.js";
18
+ import { bytewise } from "../text/compare.js";
19
+ import { diagnose, message } from "../product.js";
17
20
  /** The options `analyze` takes beside the ones every verb takes. */
18
21
  export const ANALYZE_OPTIONS = {
19
22
  plain: ["report", "baseline-write", "exit-code", "template"],
@@ -31,7 +34,7 @@ class ReportPathError extends Error {
31
34
  }
32
35
  /** The formats this analyzer renders, for a message that names them. */
33
36
  function formatNames() {
34
- return [...RENDERINGS.keys()].sort().join(", ");
37
+ return [...RENDERINGS.keys()].sort(bytewise).join(", ");
35
38
  }
36
39
  /** The renderings one invocation writes: the ones it named, else the configuration's. */
37
40
  function formatsOf(named, configured) {
@@ -39,10 +42,10 @@ function formatsOf(named, configured) {
39
42
  const seen = new Set();
40
43
  for (const format of formats) {
41
44
  if (!RENDERINGS.has(format)) {
42
- throw new ConfigError("malformed", "", `${JSON.stringify(format)} is not a format this analyzer renders: the formats are ${formatNames()}`);
45
+ throw new UsageError(`${JSON.stringify(format)} is not a format this analyzer renders: the formats are ${formatNames()}`);
43
46
  }
44
47
  if (seen.has(format)) {
45
- throw new ConfigError("malformed", "", `the format ${JSON.stringify(format)} is named twice`);
48
+ throw new UsageError(`the format ${JSON.stringify(format)} is named twice`);
46
49
  }
47
50
  seen.add(format);
48
51
  }
@@ -128,7 +131,7 @@ function counted(n, noun) {
128
131
  function templateOf(host, invoked, named, formats) {
129
132
  if (named === undefined || named === "") {
130
133
  if (formats.includes("template")) {
131
- throw new ConfigError("malformed", "", "the template format renders the template --template names, and none was named");
134
+ throw new UsageError("the template format renders the template --template names, and none was named");
132
135
  }
133
136
  return undefined;
134
137
  }
@@ -137,7 +140,7 @@ function templateOf(host, invoked, named, formats) {
137
140
  source = host.readFile(resolvePath(invoked, named));
138
141
  }
139
142
  catch (error) {
140
- throw new ConfigError("malformed", "", `--template=${named}: ${error instanceof Error ? error.message : String(error)}`);
143
+ throw new UsageError(`--template=${named}: ${message(error)}`);
141
144
  }
142
145
  try {
143
146
  return parseTemplate(source);
@@ -146,7 +149,7 @@ function templateOf(host, invoked, named, formats) {
146
149
  if (!(error instanceof TemplateError)) {
147
150
  throw error;
148
151
  }
149
- throw new ConfigError("malformed", "", `--template=${named}: ${error.message}`);
152
+ throw new UsageError(`--template=${named}: ${error.message}`);
150
153
  }
151
154
  }
152
155
  /**
@@ -160,17 +163,13 @@ function templateOf(host, invoked, named, formats) {
160
163
  export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repeated }) => {
161
164
  const reportPath = option("report");
162
165
  if (reportPath === undefined || reportPath === "") {
163
- throw new ConfigError("malformed", "", "analyze writes its report to the path --report names, and no path was named");
166
+ throw new UsageError("analyze writes its report to the path --report names, and no path was named");
164
167
  }
165
168
  const exitCode = option("exit-code") ?? EXIT_CODE_ON;
166
169
  if (exitCode !== EXIT_CODE_ON && exitCode !== EXIT_CODE_OFF) {
167
- throw new ConfigError("malformed", "", `--exit-code=${JSON.stringify(exitCode)}: the values are ${EXIT_CODE_ON} and ${EXIT_CODE_OFF}`);
170
+ throw new UsageError(`--exit-code=${JSON.stringify(exitCode)}: the values are ${EXIT_CODE_ON} and ${EXIT_CODE_OFF}`);
168
171
  }
169
172
  const { config, provenance } = resolve(inputs());
170
- const kind = config.targetKind;
171
- if (kind === "") {
172
- throw new ConfigError("missing-target-kind", "target.kind", "target.kind is not set");
173
- }
174
173
  const invoked = host.workingDirectory();
175
174
  const baseline = option("baseline-write");
176
175
  const documents = {
@@ -186,7 +185,7 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
186
185
  version = host.analyzerVersion();
187
186
  }
188
187
  catch (error) {
189
- err.write(`deadset-ts: ${error instanceof Error ? error.message : String(error)}\n`);
188
+ diagnose(err, message(error));
190
189
  return EXIT_FAILURE;
191
190
  }
192
191
  let report;
@@ -225,7 +224,7 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
225
224
  conformance: CONFORMANCE,
226
225
  declaredGaps: DECLARED_GAPS,
227
226
  target: {
228
- kind: kindOf(kind, LANGUAGE),
227
+ kind: kindOf(config.target.kind),
229
228
  root,
230
229
  identity: moduleIdentity(host, scoped.target),
231
230
  },
@@ -247,14 +246,14 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
247
246
  ...unseenLoadNotes(run.unseenLoads),
248
247
  ],
249
248
  });
250
- report = capFindings(sortFindings(built, config.reporters.sort), config.reporters.maxFindings);
249
+ report = capFindings(sortFindings(built, config.reporters.sort), config.reporters.max_findings);
251
250
  verdict = verdictOf(result, config);
252
251
  }
253
252
  catch (error) {
254
253
  if (!(error instanceof ReportPathError)) {
255
254
  throw error;
256
255
  }
257
- err.write(`deadset-ts: ${error.message}\n`);
256
+ diagnose(err, error.message);
258
257
  return EXIT_FAILURE;
259
258
  }
260
259
  try {
@@ -281,30 +280,30 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
281
280
  }
282
281
  }
283
282
  catch (error) {
284
- err.write(`deadset-ts: ${error instanceof Error ? error.message : String(error)}\n`);
283
+ diagnose(err, message(error));
285
284
  return EXIT_FAILURE;
286
285
  }
287
286
  for (const note of partial) {
288
- err.write(`deadset-ts: ${note}\n`);
287
+ diagnose(err, note);
289
288
  }
290
289
  for (const line of [...unread, ...unfound]) {
291
- err.write(`deadset-ts: ${line}\n`);
290
+ diagnose(err, line);
292
291
  }
293
292
  for (const warning of warnings) {
294
- 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`);
293
+ diagnose(err, `${warning.path}:${String(warning.line)}: a <script> start tag that begins a line is not read as a block: ${warning.reason}`);
295
294
  }
296
295
  for (const skip of report.type_error_skips) {
297
- err.write(`deadset-ts: ${skip.path}:${String(skip.line)}: ${skip.message}: the function or statement holding this type error is not evaluated\n`);
296
+ diagnose(err, `${skip.path}:${String(skip.line)}: ${skip.message}: the function or statement holding this type error is not evaluated`);
298
297
  }
299
298
  if (report.totals.pending > 0) {
300
- 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`);
299
+ diagnose(err, `${counted(report.totals.pending, "pending finding")}: the report names a declared cross-language edge, and no merge has resolved it`);
301
300
  }
302
301
  if (report.totals.stale_suppressions > 0) {
303
- err.write(`deadset-ts: ${counted(report.totals.stale_suppressions, "stale suppression")}\n`);
302
+ diagnose(err, counted(report.totals.stale_suppressions, "stale suppression"));
304
303
  }
305
304
  if (exitCode === EXIT_CODE_OFF) {
306
305
  if (verdict !== EXIT_CLEAN) {
307
- err.write(`deadset-ts: the exit code is configured off: the verdict of this run is ${String(verdict)}\n`);
306
+ diagnose(err, `the exit code is configured off: the verdict of this run is ${String(verdict)}`);
308
307
  }
309
308
  return EXIT_CLEAN;
310
309
  }
@@ -1,7 +1,7 @@
1
1
  import { CONTRACT_VERSION, EXIT_CLEAN, EXIT_FAILURE, EXIT_USAGE, SCHEMA_VERSIONS, } from "../contract/contract.generated.js";
2
2
  import { CONFORMANCE } from "../conformance.js";
3
- import { ANALYZER_NAME, LANGUAGE } from "../report.js";
4
3
  import {} from "./verb.js";
4
+ import { diagnose, LANGUAGE, message, PRODUCT } from "../product.js";
5
5
  /**
6
6
  * The document `describe` writes: the analyzer, the Contract version it implements, the
7
7
  * report schema versions it reads, the languages it claims, and its recorded result over
@@ -9,7 +9,7 @@ import {} from "./verb.js";
9
9
  */
10
10
  export function describeDocument(version, recorded) {
11
11
  const document = {
12
- name: ANALYZER_NAME,
12
+ name: PRODUCT,
13
13
  version,
14
14
  contract_version: CONTRACT_VERSION,
15
15
  schema_versions_accepted: SCHEMA_VERSIONS,
@@ -29,7 +29,7 @@ export function describeDocument(version, recorded) {
29
29
  /** Writes this analyzer's description to the output stream, and nothing to the error stream. */
30
30
  export const describeVerb = ({ out, err, host, args }) => {
31
31
  if (args.length > 0) {
32
- err.write(`deadset-ts: describe takes no argument, got ${JSON.stringify(args[0])}\n`);
32
+ diagnose(err, `describe takes no argument, got ${JSON.stringify(args[0])}`);
33
33
  return EXIT_USAGE;
34
34
  }
35
35
  let version;
@@ -37,7 +37,7 @@ export const describeVerb = ({ out, err, host, args }) => {
37
37
  version = host.analyzerVersion();
38
38
  }
39
39
  catch (error) {
40
- err.write(`deadset-ts: describe: ${error instanceof Error ? error.message : String(error)}\n`);
40
+ diagnose(err, `describe: ${message(error)}`);
41
41
  return EXIT_FAILURE;
42
42
  }
43
43
  out.write(describeDocument(version, CONFORMANCE));
@@ -1,7 +1,6 @@
1
1
  import { EXIT_CLEAN, EXIT_USAGE } from "../contract/contract.generated.js";
2
2
  import { runAnalysis } from "../analysis.js";
3
3
  import { listing } from "../components.js";
4
- import { ConfigError } from "../config.js";
5
4
  import { OUTSIDE, swept as judged } from "../graph.js";
6
5
  import { referenceKey } from "../matrix.js";
7
6
  import { partialNotes } from "../partial.js";
@@ -9,6 +8,9 @@ import { resolvePath } from "../paths.js";
9
8
  import { positionKey } from "../position.js";
10
9
  import { resolve } from "../resolve.js";
11
10
  import {} from "./verb.js";
11
+ import { UsageError } from "../usage.js";
12
+ import { bytewise } from "../text/compare.js";
13
+ import { diagnose } from "../product.js";
12
14
  /**
13
15
  * The three spellings of an explanation request, each naming the symbol to explain. They are
14
16
  * one request, because exactly one state holds for a symbol and the answer is that state.
@@ -34,10 +36,10 @@ function namedSymbol(option) {
34
36
  });
35
37
  const [only, ...more] = named;
36
38
  if (only === undefined) {
37
- 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");
38
40
  }
39
41
  if (more.length > 0) {
40
- 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`);
41
43
  }
42
44
  return only.value;
43
45
  }
@@ -74,7 +76,7 @@ function subjectOf(symbols, named) {
74
76
  const partial = symbols
75
77
  .filter((symbol) => symbol.ref.toLowerCase().includes(folded) || symbol.name.toLowerCase().includes(folded))
76
78
  .map(described)
77
- .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
79
+ .sort(bytewise);
78
80
  return { candidates: partial.slice(0, MAX_PARTIAL_MATCHES) };
79
81
  }
80
82
  /** Every reference of the run once, a reference several configurations make counted once. */
@@ -318,7 +320,7 @@ export const explainVerb = ({ out, err, host, inputs, scope, openClient, option
318
320
  targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
319
321
  });
320
322
  if (subject === undefined) {
321
- 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`);
322
324
  for (const candidate of candidates) {
323
325
  err.write(` ${candidate}\n`);
324
326
  }
@@ -330,7 +332,7 @@ export const explainVerb = ({ out, err, host, inputs, scope, openClient, option
330
332
  out.write(explanation(analysis, subject, config.reporters.cascade));
331
333
  }
332
334
  for (const note of notes) {
333
- err.write(`deadset-ts: ${note}\n`);
335
+ diagnose(err, note);
334
336
  }
335
337
  return subject === undefined ? EXIT_USAGE : EXIT_CLEAN;
336
338
  };
@@ -2,8 +2,9 @@ import { EXIT_CLEAN } from "../contract/contract.generated.js";
2
2
  import { runProjects } from "../analysis.js";
3
3
  import { partialNotes } from "../partial.js";
4
4
  import { resolvePath } from "../paths.js";
5
- import { resolveUnkinded } from "../resolve.js";
5
+ import { resolvePartial } from "../resolve.js";
6
6
  import {} from "./verb.js";
7
+ import { diagnose } from "../product.js";
7
8
  /**
8
9
  * Writes the name of every project one run analyzes, one per line: the identifier the
9
10
  * build matrix declares for it, or, where discovery derived the project, its
@@ -12,7 +13,7 @@ import {} from "./verb.js";
12
13
  * instead, and one that fails the analysis fails this command the same way.
13
14
  */
14
15
  export const printProjectsVerb = ({ out, err, host, inputs, scope, openClient }) => {
15
- const config = resolveUnkinded(inputs());
16
+ const config = resolvePartial(inputs());
16
17
  const scoped = scope();
17
18
  const answer = runProjects(openClient(config), host, scoped, config);
18
19
  for (const id of answer.configurations) {
@@ -23,7 +24,7 @@ export const printProjectsVerb = ({ out, err, host, inputs, scope, openClient })
23
24
  unanswered: answer.unanswered,
24
25
  targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
25
26
  })) {
26
- err.write(`deadset-ts: ${note}\n`);
27
+ diagnose(err, note);
27
28
  }
28
29
  return EXIT_CLEAN;
29
30
  };
@@ -5,6 +5,7 @@ import { partialNotes } from "../partial.js";
5
5
  import { resolvePath } from "../paths.js";
6
6
  import { resolve } from "../resolve.js";
7
7
  import {} from "./verb.js";
8
+ import { diagnose } from "../product.js";
8
9
  /**
9
10
  * Writes every declaration an exemption held back in the production sweep a report is
10
11
  * built from, one per line, and nothing where no exemption held one back. One fact
@@ -27,7 +28,7 @@ export const printRetainedVerb = ({ out, err, host, inputs, scope, openClient })
27
28
  unanswered: swept.unanswered,
28
29
  targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
29
30
  })) {
30
- err.write(`deadset-ts: ${note}\n`);
31
+ diagnose(err, note);
31
32
  }
32
33
  return EXIT_CLEAN;
33
34
  };
@@ -4,6 +4,7 @@ import { partialNotes } from "../partial.js";
4
4
  import { resolvePath } from "../paths.js";
5
5
  import { resolve } from "../resolve.js";
6
6
  import {} from "./verb.js";
7
+ import { diagnose } from "../product.js";
7
8
  /**
8
9
  * One root as a line: the declaration's reference, why it is a root, and the string
9
10
  * that named it, separated by tabs. A run of several configurations adds the ones
@@ -40,7 +41,7 @@ export const printRootsVerb = ({ out, err, host, inputs, scope, openClient }) =>
40
41
  unanswered: answer.unanswered,
41
42
  targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
42
43
  })) {
43
- err.write(`deadset-ts: ${note}\n`);
44
+ diagnose(err, note);
44
45
  }
45
46
  return answer.findings.length > 0 ? EXIT_FINDINGS : EXIT_CLEAN;
46
47
  };
@@ -1,7 +1,8 @@
1
1
  import { CONTRACT_VERSION, EXIT_CLEAN } from "../contract/contract.generated.js";
2
2
  import {} from "./verb.js";
3
+ import { PRODUCT } from "../product.js";
3
4
  /** Writes the version the host reports and the Contract version this analyzer implements. */
4
5
  export const versionVerb = ({ out, host }) => {
5
- out.write(`deadset-ts ${host.analyzerVersion()}\ncontract ${CONTRACT_VERSION}\n`);
6
+ out.write(`${PRODUCT} ${host.analyzerVersion()}\ncontract ${CONTRACT_VERSION}\n`);
6
7
  return EXIT_CLEAN;
7
8
  };
@@ -3,8 +3,9 @@
3
3
  * path from the target root. A workflow or hook file is read as text, line by line, with no
4
4
  * YAML parsed.
5
5
  */
6
- import { scriptTokens } from "./manifest.js";
7
6
  import { joinPath, relativePath, resolvePath } from "./paths.js";
7
+ import { filesIn } from "./fs/tree.js";
8
+ import { commandsOf, scriptTokens, unquoted, withoutComment } from "./text/shell.js";
8
9
  const WORKFLOWS = ".github/workflows";
9
10
  /** A line holding a run command: indentation and one sequence marker before `run:`. */
10
11
  const RUN_LINE = /^(\s*(?:- )?)run:(.*)$/u;
@@ -19,26 +20,13 @@ const HOOK_MANAGERS = [
19
20
  ];
20
21
  /** The names GNU make reads a makefile under, in the order it tries them. */
21
22
  const MAKEFILES = ["GNUmakefile", "makefile", "Makefile"];
22
- /** The operators that end one shell command of a recipe line. */
23
- const COMMAND_SEPARATORS = /&&|\|\||;|\|/u;
24
23
  const BLOCK_SCALAR = /^[|>][+-]?$/u;
25
- const QUOTED = /^(["'])(.*)\1$/su;
26
24
  /** A token holding one of these is computed or a pattern, so it names no file. */
27
25
  const COMPUTED = /[${*?[]/u;
28
26
  const INDENT = /^\s*/u;
29
- /** The files directly in one directory whose name `accepts`, in name order. */
30
- function filesIn(host, dir, accepts) {
31
- let entries;
32
- try {
33
- entries = host.readDirectory(dir);
34
- }
35
- catch {
36
- return [];
37
- }
38
- return entries
39
- .filter((entry) => !entry.directory && accepts(entry.name))
40
- .map((entry) => joinPath(dir, entry.name))
41
- .sort();
27
+ /** The files directly in one directory whose name `accepts`, by path in name order. */
28
+ function pathsIn(host, dir, accepts) {
29
+ return filesIn(host, dir, accepts).map((name) => joinPath(dir, name));
42
30
  }
43
31
  function readText(host, path) {
44
32
  try {
@@ -58,7 +46,7 @@ export function runCommands(text, key = RUN_LINE) {
58
46
  continue;
59
47
  }
60
48
  const column = (match[1] ?? "").length;
61
- const value = (match[2] ?? "").trim().replace(QUOTED, "$2");
49
+ const value = unquoted((match[2] ?? "").trim());
62
50
  if (!BLOCK_SCALAR.test(value)) {
63
51
  found.push(value);
64
52
  continue;
@@ -88,7 +76,7 @@ function recipeLines(host, root) {
88
76
  .replace(/\\\r?\n/gu, " ")
89
77
  .split(/\r?\n/u)
90
78
  .filter((line) => line.startsWith("\t"))
91
- .map((line) => withoutComment(line.slice(1).replace(/^[@+-]+/u, "")).split(COMMAND_SEPARATORS));
79
+ .map((line) => commandsOf(withoutComment(line.slice(1).replace(/^[@+-]+/u, ""))));
92
80
  }
93
81
  /**
94
82
  * Every token the recipe lines of the target root's makefile hold, each with the directory
@@ -113,31 +101,6 @@ function recipeTokens(host, root) {
113
101
  }
114
102
  return found;
115
103
  }
116
- /** One shell line up to its comment: a `#` that starts a word outside quotes. */
117
- function withoutComment(line) {
118
- let quote = "";
119
- for (let at = 0; at < line.length; at += 1) {
120
- const char = line[at];
121
- if (quote !== "") {
122
- if (char === quote) {
123
- quote = "";
124
- }
125
- else if (char === "\\" && quote === '"') {
126
- at += 1;
127
- }
128
- }
129
- else if (char === "'" || char === '"') {
130
- quote = char;
131
- }
132
- else if (char === "\\") {
133
- at += 1;
134
- }
135
- else if (char === "#" && (at === 0 || /\s/u.test(line[at - 1] ?? ""))) {
136
- return line.slice(0, at);
137
- }
138
- }
139
- return line;
140
- }
141
104
  /**
142
105
  * Every command the target root's workflow steps, git hooks and makefile run: the `run:`
143
106
  * steps of the workflow files in `.github/workflows`, every line of each file in `.husky`
@@ -159,11 +122,11 @@ export function workflowTokens(host, targetRoot) {
159
122
  /** The commands of the workflow steps, git hooks and hook managers at one root. */
160
123
  function stepCommands(host, root) {
161
124
  const commands = [];
162
- for (const path of filesIn(host, joinPath(root, WORKFLOWS), (name) => /\.ya?ml$/u.test(name))) {
125
+ for (const path of pathsIn(host, joinPath(root, WORKFLOWS), (name) => /\.ya?ml$/u.test(name))) {
163
126
  commands.push(...runCommands(readText(host, path) ?? ""));
164
127
  }
165
128
  for (const dir of HOOK_DIRS) {
166
- for (const path of filesIn(host, joinPath(root, dir), () => true)) {
129
+ for (const path of pathsIn(host, joinPath(root, dir), () => true)) {
167
130
  commands.push(...(readText(host, path) ?? "").split(/\r?\n/u).map(withoutComment));
168
131
  }
169
132
  }
@@ -8,6 +8,7 @@
8
8
  import { emittedFrom } from "./emit-map.js";
9
9
  import { readManifest } from "./manifest.js";
10
10
  import { joinPath, relativePath } from "./paths.js";
11
+ import { MANIFEST } from "./fs/manifest.js";
11
12
  /** The one character a subpath pattern of a manifest's `exports` holds. */
12
13
  const SUBPATH_WILDCARD = "*";
13
14
  /** The paths one written target stands for in its member's sources, itself included. */
@@ -53,8 +54,7 @@ export function memberEntries(host, resolver, targetRoot) {
53
54
  return [];
54
55
  }
55
56
  const manifest = readManifest(host, member.dir);
56
- const where = relativePath(targetRoot, joinPath(member.dir, "package.json")) ??
57
- joinPath(member.dir, "package.json");
57
+ const where = relativePath(targetRoot, joinPath(member.dir, MANIFEST)) ?? joinPath(member.dir, MANIFEST);
58
58
  const kept = manifest.entries.flatMap((entry) => {
59
59
  const exported = entry.member.startsWith("exports");
60
60
  if (member.private && exported) {
@@ -9,6 +9,8 @@ import { ModuleKind, ModuleResolutionKind } from "@typescript/native/sync";
9
9
  import { isStringLiteralLikeNode } from "@typescript/native/ast";
10
10
  import { baseUrlPaths } from "./base-url.js";
11
11
  import { dirnamePath, resolvePath } from "./paths.js";
12
+ import { bytewise } from "./text/compare.js";
13
+ import { PRODUCT } from "./product.js";
12
14
  /** The most questions one round trip carries. */
13
15
  const BATCH = 2048;
14
16
  /**
@@ -55,7 +57,7 @@ function unstatable(reading) {
55
57
  export function writtenPath(host, configFile) {
56
58
  const stem = configFile.replace(/\.json$/u, "");
57
59
  for (let counter = 0;; counter += 1) {
58
- const path = `${stem}.deadset-ts${counter === 0 ? "" : `-${String(counter)}`}.json`;
60
+ const path = `${stem}.${PRODUCT}${counter === 0 ? "" : `-${String(counter)}`}.json`;
59
61
  if (host.kindOf(path) === "absent") {
60
62
  return path;
61
63
  }
@@ -102,10 +104,10 @@ function configurationOf(engine, host, reading) {
102
104
  ...(exclude === undefined ? {} : { exclude }),
103
105
  });
104
106
  const file = writtenPath(host, reading.configFile);
105
- const expected = [...reading.parsed.fileNames].sort().join("\u0000");
107
+ const expected = [...reading.parsed.fileNames].sort(bytewise).join("\u0000");
106
108
  const listed = (value) => {
107
109
  try {
108
- return [...engine.parseConfigJson(value, file).fileNames].sort().join("\u0000");
110
+ return [...engine.parseConfigJson(value, file).fileNames].sort(bytewise).join("\u0000");
109
111
  }
110
112
  catch {
111
113
  return undefined;
@@ -7,8 +7,10 @@
7
7
  */
8
8
  import { emittedFrom } from "./emit-map.js";
9
9
  import { gitIgnored } from "./git-ignore.js";
10
- import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
10
+ import { ancestors, dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath, } from "./paths.js";
11
11
  import { satisfies } from "./version-range.js";
12
+ import { isRecord } from "./json-document.js";
13
+ import { INSTALLED_PACKAGES, MANIFEST, manifestAt } from "./fs/manifest.js";
12
14
  /** The manifest members that name an entry point for the subpath `.` without `exports`. */
13
15
  const LEGACY_MEMBERS = ["types", "typings", "module", "main"];
14
16
  /** The dependency members a manifest declares other packages under. */
@@ -20,9 +22,6 @@ const DEPENDENCY_MEMBERS = [
20
22
  ];
21
23
  const SOURCE = /(?<!\.d)\.(?:[mc]?ts|tsx)$/u;
22
24
  const OUTPUT = /(?:\.d\.[mc]?ts|\.[mc]?js|\.jsx)$/u;
23
- function isRecord(value) {
24
- return typeof value === "object" && value !== null && !Array.isArray(value);
25
- }
26
25
  /** The package name a bare specifier names, or `undefined` for any other specifier. */
27
26
  export function packageNameOf(specifier) {
28
27
  if (specifier === "" ||
@@ -146,7 +145,7 @@ export function workspaceResolver(host, workspace) {
146
145
  if (links.has(key)) {
147
146
  return links.get(key);
148
147
  }
149
- const candidate = joinPath(dir, "node_modules", name);
148
+ const candidate = joinPath(dir, INSTALLED_PACKAGES, name);
150
149
  let found;
151
150
  if (host.kindOf(candidate) === "absent") {
152
151
  const up = dirnamePath(dir);
@@ -171,16 +170,13 @@ export function workspaceResolver(host, workspace) {
171
170
  if (manifests.has(dir)) {
172
171
  return manifests.get(dir);
173
172
  }
174
- let found;
175
- try {
176
- const value = JSON.parse(host.readFile(joinPath(dir, "package.json")));
177
- found = isRecord(value) ? { dir, manifest: value } : undefined;
178
- }
179
- catch {
180
- const up = dirnamePath(dir);
181
- found =
182
- up === dir || root === undefined || !inside(root, up) ? undefined : nearestManifest(up);
183
- }
173
+ const manifest = manifestAt(host, dir);
174
+ const up = dirnamePath(dir);
175
+ const found = manifest !== undefined
176
+ ? { dir, manifest }
177
+ : up === dir || root === undefined || !inside(root, up)
178
+ ? undefined
179
+ : nearestManifest(up);
184
180
  manifests.set(dir, found);
185
181
  return found;
186
182
  };
@@ -191,7 +187,7 @@ export function workspaceResolver(host, workspace) {
191
187
  const member = candidates.find((one) => one.dir === linked);
192
188
  return member === undefined ? { kind: "none" } : { kind: "member", member };
193
189
  }
194
- const scope = workspace.memberOf(joinPath(fromDir, "package.json"));
190
+ const scope = workspace.memberOf(joinPath(fromDir, MANIFEST));
195
191
  if (scope?.name === name) {
196
192
  return { kind: "member", member: scope };
197
193
  }
@@ -221,7 +217,7 @@ export function workspaceResolver(host, workspace) {
221
217
  * directory however its configuration writes it.
222
218
  */
223
219
  const mirrored = (member, path) => {
224
- for (let dir = dirnamePath(path); inside(member.dir, dir); dir = dirnamePath(dir)) {
220
+ for (const dir of ancestors(dirnamePath(path), member.dir).filter((at) => at !== member.dir)) {
225
221
  const found = emittedFrom(path, { outDir: dir, sourceDirs: [member.dir] }).find((candidate) => SOURCE.test(candidate) && !ignored(candidate) && host.kindOf(candidate) === "file");
226
222
  if (found !== undefined) {
227
223
  return found;