@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,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
  }
@@ -31,6 +41,7 @@ export function openEngine(options) {
31
41
  ...(mapper === undefined ? {} : { runExternalCode: true }),
32
42
  });
33
43
  let closed = false;
44
+ const parsed = new Map();
34
45
  return {
35
46
  parseConfigFile: (file) => api.parseConfigFile(file),
36
47
  parseConfigJson: (json, configFile) => api.parseJsonConfigFileContent(json, { configFileName: configFile }),
@@ -54,8 +65,14 @@ export function openEngine(options) {
54
65
  moduleResolutions: { fallback: "resolve", entries: [...entries] },
55
66
  }),
56
67
  parseSourceFile: (fileName, text) => {
68
+ // Every project of a run reads the same files outside its program, so one parse serves all.
69
+ const known = parsed.get(fileName);
70
+ if (known?.text === text) {
71
+ return known.file;
72
+ }
57
73
  const retained = api.createSourceFile(fileName, text);
58
74
  try {
75
+ parsed.set(fileName, { text, file: retained.sourceFile });
59
76
  return retained.sourceFile;
60
77
  }
61
78
  finally {
@@ -245,14 +262,13 @@ function projectsOf(snapshot, built, written) {
245
262
  return opened;
246
263
  }
247
264
  /**
248
- * Runs `visit` over every project the compiler configurations name, inside one never updated
249
- * snapshot of one client, and answers in their order. A configuration `programs` describes is
250
- * built as that program, or opened from the configuration it writes. One the snapshot holds
251
- * nothing for ends the run under `"refuse"` and is named in the result under `"record"`.
252
- * `first` runs over every opened project before the first visit and `last` after the last,
253
- * 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.
254
269
  */
255
- 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();
256
272
  try {
257
273
  const built = [];
258
274
  const params = [];
@@ -274,30 +290,13 @@ export function runSession(engine, configFiles, visit, unopened = "refuse", prog
274
290
  const opened = projectsOf(snapshot, built, written);
275
291
  const log = new UnansweredLog();
276
292
  const views = [];
277
- const missing = [];
278
293
  for (const configFile of configFiles) {
279
294
  const project = opened.get(configFile);
280
- if (project === undefined) {
281
- if (unopened === "refuse") {
282
- throw new DiscoveryError(`${configFile} opened no project`, []);
283
- }
284
- missing.push(configFile);
285
- continue;
295
+ if (project !== undefined) {
296
+ views.push(viewOf(project, configFile, engine, log));
286
297
  }
287
- views.push(viewOf(project, configFile, engine, log));
288
298
  }
289
- first(views.map((view) => ({
290
- configFile: view.configFile,
291
- fileNames: () => view.ownSourceFiles().map((file) => file.fileName),
292
- })));
293
- const projects = views.map((view) => visit(view));
294
- last();
295
- return {
296
- projects,
297
- unopened: missing,
298
- timing: engine.getTimingInfo(),
299
- unanswered: log.questions,
300
- };
299
+ return use({ views }, () => log.questions);
301
300
  }
302
301
  finally {
303
302
  snapshot.dispose();
@@ -307,3 +306,16 @@ export function runSession(engine, configFiles, visit, unopened = "refuse", prog
307
306
  engine.close();
308
307
  }
309
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,10 +3,14 @@
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";
7
- import { dirnamePath, relativePath, resolvePath } from "./paths.js";
6
+ import { commonDirectory, emittedFrom } from "./load/emit-map.js";
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 = "*";
10
+ /** The extension a source file's name drops when the project writes it out. */
11
+ const SOURCE_EXTENSION = /\.[cm]?[jt]sx?$/u;
12
+ /** The extensions the project may write a source file's output under. */
13
+ const OUTPUT_EXTENSIONS = [".js", ".mjs", ".cjs", ".jsx", ".d.ts", ".d.mts", ".d.cts"];
10
14
  /**
11
15
  * Whether one path is what a subpath pattern stands for: the text before the
12
16
  * wildcard, any run of characters including the solidus, then the text after it.
@@ -17,6 +21,27 @@ function matchesSubpath(pattern, path) {
17
21
  const after = pattern.slice(at + SUBPATH_WILDCARD.length);
18
22
  return (path.length >= before.length + after.length && path.startsWith(before) && path.endsWith(after));
19
23
  }
24
+ /** The first own file of the paths given that the project holds, or none. */
25
+ function heldAt(byName, paths) {
26
+ for (const candidate of paths) {
27
+ const file = byName.get(candidate);
28
+ if (file !== undefined) {
29
+ return [file];
30
+ }
31
+ }
32
+ return [];
33
+ }
34
+ /** Where one project writes its output, and the directory it writes it from, absolute. */
35
+ function layoutOf(project, fileNames) {
36
+ const options = project.program.getCompilerOptions();
37
+ const configDir = dirnamePath(project.configFile);
38
+ const at = (option) => option === undefined ? undefined : resolvePath(configDir, option);
39
+ return {
40
+ outDir: at(options.outDir),
41
+ declarationDir: at(options.declarationDir),
42
+ sourceDir: at(options.rootDir) ?? commonDirectory(fileNames),
43
+ };
44
+ }
20
45
  /**
21
46
  * The lookups over one project's own files.
22
47
  *
@@ -36,12 +61,7 @@ export function sourceFilesOf(project, targetRoot) {
36
61
  byPath.set(path, file);
37
62
  }
38
63
  }
39
- const options = project.program.getCompilerOptions();
40
- const configDir = dirnamePath(project.configFile);
41
- const at = (option) => option === undefined ? undefined : resolvePath(configDir, option);
42
- const outDir = at(options.outDir);
43
- const declarationDir = at(options.declarationDir);
44
- const sourceDir = at(options.rootDir) ?? commonDirectory([...byName.keys()]);
64
+ const { outDir, declarationDir, sourceDir } = layoutOf(project, [...byName.keys()]);
45
65
  const layout = { outDir, declarationDir, sourceDirs: [sourceDir] };
46
66
  /** The source paths one written path may stand for, the path itself first. */
47
67
  const candidates = (path) => [path, ...emittedFrom(path, layout)];
@@ -49,15 +69,20 @@ export function sourceFilesOf(project, targetRoot) {
49
69
  byPath,
50
70
  byName,
51
71
  outputDirs: [...new Set([outDir, declarationDir])].filter((dir) => dir !== undefined),
72
+ writtenAs: (file) => {
73
+ const below = relativePath(sourceDir, file.fileName);
74
+ if (below === undefined) {
75
+ return [];
76
+ }
77
+ const written = [outDir, declarationDir].flatMap((dir) => dir === undefined
78
+ ? []
79
+ : OUTPUT_EXTENSIONS.map((extension) => joinPath(dir, below.replace(SOURCE_EXTENSION, extension))));
80
+ return [...new Set(written)].filter((path) => heldAt(byName, candidates(path))[0] === file);
81
+ },
82
+ namedAsOutput: (outputDir, path) => heldAt(byName, emittedFrom(path, { outDir: outputDir, sourceDirs: [sourceDir] })),
52
83
  named: (path) => {
53
84
  if (!path.includes(SUBPATH_WILDCARD)) {
54
- for (const candidate of candidates(path)) {
55
- const held = byName.get(candidate);
56
- if (held !== undefined) {
57
- return [held];
58
- }
59
- }
60
- return [];
85
+ return heldAt(byName, candidates(path));
61
86
  }
62
87
  const patterns = candidates(path).filter((candidate) => candidate.includes(SUBPATH_WILDCARD));
63
88
  return [...byName.values()].filter((file) => patterns.some((pattern) => matchesSubpath(pattern, file.fileName)));
@@ -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, 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,20 +98,28 @@ 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 };
109
108
  }
110
- const named = byReference.get(declarationKey(symbol, written.path)) ?? [];
109
+ const named = byReference.get(declarationKey(symbol, written.path)) ??
110
+ (isPartKind(code)
111
+ ? byReference.get(declarationKey(parameterHolder(symbol), written.path))
112
+ : undefined) ??
113
+ [];
111
114
  if (named.length === 0) {
112
115
  return { records: [{ ...written, bound: "" }], refusals: [] };
113
116
  }
114
117
  return { records: named.map((one) => ({ ...written, bound: one.id })), refusals: [] };
115
118
  }
119
+ /** The reference of the declaration a parameter's reference names it under, or `ref` itself. */
120
+ function parameterHolder(ref) {
121
+ return ref.endsWith(")") ? ref.slice(0, ref.lastIndexOf("(")) : ref;
122
+ }
116
123
  function byReferenceOf(symbols) {
117
124
  const byReference = new Map();
118
125
  for (const symbol of symbols) {
@@ -123,28 +130,27 @@ function byReferenceOf(symbols) {
123
130
  }
124
131
  /** Reads one document at the target root, binding each record against the run's declarations. */
125
132
  function readDocument(host, targetRoot, symbols, shape) {
126
- const file = joinPath(targetRoot, shape.file);
127
- if (host.kindOf(file) === "absent") {
133
+ const text = readTargetDocument(host, targetRoot, shape.file);
134
+ if (text === undefined) {
128
135
  return { records: [], refusals: [] };
129
136
  }
130
- const text = host.readFile(file);
131
137
  const malformed = (detail, site = documentSite(shape)) => {
132
138
  throw new SuppressionError(shape.mechanism, siteText(site), detail, shape.documentWant);
133
139
  };
134
- let parsed;
140
+ let document;
135
141
  try {
136
- parsed = JSON.parse(text);
142
+ document = readStrict(text, shape.array);
137
143
  }
138
144
  catch (error) {
139
- 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));
140
149
  }
141
- if (!isObject(parsed)) {
150
+ const parsed = document.value;
151
+ if (!isRecord(parsed)) {
142
152
  return malformed(shape.file);
143
153
  }
144
- const walk = walkDocument(text, shape.array);
145
- if (walk.twice !== undefined) {
146
- malformed(`the member ${JSON.stringify(walk.twice.name)} is written twice, so neither value is chosen`, positionAt(text, shape.file, walk.twice.offset));
147
- }
148
154
  for (const key of Object.keys(parsed)) {
149
155
  if (key !== "description" && key !== shape.array) {
150
156
  malformed(`the member ${JSON.stringify(key)} is not one the grammar declares`);
@@ -159,7 +165,7 @@ function readDocument(host, targetRoot, symbols, shape) {
159
165
  const records = [];
160
166
  const refusals = [];
161
167
  held.forEach((value, index) => {
162
- const offset = walk.starts[index];
168
+ const offset = document.starts.get(index);
163
169
  const site = offset === undefined ? documentSite(shape) : positionAt(text, shape.file, offset);
164
170
  const read = readRecord(value, site, byReference, shape);
165
171
  records.push(...read.records);
@@ -219,9 +225,9 @@ export function otherSchemeRows(text, own = OWN_SCHEME) {
219
225
  parsed = JSON.parse(text);
220
226
  }
221
227
  catch (error) {
222
- return refuse(error instanceof Error ? error.message : String(error));
228
+ return refuse(message(error));
223
229
  }
224
- const held = isObject(parsed) ? parsed[BASELINE_SHAPE.array] : undefined;
230
+ const held = isRecord(parsed) ? parsed[BASELINE_SHAPE.array] : undefined;
225
231
  if (!Array.isArray(held)) {
226
232
  return refuse(BASELINE_SHAPE.documentWant);
227
233
  }
@@ -230,7 +236,7 @@ export function otherSchemeRows(text, own = OWN_SCHEME) {
230
236
  readRecord(value, documentSite(BASELINE_SHAPE), new Map(), BASELINE_SHAPE);
231
237
  }
232
238
  catch (error) {
233
- return refuse(error instanceof Error ? error.message : String(error));
239
+ return refuse(message(error));
234
240
  }
235
241
  // readRecord has checked every member is a string and the code and symbol are present.
236
242
  const { code, symbol, path, reason } = value;
@@ -271,10 +277,9 @@ function* separatorsEscaped(pieces) {
271
277
  * A baseline holding one row per finding, in the order given, which is the report's, and
272
278
  * the other language's rows `kept`, Go's rows ahead of TypeScript's and each scheme's in
273
279
  * the order given, so two analyzers writing one document in either order write the same
274
- * bytes, in the form grammar/suppression.md fixes. Every recorded row carries the
275
- * provenance as its reason. A finding
276
- * missing a value a row needs, and an identity missing its name or version, are refused
277
- * 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.
278
283
  */
279
284
  export function writeBaseline(findings, provenance, kept = []) {
280
285
  if (provenance.analyzer === "" || provenance.version === "") {
@@ -3,11 +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";
8
+ import { importBindings } from "./import-bindings.js";
7
9
  import { isParameterProperty, nodeKey } from "./inventory.js";
8
10
  import { byPosition, positionKey, renderPosition } from "./position.js";
11
+ import { importRef } from "./ref.js";
9
12
  import { signatureOf } from "./signatures.js";
10
- import { isPartKind, SUPPRESSION_WITHOUT_REASON, SuppressionError, siteText, } from "./suppress.js";
13
+ import { isPartKind, SuppressionError, siteText, } from "./suppress.js";
11
14
  /**
12
15
  * The three expressions the grammar applies to the text of each line comment, in this
13
16
  * order: a comment in the namespace, a well-formed directive, and a directive that lacks
@@ -144,7 +147,7 @@ function bindingKey(one) {
144
147
  * function also begins, for a directive, on the line of each of its parameters, and a
145
148
  * class, for a part kind, on the lines of its constructor and its constructor's parameters.
146
149
  */
147
- function declarationLines(file, held, symbols) {
150
+ function declarationLines(file, held, symbols, targetRoot) {
148
151
  const below = new Map();
149
152
  const add = (offset, one) => {
150
153
  const line = file.getLineAndCharacterOfPosition(tokenStart(file.text, offset)).line + 1;
@@ -186,6 +189,20 @@ function declarationLines(file, held, symbols) {
186
189
  node.forEachChild(visit);
187
190
  };
188
191
  file.forEachChild(visit);
192
+ // An import binding is a subject of its own, bound on its line as a declaration is.
193
+ const fileId = held.declarations.get(nodeKey(file, file));
194
+ const moduleRef = held.symbols.find((symbol) => symbol.id === fileId)?.ref ?? "";
195
+ for (const name of moduleRef === "" ? [] : importBindings(file)) {
196
+ const position = renderPosition(file, targetRoot, name.getStart());
197
+ const one = { id: positionKey(position), ref: importRef(moduleRef, name.text), position };
198
+ let statement = name;
199
+ while (statement.parent.kind !== SyntaxKind.SourceFile) {
200
+ statement = statement.parent;
201
+ }
202
+ for (const at of [statement.pos, name.getStart()]) {
203
+ add(at, { declared: one, part: one });
204
+ }
205
+ }
189
206
  return below;
190
207
  }
191
208
  /**
@@ -210,7 +227,7 @@ export function inlineDirectives(project, held, targetRoot) {
210
227
  if (comments.length === 0) {
211
228
  continue;
212
229
  }
213
- const below = declarationLines(file, held, symbols);
230
+ const below = declarationLines(file, held, symbols, targetRoot);
214
231
  for (const comment of comments) {
215
232
  const site = renderPosition(file, targetRoot, comment.offset);
216
233
  const target = trails(file, comment.offset) ? site.line : site.line + 1;
@@ -258,7 +275,7 @@ export function inlineSuppressions(perProject) {
258
275
  case "no-reason":
259
276
  for (const code of directive.codes) {
260
277
  refusals.push({
261
- reported: SUPPRESSION_WITHOUT_REASON,
278
+ reported: CODE.suppressionWithoutReason,
262
279
  code,
263
280
  symbol: "",
264
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) {
@@ -144,27 +126,29 @@ function claimKey(finding) {
144
126
  }
145
127
  /**
146
128
  * Whether one record names one finding. A record reaches a finding in two ways: it bound
147
- * the declaration at the finding's position, or it names the finding's reference and the
148
- * file the finding is reported in, each exactly. The second is how an entry copied from a
149
- * finding about a part of a declaration reaches the part, and the only way any record
150
- * reaches a dependency row.
129
+ * the declaration at the finding's position, or it names the finding's reference, or the
130
+ * reference of the declaration holding a part that has one of its own, and the file the
131
+ * finding is reported in, each exactly. The second is how an entry copied from a finding
132
+ * about a part of a declaration reaches the part, and the only way any record reaches a
133
+ * dependency row.
151
134
  */
152
135
  export function pairs(record, finding) {
153
136
  if (record.code !== finding.code) {
154
137
  return false;
155
138
  }
156
- if (DEPENDENCY_SUBJECTS.has(finding.symbol.kind)) {
139
+ if (isDependencySubject(finding.symbol.kind)) {
157
140
  return (record.mechanism !== "inline" &&
158
141
  record.symbol === finding.symbol.ref &&
159
142
  record.path === finding.position.path);
160
143
  }
161
- if (ROW_SUBJECTS.has(finding.symbol.kind)) {
144
+ if (isRowSubject(finding.symbol.kind)) {
162
145
  return false;
163
146
  }
164
147
  if (record.bound !== "" && record.bound === positionKey(finding.position)) {
165
148
  return true;
166
149
  }
167
- return record.symbol === finding.symbol.ref && record.path === finding.position.path;
150
+ return ((record.symbol === finding.symbol.ref || record.symbol === finding.symbol.holder) &&
151
+ record.path === finding.position.path);
168
152
  }
169
153
  /**
170
154
  * Decides every record against `would`, the findings the run would produce were no record