@cplieger/deadset-ts 8.0.0-dev.16 → 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 (131) 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 +58 -22
  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/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/report.generated.js +6 -2
  14. package/dist/src/dependencies.js +25 -29
  15. package/dist/src/destructuring.js +1 -19
  16. package/dist/src/element-access.js +179 -164
  17. package/dist/src/entries/build-scripts.js +2 -2
  18. package/dist/src/entries/configuration-files.js +3 -7
  19. package/dist/src/entries/conventions.js +2 -2
  20. package/dist/src/entries/html-entries.js +7 -10
  21. package/dist/src/entries/mdx-entries.js +2 -2
  22. package/dist/src/entries/static-module.js +2 -1
  23. package/dist/src/facts.js +4 -4
  24. package/dist/src/findings/completion.js +40 -4
  25. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  26. package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
  27. package/dist/src/framework-lifecycle.js +41 -14
  28. package/dist/src/graph.js +12 -4
  29. package/dist/src/import-bindings.js +73 -28
  30. package/dist/src/index/sites.js +557 -0
  31. package/dist/src/indexed-access.js +523 -478
  32. package/dist/src/interface-satisfaction.js +1 -1
  33. package/dist/src/keyof-comparisons.js +124 -103
  34. package/dist/src/load/expected-module.js +2 -2
  35. package/dist/src/load/specifiers.js +2 -4
  36. package/dist/src/load/synthesize.js +5 -0
  37. package/dist/src/load/workspace-resolution.js +2 -2
  38. package/dist/src/matrix.js +6 -4
  39. package/dist/src/module-declarations.js +70 -85
  40. package/dist/src/overrides.js +134 -112
  41. package/dist/src/paths.js +46 -7
  42. package/dist/src/platform-calls.js +2 -2
  43. package/dist/src/platform-symbols.js +13 -19
  44. package/dist/src/references/read-rule.js +1 -0
  45. package/dist/src/references/read-rules.js +48 -0
  46. package/dist/src/references/sink.js +151 -0
  47. package/dist/src/references/sources.js +450 -0
  48. package/dist/src/references.js +110 -1285
  49. package/dist/src/roots/configured-declarations.js +3 -0
  50. package/dist/src/roots/root-set.js +1 -1
  51. package/dist/src/roots/unseen-loads.js +38 -10
  52. package/dist/src/serialization-contract.js +8 -3
  53. package/dist/src/stores.js +4 -2
  54. package/dist/src/sweep.js +25 -13
  55. package/dist/src/symbol-keys.js +89 -83
  56. package/dist/src/tests/file-class.js +76 -0
  57. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  58. package/dist/src/{test-support.js → tests/support.js} +2 -11
  59. package/dist/src/type-parameter-reads.js +178 -139
  60. package/dist/src/unimplemented-callees.js +262 -242
  61. package/dist/src/verbs/analyze.js +3 -3
  62. package/dist/src/verbs/explain.js +50 -28
  63. package/dist/src/verbs/print-retained.js +2 -2
  64. package/jsr.json +1 -1
  65. package/package.json +2 -2
  66. package/src/alias-chain.ts +0 -7
  67. package/src/amd.ts +130 -0
  68. package/src/analysis.ts +86 -23
  69. package/src/augmentations.ts +41 -27
  70. package/src/bindings.ts +460 -0
  71. package/src/calls.ts +25 -7
  72. package/src/commands/tools.ts +5 -11
  73. package/src/component-files.ts +4 -2
  74. package/src/conformance-record.ts +1 -1
  75. package/src/consumers.ts +8 -11
  76. package/src/contextual-uses.ts +2 -22
  77. package/src/contract/config.generated.ts +4 -1
  78. package/src/contract/report.generated.ts +6 -2
  79. package/src/dependencies.ts +27 -28
  80. package/src/destructuring.ts +1 -31
  81. package/src/element-access.ts +188 -182
  82. package/src/entries/build-scripts.ts +2 -2
  83. package/src/entries/configuration-files.ts +11 -8
  84. package/src/entries/conventions.ts +2 -1
  85. package/src/entries/html-entries.ts +15 -14
  86. package/src/entries/mdx-entries.ts +2 -2
  87. package/src/entries/static-module.ts +2 -1
  88. package/src/facts.ts +5 -3
  89. package/src/findings/boundary.ts +2 -0
  90. package/src/findings/completion.ts +43 -4
  91. package/src/findings/emitter.ts +1 -1
  92. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  93. package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
  94. package/src/framework-lifecycle.ts +42 -14
  95. package/src/graph.ts +24 -6
  96. package/src/import-bindings.ts +98 -29
  97. package/src/index/sites.ts +781 -0
  98. package/src/indexed-access.ts +570 -527
  99. package/src/interface-satisfaction.ts +1 -1
  100. package/src/keyof-comparisons.ts +131 -116
  101. package/src/load/expected-module.ts +8 -2
  102. package/src/load/specifiers.ts +8 -5
  103. package/src/load/synthesize.ts +6 -0
  104. package/src/load/workspace-resolution.ts +9 -2
  105. package/src/matrix.ts +6 -3
  106. package/src/module-declarations.ts +74 -98
  107. package/src/overrides.ts +162 -134
  108. package/src/paths.ts +54 -8
  109. package/src/platform-calls.ts +3 -3
  110. package/src/platform-symbols.ts +11 -27
  111. package/src/references/read-rule.ts +60 -0
  112. package/src/references/read-rules.ts +55 -0
  113. package/src/references/sink.ts +213 -0
  114. package/src/references/sources.ts +632 -0
  115. package/src/references.ts +168 -1733
  116. package/src/report.ts +1 -1
  117. package/src/roots/configured-declarations.ts +3 -0
  118. package/src/roots/root-set.ts +1 -1
  119. package/src/roots/unseen-loads.ts +49 -11
  120. package/src/serialization-contract.ts +8 -3
  121. package/src/stores.ts +13 -2
  122. package/src/sweep.ts +37 -14
  123. package/src/symbol-keys.ts +102 -102
  124. package/src/tests/file-class.ts +118 -0
  125. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  126. package/src/{test-support.ts → tests/support.ts} +9 -20
  127. package/src/type-parameter-reads.ts +187 -158
  128. package/src/unimplemented-callees.ts +297 -280
  129. package/src/verbs/analyze.ts +3 -2
  130. package/src/verbs/explain.ts +74 -31
  131. package/src/verbs/print-retained.ts +2 -2
