@cplieger/deadset-ts 8.0.0-dev.5 → 8.0.0-dev.7

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 (201) hide show
  1. package/dist/bin/mapper-connection.js +2 -1
  2. package/dist/bin/node-host.js +3 -2
  3. package/dist/src/analysis.js +28 -46
  4. package/dist/src/automatic-types.js +10 -29
  5. package/dist/src/build-scripts.js +17 -29
  6. package/dist/src/component-layer.js +4 -2
  7. package/dist/src/components.js +2 -3
  8. package/dist/src/config.js +3 -2
  9. package/dist/src/configuration-files.js +27 -168
  10. package/dist/src/conformance-record.js +1 -1
  11. package/dist/src/contract/contract.generated.js +1084 -0
  12. package/dist/src/contract/report.generated.js +796 -0
  13. package/dist/src/conventions.js +20 -60
  14. package/dist/src/dependencies.js +25 -43
  15. package/dist/src/discover.js +13 -39
  16. package/dist/src/edges.js +7 -15
  17. package/dist/src/element-access.js +4 -6
  18. package/dist/src/entry-point-gaps.js +2 -8
  19. package/dist/src/entry-points.js +3 -10
  20. package/dist/src/enum-group.js +3 -10
  21. package/dist/src/erased-stores.js +4 -6
  22. package/dist/src/exempt-classes.js +3 -63
  23. package/dist/src/exempt.js +13 -40
  24. package/dist/src/file-facts.js +2 -25
  25. package/dist/src/findings/completion.js +11 -20
  26. package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
  27. package/dist/src/findings/emitters.js +4 -4
  28. package/dist/src/findings/interfaces.js +4 -5
  29. package/dist/src/findings/intra-function.js +8 -7
  30. package/dist/src/findings/non-code-artifacts.js +5 -6
  31. package/dist/src/findings/reads-and-writes.js +11 -9
  32. package/dist/src/findings/self-check.js +10 -12
  33. package/dist/src/findings/unused-declarations.js +28 -27
  34. package/dist/src/findings/visibility-narrowing.js +9 -11
  35. package/dist/src/findings-pass.js +7 -14
  36. package/dist/src/fs/manifest.js +43 -0
  37. package/dist/src/fs/tree.js +88 -0
  38. package/dist/src/git-ignore.js +34 -25
  39. package/dist/src/html-entries.js +6 -33
  40. package/dist/src/in-operands.js +2 -1
  41. package/dist/src/index.js +1 -1
  42. package/dist/src/intra-function-parts.js +18 -26
  43. package/dist/src/inventory.js +2 -21
  44. package/dist/src/json-document.js +4 -0
  45. package/dist/src/keyof-comparisons.js +4 -6
  46. package/dist/src/kinds.js +4 -360
  47. package/dist/src/manifest.js +5 -43
  48. package/dist/src/mdx-entries.js +3 -26
  49. package/dist/src/partial.js +2 -1
  50. package/dist/src/paths.js +26 -15
  51. package/dist/src/position.js +2 -1
  52. package/dist/src/print.js +3 -2
  53. package/dist/src/product.js +13 -0
  54. package/dist/src/references.js +14 -27
  55. package/dist/src/report-schema.js +272 -0
  56. package/dist/src/report.js +39 -42
  57. package/dist/src/reporters/sarif.js +4 -2
  58. package/dist/src/reporters/template.js +4 -19
  59. package/dist/src/resolve.js +12 -26
  60. package/dist/src/roots.js +7 -17
  61. package/dist/src/run.js +10 -8
  62. package/dist/src/schema.js +4 -3
  63. package/dist/src/scope.js +4 -5
  64. package/dist/src/serialization-contract.js +4 -8
  65. package/dist/src/session.js +2 -1
  66. package/dist/src/setup-failure.js +2 -6
  67. package/dist/src/suppress-file.js +12 -13
  68. package/dist/src/suppress-inline.js +3 -2
  69. package/dist/src/suppress.js +29 -48
  70. package/dist/src/symbol-keys.js +3 -5
  71. package/dist/src/template-field.js +5 -15
  72. package/dist/src/test-globals.js +6 -28
  73. package/dist/src/test-joins.js +6 -6
  74. package/dist/src/text/compare.js +26 -0
  75. package/dist/src/text/glob.js +194 -0
  76. package/dist/src/text/shell.js +159 -0
  77. package/dist/src/{yaml-document.js → text/yaml.js} +38 -0
  78. package/dist/src/type-errors.js +14 -38
  79. package/dist/src/unseen-loads.js +2 -9
  80. package/dist/src/unwrap.js +23 -0
  81. package/dist/src/verbs/analyze.js +22 -15
  82. package/dist/src/verbs/describe.js +7 -7
  83. package/dist/src/verbs/explain.js +7 -4
  84. package/dist/src/verbs/print-config.js +2 -1
  85. package/dist/src/verbs/print-projects.js +4 -2
  86. package/dist/src/verbs/print-retained.js +4 -2
  87. package/dist/src/verbs/print-roots.js +4 -2
  88. package/dist/src/verbs/verb.js +1 -5
  89. package/dist/src/verbs/version.js +4 -3
  90. package/dist/src/workflow-steps.js +9 -46
  91. package/dist/src/workspace-manifests.js +2 -2
  92. package/dist/src/workspace-programs.js +5 -3
  93. package/dist/src/workspace-resolution.js +13 -17
  94. package/dist/src/workspace.js +28 -116
  95. package/jsr.json +1 -1
  96. package/package.json +2 -1
  97. package/src/analysis.ts +29 -54
  98. package/src/automatic-types.ts +11 -29
  99. package/src/build-scripts.ts +17 -38
  100. package/src/component-layer.ts +4 -2
  101. package/src/components.ts +2 -4
  102. package/src/config.ts +11 -15
  103. package/src/configuration-files.ts +27 -170
  104. package/src/conformance-record.ts +1 -1
  105. package/src/contract/contract.generated.ts +1148 -0
  106. package/src/contract/report.generated.ts +1157 -0
  107. package/src/conventions.ts +26 -74
  108. package/src/dependencies.ts +32 -50
  109. package/src/discover.ts +14 -39
  110. package/src/edges.ts +7 -21
  111. package/src/element-access.ts +4 -7
  112. package/src/entry-point-gaps.ts +3 -9
  113. package/src/entry-points.ts +2 -13
  114. package/src/enum-group.ts +2 -11
  115. package/src/erased-stores.ts +3 -7
  116. package/src/exempt-classes.ts +5 -74
  117. package/src/exempt.ts +17 -47
  118. package/src/file-facts.ts +6 -24
  119. package/src/finding.ts +9 -9
  120. package/src/findings/completion.ts +22 -23
  121. package/src/findings/dependencies-and-module-machinery.ts +4 -7
  122. package/src/findings/emitter.ts +6 -7
  123. package/src/findings/emitters.ts +6 -6
  124. package/src/findings/interfaces.ts +4 -6
  125. package/src/findings/intra-function.ts +8 -8
  126. package/src/findings/non-code-artifacts.ts +5 -7
  127. package/src/findings/reads-and-writes.ts +15 -12
  128. package/src/findings/self-check.ts +11 -17
  129. package/src/findings/unused-declarations.ts +28 -27
  130. package/src/findings/visibility-narrowing.ts +9 -12
  131. package/src/findings-pass.ts +7 -16
  132. package/src/fs/manifest.ts +52 -0
  133. package/src/fs/tree.ts +110 -0
  134. package/src/git-ignore.ts +38 -25
  135. package/src/html-entries.ts +6 -33
  136. package/src/in-operands.ts +2 -1
  137. package/src/index.ts +1 -1
  138. package/src/intra-function-parts.ts +18 -28
  139. package/src/inventory.ts +2 -20
  140. package/src/json-document.ts +5 -0
  141. package/src/keyof-comparisons.ts +3 -7
  142. package/src/kinds.ts +6 -406
  143. package/src/manifest.ts +5 -46
  144. package/src/mdx-entries.ts +3 -26
  145. package/src/partial.ts +2 -1
  146. package/src/paths.ts +27 -15
  147. package/src/position.ts +2 -1
  148. package/src/print.ts +3 -2
  149. package/src/product.ts +18 -0
  150. package/src/references.ts +14 -29
  151. package/src/report-schema.ts +364 -0
  152. package/src/report.ts +66 -229
  153. package/src/reporters/annotations.ts +2 -2
  154. package/src/reporters/json.ts +1 -1
  155. package/src/reporters/reporter.ts +2 -2
  156. package/src/reporters/reporters.ts +1 -1
  157. package/src/reporters/sarif.ts +17 -12
  158. package/src/reporters/template.ts +4 -20
  159. package/src/reporters/text.ts +3 -3
  160. package/src/resolve.ts +31 -38
  161. package/src/roots.ts +7 -20
  162. package/src/run.ts +10 -14
  163. package/src/schema.ts +4 -3
  164. package/src/scope.ts +4 -10
  165. package/src/serialization-contract.ts +4 -9
  166. package/src/session.ts +2 -1
  167. package/src/setup-failure.ts +5 -7
  168. package/src/suppress-file.ts +11 -15
  169. package/src/suppress-inline.ts +2 -2
  170. package/src/suppress.ts +39 -55
  171. package/src/symbol-keys.ts +2 -5
  172. package/src/template-field.ts +5 -18
  173. package/src/test-globals.ts +9 -27
  174. package/src/test-joins.ts +6 -8
  175. package/src/text/compare.ts +27 -0
  176. package/src/text/glob.ts +210 -0
  177. package/src/text/shell.ts +161 -0
  178. package/src/{yaml-document.ts → text/yaml.ts} +46 -0
  179. package/src/type-errors.ts +25 -36
  180. package/src/unseen-loads.ts +1 -12
  181. package/src/unwrap.ts +34 -0
  182. package/src/verbs/analyze.ts +38 -25
  183. package/src/verbs/describe.ts +13 -7
  184. package/src/verbs/explain.ts +7 -4
  185. package/src/verbs/print-config.ts +2 -1
  186. package/src/verbs/print-projects.ts +4 -2
  187. package/src/verbs/print-retained.ts +4 -2
  188. package/src/verbs/print-roots.ts +4 -2
  189. package/src/verbs/verb.ts +0 -6
  190. package/src/verbs/version.ts +4 -3
  191. package/src/workflow-steps.ts +9 -44
  192. package/src/workspace-manifests.ts +2 -2
  193. package/src/workspace-programs.ts +5 -3
  194. package/src/workspace-resolution.ts +21 -17
  195. package/src/workspace.ts +35 -121
  196. package/dist/src/glob.js +0 -65
  197. package/dist/src/pnpm-workspace.js +0 -120
  198. package/dist/src/version.js +0 -7
  199. package/src/glob.ts +0 -67
  200. package/src/pnpm-workspace.ts +0 -135
  201. package/src/version.ts +0 -7
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. */
@@ -117,7 +48,7 @@ export function typescriptVisibilityOf(exemptionClass: TSExemptionClass): TypeSc
117
48
  return VISIBILITY.get(exemptionClass) ?? { private: false, privateName: false };
