@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
@@ -1,8 +1,18 @@
1
1
  import { API, SymbolFlags } from "@typescript/native/sync";
2
2
  import { componentLayer, mapperManifest } from "./component-layer.js";
3
- import { DiscoveryError } from "./discover.js";
4
3
  import { joinPath } from "./paths.js";
5
4
  import { queriesOf, UnansweredLog, } from "./query.js";
5
+ import { MANIFEST } from "./fs/manifest.js";
6
+ /** The most questions one round trip carries. */
7
+ const BATCH = 2048;
8
+ /** Asks one question of every item, in round trips of at most {@link BATCH}, answers in order. */
9
+ export function batched(engine, items, question) {
10
+ const answers = [];
11
+ for (let at = 0; at < items.length; at += BATCH) {
12
+ answers.push(...engine.batch(items.slice(at, at + BATCH).map(question)));
13
+ }
14
+ return answers;
15
+ }
6
16
  /**
7
17
  * Opens the compiler client the analysis reads through.
8
18
  *
@@ -16,7 +26,7 @@ export function openEngine(options) {
16
26
  if (components !== undefined && components.extensions.length > 0) {
17
27
  const made = components.host.temporaryDirectory();
18
28
  try {
19
- components.host.writeDocument(joinPath(made.path, "package.json"), [
29
+ components.host.writeDocument(joinPath(made.path, MANIFEST), [
20
30
  mapperManifest(components.host.componentMapperCommand()),
21
31
  ]);
22
32
  }
@@ -252,14 +262,13 @@ function projectsOf(snapshot, built, written) {
252
262
  return opened;
253
263
  }
254
264
  /**
255
- * Runs `visit` over every project the compiler configurations name, inside one never updated
256
- * snapshot of one client, and answers in their order. A configuration `programs` describes is
257
- * built as that program, or opened from the configuration it writes. One the snapshot holds
258
- * nothing for ends the run under `"refuse"` and is named in the result under `"record"`.
259
- * `first` runs over every opened project before the first visit and `last` after the last,
260
- * both while the client answers; the snapshot and the client are then released.
265
+ * Opens every project the compiler configurations name in one never updated snapshot of one
266
+ * client, and hands them to `use` while the client answers; the snapshot and the client are
267
+ * then released. A configuration `options.programs` describes is built as that program, or
268
+ * opened from the configuration it writes. One the snapshot holds nothing for has no view.
261
269
  */
