@cplieger/deadset-ts 8.0.0-dev.15 → 8.0.0-dev.17

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 (207) hide show
  1. package/dist/src/alias-chain.js +0 -5
  2. package/dist/src/amd.js +81 -0
  3. package/dist/src/analysis.js +94 -54
  4. package/dist/src/augmentations.js +23 -24
  5. package/dist/src/bindings.js +382 -0
  6. package/dist/src/calls.js +25 -8
  7. package/dist/src/commands/read.js +6 -7
  8. package/dist/src/commands/tools.js +5 -9
  9. package/dist/src/component-files.js +3 -2
  10. package/dist/src/conformance-record.js +1 -1
  11. package/dist/src/consumers.js +9 -10
  12. package/dist/src/contextual-uses.js +1 -17
  13. package/dist/src/contract/config.generated.js +5 -2
  14. package/dist/src/contract/contract.generated.js +119 -2
  15. package/dist/src/contract/report.generated.js +6 -2
  16. package/dist/src/dependencies.js +98 -71
  17. package/dist/src/destructuring.js +1 -19
  18. package/dist/src/element-access.js +179 -164
  19. package/dist/src/entries/build-scripts.js +222 -0
  20. package/dist/src/{configuration-files.js → entries/configuration-files.js} +22 -64
  21. package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
  22. package/dist/src/entries/conventions.js +739 -0
  23. package/dist/src/{html-entries.js → entries/html-entries.js} +9 -12
  24. package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +3 -3
  25. package/dist/src/entries/run-directories.js +77 -0
  26. package/dist/src/entries/sources.js +144 -0
  27. package/dist/src/entries/static-module.js +670 -0
  28. package/dist/src/entries/test-runners.js +684 -0
  29. package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
  30. package/dist/src/extensions.js +37 -0
  31. package/dist/src/facts.js +38 -0
  32. package/dist/src/findings/completion.js +40 -4
  33. package/dist/src/findings/emitters.js +5 -8
  34. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  35. package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
  36. package/dist/src/findings/self-check.js +36 -20
  37. package/dist/src/findings-pass.js +5 -6
  38. package/dist/src/framework-lifecycle.js +42 -15
  39. package/dist/src/fs/manifest.js +15 -20
  40. package/dist/src/generated-file.js +172 -29
  41. package/dist/src/graph.js +14 -8
  42. package/dist/src/import-bindings.js +73 -28
  43. package/dist/src/index/sites.js +557 -0
  44. package/dist/src/index.js +1 -1
  45. package/dist/src/indexed-access.js +523 -478
  46. package/dist/src/injection-container.js +1 -1
  47. package/dist/src/interface-satisfaction.js +1 -1
  48. package/dist/src/inventory/commonjs.js +62 -17
  49. package/dist/src/inventory/enumerate.js +2 -1
  50. package/dist/src/javascript-file.js +26 -11
  51. package/dist/src/keyof-comparisons.js +124 -103
  52. package/dist/src/load/classify.js +28 -9
  53. package/dist/src/load/diagnostics.js +8 -1
  54. package/dist/src/load/discover.js +15 -7
  55. package/dist/src/load/expected-module.js +2 -2
  56. package/dist/src/load/plan.js +2 -2
  57. package/dist/src/load/specifiers.js +2 -4
  58. package/dist/src/load/synthesize.js +10 -5
  59. package/dist/src/load/workspace-manifests.js +1 -1
  60. package/dist/src/load/workspace-resolution.js +2 -2
  61. package/dist/src/matrix.js +12 -17
  62. package/dist/src/module-declarations.js +70 -85
  63. package/dist/src/overrides.js +134 -112
  64. package/dist/src/paths.js +56 -1
  65. package/dist/src/platform-calls.js +78 -0
  66. package/dist/src/platform-symbols.js +134 -0
  67. package/dist/src/references/read-rule.js +1 -0
  68. package/dist/src/references/read-rules.js +48 -0
  69. package/dist/src/references/sink.js +151 -0
  70. package/dist/src/references/sources.js +450 -0
  71. package/dist/src/references.js +111 -1116
  72. package/dist/src/resolve.js +1 -1
  73. package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +7 -4
  74. package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
  75. package/dist/src/{manifest.js → roots/manifest.js} +4 -4
  76. package/dist/src/roots/published-reach.js +412 -0
  77. package/dist/src/roots/root-set.js +489 -0
  78. package/dist/src/roots/unseen-loads.js +236 -0
  79. package/dist/src/serialization-contract.js +9 -4
  80. package/dist/src/source-files.js +5 -9
  81. package/dist/src/stores.js +4 -2
  82. package/dist/src/suppress.js +30 -32
  83. package/dist/src/sweep.js +25 -13
  84. package/dist/src/symbol-keys.js +89 -83
  85. package/dist/src/tests/file-class.js +76 -0
  86. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  87. package/dist/src/{test-support.js → tests/support.js} +5 -12
  88. package/dist/src/type-parameter-reads.js +178 -139
  89. package/dist/src/unimplemented-callees.js +262 -242
  90. package/dist/src/verbs/analyze.js +15 -3
  91. package/dist/src/verbs/describe.js +5 -3
  92. package/dist/src/verbs/explain.js +50 -28
  93. package/dist/src/verbs/print-retained.js +2 -2
  94. package/jsr.json +1 -1
  95. package/package.json +3 -3
  96. package/src/alias-chain.ts +0 -7
  97. package/src/amd.ts +130 -0
  98. package/src/analysis.ts +156 -70
  99. package/src/augmentations.ts +41 -27
  100. package/src/automatic-types.ts +1 -1
  101. package/src/bindings.ts +460 -0
  102. package/src/calls.ts +25 -7
  103. package/src/commands/read.ts +6 -8
  104. package/src/commands/tools.ts +5 -11
  105. package/src/component-files.ts +4 -2
  106. package/src/conformance-record.ts +1 -1
  107. package/src/consumers.ts +12 -11
  108. package/src/contextual-uses.ts +2 -22
  109. package/src/contract/config.generated.ts +5 -2
  110. package/src/contract/contract.generated.ts +138 -2
  111. package/src/contract/report.generated.ts +6 -2
  112. package/src/dependencies.ts +117 -71
  113. package/src/destructuring.ts +1 -31
  114. package/src/element-access.ts +188 -182
  115. package/src/entries/build-scripts.ts +292 -0
  116. package/src/{configuration-files.ts → entries/configuration-files.ts} +33 -82
  117. package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
  118. package/src/{conventions.ts → entries/conventions.ts} +349 -523
  119. package/src/{html-entries.ts → entries/html-entries.ts} +19 -18
  120. package/src/{mdx-entries.ts → entries/mdx-entries.ts} +5 -5
  121. package/src/entries/run-directories.ts +108 -0
  122. package/src/entries/sources.ts +266 -0
  123. package/src/entries/static-module.ts +858 -0
  124. package/src/entries/test-runners.ts +868 -0
  125. package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
  126. package/src/exempt.ts +3 -2
  127. package/src/extensions.ts +45 -0
  128. package/src/facts.ts +46 -0
  129. package/src/file-facts.ts +1 -1
  130. package/src/findings/boundary.ts +2 -0
  131. package/src/findings/completion.ts +43 -4
  132. package/src/findings/emitter.ts +1 -1
  133. package/src/findings/emitters.ts +14 -10
  134. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  135. package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
  136. package/src/findings/self-check.ts +41 -22
  137. package/src/findings/visibility-narrowing.ts +1 -1
  138. package/src/findings-pass.ts +5 -12
  139. package/src/framework-lifecycle.ts +43 -15
  140. package/src/fs/manifest.ts +25 -21
  141. package/src/generated-file.ts +240 -36
  142. package/src/graph.ts +27 -11
  143. package/src/import-bindings.ts +98 -29
  144. package/src/index/sites.ts +781 -0
  145. package/src/index.ts +1 -1
  146. package/src/indexed-access.ts +570 -527
  147. package/src/injection-container.ts +1 -1
  148. package/src/interface-satisfaction.ts +1 -1
  149. package/src/inventory/commonjs.ts +102 -17
  150. package/src/inventory/enumerate.ts +2 -1
  151. package/src/javascript-file.ts +29 -12
  152. package/src/keyof-comparisons.ts +131 -116
  153. package/src/load/classify.ts +49 -13
  154. package/src/load/diagnostics.ts +11 -1
  155. package/src/load/discover.ts +27 -11
  156. package/src/load/expected-module.ts +8 -2
  157. package/src/load/plan.ts +11 -2
  158. package/src/load/specifiers.ts +8 -5
  159. package/src/load/synthesize.ts +12 -6
  160. package/src/load/test-globals.ts +1 -1
  161. package/src/load/workspace-manifests.ts +1 -1
  162. package/src/load/workspace-resolution.ts +9 -2
  163. package/src/load/workspace-run.ts +1 -1
  164. package/src/matrix.ts +13 -17
  165. package/src/module-declarations.ts +74 -98
  166. package/src/overrides.ts +162 -134
  167. package/src/paths.ts +65 -1
  168. package/src/platform-calls.ts +119 -0
  169. package/src/platform-symbols.ts +189 -0
  170. package/src/references/read-rule.ts +60 -0
  171. package/src/references/read-rules.ts +55 -0
  172. package/src/references/sink.ts +213 -0
  173. package/src/references/sources.ts +632 -0
  174. package/src/references.ts +174 -1492
  175. package/src/report.ts +2 -2
  176. package/src/resolve.ts +1 -1
  177. package/src/{configured-declarations.ts → roots/configured-declarations.ts} +10 -7
  178. package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
  179. package/src/{manifest.ts → roots/manifest.ts} +5 -5
  180. package/src/roots/published-reach.ts +481 -0
  181. package/src/{roots.ts → roots/root-set.ts} +203 -630
  182. package/src/roots/unseen-loads.ts +345 -0
  183. package/src/serialization-contract.ts +9 -4
  184. package/src/source-files.ts +5 -10
  185. package/src/stores.ts +13 -2
  186. package/src/suppress.ts +48 -33
  187. package/src/sweep.ts +37 -14
  188. package/src/symbol-keys.ts +102 -102
  189. package/src/tests/file-class.ts +118 -0
  190. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  191. package/src/{test-support.ts → tests/support.ts} +15 -21
  192. package/src/type-parameter-reads.ts +187 -158
  193. package/src/unimplemented-callees.ts +297 -280
  194. package/src/verbs/analyze.ts +19 -5
  195. package/src/verbs/describe.ts +5 -2
  196. package/src/verbs/explain.ts +75 -32
  197. package/src/verbs/print-retained.ts +2 -2
  198. package/dist/src/build-scripts.js +0 -418
  199. package/dist/src/conventions.js +0 -907
  200. package/dist/src/entry-points.js +0 -444
  201. package/dist/src/own-body.js +0 -28
  202. package/dist/src/roots.js +0 -865
  203. package/dist/src/unseen-loads.js +0 -189
  204. package/src/build-scripts.ts +0 -536
  205. package/src/entry-points.ts +0 -584
  206. package/src/own-body.ts +0 -42
  207. package/src/unseen-loads.ts +0 -282