118
49
  }
119
50
 
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
- }
51
+ /** Every class of the vocabulary, whichever language it runs on, in the vocabulary's order. */
52
+ export const EXEMPTION_CLASS_NAMES: readonly ExemptionClass[] = EXEMPTION_CLASSES.map(
53
+ (row) => row.class,
54
+ );
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 { EXEMPTION_CLASSES } from "./contract/contract.generated.ts";
10
11
  import type { Config, TSSection } from "./config.ts";
11
- import { ConfigError } 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 {
@@ -73,24 +72,11 @@ interface Holding {
73
72
  }
74
73
 
75
74
  /**
76
- * The classes `exemptions.disabled` switches off. A name is refused unless the
77
- * vocabulary holds it; a class that runs on another language only is admitted and
78
- * 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.
79
77
  */
80
78
  export function disabledClasses(config: Config): ReadonlySet<ExemptionClass> {
81
- const disabled = new Set<ExemptionClass>();
82
- for (const name of config.exemptionsDisabled) {
83
- if (!isExemptionClass(name)) {
84
- const known = EXEMPTION_CLASSES.map((row) => row.class).join(", ");
85
- throw new ConfigError(
86
- "malformed",
87
- "exemptions.disabled",
88
- `exemptions.disabled: ${JSON.stringify(name)} is not an exemption class: the classes are ${known}`,
89
- );
90
- }
91
- disabled.add(name);
92
- }
93
- return disabled;
79
+ return new Set(config.exemptionsDisabled);
94
80
  }
95
81
 
96
82
  /**
@@ -107,20 +93,12 @@ function retainable(allowed: TypeScriptVisibility, visibility: Visibility | unde
107
93
  return true;
108
94
  }
109
95
 
110
- /**
111
- * The kinds of declaration each class can hold back, which a detector that stops holds
112
- * back whole. A class with no row here holds back every declaration but a file.
113
- */
114
- const RETAINABLE: ReadonlyMap<TSExemptionClass, ReadonlySet<SymbolKind>> = new Map([
115
- ["interface-satisfaction", new Set<SymbolKind>(["method", "class-member"])],
116
- ["enum-group", new Set<SymbolKind>(["enum-member"])],
117
- ["injection-container", new Set<SymbolKind>(["method", "class-member"])],
118
- ["framework-lifecycle", new Set<SymbolKind>(["method", "class-member"])],
119
- [
120
- "serialization-contract",
121
- new Set<SymbolKind>(["method", "class-member", "interface-method", "type-member"]),
122
- ],
123
- ]);
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
+ );
124
102
 
