@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,1228 @@
|
|
|
1
|
+
import { relative } from "node:path";
|
|
2
|
+
import type * as ts from "typescript";
|
|
3
|
+
import {
|
|
4
|
+
type CreateTypeInfoApiSession,
|
|
5
|
+
type ExportedTypeInfo,
|
|
6
|
+
type FileSnapshotResult,
|
|
7
|
+
type TypeInfoApi,
|
|
8
|
+
type TypeInfoApiSession,
|
|
9
|
+
type TypeInfoFileQueryOptions,
|
|
10
|
+
type TypeInfoFileSnapshot,
|
|
11
|
+
type TypeProjectionStep,
|
|
12
|
+
type WatchDependencyDescriptor,
|
|
13
|
+
} from "./types.js";
|
|
14
|
+
import type {
|
|
15
|
+
AnyTypeNode,
|
|
16
|
+
ArrayTypeNode,
|
|
17
|
+
ConditionalTypeNode,
|
|
18
|
+
ConstructorTypeNode,
|
|
19
|
+
EnumTypeNode,
|
|
20
|
+
FunctionSignature,
|
|
21
|
+
FunctionTypeNode,
|
|
22
|
+
IndexedAccessTypeNode,
|
|
23
|
+
IndexSignatureInfo,
|
|
24
|
+
ImportInfo,
|
|
25
|
+
IntersectionTypeNode,
|
|
26
|
+
LiteralTypeNode,
|
|
27
|
+
MappedTypeNode,
|
|
28
|
+
NeverTypeNode,
|
|
29
|
+
ObjectProperty,
|
|
30
|
+
ObjectTypeNode,
|
|
31
|
+
OverloadSetTypeNode,
|
|
32
|
+
PrimitiveTypeNode,
|
|
33
|
+
ReferenceTypeNode,
|
|
34
|
+
TemplateLiteralTypeNode,
|
|
35
|
+
TupleTypeNode,
|
|
36
|
+
TypeNode,
|
|
37
|
+
TypeOperatorTypeNode,
|
|
38
|
+
TypeParameter,
|
|
39
|
+
TypeTargetSpec,
|
|
40
|
+
UnionTypeNode,
|
|
41
|
+
UnknownTypeNode,
|
|
42
|
+
} from "./types.js";
|
|
43
|
+
import {
|
|
44
|
+
getAliasedSymbol as resolveAliasedSymbol,
|
|
45
|
+
getBaseTypes as getBaseTypesInternal,
|
|
46
|
+
getIndexInfosOfType,
|
|
47
|
+
getSymbolExports,
|
|
48
|
+
getTypeReferenceTarget,
|
|
49
|
+
getTypeSymbol,
|
|
50
|
+
FALLBACK_OBJECT_FLAGS_MAPPED,
|
|
51
|
+
} from "./internal/tsInternal.js";
|
|
52
|
+
import {
|
|
53
|
+
dedupeSorted,
|
|
54
|
+
pathIsUnderBase,
|
|
55
|
+
resolvePathUnderBase,
|
|
56
|
+
resolveRelativePath,
|
|
57
|
+
toPosixPath,
|
|
58
|
+
} from "./internal/path.js";
|
|
59
|
+
import { validatePathSegment, validateRelativeGlobs } from "./internal/validation.js";
|
|
60
|
+
|
|
61
|
+
const TYPE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".d.ts"];
|
|
62
|
+
const DEFAULT_MAX_DEPTH = 6;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Pre-resolved type for structural assignability checks.
|
|
66
|
+
* Host resolves ts.Type from the program (e.g. Fx from @typed/fx) and passes it.
|
|
67
|
+
*/
|
|
68
|
+
export interface ResolvedTypeTarget {
|
|
69
|
+
readonly id: string;
|
|
70
|
+
readonly type: ts.Type;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface CreateTypeInfoApiSessionOptions {
|
|
74
|
+
readonly ts: typeof import("typescript");
|
|
75
|
+
readonly program: ts.Program;
|
|
76
|
+
readonly maxTypeDepth?: number;
|
|
77
|
+
/** Pre-resolved types for assignability checks. Takes precedence over typeTargetSpecs when both exist. */
|
|
78
|
+
readonly typeTargets?: readonly ResolvedTypeTarget[];
|
|
79
|
+
/**
|
|
80
|
+
* Specs to resolve from program imports for assignability checks.
|
|
81
|
+
* Resolution happens internally; use this instead of typeTargets when you have module+export specs.
|
|
82
|
+
*/
|
|
83
|
+
readonly typeTargetSpecs?: readonly TypeTargetSpec[];
|
|
84
|
+
/**
|
|
85
|
+
* When true (default) and typeTargetSpecs are provided but resolution finds zero targets,
|
|
86
|
+
* session creation throws. Set false to allow empty resolution (assignableTo will be undefined for all exports).
|
|
87
|
+
*/
|
|
88
|
+
readonly failWhenNoTargetsResolved?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Assignability check mode.
|
|
91
|
+
* - "strict": Use only checker.isTypeAssignableTo; no heuristic fallbacks. Correct TS semantics.
|
|
92
|
+
* - "compatibility" (default): Use checker.isTypeAssignableTo first, then fallbacks for generic/inheritance
|
|
93
|
+
* when types come from different files (e.g. fixture vs bootstrap). Union sources require ALL
|
|
94
|
+
* constituents to be assignable (sound union semantics).
|
|
95
|
+
*/
|
|
96
|
+
readonly assignabilityMode?: "strict" | "compatibility";
|
|
97
|
+
/**
|
|
98
|
+
* Optional callback when an internal TS API or fallback path catches an error.
|
|
99
|
+
* Assignability and serialization may degrade (e.g. "not assignable" or safe fallback) without throwing.
|
|
100
|
+
* Use for observability in production; default behavior is unchanged when not provided.
|
|
101
|
+
*/
|
|
102
|
+
readonly onInternalError?: (err: unknown, context: string) => void;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const compareByName = <T extends { readonly name: string }>(a: T, b: T): number =>
|
|
106
|
+
a.name.localeCompare(b.name);
|
|
107
|
+
|
|
108
|
+
const toOptionalFlag = (symbol: ts.Symbol, tsMod: typeof import("typescript")): boolean =>
|
|
109
|
+
(symbol.flags & tsMod.SymbolFlags.Optional) !== 0;
|
|
110
|
+
|
|
111
|
+
const hasReadonlyModifier = (
|
|
112
|
+
declaration: ts.Declaration | undefined,
|
|
113
|
+
tsMod: typeof import("typescript"),
|
|
114
|
+
): boolean => {
|
|
115
|
+
if (!declaration || !tsMod.canHaveModifiers(declaration)) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const modifiers = tsMod.getModifiers(declaration);
|
|
120
|
+
return modifiers?.some((modifier) => modifier.kind === tsMod.SyntaxKind.ReadonlyKeyword) ?? false;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const asLiteral = (
|
|
124
|
+
type: ts.Type,
|
|
125
|
+
checker: ts.TypeChecker,
|
|
126
|
+
tsMod: typeof import("typescript"),
|
|
127
|
+
): LiteralTypeNode | undefined => {
|
|
128
|
+
const text = checker.typeToString(type);
|
|
129
|
+
if (
|
|
130
|
+
(type.flags & tsMod.TypeFlags.StringLiteral) !== 0 ||
|
|
131
|
+
(type.flags & tsMod.TypeFlags.NumberLiteral) !== 0 ||
|
|
132
|
+
(type.flags & tsMod.TypeFlags.BooleanLiteral) !== 0 ||
|
|
133
|
+
(type.flags & tsMod.TypeFlags.BigIntLiteral) !== 0
|
|
134
|
+
) {
|
|
135
|
+
return { kind: "literal", text };
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const serializeFunctionSignature = (
|
|
141
|
+
signature: ts.Signature,
|
|
142
|
+
checker: ts.TypeChecker,
|
|
143
|
+
tsMod: typeof import("typescript"),
|
|
144
|
+
depth: number,
|
|
145
|
+
maxDepth: number,
|
|
146
|
+
visited: Set<string>,
|
|
147
|
+
onInternalError?: (err: unknown, context: string) => void,
|
|
148
|
+
registry?: WeakMap<TypeNode, ts.Type>,
|
|
149
|
+
): { parameters: readonly TypeParameter[]; returnType: TypeNode } => {
|
|
150
|
+
const parameters: TypeParameter[] = signature.getParameters().map((parameter) => {
|
|
151
|
+
const declaration = parameter.valueDeclaration ?? parameter.declarations?.[0];
|
|
152
|
+
const parameterType = declaration
|
|
153
|
+
? checker.getTypeOfSymbolAtLocation(parameter, declaration)
|
|
154
|
+
: checker.getDeclaredTypeOfSymbol(parameter);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
name: parameter.getName(),
|
|
158
|
+
optional: toOptionalFlag(parameter, tsMod),
|
|
159
|
+
type: serializeTypeNode(parameterType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const returnType = serializeTypeNode(
|
|
164
|
+
checker.getReturnTypeOfSignature(signature),
|
|
165
|
+
checker,
|
|
166
|
+
tsMod,
|
|
167
|
+
depth + 1,
|
|
168
|
+
maxDepth,
|
|
169
|
+
visited,
|
|
170
|
+
onInternalError,
|
|
171
|
+
registry,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
parameters: parameters.sort(compareByName),
|
|
176
|
+
returnType,
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const serializeObjectProperties = (
|
|
181
|
+
type: ts.Type,
|
|
182
|
+
checker: ts.TypeChecker,
|
|
183
|
+
tsMod: typeof import("typescript"),
|
|
184
|
+
depth: number,
|
|
185
|
+
maxDepth: number,
|
|
186
|
+
visited: Set<string>,
|
|
187
|
+
onInternalError?: (err: unknown, context: string) => void,
|
|
188
|
+
registry?: WeakMap<TypeNode, ts.Type>,
|
|
189
|
+
): readonly ObjectProperty[] => {
|
|
190
|
+
const properties = checker.getPropertiesOfType(type);
|
|
191
|
+
const snapshots = properties.map((property): ObjectProperty => {
|
|
192
|
+
const declaration = property.valueDeclaration ?? property.declarations?.[0];
|
|
193
|
+
const propertyType = declaration
|
|
194
|
+
? checker.getTypeOfSymbolAtLocation(property, declaration)
|
|
195
|
+
: checker.getDeclaredTypeOfSymbol(property);
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
name: property.getName(),
|
|
199
|
+
optional: toOptionalFlag(property, tsMod),
|
|
200
|
+
readonly: hasReadonlyModifier(declaration, tsMod),
|
|
201
|
+
type: serializeTypeNode(propertyType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
return snapshots.sort(compareByName);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const serializeIndexSignature = (
|
|
209
|
+
type: ts.Type,
|
|
210
|
+
checker: ts.TypeChecker,
|
|
211
|
+
tsMod: typeof import("typescript"),
|
|
212
|
+
depth: number,
|
|
213
|
+
maxDepth: number,
|
|
214
|
+
visited: Set<string>,
|
|
215
|
+
onInternalError?: (err: unknown, context: string) => void,
|
|
216
|
+
registry?: WeakMap<TypeNode, ts.Type>,
|
|
217
|
+
): IndexSignatureInfo | undefined => {
|
|
218
|
+
const infos = getIndexInfosOfType(type, checker);
|
|
219
|
+
if (!infos || infos.length === 0) return undefined;
|
|
220
|
+
const first = infos[0];
|
|
221
|
+
if (!first) return undefined;
|
|
222
|
+
return {
|
|
223
|
+
keyType: serializeTypeNode(first.keyType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
224
|
+
valueType: serializeTypeNode(first.type, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
225
|
+
readonly: first.isReadonly ?? false,
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const primitiveTypeNode = (
|
|
230
|
+
type: ts.Type,
|
|
231
|
+
checker: ts.TypeChecker,
|
|
232
|
+
tsMod: typeof import("typescript"),
|
|
233
|
+
): PrimitiveTypeNode | AnyTypeNode | UnknownTypeNode | NeverTypeNode | undefined => {
|
|
234
|
+
const text = checker.typeToString(type);
|
|
235
|
+
if ((type.flags & tsMod.TypeFlags.Any) !== 0) {
|
|
236
|
+
return { kind: "any", text };
|
|
237
|
+
}
|
|
238
|
+
if ((type.flags & tsMod.TypeFlags.Unknown) !== 0) {
|
|
239
|
+
return { kind: "unknown", text };
|
|
240
|
+
}
|
|
241
|
+
if ((type.flags & tsMod.TypeFlags.Never) !== 0) {
|
|
242
|
+
return { kind: "never", text };
|
|
243
|
+
}
|
|
244
|
+
if (
|
|
245
|
+
(type.flags & tsMod.TypeFlags.StringLike) !== 0 ||
|
|
246
|
+
(type.flags & tsMod.TypeFlags.NumberLike) !== 0 ||
|
|
247
|
+
(type.flags & tsMod.TypeFlags.BooleanLike) !== 0 ||
|
|
248
|
+
(type.flags & tsMod.TypeFlags.BigIntLike) !== 0 ||
|
|
249
|
+
(type.flags & tsMod.TypeFlags.ESSymbolLike) !== 0 ||
|
|
250
|
+
(type.flags & tsMod.TypeFlags.Null) !== 0 ||
|
|
251
|
+
(type.flags & tsMod.TypeFlags.Undefined) !== 0 ||
|
|
252
|
+
(type.flags & tsMod.TypeFlags.Void) !== 0
|
|
253
|
+
) {
|
|
254
|
+
return { kind: "primitive", text };
|
|
255
|
+
}
|
|
256
|
+
return undefined;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const UNKNOWN_NODE: UnknownTypeNode = { kind: "unknown", text: "unknown" };
|
|
260
|
+
|
|
261
|
+
const serializeTypeNode = (
|
|
262
|
+
type: ts.Type,
|
|
263
|
+
checker: ts.TypeChecker,
|
|
264
|
+
tsMod: typeof import("typescript"),
|
|
265
|
+
depth: number,
|
|
266
|
+
maxDepth: number,
|
|
267
|
+
visited: Set<string>,
|
|
268
|
+
onInternalError?: (err: unknown, context: string) => void,
|
|
269
|
+
registry?: WeakMap<TypeNode, ts.Type>,
|
|
270
|
+
): TypeNode => {
|
|
271
|
+
const text = checker.typeToString(type);
|
|
272
|
+
const reg = <T extends TypeNode>(node: T): T => {
|
|
273
|
+
registry?.set(node, type);
|
|
274
|
+
return node;
|
|
275
|
+
};
|
|
276
|
+
const typeId = `${(type as { id?: number }).id ?? "anon"}:${text}`;
|
|
277
|
+
|
|
278
|
+
if (depth > maxDepth || visited.has(typeId)) {
|
|
279
|
+
return reg({ kind: "reference", text });
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
visited.add(typeId);
|
|
283
|
+
|
|
284
|
+
const literal = asLiteral(type, checker, tsMod);
|
|
285
|
+
if (literal) {
|
|
286
|
+
return reg(literal);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (type.isUnion()) {
|
|
290
|
+
const union: UnionTypeNode = {
|
|
291
|
+
kind: "union",
|
|
292
|
+
text,
|
|
293
|
+
elements: type.types.map((value) =>
|
|
294
|
+
serializeTypeNode(value, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
295
|
+
),
|
|
296
|
+
};
|
|
297
|
+
return reg(union);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (type.isIntersection()) {
|
|
301
|
+
const intersection: IntersectionTypeNode = {
|
|
302
|
+
kind: "intersection",
|
|
303
|
+
text,
|
|
304
|
+
elements: type.types.map((value) =>
|
|
305
|
+
serializeTypeNode(value, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
306
|
+
),
|
|
307
|
+
};
|
|
308
|
+
return reg(intersection);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const primitive = primitiveTypeNode(type, checker, tsMod);
|
|
312
|
+
if (primitive) {
|
|
313
|
+
return reg(primitive);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if ((type.flags & tsMod.TypeFlags.Enum) !== 0) {
|
|
317
|
+
const members: { name: string; value?: string | number }[] = [];
|
|
318
|
+
try {
|
|
319
|
+
const props = checker.getPropertiesOfType(type);
|
|
320
|
+
for (const sym of props) {
|
|
321
|
+
const name = sym.name;
|
|
322
|
+
let value: string | number | undefined;
|
|
323
|
+
const decl = sym.valueDeclaration;
|
|
324
|
+
if (decl && tsMod.isEnumMember?.(decl) && decl.initializer) {
|
|
325
|
+
const init = decl.initializer;
|
|
326
|
+
if (tsMod.isNumericLiteral?.(init)) {
|
|
327
|
+
value = parseInt((init as { text: string }).text, 10);
|
|
328
|
+
} else if (tsMod.isStringLiteral?.(init) || tsMod.isNoSubstitutionTemplateLiteral?.(init)) {
|
|
329
|
+
value = (init as { text: string }).text;
|
|
330
|
+
} else {
|
|
331
|
+
const cv = (checker as ts.TypeChecker & { getConstantValue?(n: ts.Node): string | number | undefined }).getConstantValue?.(init);
|
|
332
|
+
if (cv !== undefined) value = cv;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
members.push(value !== undefined ? { name, value } : { name });
|
|
336
|
+
}
|
|
337
|
+
} catch (err) {
|
|
338
|
+
onInternalError?.(err, "enum-members");
|
|
339
|
+
}
|
|
340
|
+
const enumNode: EnumTypeNode = {
|
|
341
|
+
kind: "enum",
|
|
342
|
+
text,
|
|
343
|
+
members: members.length > 0 ? members : undefined,
|
|
344
|
+
};
|
|
345
|
+
return reg(enumNode);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if ((type.flags & tsMod.TypeFlags.Conditional) !== 0) {
|
|
349
|
+
const ct = type as ts.Type & {
|
|
350
|
+
checkType: ts.Type;
|
|
351
|
+
extendsType: ts.Type;
|
|
352
|
+
resolvedTrueType?: ts.Type;
|
|
353
|
+
resolvedFalseType?: ts.Type;
|
|
354
|
+
root?: { node?: { trueType?: ts.TypeNode; falseType?: ts.TypeNode } };
|
|
355
|
+
};
|
|
356
|
+
const trueType =
|
|
357
|
+
ct.resolvedTrueType ??
|
|
358
|
+
(ct.root?.node?.trueType
|
|
359
|
+
? (checker as ts.TypeChecker & { getTypeFromTypeNode?(n: ts.TypeNode): ts.Type }).getTypeFromTypeNode?.(ct.root.node.trueType!)
|
|
360
|
+
: undefined);
|
|
361
|
+
const falseType =
|
|
362
|
+
ct.resolvedFalseType ??
|
|
363
|
+
(ct.root?.node?.falseType
|
|
364
|
+
? (checker as ts.TypeChecker & { getTypeFromTypeNode?(n: ts.TypeNode): ts.Type }).getTypeFromTypeNode?.(ct.root.node.falseType!)
|
|
365
|
+
: undefined);
|
|
366
|
+
const conditional: ConditionalTypeNode = {
|
|
367
|
+
kind: "conditional",
|
|
368
|
+
text,
|
|
369
|
+
checkType: serializeTypeNode(ct.checkType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
370
|
+
extendsType: serializeTypeNode(ct.extendsType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
371
|
+
trueType: trueType
|
|
372
|
+
? serializeTypeNode(trueType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)
|
|
373
|
+
: UNKNOWN_NODE,
|
|
374
|
+
falseType: falseType
|
|
375
|
+
? serializeTypeNode(falseType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)
|
|
376
|
+
: UNKNOWN_NODE,
|
|
377
|
+
};
|
|
378
|
+
return reg(conditional);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if ((type.flags & tsMod.TypeFlags.IndexedAccess) !== 0) {
|
|
382
|
+
const iat = type as ts.Type & { objectType: ts.Type; indexType: ts.Type };
|
|
383
|
+
const indexed: IndexedAccessTypeNode = {
|
|
384
|
+
kind: "indexedAccess",
|
|
385
|
+
text,
|
|
386
|
+
objectType: serializeTypeNode(iat.objectType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
387
|
+
indexType: serializeTypeNode(iat.indexType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
388
|
+
};
|
|
389
|
+
return reg(indexed);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if ((type.flags & tsMod.TypeFlags.TemplateLiteral) !== 0) {
|
|
393
|
+
const tlt = type as ts.Type & { texts?: readonly string[]; types?: readonly ts.Type[] };
|
|
394
|
+
const texts = tlt.texts ?? [];
|
|
395
|
+
const types = tlt.types ?? [];
|
|
396
|
+
const template: TemplateLiteralTypeNode = {
|
|
397
|
+
kind: "templateLiteral",
|
|
398
|
+
text,
|
|
399
|
+
texts,
|
|
400
|
+
types: types.map((t) => serializeTypeNode(t, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)),
|
|
401
|
+
};
|
|
402
|
+
return reg(template);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const objType = type as ts.Type & { objectFlags?: number };
|
|
406
|
+
const mappedFlag = tsMod.ObjectFlags?.Mapped ?? FALLBACK_OBJECT_FLAGS_MAPPED;
|
|
407
|
+
if (objType.objectFlags !== undefined && (objType.objectFlags & mappedFlag) !== 0) {
|
|
408
|
+
const mt = type as ts.Type & {
|
|
409
|
+
constraint?: ts.Type;
|
|
410
|
+
templateType?: ts.Type;
|
|
411
|
+
mappedType?: ts.Type;
|
|
412
|
+
modifierFlags?: number;
|
|
413
|
+
};
|
|
414
|
+
const constraintType = mt.constraint ?? (mt as ts.Type & { typeParameter?: { constraint?: ts.Type } }).typeParameter?.constraint;
|
|
415
|
+
const mappedType = mt.templateType ?? mt.mappedType;
|
|
416
|
+
const mapped: MappedTypeNode = {
|
|
417
|
+
kind: "mapped",
|
|
418
|
+
text,
|
|
419
|
+
constraintType: constraintType
|
|
420
|
+
? serializeTypeNode(constraintType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)
|
|
421
|
+
: UNKNOWN_NODE,
|
|
422
|
+
mappedType: mappedType
|
|
423
|
+
? serializeTypeNode(mappedType, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)
|
|
424
|
+
: UNKNOWN_NODE,
|
|
425
|
+
};
|
|
426
|
+
return reg(mapped);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if ((type.flags & tsMod.TypeFlags.Index) !== 0) {
|
|
430
|
+
const idxType = type as ts.Type & { type: ts.Type };
|
|
431
|
+
const typeOperator: TypeOperatorTypeNode = {
|
|
432
|
+
kind: "typeOperator",
|
|
433
|
+
text,
|
|
434
|
+
operator: "keyof",
|
|
435
|
+
type: serializeTypeNode(idxType.type, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
436
|
+
};
|
|
437
|
+
return reg(typeOperator);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (checker.isTupleType(type)) {
|
|
441
|
+
const typeArguments = checker.getTypeArguments(type as ts.TypeReference);
|
|
442
|
+
const tuple: TupleTypeNode = {
|
|
443
|
+
kind: "tuple",
|
|
444
|
+
text,
|
|
445
|
+
elements: typeArguments.map((value) =>
|
|
446
|
+
serializeTypeNode(value, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
447
|
+
),
|
|
448
|
+
};
|
|
449
|
+
return reg(tuple);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (checker.isArrayType(type)) {
|
|
453
|
+
const typeArguments = checker.getTypeArguments(type as ts.TypeReference);
|
|
454
|
+
const element = typeArguments[0];
|
|
455
|
+
const array: ArrayTypeNode = {
|
|
456
|
+
kind: "array",
|
|
457
|
+
text,
|
|
458
|
+
elements: element
|
|
459
|
+
? [serializeTypeNode(element, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry)]
|
|
460
|
+
: [UNKNOWN_NODE],
|
|
461
|
+
};
|
|
462
|
+
return reg(array);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const referenceArguments = checker.getTypeArguments(type as ts.TypeReference);
|
|
466
|
+
if (referenceArguments.length > 0) {
|
|
467
|
+
const ref: ReferenceTypeNode = {
|
|
468
|
+
kind: "reference",
|
|
469
|
+
text,
|
|
470
|
+
typeArguments: referenceArguments.map((value) =>
|
|
471
|
+
serializeTypeNode(value, checker, tsMod, depth + 1, maxDepth, visited, onInternalError, registry),
|
|
472
|
+
),
|
|
473
|
+
};
|
|
474
|
+
return reg(ref);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const constructSignatures = checker.getSignaturesOfType(type, tsMod.SignatureKind.Construct);
|
|
478
|
+
if (constructSignatures.length > 0) {
|
|
479
|
+
const sig = serializeFunctionSignature(
|
|
480
|
+
constructSignatures[0],
|
|
481
|
+
checker,
|
|
482
|
+
tsMod,
|
|
483
|
+
depth,
|
|
484
|
+
maxDepth,
|
|
485
|
+
visited,
|
|
486
|
+
onInternalError,
|
|
487
|
+
registry,
|
|
488
|
+
);
|
|
489
|
+
const ctor: ConstructorTypeNode = {
|
|
490
|
+
kind: "constructor",
|
|
491
|
+
text,
|
|
492
|
+
parameters: sig.parameters,
|
|
493
|
+
returnType: sig.returnType,
|
|
494
|
+
};
|
|
495
|
+
return reg(ctor);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const callSignatures = checker.getSignaturesOfType(type, tsMod.SignatureKind.Call);
|
|
499
|
+
if (callSignatures.length > 0) {
|
|
500
|
+
if (callSignatures.length > 1) {
|
|
501
|
+
const signatures: FunctionSignature[] = callSignatures.map((s) => {
|
|
502
|
+
const serialized = serializeFunctionSignature(s, checker, tsMod, depth, maxDepth, visited, onInternalError, registry);
|
|
503
|
+
return { parameters: serialized.parameters, returnType: serialized.returnType };
|
|
504
|
+
});
|
|
505
|
+
const overload: OverloadSetTypeNode = { kind: "overloadSet", text, signatures };
|
|
506
|
+
return reg(overload);
|
|
507
|
+
}
|
|
508
|
+
const signature = serializeFunctionSignature(
|
|
509
|
+
callSignatures[0],
|
|
510
|
+
checker,
|
|
511
|
+
tsMod,
|
|
512
|
+
depth,
|
|
513
|
+
maxDepth,
|
|
514
|
+
visited,
|
|
515
|
+
onInternalError,
|
|
516
|
+
registry,
|
|
517
|
+
);
|
|
518
|
+
const fn: FunctionTypeNode = {
|
|
519
|
+
kind: "function",
|
|
520
|
+
text,
|
|
521
|
+
parameters: signature.parameters,
|
|
522
|
+
returnType: signature.returnType,
|
|
523
|
+
};
|
|
524
|
+
return reg(fn);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const indexSig = serializeIndexSignature(type, checker, tsMod, depth, maxDepth, visited, onInternalError, registry);
|
|
528
|
+
const object: ObjectTypeNode = {
|
|
529
|
+
kind: "object",
|
|
530
|
+
text,
|
|
531
|
+
properties: serializeObjectProperties(type, checker, tsMod, depth, maxDepth, visited, onInternalError, registry),
|
|
532
|
+
...(indexSig !== undefined && { indexSignature: indexSig }),
|
|
533
|
+
};
|
|
534
|
+
return reg(object);
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Serialize a TypeScript type to TypeNode. For testing serializer branches (typeOperator, enum, etc).
|
|
539
|
+
* @internal
|
|
540
|
+
*/
|
|
541
|
+
export function serializeTypeForTest(
|
|
542
|
+
type: ts.Type,
|
|
543
|
+
checker: ts.TypeChecker,
|
|
544
|
+
tsMod: typeof import("typescript"),
|
|
545
|
+
maxDepth = 6,
|
|
546
|
+
): TypeNode {
|
|
547
|
+
return serializeTypeNode(type, checker, tsMod, 0, maxDepth, new Set());
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Generate bootstrap file content that imports all modules from typeTargetSpecs.
|
|
552
|
+
* Include this file in the program's rootNames so resolveTypeTargetsFromSpecs can
|
|
553
|
+
* find types without requiring user source to import them.
|
|
554
|
+
*
|
|
555
|
+
* @example
|
|
556
|
+
* ```ts
|
|
557
|
+
* const bootstrapContent = createTypeTargetBootstrapContent(HTTPAPI_TYPE_TARGET_SPECS);
|
|
558
|
+
* const bootstrapPath = join(tmpDir, "__typeTargetBootstrap.ts");
|
|
559
|
+
* writeFileSync(bootstrapPath, bootstrapContent);
|
|
560
|
+
* const program = ts.createProgram([...rootFiles, bootstrapPath], options, host);
|
|
561
|
+
* ```
|
|
562
|
+
*/
|
|
563
|
+
export function createTypeTargetBootstrapContent(
|
|
564
|
+
specs: readonly TypeTargetSpec[],
|
|
565
|
+
): string {
|
|
566
|
+
const seen = new Set<string>();
|
|
567
|
+
const lines: string[] = [
|
|
568
|
+
"/**",
|
|
569
|
+
" * Auto-generated bootstrap for type target resolution.",
|
|
570
|
+
" * Imports canonical modules so resolveTypeTargetsFromSpecs can find types.",
|
|
571
|
+
" * Include in program rootNames when using typeTargetSpecs without user imports.",
|
|
572
|
+
" */",
|
|
573
|
+
];
|
|
574
|
+
for (const spec of specs) {
|
|
575
|
+
if (seen.has(spec.module)) continue;
|
|
576
|
+
seen.add(spec.module);
|
|
577
|
+
const id = spec.module.replace(/\W+/g, "_").replace(/_+/g, "_") || "m";
|
|
578
|
+
lines.push(`import * as _${id} from "${spec.module}";`);
|
|
579
|
+
lines.push(`void _${id};`);
|
|
580
|
+
}
|
|
581
|
+
lines.push("export {};");
|
|
582
|
+
return lines.join("\n");
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Resolve type targets from program imports using the given specs.
|
|
587
|
+
* Scans source files for imports from the specified modules and extracts types.
|
|
588
|
+
* Requires the program to include files that import from these modules (e.g.
|
|
589
|
+
* use createTypeTargetBootstrapContent and add to rootNames).
|
|
590
|
+
*/
|
|
591
|
+
export function resolveTypeTargetsFromSpecs(
|
|
592
|
+
program: ts.Program,
|
|
593
|
+
tsMod: typeof import("typescript"),
|
|
594
|
+
specs: readonly TypeTargetSpec[],
|
|
595
|
+
): ResolvedTypeTarget[] {
|
|
596
|
+
const checker = program.getTypeChecker();
|
|
597
|
+
const found = new Map<string, ts.Type>();
|
|
598
|
+
|
|
599
|
+
const getTypeFromSymbol = (symbol: ts.Symbol): ts.Type | undefined => {
|
|
600
|
+
const aliased = resolveAliasedSymbol(symbol, checker, tsMod);
|
|
601
|
+
const decls = aliased.declarations ?? (aliased.valueDeclaration ? [aliased.valueDeclaration] : []);
|
|
602
|
+
const typeDecl = decls.find(
|
|
603
|
+
(d) =>
|
|
604
|
+
tsMod.isTypeAliasDeclaration(d) ||
|
|
605
|
+
tsMod.isInterfaceDeclaration(d) ||
|
|
606
|
+
tsMod.isTypeParameterDeclaration(d),
|
|
607
|
+
);
|
|
608
|
+
const decl = typeDecl ?? decls[0];
|
|
609
|
+
const useDeclaredType =
|
|
610
|
+
decl &&
|
|
611
|
+
(tsMod.isTypeAliasDeclaration(decl) ||
|
|
612
|
+
tsMod.isInterfaceDeclaration(decl) ||
|
|
613
|
+
tsMod.isTypeParameterDeclaration(decl));
|
|
614
|
+
const type = useDeclaredType
|
|
615
|
+
? checker.getDeclaredTypeOfSymbol(aliased)
|
|
616
|
+
: decl
|
|
617
|
+
? checker.getTypeOfSymbolAtLocation(aliased, decl)
|
|
618
|
+
: checker.getDeclaredTypeOfSymbol(aliased);
|
|
619
|
+
if (!type || (type.flags & tsMod.TypeFlags.Any) !== 0) return undefined;
|
|
620
|
+
return type;
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
const getTypeFromSpec = (
|
|
624
|
+
symbol: ts.Symbol,
|
|
625
|
+
spec: (typeof specs)[number],
|
|
626
|
+
): ts.Type | undefined => {
|
|
627
|
+
const primaryType = getTypeFromSymbol(symbol);
|
|
628
|
+
if (!primaryType || !spec.typeMember) return primaryType;
|
|
629
|
+
const aliased = resolveAliasedSymbol(symbol, checker, tsMod);
|
|
630
|
+
const exports = getSymbolExports(aliased);
|
|
631
|
+
if (!exports) return primaryType;
|
|
632
|
+
const memberSymbol = Array.from(exports.values()).find(
|
|
633
|
+
(s) => s.getName() === spec.typeMember,
|
|
634
|
+
);
|
|
635
|
+
if (!memberSymbol) return primaryType;
|
|
636
|
+
const memberType = checker.getDeclaredTypeOfSymbol(memberSymbol);
|
|
637
|
+
if (!memberType || (memberType.flags & tsMod.TypeFlags.Any) !== 0) return primaryType;
|
|
638
|
+
return memberType;
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
const addFromModuleSymbol = (moduleSymbol: ts.Symbol | undefined, moduleSpec: string): void => {
|
|
642
|
+
if (!moduleSymbol) return;
|
|
643
|
+
const exportsByName = new Map(
|
|
644
|
+
checker.getExportsOfModule(moduleSymbol).map((moduleExport) => [
|
|
645
|
+
moduleExport.getName(),
|
|
646
|
+
moduleExport,
|
|
647
|
+
]),
|
|
648
|
+
);
|
|
649
|
+
for (const spec of specs) {
|
|
650
|
+
if (spec.module !== moduleSpec || found.has(spec.id)) continue;
|
|
651
|
+
const moduleExport = exportsByName.get(spec.exportName);
|
|
652
|
+
if (!moduleExport) continue;
|
|
653
|
+
const type = getTypeFromSpec(moduleExport, spec);
|
|
654
|
+
if (type) found.set(spec.id, type);
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
for (const sourceFile of program.getSourceFiles()) {
|
|
659
|
+
if (sourceFile.isDeclarationFile) continue;
|
|
660
|
+
tsMod.forEachChild(sourceFile, (node) => {
|
|
661
|
+
if (!tsMod.isImportDeclaration(node)) return;
|
|
662
|
+
const moduleSpecifier = node.moduleSpecifier;
|
|
663
|
+
if (!tsMod.isStringLiteral(moduleSpecifier)) return;
|
|
664
|
+
const moduleSpec = moduleSpecifier.text;
|
|
665
|
+
const binding = node.importClause;
|
|
666
|
+
if (!binding) return;
|
|
667
|
+
const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier);
|
|
668
|
+
|
|
669
|
+
const addNamed = (name: ts.ImportSpecifier) => {
|
|
670
|
+
const exportName = (name.propertyName ?? name.name).getText(sourceFile);
|
|
671
|
+
const spec = specs.find((s) => s.module === moduleSpec && s.exportName === exportName);
|
|
672
|
+
if (!spec || found.has(spec.id)) return;
|
|
673
|
+
const symbol = checker.getSymbolAtLocation(name.name);
|
|
674
|
+
if (!symbol) return;
|
|
675
|
+
const type = getTypeFromSpec(symbol, spec);
|
|
676
|
+
if (type) found.set(spec.id, type);
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
if (binding.namedBindings) {
|
|
680
|
+
if (tsMod.isNamedImports(binding.namedBindings)) {
|
|
681
|
+
for (const elem of binding.namedBindings.elements) {
|
|
682
|
+
addNamed(elem);
|
|
683
|
+
}
|
|
684
|
+
} else if (tsMod.isNamespaceImport(binding.namedBindings)) {
|
|
685
|
+
const nsName = binding.namedBindings.name;
|
|
686
|
+
const symbol = checker.getSymbolAtLocation(nsName);
|
|
687
|
+
const nsType = symbol ? checker.getTypeOfSymbolAtLocation(symbol, nsName) : undefined;
|
|
688
|
+
for (const spec of specs) {
|
|
689
|
+
if (spec.module !== moduleSpec || found.has(spec.id)) continue;
|
|
690
|
+
const prop = nsType?.getProperty?.(spec.exportName);
|
|
691
|
+
if (!prop) continue;
|
|
692
|
+
const type = getTypeFromSpec(prop, spec);
|
|
693
|
+
if (type) found.set(spec.id, type);
|
|
694
|
+
}
|
|
695
|
+
addFromModuleSymbol(moduleSymbol, moduleSpec);
|
|
696
|
+
}
|
|
697
|
+
} else if (binding.name) {
|
|
698
|
+
const symbol = checker.getSymbolAtLocation(binding.name);
|
|
699
|
+
const nsType = symbol ? checker.getTypeOfSymbolAtLocation(symbol, binding.name) : undefined;
|
|
700
|
+
for (const spec of specs) {
|
|
701
|
+
if (spec.module !== moduleSpec || found.has(spec.id)) continue;
|
|
702
|
+
const prop = nsType?.getProperty?.(spec.exportName);
|
|
703
|
+
if (!prop) continue;
|
|
704
|
+
const type = getTypeFromSpec(prop, spec);
|
|
705
|
+
if (type) found.set(spec.id, type);
|
|
706
|
+
}
|
|
707
|
+
addFromModuleSymbol(moduleSymbol, moduleSpec);
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return specs.filter((s) => found.has(s.id)).map((s) => ({ id: s.id, type: found.get(s.id)! }));
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Resolve an export symbol to the symbol it aliases when present (re-exports and import-then-export),
|
|
717
|
+
* so we derive the type from the target file for cross-file type resolution.
|
|
718
|
+
*/
|
|
719
|
+
const resolveExportSymbol = (
|
|
720
|
+
symbol: ts.Symbol,
|
|
721
|
+
checker: ts.TypeChecker,
|
|
722
|
+
tsMod: typeof import("typescript"),
|
|
723
|
+
): ts.Symbol => resolveAliasedSymbol(symbol, checker, tsMod);
|
|
724
|
+
|
|
725
|
+
/** Get the base GenericType from a generic instantiation (TypeReference). */
|
|
726
|
+
const getGenericBase = (
|
|
727
|
+
type: ts.Type,
|
|
728
|
+
checker: ts.TypeChecker,
|
|
729
|
+
): (ts.Type & { symbol?: ts.Symbol }) | undefined => getTypeReferenceTarget(type, checker);
|
|
730
|
+
|
|
731
|
+
/** Get the symbol representing the "root" type for comparison (handles GenericType and TypeReference). */
|
|
732
|
+
const getComparisonSymbol = (
|
|
733
|
+
type: ts.Type,
|
|
734
|
+
checker: ts.TypeChecker,
|
|
735
|
+
): ts.Symbol | undefined => {
|
|
736
|
+
const base = getGenericBase(type, checker);
|
|
737
|
+
if (base?.symbol) return base.symbol;
|
|
738
|
+
return getTypeSymbol(type);
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
type AssignabilityMode = "strict" | "compatibility";
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Assignability and serialization may use internal TS APIs or heuristics. When those
|
|
745
|
+
* paths throw, errors are caught and behavior degrades (e.g. "not assignable" or a
|
|
746
|
+
* safe fallback node); no errors are thrown to the caller. Use session option
|
|
747
|
+
* onInternalError for observability when provided.
|
|
748
|
+
*/
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Check assignability. In strict mode, uses only checker.isTypeAssignableTo.
|
|
752
|
+
* In compatibility mode, adds fallbacks for generic instantiations and inheritance
|
|
753
|
+
* when types come from different files. Union sources require ALL constituents to
|
|
754
|
+
* be assignable (sound semantics).
|
|
755
|
+
*/
|
|
756
|
+
const isAssignableTo = (
|
|
757
|
+
source: ts.Type,
|
|
758
|
+
target: ts.Type,
|
|
759
|
+
checker: ts.TypeChecker,
|
|
760
|
+
tsMod: typeof import("typescript"),
|
|
761
|
+
mode: AssignabilityMode,
|
|
762
|
+
): boolean => {
|
|
763
|
+
if (checker.isTypeAssignableTo(source, target)) return true;
|
|
764
|
+
if (mode === "strict") return false;
|
|
765
|
+
|
|
766
|
+
const tgtBase = getGenericBase(target, checker);
|
|
767
|
+
const tgtSymbol = tgtBase?.symbol ?? getTypeSymbol(target);
|
|
768
|
+
const matchSymbol = (sym: ts.Symbol | undefined) =>
|
|
769
|
+
sym && tgtSymbol && sym === tgtSymbol;
|
|
770
|
+
|
|
771
|
+
if (source.isUnion()) {
|
|
772
|
+
const constituents = source.types;
|
|
773
|
+
if (
|
|
774
|
+
!constituents.every((t) =>
|
|
775
|
+
isAssignableTo(t, target, checker, tsMod, mode),
|
|
776
|
+
)
|
|
777
|
+
)
|
|
778
|
+
return false;
|
|
779
|
+
return true;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const srcBase = getGenericBase(source, checker);
|
|
783
|
+
if (srcBase?.symbol && matchSymbol(srcBase.symbol)) return true;
|
|
784
|
+
|
|
785
|
+
const tgtTarget = getTypeReferenceTarget(target, checker);
|
|
786
|
+
if (srcBase?.symbol && tgtTarget?.symbol && srcBase.symbol === tgtTarget.symbol)
|
|
787
|
+
return true;
|
|
788
|
+
|
|
789
|
+
const srcSymbol = getComparisonSymbol(source, checker);
|
|
790
|
+
if (matchSymbol(srcSymbol)) return true;
|
|
791
|
+
|
|
792
|
+
if ((source.flags & tsMod.TypeFlags.Object) !== 0) {
|
|
793
|
+
const srcSym = (source as ts.Type & { symbol?: ts.Symbol }).symbol;
|
|
794
|
+
const isClassOrInterface =
|
|
795
|
+
srcSym &&
|
|
796
|
+
(srcSym.flags & (tsMod.SymbolFlags.Class | tsMod.SymbolFlags.Interface)) !== 0;
|
|
797
|
+
if (isClassOrInterface) {
|
|
798
|
+
const bases = getBaseTypesInternal(source as ts.InterfaceType, checker);
|
|
799
|
+
const hasMatchingBase = (bases ?? []).some((b) => {
|
|
800
|
+
const bSym = getTypeSymbol(b);
|
|
801
|
+
return bSym && matchSymbol(bSym);
|
|
802
|
+
});
|
|
803
|
+
if (hasMatchingBase) return true;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const tgtName = tgtSymbol?.getName();
|
|
808
|
+
if (tgtName && srcBase?.symbol?.getName() === tgtName) return true;
|
|
809
|
+
if (tgtName && srcSymbol?.getName() === tgtName) return true;
|
|
810
|
+
|
|
811
|
+
return false;
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
const serializeExport = (
|
|
815
|
+
symbol: ts.Symbol,
|
|
816
|
+
checker: ts.TypeChecker,
|
|
817
|
+
tsMod: typeof import("typescript"),
|
|
818
|
+
maxDepth: number,
|
|
819
|
+
onInternalError?: (err: unknown, context: string) => void,
|
|
820
|
+
registry?: WeakMap<TypeNode, ts.Type>,
|
|
821
|
+
): ExportedTypeInfo => {
|
|
822
|
+
const resolved = resolveExportSymbol(symbol, checker, tsMod);
|
|
823
|
+
const declaration = resolved.valueDeclaration ?? resolved.declarations?.[0];
|
|
824
|
+
const useDeclaredType =
|
|
825
|
+
declaration &&
|
|
826
|
+
(tsMod.isTypeAliasDeclaration(declaration) || tsMod.isInterfaceDeclaration(declaration));
|
|
827
|
+
const exportedType = useDeclaredType
|
|
828
|
+
? checker.getDeclaredTypeOfSymbol(resolved)
|
|
829
|
+
: declaration
|
|
830
|
+
? checker.getTypeOfSymbolAtLocation(resolved, declaration)
|
|
831
|
+
: checker.getDeclaredTypeOfSymbol(resolved);
|
|
832
|
+
|
|
833
|
+
return {
|
|
834
|
+
name: symbol.getName(),
|
|
835
|
+
declarationKind: declaration ? tsMod.SyntaxKind[declaration.kind] : undefined,
|
|
836
|
+
declarationText: declaration ? declaration.getText() : undefined,
|
|
837
|
+
docs: tsMod.displayPartsToString(symbol.getDocumentationComment(checker)) || undefined,
|
|
838
|
+
type: serializeTypeNode(exportedType, checker, tsMod, 0, maxDepth, new Set(), onInternalError, registry),
|
|
839
|
+
};
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
const collectImports = (
|
|
843
|
+
sourceFile: ts.SourceFile,
|
|
844
|
+
program: ts.Program,
|
|
845
|
+
tsMod: typeof import("typescript"),
|
|
846
|
+
): ImportInfo[] => {
|
|
847
|
+
const imports: ImportInfo[] = [];
|
|
848
|
+
tsMod.forEachChild(sourceFile, (node) => {
|
|
849
|
+
if (!tsMod.isImportDeclaration(node)) return;
|
|
850
|
+
const moduleSpecifier = node.moduleSpecifier;
|
|
851
|
+
if (!tsMod.isStringLiteral(moduleSpecifier)) return;
|
|
852
|
+
const spec = moduleSpecifier.text;
|
|
853
|
+
const binding = node.importClause;
|
|
854
|
+
let info: ImportInfo = { moduleSpecifier: spec };
|
|
855
|
+
if (binding) {
|
|
856
|
+
if (binding.namedBindings) {
|
|
857
|
+
if (tsMod.isNamedImports(binding.namedBindings)) {
|
|
858
|
+
info = {
|
|
859
|
+
...info,
|
|
860
|
+
importedNames: binding.namedBindings.elements.map((el) =>
|
|
861
|
+
(el.propertyName ?? el.name).getText(sourceFile),
|
|
862
|
+
),
|
|
863
|
+
};
|
|
864
|
+
} else if (tsMod.isNamespaceImport(binding.namedBindings)) {
|
|
865
|
+
info = {
|
|
866
|
+
...info,
|
|
867
|
+
namespaceImport: binding.namedBindings.name.getText(sourceFile),
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if (binding.name) {
|
|
872
|
+
info = { ...info, defaultImport: binding.name.getText(sourceFile) };
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
imports.push(info);
|
|
876
|
+
});
|
|
877
|
+
return imports;
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
const createFileSnapshot = (
|
|
881
|
+
sourceFile: ts.SourceFile,
|
|
882
|
+
checker: ts.TypeChecker,
|
|
883
|
+
program: ts.Program,
|
|
884
|
+
tsMod: typeof import("typescript"),
|
|
885
|
+
maxDepth: number,
|
|
886
|
+
includeImports: boolean,
|
|
887
|
+
onInternalError?: (err: unknown, context: string) => void,
|
|
888
|
+
registry?: WeakMap<TypeNode, ts.Type>,
|
|
889
|
+
): TypeInfoFileSnapshot => {
|
|
890
|
+
const filePath = sourceFile.fileName;
|
|
891
|
+
const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
|
|
892
|
+
const exports =
|
|
893
|
+
moduleSymbol
|
|
894
|
+
? checker
|
|
895
|
+
.getExportsOfModule(moduleSymbol)
|
|
896
|
+
.map((value) =>
|
|
897
|
+
serializeExport(value, checker, tsMod, maxDepth, onInternalError, registry),
|
|
898
|
+
)
|
|
899
|
+
.sort(compareByName)
|
|
900
|
+
: [];
|
|
901
|
+
const imports = includeImports ? collectImports(sourceFile, program, tsMod) : undefined;
|
|
902
|
+
|
|
903
|
+
return {
|
|
904
|
+
filePath,
|
|
905
|
+
exports,
|
|
906
|
+
...(imports !== undefined && imports.length > 0 ? { imports } : {}),
|
|
907
|
+
};
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Apply a sequence of projection steps to a ts.Type, navigating to a sub-type.
|
|
912
|
+
* Returns undefined when any step fails (e.g. returnType on non-function).
|
|
913
|
+
*/
|
|
914
|
+
const applyProjection = (
|
|
915
|
+
type: ts.Type,
|
|
916
|
+
steps: readonly TypeProjectionStep[],
|
|
917
|
+
checker: ts.TypeChecker,
|
|
918
|
+
tsMod: typeof import("typescript"),
|
|
919
|
+
opts: {
|
|
920
|
+
onInternalError?: (err: unknown, context: string) => void;
|
|
921
|
+
targetsByIdMap: Map<string, ts.Type>;
|
|
922
|
+
assignabilityMode: AssignabilityMode;
|
|
923
|
+
maxDepth: number;
|
|
924
|
+
},
|
|
925
|
+
): ts.Type | undefined => {
|
|
926
|
+
const { onInternalError, targetsByIdMap, assignabilityMode, maxDepth } = opts;
|
|
927
|
+
let current: ts.Type = type;
|
|
928
|
+
for (const step of steps) {
|
|
929
|
+
try {
|
|
930
|
+
switch (step.kind) {
|
|
931
|
+
case "returnType": {
|
|
932
|
+
const sigs = checker.getSignaturesOfType(current, tsMod.SignatureKind.Call);
|
|
933
|
+
if (sigs.length === 0) return undefined;
|
|
934
|
+
current = checker.getReturnTypeOfSignature(sigs[0]);
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
case "param": {
|
|
938
|
+
const sigs = checker.getSignaturesOfType(current, tsMod.SignatureKind.Call);
|
|
939
|
+
if (sigs.length === 0) return undefined;
|
|
940
|
+
const params = sigs[0].getParameters();
|
|
941
|
+
if (step.index < 0 || step.index >= params.length) return undefined;
|
|
942
|
+
const param = params[step.index];
|
|
943
|
+
const decl = param.valueDeclaration ?? param.declarations?.[0];
|
|
944
|
+
current = decl
|
|
945
|
+
? checker.getTypeOfSymbolAtLocation(param, decl)
|
|
946
|
+
: checker.getDeclaredTypeOfSymbol(param);
|
|
947
|
+
break;
|
|
948
|
+
}
|
|
949
|
+
case "typeArg": {
|
|
950
|
+
const args = checker.getTypeArguments(current as ts.TypeReference);
|
|
951
|
+
if (step.index < 0 || step.index >= args.length) return undefined;
|
|
952
|
+
current = args[step.index];
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
case "property": {
|
|
956
|
+
const prop = current.getProperty(step.name);
|
|
957
|
+
if (!prop) return undefined;
|
|
958
|
+
const decl = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
959
|
+
current = decl
|
|
960
|
+
? checker.getTypeOfSymbolAtLocation(prop, decl)
|
|
961
|
+
: checker.getDeclaredTypeOfSymbol(prop);
|
|
962
|
+
break;
|
|
963
|
+
}
|
|
964
|
+
case "ensure": {
|
|
965
|
+
const target = targetsByIdMap.get(step.targetId);
|
|
966
|
+
if (!target) return undefined;
|
|
967
|
+
if (!isAssignableTo(current, target, checker, tsMod, assignabilityMode))
|
|
968
|
+
return undefined;
|
|
969
|
+
break;
|
|
970
|
+
}
|
|
971
|
+
case "predicate": {
|
|
972
|
+
const serialized = serializeTypeNode(
|
|
973
|
+
current,
|
|
974
|
+
checker,
|
|
975
|
+
tsMod,
|
|
976
|
+
0,
|
|
977
|
+
maxDepth,
|
|
978
|
+
new Set(),
|
|
979
|
+
onInternalError,
|
|
980
|
+
);
|
|
981
|
+
if (!step.fn(serialized)) return undefined;
|
|
982
|
+
break;
|
|
983
|
+
}
|
|
984
|
+
default:
|
|
985
|
+
return undefined;
|
|
986
|
+
}
|
|
987
|
+
} catch (err) {
|
|
988
|
+
onInternalError?.(err, `applyProjection:${step.kind}`);
|
|
989
|
+
return undefined;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
return current;
|
|
993
|
+
};
|
|
994
|
+
|
|
995
|
+
export const createTypeInfoApiSession = (
|
|
996
|
+
options: CreateTypeInfoApiSessionOptions,
|
|
997
|
+
): TypeInfoApiSession => {
|
|
998
|
+
let effectiveTypeTargets: readonly ResolvedTypeTarget[] | undefined =
|
|
999
|
+
options.typeTargets ??
|
|
1000
|
+
(options.typeTargetSpecs?.length
|
|
1001
|
+
? resolveTypeTargetsFromSpecs(options.program, options.ts, options.typeTargetSpecs)
|
|
1002
|
+
: undefined);
|
|
1003
|
+
|
|
1004
|
+
const failWhenNoTargets = options.failWhenNoTargetsResolved !== false;
|
|
1005
|
+
if (
|
|
1006
|
+
failWhenNoTargets &&
|
|
1007
|
+
options.typeTargetSpecs &&
|
|
1008
|
+
options.typeTargetSpecs.length > 0 &&
|
|
1009
|
+
(!effectiveTypeTargets || effectiveTypeTargets.length === 0)
|
|
1010
|
+
) {
|
|
1011
|
+
const modules = [...new Set(options.typeTargetSpecs.map((s) => s.module))].join(", ");
|
|
1012
|
+
throw new Error(
|
|
1013
|
+
`type targets could not be resolved; ensure program includes imports from: ${modules}. ` +
|
|
1014
|
+
"Use createTypeTargetBootstrapContent(typeTargetSpecs) to generate a bootstrap file and add it to program rootNames.",
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
const checker = options.program.getTypeChecker();
|
|
1018
|
+
const descriptors = new Map<string, WatchDependencyDescriptor>();
|
|
1019
|
+
const snapshotCache = new Map<string, TypeInfoFileSnapshot>();
|
|
1020
|
+
const directoryCache = new Map<string, TypeInfoFileSnapshot[]>();
|
|
1021
|
+
const maxDepth = options.maxTypeDepth ?? DEFAULT_MAX_DEPTH;
|
|
1022
|
+
const assignabilityMode: AssignabilityMode =
|
|
1023
|
+
options.assignabilityMode ?? "compatibility";
|
|
1024
|
+
const typeNodeRegistry = new WeakMap<TypeNode, ts.Type>();
|
|
1025
|
+
const targetsByIdMap = new Map<string, ts.Type>(
|
|
1026
|
+
(effectiveTypeTargets ?? []).map((t) => [t.id, t.type]),
|
|
1027
|
+
);
|
|
1028
|
+
|
|
1029
|
+
const registerDescriptor = (descriptor: WatchDependencyDescriptor): void => {
|
|
1030
|
+
if (descriptor.type === "file") {
|
|
1031
|
+
descriptors.set(`file:${descriptor.path}`, descriptor);
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
const globs = descriptor.relativeGlobs.join("|");
|
|
1036
|
+
descriptors.set(
|
|
1037
|
+
`glob:${descriptor.baseDir}:${descriptor.recursive ? "r" : "nr"}:${globs}`,
|
|
1038
|
+
descriptor,
|
|
1039
|
+
);
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
const file = (
|
|
1043
|
+
relativePath: string,
|
|
1044
|
+
queryOptions: TypeInfoFileQueryOptions,
|
|
1045
|
+
): FileSnapshotResult => {
|
|
1046
|
+
const baseDirResult = validatePathSegment(queryOptions.baseDir, "baseDir");
|
|
1047
|
+
if (!baseDirResult.ok) return { ok: false, error: "invalid-input" };
|
|
1048
|
+
const relativePathResult = validatePathSegment(relativePath, "relativePath");
|
|
1049
|
+
if (!relativePathResult.ok) return { ok: false, error: "invalid-input" };
|
|
1050
|
+
const baseDir = baseDirResult.value;
|
|
1051
|
+
const normalizedRelativePath = relativePathResult.value;
|
|
1052
|
+
|
|
1053
|
+
const pathResult = resolvePathUnderBase(baseDir, normalizedRelativePath);
|
|
1054
|
+
if (!pathResult.ok) {
|
|
1055
|
+
return {
|
|
1056
|
+
ok: false,
|
|
1057
|
+
error: "path-escapes-base",
|
|
1058
|
+
path: resolveRelativePath(baseDir, normalizedRelativePath),
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
const absolutePath = pathResult.path;
|
|
1062
|
+
|
|
1063
|
+
try {
|
|
1064
|
+
if (!pathIsUnderBase(baseDir, absolutePath)) {
|
|
1065
|
+
return { ok: false, error: "path-escapes-base", path: absolutePath };
|
|
1066
|
+
}
|
|
1067
|
+
} catch {
|
|
1068
|
+
return { ok: false, error: "path-escapes-base", path: absolutePath };
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if (queryOptions.watch) {
|
|
1072
|
+
registerDescriptor({
|
|
1073
|
+
type: "file",
|
|
1074
|
+
path: absolutePath,
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
const sourceFile = options.program.getSourceFile(absolutePath);
|
|
1079
|
+
if (!sourceFile) {
|
|
1080
|
+
return { ok: false, error: "file-not-in-program", path: absolutePath };
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
const cached = snapshotCache.get(absolutePath);
|
|
1084
|
+
if (cached !== undefined) {
|
|
1085
|
+
return { ok: true, snapshot: cached };
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
const snapshot = createFileSnapshot(
|
|
1089
|
+
sourceFile,
|
|
1090
|
+
checker,
|
|
1091
|
+
options.program,
|
|
1092
|
+
options.ts,
|
|
1093
|
+
maxDepth,
|
|
1094
|
+
true,
|
|
1095
|
+
options.onInternalError,
|
|
1096
|
+
typeNodeRegistry,
|
|
1097
|
+
);
|
|
1098
|
+
snapshotCache.set(absolutePath, snapshot);
|
|
1099
|
+
return { ok: true, snapshot };
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
const directoryCacheKey = (
|
|
1103
|
+
baseDir: string,
|
|
1104
|
+
normalizedGlobs: readonly string[],
|
|
1105
|
+
recursive: boolean,
|
|
1106
|
+
): string =>
|
|
1107
|
+
`${baseDir}\0${normalizedGlobs.join("\0")}\0${recursive ? "r" : "nr"}`;
|
|
1108
|
+
|
|
1109
|
+
const directory: TypeInfoApi["directory"] = (relativeGlobs, queryOptions) => {
|
|
1110
|
+
const baseDirResult = validatePathSegment(queryOptions.baseDir, "baseDir");
|
|
1111
|
+
if (!baseDirResult.ok) return [];
|
|
1112
|
+
const baseDir = baseDirResult.value;
|
|
1113
|
+
|
|
1114
|
+
const globsResult = validateRelativeGlobs(relativeGlobs, "relativeGlobs");
|
|
1115
|
+
if (!globsResult.ok) return [];
|
|
1116
|
+
const normalizedGlobs = dedupeSorted(globsResult.value);
|
|
1117
|
+
|
|
1118
|
+
const cacheKey = directoryCacheKey(baseDir, normalizedGlobs, queryOptions.recursive ?? false);
|
|
1119
|
+
const cached = directoryCache.get(cacheKey);
|
|
1120
|
+
if (cached !== undefined) return cached;
|
|
1121
|
+
|
|
1122
|
+
if (queryOptions.watch) {
|
|
1123
|
+
registerDescriptor({
|
|
1124
|
+
type: "glob",
|
|
1125
|
+
baseDir: toPosixPath(baseDir),
|
|
1126
|
+
relativeGlobs: normalizedGlobs,
|
|
1127
|
+
recursive: queryOptions.recursive,
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
const depth = queryOptions.recursive ? undefined : 1;
|
|
1132
|
+
const includes =
|
|
1133
|
+
queryOptions.recursive && normalizedGlobs.every((g) => !g.includes("**"))
|
|
1134
|
+
? [...normalizedGlobs, ...normalizedGlobs.map((g) => `**/${g}`)]
|
|
1135
|
+
: normalizedGlobs;
|
|
1136
|
+
|
|
1137
|
+
const matchedPaths = options.ts.sys.readDirectory(
|
|
1138
|
+
baseDir,
|
|
1139
|
+
TYPE_EXTENSIONS,
|
|
1140
|
+
undefined,
|
|
1141
|
+
includes,
|
|
1142
|
+
depth,
|
|
1143
|
+
);
|
|
1144
|
+
|
|
1145
|
+
const normalizedMatches = dedupeSorted(
|
|
1146
|
+
matchedPaths.map((value) => resolveRelativePath(baseDir, value)),
|
|
1147
|
+
);
|
|
1148
|
+
const underBase = normalizedMatches.filter((filePath) => pathIsUnderBase(baseDir, filePath));
|
|
1149
|
+
const filteredMatches = queryOptions.recursive
|
|
1150
|
+
? underBase
|
|
1151
|
+
: underBase.filter((filePath) => {
|
|
1152
|
+
const relativePath = toPosixPath(relative(baseDir, filePath));
|
|
1153
|
+
return !relativePath.includes("/");
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
const program = options.program;
|
|
1157
|
+
const tsMod = options.ts;
|
|
1158
|
+
const snapshots = filteredMatches.flatMap((filePath) => {
|
|
1159
|
+
const sf = program.getSourceFile(filePath);
|
|
1160
|
+
if (!sf) return [];
|
|
1161
|
+
try {
|
|
1162
|
+
return [
|
|
1163
|
+
createFileSnapshot(
|
|
1164
|
+
sf,
|
|
1165
|
+
checker,
|
|
1166
|
+
program,
|
|
1167
|
+
tsMod,
|
|
1168
|
+
maxDepth,
|
|
1169
|
+
true,
|
|
1170
|
+
options.onInternalError,
|
|
1171
|
+
typeNodeRegistry,
|
|
1172
|
+
),
|
|
1173
|
+
];
|
|
1174
|
+
} catch (err) {
|
|
1175
|
+
options.onInternalError?.(err, `directory() serialization for ${filePath}`);
|
|
1176
|
+
return [];
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
directoryCache.set(cacheKey, snapshots);
|
|
1180
|
+
return snapshots;
|
|
1181
|
+
};
|
|
1182
|
+
|
|
1183
|
+
const resolveExport = (
|
|
1184
|
+
baseDir: string,
|
|
1185
|
+
filePath: string,
|
|
1186
|
+
exportName: string,
|
|
1187
|
+
): ExportedTypeInfo | undefined => {
|
|
1188
|
+
const result = file(filePath, { baseDir });
|
|
1189
|
+
if (!result.ok) return undefined;
|
|
1190
|
+
return result.snapshot.exports.find((e) => e.name === exportName);
|
|
1191
|
+
};
|
|
1192
|
+
|
|
1193
|
+
const apiIsAssignableTo = (
|
|
1194
|
+
node: TypeNode,
|
|
1195
|
+
targetId: string,
|
|
1196
|
+
projection?: readonly TypeProjectionStep[],
|
|
1197
|
+
): boolean => {
|
|
1198
|
+
const sourceType = typeNodeRegistry.get(node);
|
|
1199
|
+
if (!sourceType) return false;
|
|
1200
|
+
const targetType = targetsByIdMap.get(targetId);
|
|
1201
|
+
if (!targetType) return false;
|
|
1202
|
+
const projected = projection?.length
|
|
1203
|
+
? applyProjection(sourceType, projection, checker, options.ts, {
|
|
1204
|
+
onInternalError: options.onInternalError,
|
|
1205
|
+
targetsByIdMap,
|
|
1206
|
+
assignabilityMode,
|
|
1207
|
+
maxDepth,
|
|
1208
|
+
})
|
|
1209
|
+
: sourceType;
|
|
1210
|
+
if (!projected) return false;
|
|
1211
|
+
return isAssignableTo(projected, targetType, checker, options.ts, assignabilityMode);
|
|
1212
|
+
};
|
|
1213
|
+
|
|
1214
|
+
return {
|
|
1215
|
+
api: {
|
|
1216
|
+
file,
|
|
1217
|
+
directory,
|
|
1218
|
+
resolveExport,
|
|
1219
|
+
isAssignableTo: apiIsAssignableTo,
|
|
1220
|
+
},
|
|
1221
|
+
consumeDependencies: () => [...descriptors.values()],
|
|
1222
|
+
};
|
|
1223
|
+
};
|
|
1224
|
+
|
|
1225
|
+
export const createTypeInfoApiSessionFactory =
|
|
1226
|
+
(options: CreateTypeInfoApiSessionOptions): CreateTypeInfoApiSession =>
|
|
1227
|
+
() =>
|
|
1228
|
+
createTypeInfoApiSession(options);
|