@cplieger/deadset-ts 8.0.0-dev.16 → 8.0.0-dev.18

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 (161) 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 +82 -91
  4. package/dist/src/augmentations.js +24 -25
  5. package/dist/src/bindings.js +382 -0
  6. package/dist/src/calls.js +279 -43
  7. package/dist/src/commands/tools.js +5 -9
  8. package/dist/src/component-files.js +3 -2
  9. package/dist/src/conformance-record.js +1 -1
  10. package/dist/src/consumers.js +6 -9
  11. package/dist/src/contextual-uses.js +1 -17
  12. package/dist/src/contract/config.generated.js +4 -1
  13. package/dist/src/contract/contract.generated.js +2 -2
  14. package/dist/src/contract/report.generated.js +6 -2
  15. package/dist/src/dependencies.js +25 -29
  16. package/dist/src/destructuring.js +1 -19
  17. package/dist/src/element-access.js +179 -164
  18. package/dist/src/entries/build-scripts.js +2 -2
  19. package/dist/src/entries/configuration-files.js +3 -7
  20. package/dist/src/entries/conventions.js +2 -2
  21. package/dist/src/entries/html-entries.js +7 -10
  22. package/dist/src/entries/mdx-entries.js +2 -2
  23. package/dist/src/entries/static-module.js +2 -1
  24. package/dist/src/exempt.js +0 -26
  25. package/dist/src/exposure.js +400 -0
  26. package/dist/src/facts.js +4 -4
  27. package/dist/src/findings/completion.js +44 -21
  28. package/dist/src/findings/emitters.js +22 -33
  29. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  30. package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
  31. package/dist/src/findings/intra-function.js +3 -3
  32. package/dist/src/findings/reads-and-writes.js +71 -15
  33. package/dist/src/findings/unused-declarations.js +2 -22
  34. package/dist/src/findings/visibility-narrowing.js +14 -20
  35. package/dist/src/framework-lifecycle.js +41 -14
  36. package/dist/src/import-bindings.js +73 -28
  37. package/dist/src/index/sites.js +557 -0
  38. package/dist/src/indexed-access.js +523 -478
  39. package/dist/src/interface-satisfaction.js +1 -1
  40. package/dist/src/inventory.js +18 -0
  41. package/dist/src/keyof-comparisons.js +124 -103
  42. package/dist/src/{components.js → liveness/components.js} +1 -1
  43. package/dist/src/{graph.js → liveness/graph.js} +45 -20
  44. package/dist/src/liveness/matrix.js +99 -0
  45. package/dist/src/liveness/sweep.js +558 -0
  46. package/dist/src/load/expected-module.js +2 -2
  47. package/dist/src/load/specifiers.js +2 -4
  48. package/dist/src/load/synthesize.js +5 -0
  49. package/dist/src/load/workspace-resolution.js +2 -2
  50. package/dist/src/module-declarations.js +70 -85
  51. package/dist/src/overrides.js +134 -112
  52. package/dist/src/paths.js +46 -7
  53. package/dist/src/platform-calls.js +2 -2
  54. package/dist/src/platform-symbols.js +13 -19
  55. package/dist/src/query.js +46 -0
  56. package/dist/src/references/read-rule.js +1 -0
  57. package/dist/src/references/read-rules.js +48 -0
  58. package/dist/src/references/sink.js +237 -0
  59. package/dist/src/references/sources.js +450 -0
  60. package/dist/src/references.js +110 -1285
  61. package/dist/src/roots/configured-declarations.js +3 -0
  62. package/dist/src/roots/published-reach.js +4 -4
  63. package/dist/src/roots/root-set.js +1 -1
  64. package/dist/src/roots/unseen-loads.js +38 -10
  65. package/dist/src/serialization-contract.js +8 -3
  66. package/dist/src/stores.js +4 -2
  67. package/dist/src/symbol-keys.js +89 -83
  68. package/dist/src/tests/file-class.js +76 -0
  69. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  70. package/dist/src/{test-support.js → tests/support.js} +2 -11
  71. package/dist/src/type-parameter-reads.js +178 -139
  72. package/dist/src/unimplemented-callees.js +262 -242
  73. package/dist/src/verbs/analyze.js +3 -3
  74. package/dist/src/verbs/explain.js +75 -132
  75. package/dist/src/verbs/print-retained.js +3 -3
  76. package/jsr.json +1 -1
  77. package/package.json +2 -2
  78. package/src/alias-chain.ts +0 -7
  79. package/src/amd.ts +130 -0
  80. package/src/analysis.ts +125 -128
  81. package/src/augmentations.ts +42 -28
  82. package/src/bindings.ts +460 -0
  83. package/src/calls.ts +338 -46
  84. package/src/commands/tools.ts +5 -11
  85. package/src/component-files.ts +4 -2
  86. package/src/conformance-record.ts +1 -1
  87. package/src/consumers.ts +8 -11
  88. package/src/contextual-uses.ts +2 -22
  89. package/src/contract/config.generated.ts +4 -1
  90. package/src/contract/contract.generated.ts +2 -2
  91. package/src/contract/report.generated.ts +6 -2
  92. package/src/dependencies.ts +27 -28
  93. package/src/destructuring.ts +1 -31
  94. package/src/element-access.ts +188 -182
  95. package/src/entries/build-scripts.ts +2 -2
  96. package/src/entries/configuration-files.ts +11 -8
  97. package/src/entries/conventions.ts +2 -1
  98. package/src/entries/html-entries.ts +15 -14
  99. package/src/entries/mdx-entries.ts +2 -2
  100. package/src/entries/static-module.ts +2 -1
  101. package/src/exempt.ts +31 -46
  102. package/src/exposure.ts +492 -0
  103. package/src/facts.ts +5 -3
  104. package/src/finding.ts +1 -1
  105. package/src/findings/boundary.ts +9 -0
  106. package/src/findings/completion.ts +48 -23
  107. package/src/findings/emitter.ts +1 -1
  108. package/src/findings/emitters.ts +23 -41
  109. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  110. package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
  111. package/src/findings/interfaces.ts +3 -3
  112. package/src/findings/intra-function.ts +3 -3
  113. package/src/findings/reads-and-writes.ts +99 -26
  114. package/src/findings/unused-declarations.ts +4 -28
  115. package/src/findings/visibility-narrowing.ts +25 -26
  116. package/src/framework-lifecycle.ts +42 -14
  117. package/src/import-bindings.ts +98 -29
  118. package/src/index/sites.ts +781 -0
  119. package/src/indexed-access.ts +570 -527
  120. package/src/interface-satisfaction.ts +2 -2
  121. package/src/inventory.ts +19 -0
  122. package/src/keyof-comparisons.ts +131 -116
  123. package/src/{components.ts → liveness/components.ts} +3 -3
  124. package/src/{graph.ts → liveness/graph.ts} +93 -27
  125. package/src/liveness/matrix.ts +162 -0
  126. package/src/liveness/sweep.ts +855 -0
  127. package/src/load/expected-module.ts +8 -2
  128. package/src/load/specifiers.ts +8 -5
  129. package/src/load/synthesize.ts +6 -0
  130. package/src/load/workspace-resolution.ts +9 -2
  131. package/src/module-declarations.ts +74 -98
  132. package/src/overrides.ts +162 -134
  133. package/src/paths.ts +54 -8
  134. package/src/platform-calls.ts +3 -3
  135. package/src/platform-symbols.ts +11 -27
  136. package/src/query.ts +81 -0
  137. package/src/references/read-rule.ts +60 -0
  138. package/src/references/read-rules.ts +55 -0
  139. package/src/references/sink.ts +314 -0
  140. package/src/references/sources.ts +632 -0
  141. package/src/references.ts +175 -1733
  142. package/src/report.ts +1 -1
  143. package/src/roots/configured-declarations.ts +3 -0
  144. package/src/roots/published-reach.ts +9 -4
  145. package/src/roots/root-set.ts +1 -1
  146. package/src/roots/unseen-loads.ts +49 -11
  147. package/src/serialization-contract.ts +8 -3
  148. package/src/stores.ts +15 -3
  149. package/src/symbol-keys.ts +102 -102
  150. package/src/tests/file-class.ts +135 -0
  151. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  152. package/src/{test-support.ts → tests/support.ts} +9 -20
  153. package/src/type-parameter-reads.ts +187 -158
  154. package/src/unimplemented-callees.ts +297 -280
  155. package/src/verbs/analyze.ts +3 -2
  156. package/src/verbs/explain.ts +83 -158
  157. package/src/verbs/print-retained.ts +3 -3
  158. package/dist/src/matrix.js +0 -273
  159. package/dist/src/sweep.js +0 -222
  160. package/src/matrix.ts +0 -463
  161. package/src/sweep.ts +0 -361