125
103
  /** The detail of a record a detector that could not finish holds. */
126
104
  const UNANSWERED_DETAIL = "kept live by a question the checker did not answer";
@@ -149,7 +127,7 @@ function detected<Brand>(
149
127
  }
150
128
  const kinds = RETAINABLE.get(exemptionClass);
151
129
  return input.held.symbols
152
- .filter((symbol) => (kinds === undefined ? symbol.kind !== "file" : kinds.has(symbol.kind)))
130
+ .filter((symbol) => kinds?.has(symbol.kind) === true)
153
131
  .map((symbol) => ({ id: symbol.id, detail: UNANSWERED_DETAIL, site: symbol.position }));
154
132
  }
155
133
  }
@@ -229,14 +207,6 @@ export function heldRecords(records: readonly Exemption[], holding: Holding): De
229
207
  return { records: exemptionsOf(found), inTestFiles };
230
208
  }
231
209
 
232
- /** Two strings ordered bytewise. */
233
- function compare(a: string, b: string): number {
234
- if (a === b) {
235
- return 0;
236
- }
237
- return a < b ? -1 : 1;
238
- }
239
-
240
210
  /**
241
211
  * Records as a run holds them: ordered by site, then class, then declaration, and one
242
212
  * per declaration, class, detail and file it holds while live, the one at the first
@@ -244,7 +214,7 @@ function compare(a: string, b: string): number {
244
214
  */
