@cplieger/deadset-ts 8.0.0-dev.1 → 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 (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 +4 -3
  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
@@ -13,8 +13,6 @@
13
13
 
14
14
  import {
15
15
  isArrayLiteralExpression,
16
- isAsExpression,
17
- isBinaryExpression,
18
16
  isCallExpression,
19
17
  isExportAssignment,
20
18
  isIdentifier,
@@ -22,12 +20,9 @@ import {
22
20
  isNewExpression,
23
21
  isNoSubstitutionTemplateLiteral,
24
22
  isObjectLiteralExpression,
25
- isParenthesizedExpression,
26
23
  isPropertyAccessExpression,
27
24
  isPropertyAssignment,
28
- isSatisfiesExpression,
29
25
  isStringLiteral,
30
- isTemplateExpression,
31
26
  SyntaxKind,
32
27
  type Node,
33
28
  type SourceFile,
@@ -40,7 +35,7 @@ import {
40
35
  type ParseFile,
41
36
  } from "./configuration-files.ts";
42
37
  import type { Selected } from "./conventions.ts";
43
- import { globExpression } from "./glob.ts";
38
+ import { buildScriptEntries } from "./build-scripts.ts";
44
39
  import type { Host } from "./host.ts";
45
40
  import { htmlEntries } from "./html-entries.ts";
46
41
  import { documentEntries } from "./mdx-entries.ts";
@@ -48,7 +43,10 @@ import { dirnamePath, relativePath, resolvePath } from "./paths.ts";
48
43
  import { UNANSWERED } from "./query.ts";
49
44
  import type { ProjectView } from "./session.ts";
50
45
  import type { SourceFiles } from "./source-files.ts";
46
+ import { computedLoadDirectory, leadingLiteral } from "./unseen-loads.ts";
51
47
  import { workflowEntries } from "./workflow-steps.ts";
48
+ import { unwrapped } from "./unwrap.ts";
49
+ import { globExpression } from "./text/glob.ts";
52
50
 
53
51
  /** Which rule entered a file. */
54
52
  export type EntryRule =
@@ -56,6 +54,8 @@ export type EntryRule =
56
54
  | "configuration-file"
57
55
  /** A file a relative string of a configuration module or a JSON configuration file names. */
58
56
  | "configuration-string"
57
+ /** An entry a build script's bundler call names, whose exports the bundle exposes. */
58
+ | "bundler-entry"
59
59
  /** The test runner: its workspace configuration, its setup files and the test files. */
60
60
  | "test-runner"
61
61
  /** The test files below the browser-test runner's test directory. */
@@ -164,15 +164,6 @@ function literalOf(node: Node | undefined): string | undefined {
164
164
  return undefined;
165
165
  }
166
166
 
167
- /** The expression a wrapper that changes no value holds: parentheses and type assertions. */
168
- function unwrapped(node: Node): Node {
169
- let at = node;
170
- while (isParenthesizedExpression(at) || isAsExpression(at) || isSatisfiesExpression(at)) {
171
- at = at.expression;
172
- }
173
- return at;
174
- }
175
-
176
167
  /**
177
168
  * The object literals one configuration file's default export is written as: the
178
169
  * literal itself, or each literal argument of the call it is, at any depth of calls.
@@ -324,10 +315,7 @@ function configurationEntries(
324
315
  strings(dirnamePath(document.path), unselectedIn(document.path, document.strings));
325
316
  }
326
317
  for (const { file } of outside) {
327
- strings(
328
- dirnamePath(file.fileName),
329
- moduleStrings(file).map((one) => one.text),
330
- );
318
+ strings(dirnamePath(file.fileName), unselected(file));
331
319
  }
332
320
  return found;
333
321
  }
@@ -541,18 +529,6 @@ function workersOf<Brand>(
541
529
  return found;
542
530
  }
543
531
 
544
- /** The literal text a computed path starts with: a template's head, or a concatenation's first literal. */
545
- function leadingLiteral(node: Node): string | undefined {
546
- const at = unwrapped(node);
547
- if (isTemplateExpression(at)) {
548
- return at.head.text;
549
- }
550
- if (isBinaryExpression(at) && at.operatorToken.kind === SyntaxKind.PlusToken) {
551
- return literalOf(unwrapped(at.left)) ?? leadingLiteral(at.left);
552
- }
553
- return undefined;
554
- }
555
-
556
532
  /** Whether one call is `import.meta.glob(...)`. */
557
533
  function isMetaGlob(node: Node): boolean {
558
534
  if (!isCallExpression(node) || !isPropertyAccessExpression(node.expression)) {
@@ -579,13 +555,8 @@ function computedImportsOf(file: SourceFile, files: SourceFiles): readonly Entry
579
555
  const dir = dirnamePath(file.fileName);
580
556
  const found: EntryPoint[] = [];
581
557
  const below = (prefix: string): void => {
582
- const target = resolvePath(dir, prefix);
583
- if (
584
- !isModuleRelative(prefix) ||
585
- !prefix.endsWith("/") ||
586
- target === dir ||
587
- relativePath(target, dir) !== undefined
588
- ) {
558
+ const target = computedLoadDirectory(dir, prefix);
559
+ if (target === undefined) {
589
560
  return;
590
561
  }
591
562
  for (const candidate of files.byName.values()) {
@@ -680,6 +651,9 @@ export function entryPoints<Brand>(
680
651
  ...(names === undefined ? {} : { names }),
681
652
  });
682
653
  }
654
+ for (const { file, source } of buildScriptEntries(host, targetRoot, files, parse)) {
655
+ found.push({ file, rule: "bundler-entry", source, exports: true });
656
+ }
683
657
  for (const { file, source } of workflowEntries(host, targetRoot, files)) {
684
658
  found.push({ file, rule: "workflow-step", source, exports: true });
685
659
  }
package/src/enum-group.ts CHANGED
@@ -18,7 +18,6 @@ import {
18
18
  isIdentifier,
19
19
  isMethodDeclaration,
20
20
  isNoSubstitutionTemplateLiteral,
21
- isParenthesizedExpression,
22
21
  isParenthesizedTypeNode,
23
22
  isPropertyAccessExpression,
24
23
  isReturnStatement,
@@ -28,6 +27,7 @@ import {
28
27
  type Node,
29
28
  type SourceFile,
30
29
  } from "@typescript/native/ast";
30
+ import { parenthesesRemoved } from "./unwrap.ts";
31
31
  import { TypeFlags, type Type } from "@typescript/native/sync";
32
32
  import { aliasChains } from "./alias-chain.ts";
33
33
  import type { DetectorInput, Evidence } from "./exempt.ts";
@@ -55,15 +55,6 @@ interface Site {
55
55
  readonly at: Node;
56
56
  }
57
57
 
58
- /** The expression one node is, through however many parentheses are written around it. */
59
- function unparenthesized(node: Node): Node {
60
- let held = node;
61
- while (isParenthesizedExpression(held)) {
62
- held = held.expression;
63
- }
64
- return held;
65
- }
66
-
67
58
  /**
68
59
  * The name node a type node names its declaration by: the identifier of a type
69
60
  * reference, the right side of a qualified one, through parentheses. A type that is no
@@ -86,7 +77,7 @@ function typeName(type: Node | undefined): Node | undefined {
86
77
 
87
78
  /** The name node an expression names an object by: an identifier, or a property access's name. */
88
79
  function objectName(expression: Node): Node | undefined {
89
- const held = unparenthesized(expression);
80
+ const held = parenthesesRemoved(expression);
90
81
  if (isIdentifier(held)) {
91
82
  return held;
92
83
  }
@@ -16,7 +16,6 @@ import {
16
16
  isNoSubstitutionTemplateLiteral,
17
17
  isNumericLiteral,
18
18
  isObjectLiteralExpression,
19
- isParenthesizedExpression,
20
19
  isPropertyAccessExpression,
21
20
  isPropertyAssignment,
22
21
  isShorthandPropertyAssignment,
@@ -31,6 +30,7 @@ import { SymbolFlags, TypeFlags, type Type } from "@typescript/native/sync";
31
30
  import { must } from "./query.ts";
32
31
  import type { ProjectView } from "./session.ts";
33
32
  import { checkedTypes } from "./value-reach.ts";
33
+ import { parenthesesRemoved } from "./unwrap.ts";
34
34
 
35
35
  /** The types that keep nothing of the value they are given. */
36
36
  const ERASED = TypeFlags.Any | TypeFlags.Unknown;
@@ -81,10 +81,6 @@ function literalKey(node: Expression): string | undefined {
81
81
  : undefined;
82
82
  }
83
83
 
84
- function unwrapped(node: Expression): Expression {
85
- return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
86
- }
87
-
88
84
  /**
89
85
  * Whether a variable declaration's type can differ from what is stored in it: it carries an
90
86
  * annotation, or it has neither an annotation nor an initializer, so its type evolves.
@@ -150,8 +146,8 @@ function propertyAt(source: Expression, name: string): Expression | undefined {
150
146
  * here, and a default value is stored in its target too.
151
147
  */
152
148
  function patternTargets(pattern: Expression, from: Stored, out: Target[]): void {
153
- const target = unwrapped(pattern);
154
- const source = from.path.length === 0 ? unwrapped(from.value) : from.value;
149
+ const target = parenthesesRemoved(pattern);
150
+ const source = from.path.length === 0 ? parenthesesRemoved(from.value) : from.value;
155
151
  const into = (element: Expression, step: Step, literal: Expression | undefined): void => {
156
152
  patternTargets(
157
153
  element,
@@ -1,12 +1,4 @@
1
- /**
2
- * The exemption classes of the Contract's vocabulary, in its order: each class's name, its
3
- * languages and, for a TypeScript class, which private members it may retain. The rows are
4
- * written out, as the fixed severity codes are, and a test holds them equal to the Contract.
5
- * A class name is a type, so a table or a record naming a class outside the vocabulary, or
6
- * one of another language only, does not compile.
7
- */
8
-
9
- import type { Language } from "./config.ts";
1
+ import { EXEMPTION_CLASSES, type Language } from "./contract/contract.generated.ts";
10
2
 
11
3
  /**
12
4
  * Whether a class may retain a member declared with the `private` modifier, and one
@@ -19,67 +11,6 @@ export interface TypeScriptVisibility {
19
11
  readonly privateName: boolean;
20
12
  }
21
13
 
22
- /** One class of the vocabulary. */
23
- interface ExemptionClassRow {
24
- readonly class: string;
25
- readonly languages: readonly Language[];
26
- /** Present exactly on a class that runs on TypeScript. */
27
- readonly typescriptVisibility?: TypeScriptVisibility;
28
- }
29
-
30
- /** Every class of the vocabulary, in the order the Contract lists them. */
31
- export const EXEMPTION_CLASSES = [
32
- {
33
- class: "interface-satisfaction",
34
- languages: ["go", "ts"],
35
- typescriptVisibility: { private: false, privateName: false },
36
- },
37
- { class: "encoding-reflection", languages: ["go"] },
38
- { class: "format-verb-contract", languages: ["go"] },
39
- { class: "errors-duck-typing", languages: ["go"] },
40
- {
41
- class: "enum-group",
42
- languages: ["go", "ts"],
43
- typescriptVisibility: { private: false, privateName: false },
44
- },
45
- {
46
- class: "generated-file",
47
- languages: ["go", "ts"],
48
- typescriptVisibility: { private: true, privateName: false },
49
- },
50
- { class: "linkname-cgo-asm-plugin", languages: ["go"] },
51
- {
52
- class: "template-field",
53
- languages: ["go", "ts"],
54
- typescriptVisibility: { private: true, privateName: false },
55
- },
56
- {
57
- class: "reflective-lookup",
58
- languages: ["go", "ts"],
59
- typescriptVisibility: { private: true, privateName: false },
60
- },
61
- {
62
- class: "decorator",
63
- languages: ["ts"],
64
- typescriptVisibility: { private: true, privateName: false },
65
- },
66
- {
67
- class: "injection-container",
68
- languages: ["ts"],
69
- typescriptVisibility: { private: true, privateName: false },
70
- },
71
- {
72
- class: "framework-lifecycle",
73
- languages: ["ts"],
74
- typescriptVisibility: { private: true, privateName: false },
75
- },
76
- {
77
- class: "serialization-contract",
78
- languages: ["ts"],
79
- typescriptVisibility: { private: true, privateName: false },
80
- },
81
- ] as const satisfies readonly ExemptionClassRow[];
82
-
83
14
  type Row = (typeof EXEMPTION_CLASSES)[number];
84
15
 
85
16
  /** The name of a class of the vocabulary. */
@@ -116,8 +47,3 @@ const VISIBILITY: ReadonlyMap<TSExemptionClass, TypeScriptVisibility> = new Map(
116
47
  export function typescriptVisibilityOf(exemptionClass: TSExemptionClass): TypeScriptVisibility {
117
48
  return VISIBILITY.get(exemptionClass) ?? { private: false, privateName: false };
118
49
  }
119
-
120
- /** Whether one name is a class of the vocabulary, whichever language it runs on. */
121
- export function isExemptionClass(name: string): name is ExemptionClass {
122
- return EXEMPTION_CLASSES.some((row) => row.class === name);
123
- }
package/src/exempt.ts CHANGED
@@ -7,24 +7,23 @@
7
7
  * hands it the table of the classes it has.
8
8
  */
9
9
 
10
- import type { Config, TSSection } from "./config.ts";
11
- import { ConfigError } from "./config.ts";
10
+ import { EXEMPTION_CLASSES } from "./contract/contract.generated.ts";
11
+ import type { ResolvedConfig } from "./config.ts";
12
12
  import {
13
- EXEMPTION_CLASSES,
14
- isExemptionClass,
15
13
  TS_EXEMPTION_CLASSES,
16
14
  typescriptVisibilityOf,
17
15
  type ExemptionClass,
18
16
  type TSExemptionClass,
19
17
  type TypeScriptVisibility,
20
18
  } from "./exempt-classes.ts";
21
- import type { Inventory, InventorySymbol, SymbolKind, Visibility } from "./inventory.ts";
19
+ import type { Inventory, InventorySymbol, Visibility } from "./inventory.ts";
22
20
  import type { Matrix } from "./matrix.ts";
23
21
  import { byPosition, positionKey, type Position } from "./position.ts";
24
22
  import { Unanswerable } from "./query.ts";
25
23
  import type { ProjectView } from "./session.ts";
26
24
  import type { Templates } from "./template-field.ts";
27
25
  import { sweep, type Exemption, type Implementing, type Mode, type SweepInput } from "./sweep.ts";
26
+ import { bytewise } from "./text/compare.ts";
28
27
 
29
28
  /** One declaration a detector holds back, and where and why. */
30
29
  export interface Evidence {
@@ -37,6 +36,8 @@ export interface Evidence {
37
36
  readonly whileLive?: string;
38
37
  /** Where the evidence is an implementation of interface methods, which and how. */
39
38
  readonly implementing?: Implementing;
39
+ /** Whether the evidence holds only where a write the mode counts stores into the declaration. */
40
+ readonly whileWritten?: true;
40
41
  }
41
42
 
42
43
  /** What a detector reads of one project, while the project's view is open. */
@@ -47,7 +48,7 @@ export interface DetectorInput<Brand> {
47
48
  /** The configured templates, read once for the run. */
48
49
  readonly templates: Templates;
49
50
  /** The `ts` section, whose declarations the configured classes read. */
50
- readonly ts: TSSection;
51
+ readonly ts: ResolvedConfig["ts"];
51
52
  /** The absolute path of each declared consumer, whose modules are part of the analysis. */
52
53
  readonly consumers: readonly string[];
53
54
  /** The project's generated files, by path below the target root, each with the detail its retentions name. */
@@ -71,24 +72,11 @@ interface Holding {
71
72
  }
72
73
 
73
74
  /**
74
- * The classes `exemptions.disabled` switches off. A name is refused unless the
75
- * vocabulary holds it; a class that runs on another language only is admitted and
76
- * switches nothing off here, so one configuration serves every analyzer.
75
+ * The classes `exemptions.disabled` switches off. A class that runs on another language
76
+ * only switches nothing off here, so one configuration serves every analyzer.
77
77
  */
78
- export function disabledClasses(config: Config): ReadonlySet<ExemptionClass> {
79
- const disabled = new Set<ExemptionClass>();
80
- for (const name of config.exemptionsDisabled) {
81
- if (!isExemptionClass(name)) {
82
- const known = EXEMPTION_CLASSES.map((row) => row.class).join(", ");
83
- throw new ConfigError(
84
- "malformed",
85
- "exemptions.disabled",
86
- `exemptions.disabled: ${JSON.stringify(name)} is not an exemption class: the classes are ${known}`,
87
- );
88
- }
89
- disabled.add(name);
90
- }
91
- return disabled;
78
+ export function disabledClasses(config: ResolvedConfig): ReadonlySet<ExemptionClass> {
79
+ return new Set(config.exemptions.disabled);
92
80
  }
93
81
 
94
82
  /**
@@ -105,20 +93,12 @@ function retainable(allowed: TypeScriptVisibility, visibility: Visibility | unde
105
93
  return true;
106
94
  }
107
95
 
108
- /**
109
- * The kinds of declaration each class can hold back, which a detector that stops holds
110
- * back whole. A class with no row here holds back every declaration but a file.
111
- */
112
- const RETAINABLE: ReadonlyMap<TSExemptionClass, ReadonlySet<SymbolKind>> = new Map([
113
- ["interface-satisfaction", new Set<SymbolKind>(["method", "class-member"])],
114
- ["enum-group", new Set<SymbolKind>(["enum-member"])],
115
- ["injection-container", new Set<SymbolKind>(["method", "class-member"])],
116
- ["framework-lifecycle", new Set<SymbolKind>(["method", "class-member"])],
117
- [
118
- "serialization-contract",
119
- new Set<SymbolKind>(["method", "class-member", "interface-method", "type-member"]),
120
- ],
121
- ]);
96
+ /** The kinds of declaration each class can hold back, which a detector that stops holds back whole. */
97
+ const RETAINABLE: ReadonlyMap<string, ReadonlySet<string>> = new Map(
98
+ EXEMPTION_CLASSES.flatMap((row) =>
99
+ "ts" in row.retainsKinds ? [[row.class, new Set<string>(row.retainsKinds.ts)] as const] : [],
100
+ ),
101
+ );
122
102
 
123
103
  /** The detail of a record a detector that could not finish holds. */
124
104
  const UNANSWERED_DETAIL = "kept live by a question the checker did not answer";
@@ -147,7 +127,7 @@ function detected<Brand>(
147
127
  }
148
128
  const kinds = RETAINABLE.get(exemptionClass);
149
129
  return input.held.symbols
150
- .filter((symbol) => (kinds === undefined ? symbol.kind !== "file" : kinds.has(symbol.kind)))
130
+ .filter((symbol) => kinds?.has(symbol.kind) === true)
151
131
  .map((symbol) => ({ id: symbol.id, detail: UNANSWERED_DETAIL, site: symbol.position }));
152
132
  }
153
133
  }
@@ -193,12 +173,26 @@ export function evidenceRecords<Brand>(
193
173
  site: evidence.site,
194
174
  ...(evidence.whileLive === undefined ? {} : { whileLive: evidence.whileLive }),
195
175
  ...(evidence.implementing === undefined ? {} : { implementing: evidence.implementing }),
176
+ ...(evidence.whileWritten === undefined ? {} : { whileWritten: evidence.whileWritten }),
196
177
  });
197
178
  }
198
179
  }
199
180
  return found;
200
181
  }
201
182
 
183
+ /**
184
+ * The records that hold given the declarations a write the mode counts stores into: one
185
+ * that holds only while written holds where its declaration is among them.
186
+ */
187
+ export function writtenRecords(
188
+ records: readonly Exemption[],
189
+ written: ReadonlySet<string>,
190
+ ): readonly Exemption[] {
191
+ return records.flatMap(({ whileWritten, ...record }) =>
192
+ whileWritten === undefined || written.has(record.id) ? [record] : [],
193
+ );
194
+ }
195
+
202
196
  /** The records that hold under one mode, beside the evidence a production mode found in a test file. */
203
197
  export function heldRecords(records: readonly Exemption[], holding: Holding): Detected {
204
198
  const found: Exemption[] = [];
@@ -213,14 +207,6 @@ export function heldRecords(records: readonly Exemption[], holding: Holding): De
213
207
  return { records: exemptionsOf(found), inTestFiles };
214
208
  }
215
209
 
216
- /** Two strings ordered bytewise. */
217
- function compare(a: string, b: string): number {
218
- if (a === b) {
219
- return 0;
220
- }
221
- return a < b ? -1 : 1;
222
- }
223
-
224
210
  /**
225
211
  * Records as a run holds them: ordered by site, then class, then declaration, and one
226
212
  * per declaration, class, detail and file it holds while live, the one at the first
@@ -228,7 +214,7 @@ function compare(a: string, b: string): number {
228
214
  */
229
215
  export function exemptionsOf(found: readonly Exemption[]): readonly Exemption[] {
230
216
  const ordered = [...found].sort(
231
- (a, b) => byPosition(a.site, b.site) || compare(a.class, b.class) || compare(a.id, b.id),
217
+ (a, b) => byPosition(a.site, b.site) || bytewise(a.class, b.class) || bytewise(a.id, b.id),
232
218
  );
233
219
  const seen = new Set<string>();
234
220
  return ordered.filter((record) => {
@@ -300,8 +286,8 @@ export function retainedIn(
300
286
  }
301
287
 
302
288
  /**
303
- * One line per held-back declaration, in site order: its reference, then each record's
304
- * class, site and detail, all separated by tabs.
289
+ * One line per held-back declaration, in site order: its reference and position, then
290
+ * each record's class, site and detail, all separated by tabs.
305
291
  */
306
292
  export function retainedLines(
307
293
  symbols: readonly InventorySymbol[],
@@ -318,6 +304,6 @@ export function retainedLines(
318
304
  positionKey(record.site),
319
305
  record.detail,
320
306
  ]);
321
- return [[symbol.ref, ...fields].join("\t")];
307
+ return [[symbol.ref, positionKey(symbol.position), ...fields].join("\t")];
322
308
  });
323
309
  }
package/src/failure.ts ADDED
@@ -0,0 +1,63 @@
1
+ import { FAILURE_CLASSES, FAILURE_PREFIXES } from "./contract/contract.generated.ts";
2
+ import type { LocatedDiagnostic } from "./load/diagnostics.ts";
3
+ import { PRODUCT } from "./product.ts";
4
+
5
+ /** A class of failure the Contract's exit-code table names, of either group. */
6
+ type FailureClass = keyof typeof FAILURE_CLASSES;
7
+
8
+ /** One cause the run ends with the failure code for. */
9
+ export interface Failure {
10
+ readonly failureClass: FailureClass;
11
+ /** What fails and the fix, on one line. */
12
+ readonly detail: string;
13
+ }
14
+
15
+ /** The failure as a class line of the error stream, opened by its group's prefix. */
16
+ export function failureLine(failure: Failure): string {
17
+ return `${FAILURE_PREFIXES[FAILURE_CLASSES[failure.failureClass]]}${failure.failureClass}: ${failure.detail}`;
18
+ }
19
+
20
+ /**
21
+ * The lines of the error stream that name the failures: the first one's class line, the run's
22
+ * only one, then each further failure as its class and detail with no group prefix.
23
+ */
24
+ export function failureLines([first, ...further]: readonly [Failure, ...Failure[]]): string[] {
25
+ return [failureLine(first), ...further.map((one) => `${one.failureClass}: ${one.detail}`)];
26
+ }
27
+
28
+ /**
29
+ * A run the analysis cannot answer. It ends the run with the failure code and no finding
30
+ * list, printing the compiler's located errors and then the failures' lines.
31
+ */
32
+ export class FailureExit extends Error {
33
+ readonly failures: readonly [Failure, ...Failure[]];
34
+ /** The compiler's errors behind the failures, in the order it reported them. */
35
+ readonly diagnostics: readonly LocatedDiagnostic[];
36
+
37
+ constructor(
38
+ failures: readonly [Failure, ...Failure[]],
39
+ diagnostics: readonly LocatedDiagnostic[] = [],
40
+ ) {
41
+ super(failureLines(failures).join("\n"));
42
+ this.name = "FailureExit";
43
+ this.failures = failures;
44
+ this.diagnostics = diagnostics;
45
+ }
46
+ }
47
+
48
+ /** A failure exit of one failure. */
49
+ export function failureExit(
50
+ failureClass: FailureClass,
51
+ detail: string,
52
+ diagnostics: readonly LocatedDiagnostic[] = [],
53
+ ): FailureExit {
54
+ return new FailureExit([{ failureClass, detail }], diagnostics);
55
+ }
56
+
57
+ /** A fault of this analyzer itself, `fault` saying what it cannot do. */
58
+ export function productFault(fault: string): FailureExit {
59
+ return failureExit(
60
+ "product-fault",
61
+ `${PRODUCT} ${fault}: reinstall ${PRODUCT}, and report the fault to its maintainers if a reinstalled ${PRODUCT} fails the same way`,
62
+ );
63
+ }
package/src/file-facts.ts CHANGED
@@ -5,12 +5,13 @@
5
5
  */
6
6
 
7
7
  import type { SourceFile } from "@typescript/native/ast";
8
- import { IGNORED_DIRECTORIES } from "./discover.ts";
9
8
  import type { Host } from "./host.ts";
10
9
  import { packageScope } from "./inventory.ts";
11
10
  import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
12
11
  import { renderRef } from "./ref.ts";
13
12
  import type { ProjectView } from "./session.ts";
13
+ import type { UnseenLoad } from "./unseen-loads.ts";
14
+ import { filesBelow } from "./fs/tree.ts";
14
15
 
15
16
  /** A file name the compiler reads as a source file. */
16
17
  const SOURCE_FILE = /\.(?:ts|tsx|mts|cts)$/u;
@@ -51,6 +52,8 @@ export interface FileFacts {
51
52
  readonly generated?: ReadonlySet<string>;
52
53
  /** The property names an `in` expression of a production file tests by a literal. */
53
54
  readonly inOperands?: ReadonlySet<string>;
55
+ /** The loads of production files whose target the analysis cannot resolve. */
56
+ readonly unseenLoads?: readonly UnseenLoad[];
54
57
  }
55
58
 
56
59
  /** The line of the last character of one text, counted from one; an empty text is one line. */
@@ -71,29 +74,11 @@ function lastLine(text: string): number {
71
74
  * that cannot be read is skipped, as discovery skips it.
72
75
  */
73
76
  export function sourcePaths(host: Host, targetRoot: string): readonly string[] {
74
- const found: string[] = [];
75
- const pending = [""];
76
- for (let dir = pending.pop(); dir !== undefined; dir = pending.pop()) {
77
- let entries: readonly { readonly name: string; readonly directory: boolean }[];
78
- try {
79
- entries = host.readDirectory(joinPath(targetRoot, dir));
80
- } catch {
81
- continue;
82
- }
83
- for (const entry of entries) {
84
- const path = dir === "" ? entry.name : `${dir}/${entry.name}`;
85
- if (entry.directory) {
86
- if (!IGNORED_DIRECTORIES.has(entry.name)) {
87
- pending.push(path);
88
- }
89
- continue;
90
- }
91
- if (SOURCE_FILE.test(entry.name) && !DECLARATION_FILE.test(entry.name)) {
92
- found.push(path);
93
- }
94
- }
95
- }
96
- return found.sort((a, b) => (a < b ? -1 : 1));
77
+ return filesBelow(
78
+ host,
79
+ targetRoot,
80
+ (name) => SOURCE_FILE.test(name) && !DECLARATION_FILE.test(name),
81
+ );
97
82
  }
98
83
 
99
84
  /**
package/src/finding.ts CHANGED
@@ -1,8 +1,8 @@
1
- import type { Confidence, Severity } from "./config.ts";
2
- import type { Fixability } from "./kinds.ts";
1
+ import type { Fixability, Confidence, Severity } from "./contract/contract.generated.ts";
3
2
  import type { DependencySection } from "./ref.ts";
4
3
  import type { Mechanism } from "./suppress.ts";
5
4
  import type { Relation } from "./sweep.ts";
5
+ import type { FindingSymbol } from "./contract/report.generated.ts";
6
6
 
7
7
  /**
8
8
  * The finding: what an emitter decides about one subject, and every member the
@@ -29,7 +29,7 @@ interface FindingSubject {
29
29
  /** The stable symbol reference, or for a configured root the root as written. */
30
30
  readonly ref: string;
31
31
  /** The subject kind, from the closed vocabulary of the finding schema. */
32
- readonly kind: string;
32
+ readonly kind: FindingSymbol["kind"];
33
33
  /** The display name a text line renders. */
34
34
  readonly name: string;
35
35
  /** The number of source lines the subject spans. */
@@ -55,19 +55,19 @@ export interface PositionedSymbol {
55
55
  */
56
56
  export interface FindingDetails {
57
57
  /**
58
- * On `DS1101` and `DS1104`: the widest scope that holds every reference to the
58
+ * On `DS1501` and `DS1503`: the widest scope that holds every reference to the
59
59
  * subject, which is the visibility its references support.
60
60
  */
61
61
  readonly narrowerVisibility?: "file" | "package";
62
- /** On `DS1201` and `DS1203`: the classes implementing the interface, in site order. */
62
+ /** On `DS1201` and `DS1202`: the classes implementing the interface, in site order. */
63
63
  readonly implementations?: readonly PositionedSymbol[];
64
- /** On `DS1301` and `DS1807`: every position the subject is written at, in site order. */
64
+ /** On `DS1301` and `DS1404`: every position the subject is written at, in site order. */
65
65
  readonly writePositions?: readonly FindingPosition[];
66
- /** On `DS1601`: the manifest section that declares the dependency. */
66
+ /** On `DS1701`: the manifest section that declares the dependency. */
67
67
  readonly dependencyClass?: DependencySection;
68
- /** On `DS1701` and `DS1702`: the document that holds the refused suppression. */
68
+ /** On `DS9001` and `DS9002`: the document that holds the refused suppression. */
69
69
  readonly mechanism?: Mechanism;
70
- /** On `DS1701` and `DS1702`: the refused suppression as written, a member absent where it lacks one. */
70
+ /** On `DS9001` and `DS9002`: the refused suppression as written, a member absent where it lacks one. */
71
71
  readonly entry?: SuppressionEntry;
72
72
  /** On the intra-function kinds: the external rules that report the same kind, as the vocabulary lists them. */
73
73
  readonly overlap?: readonly string[];
@@ -119,6 +119,11 @@ export interface CompletedFinding extends Finding {
119
119
  readonly reachabilityClass: Confidence;
120
120
  /** The reachability class capped by the code's ceiling. */
121
121
  readonly confidence: Confidence;
122
+ /**
123
+ * Where test-used code alone lowers the confidence, the one the finding would carry
124
+ * otherwise, which its component's cap and the dials read.
125
+ */
126
+ readonly componentConfidence?: Confidence;
122
127
  /**
123
128
  * The relation that decided the subject: present on a declaration the sweep judged
124
129
  * dead, absent on a live one and on a subject that is no declaration.
@@ -25,4 +25,6 @@ export interface Boundary {
25
25
  readonly encapsulated: boolean;
26
26
  /** Every side of a declared cross-language edge that names a symbol of this language. */
27
27
  readonly edges: readonly EdgeSide[];
28
+ /** The members a function outside the analysis fills, which it may read unseen. */
29
+ readonly filledOutside?: ReadonlySet<string>;
28
30
  }