@@ -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";
@@ -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
  }
@@ -2,7 +2,7 @@ import { isNoSubstitutionTemplateLiteral, isStringLiteral, } from "@typescript/n
2
2
  import { packageOfSpecifier } from "../dependencies.js";
3
3
  import { MANIFEST } from "../fs/manifest.js";
4
4
  import { entriesOf } from "../fs/tree.js";
5
- import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "../paths.js";
5
+ import { cannotStartPackageName, dirnamePath, joinPath, relativePath, resolvePath, } from "../paths.js";
6
6
  import { dependencySpecifiers, installedPackage, manifestsAbove } from "./module-lookup.js";
7
7
  /** The string literal holding one offset, innermost first, a doc comment's included. */
8
8
  function literalAt(file, offset) {
@@ -77,7 +77,7 @@ function declaringManifest(host, dir, targetRoot, name) {
77
77
  function expectedModule(context, importer, specifier) {
78
78
  const { host, targetRoot } = context;
79
79
  const fix = "run the generator or the build that writes it";
80
- if (specifier.startsWith(".") || isAbsolutePath(specifier)) {
80
+ if (cannotStartPackageName(specifier)) {
81
81
  return provided(host, resolvePath(dirnamePath(importer), specifier), false)
82
82
  ? undefined
83
83
  : `which names no file of the target: ${fix}`;
@@ -9,12 +9,10 @@ import { isBinaryExpression, isCallExpression, isParenthesizedExpression, isStri
9
9
  import { packageOfSpecifier } from "../dependencies.js";
10
10
  import { readManifestAt } from "../fs/manifest.js";
11
11
  import { JAVASCRIPT_FILE } from "../javascript-file.js";
12
- import { ancestors, dirnamePath, relativePath, resolvePath } from "../paths.js";
12
+ import { ancestors, dirnamePath, isRelativeSpecifier, relativePath, resolvePath, } from "../paths.js";
13
13
  import { renderPosition } from "../position.js";
14
14
  import { batched } from "../session.js";
15
15
  import { dependencySpecifiers } from "./module-lookup.js";
16
- /** A relative specifier: `.`, `..`, or one starting `./` or `../`. */
17
- const RELATIVE = /^\.\.?(?:\/|$)/u;
18
16
  /**
19
17
  * Whether one specifier is loaded optionally: written inside a `try` block, or as a call whose
20
18
  * result an `||` or a `??` falls back from, so the program is written for its absence.
@@ -87,7 +85,7 @@ export function readSpecifiers(engine, host, project, { checked, root, outputs }
87
85
  const resolved = (answers[at]?.resolvedFileName ?? "") !== "";
88
86
  const specifier = usage.node.text;
89
87
  const name = packageOfSpecifier(specifier);
90
- if (RELATIVE.test(specifier)) {
88
+ if (isRelativeSpecifier(specifier)) {
91
89
  const named = resolvePath(dirnamePath(usage.file.fileName), specifier);
92
90
  if (!resolved && !outputs.some((dir) => relativePath(dir, named) !== undefined)) {
93
91
  unresolved.push({
@@ -89,6 +89,11 @@ const RUNTIME_OPTIONS = {
89
89
  bundler: { module: "preserve", moduleResolution: "bundler" },
90
90
  node: { module: "nodenext", moduleResolution: "nodenext" },
91
91
  };
92
+ /** Whether one configuration is a program the run formed to resolve its modules as Node does. */
93
+ export function formedForNode(configuration) {
94
+ const options = configuration.written?.["compilerOptions"];
95
+ return isRecord(options) && options["moduleResolution"] === RUNTIME_OPTIONS.node.moduleResolution;
96
+ }
92
97
  /** The manifest members that say a package is written for Node. */
93
98
  const NODE_MEMBERS = ["main", "exports", "bin", "type"];
94
99
  /**
@@ -8,7 +8,7 @@
8
8
  import { emittedFrom } from "./emit-map.js";
9
9
  import { packageOfSpecifier } from "../dependencies.js";
10
10
  import { gitIgnored } from "../git-ignore.js";
11
- import { ancestors, dirnamePath, joinPath, relativePath, resolvePath } from "../paths.js";
11
+ import { ancestors, cannotStartPackageName, dirnamePath, joinPath, relativePath, resolvePath, } from "../paths.js";
12
12
  import { satisfies } from "../version-range.js";
13
13
  import { isRecord } from "../json-document.js";
14
14
  import { MANIFEST } from "../fs/manifest.js";
@@ -99,7 +99,7 @@ function dependsOn(specifier, member, fromDir) {
99
99
  const inRange = (range) => member.version !== undefined && satisfies(member.version, range);
100
100
  if (specifier.startsWith("workspace:")) {
101
101
  const rest = specifier.slice("workspace:".length);
102
- if (rest.startsWith(".") || rest.startsWith("/")) {
102
+ if (cannotStartPackageName(rest)) {
103
103
  return atPath(rest);
104
104
  }
105
105
  const alias = /^((?:@[^/@]+\/)?[^/@]+)@(.*)$/u.exec(rest);
@@ -6,7 +6,8 @@
6
6
  import { componentsOf } from "./components.js";
7
7
  import { graphOf, OUTSIDE } from "./graph.js";
8
8
  import { byPosition } from "./position.js";
9
- import { reachedWithTheTests, sweep, } from "./sweep.js";
9
+ import { edgeWithheldBy } from "./tests/file-class.js";
10
+ import { reachedWithTheTests, splitRefs, sweep, } from "./sweep.js";
10
11
  /**
11
12
  * What makes one reference one reference, whichever configuration made it. A consumer's
12
13
  * position is rendered against its own root, so the consumer is part of what it names.
@@ -133,12 +134,13 @@ export function sweepMatrix(matrix, input) {
133
134
  }
134
135
  const held = matrix.heldIn[at] ?? [];
135
136
  const admitted = admittedAt(at);
136
- const testRefs = union.made[at]?.test ?? 0;
137
+ const split = splitRefs(union.made[at], input.mode);
138
+ const testRefs = split.test;
137
139
  const heldByTests = heldBy[union.parent[at] ?? OUTSIDE] ??
138
140
  (union.support[at] === true && testRefs > 0 && !admitted ? symbol.id : undefined);
139
141
  const unreferenced = union.test[at] === true && !admitted && testReached[at] !== true;
140
142
  heldBy[at] = heldByTests;
141
- const productionRefs = union.made[at]?.production ?? 0;
143
+ const productionRefs = split.production;
142
144
  const counted = all.every((candidate) => candidate.relation === "reference-counting");
143
145
  candidates.push({
144
146
  id: symbol.id,
@@ -181,7 +183,7 @@ function compiledReferences(matrix, dead, mode) {
181
183
  }
182
184
  for (const edge of edges) {
183
185
  const to = graph.symbols[edge.to]?.id ?? "";
184
- if ((!mode.production || !edge.test) && dead.has(to) && !kept.has(to)) {
186
+ if (!edgeWithheldBy(mode, edge) && dead.has(to) && !kept.has(to)) {
185
187
  kept.add(to);
186
188
  changed = true;
187
189
  }
@@ -5,11 +5,12 @@
5
5
  * to the same module. One that names it by name or pattern also reads the members it
6
6
  * imports, whatever file the specifier resolves to, as a compiler reading no such file does.
7
7
  */
8
- import { isCallExpression, isExportDeclaration, isExternalModuleReference, isImportDeclaration, isImportEqualsDeclaration, isImportTypeNode, isLiteralTypeNode, isModuleDeclaration, isNamespaceExport, isNamespaceImport, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
8
+ import { isCallExpression, isExportDeclaration, isExternalModuleReference, isImportDeclaration, isImportEqualsDeclaration, isImportTypeNode, isLiteralTypeNode, isModuleDeclaration, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
9
+ import { namesImported } from "./import-bindings.js";
9
10
  import { nodeKey } from "./inventory.js";
10
- import { renderPosition } from "./position.js";
11
11
  import { UNANSWERED } from "./query.js";
12
12
  import { aliasFragment, nameComponent } from "./ref.js";
13
+ import { isPathSpecifier } from "./paths.js";
13
14
  /** Whether a node is a specifier's text: a string literal or a template with no substitution. */
14
15
  function literalText(node) {
15
16
  return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
@@ -36,32 +37,12 @@ function specifierOf(node) {
36
37
  }
37
38
  return undefined;
38
39
  }
39
- /**
40
- * The export names an import or a re-export reads from the module it names: none for a
41
- * namespace, a star or a bare form.
42
- */
43
- export function namesImported(node) {
44
- if (isImportDeclaration(node)) {
45
- const clause = node.importClause;
46
- const bindings = clause?.namedBindings;
47
- return [
48
- ...(clause?.name === undefined ? [] : ["default"]),
49
- ...(bindings === undefined || isNamespaceImport(bindings)
50
- ? []
51
- : bindings.elements.map((one) => (one.propertyName ?? one.name).text)),
52
- ];
53
- }
54
- const clause = isExportDeclaration(node) ? node.exportClause : undefined;
55
- return clause === undefined || isNamespaceExport(clause)
56
- ? []
57
- : clause.elements.map((one) => (one.propertyName ?? one.name).text);
58
- }
59
40
  /**
60
41
  * Whether a specifier matches a declaration's name, the name's one `*` standing for any run.
61
42
  * A relative name is resolved against its own file, so no specifier names it by its text.
62
43
  */
63
44
  function namesModule(specifier, name) {
64
- if (/^(?:\.{1,2}(?:\/|$)|\/)/u.test(name)) {
45
+ if (isPathSpecifier(name)) {
65
46
  return false;
66
47
  }
67
48
  const star = name.indexOf("*");
@@ -107,67 +88,71 @@ function namedBy(held) {
107
88
  });
108
89
  }
109
90
  /**
110
- * The references the imports of one project's own files make to the module declarations
111
- * they name, each at the import's specifier.
91
+ * The reads the imports of a side's files make of the module declarations of the target they
92
+ * name, each at the import's specifier. A specifier the checker leaves unanswered still names
93
+ * a declaration by its name or pattern.
112
94
  */
113
- export function moduleDeclarationReferences(project, held, root, testFiles) {
114
- const imports = [];
115
- const declared = [];
116
- for (const file of project.ownSourceFiles()) {
117
- const fileId = held.declarations.get(nodeKey(file, file));
118
- if (fileId === undefined) {
119
- continue;
120
- }
121
- const visit = (node, from) => {
122
- const own = held.declarations.get(nodeKey(file, node));
123
- if (isModuleDeclaration(node) && literalText(node.name) && own !== undefined) {
124
- declared.push({ id: own, name: node.name });
125
- }
126
- const specifier = specifierOf(node);
127
- if (specifier !== undefined) {
128
- imports.push({ file, from, specifier, names: namesImported(node) });
129
- }
130
- node.forEachChild((child) => {
131
- visit(child, own ?? from);
132
- });
133
- };
134
- file.forEachChild((child) => {
135
- visit(child, fileId);
136
- });
137
- }
138
- if (declared.length === 0 || imports.length === 0) {
139
- return [];
140
- }
141
- const asked = [...declared.map((one) => one.name), ...imports.map((one) => one.specifier)];
142
- const answers = project.symbolsAt(asked.map((node) => project.handle(node)));
143
- const moduleOf = (at) => {
144
- const symbol = answers[at];
145
- return symbol === undefined || symbol === UNANSWERED ? undefined : symbol.id;
146
- };
147
- const named = namedBy(held);
148
- const found = [];
149
- imports.forEach((one, index) => {
150
- const resolved = moduleOf(declared.length + index);
151
- const position = renderPosition(one.file, root, one.specifier.getStart());
152
- const read = (to, resolution, test) => {
153
- found.push({ from: one.from, to, position, use: "read", resolution, test });
154
- };
155
- const test = testFiles.has(position.path);
156
- const same = new Set(declared
157
- .filter((_, at) => resolved !== undefined && resolved === moduleOf(at))
158
- .map((declaration) => declaration.id));
159
- for (const id of same) {
160
- read(id, "batch", test);
161
- }
162
- for (const declaration of named(one.specifier.text, one.names)) {
163
- // A wildcard declaration is how the compiler types the file a matching specifier
164
- // names, so the import uses it whoever writes it, a test included.
165
- if (declaration.wildcard || !same.has(declaration.id)) {
166
- for (const to of [declaration.id, ...declaration.members]) {
167
- read(to, "syntax", test && !declaration.wildcard);
95
+ export const moduleDeclarations = {
96
+ start() {
97
+ const imports = [];
98
+ return {
99
+ visit(node, { file, from }) {
100
+ const specifier = specifierOf(node);
101
+ if (specifier !== undefined) {
102
+ imports.push({ file, from, specifier, names: namesImported(node) });
168
103
  }
169
- }
170
- }
171
- });
172
- return found;
173
- }
104
+ },
105
+ reads({ project, held }) {
106
+ const found = [];
107
+ // A module declaration whose name is a string is written at a file's top level.
108
+ const declared = project.ownSourceFiles().flatMap((file) => file.statements.flatMap((statement) => {
109
+ const id = held.declarations.get(nodeKey(file, statement));
110
+ return isModuleDeclaration(statement) && literalText(statement.name) && id !== undefined
111
+ ? [{ id, name: statement.name }]
112
+ : [];
113
+ }));
114
+ if (declared.length === 0 || imports.length === 0) {
115
+ return found;
116
+ }
117
+ const asked = [...declared.map((one) => one.name), ...imports.map((one) => one.specifier)];
118
+ const answers = project.symbolsAt(asked.map((node) => project.handle(node)));
119
+ const moduleOf = (at) => {
120
+ const symbol = answers[at];
121
+ return symbol === undefined || symbol === UNANSWERED ? undefined : symbol.id;
122
+ };
123
+ const named = namedBy(held);
124
+ imports.forEach((one, index) => {
125
+ const resolved = moduleOf(declared.length + index);
126
+ const read = (to, via, production) => {
127
+ found.push({
128
+ kind: "named",
129
+ from: one.from,
130
+ to,
131
+ file: one.file,
132
+ at: one.specifier,
133
+ use: "read",
134
+ via,
135
+ ...(production ? { production: true } : {}),
136
+ });
137
+ };
138
+ const same = new Set(declared
139
+ .filter((_, at) => resolved !== undefined && resolved === moduleOf(at))
140
+ .map((declaration) => declaration.id));
141
+ for (const id of same) {
142
+ read(id, "batch", false);
143
+ }
144
+ for (const declaration of named(one.specifier.text, one.names)) {
145
+ // A wildcard declaration is how the compiler types the file a matching specifier
146
+ // names, so the import uses it whoever writes it, a test included.
147
+ if (declaration.wildcard || !same.has(declaration.id)) {
148
+ for (const to of [declaration.id, ...declaration.members]) {
149
+ read(to, "syntax", declaration.wildcard);
150
+ }
151
+ }
152
+ }
153
+ });
154
+ return found;
155
+ },
156
+ };
157
+ },
158
+ };