245
215
  export function exemptionsOf(found: readonly Exemption[]): readonly Exemption[] {
246
216
  const ordered = [...found].sort(
247
- (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),
248
218
  );
249
219
  const seen = new Set<string>();
250
220
  return ordered.filter((record) => {
@@ -316,8 +286,8 @@ export function retainedIn(
316
286
  }
317
287
 
318
288
  /**
319
- * One line per held-back declaration, in site order: its reference, then each record's
320
- * 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.
321
291
  */
322
292
  export function retainedLines(
323
293
  symbols: readonly InventorySymbol[],
@@ -334,6 +304,6 @@ export function retainedLines(
334
304
  positionKey(record.site),
335
305
  record.detail,
336
306
  ]);
337
- return [[symbol.ref, ...fields].join("\t")];
307
+ return [[symbol.ref, positionKey(symbol.position), ...fields].join("\t")];
338
308
  });
339
309
  }
package/src/file-facts.ts CHANGED
@@ -5,13 +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";
14
13
  import type { UnseenLoad } from "./unseen-loads.ts";
14
+ import { filesBelow } from "./fs/tree.ts";
15
15
 
16
16
  /** A file name the compiler reads as a source file. */
17
17
  const SOURCE_FILE = /\.(?:ts|tsx|mts|cts)$/u;
@@ -74,29 +74,11 @@ function lastLine(text: string): number {
74
74
  * that cannot be read is skipped, as discovery skips it.
75
75
  */
76
76
  export function sourcePaths(host: Host, targetRoot: string): readonly string[] {
77
- const found: string[] = [];
78
- const pending = [""];
79
- for (let dir = pending.pop(); dir !== undefined; dir = pending.pop()) {
80
- let entries: readonly { readonly name: string; readonly directory: boolean }[];
81
- try {
82
- entries = host.readDirectory(joinPath(targetRoot, dir));
83
- } catch {
84
- continue;
85
- }
86
- for (const entry of entries) {
87
- const path = dir === "" ? entry.name : `${dir}/${entry.name}`;
88
- if (entry.directory) {
89
- if (!IGNORED_DIRECTORIES.has(entry.name)) {
90
- pending.push(path);
91
- }
92
- continue;
93
- }
94
- if (SOURCE_FILE.test(entry.name) && !DECLARATION_FILE.test(entry.name)) {
95
- found.push(path);
96
- }
97
- }
98
- }
99
- 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
+ );
100
82
  }
