@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
@@ -9,7 +9,7 @@
9
9
  import { isCallExpression, isClassDeclaration, isClassExpression, isClassStaticBlockDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isHeritageClause, isMethodDeclaration, isNewExpression, isObjectLiteralExpression, isSetAccessorDeclaration, isTypeLiteralNode, ModifierFlags, SyntaxKind, } from "@typescript/native/ast";
10
10
  import { SymbolFlags } from "@typescript/native/sync";
11
11
  import { aliasChains } from "./alias-chain.js";
12
- import { emptyImplementation } from "./implementations.js";
12
+ import { emptyImplementation } from "./findings/implementations.js";
13
13
  import { nodeKey } from "./inventory.js";
14
14
  import { renderPosition } from "./position.js";
15
15
  import { must } from "./query.js";
@@ -2,6 +2,24 @@ import { isClassDeclaration, isComputedPropertyName, isConstructorDeclaration, i
2
2
  import { byPosition, positionKey, renderPosition, startLine } from "./position.js";
3
3
  import { renderRef, spell } from "./ref.js";
4
4
  import { signatureOf } from "./signatures.js";
5
+ /** The reader's word for each kind of declaration, as a finding names it. */
6
+ export const WORDS = {
7
+ file: "file",
8
+ function: "function",
9
+ class: "class",
10
+ interface: "interface",
11
+ type: "type alias",
12
+ enum: "enum",
13
+ namespace: "namespace",
14
+ variable: "variable",
15
+ "export-alias": "re-export",
16
+ method: "method",
17
+ "class-member": "class member",
18
+ "interface-method": "interface method",
19
+ "type-member": "type member",
20
+ "enum-member": "enum member",
21
+ "type-parameter": "type parameter",
22
+ };
5
23
  /**
6
24
  * One node's identity inside one program, which is how two readings of that program name
7
25
  * one declaration: the binder's table and the tree meet here, and so does the reference
@@ -4,10 +4,8 @@
4
4
  * that holds `keyof X`, the literal the other side has names the member of `X` under that
5
5
  * key, because deleting the member makes the comparison a type error.
6
6
  */
7
- import { isBinaryExpression, isCaseClause, isElementAccessExpression, isIdentifier, isPropertyAccessExpression, isSwitchStatement, isTypeOperatorNode, SyntaxKind, } from "@typescript/native/ast";
7
+ import { isBinaryExpression, isCaseClause, isElementAccessExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isPropertyAccessExpression, isStringLiteral, isSwitchStatement, isTypeOperatorNode, SyntaxKind, } from "@typescript/native/ast";
8
8
  import { SymbolFlags } from "@typescript/native/sync";
9
- import { nodeKey } from "./inventory.js";
10
- import { renderPosition } from "./position.js";
11
9
  import { UNANSWERED } from "./query.js";
12
10
  import { parenthesesRemoved } from "./unwrap.js";
13
11
  /** The operators that compare their operands for equality. */
@@ -55,114 +53,137 @@ function literalNames(parts) {
55
53
  return names;
56
54
  }
57
55
  /**
58
- * The references the key comparisons of one project's own files make to the members their
59
- * literal side names, each made by the declaration holding the comparison.
56
+ * The reads the key comparisons of a side's files make of the members their literal side
57
+ * names, each made by the declaration holding the comparison. A question the checker leaves
58
+ * unanswered holds what its answer could have read.
60
59
  */
61
- export function keyofComparisonReferences(project, held, chains, root, testFiles) {
62
- const queries = project.queries;
63
- const comparisons = [];
64
- for (const file of project.ownSourceFiles()) {
65
- const fileId = held.declarations.get(nodeKey(file, file));
66
- if (fileId === undefined) {
67
- continue;
68
- }
69
- const visit = (node, from) => {
70
- const holder = held.declarations.get(nodeKey(file, node)) ?? from;
71
- if (isBinaryExpression(node) && EQUALITIES.has(node.operatorToken.kind)) {
72
- comparisons.push({ file, from: holder, keyed: node.left, other: node.right });
73
- comparisons.push({ file, from: holder, keyed: node.right, other: node.left });
74
- }
75
- if (isSwitchStatement(node)) {
76
- for (const clause of node.caseBlock.clauses) {
77
- if (isCaseClause(clause)) {
78
- comparisons.push({
79
- file,
80
- from: holder,
81
- keyed: node.expression,
82
- other: clause.expression,
60
+ export const keyofComparisons = {
61
+ start() {
62
+ const comparisons = [];
63
+ return {
64
+ visit(node, { file, from }) {
65
+ if (isBinaryExpression(node) && EQUALITIES.has(node.operatorToken.kind)) {
66
+ comparisons.push({ file, from, keyed: node.left, other: node.right });
67
+ comparisons.push({ file, from, keyed: node.right, other: node.left });
68
+ }
69
+ if (isSwitchStatement(node)) {
70
+ for (const clause of node.caseBlock.clauses) {
71
+ if (isCaseClause(clause)) {
72
+ comparisons.push({ file, from, keyed: node.expression, other: clause.expression });
73
+ }
74
+ }
75
+ }
76
+ },
77
+ reads({ project, chains }) {
78
+ const queries = project.queries;
79
+ const found = [];
80
+ const named = comparisons.flatMap((one) => {
81
+ const name = nameOf(one.keyed);
82
+ return name === undefined || isElementAccessExpression(parenthesesRemoved(one.other))
83
+ ? []
84
+ : [{ ...one, name }];
85
+ });
86
+ if (named.length === 0) {
87
+ return found;
88
+ }
89
+ // The operand's own declaration states the key type, which the checker resolves to
90
+ // the literals it admits and so no longer names the type the key indexes.
91
+ const symbols = queries.symbolsAt(named.map((one) => one.name));
92
+ const keyed = named.flatMap((one, at) => {
93
+ const asked = symbols[at];
94
+ const symbol = asked === UNANSWERED || asked === undefined || (asked.flags & SymbolFlags.Alias) === 0
95
+ ? asked
96
+ : queries.aliased(asked);
97
+ if (symbol === UNANSWERED) {
98
+ const text = literalText(one.other);
99
+ found.push({
100
+ kind: "held",
101
+ from: one.from,
102
+ ...(text === undefined ? {} : { names: [text] }),
83
103
  });
104
+ return [];
84
105
  }
106
+ if (symbol === undefined) {
107
+ return [];
108
+ }
109
+ const operands = symbol.declarations.flatMap((handle) => {
110
+ const declared = project.declarationAt(handle)?.node?.type;
111
+ return declared === undefined ? [] : keyofOperands(declared);
112
+ });
113
+ return operands.length === 0 ? [] : [{ ...one, operands }];
114
+ });
115
+ if (keyed.length === 0) {
116
+ return found;
85
117
  }
86
- }
87
- node.forEachChild((child) => {
88
- visit(child, holder);
89
- });
90
- };
91
- file.forEachChild((child) => {
92
- visit(child, fileId);
93
- });
94
- }
95
- const named = comparisons.flatMap((one) => {
96
- const name = nameOf(one.keyed);
97
- return name === undefined || isElementAccessExpression(parenthesesRemoved(one.other))
98
- ? []
99
- : [{ ...one, name }];
100
- });
101
- if (named.length === 0) {
102
- return [];
103
- }
104
- // The operand's own declaration states the key type, which the checker resolves to
105
- // the literals it admits and so no longer names the type the key indexes.
106
- const symbols = queries.symbolsAt(named.map((one) => one.name));
107
- const keyed = named.flatMap((one, at) => {
108
- const asked = symbols[at];
109
- const symbol = asked === UNANSWERED || asked === undefined || (asked.flags & SymbolFlags.Alias) === 0
110
- ? asked
111
- : queries.aliased(asked);
112
- if (symbol === UNANSWERED || symbol === undefined) {
113
- return [];
114
- }
115
- const operands = symbol.declarations.flatMap((handle) => {
116
- const declared = project.declarationAt(handle)?.node?.type;
117
- return declared === undefined ? [] : keyofOperands(declared);
118
- });
119
- return operands.length === 0 ? [] : [{ ...one, operands }];
120
- });
121
- if (keyed.length === 0) {
122
- return [];
123
- }
124
- const nodes = keyed.flatMap((one) => [one.other, ...one.operands]);
125
- const types = queries.typesAt(nodes);
126
- const typeOf = new Map();
127
- nodes.forEach((node, at) => {
128
- const type = types[at];
129
- if (type !== UNANSWERED && type !== undefined) {
130
- typeOf.set(node, type);
131
- }
132
- });
133
- const partsOf = (type) => {
134
- const parts = queries.constituents(type);
135
- return parts === UNANSWERED ? [type] : parts;
136
- };
137
- const found = [];
138
- for (const one of keyed) {
139
- const literal = typeOf.get(one.other);
140
- const names = literal === undefined ? [] : literalNames(partsOf(literal));
141
- if (names.length === 0) {
142
- continue;
143
- }
144
- const position = renderPosition(one.file, root, one.other.getStart());
145
- for (const operand of one.operands) {
146
- const object = typeOf.get(operand);
147
- for (const part of object === undefined ? [] : partsOf(object)) {
148
- const properties = queries.propertiesOf(part);
149
- for (const property of properties === UNANSWERED ? [] : properties) {
150
- if (!names.includes(property.name)) {
151
- continue;
118
+ const nodes = keyed.flatMap((one) => [one.other, ...one.operands]);
119
+ const types = queries.typesAt(nodes);
120
+ const typeOf = new Map();
121
+ nodes.forEach((node, at) => {
122
+ const type = types[at];
123
+ if (type !== undefined) {
124
+ typeOf.set(node, type);
152
125
  }
153
- for (const to of chains.declarationsOf(property)) {
126
+ });
127
+ const partsOf = (type) => {
128
+ const parts = queries.constituents(type);
129
+ return parts === UNANSWERED ? [type] : parts;
130
+ };
131
+ for (const one of keyed) {
132
+ const literal = typeOf.get(one.other);
133
+ const objects = one.operands.map((operand) => typeOf.get(operand));
134
+ if (literal === UNANSWERED) {
135
+ const known = objects.filter((type) => type !== undefined && type !== UNANSWERED);
154
136
  found.push({
137
+ kind: "held",
155
138
  from: one.from,
156
- to,
157
- position,
158
- use: "read",
159
- resolution: "keyed",
160
- test: testFiles.has(position.path),
139
+ types: known.flatMap(partsOf),
140
+ ...(known.length < objects.length ? { members: true } : {}),
161
141
  });
142
+ continue;
143
+ }
144
+ const names = literal === undefined ? [] : literalNames(partsOf(literal));
145
+ if (names.length === 0) {
146
+ continue;
147
+ }
148
+ for (const object of objects) {
149
+ if (object === UNANSWERED) {
150
+ found.push({ kind: "held", from: one.from, names });
151
+ continue;
152
+ }
153
+ for (const part of object === undefined ? [] : partsOf(object)) {
154
+ const properties = queries.propertiesOf(part);
155
+ if (properties === UNANSWERED) {
156
+ found.push({ kind: "held", from: one.from, names });
157
+ continue;
158
+ }
159
+ for (const property of properties) {
160
+ if (!names.includes(property.name)) {
161
+ continue;
162
+ }
163
+ for (const to of chains.declarationsOf(property)) {
164
+ found.push({
165
+ kind: "named",
166
+ from: one.from,
167
+ to,
168
+ file: one.file,
169
+ at: one.other,
170
+ use: "read",
171
+ via: "keyed",
172
+ });
173
+ }
174
+ }
175
+ }
162
176
  }
163
177
  }
164
- }
165
- }
166
- }
167
- return found;
178
+ return found;
179
+ },
180
+ };
181
+ },
182
+ };
183
+ /** The text a string or number literal operand spells, where it is one. */
184
+ function literalText(node) {
185
+ const at = parenthesesRemoved(node);
186
+ return isStringLiteral(at) || isNumericLiteral(at) || isNoSubstitutionTemplateLiteral(at)
187
+ ? at.text
188
+ : undefined;
168
189
  }
@@ -4,7 +4,7 @@
4
4
  * and every dead declaration that falls with them, being dead only through them.
5
5
  */
6
6
  import { OUTSIDE } from "./graph.js";
7
- import { PRODUCT } from "./product.js";
7
+ import { PRODUCT } from "../product.js";
8
8
  /** The fewest digits a component identifier's number is written with. */
9
9
  const ID_DIGITS = 4;
10
10
  /** The identifier of the component at one place of the run's order, counted from one. */
@@ -1,9 +1,10 @@
1
1
  /**
2
- * One project's declarations, the references between them and the roots that seed
3
- * reachability, indexed for the sweep. The graph reads no file and asks the compiler
4
- * nothing, so a sweep over it is a function of the values it was built from, and the
5
- * inventory's order is the order every set a sweep answers reads in.
2
+ * The run's declarations, the references every configuration makes between them and the
3
+ * roots that seed reachability, indexed for the sweep. The graph reads no file and asks the
4
+ * compiler nothing, so a sweep over it is a function of the values it was built from, and
5
+ * the inventory's order is the order every set a sweep answers reads in.
6
6
  */
7
+ import { madeByTestCode } from "../tests/file-class.js";
7
8
  /** The position of a declaration the inventory does not hold. */
8
9
  export const OUTSIDE = -1;
9
10
  /**
@@ -66,42 +67,63 @@ export function swept(kind) {
66
67
  export function namesACaller(kind) {
67
68
  return NAMES_A_CALLER[kind];
68
69
  }
70
+ /** Adds one reference's count to the counts of the declaration it names. */
71
+ function count(counts, test, consumer) {
72
+ if (test) {
73
+ counts.test += 1;
74
+ counts.consumerTest += consumer ? 1 : 0;
75
+ }
76
+ else {
77
+ counts.production += 1;
78
+ }
79
+ }
69
80
  /**
70
- * Indexes one project's inventory, references and roots. `testFiles` are the paths the
81
+ * Indexes the run's inventory, references and roots. `testFiles` are the paths the
71
82
  * reference pass classified as test files, so a declaration one holds is a test
72
83
  * declaration. A reference to a declaration the inventory does not hold is dropped; one
73
84
  * from such a declaration still counts toward its target, because reference counting
74
85
  * asks whether any declaration names the target. A root naming nothing is dropped.
75
86
  */
76
- export function graphOf(symbols, references, roots, testFiles, supportFiles = [], ambient = new Set()) {
87
+ export function graphOf(symbols, references, roots, testFiles, supportFiles = [], ambient = new Set(), sources = {}) {
77
88
  const supports = new Set(supportFiles);
78
89
  const index = new Map(symbols.map((symbol, at) => [symbol.id, at]));
79
90
  const at = (id) => index.get(id) ?? OUTSIDE;
80
91
  const tests = new Set(testFiles);
92
+ const unjudged = sources.unjudged ?? new Set();
81
93
  const out = symbols.map(() => []);
82
- const made = symbols.map(() => ({ production: 0, test: 0 }));
94
+ const made = symbols.map(() => ({ production: 0, test: 0, consumerTest: 0 }));
95
+ const guessedMade = symbols.map(() => ({ production: 0, test: 0, consumerTest: 0 }));
83
96
  const consumed = [];
84
- for (const reference of references) {
97
+ references.forEach((reference, place) => {
85
98
  const to = at(reference.to);
86
99
  if (to === OUTSIDE) {
87
- continue;
100
+ return;
88
101
  }
102
+ const test = madeByTestCode(reference, supports);
103
+ const consumer = reference.consumer !== undefined;
104
+ const guessed = reference.guessed === true;
89
105
  const counts = made[to];
90
106
  if (counts !== undefined) {
91
- if (reference.test) {
92
- counts.test += 1;
93
- }
94
- else {
95
- counts.production += 1;
96
- }
107
+ count(counts, test, consumer);
97
108
  }
98
- const edge = { to, test: reference.test, evaluation: reference.use === "evaluation" };
99
- if (reference.consumer !== undefined || reference.unheld === true) {
109
+ const guesses = guessedMade[to];
110
+ if (guessed && guesses !== undefined) {
111
+ count(guesses, test, consumer);
112
+ }
113
+ const edge = {
114
+ to,
115
+ reference: place,
116
+ test,
117
+ guessed,
118
+ evaluation: reference.use === "evaluation",
119
+ consumer,
120
+ };
121
+ if (consumer || reference.unheld === true || unjudged.has(reference.from)) {
100
122
  consumed.push(edge);
101
- continue;
123
+ return;
102
124
  }
103
125
  out[at(reference.from)]?.push(edge);
104
- }
126
+ });
105
127
  const parent = symbols.map((symbol) => at(symbol.parent));
106
128
  const exportsOf = symbols.map(() => []);
107
129
  symbols.forEach((symbol, position) => {
@@ -114,13 +136,16 @@ export function graphOf(symbols, references, roots, testFiles, supportFiles = []
114
136
  for (const root of roots) {
115
137
  const position = at(root.id);
116
138
  if (position !== OUTSIDE) {
117
- rooted.push({ at: position, kind: root.kind });
139
+ rooted.push({ at: position, kind: root.kind, source: root.source });
118
140
  }
119
141
  }
120
142
  return {
121
143
  symbols,
122
144
  out,
123
145
  made,
146
+ guessedMade,
147
+ references,
148
+ configurations: sources.configurations ?? references.map(() => []),
124
149
  parent,
125
150
  test: symbols.map((symbol) => tests.has(symbol.position.path)),
126
151
  support: symbols.map((symbol) => supports.has(symbol.position.path)),
@@ -0,0 +1,99 @@
1
+ /**
2
+ * One run's projects as one matrix: every declaration any project judges, keyed by its
3
+ * position and carrying the configurations that hold it, and one graph over every
4
+ * configuration's references and roots, which the sweep judges once (matrix.4, matrix.5).
5
+ */
6
+ import { graphOf, swept } from "./graph.js";
7
+ import { byPosition } from "../position.js";
8
+ /**
9
+ * What makes one reference one reference, whichever configuration made it. A consumer's
10
+ * position is rendered against its own root, so the consumer is part of what it names.
11
+ */
12
+ export function referenceKey(reference) {
13
+ const { path, line, column } = reference.position;
14
+ return [
15
+ reference.consumer ?? "",
16
+ reference.from,
17
+ reference.to,
18
+ path,
19
+ String(line),
20
+ String(column),
21
+ reference.use,
22
+ ].join("\u0000");
23
+ }
24
+ /**
25
+ * Indexes what the passes answered for each configuration of one run, in the run's order.
26
+ * A declaration is keyed by its rendered position, with the fields of the first
27
+ * configuration that judges it; one in a file every holder holds only for its references
28
+ * is judged by none and is not in the union, and a reference it makes enters the union as a
29
+ * consumer's does. One reference at one position of a file several configurations compile
30
+ * is one reference; two one configuration makes are two.
31
+ */
32
+ export function matrixOf(per) {
33
+ const symbols = [];
34
+ const holders = new Map();
35
+ const references = [];
36
+ const makers = [];
37
+ const firstSeen = new Map();
38
+ const judged = new Set();
39
+ const unjudged = new Set();
40
+ const dependents = new Map();
41
+ per.forEach((one, index) => {
42
+ for (const [id, after] of one.dependents ?? []) {
43
+ if (!dependents.has(id)) {
44
+ dependents.set(id, after);
45
+ }
46
+ }
47
+ const referenceOnly = new Set(one.referenceOnly);
48
+ for (const symbol of one.symbols) {
49
+ const held = holders.get(symbol.id);
50
+ if (held === undefined) {
51
+ holders.set(symbol.id, [one.configuration]);
52
+ }
53
+ else if (!held.includes(one.configuration)) {
54
+ held.push(one.configuration);
55
+ }
56
+ if (referenceOnly.has(symbol.position.path)) {
57
+ if (swept(symbol.kind)) {
58
+ unjudged.add(symbol.id);
59
+ }
60
+ }
61
+ else if (!judged.has(symbol.id)) {
62
+ judged.add(symbol.id);
63
+ symbols.push(symbol);
64
+ }
65
+ }
66
+ for (const reference of one.references) {
67
+ const key = referenceKey(reference);
68
+ const seen = firstSeen.get(key);
69
+ if (seen !== undefined && seen.index !== index) {
70
+ for (const place of seen.places) {
71
+ const making = makers[place];
72
+ if (making !== undefined && !making.includes(one.configuration)) {
73
+ making.push(one.configuration);
74
+ }
75
+ }
76
+ continue;
77
+ }
78
+ const places = seen?.places ?? [];
79
+ if (seen === undefined) {
80
+ firstSeen.set(key, { index, places });
81
+ }
82
+ places.push(references.length);
83
+ references.push(reference);
84
+ makers.push([one.configuration]);
85
+ }
86
+ });
87
+ for (const id of judged) {
88
+ unjudged.delete(id);
89
+ }
90
+ symbols.sort((a, b) => byPosition(a.position, b.position));
91
+ const testUsed = new Set(per.flatMap((one) => one.testUsedFiles ?? []));
92
+ return {
93
+ configurations: per.map((one) => one.configuration),
94
+ union: graphOf(symbols, references, per.flatMap((one) => one.roots), [...new Set(per.flatMap((one) => one.testFiles))], [...new Set(per.flatMap((one) => one.supportFiles ?? []))], new Set(per.flatMap((one) => [...(one.ambient ?? [])])), { unjudged, configurations: makers }),
95
+ heldIn: symbols.map((symbol) => holders.get(symbol.id) ?? []),
96
+ testUsed: symbols.map((symbol) => testUsed.has(symbol.position.path)),
97
+ dependents,
98
+ };
99
+ }