@@ -19,7 +19,6 @@ export function aliasChains(project, held) {
19
19
  const ownFiles = project.ownPaths();
20
20
  const steps = new Map();
21
21
  const chains = new Map();
22
- let asked = 0;
23
22
  const declarationsOf = (symbol) => {
24
23
  const ids = [];
25
24
  for (const handle of symbol.declarations) {
@@ -52,7 +51,6 @@ export function aliasChains(project, held) {
52
51
  return steps.get(symbol.id);
53
52
  }
54
53
  const next = project.aliasStepOf(symbol);
55
- asked += 1;
56
54
  steps.set(symbol.id, next);
57
55
  return next;
58
56
  };
@@ -145,8 +143,5 @@ export function aliasChains(project, held) {
145
143
  declarationsOf,
146
144
  chainOf,
147
145
  nextDeclared,
148
- get steps() {
149
- return asked;
150
- },
151
146
  };
152
147
  }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * AMD's module forms (references-ts.37), read from the syntax because the binder declares
3
+ * none of them: `define([<ids>], factory)`, `define("<name>", [<ids>], factory)` and
4
+ * `require([<ids>], callback)` (https://github.com/amdjs/amdjs-api/blob/master/AMD.md).
5
+ */
6
+ import { isArrayLiteralExpression, isArrowFunction, isFunctionExpression, isCallExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isStringLiteral, } from "@typescript/native/ast";
7
+ import { hasURLScheme, isAbsolutePath, resolvePath, startsRelativePath } from "./paths.js";
8
+ import { parenthesesRemoved } from "./unwrap.js";
9
+ /** The ids AMD binds to the factory's CommonJS parameters, which load no module. */
10
+ const FREE_VARIABLES = new Set(["require", "exports", "module"]);
11
+ /** The platform function `callee` is written as. */
12
+ export function amdPlatform(callee) {
13
+ return callee.text === "define" ? "define" : "require";
14
+ }
15
+ /**
16
+ * The dependency list one call writes, where it is a call of a bare `define` or `require` in
17
+ * one of AMD's forms. A `define` with no list, of a factory or a value alone, loads nothing;
18
+ * a `require` whose first argument is no array literal is CommonJS's.
19
+ */
20
+ export function amdLoadOf(node) {
21
+ if (!isCallExpression(node)) {
22
+ return undefined;
23
+ }
24
+ const callee = parenthesesRemoved(node.expression);
25
+ if (!isIdentifier(callee) || (callee.text !== "define" && callee.text !== "require")) {
26
+ return undefined;
27
+ }
28
+ const written = node.arguments.map((one) => parenthesesRemoved(one));
29
+ const [first] = written;
30
+ if (first === undefined) {
31
+ return undefined;
32
+ }
33
+ const define = callee.text === "define";
34
+ const named = define && isLiteralText(first);
35
+ const list = written[named ? 1 : 0];
36
+ if (list !== undefined && isArrayLiteralExpression(list)) {
37
+ const elements = list.elements.map((one) => parenthesesRemoved(one));
38
+ const factory = written[named ? 2 : 1];
39
+ const ids = elements.flatMap((one, at) => isLiteralText(one) && !FREE_VARIABLES.has(one.text)
40
+ ? [{ id: one, received: receives(factory, at) }]
41
+ : []);
42
+ return { callee, ids, computed: elements.some((one) => !isLiteralText(one)) };
43
+ }
44
+ if (!define) {
45
+ return undefined;
46
+ }
47
+ // A factory after the list's place makes what stands there a list, here a computed one.
48
+ return { callee, ids: [], computed: written.length > (named ? 2 : 1) };
49
+ }
50
+ function isLiteralText(node) {
51
+ return isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node);
52
+ }
53
+ /** Whether a factory may receive the dependency at place `at` of its list. */
54
+ function receives(factory, at) {
55
+ if (factory === undefined) {
56
+ return false;
57
+ }
58
+ if (!isFunctionExpression(factory) && !isArrowFunction(factory)) {
59
+ return true;
60
+ }
61
+ const { parameters } = factory;
62
+ return at < parameters.length || parameters.some((one) => one.dotDotDotToken !== undefined);
63
+ }
64
+ /**
65
+ * The path a dependency id names against the calling file's directory: a relative module id,
66
+ * `./` or `../` with no `.js` ending, with `.js` appended. RequireJS reads any other id
67
+ * through its configuration or as a URL against the page, which the analysis does not know.
68
+ */
69
+ export function amdPath(dir, id) {
70
+ return startsRelativePath(id) && !id.endsWith(".js") ? resolvePath(dir, `${id}.js`) : undefined;
71
+ }
72
+ /**
73
+ * Whether one call may load a file the analysis cannot name: a computed list, or an id that
74
+ * RequireJS reads as a URL, one ending in `.js`, starting with `/` or carrying a scheme. Any
75
+ * other id {@link amdPath} reads no path from is a package's.
76
+ */
77
+ export function amdUnresolved(load) {
78
+ return (load.computed ||
79
+ load.ids.some(({ id }) => amdPath("/", id.text) === undefined &&
80
+ (id.text.endsWith(".js") || isAbsolutePath(id.text) || hasURLScheme(id.text))));
81
+ }
@@ -12,6 +12,7 @@ import { consumerReferences } from "./consumers.js";
12
12
  import { configuredDeclarations, configuredRef, declarationsNamed, } from "./roots/configured-declarations.js";
13
13
  import { decorator } from "./decorator.js";
14
14
  import { configurationNeeds, dependenciesOf, projectNeeds } from "./dependencies.js";
15
+ import { carriersOf, NO_CARRIERS } from "./exposure.js";
15
16
  import { deprecatedDeclarations } from "./deprecation.js";
16
17
  import { moduleIdentity } from "./load/discover.js";
17
18
  import { loadRun } from "./load/load.js";
@@ -19,36 +20,40 @@ import { readSources } from "./entries/sources.js";
19
20
  import { readWorkflows } from "./entries/workflow-steps.js";
20
21
  import { readEdgeSides } from "./edges.js";
21
22
  import { enumGroup } from "./enum-group.js";
23
+ import { bindingsOf, distinctBindings } from "./bindings.js";
22
24
  import { generatedFile, generatedFilesOf } from "./generated-file.js";
23
- import { inOperandNames } from "./in-operands.js";
25
+ import { inOperandNames } from "./findings/in-operands.js";
24
26
  import { sourceFilesOf } from "./source-files.js";
25
- import { evidenceRecords, heldRecords, disabledClasses, exemptionsOf, holdingWhile, isUnansweredRecord, retainedIn, writtenRecords, } from "./exempt.js";
27
+ import { evidenceRecords, heldRecords, disabledClasses, exemptionsOf, isUnansweredRecord, writtenRecords, } from "./exempt.js";
26
28
  import { heldByInclusion, sourcePaths, sourceTree } from "./file-facts.js";
27
29
  import { findingsPass, recordedFindings, rowKey } from "./findings-pass.js";
28
30
  import { unmatchedRootFindings } from "./findings/self-check.js";
29
31
  import { frameworkLifecycle } from "./framework-lifecycle.js";
30
- import { implementations, mergeImplementations } from "./implementations.js";
32
+ import { implementations, mergeImplementations } from "./findings/implementations.js";
31
33
  import { injectionContainer } from "./injection-container.js";
32
34
  import { interfaceSatisfaction, settledSatisfaction } from "./interface-satisfaction.js";
33
35
  import { intraFunctionFacts, projectParts } from "./intra-function-parts.js";
34
36
  import {} from "./inventory.js";
35
37
  import { inventory } from "./inventory/enumerate.js";
36
38
  import { readManifest } from "./roots/manifest.js";
37
- import { liveFilesAt, matrixOf, referenceKey, sweepMatrix, } from "./matrix.js";
39
+ import { matrixOf, referenceKey } from "./liveness/matrix.js";
38
40
  import { ancestors, dirnamePath, extensionOf, joinPath, relativePath, resolvePath, } from "./paths.js";
39
41
  import { byPosition, isComponentFile } from "./position.js";
40
42
  import { skippedUnits } from "./type-errors.js";
41
43
  import { reflectiveLookup } from "./reflective-lookup.js";
42
- import { references, testFileRulesOf } from "./references.js";
44
+ import { references } from "./references.js";
43
45
  import { rootSet, unmatchedEverywhere, } from "./roots/root-set.js";
44
46
  import { decodedMembers, serializationContract } from "./serialization-contract.js";
45
- import { outsideTestReferences, testFilesAt } from "./outside-tests.js";
47
+ import { outsideTestReferences, testFilesAt } from "./tests/outside.js";
48
+ import { formedForNode } from "./load/synthesize.js";
46
49
  import { GLOBALS_RUNNER, namesTestGlobals } from "./load/test-globals.js";
47
- import { supportReferences, testSupportFiles } from "./test-support.js";
50
+ import { targetTestFile, testFilePaths, testFileRulesOf, withheldBy, } from "./tests/file-class.js";
51
+ import { testSupportFiles } from "./tests/support.js";
48
52
  import { accessorsOf, storesOf } from "./stores.js";
49
53
  import { isPartKind } from "./suppress.js";
50
54
  import { bindRows, readBaseline, readIgnoreFile } from "./suppress-file.js";
51
55
  import { inlineDirectives, inlineSuppressions } from "./suppress-inline.js";
56
+ import { sweep } from "./liveness/sweep.js";
52
57
  import { readTemplates, templateField } from "./template-field.js";
53
58
  import { bytewise } from "./text/compare.js";
54
59
  import { holdsManifest, manifestAt } from "./fs/manifest.js";
@@ -109,6 +114,7 @@ function readProjects(engine, host, scope, config, stage) {
109
114
  workflows,
110
115
  });
