@typed/virtual-modules 1.0.0-beta.1
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/README.md +135 -0
- package/dist/CompilerHostAdapter.d.ts +3 -0
- package/dist/CompilerHostAdapter.d.ts.map +1 -0
- package/dist/CompilerHostAdapter.js +160 -0
- package/dist/LanguageServiceAdapter.d.ts +3 -0
- package/dist/LanguageServiceAdapter.d.ts.map +1 -0
- package/dist/LanguageServiceAdapter.js +488 -0
- package/dist/LanguageServiceSession.d.ts +16 -0
- package/dist/LanguageServiceSession.d.ts.map +1 -0
- package/dist/LanguageServiceSession.js +122 -0
- package/dist/NodeModulePluginLoader.d.ts +8 -0
- package/dist/NodeModulePluginLoader.d.ts.map +1 -0
- package/dist/NodeModulePluginLoader.js +175 -0
- package/dist/PluginManager.d.ts +10 -0
- package/dist/PluginManager.d.ts.map +1 -0
- package/dist/PluginManager.js +151 -0
- package/dist/TypeInfoApi.d.ts +71 -0
- package/dist/TypeInfoApi.d.ts.map +1 -0
- package/dist/TypeInfoApi.js +855 -0
- package/dist/VmcConfigLoader.d.ts +31 -0
- package/dist/VmcConfigLoader.d.ts.map +1 -0
- package/dist/VmcConfigLoader.js +231 -0
- package/dist/VmcResolverLoader.d.ts +30 -0
- package/dist/VmcResolverLoader.d.ts.map +1 -0
- package/dist/VmcResolverLoader.js +71 -0
- package/dist/collectTypeTargetSpecs.d.ts +6 -0
- package/dist/collectTypeTargetSpecs.d.ts.map +1 -0
- package/dist/collectTypeTargetSpecs.js +19 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/internal/VirtualRecordStore.d.ts +60 -0
- package/dist/internal/VirtualRecordStore.d.ts.map +1 -0
- package/dist/internal/VirtualRecordStore.js +199 -0
- package/dist/internal/materializeVirtualFile.d.ts +12 -0
- package/dist/internal/materializeVirtualFile.d.ts.map +1 -0
- package/dist/internal/materializeVirtualFile.js +28 -0
- package/dist/internal/path.d.ts +40 -0
- package/dist/internal/path.d.ts.map +1 -0
- package/dist/internal/path.js +71 -0
- package/dist/internal/sanitize.d.ts +6 -0
- package/dist/internal/sanitize.d.ts.map +1 -0
- package/dist/internal/sanitize.js +15 -0
- package/dist/internal/tsInternal.d.ts +65 -0
- package/dist/internal/tsInternal.d.ts.map +1 -0
- package/dist/internal/tsInternal.js +99 -0
- package/dist/internal/validation.d.ts +28 -0
- package/dist/internal/validation.d.ts.map +1 -0
- package/dist/internal/validation.js +66 -0
- package/dist/typeTargetBootstrap.d.ts +33 -0
- package/dist/typeTargetBootstrap.d.ts.map +1 -0
- package/dist/typeTargetBootstrap.js +57 -0
- package/dist/types.d.ts +405 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/package.json +38 -0
- package/src/CompilerHostAdapter.test.ts +180 -0
- package/src/CompilerHostAdapter.ts +316 -0
- package/src/LanguageServiceAdapter.test.ts +521 -0
- package/src/LanguageServiceAdapter.ts +631 -0
- package/src/LanguageServiceSession.ts +160 -0
- package/src/LanguageServiceTester.integration.test.ts +268 -0
- package/src/NodeModulePluginLoader.test.ts +170 -0
- package/src/NodeModulePluginLoader.ts +268 -0
- package/src/PluginManager.test.ts +178 -0
- package/src/PluginManager.ts +218 -0
- package/src/TypeInfoApi.test.ts +1211 -0
- package/src/TypeInfoApi.ts +1228 -0
- package/src/VmcConfigLoader.test.ts +108 -0
- package/src/VmcConfigLoader.ts +297 -0
- package/src/VmcResolverLoader.test.ts +181 -0
- package/src/VmcResolverLoader.ts +116 -0
- package/src/collectTypeTargetSpecs.ts +22 -0
- package/src/index.ts +35 -0
- package/src/internal/VirtualRecordStore.ts +304 -0
- package/src/internal/materializeVirtualFile.ts +38 -0
- package/src/internal/path.ts +106 -0
- package/src/internal/sanitize.ts +16 -0
- package/src/internal/tsInternal.ts +127 -0
- package/src/internal/validation.ts +85 -0
- package/src/typeTargetBootstrap.ts +75 -0
- package/src/types.ts +535 -0
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
import { relative } from "node:path";
|
|
2
|
+
import { getAliasedSymbol as resolveAliasedSymbol, getBaseTypes as getBaseTypesInternal, getIndexInfosOfType, getSymbolExports, getTypeReferenceTarget, getTypeSymbol, FALLBACK_OBJECT_FLAGS_MAPPED, } from "./internal/tsInternal.js";
|
|
3
|
+
import { dedupeSorted, pathIsUnderBase, resolvePathUnderBase, resolveRelativePath, toPosixPath, } from "./internal/path.js";
|
|
4
|
+
import { validatePathSegment, validateRelativeGlobs } from "./internal/validation.js";
|
|
5
|
+
const TYPE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".d.ts"];
|
|
6
|
+
const DEFAULT_MAX_DEPTH = 6;
|
|
7
|
+
const compareByName = (a, b) => a.name.localeCompare(b.name);
|
|
8
|
+
const toOptionalFlag = (symbol, tsMod) => (symbol.flags & tsMod.SymbolFlags.Optional) !== 0;
|
|
9
|
+
const hasReadonlyModifier = (declaration, tsMod) => {
|
|
10
|
+
if (!declaration || !tsMod.canHaveModifiers(declaration)) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const modifiers = tsMod.getModifiers(declaration);
|
|
14
|
+
return modifiers?.some((modifier) => modifier.kind === tsMod.SyntaxKind.ReadonlyKeyword) ?? false;
|
|
15
|
+
};
|
|
16
|
+
const asLiteral = (type, checker, tsMod) => {
|
|
17
|
+
const text = checker.typeToString(type);
|
|
18
|
+
if ((type.flags & tsMod.TypeFlags.StringLiteral) !== 0 ||
|
|
19
|
+
(type.flags & tsMod.TypeFlags.NumberLiteral) !== 0 ||
|
|
20
|
+
(type.flags & tsMod.TypeFlags.BooleanLiteral) !== 0 ||
|
|
21
|
+
(type.flags & tsMod.TypeFlags.BigIntLiteral) !== 0) {
|
|
22
|
+
return { kind: "literal", text };
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
};
|
|
26
|
+
const serializeFunctionSignature = (signature, checker, tsMod, depth, maxDepth, visited, onInternalError, registry) => {
|
|
27
|
+
const parameters = signature.getParameters().map((parameter) => {
|
|
28
|
+
const declaration = parameter.valueDeclaration ?? parameter.declarations?.[0];
|
|
29
|
+
const parameterType = declaration
|
|
30
|
+
? checker.getTypeOfSymbolAtLocation(parameter, declaration)
|
|
31
|
+
: checker.getDeclaredTypeOfSymbol(parameter);
|
|
32
|
+
return {
|
|
33
|
+
name: parameter.getName(),
|
|
34
|
+
optional: toOptionalFlag(parameter, tsMod),
|
|
35
|
+
type: serializeTypeNode(parameterType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
const returnType = serializeTypeNode(checker.getReturnTypeOfSignature(signature), checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry);
|
|
39
|
+
return {
|
|
40
|
+
parameters: parameters.sort(compareByName),
|
|
41
|
+
returnType,
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
const serializeObjectProperties = (type, checker, tsMod, depth, maxDepth, visited, onInternalError, registry) => {
|
|
45
|
+
const properties = checker.getPropertiesOfType(type);
|
|
46
|
+
const snapshots = properties.map((property) => {
|
|
47
|
+
const declaration = property.valueDeclaration ?? property.declarations?.[0];
|
|
48
|
+
const propertyType = declaration
|
|
49
|
+
? checker.getTypeOfSymbolAtLocation(property, declaration)
|
|
50
|
+
: checker.getDeclaredTypeOfSymbol(property);
|
|
51
|
+
return {
|
|
52
|
+
name: property.getName(),
|
|
53
|
+
optional: toOptionalFlag(property, tsMod),
|
|
54
|
+
readonly: hasReadonlyModifier(declaration, tsMod),
|
|
55
|
+
type: serializeTypeNode(propertyType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
return snapshots.sort(compareByName);
|
|
59
|
+
};
|
|
60
|
+
const serializeIndexSignature = (type, checker, tsMod, depth, maxDepth, visited, onInternalError, registry) => {
|
|
61
|
+
const infos = getIndexInfosOfType(type, checker);
|
|
62
|
+
if (!infos || infos.length === 0)
|
|
63
|
+
return undefined;
|
|
64
|
+
const first = infos[0];
|
|
65
|
+
if (!first)
|
|
66
|
+
return undefined;
|
|
67
|
+
return {
|
|
68
|
+
keyType: serializeTypeNode(first.keyType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
69
|
+
valueType: serializeTypeNode(first.type, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
70
|
+
readonly: first.isReadonly ?? false,
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
const primitiveTypeNode = (type, checker, tsMod) => {
|
|
74
|
+
const text = checker.typeToString(type);
|
|
75
|
+
if ((type.flags & tsMod.TypeFlags.Any) !== 0) {
|
|
76
|
+
return { kind: "any", text };
|
|
77
|
+
}
|
|
78
|
+
if ((type.flags & tsMod.TypeFlags.Unknown) !== 0) {
|
|
79
|
+
return { kind: "unknown", text };
|
|
80
|
+
}
|
|
81
|
+
if ((type.flags & tsMod.TypeFlags.Never) !== 0) {
|
|
82
|
+
return { kind: "never", text };
|
|
83
|
+
}
|
|
84
|
+
if ((type.flags & tsMod.TypeFlags.StringLike) !== 0 ||
|
|
85
|
+
(type.flags & tsMod.TypeFlags.NumberLike) !== 0 ||
|
|
86
|
+
(type.flags & tsMod.TypeFlags.BooleanLike) !== 0 ||
|
|
87
|
+
(type.flags & tsMod.TypeFlags.BigIntLike) !== 0 ||
|
|
88
|
+
(type.flags & tsMod.TypeFlags.ESSymbolLike) !== 0 ||
|
|
89
|
+
(type.flags & tsMod.TypeFlags.Null) !== 0 ||
|
|
90
|
+
(type.flags & tsMod.TypeFlags.Undefined) !== 0 ||
|
|
91
|
+
(type.flags & tsMod.TypeFlags.Void) !== 0) {
|
|
92
|
+
return { kind: "primitive", text };
|
|
93
|
+
}
|
|
94
|
+
return undefined;
|
|
95
|
+
};
|
|
96
|
+
const UNKNOWN_NODE = { kind: "unknown", text: "unknown" };
|
|
97
|
+
const serializeTypeNode = (type, checker, tsMod, depth, maxDepth, visited, onInternalError, registry) => {
|
|
98
|
+
const text = checker.typeToString(type);
|
|
99
|
+
const reg = (node) => {
|
|
100
|
+
registry?.set(node, type);
|
|
101
|
+
return node;
|
|
102
|
+
};
|
|
103
|
+
const typeId = `${type.id ?? "anon"}:${text}`;
|
|
104
|
+
if (depth > maxDepth || visited.has(typeId)) {
|
|
105
|
+
return reg({ kind: "reference", text });
|
|
106
|
+
}
|
|
107
|
+
visited.add(typeId);
|
|
108
|
+
const literal = asLiteral(type, checker, tsMod);
|
|
109
|
+
if (literal) {
|
|
110
|
+
return reg(literal);
|
|
111
|
+
}
|
|
112
|
+
if (type.isUnion()) {
|
|
113
|
+
const union = {
|
|
114
|
+
kind: "union",
|
|
115
|
+
text,
|
|
116
|
+
elements: type.types.map((value) => serializeTypeNode(value, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)),
|
|
117
|
+
};
|
|
118
|
+
return reg(union);
|
|
119
|
+
}
|
|
120
|
+
if (type.isIntersection()) {
|
|
121
|
+
const intersection = {
|
|
122
|
+
kind: "intersection",
|
|
123
|
+
text,
|
|
124
|
+
elements: type.types.map((value) => serializeTypeNode(value, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)),
|
|
125
|
+
};
|
|
126
|
+
return reg(intersection);
|
|
127
|
+
}
|
|
128
|
+
const primitive = primitiveTypeNode(type, checker, tsMod);
|
|
129
|
+
if (primitive) {
|
|
130
|
+
return reg(primitive);
|
|
131
|
+
}
|
|
132
|
+
if ((type.flags & tsMod.TypeFlags.Enum) !== 0) {
|
|
133
|
+
const members = [];
|
|
134
|
+
try {
|
|
135
|
+
const props = checker.getPropertiesOfType(type);
|
|
136
|
+
for (const sym of props) {
|
|
137
|
+
const name = sym.name;
|
|
138
|
+
let value;
|
|
139
|
+
const decl = sym.valueDeclaration;
|
|
140
|
+
if (decl && tsMod.isEnumMember?.(decl) && decl.initializer) {
|
|
141
|
+
const init = decl.initializer;
|
|
142
|
+
if (tsMod.isNumericLiteral?.(init)) {
|
|
143
|
+
value = parseInt(init.text, 10);
|
|
144
|
+
}
|
|
145
|
+
else if (tsMod.isStringLiteral?.(init) || tsMod.isNoSubstitutionTemplateLiteral?.(init)) {
|
|
146
|
+
value = init.text;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const cv = checker.getConstantValue?.(init);
|
|
150
|
+
if (cv !== undefined)
|
|
151
|
+
value = cv;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
members.push(value !== undefined ? { name, value } : { name });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
onInternalError?.(err, "enum-members");
|
|
159
|
+
}
|
|
160
|
+
const enumNode = {
|
|
161
|
+
kind: "enum",
|
|
162
|
+
text,
|
|
163
|
+
members: members.length > 0 ? members : undefined,
|
|
164
|
+
};
|
|
165
|
+
return reg(enumNode);
|
|
166
|
+
}
|
|
167
|
+
if ((type.flags & tsMod.TypeFlags.Conditional) !== 0) {
|
|
168
|
+
const ct = type;
|
|
169
|
+
const trueType = ct.resolvedTrueType ??
|
|
170
|
+
(ct.root?.node?.trueType
|
|
171
|
+
? checker.getTypeFromTypeNode?.(ct.root.node.trueType)
|
|
172
|
+
: undefined);
|
|
173
|
+
const falseType = ct.resolvedFalseType ??
|
|
174
|
+
(ct.root?.node?.falseType
|
|
175
|
+
? checker.getTypeFromTypeNode?.(ct.root.node.falseType)
|
|
176
|
+
: undefined);
|
|
177
|
+
const conditional = {
|
|
178
|
+
kind: "conditional",
|
|
179
|
+
text,
|
|
180
|
+
checkType: serializeTypeNode(ct.checkType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
181
|
+
extendsType: serializeTypeNode(ct.extendsType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
182
|
+
trueType: trueType
|
|
183
|
+
? serializeTypeNode(trueType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)
|
|
184
|
+
: UNKNOWN_NODE,
|
|
185
|
+
falseType: falseType
|
|
186
|
+
? serializeTypeNode(falseType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)
|
|
187
|
+
: UNKNOWN_NODE,
|
|
188
|
+
};
|
|
189
|
+
return reg(conditional);
|
|
190
|
+
}
|
|
191
|
+
if ((type.flags & tsMod.TypeFlags.IndexedAccess) !== 0) {
|
|
192
|
+
const iat = type;
|
|
193
|
+
const indexed = {
|
|
194
|
+
kind: "indexedAccess",
|
|
195
|
+
text,
|
|
196
|
+
objectType: serializeTypeNode(iat.objectType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
197
|
+
indexType: serializeTypeNode(iat.indexType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
198
|
+
};
|
|
199
|
+
return reg(indexed);
|
|
200
|
+
}
|
|
201
|
+
if ((type.flags & tsMod.TypeFlags.TemplateLiteral) !== 0) {
|
|
202
|
+
const tlt = type;
|
|
203
|
+
const texts = tlt.texts ?? [];
|
|
204
|
+
const types = tlt.types ?? [];
|
|
205
|
+
const template = {
|
|
206
|
+
kind: "templateLiteral",
|
|
207
|
+
text,
|
|
208
|
+
texts,
|
|
209
|
+
types: types.map((t) => serializeTypeNode(t, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)),
|
|
210
|
+
};
|
|
211
|
+
return reg(template);
|
|
212
|
+
}
|
|
213
|
+
const objType = type;
|
|
214
|
+
const mappedFlag = tsMod.ObjectFlags?.Mapped ?? FALLBACK_OBJECT_FLAGS_MAPPED;
|
|
215
|
+
if (objType.objectFlags !== undefined && (objType.objectFlags & mappedFlag) !== 0) {
|
|
216
|
+
const mt = type;
|
|
217
|
+
const constraintType = mt.constraint ?? mt.typeParameter?.constraint;
|
|
218
|
+
const mappedType = mt.templateType ?? mt.mappedType;
|
|
219
|
+
const mapped = {
|
|
220
|
+
kind: "mapped",
|
|
221
|
+
text,
|
|
222
|
+
constraintType: constraintType
|
|
223
|
+
? serializeTypeNode(constraintType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)
|
|
224
|
+
: UNKNOWN_NODE,
|
|
225
|
+
mappedType: mappedType
|
|
226
|
+
? serializeTypeNode(mappedType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)
|
|
227
|
+
: UNKNOWN_NODE,
|
|
228
|
+
};
|
|
229
|
+
return reg(mapped);
|
|
230
|
+
}
|
|
231
|
+
if ((type.flags & tsMod.TypeFlags.Index) !== 0) {
|
|
232
|
+
const idxType = type;
|
|
233
|
+
const typeOperator = {
|
|
234
|
+
kind: "typeOperator",
|
|
235
|
+
text,
|
|
236
|
+
operator: "keyof",
|
|
237
|
+
type: serializeTypeNode(idxType.type, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
238
|
+
};
|
|
239
|
+
return reg(typeOperator);
|
|
240
|
+
}
|
|
241
|
+
if (checker.isTupleType(type)) {
|
|
242
|
+
const typeArguments = checker.getTypeArguments(type);
|
|
243
|
+
const tuple = {
|
|
244
|
+
kind: "tuple",
|
|
245
|
+
text,
|
|
246
|
+
elements: typeArguments.map((value) => serializeTypeNode(value, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)),
|
|
247
|
+
};
|
|
248
|
+
return reg(tuple);
|
|
249
|
+
}
|
|
250
|
+
if (checker.isArrayType(type)) {
|
|
251
|
+
const typeArguments = checker.getTypeArguments(type);
|
|
252
|
+
const element = typeArguments[0];
|
|
253
|
+
const array = {
|
|
254
|
+
kind: "array",
|
|
255
|
+
text,
|
|
256
|
+
elements: element
|
|
257
|
+
? [serializeTypeNode(element, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)]
|
|
258
|
+
: [UNKNOWN_NODE],
|
|
259
|
+
};
|
|
260
|
+
return reg(array);
|
|
261
|
+
}
|
|
262
|
+
const referenceArguments = checker.getTypeArguments(type);
|
|
263
|
+
if (referenceArguments.length > 0) {
|
|
264
|
+
const ref = {
|
|
265
|
+
kind: "reference",
|
|
266
|
+
text,
|
|
267
|
+
typeArguments: referenceArguments.map((value) => serializeTypeNode(value, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)),
|
|
268
|
+
};
|
|
269
|
+
return reg(ref);
|
|
270
|
+
}
|
|
271
|
+
const constructSignatures = checker.getSignaturesOfType(type, tsMod.SignatureKind.Construct);
|
|
272
|
+
if (constructSignatures.length > 0) {
|
|
273
|
+
const sig = serializeFunctionSignature(constructSignatures[0], checker, tsMod, depth, maxDepth, visited, onInternalError, registry);
|
|
274
|
+
const ctor = {
|
|
275
|
+
kind: "constructor",
|
|
276
|
+
text,
|
|
277
|
+
parameters: sig.parameters,
|
|
278
|
+
returnType: sig.returnType,
|
|
279
|
+
};
|
|
280
|
+
return reg(ctor);
|
|
281
|
+
}
|
|
282
|
+
const callSignatures = checker.getSignaturesOfType(type, tsMod.SignatureKind.Call);
|
|
283
|
+
if (callSignatures.length > 0) {
|
|
284
|
+
if (callSignatures.length > 1) {
|
|
285
|
+
const signatures = callSignatures.map((s) => {
|
|
286
|
+
const serialized = serializeFunctionSignature(s, checker, tsMod, depth, maxDepth, visited, onInternalError, registry);
|
|
287
|
+
return { parameters: serialized.parameters, returnType: serialized.returnType };
|
|
288
|
+
});
|
|
289
|
+
const overload = { kind: "overloadSet", text, signatures };
|
|
290
|
+
return reg(overload);
|
|
291
|
+
}
|
|
292
|
+
const signature = serializeFunctionSignature(callSignatures[0], checker, tsMod, depth, maxDepth, visited, onInternalError, registry);
|
|
293
|
+
const fn = {
|
|
294
|
+
kind: "function",
|
|
295
|
+
text,
|
|
296
|
+
parameters: signature.parameters,
|
|
297
|
+
returnType: signature.returnType,
|
|
298
|
+
};
|
|
299
|
+
return reg(fn);
|
|
300
|
+
}
|
|
301
|
+
const indexSig = serializeIndexSignature(type, checker, tsMod, depth, maxDepth, visited, onInternalError, registry);
|
|
302
|
+
const object = {
|
|
303
|
+
kind: "object",
|
|
304
|
+
text,
|
|
305
|
+
properties: serializeObjectProperties(type, checker, tsMod, depth, maxDepth, visited, onInternalError, registry),
|
|
306
|
+
...(indexSig !== undefined && { indexSignature: indexSig }),
|
|
307
|
+
};
|
|
308
|
+
return reg(object);
|
|
309
|
+
};
|
|
310
|
+
/**
|
|
311
|
+
* Serialize a TypeScript type to TypeNode. For testing serializer branches (typeOperator, enum, etc).
|
|
312
|
+
* @internal
|
|
313
|
+
*/
|
|
314
|
+
export function serializeTypeForTest(type, checker, tsMod, maxDepth = 6) {
|
|
315
|
+
return serializeTypeNode(type, checker, tsMod, 0, maxDepth, new Set());
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Generate bootstrap file content that imports all modules from typeTargetSpecs.
|
|
319
|
+
* Include this file in the program's rootNames so resolveTypeTargetsFromSpecs can
|
|
320
|
+
* find types without requiring user source to import them.
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* ```ts
|
|
324
|
+
* const bootstrapContent = createTypeTargetBootstrapContent(HTTPAPI_TYPE_TARGET_SPECS);
|
|
325
|
+
* const bootstrapPath = join(tmpDir, "__typeTargetBootstrap.ts");
|
|
326
|
+
* writeFileSync(bootstrapPath, bootstrapContent);
|
|
327
|
+
* const program = ts.createProgram([...rootFiles, bootstrapPath], options, host);
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
export function createTypeTargetBootstrapContent(specs) {
|
|
331
|
+
const seen = new Set();
|
|
332
|
+
const lines = [
|
|
333
|
+
"/**",
|
|
334
|
+
" * Auto-generated bootstrap for type target resolution.",
|
|
335
|
+
" * Imports canonical modules so resolveTypeTargetsFromSpecs can find types.",
|
|
336
|
+
" * Include in program rootNames when using typeTargetSpecs without user imports.",
|
|
337
|
+
" */",
|
|
338
|
+
];
|
|
339
|
+
for (const spec of specs) {
|
|
340
|
+
if (seen.has(spec.module))
|
|
341
|
+
continue;
|
|
342
|
+
seen.add(spec.module);
|
|
343
|
+
const id = spec.module.replace(/\W+/g, "_").replace(/_+/g, "_") || "m";
|
|
344
|
+
lines.push(`import * as _${id} from "${spec.module}";`);
|
|
345
|
+
lines.push(`void _${id};`);
|
|
346
|
+
}
|
|
347
|
+
lines.push("export {};");
|
|
348
|
+
return lines.join("\n");
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Resolve type targets from program imports using the given specs.
|
|
352
|
+
* Scans source files for imports from the specified modules and extracts types.
|
|
353
|
+
* Requires the program to include files that import from these modules (e.g.
|
|
354
|
+
* use createTypeTargetBootstrapContent and add to rootNames).
|
|
355
|
+
*/
|
|
356
|
+
export function resolveTypeTargetsFromSpecs(program, tsMod, specs) {
|
|
357
|
+
const checker = program.getTypeChecker();
|
|
358
|
+
const found = new Map();
|
|
359
|
+
const getTypeFromSymbol = (symbol) => {
|
|
360
|
+
const aliased = resolveAliasedSymbol(symbol, checker, tsMod);
|
|
361
|
+
const decls = aliased.declarations ?? (aliased.valueDeclaration ? [aliased.valueDeclaration] : []);
|
|
362
|
+
const typeDecl = decls.find((d) => tsMod.isTypeAliasDeclaration(d) ||
|
|
363
|
+
tsMod.isInterfaceDeclaration(d) ||
|
|
364
|
+
tsMod.isTypeParameterDeclaration(d));
|
|
365
|
+
const decl = typeDecl ?? decls[0];
|
|
366
|
+
const useDeclaredType = decl &&
|
|
367
|
+
(tsMod.isTypeAliasDeclaration(decl) ||
|
|
368
|
+
tsMod.isInterfaceDeclaration(decl) ||
|
|
369
|
+
tsMod.isTypeParameterDeclaration(decl));
|
|
370
|
+
const type = useDeclaredType
|
|
371
|
+
? checker.getDeclaredTypeOfSymbol(aliased)
|
|
372
|
+
: decl
|
|
373
|
+
? checker.getTypeOfSymbolAtLocation(aliased, decl)
|
|
374
|
+
: checker.getDeclaredTypeOfSymbol(aliased);
|
|
375
|
+
if (!type || (type.flags & tsMod.TypeFlags.Any) !== 0)
|
|
376
|
+
return undefined;
|
|
377
|
+
return type;
|
|
378
|
+
};
|
|
379
|
+
const getTypeFromSpec = (symbol, spec) => {
|
|
380
|
+
const primaryType = getTypeFromSymbol(symbol);
|
|
381
|
+
if (!primaryType || !spec.typeMember)
|
|
382
|
+
return primaryType;
|
|
383
|
+
const aliased = resolveAliasedSymbol(symbol, checker, tsMod);
|
|
384
|
+
const exports = getSymbolExports(aliased);
|
|
385
|
+
if (!exports)
|
|
386
|
+
return primaryType;
|
|
387
|
+
const memberSymbol = Array.from(exports.values()).find((s) => s.getName() === spec.typeMember);
|
|
388
|
+
if (!memberSymbol)
|
|
389
|
+
return primaryType;
|
|
390
|
+
const memberType = checker.getDeclaredTypeOfSymbol(memberSymbol);
|
|
391
|
+
if (!memberType || (memberType.flags & tsMod.TypeFlags.Any) !== 0)
|
|
392
|
+
return primaryType;
|
|
393
|
+
return memberType;
|
|
394
|
+
};
|
|
395
|
+
const addFromModuleSymbol = (moduleSymbol, moduleSpec) => {
|
|
396
|
+
if (!moduleSymbol)
|
|
397
|
+
return;
|
|
398
|
+
const exportsByName = new Map(checker.getExportsOfModule(moduleSymbol).map((moduleExport) => [
|
|
399
|
+
moduleExport.getName(),
|
|
400
|
+
moduleExport,
|
|
401
|
+
]));
|
|
402
|
+
for (const spec of specs) {
|
|
403
|
+
if (spec.module !== moduleSpec || found.has(spec.id))
|
|
404
|
+
continue;
|
|
405
|
+
const moduleExport = exportsByName.get(spec.exportName);
|
|
406
|
+
if (!moduleExport)
|
|
407
|
+
continue;
|
|
408
|
+
const type = getTypeFromSpec(moduleExport, spec);
|
|
409
|
+
if (type)
|
|
410
|
+
found.set(spec.id, type);
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
for (const sourceFile of program.getSourceFiles()) {
|
|
414
|
+
if (sourceFile.isDeclarationFile)
|
|
415
|
+
continue;
|
|
416
|
+
tsMod.forEachChild(sourceFile, (node) => {
|
|
417
|
+
if (!tsMod.isImportDeclaration(node))
|
|
418
|
+
return;
|
|
419
|
+
const moduleSpecifier = node.moduleSpecifier;
|
|
420
|
+
if (!tsMod.isStringLiteral(moduleSpecifier))
|
|
421
|
+
return;
|
|
422
|
+
const moduleSpec = moduleSpecifier.text;
|
|
423
|
+
const binding = node.importClause;
|
|
424
|
+
if (!binding)
|
|
425
|
+
return;
|
|
426
|
+
const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier);
|
|
427
|
+
const addNamed = (name) => {
|
|
428
|
+
const exportName = (name.propertyName ?? name.name).getText(sourceFile);
|
|
429
|
+
const spec = specs.find((s) => s.module === moduleSpec && s.exportName === exportName);
|
|
430
|
+
if (!spec || found.has(spec.id))
|
|
431
|
+
return;
|
|
432
|
+
const symbol = checker.getSymbolAtLocation(name.name);
|
|
433
|
+
if (!symbol)
|
|
434
|
+
return;
|
|
435
|
+
const type = getTypeFromSpec(symbol, spec);
|
|
436
|
+
if (type)
|
|
437
|
+
found.set(spec.id, type);
|
|
438
|
+
};
|
|
439
|
+
if (binding.namedBindings) {
|
|
440
|
+
if (tsMod.isNamedImports(binding.namedBindings)) {
|
|
441
|
+
for (const elem of binding.namedBindings.elements) {
|
|
442
|
+
addNamed(elem);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
else if (tsMod.isNamespaceImport(binding.namedBindings)) {
|
|
446
|
+
const nsName = binding.namedBindings.name;
|
|
447
|
+
const symbol = checker.getSymbolAtLocation(nsName);
|
|
448
|
+
const nsType = symbol ? checker.getTypeOfSymbolAtLocation(symbol, nsName) : undefined;
|
|
449
|
+
for (const spec of specs) {
|
|
450
|
+
if (spec.module !== moduleSpec || found.has(spec.id))
|
|
451
|
+
continue;
|
|
452
|
+
const prop = nsType?.getProperty?.(spec.exportName);
|
|
453
|
+
if (!prop)
|
|
454
|
+
continue;
|
|
455
|
+
const type = getTypeFromSpec(prop, spec);
|
|
456
|
+
if (type)
|
|
457
|
+
found.set(spec.id, type);
|
|
458
|
+
}
|
|
459
|
+
addFromModuleSymbol(moduleSymbol, moduleSpec);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
else if (binding.name) {
|
|
463
|
+
const symbol = checker.getSymbolAtLocation(binding.name);
|
|
464
|
+
const nsType = symbol ? checker.getTypeOfSymbolAtLocation(symbol, binding.name) : undefined;
|
|
465
|
+
for (const spec of specs) {
|
|
466
|
+
if (spec.module !== moduleSpec || found.has(spec.id))
|
|
467
|
+
continue;
|
|
468
|
+
const prop = nsType?.getProperty?.(spec.exportName);
|
|
469
|
+
if (!prop)
|
|
470
|
+
continue;
|
|
471
|
+
const type = getTypeFromSpec(prop, spec);
|
|
472
|
+
if (type)
|
|
473
|
+
found.set(spec.id, type);
|
|
474
|
+
}
|
|
475
|
+
addFromModuleSymbol(moduleSymbol, moduleSpec);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
return specs.filter((s) => found.has(s.id)).map((s) => ({ id: s.id, type: found.get(s.id) }));
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Resolve an export symbol to the symbol it aliases when present (re-exports and import-then-export),
|
|
483
|
+
* so we derive the type from the target file for cross-file type resolution.
|
|
484
|
+
*/
|
|
485
|
+
const resolveExportSymbol = (symbol, checker, tsMod) => resolveAliasedSymbol(symbol, checker, tsMod);
|
|
486
|
+
/** Get the base GenericType from a generic instantiation (TypeReference). */
|
|
487
|
+
const getGenericBase = (type, checker) => getTypeReferenceTarget(type, checker);
|
|
488
|
+
/** Get the symbol representing the "root" type for comparison (handles GenericType and TypeReference). */
|
|
489
|
+
const getComparisonSymbol = (type, checker) => {
|
|
490
|
+
const base = getGenericBase(type, checker);
|
|
491
|
+
if (base?.symbol)
|
|
492
|
+
return base.symbol;
|
|
493
|
+
return getTypeSymbol(type);
|
|
494
|
+
};
|
|
495
|
+
/**
|
|
496
|
+
* Assignability and serialization may use internal TS APIs or heuristics. When those
|
|
497
|
+
* paths throw, errors are caught and behavior degrades (e.g. "not assignable" or a
|
|
498
|
+
* safe fallback node); no errors are thrown to the caller. Use session option
|
|
499
|
+
* onInternalError for observability when provided.
|
|
500
|
+
*/
|
|
501
|
+
/**
|
|
502
|
+
* Check assignability. In strict mode, uses only checker.isTypeAssignableTo.
|
|
503
|
+
* In compatibility mode, adds fallbacks for generic instantiations and inheritance
|
|
504
|
+
* when types come from different files. Union sources require ALL constituents to
|
|
505
|
+
* be assignable (sound semantics).
|
|
506
|
+
*/
|
|
507
|
+
const isAssignableTo = (source, target, checker, tsMod, mode) => {
|
|
508
|
+
if (checker.isTypeAssignableTo(source, target))
|
|
509
|
+
return true;
|
|
510
|
+
if (mode === "strict")
|
|
511
|
+
return false;
|
|
512
|
+
const tgtBase = getGenericBase(target, checker);
|
|
513
|
+
const tgtSymbol = tgtBase?.symbol ?? getTypeSymbol(target);
|
|
514
|
+
const matchSymbol = (sym) => sym && tgtSymbol && sym === tgtSymbol;
|
|
515
|
+
if (source.isUnion()) {
|
|
516
|
+
const constituents = source.types;
|
|
517
|
+
if (!constituents.every((t) => isAssignableTo(t, target, checker, tsMod, mode)))
|
|
518
|
+
return false;
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
const srcBase = getGenericBase(source, checker);
|
|
522
|
+
if (srcBase?.symbol && matchSymbol(srcBase.symbol))
|
|
523
|
+
return true;
|
|
524
|
+
const tgtTarget = getTypeReferenceTarget(target, checker);
|
|
525
|
+
if (srcBase?.symbol && tgtTarget?.symbol && srcBase.symbol === tgtTarget.symbol)
|
|
526
|
+
return true;
|
|
527
|
+
const srcSymbol = getComparisonSymbol(source, checker);
|
|
528
|
+
if (matchSymbol(srcSymbol))
|
|
529
|
+
return true;
|
|
530
|
+
if ((source.flags & tsMod.TypeFlags.Object) !== 0) {
|
|
531
|
+
const srcSym = source.symbol;
|
|
532
|
+
const isClassOrInterface = srcSym &&
|
|
533
|
+
(srcSym.flags & (tsMod.SymbolFlags.Class | tsMod.SymbolFlags.Interface)) !== 0;
|
|
534
|
+
if (isClassOrInterface) {
|
|
535
|
+
const bases = getBaseTypesInternal(source, checker);
|
|
536
|
+
const hasMatchingBase = (bases ?? []).some((b) => {
|
|
537
|
+
const bSym = getTypeSymbol(b);
|
|
538
|
+
return bSym && matchSymbol(bSym);
|
|
539
|
+
});
|
|
540
|
+
if (hasMatchingBase)
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const tgtName = tgtSymbol?.getName();
|
|
545
|
+
if (tgtName && srcBase?.symbol?.getName() === tgtName)
|
|
546
|
+
return true;
|
|
547
|
+
if (tgtName && srcSymbol?.getName() === tgtName)
|
|
548
|
+
return true;
|
|
549
|
+
return false;
|
|
550
|
+
};
|
|
551
|
+
const serializeExport = (symbol, checker, tsMod, maxDepth, onInternalError, registry) => {
|
|
552
|
+
const resolved = resolveExportSymbol(symbol, checker, tsMod);
|
|
553
|
+
const declaration = resolved.valueDeclaration ?? resolved.declarations?.[0];
|
|
554
|
+
const useDeclaredType = declaration &&
|
|
555
|
+
(tsMod.isTypeAliasDeclaration(declaration) || tsMod.isInterfaceDeclaration(declaration));
|
|
556
|
+
const exportedType = useDeclaredType
|
|
557
|
+
? checker.getDeclaredTypeOfSymbol(resolved)
|
|
558
|
+
: declaration
|
|
559
|
+
? checker.getTypeOfSymbolAtLocation(resolved, declaration)
|
|
560
|
+
: checker.getDeclaredTypeOfSymbol(resolved);
|
|
561
|
+
return {
|
|
562
|
+
name: symbol.getName(),
|
|
563
|
+
declarationKind: declaration ? tsMod.SyntaxKind[declaration.kind] : undefined,
|
|
564
|
+
declarationText: declaration ? declaration.getText() : undefined,
|
|
565
|
+
docs: tsMod.displayPartsToString(symbol.getDocumentationComment(checker)) || undefined,
|
|
566
|
+
type: serializeTypeNode(exportedType, checker, tsMod, 0, maxDepth, new Set(), onInternalError, registry),
|
|
567
|
+
};
|
|
568
|
+
};
|
|
569
|
+
const collectImports = (sourceFile, program, tsMod) => {
|
|
570
|
+
const imports = [];
|
|
571
|
+
tsMod.forEachChild(sourceFile, (node) => {
|
|
572
|
+
if (!tsMod.isImportDeclaration(node))
|
|
573
|
+
return;
|
|
574
|
+
const moduleSpecifier = node.moduleSpecifier;
|
|
575
|
+
if (!tsMod.isStringLiteral(moduleSpecifier))
|
|
576
|
+
return;
|
|
577
|
+
const spec = moduleSpecifier.text;
|
|
578
|
+
const binding = node.importClause;
|
|
579
|
+
let info = { moduleSpecifier: spec };
|
|
580
|
+
if (binding) {
|
|
581
|
+
if (binding.namedBindings) {
|
|
582
|
+
if (tsMod.isNamedImports(binding.namedBindings)) {
|
|
583
|
+
info = {
|
|
584
|
+
...info,
|
|
585
|
+
importedNames: binding.namedBindings.elements.map((el) => (el.propertyName ?? el.name).getText(sourceFile)),
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
else if (tsMod.isNamespaceImport(binding.namedBindings)) {
|
|
589
|
+
info = {
|
|
590
|
+
...info,
|
|
591
|
+
namespaceImport: binding.namedBindings.name.getText(sourceFile),
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (binding.name) {
|
|
596
|
+
info = { ...info, defaultImport: binding.name.getText(sourceFile) };
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
imports.push(info);
|
|
600
|
+
});
|
|
601
|
+
return imports;
|
|
602
|
+
};
|
|
603
|
+
const createFileSnapshot = (sourceFile, checker, program, tsMod, maxDepth, includeImports, onInternalError, registry) => {
|
|
604
|
+
const filePath = sourceFile.fileName;
|
|
605
|
+
const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
|
|
606
|
+
const exports = moduleSymbol
|
|
607
|
+
? checker
|
|
608
|
+
.getExportsOfModule(moduleSymbol)
|
|
609
|
+
.map((value) => serializeExport(value, checker, tsMod, maxDepth, onInternalError, registry))
|
|
610
|
+
.sort(compareByName)
|
|
611
|
+
: [];
|
|
612
|
+
const imports = includeImports ? collectImports(sourceFile, program, tsMod) : undefined;
|
|
613
|
+
return {
|
|
614
|
+
filePath,
|
|
615
|
+
exports,
|
|
616
|
+
...(imports !== undefined && imports.length > 0 ? { imports } : {}),
|
|
617
|
+
};
|
|
618
|
+
};
|
|
619
|
+
/**
|
|
620
|
+
* Apply a sequence of projection steps to a ts.Type, navigating to a sub-type.
|
|
621
|
+
* Returns undefined when any step fails (e.g. returnType on non-function).
|
|
622
|
+
*/
|
|
623
|
+
const applyProjection = (type, steps, checker, tsMod, opts) => {
|
|
624
|
+
const { onInternalError, targetsByIdMap, assignabilityMode, maxDepth } = opts;
|
|
625
|
+
let current = type;
|
|
626
|
+
for (const step of steps) {
|
|
627
|
+
try {
|
|
628
|
+
switch (step.kind) {
|
|
629
|
+
case "returnType": {
|
|
630
|
+
const sigs = checker.getSignaturesOfType(current, tsMod.SignatureKind.Call);
|
|
631
|
+
if (sigs.length === 0)
|
|
632
|
+
return undefined;
|
|
633
|
+
current = checker.getReturnTypeOfSignature(sigs[0]);
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
case "param": {
|
|
637
|
+
const sigs = checker.getSignaturesOfType(current, tsMod.SignatureKind.Call);
|
|
638
|
+
if (sigs.length === 0)
|
|
639
|
+
return undefined;
|
|
640
|
+
const params = sigs[0].getParameters();
|
|
641
|
+
if (step.index < 0 || step.index >= params.length)
|
|
642
|
+
return undefined;
|
|
643
|
+
const param = params[step.index];
|
|
644
|
+
const decl = param.valueDeclaration ?? param.declarations?.[0];
|
|
645
|
+
current = decl
|
|
646
|
+
? checker.getTypeOfSymbolAtLocation(param, decl)
|
|
647
|
+
: checker.getDeclaredTypeOfSymbol(param);
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
case "typeArg": {
|
|
651
|
+
const args = checker.getTypeArguments(current);
|
|
652
|
+
if (step.index < 0 || step.index >= args.length)
|
|
653
|
+
return undefined;
|
|
654
|
+
current = args[step.index];
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
657
|
+
case "property": {
|
|
658
|
+
const prop = current.getProperty(step.name);
|
|
659
|
+
if (!prop)
|
|
660
|
+
return undefined;
|
|
661
|
+
const decl = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
662
|
+
current = decl
|
|
663
|
+
? checker.getTypeOfSymbolAtLocation(prop, decl)
|
|
664
|
+
: checker.getDeclaredTypeOfSymbol(prop);
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
case "ensure": {
|
|
668
|
+
const target = targetsByIdMap.get(step.targetId);
|
|
669
|
+
if (!target)
|
|
670
|
+
return undefined;
|
|
671
|
+
if (!isAssignableTo(current, target, checker, tsMod, assignabilityMode))
|
|
672
|
+
return undefined;
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
case "predicate": {
|
|
676
|
+
const serialized = serializeTypeNode(current, checker, tsMod, 0, maxDepth, new Set(), onInternalError);
|
|
677
|
+
if (!step.fn(serialized))
|
|
678
|
+
return undefined;
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
default:
|
|
682
|
+
return undefined;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
catch (err) {
|
|
686
|
+
onInternalError?.(err, `applyProjection:${step.kind}`);
|
|
687
|
+
return undefined;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return current;
|
|
691
|
+
};
|
|
692
|
+
export const createTypeInfoApiSession = (options) => {
|
|
693
|
+
let effectiveTypeTargets = options.typeTargets ??
|
|
694
|
+
(options.typeTargetSpecs?.length
|
|
695
|
+
? resolveTypeTargetsFromSpecs(options.program, options.ts, options.typeTargetSpecs)
|
|
696
|
+
: undefined);
|
|
697
|
+
const failWhenNoTargets = options.failWhenNoTargetsResolved !== false;
|
|
698
|
+
if (failWhenNoTargets &&
|
|
699
|
+
options.typeTargetSpecs &&
|
|
700
|
+
options.typeTargetSpecs.length > 0 &&
|
|
701
|
+
(!effectiveTypeTargets || effectiveTypeTargets.length === 0)) {
|
|
702
|
+
const modules = [...new Set(options.typeTargetSpecs.map((s) => s.module))].join(", ");
|
|
703
|
+
throw new Error(`type targets could not be resolved; ensure program includes imports from: ${modules}. ` +
|
|
704
|
+
"Use createTypeTargetBootstrapContent(typeTargetSpecs) to generate a bootstrap file and add it to program rootNames.");
|
|
705
|
+
}
|
|
706
|
+
const checker = options.program.getTypeChecker();
|
|
707
|
+
const descriptors = new Map();
|
|
708
|
+
const snapshotCache = new Map();
|
|
709
|
+
const directoryCache = new Map();
|
|
710
|
+
const maxDepth = options.maxTypeDepth ?? DEFAULT_MAX_DEPTH;
|
|
711
|
+
const assignabilityMode = options.assignabilityMode ?? "compatibility";
|
|
712
|
+
const typeNodeRegistry = new WeakMap();
|
|
713
|
+
const targetsByIdMap = new Map((effectiveTypeTargets ?? []).map((t) => [t.id, t.type]));
|
|
714
|
+
const registerDescriptor = (descriptor) => {
|
|
715
|
+
if (descriptor.type === "file") {
|
|
716
|
+
descriptors.set(`file:${descriptor.path}`, descriptor);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const globs = descriptor.relativeGlobs.join("|");
|
|
720
|
+
descriptors.set(`glob:${descriptor.baseDir}:${descriptor.recursive ? "r" : "nr"}:${globs}`, descriptor);
|
|
721
|
+
};
|
|
722
|
+
const file = (relativePath, queryOptions) => {
|
|
723
|
+
const baseDirResult = validatePathSegment(queryOptions.baseDir, "baseDir");
|
|
724
|
+
if (!baseDirResult.ok)
|
|
725
|
+
return { ok: false, error: "invalid-input" };
|
|
726
|
+
const relativePathResult = validatePathSegment(relativePath, "relativePath");
|
|
727
|
+
if (!relativePathResult.ok)
|
|
728
|
+
return { ok: false, error: "invalid-input" };
|
|
729
|
+
const baseDir = baseDirResult.value;
|
|
730
|
+
const normalizedRelativePath = relativePathResult.value;
|
|
731
|
+
const pathResult = resolvePathUnderBase(baseDir, normalizedRelativePath);
|
|
732
|
+
if (!pathResult.ok) {
|
|
733
|
+
return {
|
|
734
|
+
ok: false,
|
|
735
|
+
error: "path-escapes-base",
|
|
736
|
+
path: resolveRelativePath(baseDir, normalizedRelativePath),
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
const absolutePath = pathResult.path;
|
|
740
|
+
try {
|
|
741
|
+
if (!pathIsUnderBase(baseDir, absolutePath)) {
|
|
742
|
+
return { ok: false, error: "path-escapes-base", path: absolutePath };
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
catch {
|
|
746
|
+
return { ok: false, error: "path-escapes-base", path: absolutePath };
|
|
747
|
+
}
|
|
748
|
+
if (queryOptions.watch) {
|
|
749
|
+
registerDescriptor({
|
|
750
|
+
type: "file",
|
|
751
|
+
path: absolutePath,
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
const sourceFile = options.program.getSourceFile(absolutePath);
|
|
755
|
+
if (!sourceFile) {
|
|
756
|
+
return { ok: false, error: "file-not-in-program", path: absolutePath };
|
|
757
|
+
}
|
|
758
|
+
const cached = snapshotCache.get(absolutePath);
|
|
759
|
+
if (cached !== undefined) {
|
|
760
|
+
return { ok: true, snapshot: cached };
|
|
761
|
+
}
|
|
762
|
+
const snapshot = createFileSnapshot(sourceFile, checker, options.program, options.ts, maxDepth, true, options.onInternalError, typeNodeRegistry);
|
|
763
|
+
snapshotCache.set(absolutePath, snapshot);
|
|
764
|
+
return { ok: true, snapshot };
|
|
765
|
+
};
|
|
766
|
+
const directoryCacheKey = (baseDir, normalizedGlobs, recursive) => `${baseDir}\0${normalizedGlobs.join("\0")}\0${recursive ? "r" : "nr"}`;
|
|
767
|
+
const directory = (relativeGlobs, queryOptions) => {
|
|
768
|
+
const baseDirResult = validatePathSegment(queryOptions.baseDir, "baseDir");
|
|
769
|
+
if (!baseDirResult.ok)
|
|
770
|
+
return [];
|
|
771
|
+
const baseDir = baseDirResult.value;
|
|
772
|
+
const globsResult = validateRelativeGlobs(relativeGlobs, "relativeGlobs");
|
|
773
|
+
if (!globsResult.ok)
|
|
774
|
+
return [];
|
|
775
|
+
const normalizedGlobs = dedupeSorted(globsResult.value);
|
|
776
|
+
const cacheKey = directoryCacheKey(baseDir, normalizedGlobs, queryOptions.recursive ?? false);
|
|
777
|
+
const cached = directoryCache.get(cacheKey);
|
|
778
|
+
if (cached !== undefined)
|
|
779
|
+
return cached;
|
|
780
|
+
if (queryOptions.watch) {
|
|
781
|
+
registerDescriptor({
|
|
782
|
+
type: "glob",
|
|
783
|
+
baseDir: toPosixPath(baseDir),
|
|
784
|
+
relativeGlobs: normalizedGlobs,
|
|
785
|
+
recursive: queryOptions.recursive,
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
const depth = queryOptions.recursive ? undefined : 1;
|
|
789
|
+
const includes = queryOptions.recursive && normalizedGlobs.every((g) => !g.includes("**"))
|
|
790
|
+
? [...normalizedGlobs, ...normalizedGlobs.map((g) => `**/${g}`)]
|
|
791
|
+
: normalizedGlobs;
|
|
792
|
+
const matchedPaths = options.ts.sys.readDirectory(baseDir, TYPE_EXTENSIONS, undefined, includes, depth);
|
|
793
|
+
const normalizedMatches = dedupeSorted(matchedPaths.map((value) => resolveRelativePath(baseDir, value)));
|
|
794
|
+
const underBase = normalizedMatches.filter((filePath) => pathIsUnderBase(baseDir, filePath));
|
|
795
|
+
const filteredMatches = queryOptions.recursive
|
|
796
|
+
? underBase
|
|
797
|
+
: underBase.filter((filePath) => {
|
|
798
|
+
const relativePath = toPosixPath(relative(baseDir, filePath));
|
|
799
|
+
return !relativePath.includes("/");
|
|
800
|
+
});
|
|
801
|
+
const program = options.program;
|
|
802
|
+
const tsMod = options.ts;
|
|
803
|
+
const snapshots = filteredMatches.flatMap((filePath) => {
|
|
804
|
+
const sf = program.getSourceFile(filePath);
|
|
805
|
+
if (!sf)
|
|
806
|
+
return [];
|
|
807
|
+
try {
|
|
808
|
+
return [
|
|
809
|
+
createFileSnapshot(sf, checker, program, tsMod, maxDepth, true, options.onInternalError, typeNodeRegistry),
|
|
810
|
+
];
|
|
811
|
+
}
|
|
812
|
+
catch (err) {
|
|
813
|
+
options.onInternalError?.(err, `directory() serialization for ${filePath}`);
|
|
814
|
+
return [];
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
directoryCache.set(cacheKey, snapshots);
|
|
818
|
+
return snapshots;
|
|
819
|
+
};
|
|
820
|
+
const resolveExport = (baseDir, filePath, exportName) => {
|
|
821
|
+
const result = file(filePath, { baseDir });
|
|
822
|
+
if (!result.ok)
|
|
823
|
+
return undefined;
|
|
824
|
+
return result.snapshot.exports.find((e) => e.name === exportName);
|
|
825
|
+
};
|
|
826
|
+
const apiIsAssignableTo = (node, targetId, projection) => {
|
|
827
|
+
const sourceType = typeNodeRegistry.get(node);
|
|
828
|
+
if (!sourceType)
|
|
829
|
+
return false;
|
|
830
|
+
const targetType = targetsByIdMap.get(targetId);
|
|
831
|
+
if (!targetType)
|
|
832
|
+
return false;
|
|
833
|
+
const projected = projection?.length
|
|
834
|
+
? applyProjection(sourceType, projection, checker, options.ts, {
|
|
835
|
+
onInternalError: options.onInternalError,
|
|
836
|
+
targetsByIdMap,
|
|
837
|
+
assignabilityMode,
|
|
838
|
+
maxDepth,
|
|
839
|
+
})
|
|
840
|
+
: sourceType;
|
|
841
|
+
if (!projected)
|
|
842
|
+
return false;
|
|
843
|
+
return isAssignableTo(projected, targetType, checker, options.ts, assignabilityMode);
|
|
844
|
+
};
|
|
845
|
+
return {
|
|
846
|
+
api: {
|
|
847
|
+
file,
|
|
848
|
+
directory,
|
|
849
|
+
resolveExport,
|
|
850
|
+
isAssignableTo: apiIsAssignableTo,
|
|
851
|
+
},
|
|
852
|
+
consumeDependencies: () => [...descriptors.values()],
|
|
853
|
+
};
|
|
854
|
+
};
|
|
855
|
+
export const createTypeInfoApiSessionFactory = (options) => () => createTypeInfoApiSession(options);
|