262
- export function runSession(engine, configFiles, visit, unopened = "refuse", programs = new Map(), last = () => undefined, first = () => undefined) {
270
+ export function openSession(engine, configFiles, options, use) {
271
+ const programs = options.programs ?? new Map();
263
272
  try {
264
273
  const built = [];
265
274
  const params = [];
@@ -281,30 +290,13 @@ export function runSession(engine, configFiles, visit, unopened = "refuse", prog
281
290
  const opened = projectsOf(snapshot, built, written);
282
291
  const log = new UnansweredLog();
283
292
  const views = [];
284
- const missing = [];
285
293
  for (const configFile of configFiles) {
286
294
  const project = opened.get(configFile);
287
- if (project === undefined) {
288
- if (unopened === "refuse") {
289
- throw new DiscoveryError(`${configFile} opened no project`, []);
290
- }
291
- missing.push(configFile);
292
- continue;
295
+ if (project !== undefined) {
296
+ views.push(viewOf(project, configFile, engine, log));
293
297
  }
294
- views.push(viewOf(project, configFile, engine, log));
295
298
  }
296
- first(views.map((view) => ({
297
- configFile: view.configFile,
298
- fileNames: () => view.ownSourceFiles().map((file) => file.fileName),
299
- })));
300
- const projects = views.map((view) => visit(view));
301
- last();
302
- return {
303
- projects,
304
- unopened: missing,
305
- timing: engine.getTimingInfo(),
306
- unanswered: log.questions,
307
- };
299
+ return use({ views }, () => log.questions);
308
300
  }
309
301
  finally {
310
302
  snapshot.dispose();
@@ -314,3 +306,16 @@ export function runSession(engine, configFiles, visit, unopened = "refuse", prog
314
306
  engine.close();
315
307
  }
316
308
  }
309
+ /**
310
+ * Runs `visit` over the project {@link openSession} opens for each configuration, and answers
311
+ * in their order; a configuration the snapshot opens nothing for throws before any visit.
312
+ */
313
+ export function runSession(engine, configFiles, visit) {
314
+ return openSession(engine, configFiles, {}, (opened) => {
315
+ const unopened = configFiles.find((configFile) => !opened.views.some((view) => view.configFile === configFile));
316
+ if (unopened !== undefined) {
317
+ throw new Error(`${unopened} opened no project`);
318
+ }
319
+ return { projects: opened.views.map((view) => visit(view)), timing: engine.getTimingInfo() };
320
+ });
321
+ }
@@ -3,7 +3,7 @@
3
3
  * in a document or a string names one: as the file itself, or as a file the
4
4
  * compiler emits from it.
5
5
  */
6
- import { commonDirectory, emittedFrom } from "./emit-map.js";
6
+ import { commonDirectory, emittedFrom } from "./load/emit-map.js";
7
7
  import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
8
8
  /** The one character a subpath pattern of a manifest's `exports` holds. */
9
9
  const SUBPATH_WILDCARD = "*";
@@ -4,11 +4,13 @@
4
4
  * one record shape, so one decoder and one matcher serve both, and a row moves into the
5
5
  * ignore file by gaining a maintainer's reason.
6
6
  */
7
+ import { CODE } from "./contract/contract.generated.js";
8
+ import { readTargetDocument } from "./fs/document.js";
7
9
  import { jsonChunks } from "./json-chunks.js";
8
- import { joinPath } from "./paths.js";
9
- import { positionAt, walkDocument } from "./json-document.js";
10
+ import { isRecord, positionAt, readStrict, StrictJSONError } from "./json-document.js";
10
11
  import { declarationKey } from "./ref.js";
11
- import { CODE_FORM, SUPPRESSION_WITHOUT_REASON, SuppressionError, UNSCOPED_ENTRY, blankReason, isEntryPath, isPartKind, isSymbolRef, siteText, } from "./suppress.js";
12
+ import { CODE_FORM, SuppressionError, blankReason, isEntryPath, isPartKind, isSymbolRef, siteText, } from "./suppress.js";
13
+ import { message } from "./product.js";
12
14
  /** The name and the location the grammar fixes for the ignore file. */
13
15
  export const IGNORE_FILE = "deadset-ignore.json";
14
16
  /** The name and the location the grammar fixes for the baseline. */
@@ -42,9 +44,6 @@ const PATH_WANT = "a path relative to the target root, with the solidus as separ
42
44
  function documentSite(shape) {
43
45
  return { path: shape.file, line: 0, column: 0 };
44
46
  }
45
- function isObject(value) {
46
- return typeof value === "object" && value !== null && !Array.isArray(value);
47
- }
48
47
  /**
49
48
  * Reads one entry or row: the records it binds, the refusals it carries, or the
50
49
  * malformed value that ends the run.
@@ -53,7 +52,7 @@ function readRecord(value, site, byReference, shape) {
53
52
  const refuse = (text, want) => {
54
53
  throw new SuppressionError(shape.mechanism, siteText(site), text, want);
55
54
  };
56
- if (!isObject(value)) {
55
+ if (!isRecord(value)) {
57
56
  return refuse(JSON.stringify(value), shape.recordWant);
58
57
  }
59
58
  for (const [key, held] of Object.entries(value)) {
@@ -99,10 +98,10 @@ function readRecord(value, site, byReference, shape) {
99
98
  };
100
99
  const refusals = [];
101
100
  if (blankReason(reason)) {
102
- refusals.push({ ...written, reported: SUPPRESSION_WITHOUT_REASON });
101
+ refusals.push({ ...written, reported: CODE.suppressionWithoutReason });
103
102
  }
104
103
  if (written.path === "") {
105
- refusals.push({ ...written, reported: UNSCOPED_ENTRY });
104
+ refusals.push({ ...written, reported: CODE.unscopedIgnoreEntry });
106
105
  }
107
106
  if (refusals.length > 0) {
108
107
  return { records: [], refusals };
@@ -131,28 +130,27 @@ function byReferenceOf(symbols) {
131
130
  }
132
131
  /** Reads one document at the target root, binding each record against the run's declarations. */
133
132
  function readDocument(host, targetRoot, symbols, shape) {
134
- const file = joinPath(targetRoot, shape.file);
135
- if (host.kindOf(file) === "absent") {
133
+ const text = readTargetDocument(host, targetRoot, shape.file);
134
+ if (text === undefined) {
136
135
  return { records: [], refusals: [] };
137
136
  }
138
- const text = host.readFile(file);
139
137
  const malformed = (detail, site = documentSite(shape)) => {
140
138
  throw new SuppressionError(shape.mechanism, siteText(site), detail, shape.documentWant);
141
139
  };
142
- let parsed;
140
+ let document;
143
141
  try {
144
- parsed = JSON.parse(text);
142
+ document = readStrict(text, shape.array);
145
143
  }
146
144
  catch (error) {
147
- return malformed(`${shape.file}: ${error instanceof Error ? error.message : String(error)}`);
145
+ if (!(error instanceof StrictJSONError)) {
146
+ throw error;
147
+ }
148
+ return malformed(error.message, positionAt(text, shape.file, error.offset));
148
149
  }
149
- if (!isObject(parsed)) {
150
+ const parsed = document.value;
151
+ if (!isRecord(parsed)) {
150
152
  return malformed(shape.file);
151
153
  }
152
- const walk = walkDocument(text, shape.array);
153
- if (walk.twice !== undefined) {
154
- malformed(`the member ${JSON.stringify(walk.twice.name)} is written twice, so neither value is chosen`, positionAt(text, shape.file, walk.twice.offset));
155
- }
156
154
  for (const key of Object.keys(parsed)) {
157
155
  if (key !== "description" && key !== shape.array) {
158
156
  malformed(`the member ${JSON.stringify(key)} is not one the grammar declares`);
@@ -167,7 +165,7 @@ function readDocument(host, targetRoot, symbols, shape) {
167
165
  const records = [];
168
166
  const refusals = [];
169
167
  held.forEach((value, index) => {
170
- const offset = walk.starts[index];
168
+ const offset = document.starts.get(index);
171
169
  const site = offset === undefined ? documentSite(shape) : positionAt(text, shape.file, offset);
172
170
  const read = readRecord(value, site, byReference, shape);
173
171
  records.push(...read.records);
@@ -227,9 +225,9 @@ export function otherSchemeRows(text, own = OWN_SCHEME) {
227
225
  parsed = JSON.parse(text);
228
226
  }
229
227
  catch (error) {
230
- return refuse(error instanceof Error ? error.message : String(error));
228
+ return refuse(message(error));
231
229
  }
232
- const held = isObject(parsed) ? parsed[BASELINE_SHAPE.array] : undefined;
230
+ const held = isRecord(parsed) ? parsed[BASELINE_SHAPE.array] : undefined;
233
231
  if (!Array.isArray(held)) {
234
232
  return refuse(BASELINE_SHAPE.documentWant);
235
233
  }
@@ -238,7 +236,7 @@ export function otherSchemeRows(text, own = OWN_SCHEME) {
238
236
  readRecord(value, documentSite(BASELINE_SHAPE), new Map(), BASELINE_SHAPE);
239
237
  }
240
238
  catch (error) {
241
- return refuse(error instanceof Error ? error.message : String(error));
239
+ return refuse(message(error));
242
240
  }
243
241
  // readRecord has checked every member is a string and the code and symbol are present.
244
242
  const { code, symbol, path, reason } = value;
@@ -279,10 +277,9 @@ function* separatorsEscaped(pieces) {
279
277
  * A baseline holding one row per finding, in the order given, which is the report's, and
280
278
  * the other language's rows `kept`, Go's rows ahead of TypeScript's and each scheme's in
281
279
  * the order given, so two analyzers writing one document in either order write the same
282
- * bytes, in the form grammar/suppression.md fixes. Every recorded row carries the
283
- * provenance as its reason. A finding
284
- * missing a value a row needs, and an identity missing its name or version, are refused
285
- * rather than written, because the grammar requires a reason on every row.
280
+ * bytes, in the form grammar/suppression.md fixes. Every row carries the provenance as
281
+ * its reason, so a finding missing a value a row needs, or an identity missing its name
282
+ * or version, is refused rather than written.
286
283
  */
287
284
  export function writeBaseline(findings, provenance, kept = []) {
288
285
  if (provenance.analyzer === "" || provenance.version === "") {
@@ -3,13 +3,14 @@
3
3
  * one or more codes and a reason, and binding to every declaration that begins on the line
4
4
  * below it, or on its own line where it trails a token there.
5
5
  */
6
+ import { CODE } from "./contract/contract.generated.js";
6
7
  import { isConstructorDeclaration, SyntaxKind, } from "@typescript/native/ast";
7
8
  import { importBindings } from "./import-bindings.js";
8
9
  import { isParameterProperty, nodeKey } from "./inventory.js";
9
10
  import { byPosition, positionKey, renderPosition } from "./position.js";
10
11
  import { importRef } from "./ref.js";
11
12
  import { signatureOf } from "./signatures.js";
12
- import { isPartKind, SUPPRESSION_WITHOUT_REASON, SuppressionError, siteText, } from "./suppress.js";
13
+ import { isPartKind, SuppressionError, siteText, } from "./suppress.js";
13
14
  /**
14
15
  * The three expressions the grammar applies to the text of each line comment, in this
15
16
  * order: a comment in the namespace, a well-formed directive, and a directive that lacks
@@ -274,7 +275,7 @@ export function inlineSuppressions(perProject) {
274
275
  case "no-reason":
275
276
  for (const code of directive.codes) {
276
277
  refusals.push({
277
- reported: SUPPRESSION_WITHOUT_REASON,
278
+ reported: CODE.suppressionWithoutReason,
278
279
  code,
279
280
  symbol: "",
280
281
  path: site.path,
@@ -6,23 +6,18 @@
6
6
  * withholds a finding its code would have produced there, dormant when the configuration
7
7
  * withholds every such finding anyway, and stale otherwise.
8
8
  */
9
- import { ConfigError } from "./config.js";
9
+ import { KIND_ROWS, SYMBOL_SUBJECTS, } from "./contract/contract.generated.js";
10
+ import { UsageError } from "./usage.js";
10
11
  import { positionKey } from "./position.js";
11
12
  import { isRef } from "./ref.js";
12
- /** The code a suppression that carries no reason is reported under. */
13
- export const SUPPRESSION_WITHOUT_REASON = "DS1701";
14
- /** The code an ignore entry or a baseline row that names no path is reported under. */
15
- export const UNSCOPED_ENTRY = "DS1702";
16
- /** The code a suppression that withholds nothing is reported under. */
17
- export const STALE_SUPPRESSION = "DS1703";
18
13
  /**
19
14
  * A directive, an entry or a document the grammar refuses before any finding exists: an
20
15
  * instruction the analysis cannot carry out. It is a malformed input, so the command line
21
16
  * answers it with the usage code.
22
17
  */
23
- export class SuppressionError extends ConfigError {
18
+ export class SuppressionError extends UsageError {
24
19
  constructor(mechanism, at, text, want) {
25
- super("malformed", at, `${mechanism} ${at}: ${text}: want ${want}`);
20
+ super(`${mechanism} ${at}: ${text}: want ${want}`);
26
21
  this.name = "SuppressionError";
27
22
  }
28
23
  }
@@ -57,6 +52,7 @@ export const GO_REF_EXPRESSIONS = [
57
52
  `^go://${GO_PATH}#${GO_IDENT}$`,
58
53
  `^go://${GO_PATH}#${GO_IDENT}(?:\\.${GO_IDENT})+$`,
59
54
  `^go://${GO_PATH}#${GO_IDENT}(?:\\.${GO_IDENT})?\\[${GO_IDENT}\\]$`,
55
+ `^go://${GO_PATH}#(?:${GO_IDENT}(?:\\.${GO_IDENT})?)?\\(${GO_IDENT}\\)$`,
60
56
  `^go://${GO_PATH}#${GO_FILE}:file$`,
61
57
  `^go://${GO_PATH}#${GO_PATH}(?:@${GO_VERSION})?:require$`,
62
58
  `^go://${GO_PATH}#${GO_PATH}(?:@${GO_VERSION})?:replace$`,
@@ -70,73 +66,59 @@ export function isSymbolRef(value) {
70
66
  export function blankReason(reason) {
71
67
  return reason === undefined || /^[ \t\r\n]*$/u.test(reason);
72
68
  }
69
+ /** The subject of the kind each live code names. */
70
+ const SUBJECT_OF_CODE = new Map(KIND_ROWS.map((row) => [row.code, row.subject]));
71
+ /** The subject a finding about one symbol kind is about. */
72
+ function subjectOf(kind) {
73
+ return Object.hasOwn(SYMBOL_SUBJECTS, kind)
74
+ ? SYMBOL_SUBJECTS[kind]
75
+ : undefined;
76
+ }
77
+ /** Whether a subject is a row of a document rather than a declaration of the program. */
78
+ function rowOf(subject) {
79
+ return subject === "file" || subject === "dependency" || subject === "record";
80
+ }
73
81
  /**
74
- * The subject kinds that are a dependency row, each with the code that reports one. An
75
- * ignore entry or a baseline row naming the row's reference and file withholds its finding
76
- * and marks nothing, and an inline directive never binds one.
77
- */
78
- const DEPENDENCY_SUBJECTS = new Map([
79
- ["dependency", ["DS1601"]],
80
- ["module-directive", ["DS1605"]],
81
- ]);
82
- /**
83
- * The subject kinds that are a row of a document rather than a declaration of the program,
84
- * each with the codes that report one. Apart from a dependency row, nothing withholds a
85
- * finding about one.
82
+ * Whether a finding's subject is a row of a document rather than a declaration. Apart
83
+ * from a dependency row, nothing withholds a finding about one.
86
84
  */
87
- const ROW_SUBJECTS = new Map([
88
- ["file", ["DS1501", "DS1502"]],
89
- ...DEPENDENCY_SUBJECTS,
90
- ["root", ["DS1704"]],
91
- ["configured-declaration", ["DS1706"]],
92
- ["suppression", ["DS1701", "DS1702", "DS1703"]],
93
- ["edge", ["DS1705"]],
94
- ]);
95
- /** Whether a finding's subject is a row of a document rather than a declaration. */
96
85
  export function isRowSubject(kind) {
97
- return ROW_SUBJECTS.has(kind);
86
+ return rowOf(subjectOf(kind));
98
87
  }
99
88
  /** Whether a code reports a row of a document. */
100
89
  export function isRowCode(code) {
101
- return [...ROW_SUBJECTS.values()].some((codes) => codes.includes(code));
90
+ return rowOf(SUBJECT_OF_CODE.get(code));
102
91
  }
103
- /** Whether a finding's subject is a dependency row. */
92
+ /**
93
+ * Whether a finding's subject is a dependency row. An ignore entry or a baseline row
94
+ * naming the row's reference and file withholds its finding and marks nothing, and an
95
+ * inline directive never binds one.
96
+ */
104
97
  export function isDependencySubject(kind) {
105
- return DEPENDENCY_SUBJECTS.has(kind);
98
+ return subjectOf(kind) === "dependency";
106
99
  }
107
100
  /** Whether a code reports a dependency row, which only an ignore entry or a baseline row names. */
108
101
  export function isDependencyCode(code) {
109
- return [...DEPENDENCY_SUBJECTS.values()].some((codes) => codes.includes(code));
102
+ return SUBJECT_OF_CODE.get(code) === "dependency";
110
103
  }
111
104
  /** Whether a code reports a row of a document that no record of any mechanism can name. */
112
105
  export function isUnsuppressibleCode(code) {
113
106
  return isRowCode(code) && !isDependencyCode(code);
114
107
  }
115
108
  /**
116
- * The subject kinds that are a part of a declaration rather than a declaration: a part
109
+ * Whether a finding's subject is a part of the declaration its reference names: a part
117
110
  * has no reference of its own, so a finding about one names the declaration that holds
118
111
  * it, and the part is decided inside that declaration whatever uses it.
119
112
  */
120
- const PART_SUBJECTS = new Set([
121
- "parameter",
122
- "receiver",
123
- "result",
124
- "statement",
125
- "case",
126
- "store",
127
- ]);
128
- /** Whether a finding's subject is a part of the declaration its reference names. */
129
113
  export function isPartSubject(kind) {
130
- return PART_SUBJECTS.has(kind);
114
+ return subjectOf(kind) === "part";
131
115
  }
132
- /** The prefix of the codes of the part kinds, the intra-function range. */
133
- const PART_KIND_PREFIX = "DS18";
134
116
  /**
135
117
  * Whether a code is a part kind, whose record withholds its finding and marks nothing:
136
118
  * the record says the part is wanted, not that the declaration holding it is.
137
119
  */
138
120
  export function isPartKind(code) {
139
- return code.startsWith(PART_KIND_PREFIX);
121
+ return SUBJECT_OF_CODE.get(code) === "part";
140
122
  }
141
123
  /** One finding's identity: its code and the position of the thing it names. */
142
124
  function claimKey(finding) {
@@ -154,12 +136,12 @@ export function pairs(record, finding) {
154
136
  if (record.code !== finding.code) {
155
137
  return false;
156
138
  }
157
- if (DEPENDENCY_SUBJECTS.has(finding.symbol.kind)) {
139
+ if (isDependencySubject(finding.symbol.kind)) {
158
140
  return (record.mechanism !== "inline" &&
159
141
  record.symbol === finding.symbol.ref &&
160
142
  record.path === finding.position.path);
161
143
  }
162
- if (ROW_SUBJECTS.has(finding.symbol.kind)) {
144
+ if (isRowSubject(finding.symbol.kind)) {
163
145
  return false;
164
146
  }
165
147
  if (record.bound !== "" && record.bound === positionKey(finding.position)) {
package/dist/src/sweep.js CHANGED
@@ -84,10 +84,10 @@ export function runnerCalls(symbol) {
84
84
  * That run's roots are the graph's own and, of each test file, its top level and every
85
85
  * function it exports, which a test runner may call, and every export of a manual mock,
86
86
  * which the runner reads; any other export of a test file, and test-support code, which
87
- * runs where a test imports it, are judged as any file's are.
88
- * `held` seeds it as a mark or an exemption seeds a sweep.
87
+ * runs where a test imports it, are judged as any file's are. `held` seeds it as a mark
88
+ * seeds a sweep; with `namespaces` false a file read reaches none of the file's exports.
89
89
  */
90
- export function reachedWithTheTests(graph, held) {
90
+ export function reachedWithTheTests(graph, held, namespaces = true) {
91
91
  const tests = graph.symbols.flatMap((symbol, at) => {
92
92
  const exported = symbol.exported && graph.symbols[graph.parent[at] ?? OUTSIDE]?.kind === "file";
93
93
  const called = exported && runnerCalls(symbol);
@@ -97,9 +97,7 @@ export function reachedWithTheTests(graph, held) {
97
97
  ? [{ at, kind: "test-runner" }]
98
98
  : [];
99
99
  });
100
- return reachable({ ...graph, rooted: [...graph.rooted, ...tests] }, flagged(graph, held), {
101
- production: false,
102
- });
100
+ return reachable({ ...graph, rooted: [...graph.rooted, ...tests] }, flagged(graph, held), { production: false }, namespaces);
103
101
  }
104
102
  /**
105
103
  * The references a loaded consumer makes that the mode counts. A production mode counts
@@ -126,7 +124,7 @@ function flagged(graph, ids) {
126
124
  * included, while a root reaches through the references the mode counts. The held seed
127
125
  * runs first, so what it reaches is expanded under the wider rule.
128
126
  */
129
- function reachable(graph, held, mode) {
127
+ function reachable(graph, held, mode, namespaces = true) {
130
128
  const reached = graph.symbols.map(() => false);
131
129
  const expanded = graph.symbols.map(() => false);
132
130
  const queue = [];
@@ -154,7 +152,7 @@ function reachable(graph, held, mode) {
154
152
  if (!holding && mode.production && edge.test) {
155
153
  continue;
156
154
  }
157
- enter(edge.to, !edge.evaluation);
155
+ enter(edge.to, namespaces && !edge.evaluation);
158
156
  }
159
157
  }
160
158
  };
@@ -169,7 +167,7 @@ function reachable(graph, held, mode) {
169
167
  }
170
168
  // A consumer's reference reaches what it names as one of the target's own does.
171
169
  for (const edge of callsOf(graph, mode)) {
172
- enter(edge.to, !edge.evaluation);
170
+ enter(edge.to, namespaces && !edge.evaluation);
173
171
  }
174
172
  walk(false);
175
173
  return reached;
@@ -5,16 +5,14 @@
5
5
  * No name node resolves to such a member: the key's name resolves to the constant. An
6
6
  * iteration reads the member its well-known iteration symbol keys.
7
7
  */
8
- import { isComputedPropertyName, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isParenthesizedExpression, isPropertyAccessExpression, isSpreadElement, isStringLiteral, } from "@typescript/native/ast";
8
+ import { isComputedPropertyName, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isPropertyAccessExpression, isSpreadElement, isStringLiteral, } from "@typescript/native/ast";
9
+ import { parenthesesRemoved } from "./unwrap.js";
9
10
  import { nodeKey } from "./inventory.js";
10
11
  import { renderPosition } from "./position.js";
11
12
  import { isAnswered } from "./query.js";
12
13
  /** The identifier an element access's argument names its key by, where it is a name. */
13
14
  export function keyNameOf(argument) {
14
- let at = argument;
15
- while (isParenthesizedExpression(at)) {
16
- at = at.expression;
17
- }
15
+ const at = parenthesesRemoved(argument);
18
16
  if (isIdentifier(at)) {
19
17
  return at;
20
18
  }
@@ -12,10 +12,12 @@
12
12
  * type, because nothing ties the name to a type.
13
13
  */
14
14
  import { readComponent } from "./component-files.js";
15
- import { ConfigError } from "./config.js";
16
15
  import { memberNames } from "./member-names.js";
17
16
  import { extensionOf, isAbsolutePath, joinPath, normalizePath, relativePath } from "./paths.js";
18
17
  import { isComponentFile } from "./position.js";
18
+ import { bytewise } from "./text/compare.js";
19
+ import { filesBelow } from "./fs/tree.js";
20
+ import { UsageError } from "./usage.js";
19
21
  /** The kinds of declaration an action can name. */
20
22
  const READ_BY_A_TEMPLATE = new Set([
21
23
  "method",
@@ -30,13 +32,6 @@ const READ_BY_A_TEMPLATE = new Set([
30
32
  const READ_BY_MARKUP = new Set([...READ_BY_A_TEMPLATE, "enum-member"]);
31
33
  /** One identifier, as the grammar of the analyzed language spells one. */
32
34
  const IDENTIFIER = /[\p{ID_Start}$_][\p{ID_Continue}$\u200C\u200D]*/uy;
33
- /** Two strings ordered bytewise. */
34
- function compare(a, b) {
35
- if (a === b) {
36
- return 0;
37
- }
38
- return a < b ? -1 : 1;
39
- }
40
35
  /**
41
36
  * Every template under the configured directories, read once for the run, in path
42
37
  * order and each file once. A directory is named relative to the target root, and
@@ -48,34 +43,29 @@ function compare(a, b) {
48
43
  export function readTemplates(host, targetRoot, analysis) {
49
44
  const files = new Map();
50
45
  const read = (dir) => {
51
- const entries = [...host.readDirectory(joinPath(targetRoot, dir))].sort((a, b) => compare(a.name, b.name));
52
- for (const entry of entries) {
53
- const path = joinPath(dir, entry.name);
54
- if (entry.directory) {
55
- read(path);
56
- continue;
57
- }
46
+ for (const below of filesBelow(host, joinPath(targetRoot, dir), () => true)) {
47
+ const path = joinPath(dir, below);
58
48
  const absolute = joinPath(targetRoot, path);
59
49
  if (!files.has(path) && host.kindOf(absolute) === "file") {
60
50
  files.set(path, host.readFile(absolute));
61
51
  }
62
52
  }
63
53
  };
64
- for (const configured of analysis.templateDirs) {
54
+ for (const configured of analysis.template_dirs) {
65
55
  const dir = normalizePath(configured);
66
56
  if (isAbsolutePath(dir) ||
67
57
  dir === ".." ||
68
58
  dir.startsWith("../") ||
69
59
  host.kindOf(joinPath(targetRoot, dir)) !== "directory") {
70
- throw new ConfigError("malformed", "analysis.template_dirs", `analysis.template_dirs: ${JSON.stringify(configured)} is not a directory below the target root`);
60
+ throw new UsageError(`analysis.template_dirs: ${JSON.stringify(configured)} is not a directory below the target root`);
71
61
  }
72
62
  read(dir);
73
63
  }
74
64
  return {
75
- delimiters: analysis.templateDelimiters,
65
+ delimiters: analysis.template_delimiters,
76
66
  files: [...files]
77
67
  .map(([path, text]) => ({ path, text }))
78
- .sort((a, b) => compare(a.path, b.path)),
68
+ .sort((a, b) => bytewise(a.path, b.path)),
79
69
  };
80
70
  }
81
71
  /** The end of the quoted string that opens at `from`, past its closing quote. */
@@ -5,21 +5,18 @@
5
5
  * another such file, so it is judged with the tests: what it references is referenced by
6
6
  * test code.
7
7
  */
8
- import { CONVENTION_ROWS } from "./convention-rows.js";
8
+ import { CONVENTION_ROWS, TEST_LIBRARIES, TEST_LIBRARY_SCOPES } from "./convention-rows.js";
9
9
  import { packageOfSpecifier } from "./dependencies.js";
10
10
  /** The root kind that never takes a file out of test-support code: the skip only withholds. */
11
11
  const SKIP_ROOT = "type-error";
12
12
  /**
13
13
  * The test runners and test libraries an import of which is test evidence: each runner a
14
- * convention row reads test files for, the platform's own runner, and the libraries below.
14
+ * convention row reads test files for, and the convention table's test libraries.
15
15
  */
16
16
  const TEST_PACKAGES = new Set([
17
17
  ...CONVENTION_ROWS.filter((row) => row.testFiles !== undefined).map((row) => row.package),
18
- "node:test",
19
- "@playwright/test",
18
+ ...TEST_LIBRARIES,
20
19
  ]);
21
- /** The scopes every package of which is a test library. */
22
- const TEST_SCOPES = ["@testing-library/"];
23
20
  /**
24
21
  * The Node.js built-in modules a bare specifier names, which are no package. The set is
25
22
  * closed: Node.js adds a new built-in under the `node:` scheme alone
@@ -73,7 +70,7 @@ const NODE_BUILTINS = new Set([
73
70
  function namesTestPackage(specifier) {
74
71
  const name = specifier.startsWith("node:") ? specifier : packageOfSpecifier(specifier);
75
72
  return (name !== undefined &&
76
- (TEST_PACKAGES.has(name) || TEST_SCOPES.some((scope) => name.startsWith(scope))));
73
+ (TEST_PACKAGES.has(name) || TEST_LIBRARY_SCOPES.some((scope) => name.startsWith(scope))));
77
74
  }
78
75
  /**
79
76
  * Whether a file carries test evidence: an import of a test runner or a test library, or
@@ -106,9 +103,9 @@ function evidenceReader(tests, evidence) {
106
103
  * The files of one project that are test-support code, below the target root: the
107
104
  * largest set of files a test file reaches, none of them a test file or holding a root,
108
105
  * each carrying test evidence of its own, whose every user outside the set is a test
109
- * file. A root has a caller outside the reference graph: a runtime, a tool, a
110
- * configuration, or a consumer of a library's published API. `testFiles` are the files
111
- * the test-file rules classified.
106
+ * file; and the files only tests reach that are not among them. A root has a caller
107
+ * outside the reference graph: a runtime, a tool, a configuration, or a consumer of a
108
+ * library's published API. `testFiles` are the files the test-file rules classified.
112
109
  */
113
110
  export function testSupportFiles(symbols, references, roots, testFiles, evidence) {
114
111
  const fileOf = new Map(symbols.map((symbol) => [symbol.id, symbol.position.path]));
@@ -162,7 +159,7 @@ export function testSupportFiles(symbols, references, roots, testFiles, evidence
162
159
  const onlyTests = withoutOutsideUsers(new Set([...reached].filter((path) => !rooted.has(path))));
163
160
  const carries = evidenceReader(tests, evidence);
164
161
  const support = withoutOutsideUsers(new Set([...onlyTests].filter((path) => carries(onlyTests, path))));
165
- return support;
162
+ return { support, testUsed: new Set([...onlyTests].filter((path) => !support.has(path))) };
166
163
  }
167
164
  /** The references with every one a test-support file makes counted as a test reference. */
168
165
  export function supportReferences(references, support) {
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Two strings in the order of their UTF-8 bytes, the order every set of a run is read in.
3
+ * That is code point order, which UTF-16 code unit order (`<` on strings) breaks above
4
+ * U+FFFF: a surrogate unit is below U+E000 while the code point it encodes is above U+FFFF.
5
+ */
6
+ export function bytewise(a, b) {
7
+ if (a === b) {
8
+ return 0;
9
+ }
10
+ const length = Math.min(a.length, b.length);
11
+ for (let at = 0; at < length; at += 1) {
12
+ const x = a.charCodeAt(at);
13
+ const y = b.charCodeAt(at);
14
+ if (x !== y) {
15
+ return ranked(x) < ranked(y) ? -1 : 1;
16
+ }
17
+ }
18
+ return a.length < b.length ? -1 : 1;
19
+ }
20
+ /** One UTF-16 unit moved so that a surrogate ranks above every unit of U+E000 to U+FFFF. */
21
+ function ranked(unit) {
22
+ if (unit >= 0xe000) {
23
+ return unit - 0x800;
24
+ }
25
+ return unit >= 0xd800 ? unit + 0x2000 : unit;
26
+ }