111
116
  const rooted = rootSet(project, held, targetRoot, { ...options, sources, skips });
117
+ const generated = generatedFilesOf(own, sources.generated);
112
118
  return {
113
119
  configuration: planned.id,
114
120
  configFile: project.configFile,
@@ -120,8 +126,12 @@ function readProjects(engine, host, scope, config, stage) {
120
126
  .ownSourceFiles()
121
127
  .filter((file) => referenceOnly(file.fileName))
122
128
  .flatMap((file) => relativePath(absoluteRoot, file.fileName) ?? []),
123
- generated: generatedFilesOf(own, sources.generated),
129
+ generated,
130
+ bindings: bindingsOf(own.byPath, generated, new Map(held.symbols
131
+ .filter((one) => one.kind === "file")
132
+ .map((one) => [one.position.path, one.ref]))),
124
133
  sources,
134
+ formedForNode: formedForNode(planned),
125
135
  };
126
136
  };
127
137
  for (const file of project.ownSourceFiles()) {
@@ -130,14 +140,25 @@ function readProjects(engine, host, scope, config, stage) {
130
140
  return stage(project, { configuration: planned.id, read });
131
141
  });
132
142
  const consumed = new Map();
143
+ const consumerHeld = new Set();
133
144
  for (const { project, consumer } of classified.consumed) {
134
145
  const side = { id: consumer.id, root: consumer.path };
135
- for (const reference of consumerReferences(project, host, targetRoot, side, config, parse)) {
146
+ const read = consumerReferences(project, host, targetRoot, side, config, parse);
147
+ for (const reference of read.references) {
136
148
  consumed.set(referenceKey(reference), reference);
137
149
  }
150
+ for (const id of read.held) {
151
+ consumerHeld.add(id);
152
+ }
138
153
  }
139
154
  const outsideTests = testFilesAt(host, targetRoot, sourcePaths(host, targetRoot).filter((path) => !heldFiles.has(path) && testPatterns.some((pattern) => pattern.test(path))), parse);
140
- return { answers, references: [...consumed.values()], outsideTests, workflows };
155
+ return {
156
+ answers,
157
+ references: [...consumed.values()],
158
+ consumerHeld: [...consumerHeld],
159
+ outsideTests,
160
+ workflows,
161
+ };
141
162
  });