101
83
 
102
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[];
@@ -4,39 +4,38 @@
4
4
  * together with every finding that falls with a root they withhold.
5
5
  */
6
6
 
7
+ import {
8
+ CODE,
9
+ FAMILY_KEY_LENGTH,
10
+ type Confidence,
11
+ type Severity,
12
+ KIND_ROWS,
13
+ type KindRow,
14
+ } from "../contract/contract.generated.ts";
7
15
  import type { RunSweep } from "../analysis.ts";
8
16
  import { componentId, type Component } from "../components.ts";
9
17
  import { deadComponents } from "../matrix.ts";
10
- import type { Cascade, Confidence, Severity } from "../config.ts";
18
+ import type { Cascade } from "../config.ts";
11
19
  import type { CompletedFinding, Finding, FindingComponent, PositionedSymbol } from "../finding.ts";
12
20
  import { OUTSIDE, type Graph } from "../graph.ts";
13
21
  import type { SymbolKind } from "../inventory.ts";
14
- import { KINDS, type KindRow } from "../kinds.ts";
22
+ import { KINDS } from "../kinds.ts";
15
23
  import { byPosition, positionKey } from "../position.ts";
16
24
  import { declarationKey } from "../ref.ts";
17
- import { FAMILY_KEY_LENGTH } from "../resolve.ts";
18
25
  import { isPartSubject, isRowSubject, type Dials } from "../suppress.ts";
19
26
  import type { Candidate } from "../sweep.ts";
20
27
  import type { Boundary } from "./boundary.ts";
21
28
  import type { EmitterInput } from "./emitter.ts";
22
29
 
23
- /** The unused-file code, whose deletion removes every use its file writes. */
24
- const FILE_NEVER_IMPORTED = "DS1502";
25
-
26
30
  /**
27
- * The codes whose subject the run that counts test references holds live, as
28
- * finding.schema.json lists them, so a test file's candidate reported under one carries
29
- * none of a dead declaration's sweep facts.
31
+ * The codes whose subject the run that counts test references holds live, so a test
32
+ * file's candidate reported under one carries none of a dead declaration's sweep facts.
30
33
  */
31
- const LIVE_SUBJECT: ReadonlySet<string> = new Set([
32
- "DS1101",
33
- "DS1102",
34
- "DS1104",
35
- "DS1204",
36
- "DS1301",
37
- ]);
34
+ const LIVE_SUBJECT: ReadonlySet<string> = new Set(
35
+ KIND_ROWS.filter((row) => row.holdsLive).map((row) => row.code),
36
+ );
38
37
 
39
- /** The fixability grammar/analysis.md "Fixability" gives a finding about a held declaration. */
38
+ /** The fixability grammar/analysis/fixability.md gives a finding about a held declaration. */
40
39
  const MANUAL = "manual";
41
40
 
42
41
  /** The member kinds an object property can declare, which an `in` expression tests by name. */