@@ -6,13 +6,13 @@
6
6
  */
7
7
 
8
8
  import type { SourceFile } from "@typescript/native/ast";
9
- import { pathsNamed, readCommands } from "./commands/read.ts";
10
- import type { Host } from "./host.ts";
11
- import { isRecord } from "./json-document.ts";
12
- import { joinPath, resolvePath } from "./paths.ts";
13
- import type { SourceFiles } from "./source-files.ts";
14
- import { filesIn } from "./fs/tree.ts";
15
- import { yamlDocument } from "./text/yaml.ts";
9
+ import { pathsNamed, readCommands, type CommandReading } from "../commands/read.ts";
10
+ import type { Host } from "../host.ts";
11
+ import { isRecord } from "../json-document.ts";
12
+ import { joinPath, resolvePath } from "../paths.ts";
13
+ import type { SourceFiles } from "../source-files.ts";
14
+ import { filesIn } from "../fs/tree.ts";
15
+ import { yamlDocument } from "../text/yaml.ts";
16
16
 
17
17
  /** One file a workflow step, git hook or recipe runs or names. */
18
18
  interface WorkflowEntry {
@@ -204,18 +204,36 @@ export function commandFileTexts(host: Host, targetRoot: string): readonly strin
204
204
  ].flatMap((path) => readText(host, path) ?? []);