142
163
  }
143
164
  /**
@@ -202,6 +223,13 @@ export function runRoots(engine, host, scope, config, provenance) {
202
223
  unanswered: read.unanswered,
203
224
  };
204
225
  }
226
+ /** The production mode a configuration sets: a consumer's test reference is classed as `analysis.consumer_tests` says. */
227
+ export function productionMode(config) {
228
+ return {
229
+ production: true,
230
+ consumerTestsProduction: config.analysis.consumer_tests === "production",
231
+ };
232
+ }
205
233
  /** The exemption classes this analyzer detects, each by its detector; any other retains nothing. */
206
234
  export const DETECTORS = new Map([
207
235
  ["interface-satisfaction", interfaceSatisfaction],
@@ -268,11 +296,13 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
268
296
  const directories = runDirectories(host, targetRoot, (fileName, text) => engine.parseSourceFile(fileName, text));
269
297
  const read = readProjects(engine, host, scope, config, (project, opened) => {
270
298
  const projectRead = opened.read();
299
+ const testFile = targetTestFile(projectRead.sources.testFiles);
271
300
  const resolved = references(project, projectRead.held, targetRoot, {
272
- testFiles: config.ts.test_files,
273
- testFileNames: projectRead.sources.testFiles,
301
+ testFile,
274
302
  directories,
303
+ formedForNode: projectRead.formedForNode,
275
304
  });
305
+ const testFiles = testFilePaths(project, targetRoot, testFile);
276
306
  const evidence = evidenceRecords({
277
307
  project,
278
308
  held: projectRead.held,
@@ -292,7 +322,9 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
292
322
  return {
293
323
  projectRead,
294
324
  references: resolved.references,
295
- testFiles: resolved.testFilePaths,
325
+ held: resolved.held,
326
+ possiblyLoaded: resolved.possiblyLoaded,
327
+ testFiles,
296
328
  evidence,
297
329
  componentFiles: components.map(pathOf),
298
330
  imports: new Map(project.ownSourceFiles().flatMap((file) => {
@@ -316,8 +348,8 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
316
348
  line: lineOf(file.originalText, warning.offset),
317
349
  reason: warning.reason,
318
350
  }))),
319
- decoded: decodedMembers(project, projectRead.held, productionFiles(project, targetRoot, resolved.testFilePaths), consumers),
320
- extra: extra(project, projectRead, resolved.references, resolved.testFilePaths, resolved.unseenLoads.map((position) => ({
351
+ decoded: decodedMembers(project, projectRead.held, productionFiles(project, targetRoot, testFiles), consumers),
352
+ extra: extra(project, projectRead, resolved.references, testFiles, resolved.unseenLoads.map((position) => ({
321
353
  configuration: projectRead.configuration,
322
354
  position,
323
355
  }))),
@@ -359,7 +391,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
359
391
  testFiles: new Set([...one.testFiles, ...support]),
360
392
  });
361
393
  const exempt = detected.records;
362
- const made = supportReferences([...one.references, ...(satisfied?.references ?? [])], support);
394
+ const made = [...one.references, ...(satisfied?.references ?? [])];
363
395
  const held = new Set(projectRead.held.symbols.map((symbol) => symbol.position.path));
364
396
  const supportFiles = [...support].filter((path) => held.has(path)).sort(bytewise);
365
397
  const testUsedFiles = [...testUsed].filter((path) => held.has(path)).sort(bytewise);
@@ -386,13 +418,13 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
386
418
  unanswered: [
387
419
  ...projectRead.held.unanswered,
388
420
  ...projectRead.rooted.unanswered,
389
- ...made
390
- .filter((reference) => reference.resolution === "by-name")
391
- .map((reference) => reference.to),
421
+ ...made.filter((reference) => reference.guessed === true).map((reference) => reference.to),
422
+ ...one.held,
392
423
  ...exempt.filter(isUnansweredRecord).map((record) => record.id),
393
424
  ...one.decoded.unanswered,
394
425
  ],
395
426
  filledOutside: one.decoded.filled,
427
+ possiblyLoaded: one.possiblyLoaded,
396
428
  typeErrorSkips: projectRead.typeErrorSkips,
397
429
  unmappedEntries: projectRead.rooted.unmappedEntries,
398
430
  heldScripts: projectRead.rooted.heldScripts,
@@ -418,11 +450,13 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
418
450
  unansweredHeld: [
419
451
  ...new Set([
420
452
  ...answers.flatMap((one) => one.unanswered),
421
- ...consumed.filter((one) => one.resolution === "by-name").map((one) => one.to),
453
+ ...consumed.filter((one) => one.guessed === true).map((one) => one.to),
454
+ ...read.result.consumerHeld,
422
455
  ]),
423
456
  ],
424
457
  projects,
425
458
  consumers: read.plan.discovery.consumers,
459
+ declaresConsumers: scope.consumers.length > 0,
426
460
  notBuilt: read.classified.notBuilt,
427
461
  unanswered: read.unanswered,
428
462
  componentsUnread: read.plan.workspace?.componentsUnread ?? [],
@@ -436,29 +470,6 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
436
470
  workflows: read.result.workflows,
437
471
  };
438
472
  }
439
- /**
440
- * The run swept under one set of marks with the records that hold. A record a component
441
- * file's markup holds counts only while the file is live, and what it holds may be what
442
- * keeps the file live, so each file's markup joins only once a sweep without it finds the
443
- * file live, until a sweep finds no further file live.
444
- */
445
- function holdingSweep(read, input) {
446
- const paths = new Set(read.exempt.flatMap((record) => record.whileLive ?? []));
447
- let live = new Set();
448
- for (;;) {
449
- const swept = {
450
- ...input,
451
- exempt: holdingWhile(read.exempt, live),
452
- unanswered: read.unansweredHeld,
453
- };
454
- const result = sweepMatrix(read.matrix, swept);
455
- const next = new Set(liveFilesAt(read.matrix, result, paths).map((one) => one.position.path));
456
- if (next.size === live.size) {
457
- return { swept, result };
458
- }
459
- live = next;
460
- }
461
- }
462
473
  /**
463
474
  * The test references exemption evidence written in a test file makes: each is a read of
464
475
  * the declaration it would retain, made by the test file at the evidence's site.
@@ -491,7 +502,7 @@ function evidenceReferences(symbols, inTestFiles) {
491
502
  * makes being a test's.
492
503
  */
493
504
  function countedWrites(references, support, mode) {
494
- return new Set(supportReferences(references, support).flatMap((reference) => reference.use === "write" && !(mode.production && reference.test) ? [reference.to] : []));
505
+ return new Set(references.flatMap((reference) => reference.use === "write" && !withheldBy(mode, reference, support) ? [reference.to] : []));
495
506
  }
496
507
  /**
497
508
  * The declarations every reference the mode counts stores into, a loaded consumer's
@@ -501,8 +512,9 @@ function writtenOnly(read, mode) {
501
512
  const written = new Set();
502
513
  const readSome = new Set();
503
514
  for (const { configured } of read.projects) {
515
+ const support = new Set(configured.supportFiles ?? []);
504
516
  for (const reference of configured.references) {
505
- if (mode.production && reference.test) {
517
+ if (withheldBy(mode, reference, support)) {
506
518
  continue;
507
519
  }
508
520
  if (reference.use === "write" && reference.consumer === undefined) {
@@ -515,55 +527,22 @@ function writtenOnly(read, mode) {
515
527
  }
516
528
  return new Set([...written].filter((id) => !readSome.has(id)));
517
529
  }
518
- /**
519
- * The sweep of a run read once, under one set of marks. Where a question went unanswered,
520
- * the run is swept once more without what that question could have kept live and without
521
- * the references that stood in for its answer, and every declaration that sweep judges
522
- * dead and this one judges live, or dead by another rule, is held by the gap too, as is
523
- * every declaration that makes such a reference.
524
- */
530
+ /** The sweep of a run read once, under one set of marks. */
525
531
  function sweptOf(read, input) {
526
- const { swept, result } = holdingSweep(read, input);
527
- const exempt = swept.exempt ?? [];
528
- const held = new Set(read.unansweredHeld);
529
- if (held.size > 0) {
530
- // What a declaration reads where the checker did not answer is unknown, so a rule
531
- // over what it reads, the test of dead code among them, cannot judge it.
532
- for (const { configured } of read.projects) {
533
- for (const reference of configured.references) {
534
- if (reference.resolution === "by-name") {
535
- held.add(reference.from);
536
- }
537
- }
538
- }
539
- const dead = new Map(result.candidates.map((candidate) => [candidate.id, candidate]));
540
- const answered = matrixOf(read.projects.map(({ configured }) => ({
541
- ...configured,
542
- references: configured.references.filter((one) => one.resolution !== "by-name"),
543
- })));
544
- const bare = {
545
- ...swept,
546
- exempt: exempt.filter((record) => !isUnansweredRecord(record)),
547
- unanswered: [],
548
- };
549
- for (const candidate of sweepMatrix(answered, bare).candidates) {
550
- const kept = dead.get(candidate.id);
551
- if (kept?.testOfDeadCode !== candidate.testOfDeadCode ||
552
- kept.relation !== candidate.relation) {
553
- held.add(candidate.id);
554
- }
555
- }
556
- }
532
+ const componentFiles = [...new Set(read.projects.flatMap((one) => one.componentFiles))].sort(bytewise);
557
533
  return {
558
534
  matrix: read.matrix,
559
- sweep: result,
560
- exempt,
561
- retained: retainedIn(read.matrix, swept, writtenOnly(read, input.mode)),
535
+ sweep: sweep(read.matrix, {
536
+ ...input,
537
+ exempt: read.exempt,
538
+ unanswered: read.unansweredHeld,
539
+ componentFiles,
540
+ unseenCallers: !read.declaresConsumers,
541
+ writeOnly: writtenOnly(read, input.mode),
542
+ }),
562
543
  notBuilt: read.notBuilt,
563
544
  unanswered: read.unanswered,
564
- heldByUnanswered: [...held],
565
545
  skipped: read.projects.flatMap((one) => one.skipped),
566
- componentFiles: [...new Set(read.projects.flatMap((one) => one.componentFiles))].sort(bytewise),
567
546
  };
568
547
  }
569
548
  /**
@@ -592,12 +571,15 @@ export function readEmitterRun(engine, host, scope, config, mode, detectors) {
592
571
  implementations: implementations(project, read.held),
593
572
  heldByInclusion: heldByInclusion(project, targetRoot),
594
573
  generated: read.generated,
574
+ bindings: read.bindings,
595
575
  inOperands: inOperandNames(project, targetRoot, testFiles),
596
576
  rooted: read.rooted,
597
577
  directives: inlineDirectives(project, read.held, targetRoot).filter((directive) => !read.referenceOnly.includes(directive.site.path)),
598
578
  declarationsNamed: declarationsNamed(project, read.held, configuredDeclarations(config)),
599
579
  parts: projectParts(project, read.held, references, targetRoot),
600
580
  unseenLoads,
581
+ // Only a closed world reports an unnecessary export, which is all the carriers decide.
582
+ carriers: config.consumers.complete ? carriersOf(project, read.held) : NO_CARRIERS,
601
583
  }));
602
584
  // Whether a declaration carries the marker decides its code, so one whose answer went
603
585
  // unread is reported by nothing.
@@ -659,8 +641,9 @@ export function emitterInputOver(host, scope, config, read, input, selfCheck) {
659
641
  deprecated: new Set(projects.flatMap((one) => one.extra.deprecation.deprecated)),
660
642
  stores: storesOf(projects.map((one) => ({
661
643
  references: one.configured.references,
644
+ support: new Set(one.configured.supportFiles ?? []),
662
645
  accessors: one.extra.accessors,
663
- })), swept.exempt, input.mode),
646
+ })), swept.sweep.exempt, input.mode),
664
647
  dependencies: dependenciesOf(host, targetRoot, read.workflows, [...projects.map((one) => one.extra.needs), configurationNeeds(read.conventionUses)], swept.sweep.candidates.map((candidate) => candidate.id), members.read, members.unbuilt, read.nestedDirs),
665
648
  implementations: mergeImplementations(projects.map((one) => one.extra.implementations)),
666
649
  files: {
@@ -678,9 +661,16 @@ export function emitterInputOver(host, scope, config, read, input, selfCheck) {
678
661
  encapsulated: readManifest(host, targetRoot).declaresExports,
679
662
  edges: readEdgeSides(host, targetRoot),
680
663
  filledOutside: new Set(projects.flatMap((one) => one.filledOutside)),
664
+ possiblyLoaded: new Set(projects.flatMap((one) => one.possiblyLoaded)),
665
+ carriers: {
666
+ named: new Map([
667
+ ...Map.groupBy(projects.flatMap((one) => [...one.extra.carriers.named]), ([id]) => id),
668
+ ].map(([id, entries]) => [id, [...new Set(entries.flatMap(([, named]) => named))]])),
669
+ unread: new Set(projects.flatMap((one) => [...one.extra.carriers.unread])),
670
+ },
681
671
  },
682
672
  selfCheck,
683
- intraFunction: intraFunctionFacts(projects.map((one) => one.extra.parts), projects.flatMap((one) => one.configured.references), new Set([...swept.exempt.map((one) => one.id), ...swept.heldByUnanswered])),
673
+ intraFunction: intraFunctionFacts(projects.map((one) => one.extra.parts), projects.flatMap((one) => one.configured.references), new Set([...swept.sweep.exempt.map((one) => one.id), ...swept.sweep.held])),
684
674
  };
685
675
  }
686
676
  /**
@@ -725,6 +715,7 @@ function readAnalysis(engine, host, scope, config, provenance, mode, detectors)
725
715
  heldScripts: read.projects.flatMap((one) => one.heldScripts.map((held) => ({ ...held, configFile: one.configFile }))),
726
716
  unseenLoads: read.projects.flatMap((one) => one.extra.unseenLoads),
727
717
  generated: new Map(read.projects.flatMap((one) => [...one.extra.generated])),
718
+ bindings: distinctBindings(read.projects.flatMap((one) => one.extra.bindings)),
728
719
  },
729
720
  configured: read.projects.map((one) => one.configured),
730
721
  symbols,
@@ -792,7 +783,7 @@ export function runAnalysis(engine, host, scope, config, provenance, mode, detec
792
783
  * declarations of its own the run holds for them.
793
784
  */
794
785
  export function unansweredCounts(analysis) {
795
- const held = new Set(analysis.swept.heldByUnanswered);
786
+ const held = new Set(analysis.swept.sweep.held);
796
787
  return analysis.run.projects.flatMap((project) => {
797
788
  const questions = analysis.run.unanswered.filter((one) => one.configFile === project.configFile).length +
798
789
  analysis.run.unread.filter((one) => one.kind === "manifest" && one.configFile === project.configFile).length +
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { isInterfaceDeclaration, isModuleBlock, isModuleDeclaration, } from "@typescript/native/ast";
10
10
  import { nodeKey } from "./inventory.js";
11
- import { referenceKey } from "./matrix.js";
11
+ import { referenceKey } from "./liveness/matrix.js";
12
12
  /** Whether one declaration is a `declare global` block: a module-level namespace named `global`. */
13
13
  export function isGlobalBlock(symbol, held, files) {
14
14
  return (symbol.kind === "namespace" &&
@@ -50,19 +50,15 @@ function holdersOf(held, scripts) {
50
50
  * file or a `declare` holds that a `declare global` block, or the top level of a file in
51
51
  * `scripts`, declares, directly or inside a namespace there.
52
52
  */
53
- export function ambientGlobalInterfaces(held, interfaces, scripts) {
53
+ function ambientGlobalInterfaces(held, interfaces, scripts) {
54
54
  const holders = holdersOf(held, scripts);
55
55
  return holders === undefined
56
56
  ? []
57
57
  : interfaces.filter((id) => held.ambient.has(id) && holders(id).global);
58
58
  }
59
59
  /**
60
- * Each ambient global interface of the project's own files that merges into an interface
61
- * declared outside them, with every interface, namespace and block between it and the
62
- * global scope: the outside declaration names it wherever a value is typed by it. Where
63
- * every outside declaration is a package's, not the compiler's library, and declares no
64
- * member, each member it declares as well: such an interface is a slot the package reads
65
- * by type, as `keyof` does, in declarations the analysis does not walk.
60
+ * Which ambient global interfaces of the project's own files, `files`, merge into an interface
61
+ * declared outside them, and what each merge keeps.
66
62
  */
67
63
  export function mergedGlobalInterfaces(project, held, files) {
68
64
  const scripts = new Set();
@@ -88,7 +84,7 @@ export function mergedGlobalInterfaces(project, held, files) {
88
84
  const global = new Set(ambientGlobalInterfaces(held, [...names.keys()], scripts));
89
85
  const asked = [...names].filter(([id]) => global.has(id));
90
86
  if (asked.length === 0) {
91
- return [];
87
+ return { scripts, interfaces: new Set(), kept: [] };
92
88
  }
93
89
  const answers = project.symbolsAt(asked.map(([, name]) => project.handle(name)));
94
90
  const holders = holdersOf(held, scripts);
@@ -99,22 +95,25 @@ export function mergedGlobalInterfaces(project, held, files) {
99
95
  const node = project.declarationAt(handle)?.node;
100
96
  return node !== undefined && isInterfaceDeclaration(node) && node.members.length === 0;
101
97
  };
102
- return [
103
- ...new Set(asked.flatMap(([id], at) => {
104
- const symbol = answers[at];
105
- if (typeof symbol !== "object") {
106
- return [];
107
- }
108
- const outside = symbol.declarations.filter((handle) => !files.byName.has(handle.path));
109
- if (outside.length === 0) {
110
- return [];
111
- }
112
- const members = outside.every(empty)
113
- ? held.symbols.filter((one) => one.parent === id).map((one) => one.id)
114
- : [];
115
- return [id, ...(holders?.(id).ids ?? []), ...members];
116
- })),
117
- ];
98
+ const interfaces = new Set();
99
+ const kept = new Set();
100
+ asked.forEach(([id], at) => {
101
+ const symbol = answers[at];
102
+ const outside = typeof symbol === "object"
103
+ ? symbol.declarations.filter((handle) => !files.byName.has(handle.path))
104
+ : [];
105
+ if (outside.length === 0) {
106
+ return;
107
+ }
108
+ interfaces.add(id);
109
+ const members = outside.every(empty)
110
+ ? held.symbols.filter((one) => one.parent === id).map((one) => one.id)
111
+ : [];
112
+ for (const one of [id, ...(holders?.(id).ids ?? []), ...members]) {
113
+ kept.add(one);
114
+ }
115
+ });
116
+ return { scripts, interfaces, kept: [...kept] };
118
117
  }
119
118
  /**
120
119
  * For each reference to a declaration an augmentation block, or the global scope of a