@@ -186,7 +185,7 @@ function testCeilings(
186
185
 
187
186
  /**
188
187
  * The subjects dead only because production code only tests use is: each declaration the
189
- * sweep marks test-used, each `DS1502` file of such code, and each test of dead code every
188
+ * sweep marks test-used, each `DS1602` file of such code, and each test of dead code every
190
189
  * target of which is one. Nothing they hold references a dead declaration outside them, so
191
190
  * the rest of a component they share is reported without them.
192
191
  */
@@ -286,7 +285,7 @@ function membersListed(
286
285
  }
287
286
 
288
287
  /**
289
- * The run's dead components with each file a `DS1502` finding reports dead among them, so
288
+ * The run's dead components with each file a `DS1602` finding reports dead among them, so
290
289
  * the file's declarations fall with it, and with the components each subject that is no
291
290
  * declaration falls with joined into one.
292
291
  */
@@ -300,7 +299,7 @@ function runComponents(
300
299
  } {
301
300
  const union = swept.matrix.union;
302
301
  const reported = new Set(
303
- findings.filter((one) => one.code === FILE_NEVER_IMPORTED).map((one) => one.position.path),
302
+ findings.filter((one) => one.code === CODE.fileNeverImported).map((one) => one.position.path),
304
303
  );
305
304
  const files = new Map(
306
305
  union.symbols.flatMap((symbol, at) =>
@@ -419,7 +418,7 @@ export function completed(
419
418
  const fallsWith = finding.fallsWith?.[0];
420
419
  const joined = fallsWith === undefined ? undefined : componentOf(fallsWith);
421
420
  const file =
422
- finding.code === FILE_NEVER_IMPORTED ? files.get(finding.position.path) : undefined;
421
+ finding.code === CODE.fileNeverImported ? files.get(finding.position.path) : undefined;
423
422
  const found =
424
423
  joined !== undefined
425
424
  ? { ...joined, root: false }
@@ -471,7 +470,7 @@ export function completed(
471
470
  const used =
472
471
  judged !== undefined
473
472
  ? candidate !== undefined && testUsed.declarations.has(judged.id)
474
- : finding.code === FILE_NEVER_IMPORTED && testUsed.files.has(finding.position.path);
473
+ : finding.code === CODE.fileNeverImported && testUsed.files.has(finding.position.path);
475
474
  const reachabilityClass = used && candidate?.testOfDeadCode !== true ? "possible" : ownClass;
476
475
  const configurations =
477
476
  candidate?.configurations ??
@@ -499,7 +498,7 @@ export function completed(
499
498
  ? undefined
500
499
  : candidate !== undefined
501
500
  ? input.dependencies.lastUses.get(candidate.id)
502
- : finding.code === FILE_NEVER_IMPORTED
501
+ : finding.code === CODE.fileNeverImported
503
502
  ? input.dependencies.fileLastUses.get(finding.position.path)
504
503
  : undefined;
505
504
  return {
@@ -1,13 +1,10 @@
1
+ import { CODE } from "../contract/contract.generated.ts";
1
2
  import { declarationKey, type Dependencies } from "../dependencies.ts";
2
3
  import type { Finding } from "../finding.ts";
3
4
  import { renderRef, type DependencySection } from "../ref.ts";
4
5
  import type { Emitter, EmitterInput } from "./emitter.ts";
5
6
  import { nonCodeArtifacts } from "./non-code-artifacts.ts";
6
7
 
7
- const UNUSED_DEPENDENCY = "DS1601";
8
-
9
- const FILE_NEVER_IMPORTED = "DS1502";
10
-
11
8
  const MESSAGES: Readonly<Record<DependencySection, string>> = {
12
9
  dependency: "no import in the target needs the declared dependency",
13
10
  "dev-dependency": "no import in the target needs the declared development dependency",
@@ -46,7 +43,7 @@ function deadCode(input: EmitterInput): DeadCode {
46
43
  );
47
44
  for (const finding of nonCodeArtifacts(input)) {
48
45
  const id = files.get(finding.position.path);
49
- if (finding.code === FILE_NEVER_IMPORTED && id !== undefined) {
46
+ if (finding.code === CODE.fileNeverImported && id !== undefined) {
50
47
  dead.add(id);
51
48
  if (finding.unreachableOnly === true) {
52
49
  reachedOnly.add(id);
@@ -81,7 +78,7 @@ function unusedDependencies(input: EmitterInput, dependencies: Dependencies): Fi
81
78
  const atPossible = unbuilt || users?.some((id) => published.has(id)) === true;
82
79
  return [
83
80
  {
84
- code: UNUSED_DEPENDENCY,
81
+ code: CODE.unusedDependency,
85
82
  position: dependency.position,
86
83
  symbol: {
87
84
  ref: renderRef(dependency.manifest, {
@@ -105,6 +102,6 @@ function unusedDependencies(input: EmitterInput, dependencies: Dependencies): Fi
105
102
  });
106
103
  }
107
104
 
108
- /** The findings of the dependencies-and-module-machinery family, `DS1600` to `DS1699`. */
105
+ /** The findings of the dependencies family, `DS1700` to `DS1799`. */
109
106
  export const dependenciesAndModuleMachinery: Emitter = (input) =>
110
107
  unusedDependencies(input, input.dependencies);
@@ -11,7 +11,7 @@ import type { SelfCheckFacts } from "./self-check.ts";
11
11
 
12
12
  /**
13
13
  * What every emitter reads: the configuration the run resolved, the run's sweep, and
14
- * the facts about the run each kind family reads beside the sweep.
14
+ * the facts about the run each emitter reads beside the sweep.
15
15
  */
16
16
  export interface EmitterInput {
17
17
  readonly config: Config;
@@ -29,18 +29,17 @@ export interface EmitterInput {
29
29
  readonly files: FileFacts;
30
30
  /** What can reach the target from outside it: consumers, declared edges, and `exports`. */
31
31
  readonly boundary: Boundary;
32
- /** The run's suppression outcomes and configured roots, which the self-check family reports. */
32
+ /** The run's suppression outcomes and configured roots, which the configuration family reports. */
33
33
  readonly selfCheck: SelfCheckFacts;
34
- /** The parts of the run's declarations, which the intra-function family reports. */
34
+ /** The parts of the run's declarations, which the inside-functions family reports. */
35
35
  readonly intraFunction: IntraFunctionFacts;
36
36
  }
37
37
 
38
38
  /**
39
- * One kind family's rule over one run: every finding of the codes in its family's range
40
- * of the Contract's code space, whatever severity and minimum confidence the
41
- * configuration sets, in no particular order.
39
+ * One emitter's rule over one run: every finding of the codes it reports, whatever
40
+ * severity and minimum confidence the configuration sets, in no particular order.
42
41
  */
43
42
  export type Emitter = (input: EmitterInput) => readonly Finding[];
44
43
 
45
- /** The emitter of each kind family, by the family's name in the Contract, in code order. */
44
+ /** Every emitter this analyzer runs, by the name its tests select it by. */
46
45
  export type Emitters = ReadonlyMap<string, Emitter>;
@@ -1,4 +1,4 @@
1
- import type { Confidence } from "../config.ts";
1
+ import type { Confidence } from "../contract/contract.generated.ts";
2
2
  import { evaluateEdges, type EdgeEvaluation } from "../edges.ts";
3
3
  import type { CompletedFinding } from "../finding.ts";
4
4
  import { liveFilesAt } from "../matrix.ts";
@@ -16,7 +16,7 @@ import { selfCheck } from "./self-check.ts";
16
16
  import { fallenMembers, unusedDeclarations } from "./unused-declarations.ts";
17
17
  import { visibilityNarrowing } from "./visibility-narrowing.ts";
18
18
 
19
- /** Every kind family this analyzer reports, each by its emitter, in code order. */
19
+ /** Every emitter this analyzer runs, by the name its tests select it by. */
20
20
  export const EMITTERS: Emitters = new Map<string, Emitter>([
21
21
  ["unused-declarations", unusedDeclarations],
22
22
  ["visibility-narrowing", visibilityNarrowing],
@@ -76,7 +76,7 @@ interface Suppressed {
76
76
 
77
77
  /** What the emitter table answers about one run. */
78
78
  interface DecidedFindings {
79
- /** Every finding the run reports, each family's in table order. */
79
+ /** Every finding the run reports, each emitter's in table order. */
80
80
  readonly findings: readonly CompletedFinding[];
81
81
  /** One evaluation per declared edge side of this language, ordered by edge, then by side. */
82
82
  readonly evaluations: readonly EdgeEvaluation<CompletedFinding>[];
@@ -91,7 +91,7 @@ interface DecidedFindings {
91
91
  /** How many findings the minimum confidence withheld at each confidence. */
92
92
  export type Withheld = Readonly<Record<Confidence, number>>;
93
93
 
94
- /** Every family's findings over one sweep, and those of any further emitter, completed by one step. */
94
+ /** Every emitter's findings over one sweep, and those of any further emitter, completed by one step. */
95
95
  function completedOver(
96
96
  input: EmitterInput,
97
97
  ...more: readonly Emitter[]
@@ -124,8 +124,8 @@ function dialsOver(
124
124
 
125
125
  /**
126
126
  * Every finding of one run swept under its suppressions' marks, decided in one pass: each
127
- * family's findings completed by one step, so every component number is minted once, the
128
- * finding each record claims withheld, the rest withheld by the dials, so a root one family
127
+ * emitter's findings completed by one step, so every component number is minted once, the
128
+ * finding each record claims withheld, the rest withheld by the dials, so a root one emitter
129
129
  * reports withholds what another reports in its component, and every declared edge
130
130
  * evaluated over what remains, so a pending finding is in its evaluation alone.
131
131
  */