205
205
  }
206
206
 
207
+ /** What the target root's workflow steps, git hooks, hook managers and makefile run, read once per run. */
208
+ export interface Workflows {
209
+ /** Each command read, its held text followed by the directory the file writes for it. */
210
+ readonly readings: readonly CommandReading[];
211
+ /** The whole text of each file a command is read from. */
212
+ readonly texts: readonly string[];
213
+ }
214
+
215
+ /** Reads the target root's workflow steps, git hooks, hook managers and makefile. */
216
+ export function readWorkflows(host: Host, targetRoot: string): Workflows {
217
+ return {
218
+ readings: workflowCommands(host, targetRoot).map(({ text, dir, writtenDirectory }) => {
219
+ const reading = readCommands(text, dir);
220
+ return { ...reading, held: [reading.held, writtenDirectory ?? ""].join("\n") };
221
+ }),
222
+ texts: commandFileTexts(host, targetRoot),
223
+ };
224
+ }
225
+
207
226
  /**
208
227
  * The own files the target root's workflow steps, git hooks and recipes run or name, as
209
228
  * `pathsNamed` reads each command.
210
229
  */
211
230
  export function workflowEntries(
212
- host: Host,
213
- targetRoot: string,
231
+ workflows: Workflows,
214
232
  files: SourceFiles,
215
233
  ): readonly WorkflowEntry[] {
216
234
  const found: WorkflowEntry[] = [];
217
- for (const { text, dir } of workflowCommands(host, targetRoot)) {
218
- for (const { path, written } of pathsNamed(readCommands(text, dir))) {
235
+ for (const reading of workflows.readings) {
236
+ for (const { path, written } of pathsNamed(reading)) {
219
237
  for (const file of files.named(path)) {
220
238
  found.push({ file, source: written });
221
239
  }
package/src/exempt.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { EXEMPTION_CLASSES } from "./contract/contract.generated.ts";
11
+ import type { GeneratedFact } from "./generated-file.ts";
11
12
  import type { ResolvedConfig } from "./config.ts";
12
13
  import {
13
14
  TS_EXEMPTION_CLASSES,
@@ -51,8 +52,8 @@ export interface DetectorInput<Brand> {
51
52
  readonly ts: ResolvedConfig["ts"];
52
53
  /** The absolute path of each declared consumer, whose modules are part of the analysis. */
53
54
  readonly consumers: readonly string[];
54
- /** The project's generated files, by path below the target root, each with the detail its retentions name. */
55
- readonly generated?: ReadonlyMap<string, string>;
55
+ /** The project's generated files, by path below the target root, each with what decided it. */
56
+ readonly generated?: ReadonlyMap<string, GeneratedFact>;
56
57
  }
57
58
 
58
59
  /**
@@ -0,0 +1,45 @@
1
+ /** The extension lists the analysis reads files by, each written once. */
2
+
3
+ /**
4
+ * The extensions tried, in order, after a path written without one: a relative specifier
5
+ * of a configuration file (`roots.5`) and a path a command runs or loads (`roots.13`).
6
+ */
7
+ export const APPENDED_EXTENSIONS = [
8
+ ".ts",
9
+ ".tsx",
10
+ ".mts",
11
+ ".cts",
12
+ ".js",
13
+ ".jsx",
14
+ ".mjs",
15
+ ".cjs",
16
+ ] as const;
17
+
18
+ /** The extensions of a module a tool runs or loads as its configuration (`roots.2`). */
19
+ export const MODULE_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"] as const;
20
+
21
+ /** The extensions of a file a build writes from a source file. */
22
+ export const OUTPUT_EXTENSIONS = [
23
+ ".js",
24
+ ".mjs",
25
+ ".cjs",
26
+ ".jsx",
27
+ ".d.ts",
28
+ ".d.mts",
29
+ ".d.cts",
30
+ ] as const;
31
+
32
+ /** One list as a glob's brace list, its dots dropped: `{js,mjs}`. */
33
+ export function braced(extensions: readonly string[]): string {
34
+ return `{${extensions.map((one) => one.slice(1)).join(",")}}`;
35
+ }
36
+
37
+ /** The extension of one list a name ends with, the longest where several do. */
38
+ export function extensionOf(name: string, extensions: readonly string[]): string | undefined {
39
+ return extensions
40
+ .filter((one) => name.endsWith(one))
41
+ .reduce<string | undefined>(
42
+ (longest, one) => (one.length > (longest?.length ?? 0) ? one : longest),
43
+ undefined,
44
+ );
45
+ }
package/src/facts.ts ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The facts document `--facts` writes, `bindings.1` and `bindings.2`: the run's generated
3
+ * files under the one decision the generated-file class reads, and the binding identities
4
+ * they publish. No binding state is computed, so it states `evaluated: false` (`bindings.10`).
5
+ */
6
+
7
+ import { FACTS_VERSION } from "./contract/contract.generated.ts";
8
+ import type { BindingRecord } from "./bindings.ts";
9
+ import type { GeneratedFact } from "./generated-file.ts";
10
+ import { LANGUAGE, PRODUCT } from "./product.ts";
11
+ import { bytewise } from "./text/compare.ts";
12
+
13
+ /** The facts document of one run, as the text written to the path `--facts` names. */
14
+ export function factsDocument(
15
+ version: string,
16
+ generated: ReadonlyMap<string, GeneratedFact>,
17
+ bindings: readonly BindingRecord[],
18
+ ): string {
19
+ const document = {
20
+ facts_version: FACTS_VERSION,
21
+ analyzer: { name: PRODUCT, version },
22
+ language: LANGUAGE,
23
+ evaluated: false,
24
+ generated: [...generated]
25
+ .sort(([a], [b]) => bytewise(a, b))
26
+ .map(([path, fact]) => ({
27
+ path,
28
+ line: fact.line,
29
+ evidence: fact.evidence,
30
+ detail: fact.detail,
31
+ ...(fact.generator === undefined
32
+ ? {}
33
+ : {
34
+ generator: {
35
+ row: fact.generator.row,
36
+ ...(fact.generator.version === undefined
37
+ ? {}
38
+ : { version: fact.generator.version }),
39
+ },
40
+ schema_files: fact.generator.schemaFiles,
41
+ }),
42
+ })),
43
+ bindings,
44
+ };
45
+ return `${JSON.stringify(document, null, 2)}\n`;
46
+ }
package/src/file-facts.ts CHANGED
@@ -10,7 +10,7 @@ import { packageScope } from "./load/package-scope.ts";
10
10
  import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
11
11
  import { renderRef } from "./ref.ts";
12
12
  import type { ProjectView } from "./session.ts";
13
- import type { UnseenLoad } from "./unseen-loads.ts";
13
+ import type { UnseenLoad } from "./roots/unseen-loads.ts";
14
14
  import { filesBelow } from "./fs/tree.ts";
15
15
 
16
16
  /** A file name the compiler reads as a source file. */
@@ -27,4 +27,6 @@ export interface Boundary {
27
27
  readonly edges: readonly EdgeSide[];
28
28
  /** The members a function outside the analysis fills, which it may read unseen. */
29
29
  readonly filledOutside?: ReadonlySet<string>;
30
+ /** The files and declarations only a bundler's retry of an import Node leaves unresolved names. */
31
+ readonly possiblyLoaded?: ReadonlySet<string>;
30
32
  }
@@ -110,12 +110,13 @@ function testedByIn(input: EmitterInput): ReadonlySet<string> {
110
110
  * published API, a public member of a published type included, is `certain` where the
111
111
  * run loaded every consumer the scope declared and declared one, `probable` where one did
112
112
  * not load, and `possible` where it declared none, whatever the configuration says of the
113
- * consumer set. A member an `in` expression tests by name, and one a function outside the
114
- * analysis fills, is `possible`.
113
+ * consumer set. A member an `in` expression tests by name, one a function outside the
114
+ * analysis fills, and one {@link possiblyLoaded} names, is `possible`.
115
115
  */
116
116
  function reachabilityClasses(input: EmitterInput): (id: string) => Confidence {
117
117
  const tested = testedByIn(input);
118
118
  const filled = input.boundary.filledOutside ?? new Set<string>();
119
+ const possible = possiblyLoaded(input);
119
120
  const published = publishedIds(input.swept.matrix.union);
120
121
  const { declared } = input.boundary.consumers;
121
122
  const outside: Confidence =
@@ -125,7 +126,7 @@ function reachabilityClasses(input: EmitterInput): (id: string) => Confidence {
125
126
  ? "certain"
126
127
  : "probable";
127
128
  return (id) => {
128
- if (tested.has(id) || filled.has(id)) {
129
+ if (tested.has(id) || filled.has(id) || possible.has(id)) {
129
130
  return "possible";
130
131
  }
131
132
  return published.has(id) ? outside : "certain";
@@ -182,6 +183,36 @@ function testCeilings(
182
183
  : "certain";
183
184
  }
184
185
 
186
+ /**
187
+ * What only a bundler's retry of an import Node leaves unresolved names (program.38), and each
188
+ * dead declaration that reaches through dead declarations, as confidence.20 reads what only
189
+ * tests reach: each is dead only while no loader the analysis does not see loads the file.
190
+ */
191
+ function possiblyLoaded(input: EmitterInput): ReadonlySet<string> {
192
+ const seeds = input.boundary.possiblyLoaded ?? new Set<string>();
193
+ if (seeds.size === 0) {
194
+ return seeds;
195
+ }
196
+ const union = input.swept.matrix.union;
197
+ const dead = new Set(input.swept.sweep.candidates.map((one) => one.id));
198
+ const reached = new Set<string>();
199
+ const pending = union.symbols.flatMap((symbol, at) => (seeds.has(symbol.id) ? [at] : []));
200
+ for (let at = pending.pop(); at !== undefined; at = pending.pop()) {
201
+ const symbol = union.symbols[at];
202
+ if (symbol === undefined || reached.has(symbol.id)) {
203
+ continue;
204
+ }
205
+ reached.add(symbol.id);
206
+ for (const edge of union.out[at] ?? []) {
207
+ const to = union.symbols[edge.to];
208
+ if (to !== undefined && dead.has(to.id)) {
209
+ pending.push(edge.to);
210
+ }
211
+ }
212
+ }
213
+ return reached;
214
+ }
215
+
185
216
  /**
186
217
  * The subjects dead only because production code only tests use is: each declaration the
187
218
  * sweep marks test-used, each `DS1602` file of such code, and each test of dead code every
@@ -373,6 +404,9 @@ export function completed(
373
404
  union.symbols.map((symbol, at) => [declarationKey(symbol.ref, symbol.position.path), at]),
374
405
  );
375
406
  const candidates = new Map(swept.sweep.candidates.map((candidate) => [candidate.id, candidate]));
407
+ const fileIds = new Map(
408
+ union.symbols.flatMap((one) => (one.kind === "file" ? [[one.position.path, one.id]] : [])),
409
+ );
376
410
  const classOf = reachabilityClasses(input);
377
411
  const ceilingOf = testCeilings(input, classOf);
378
412
  const testUsed = testUsedSubjects(input);
@@ -465,7 +499,12 @@ export function completed(
465
499
  ? union.made[at]
466
500
  : { production: candidate.productionRefs, test: candidate.testRefs };
467
501
  const ownClass =
468
- judged === undefined ? (finding.reachabilityClass ?? "certain") : classOf(judged.id);
502
+ judged !== undefined
503
+ ? classOf(judged.id)
504
+ : (finding.reachabilityClass ??
505
+ (finding.code === CODE.fileNeverImported
506
+ ? classOf(fileIds.get(finding.position.path) ?? "")
507
+ : "certain"));
469
508
  const used =
470
509
  judged !== undefined
471
510
  ? candidate !== undefined && testUsed.declarations.has(judged.id)
@@ -3,7 +3,7 @@ import type { ResolvedConfig } from "../config.ts";
3
3
  import type { Dependencies } from "../dependencies.ts";
4
4
  import type { FileFacts } from "../file-facts.ts";
5
5
  import type { Finding } from "../finding.ts";
6
- import type { Implementations } from "../implementations.ts";
6
+ import type { Implementations } from "./implementations.ts";
7
7
  import type { IntraFunctionFacts } from "../intra-function-parts.ts";
8
8
  import type { Stores } from "../stores.ts";
9
9
  import type { Boundary } from "./boundary.ts";
@@ -161,18 +161,22 @@ function dialledOver(
161
161
  unmarked: readonly CompletedFinding[],
162
162
  ): Omit<DecidedFindings, "would"> {
163
163
  const would = [...unmarked, ...found];
164
- const ledger = ledgerOf(suppressed.records, would, dialsOver(input, found, suppressed, unmarked));
165
164
  const unanswered = heldKeys(input);
165
+ const heldIds = new Set(input.swept.heldByUnanswered);
166
+ const keyOf = (finding: CompletedFinding): string =>
167
+ declarationKey(finding.symbol.holder ?? finding.symbol.ref, finding.position.path);
168
+ const held = (finding: CompletedFinding): boolean =>
169
+ unanswered.has(keyOf(finding)) || insideSkipped(input.swept.skipped, finding.position);
170
+ const ledger = ledgerOf(
171
+ suppressed.records,
172
+ would,
173
+ dialsOver(input, found, suppressed, unmarked),
174
+ { withholds: held, holdsRecord: (record) => heldIds.has(record.bound) },
175
+ );
166
176
  const markup = markupKeys(input);
167
- const candidates = found.filter((finding) => {
168
- const key = declarationKey(finding.symbol.holder ?? finding.symbol.ref, finding.position.path);
169
- return (
170
- !ledger.withheld(finding) &&
171
- !unanswered.has(key) &&
172
- !markup.has(key) &&
173
- !insideSkipped(input.swept.skipped, finding.position)
174
- );
175
- });
177
+ const candidates = found.filter(
178
+ (finding) => !ledger.withheld(finding) && !held(finding) && !markup.has(keyOf(finding)),
179
+ );
176
180
  const kept = reportable(input, candidates);
177
181
  const evaluated = evaluateEdges(kept, input.boundary.edges, input.swept.matrix.union.symbols);
178
182
  return {
@@ -15,10 +15,10 @@ import {
15
15
  type Node,
16
16
  } from "@typescript/native/ast";
17
17
  import { SymbolFlags, type Symbol as TSSymbol, type Type } from "@typescript/native/sync";
18
- import { aliasChains } from "./alias-chain.ts";
19
- import { nodeKey, type Inventory } from "./inventory.ts";
20
- import { must, Unanswerable } from "./query.ts";
21
- import type { ProjectView } from "./session.ts";
18
+ import { aliasChains } from "../alias-chain.ts";
19
+ import { nodeKey, type Inventory } from "../inventory.ts";
20
+ import { must, Unanswerable } from "../query.ts";
21
+ import type { ProjectView } from "../session.ts";
22
22
 
23
23
  /** What one run knows about how its interfaces are implemented. */
24
24
  export interface Implementations {
@@ -11,9 +11,9 @@ import {
11
11
  SyntaxKind,
12
12
  type Node,
13
13
  } from "@typescript/native/ast";
14
- import type { ProjectView } from "./session.ts";
15
- import { sourceFilesOf } from "./source-files.ts";
16
- import { bytewise } from "./text/compare.ts";
14
+ import type { ProjectView } from "../session.ts";
15
+ import { sourceFilesOf } from "../source-files.ts";
16
+ import { bytewise } from "../text/compare.ts";
17
17
 
18
18
  /**
19
19
  * The text of each string literal, or template literal with no substitution, written as
@@ -1,12 +1,12 @@
1
- import { CODE } from "../contract/contract.generated.ts";
1
+ import { CODE, REMEDIES, type Subject } from "../contract/contract.generated.ts";
2
2
  import { REPOSITORY_DOCUMENT } from "../config.ts";
3
3
  import type { Finding, FindingDetails, SuppressionEntry } from "../finding.ts";
4
4
  import type { InventorySymbol } from "../inventory.ts";
5
5
  import { FIXED_SEVERITY_CODES } from "../kinds.ts";
6
6
  import type { Position } from "../position.ts";
7
7
  import {
8
- isDependencyCode,
9
- isUnsuppressibleCode,
8
+ names,
9
+ subjectOfCode,
10
10
  type Mechanism,
11
11
  type Refusal,
12
12
  type SuppressionRecord,
@@ -72,6 +72,43 @@ export function fileRefsOf(symbols: readonly InventorySymbol[]): FileRefs {
72
72
  return (path) => byPath.get(path);
73
73
  }
74
74
 
75
+ /**
76
+ * Why a record of `mechanism` naming `codes` can match nothing, one clause per subject whose
77
+ * findings kinds.json's remedies table lets no record of that mechanism name, in subject order.
78
+ */
79
+ function unnameable(codes: readonly string[], mechanism: Mechanism): readonly string[] {
80
+ return (Object.keys(REMEDIES) as Subject[]).flatMap((subject) => {
81
+ const named = codes.filter((code) => subjectOfCode(code) === subject);
82
+ if (named.length === 0 || names(subject, mechanism)) {
83
+ return [];
84
+ }
85
+ const { names: what, suppressions } = REMEDIES[subject];
86
+ const reports = `${spelled(named)} ${named.length === 1 ? "reports" : "each report"} ${what}`;
87
+ if (suppressions.length > 0) {
88
+ const records = suppressions.map((one) => articled(WRITTEN[one]));
89
+ return [`${reports}, which only ${spelledOr(records)} can name`];
90
+ }
91
+ const settable = named.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
92
+ const remedy =
93
+ settable.length === 0
94
+ ? "fix what the finding names instead"
95
+ : `fix what the finding names or set the severity of ${spelled(settable)} instead`;
96
+ return [`${reports}, which no suppression can name; ${remedy}`];
97
+ });
98
+ }
99
+
100
+ /** A record's written name with its indefinite article. */
101
+ function articled(name: string): string {
102
+ return `${/^[aeiou]/u.test(name) ? "an" : "a"} ${name}`;
103
+ }
104
+
105
+ /** A list as a sentence names alternatives. */
106
+ function spelledOr(items: readonly string[]): string {
107
+ return items.length < 2
108
+ ? items.join("")
109
+ : `${items.slice(0, -1).join(", ")} or ${items.at(-1) ?? ""}`;
110
+ }
111
+
75
112
  /** A list of codes as a sentence names them. */
76
113
  function spelled(codes: readonly string[]): string {
77
114
  if (codes.length < 2) {
@@ -176,26 +213,8 @@ export function staleSuppressions(
176
213
  const reports = [...new Set(records.flatMap((one) => one.reports))]
177
214
  .filter((code) => !codes.includes(code))
178
215
  .sort(bytewise);
179
- const rows = codes.filter(isUnsuppressibleCode);
180
- const dependencies = first.mechanism === "inline" ? codes.filter(isDependencyCode) : [];
181
- const settable = rows.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
182
- const remedy =
183
- settable.length === 0
184
- ? "fix what the finding names instead"
185
- : `fix what the finding names or set the severity of ${spelled(settable)} instead`;
186
216
  const declared = reports.length === 0 ? "" : `its declaration reports ${spelled(reports)}`;
187
- const because = [
188
- ...(rows.length > 0
189
- ? [
190
- `${spelled(rows)} ${rows.length === 1 ? "reports a row" : "report rows"} of a document, which no suppression can name; ${remedy}`,
191
- ]
192
- : []),
193
- ...(dependencies.length > 0
194
- ? [
195
- `${spelled(dependencies)} ${dependencies.length === 1 ? "reports a dependency row" : "report dependency rows"}, which only an ignore entry or a baseline row can name`,
196
- ]
197
- : []),
198
- ];
217
+ const because = unnameable(codes, first.mechanism);
199
218
  const instead =
200
219
  because.length > 0
201
220
  ? `, because ${because.join("; ")}${declared === "" ? "" : `; ${declared}`}`
@@ -4,7 +4,7 @@ import { OUTSIDE, type Graph } from "../graph.ts";
4
4
  import type { InventorySymbol, SymbolKind } from "../inventory.ts";
5
5
  import type { MatrixCandidate } from "../matrix.ts";
6
6
  import { declarationKey } from "../ref.ts";
7
- import type { RootKind } from "../roots.ts";
7
+ import type { RootKind } from "../roots/root-set.ts";
8
8
  import { runnerCalls } from "../sweep.ts";
9
9
  import type { Emitter, EmitterInput } from "./emitter.ts";
10
10
  import { writeOnlyDeclarations } from "./reads-and-writes.ts";
@@ -16,13 +16,7 @@ import {
16
16
  type StaleRecord,
17
17
  type StaleSuppression,
18
18
  } from "./findings/self-check.ts";
19
- import {
20
- isDependencySubject,
21
- isRowSubject,
22
- pairs,
23
- suppressionCounts,
24
- type Ledger,
25
- } from "./suppress.ts";
19
+ import { namesKind, pairs, suppressionCounts, type Ledger } from "./suppress.ts";
26
20
  import type { Provenance, Recorded } from "./suppress-file.ts";
27
21
  import { bytewise } from "./text/compare.ts";
28
22
  import { PRODUCT } from "./product.ts";
@@ -131,15 +125,14 @@ export interface RecordableFinding {
131
125
 
132
126
  /**
133
127
  * The rows one round of a baseline write records from its findings, in their order. A
134
- * finding about a row of a document other than a dependency row is not recorded: no record
135
- * withholds one, so a row recording it would be stale on the first read back. Two findings
136
- * that would record the same row, two parts of one declaration under one code, record it
137
- * once.
128
+ * finding no baseline row can name is not recorded: a row recording it would be stale on
129
+ * the first read back. Two findings that would record the same row, two parts of one
130
+ * declaration under one code, record it once.
138
131
  */
139
132
  export function recordedFindings(findings: readonly RecordableFinding[]): readonly Recorded[] {
140
133
  const rows = new Map<string, Recorded>();
141
134
  for (const finding of findings) {
142
- if (isRowSubject(finding.symbol.kind) && !isDependencySubject(finding.symbol.kind)) {
135
+ if (!namesKind(finding.symbol.kind, "baseline")) {
143
136
  continue;
144
137
  }
145
138
  const row = { code: finding.code, symbol: finding.symbol.ref, path: finding.position.path };
@@ -1,9 +1,10 @@
1
1
  /**
2
- * The framework-lifecycle class: a member a `ts.lifecycle_contracts` entry lists is held
3
- * back on each class that is the entry's component, being a class a decorator or a call
4
- * the entry's `components` name is attached to or passes, or one extending, directly or
5
- * through a chain, a class its `bases` name. A component's members are its type's, so a
6
- * member a base class declares answers for every subclass.
2
+ * The framework-lifecycle class: a member a lifecycle contract lists is held back on each
3
+ * class that is the contract's component, being a class a decorator or a call the
4
+ * contract's `components` name is attached to or passes, or one extending, directly or
5
+ * through a chain, a class its `bases` name. The contracts are the platform's custom
6
+ * elements and each `ts.lifecycle_contracts` entry. A component's members are its type's,
7
+ * so a member a base class declares answers for every subclass.
7
8
  */
8
9
 
9
10
  import {
@@ -26,13 +27,37 @@ import {
26
27
  resolveEntries,
27
28
  spelledEntry,
28
29
  type ResolvedEntry,
29
- } from "./configured-declarations.ts";
30
+ } from "./roots/configured-declarations.ts";
30
31
  import type { DetectorInput, Evidence } from "./exempt.ts";
31
32
  import { nodeKey, type Inventory } from "./inventory.ts";
32
33
  import { renderPosition } from "./position.ts";
33
34
  import { must } from "./query.ts";
34
35
  import type { ProjectView } from "./session.ts";
35
36
 
37
+ /**
38
+ * The platform's own contract (references-ts.38): `CustomElementRegistry.define` reads
39
+ * these members off the class it registers, its constructor's and its prototype's
40
+ * (https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define),
41
+ * and calls each callback as a custom element reaction.
42
+ */
43
+ const CUSTOM_ELEMENTS: LifecycleContract = {
44
+ components: [{ global: "CustomElementRegistry.define" }],
45
+ members: [
46
+ "observedAttributes:static",
47
+ "disabledFeatures:static",
48
+ "formAssociated:static",
49
+ "connectedCallback",
50
+ "disconnectedCallback",
51
+ "connectedMoveCallback",
52
+ "adoptedCallback",
53
+ "attributeChangedCallback",
54
+ "formAssociatedCallback",
55
+ "formResetCallback",
56
+ "formDisabledCallback",
57
+ "formStateRestoreCallback",
58
+ ],
59
+ };
60
+
36
61
  /** One class that is a component of one contract, and what says it is. */
37
62
  interface Component {
38
63
  readonly contract: LifecycleContract;
@@ -238,16 +263,17 @@ function byBases<Brand>(
238
263
 
239
264
  /**
240
265
  * The framework-lifecycle detector: each member a contract lists, on each class that
241
- * is a component of that contract's framework, recorded where the class was found to
242
- * be one.
266
+ * is a component of that contract's framework, recorded once per place where the class
267
+ * was found to be one.
243
268
  */
244
269
  export function frameworkLifecycle<Brand>(input: DetectorInput<Brand>): readonly Evidence[] {
245
270
  const { project, held, targetRoot } = input;
246
- if (input.ts.lifecycle_contracts.length === 0) {
271
+ const declared = [CUSTOM_ELEMENTS, ...input.ts.lifecycle_contracts];
272
+ const contracts = resolveContracts(project, held, declared);
273
+ if (contracts.every((one) => one.components.length === 0 && one.bases.length === 0)) {
247
274
  return [];
248
275
  }
249
276
  const chains = aliasChains(project, held);
250
- const contracts = resolveContracts(project, held, input.ts.lifecycle_contracts);
251
277
  const components = [
252
278
  ...byComponents(project, held, chains, contracts),
253
279
  ...byBases(project, held, chains, contracts),
@@ -256,7 +282,7 @@ export function frameworkLifecycle<Brand>(input: DetectorInput<Brand>): readonly
256
282
  return [];
257
283
  }
258
284
  const ids = new Set(components.map((one) => one.id));
259
- const sides: readonly ClassSide[] = input.ts.lifecycle_contracts.some((contract) =>
285
+ const sides: readonly ClassSide[] = declared.some((contract) =>
260
286
  contract.members.some((name) => name.endsWith(":static")),
261
287
  )
262
288
  ? ["instance", "static"]
@@ -264,7 +290,7 @@ export function frameworkLifecycle<Brand>(input: DetectorInput<Brand>): readonly
264
290
  const members = sides.map((side) => classMembers(project, held, ids, side));
265
291
  const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
266
292
 
267
- const found: Evidence[] = [];
293
+ const found = new Map<string, Evidence>();
268
294
  for (const component of components) {
269
295
  const listed = new Set(component.contract.members);
270
296
  const site = renderPosition(
@@ -275,10 +301,12 @@ export function frameworkLifecycle<Brand>(input: DetectorInput<Brand>): readonly
275
301
  for (const member of members.flatMap((side) => side.get(component.id) ?? [])) {
276
302
  const symbol = byId.get(member.id);
277
303
  const spelled = symbol === undefined ? undefined : memberComponent(symbol, byId);
278
- if (spelled !== undefined && listed.has(spelled)) {
279
- found.push({ id: member.id, detail: component.detail, site });
304
+ // A configured contract may name the platform's registry too; one record answers both.
305
+ const key = `${member.id}\0${component.detail}\0${site.path}:${String(site.line)}:${String(site.column)}`;
306
+ if (spelled !== undefined && listed.has(spelled) && !found.has(key)) {
307
+ found.set(key, { id: member.id, detail: component.detail, site });
280
308
  }
281
309
  }
282
310
  }
283
- return found;
311
+ return [...found.values()];
284
312
  }