@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.
- package/dist/src/alias-chain.js +0 -5
- package/dist/src/amd.js +81 -0
- package/dist/src/analysis.js +58 -22
- package/dist/src/augmentations.js +23 -24
- package/dist/src/bindings.js +382 -0
- package/dist/src/calls.js +25 -8
- package/dist/src/commands/tools.js +5 -9
- package/dist/src/component-files.js +3 -2
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +6 -9
- package/dist/src/contextual-uses.js +1 -17
- package/dist/src/contract/config.generated.js +4 -1
- package/dist/src/contract/report.generated.js +6 -2
- package/dist/src/dependencies.js +25 -29
- package/dist/src/destructuring.js +1 -19
- package/dist/src/element-access.js +179 -164
- package/dist/src/entries/build-scripts.js +2 -2
- package/dist/src/entries/configuration-files.js +3 -7
- package/dist/src/entries/conventions.js +2 -2
- package/dist/src/entries/html-entries.js +7 -10
- package/dist/src/entries/mdx-entries.js +2 -2
- package/dist/src/entries/static-module.js +2 -1
- package/dist/src/facts.js +4 -4
- package/dist/src/findings/completion.js +40 -4
- package/dist/src/{implementations.js → findings/implementations.js} +3 -3
- package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
- package/dist/src/framework-lifecycle.js +41 -14
- package/dist/src/graph.js +12 -4
- package/dist/src/import-bindings.js +73 -28
- package/dist/src/index/sites.js +557 -0
- package/dist/src/indexed-access.js +523 -478
- package/dist/src/interface-satisfaction.js +1 -1
- package/dist/src/keyof-comparisons.js +124 -103
- package/dist/src/load/expected-module.js +2 -2
- package/dist/src/load/specifiers.js +2 -4
- package/dist/src/load/synthesize.js +5 -0
- package/dist/src/load/workspace-resolution.js +2 -2
- package/dist/src/matrix.js +6 -4
- package/dist/src/module-declarations.js +70 -85
- package/dist/src/overrides.js +134 -112
- package/dist/src/paths.js +46 -7
- package/dist/src/platform-calls.js +2 -2
- package/dist/src/platform-symbols.js +13 -19
- package/dist/src/references/read-rule.js +1 -0
- package/dist/src/references/read-rules.js +48 -0
- package/dist/src/references/sink.js +151 -0
- package/dist/src/references/sources.js +450 -0
- package/dist/src/references.js +110 -1285
- package/dist/src/roots/configured-declarations.js +3 -0
- package/dist/src/roots/root-set.js +1 -1
- package/dist/src/roots/unseen-loads.js +38 -10
- package/dist/src/serialization-contract.js +8 -3
- package/dist/src/stores.js +4 -2
- package/dist/src/sweep.js +25 -13
- package/dist/src/symbol-keys.js +89 -83
- package/dist/src/tests/file-class.js +76 -0
- package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
- package/dist/src/{test-support.js → tests/support.js} +2 -11
- package/dist/src/type-parameter-reads.js +178 -139
- package/dist/src/unimplemented-callees.js +262 -242
- package/dist/src/verbs/analyze.js +3 -3
- package/dist/src/verbs/explain.js +50 -28
- package/dist/src/verbs/print-retained.js +2 -2
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/alias-chain.ts +0 -7
- package/src/amd.ts +130 -0
- package/src/analysis.ts +86 -23
- package/src/augmentations.ts +41 -27
- package/src/bindings.ts +460 -0
- package/src/calls.ts +25 -7
- package/src/commands/tools.ts +5 -11
- package/src/component-files.ts +4 -2
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +8 -11
- package/src/contextual-uses.ts +2 -22
- package/src/contract/config.generated.ts +4 -1
- package/src/contract/report.generated.ts +6 -2
- package/src/dependencies.ts +27 -28
- package/src/destructuring.ts +1 -31
- package/src/element-access.ts +188 -182
- package/src/entries/build-scripts.ts +2 -2
- package/src/entries/configuration-files.ts +11 -8
- package/src/entries/conventions.ts +2 -1
- package/src/entries/html-entries.ts +15 -14
- package/src/entries/mdx-entries.ts +2 -2
- package/src/entries/static-module.ts +2 -1
- package/src/facts.ts +5 -3
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +43 -4
- package/src/findings/emitter.ts +1 -1
- package/src/{implementations.ts → findings/implementations.ts} +4 -4
- package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
- package/src/framework-lifecycle.ts +42 -14
- package/src/graph.ts +24 -6
- package/src/import-bindings.ts +98 -29
- package/src/index/sites.ts +781 -0
- package/src/indexed-access.ts +570 -527
- package/src/interface-satisfaction.ts +1 -1
- package/src/keyof-comparisons.ts +131 -116
- package/src/load/expected-module.ts +8 -2
- package/src/load/specifiers.ts +8 -5
- package/src/load/synthesize.ts +6 -0
- package/src/load/workspace-resolution.ts +9 -2
- package/src/matrix.ts +6 -3
- package/src/module-declarations.ts +74 -98
- package/src/overrides.ts +162 -134
- package/src/paths.ts +54 -8
- package/src/platform-calls.ts +3 -3
- package/src/platform-symbols.ts +11 -27
- package/src/references/read-rule.ts +60 -0
- package/src/references/read-rules.ts +55 -0
- package/src/references/sink.ts +213 -0
- package/src/references/sources.ts +632 -0
- package/src/references.ts +168 -1733
- package/src/report.ts +1 -1
- package/src/roots/configured-declarations.ts +3 -0
- package/src/roots/root-set.ts +1 -1
- package/src/roots/unseen-loads.ts +49 -11
- package/src/serialization-contract.ts +8 -3
- package/src/stores.ts +13 -2
- package/src/sweep.ts +37 -14
- package/src/symbol-keys.ts +102 -102
- package/src/tests/file-class.ts +118 -0
- package/src/{outside-tests.ts → tests/outside.ts} +16 -10
- package/src/{test-support.ts → tests/support.ts} +9 -20
- package/src/type-parameter-reads.ts +187 -158
- package/src/unimplemented-callees.ts +297 -280
- package/src/verbs/analyze.ts +3 -2
- package/src/verbs/explain.ts +74 -31
- package/src/verbs/print-retained.ts +2 -2
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
} from "@typescript/native/ast";
|
|
30
30
|
import { SymbolFlags, type Type } from "@typescript/native/sync";
|
|
31
31
|
import { aliasChains } from "./alias-chain.ts";
|
|
32
|
-
import { emptyImplementation } from "./implementations.ts";
|
|
32
|
+
import { emptyImplementation } from "./findings/implementations.ts";
|
|
33
33
|
import type { DetectorInput, Evidence } from "./exempt.ts";
|
|
34
34
|
import type { Reference } from "./references.ts";
|
|
35
35
|
import type { Exemption } from "./sweep.ts";
|
package/src/keyof-comparisons.ts
CHANGED
|
@@ -10,7 +10,10 @@ import {
|
|
|
10
10
|
isCaseClause,
|
|
11
11
|
isElementAccessExpression,
|
|
12
12
|
isIdentifier,
|
|
13
|
+
isNoSubstitutionTemplateLiteral,
|
|
14
|
+
isNumericLiteral,
|
|
13
15
|
isPropertyAccessExpression,
|
|
16
|
+
isStringLiteral,
|
|
14
17
|
isSwitchStatement,
|
|
15
18
|
isTypeOperatorNode,
|
|
16
19
|
SyntaxKind,
|
|
@@ -20,12 +23,8 @@ import {
|
|
|
20
23
|
type TypeNode,
|
|
21
24
|
} from "@typescript/native/ast";
|
|
22
25
|
import { SymbolFlags, type Type } from "@typescript/native/sync";
|
|
23
|
-
import type { AliasChains } from "./alias-chain.ts";
|
|
24
|
-
import { nodeKey, type Inventory } from "./inventory.ts";
|
|
25
|
-
import { renderPosition } from "./position.ts";
|
|
26
26
|
import { UNANSWERED } from "./query.ts";
|
|
27
|
-
import type {
|
|
28
|
-
import type { ProjectView } from "./session.ts";
|
|
27
|
+
import type { Read, ReadRule } from "./references/read-rule.ts";
|
|
29
28
|
import { parenthesesRemoved } from "./unwrap.ts";
|
|
30
29
|
|
|
31
30
|
/** The operators that compare their operands for equality. */
|
|
@@ -83,125 +82,141 @@ function literalNames(parts: readonly Type[]): readonly string[] {
|
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
/**
|
|
86
|
-
* The
|
|
87
|
-
*
|
|
85
|
+
* The reads the key comparisons of a side's files make of the members their literal side
|
|
86
|
+
* names, each made by the declaration holding the comparison. A question the checker leaves
|
|
87
|
+
* unanswered holds what its answer could have read.
|
|
88
88
|
*/
|
|
89
|
-
export
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
89
|
+
export const keyofComparisons: ReadRule = {
|
|
90
|
+
start() {
|
|
91
|
+
const comparisons: Comparison[] = [];
|
|
92
|
+
return {
|
|
93
|
+
visit(node, { file, from }) {
|
|
94
|
+
if (isBinaryExpression(node) && EQUALITIES.has(node.operatorToken.kind)) {
|
|
95
|
+
comparisons.push({ file, from, keyed: node.left, other: node.right });
|
|
96
|
+
comparisons.push({ file, from, keyed: node.right, other: node.left });
|
|
97
|
+
}
|
|
98
|
+
if (isSwitchStatement(node)) {
|
|
99
|
+
for (const clause of node.caseBlock.clauses) {
|
|
100
|
+
if (isCaseClause(clause)) {
|
|
101
|
+
comparisons.push({ file, from, keyed: node.expression, other: clause.expression });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
reads({ project, chains }) {
|
|
107
|
+
const queries = project.queries;
|
|
108
|
+
const found: Read[] = [];
|
|
109
|
+
const named = comparisons.flatMap((one) => {
|
|
110
|
+
const name = nameOf(one.keyed);
|
|
111
|
+
return name === undefined || isElementAccessExpression(parenthesesRemoved(one.other))
|
|
112
|
+
? []
|
|
113
|
+
: [{ ...one, name }];
|
|
114
|
+
});
|
|
115
|
+
if (named.length === 0) {
|
|
116
|
+
return found;
|
|
117
|
+
}
|
|
118
|
+
// The operand's own declaration states the key type, which the checker resolves to
|
|
119
|
+
// the literals it admits and so no longer names the type the key indexes.
|
|
120
|
+
const symbols = queries.symbolsAt(named.map((one) => one.name));
|
|
121
|
+
const keyed = named.flatMap((one, at) => {
|
|
122
|
+
const asked = symbols[at];
|
|
123
|
+
const symbol =
|
|
124
|
+
asked === UNANSWERED || asked === undefined || (asked.flags & SymbolFlags.Alias) === 0
|
|
125
|
+
? asked
|
|
126
|
+
: queries.aliased(asked);
|
|
127
|
+
if (symbol === UNANSWERED) {
|
|
128
|
+
const text = literalText(one.other);
|
|
129
|
+
found.push({
|
|
130
|
+
kind: "held",
|
|
131
|
+
from: one.from,
|
|
132
|
+
...(text === undefined ? {} : { names: [text] }),
|
|
117
133
|
});
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
if (symbol === undefined) {
|
|
137
|
+
return [];
|
|
118
138
|
}
|
|
139
|
+
const operands = symbol.declarations.flatMap((handle) => {
|
|
140
|
+
const declared = (
|
|
141
|
+
project.declarationAt(handle)?.node as { readonly type?: TypeNode } | undefined
|
|
142
|
+
)?.type;
|
|
143
|
+
return declared === undefined ? [] : keyofOperands(declared);
|
|
144
|
+
});
|
|
145
|
+
return operands.length === 0 ? [] : [{ ...one, operands }];
|
|
146
|
+
});
|
|
147
|
+
if (keyed.length === 0) {
|
|
148
|
+
return found;
|
|
119
149
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
const named = comparisons.flatMap((one) => {
|
|
130
|
-
const name = nameOf(one.keyed);
|
|
131
|
-
return name === undefined || isElementAccessExpression(parenthesesRemoved(one.other))
|
|
132
|
-
? []
|
|
133
|
-
: [{ ...one, name }];
|
|
134
|
-
});
|
|
135
|
-
if (named.length === 0) {
|
|
136
|
-
return [];
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// The operand's own declaration states the key type, which the checker resolves to
|
|
140
|
-
// the literals it admits and so no longer names the type the key indexes.
|
|
141
|
-
const symbols = queries.symbolsAt(named.map((one) => one.name));
|
|
142
|
-
const keyed = named.flatMap((one, at) => {
|
|
143
|
-
const asked = symbols[at];
|
|
144
|
-
const symbol =
|
|
145
|
-
asked === UNANSWERED || asked === undefined || (asked.flags & SymbolFlags.Alias) === 0
|
|
146
|
-
? asked
|
|
147
|
-
: queries.aliased(asked);
|
|
148
|
-
if (symbol === UNANSWERED || symbol === undefined) {
|
|
149
|
-
return [];
|
|
150
|
-
}
|
|
151
|
-
const operands = symbol.declarations.flatMap((handle) => {
|
|
152
|
-
const declared = (
|
|
153
|
-
project.declarationAt(handle)?.node as { readonly type?: TypeNode } | undefined
|
|
154
|
-
)?.type;
|
|
155
|
-
return declared === undefined ? [] : keyofOperands(declared);
|
|
156
|
-
});
|
|
157
|
-
return operands.length === 0 ? [] : [{ ...one, operands }];
|
|
158
|
-
});
|
|
159
|
-
if (keyed.length === 0) {
|
|
160
|
-
return [];
|
|
161
|
-
}
|
|
162
|
-
const nodes = keyed.flatMap((one) => [one.other, ...one.operands]);
|
|
163
|
-
const types = queries.typesAt(nodes);
|
|
164
|
-
const typeOf = new Map<Node, Type>();
|
|
165
|
-
nodes.forEach((node, at) => {
|
|
166
|
-
const type = types[at];
|
|
167
|
-
if (type !== UNANSWERED && type !== undefined) {
|
|
168
|
-
typeOf.set(node, type);
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
const partsOf = (type: Type): readonly Type[] => {
|
|
172
|
-
const parts = queries.constituents(type);
|
|
173
|
-
return parts === UNANSWERED ? [type] : parts;
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const found: Reference[] = [];
|
|
177
|
-
for (const one of keyed) {
|
|
178
|
-
const literal = typeOf.get(one.other);
|
|
179
|
-
const names = literal === undefined ? [] : literalNames(partsOf(literal));
|
|
180
|
-
if (names.length === 0) {
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
const position = renderPosition(one.file, root, one.other.getStart());
|
|
184
|
-
for (const operand of one.operands) {
|
|
185
|
-
const object = typeOf.get(operand);
|
|
186
|
-
for (const part of object === undefined ? [] : partsOf(object)) {
|
|
187
|
-
const properties = queries.propertiesOf(part);
|
|
188
|
-
for (const property of properties === UNANSWERED ? [] : properties) {
|
|
189
|
-
if (!names.includes(property.name)) {
|
|
190
|
-
continue;
|
|
150
|
+
const nodes = keyed.flatMap((one) => [one.other, ...one.operands]);
|
|
151
|
+
const types = queries.typesAt(nodes);
|
|
152
|
+
const typeOf = new Map<Node, Type | typeof UNANSWERED>();
|
|
153
|
+
nodes.forEach((node, at) => {
|
|
154
|
+
const type = types[at];
|
|
155
|
+
if (type !== undefined) {
|
|
156
|
+
typeOf.set(node, type);
|
|
191
157
|
}
|
|
192
|
-
|
|
158
|
+
});
|
|
159
|
+
const partsOf = (type: Type): readonly Type[] => {
|
|
160
|
+
const parts = queries.constituents(type);
|
|
161
|
+
return parts === UNANSWERED ? [type] : parts;
|
|
162
|
+
};
|
|
163
|
+
for (const one of keyed) {
|
|
164
|
+
const literal = typeOf.get(one.other);
|
|
165
|
+
const objects = one.operands.map((operand) => typeOf.get(operand));
|
|
166
|
+
if (literal === UNANSWERED) {
|
|
167
|
+
const known = objects.filter((type) => type !== undefined && type !== UNANSWERED);
|
|
193
168
|
found.push({
|
|
169
|
+
kind: "held",
|
|
194
170
|
from: one.from,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
use: "read",
|
|
198
|
-
resolution: "keyed",
|
|
199
|
-
test: testFiles.has(position.path),
|
|
171
|
+
types: known.flatMap(partsOf),
|
|
172
|
+
...(known.length < objects.length ? { members: true as const } : {}),
|
|
200
173
|
});
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const names = literal === undefined ? [] : literalNames(partsOf(literal));
|
|
177
|
+
if (names.length === 0) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
for (const object of objects) {
|
|
181
|
+
if (object === UNANSWERED) {
|
|
182
|
+
found.push({ kind: "held", from: one.from, names });
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
for (const part of object === undefined ? [] : partsOf(object)) {
|
|
186
|
+
const properties = queries.propertiesOf(part);
|
|
187
|
+
if (properties === UNANSWERED) {
|
|
188
|
+
found.push({ kind: "held", from: one.from, names });
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
for (const property of properties) {
|
|
192
|
+
if (!names.includes(property.name)) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
for (const to of chains.declarationsOf(property)) {
|
|
196
|
+
found.push({
|
|
197
|
+
kind: "named",
|
|
198
|
+
from: one.from,
|
|
199
|
+
to,
|
|
200
|
+
file: one.file,
|
|
201
|
+
at: one.other,
|
|
202
|
+
use: "read",
|
|
203
|
+
via: "keyed",
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
201
208
|
}
|
|
202
209
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
return found;
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
/** The text a string or number literal operand spells, where it is one. */
|
|
217
|
+
function literalText(node: Expression): string | undefined {
|
|
218
|
+
const at = parenthesesRemoved(node);
|
|
219
|
+
return isStringLiteral(at) || isNumericLiteral(at) || isNoSubstitutionTemplateLiteral(at)
|
|
220
|
+
? at.text
|
|
221
|
+
: undefined;
|
|
207
222
|
}
|
|
@@ -8,7 +8,13 @@ import { packageOfSpecifier } from "../dependencies.ts";
|
|
|
8
8
|
import { MANIFEST } from "../fs/manifest.ts";
|
|
9
9
|
import { entriesOf } from "../fs/tree.ts";
|
|
10
10
|
import type { Host } from "../host.ts";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
cannotStartPackageName,
|
|
13
|
+
dirnamePath,
|
|
14
|
+
joinPath,
|
|
15
|
+
relativePath,
|
|
16
|
+
resolvePath,
|
|
17
|
+
} from "../paths.ts";
|
|
12
18
|
import { dependencySpecifiers, installedPackage, manifestsAbove } from "./module-lookup.ts";
|
|
13
19
|
import type { CompilerOptions } from "./plan.ts";
|
|
14
20
|
|
|
@@ -119,7 +125,7 @@ function expectedModule(
|
|
|
119
125
|
): string | undefined {
|
|
120
126
|
const { host, targetRoot } = context;
|
|
121
127
|
const fix = "run the generator or the build that writes it";
|
|
122
|
-
if (
|
|
128
|
+
if (cannotStartPackageName(specifier)) {
|
|
123
129
|
return provided(host, resolvePath(dirnamePath(importer), specifier), false)
|
|
124
130
|
? undefined
|
|
125
131
|
: `which names no file of the target: ${fix}`;
|
package/src/load/specifiers.ts
CHANGED
|
@@ -23,14 +23,17 @@ import type { Failure } from "../failure.ts";
|
|
|
23
23
|
import { readManifestAt } from "../fs/manifest.ts";
|
|
24
24
|
import type { Host } from "../host.ts";
|
|
25
25
|
import { JAVASCRIPT_FILE } from "../javascript-file.ts";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
ancestors,
|
|
28
|
+
dirnamePath,
|
|
29
|
+
isRelativeSpecifier,
|
|
30
|
+
relativePath,
|
|
31
|
+
resolvePath,
|
|
32
|
+
} from "../paths.ts";
|
|
27
33
|
import { renderPosition, type Position } from "../position.ts";
|
|
28
34
|
import { batched, type Engine, type ProjectView } from "../session.ts";
|
|
29
35
|
import { dependencySpecifiers } from "./module-lookup.ts";
|
|
30
36
|
|
|
31
|
-
/** A relative specifier: `.`, `..`, or one starting `./` or `../`. */
|
|
32
|
-
const RELATIVE = /^\.\.?(?:\/|$)/u;
|
|
33
|
-
|
|
34
37
|
/** One relative specifier of a JavaScript file that resolves to no file. */
|
|
35
38
|
export interface UnresolvedSpecifier {
|
|
36
39
|
readonly specifier: string;
|
|
@@ -141,7 +144,7 @@ export function readSpecifiers<Brand>(
|
|
|
141
144
|
const resolved = (answers[at]?.resolvedFileName ?? "") !== "";
|
|
142
145
|
const specifier = usage.node.text;
|
|
143
146
|
const name = packageOfSpecifier(specifier);
|
|
144
|
-
if (
|
|
147
|
+
if (isRelativeSpecifier(specifier)) {
|
|
145
148
|
const named = resolvePath(dirnamePath(usage.file.fileName), specifier);
|
|
146
149
|
if (!resolved && !outputs.some((dir) => relativePath(dir, named) !== undefined)) {
|
|
147
150
|
unresolved.push({
|
package/src/load/synthesize.ts
CHANGED
|
@@ -132,6 +132,12 @@ const RUNTIME_OPTIONS = {
|
|
|
132
132
|
node: { module: "nodenext", moduleResolution: "nodenext" },
|
|
133
133
|
} as const satisfies Record<Runtime, Readonly<Record<string, string>>>;
|
|
134
134
|
|
|
135
|
+
/** Whether one configuration is a program the run formed to resolve its modules as Node does. */
|
|
136
|
+
export function formedForNode(configuration: Configuration): boolean {
|
|
137
|
+
const options = configuration.written?.["compilerOptions"];
|
|
138
|
+
return isRecord(options) && options["moduleResolution"] === RUNTIME_OPTIONS.node.moduleResolution;
|
|
139
|
+
}
|
|
140
|
+
|
|
135
141
|
/** The manifest members that say a package is written for Node. */
|
|
136
142
|
const NODE_MEMBERS = ["main", "exports", "bin", "type"] as const;
|
|
137
143
|
|
|
@@ -10,7 +10,14 @@ import { emittedFrom } from "./emit-map.ts";
|
|
|
10
10
|
import { packageOfSpecifier } from "../dependencies.ts";
|
|
11
11
|
import { gitIgnored } from "../git-ignore.ts";
|
|
12
12
|
import type { Host } from "../host.ts";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
ancestors,
|
|
15
|
+
cannotStartPackageName,
|
|
16
|
+
dirnamePath,
|
|
17
|
+
joinPath,
|
|
18
|
+
relativePath,
|
|
19
|
+
resolvePath,
|
|
20
|
+
} from "../paths.ts";
|
|
14
21
|
import { satisfies } from "../version-range.ts";
|
|
15
22
|
import type { Workspace, WorkspaceMember } from "./workspace.ts";
|
|
16
23
|
import { isRecord } from "../json-document.ts";
|
|
@@ -140,7 +147,7 @@ function dependsOn(specifier: string, member: WorkspaceMember, fromDir: string):
|
|
|
140
147
|
member.version !== undefined && satisfies(member.version, range);
|
|
141
148
|
if (specifier.startsWith("workspace:")) {
|
|
142
149
|
const rest = specifier.slice("workspace:".length);
|
|
143
|
-
if (
|
|
150
|
+
if (cannotStartPackageName(rest)) {
|
|
144
151
|
return atPath(rest);
|
|
145
152
|
}
|
|
146
153
|
const alias = /^((?:@[^/@]+\/)?[^/@]+)@(.*)$/u.exec(rest);
|
package/src/matrix.ts
CHANGED
|
@@ -10,8 +10,10 @@ import type { InventorySymbol } from "./inventory.ts";
|
|
|
10
10
|
import { byPosition } from "./position.ts";
|
|
11
11
|
import type { Reference } from "./references.ts";
|
|
12
12
|
import type { Root } from "./roots/root-set.ts";
|
|
13
|
+
import { edgeWithheldBy } from "./tests/file-class.ts";
|
|
13
14
|
import {
|
|
14
15
|
reachedWithTheTests,
|
|
16
|
+
splitRefs,
|
|
15
17
|
sweep,
|
|
16
18
|
type Candidate,
|
|
17
19
|
type Liveness,
|
|
@@ -270,13 +272,14 @@ export function sweepMatrix(matrix: Matrix, input: SweepInput): SweepResult {
|
|
|
270
272
|
}
|
|
271
273
|
const held = matrix.heldIn[at] ?? [];
|
|
272
274
|
const admitted = admittedAt(at);
|
|
273
|
-
const
|
|
275
|
+
const split = splitRefs(union.made[at], input.mode);
|
|
276
|
+
const testRefs = split.test;
|
|
274
277
|
const heldByTests =
|
|
275
278
|
heldBy[union.parent[at] ?? OUTSIDE] ??
|
|
276
279
|
(union.support[at] === true && testRefs > 0 && !admitted ? symbol.id : undefined);
|
|
277
280
|
const unreferenced = union.test[at] === true && !admitted && testReached[at] !== true;
|
|
278
281
|
heldBy[at] = heldByTests;
|
|
279
|
-
const productionRefs =
|
|
282
|
+
const productionRefs = split.production;
|
|
280
283
|
const counted = all.every((candidate) => candidate.relation === "reference-counting");
|
|
281
284
|
candidates.push({
|
|
282
285
|
id: symbol.id,
|
|
@@ -325,7 +328,7 @@ function compiledReferences(
|
|
|
325
328
|
}
|
|
326
329
|
for (const edge of edges) {
|
|
327
330
|
const to = graph.symbols[edge.to]?.id ?? "";
|
|
328
|
-
if ((
|
|
331
|
+
if (!edgeWithheldBy(mode, edge) && dead.has(to) && !kept.has(to)) {
|
|
329
332
|
kept.add(to);
|
|
330
333
|
changed = true;
|
|
331
334
|
}
|
|
@@ -15,20 +15,18 @@ import {
|
|
|
15
15
|
isImportTypeNode,
|
|
16
16
|
isLiteralTypeNode,
|
|
17
17
|
isModuleDeclaration,
|
|
18
|
-
isNamespaceExport,
|
|
19
|
-
isNamespaceImport,
|
|
20
18
|
isNoSubstitutionTemplateLiteral,
|
|
21
19
|
isStringLiteral,
|
|
22
20
|
SyntaxKind,
|
|
23
21
|
type Node,
|
|
24
22
|
type SourceFile,
|
|
25
23
|
} from "@typescript/native/ast";
|
|
24
|
+
import { namesImported } from "./import-bindings.ts";
|
|
26
25
|
import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
27
|
-
import { renderPosition } from "./position.ts";
|
|
28
26
|
import { UNANSWERED } from "./query.ts";
|
|
29
27
|
import { aliasFragment, nameComponent } from "./ref.ts";
|
|
30
|
-
import type {
|
|
31
|
-
import
|
|
28
|
+
import type { Read, ReadRule } from "./references/read-rule.ts";
|
|
29
|
+
import { isPathSpecifier } from "./paths.ts";
|
|
32
30
|
|
|
33
31
|
/** Whether a node is a specifier's text: a string literal or a template with no substitution. */
|
|
34
32
|
function literalText(node: Node | undefined): node is Node & { readonly text: string } {
|
|
@@ -58,33 +56,12 @@ function specifierOf(node: Node): (Node & { readonly text: string }) | undefined
|
|
|
58
56
|
return undefined;
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
/**
|
|
62
|
-
* The export names an import or a re-export reads from the module it names: none for a
|
|
63
|
-
* namespace, a star or a bare form.
|
|
64
|
-
*/
|
|
65
|
-
export function namesImported(node: Node): readonly string[] {
|
|
66
|
-
if (isImportDeclaration(node)) {
|
|
67
|
-
const clause = node.importClause;
|
|
68
|
-
const bindings = clause?.namedBindings;
|
|
69
|
-
return [
|
|
70
|
-
...(clause?.name === undefined ? [] : ["default"]),
|
|
71
|
-
...(bindings === undefined || isNamespaceImport(bindings)
|
|
72
|
-
? []
|
|
73
|
-
: bindings.elements.map((one) => (one.propertyName ?? one.name).text)),
|
|
74
|
-
];
|
|
75
|
-
}
|
|
76
|
-
const clause = isExportDeclaration(node) ? node.exportClause : undefined;
|
|
77
|
-
return clause === undefined || isNamespaceExport(clause)
|
|
78
|
-
? []
|
|
79
|
-
: clause.elements.map((one) => (one.propertyName ?? one.name).text);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
59
|
/**
|
|
83
60
|
* Whether a specifier matches a declaration's name, the name's one `*` standing for any run.
|
|
84
61
|
* A relative name is resolved against its own file, so no specifier names it by its text.
|
|
85
62
|
*/
|
|
86
63
|
function namesModule(specifier: string, name: string): boolean {
|
|
87
|
-
if (
|
|
64
|
+
if (isPathSpecifier(name)) {
|
|
88
65
|
return false;
|
|
89
66
|
}
|
|
90
67
|
const star = name.indexOf("*");
|
|
@@ -161,76 +138,75 @@ interface Declared {
|
|
|
161
138
|
}
|
|
162
139
|
|
|
163
140
|
/**
|
|
164
|
-
* The
|
|
165
|
-
*
|
|
141
|
+
* The reads the imports of a side's files make of the module declarations of the target they
|
|
142
|
+
* name, each at the import's specifier. A specifier the checker leaves unanswered still names
|
|
143
|
+
* a declaration by its name or pattern.
|
|
166
144
|
*/
|
|
167
|
-
export
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
for (const file of project.ownSourceFiles()) {
|
|
176
|
-
const fileId = held.declarations.get(nodeKey(file, file));
|
|
177
|
-
if (fileId === undefined) {
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
const visit = (node: Node, from: string): void => {
|
|
181
|
-
const own = held.declarations.get(nodeKey(file, node));
|
|
182
|
-
if (isModuleDeclaration(node) && literalText(node.name) && own !== undefined) {
|
|
183
|
-
declared.push({ id: own, name: node.name });
|
|
184
|
-
}
|
|
185
|
-
const specifier = specifierOf(node);
|
|
186
|
-
if (specifier !== undefined) {
|
|
187
|
-
imports.push({ file, from, specifier, names: namesImported(node) });
|
|
188
|
-
}
|
|
189
|
-
node.forEachChild((child) => {
|
|
190
|
-
visit(child, own ?? from);
|
|
191
|
-
});
|
|
192
|
-
};
|
|
193
|
-
file.forEachChild((child) => {
|
|
194
|
-
visit(child, fileId);
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
if (declared.length === 0 || imports.length === 0) {
|
|
198
|
-
return [];
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const asked = [...declared.map((one) => one.name), ...imports.map((one) => one.specifier)];
|
|
202
|
-
const answers = project.symbolsAt(asked.map((node) => project.handle(node)));
|
|
203
|
-
const moduleOf = (at: number): number | undefined => {
|
|
204
|
-
const symbol = answers[at];
|
|
205
|
-
return symbol === undefined || symbol === UNANSWERED ? undefined : symbol.id;
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
const named = namedBy(held);
|
|
209
|
-
const found: Reference[] = [];
|
|
210
|
-
imports.forEach((one, index) => {
|
|
211
|
-
const resolved = moduleOf(declared.length + index);
|
|
212
|
-
const position = renderPosition(one.file, root, one.specifier.getStart());
|
|
213
|
-
const read = (to: string, resolution: Reference["resolution"], test: boolean): void => {
|
|
214
|
-
found.push({ from: one.from, to, position, use: "read", resolution, test });
|
|
215
|
-
};
|
|
216
|
-
const test = testFiles.has(position.path);
|
|
217
|
-
const same = new Set(
|
|
218
|
-
declared
|
|
219
|
-
.filter((_, at) => resolved !== undefined && resolved === moduleOf(at))
|
|
220
|
-
.map((declaration) => declaration.id),
|
|
221
|
-
);
|
|
222
|
-
for (const id of same) {
|
|
223
|
-
read(id, "batch", test);
|
|
224
|
-
}
|
|
225
|
-
for (const declaration of named(one.specifier.text, one.names)) {
|
|
226
|
-
// A wildcard declaration is how the compiler types the file a matching specifier
|
|
227
|
-
// names, so the import uses it whoever writes it, a test included.
|
|
228
|
-
if (declaration.wildcard || !same.has(declaration.id)) {
|
|
229
|
-
for (const to of [declaration.id, ...declaration.members]) {
|
|
230
|
-
read(to, "syntax", test && !declaration.wildcard);
|
|
145
|
+
export const moduleDeclarations: ReadRule = {
|
|
146
|
+
start() {
|
|
147
|
+
const imports: Import[] = [];
|
|
148
|
+
return {
|
|
149
|
+
visit(node, { file, from }) {
|
|
150
|
+
const specifier = specifierOf(node);
|
|
151
|
+
if (specifier !== undefined) {
|
|
152
|
+
imports.push({ file, from, specifier, names: namesImported(node) });
|
|
231
153
|
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
154
|
+
},
|
|
155
|
+
reads({ project, held }) {
|
|
156
|
+
const found: Read[] = [];
|
|
157
|
+
// A module declaration whose name is a string is written at a file's top level.
|
|
158
|
+
const declared: Declared[] = project.ownSourceFiles().flatMap((file) =>
|
|
159
|
+
file.statements.flatMap((statement) => {
|
|
160
|
+
const id = held.declarations.get(nodeKey(file, statement));
|
|
161
|
+
return isModuleDeclaration(statement) && literalText(statement.name) && id !== undefined
|
|
162
|
+
? [{ id, name: statement.name }]
|
|
163
|
+
: [];
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
if (declared.length === 0 || imports.length === 0) {
|
|
167
|
+
return found;
|
|
168
|
+
}
|
|
169
|
+
const asked = [...declared.map((one) => one.name), ...imports.map((one) => one.specifier)];
|
|
170
|
+
const answers = project.symbolsAt(asked.map((node) => project.handle(node)));
|
|
171
|
+
const moduleOf = (at: number): number | undefined => {
|
|
172
|
+
const symbol = answers[at];
|
|
173
|
+
return symbol === undefined || symbol === UNANSWERED ? undefined : symbol.id;
|
|
174
|
+
};
|
|
175
|
+
const named = namedBy(held);
|
|
176
|
+
imports.forEach((one, index) => {
|
|
177
|
+
const resolved = moduleOf(declared.length + index);
|
|
178
|
+
const read = (to: string, via: "batch" | "syntax", production: boolean): void => {
|
|
179
|
+
found.push({
|
|
180
|
+
kind: "named",
|
|
181
|
+
from: one.from,
|
|
182
|
+
to,
|
|
183
|
+
file: one.file,
|
|
184
|
+
at: one.specifier,
|
|
185
|
+
use: "read",
|
|
186
|
+
via,
|
|
187
|
+
...(production ? { production: true as const } : {}),
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
const same = new Set(
|
|
191
|
+
declared
|
|
192
|
+
.filter((_, at) => resolved !== undefined && resolved === moduleOf(at))
|
|
193
|
+
.map((declaration) => declaration.id),
|
|
194
|
+
);
|
|
195
|
+
for (const id of same) {
|
|
196
|
+
read(id, "batch", false);
|
|
197
|
+
}
|
|
198
|
+
for (const declaration of named(one.specifier.text, one.names)) {
|
|
199
|
+
// A wildcard declaration is how the compiler types the file a matching specifier
|
|
200
|
+
// names, so the import uses it whoever writes it, a test included.
|
|
201
|
+
if (declaration.wildcard || !same.has(declaration.id)) {
|
|
202
|
+
for (const to of [declaration.id, ...declaration.members]) {
|
|
203
|
+
read(to, "syntax", declaration.wildcard);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
return found;
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
},
|
|
212
|
+
};
|