@player-lang/functional-dsl-generator 0.0.2-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +2146 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/index.legacy-esm.js +2075 -0
- package/dist/index.mjs +2075 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +38 -0
- package/src/__tests__/__snapshots__/generator.test.ts.snap +886 -0
- package/src/__tests__/builder-class-generator.test.ts +627 -0
- package/src/__tests__/cli.test.ts +685 -0
- package/src/__tests__/default-value-generator.test.ts +365 -0
- package/src/__tests__/generator.test.ts +2860 -0
- package/src/__tests__/import-generator.test.ts +444 -0
- package/src/__tests__/path-utils.test.ts +174 -0
- package/src/__tests__/type-collector.test.ts +674 -0
- package/src/__tests__/type-transformer.test.ts +934 -0
- package/src/__tests__/utils.test.ts +597 -0
- package/src/builder-class-generator.ts +254 -0
- package/src/cli.ts +285 -0
- package/src/default-value-generator.ts +307 -0
- package/src/generator.ts +257 -0
- package/src/import-generator.ts +331 -0
- package/src/index.ts +38 -0
- package/src/path-utils.ts +155 -0
- package/src/ts-morph-type-finder.ts +319 -0
- package/src/type-categorizer.ts +131 -0
- package/src/type-collector.ts +296 -0
- package/src/type-resolver.ts +266 -0
- package/src/type-transformer.ts +487 -0
- package/src/utils.ts +762 -0
- package/types/builder-class-generator.d.ts +56 -0
- package/types/cli.d.ts +6 -0
- package/types/default-value-generator.d.ts +74 -0
- package/types/generator.d.ts +102 -0
- package/types/import-generator.d.ts +77 -0
- package/types/index.d.ts +12 -0
- package/types/path-utils.d.ts +65 -0
- package/types/ts-morph-type-finder.d.ts +73 -0
- package/types/type-categorizer.d.ts +46 -0
- package/types/type-collector.d.ts +62 -0
- package/types/type-resolver.d.ts +49 -0
- package/types/type-transformer.d.ts +74 -0
- package/types/utils.d.ts +205 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import type { NodeType, ObjectType, NamedType } from "@xlr-lib/xlr";
|
|
2
|
+
import { isGenericNamedType } from "@xlr-lib/xlr-utils";
|
|
3
|
+
import {
|
|
4
|
+
isObjectType,
|
|
5
|
+
isArrayType,
|
|
6
|
+
isRefType,
|
|
7
|
+
isOrType,
|
|
8
|
+
isAndType,
|
|
9
|
+
isNamedType,
|
|
10
|
+
isBuiltinType,
|
|
11
|
+
extractBaseName,
|
|
12
|
+
parseNamespacedType,
|
|
13
|
+
type TypeRegistry,
|
|
14
|
+
} from "./utils";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Interface for tracking type references.
|
|
18
|
+
* Implemented by the import generator to track types that need to be imported.
|
|
19
|
+
*/
|
|
20
|
+
export interface TypeTracker {
|
|
21
|
+
trackReferencedType(typeName: string): void;
|
|
22
|
+
trackNamespaceImport(namespaceName: string): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Collects type references from XLR types for import generation.
|
|
27
|
+
*/
|
|
28
|
+
export class TypeCollector {
|
|
29
|
+
private readonly typeTracker: TypeTracker;
|
|
30
|
+
private readonly genericParamSymbols: Set<string>;
|
|
31
|
+
private readonly mainTypeName: string;
|
|
32
|
+
private readonly namespaceMemberMap: Map<string, string>;
|
|
33
|
+
private readonly typeRegistry?: TypeRegistry;
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
typeTracker: TypeTracker,
|
|
37
|
+
genericParamSymbols: Set<string>,
|
|
38
|
+
mainTypeName: string,
|
|
39
|
+
namespaceMemberMap: Map<string, string>,
|
|
40
|
+
typeRegistry?: TypeRegistry,
|
|
41
|
+
) {
|
|
42
|
+
this.typeTracker = typeTracker;
|
|
43
|
+
this.genericParamSymbols = genericParamSymbols;
|
|
44
|
+
this.mainTypeName = mainTypeName;
|
|
45
|
+
this.namespaceMemberMap = namespaceMemberMap;
|
|
46
|
+
this.typeRegistry = typeRegistry;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Collect generic parameter symbols (e.g., T, U) from the type definition.
|
|
51
|
+
* These should not be imported as they are type parameters, not concrete types.
|
|
52
|
+
*
|
|
53
|
+
* Also handles the case where a non-generic type extends a generic base without
|
|
54
|
+
* passing type arguments. In that scenario, XLR copies properties from the base
|
|
55
|
+
* (including references to the base's generic params like `AnyAsset`) but does
|
|
56
|
+
* NOT propagate `genericTokens` to the child type. We scan the type registry for
|
|
57
|
+
* generic parameter symbols that should be excluded from imports.
|
|
58
|
+
*/
|
|
59
|
+
collectGenericParamSymbols(namedType: NamedType<ObjectType>): void {
|
|
60
|
+
if (isGenericNamedType(namedType)) {
|
|
61
|
+
for (const token of namedType.genericTokens) {
|
|
62
|
+
this.genericParamSymbols.add(token.symbol);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Scan the type registry for generic parameter symbols from other types.
|
|
67
|
+
// When XLR copies properties from a generic base without resolving generics,
|
|
68
|
+
// the property types still reference the base's generic parameter names
|
|
69
|
+
// (e.g., `AnyAsset` from `FileInputAssetBase<AnyAsset>`). These names are
|
|
70
|
+
// not concrete types and should not be imported. We collect them from all
|
|
71
|
+
// registry types, excluding any that are themselves registered as concrete types.
|
|
72
|
+
if (this.typeRegistry) {
|
|
73
|
+
for (const registeredType of this.typeRegistry.values()) {
|
|
74
|
+
if (isGenericNamedType(registeredType)) {
|
|
75
|
+
for (const token of registeredType.genericTokens) {
|
|
76
|
+
if (!this.typeRegistry.has(token.symbol)) {
|
|
77
|
+
this.genericParamSymbols.add(token.symbol);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Collect type references from generic parameter constraints and defaults.
|
|
87
|
+
* This ensures types used in generics like "T extends Foo = Bar<X>" have
|
|
88
|
+
* Foo, Bar, and X added to referencedTypes for import generation.
|
|
89
|
+
*/
|
|
90
|
+
collectTypesFromGenericTokens(namedType: NamedType<ObjectType>): void {
|
|
91
|
+
if (!isGenericNamedType(namedType)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const token of namedType.genericTokens) {
|
|
96
|
+
if (token.constraints) {
|
|
97
|
+
this.collectTypeReferencesFromNode(token.constraints);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (token.default) {
|
|
101
|
+
this.collectTypeReferencesFromNode(token.default);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Collect all referenced types from an object type for imports.
|
|
108
|
+
*/
|
|
109
|
+
collectReferencedTypes(objType: ObjectType): void {
|
|
110
|
+
for (const prop of Object.values(objType.properties)) {
|
|
111
|
+
this.collectReferencedTypesFromNode(prop.node);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Collect referenced types from a node type.
|
|
117
|
+
*/
|
|
118
|
+
collectReferencedTypesFromNode(node: NodeType): void {
|
|
119
|
+
if (isObjectType(node)) {
|
|
120
|
+
if (isNamedType(node)) {
|
|
121
|
+
// Named types are defined elsewhere - track for import
|
|
122
|
+
// Skip built-in types and the type being generated
|
|
123
|
+
if (
|
|
124
|
+
node.name !== this.mainTypeName &&
|
|
125
|
+
!isBuiltinType(node.name) &&
|
|
126
|
+
!this.genericParamSymbols.has(node.name)
|
|
127
|
+
) {
|
|
128
|
+
this.typeTracker.trackReferencedType(node.name);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
// Anonymous object - recurse into properties to collect type references
|
|
132
|
+
for (const prop of Object.values(node.properties)) {
|
|
133
|
+
this.collectReferencedTypesFromNode(prop.node);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} else if (isArrayType(node)) {
|
|
137
|
+
this.collectReferencedTypesFromNode(node.elementType);
|
|
138
|
+
} else if (isOrType(node)) {
|
|
139
|
+
for (const variant of node.or) {
|
|
140
|
+
this.collectReferencedTypesFromNode(variant);
|
|
141
|
+
}
|
|
142
|
+
} else if (isAndType(node)) {
|
|
143
|
+
for (const part of node.and) {
|
|
144
|
+
this.collectReferencedTypesFromNode(part);
|
|
145
|
+
}
|
|
146
|
+
} else if (isRefType(node)) {
|
|
147
|
+
const baseName = extractBaseName(node.ref);
|
|
148
|
+
|
|
149
|
+
// Check if this is a namespaced type (e.g., "Validation.CrossfieldReference")
|
|
150
|
+
const namespaced = parseNamespacedType(baseName);
|
|
151
|
+
if (namespaced) {
|
|
152
|
+
// Track the namespace for import and the member mapping
|
|
153
|
+
this.typeTracker.trackNamespaceImport(namespaced.namespace);
|
|
154
|
+
this.namespaceMemberMap.set(namespaced.member, baseName);
|
|
155
|
+
} else {
|
|
156
|
+
// Track reference types that aren't built-in or generic params
|
|
157
|
+
if (
|
|
158
|
+
!isBuiltinType(baseName) &&
|
|
159
|
+
!this.genericParamSymbols.has(baseName)
|
|
160
|
+
) {
|
|
161
|
+
this.typeTracker.trackReferencedType(baseName);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Also process generic arguments, but skip type parameters of the referenced type
|
|
166
|
+
if (node.genericArguments) {
|
|
167
|
+
for (const arg of node.genericArguments) {
|
|
168
|
+
// Skip if this argument appears to be a type parameter of the referenced type
|
|
169
|
+
// e.g., in ref="Bar<AnyAsset>", skip "AnyAsset" since it's Bar's type param
|
|
170
|
+
if (isRefType(arg)) {
|
|
171
|
+
const argName = extractBaseName(arg.ref);
|
|
172
|
+
if (this.isTypeParamOfRef(argName, node.ref)) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
this.collectReferencedTypesFromNode(arg);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Recursively collect type references from any NodeType.
|
|
184
|
+
* This handles refs, arrays, unions, intersections, and objects.
|
|
185
|
+
*/
|
|
186
|
+
private collectTypeReferencesFromNode(node: NodeType): void {
|
|
187
|
+
if (isRefType(node)) {
|
|
188
|
+
const baseName = extractBaseName(node.ref);
|
|
189
|
+
|
|
190
|
+
// Check if this is a namespaced type (e.g., "Validation.CrossfieldReference")
|
|
191
|
+
const namespaced = parseNamespacedType(baseName);
|
|
192
|
+
if (namespaced) {
|
|
193
|
+
// Track the namespace for import and the member mapping
|
|
194
|
+
this.typeTracker.trackNamespaceImport(namespaced.namespace);
|
|
195
|
+
this.namespaceMemberMap.set(namespaced.member, baseName);
|
|
196
|
+
} else if (
|
|
197
|
+
!isBuiltinType(baseName) &&
|
|
198
|
+
!this.genericParamSymbols.has(baseName)
|
|
199
|
+
) {
|
|
200
|
+
// Skip built-in types and generic param symbols
|
|
201
|
+
this.typeTracker.trackReferencedType(baseName);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Also process generic arguments, but skip type parameters of the referenced type
|
|
205
|
+
if (node.genericArguments) {
|
|
206
|
+
for (const arg of node.genericArguments) {
|
|
207
|
+
// Skip if this argument appears to be a type parameter of the referenced type
|
|
208
|
+
// e.g., in ref="Bar<AnyAsset>", skip "AnyAsset" since it's Bar's type param
|
|
209
|
+
if (isRefType(arg)) {
|
|
210
|
+
const argName = extractBaseName(arg.ref);
|
|
211
|
+
if (this.isTypeParamOfRef(argName, node.ref)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
this.collectTypeReferencesFromNode(arg);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} else if (isArrayType(node)) {
|
|
219
|
+
this.collectTypeReferencesFromNode(node.elementType);
|
|
220
|
+
} else if (isOrType(node)) {
|
|
221
|
+
for (const variant of node.or) {
|
|
222
|
+
this.collectTypeReferencesFromNode(variant);
|
|
223
|
+
}
|
|
224
|
+
} else if (isAndType(node)) {
|
|
225
|
+
for (const part of node.and) {
|
|
226
|
+
this.collectTypeReferencesFromNode(part);
|
|
227
|
+
}
|
|
228
|
+
} else if (isObjectType(node)) {
|
|
229
|
+
if (isNamedType(node)) {
|
|
230
|
+
// Skip generic param symbols and built-in types in named types
|
|
231
|
+
// Strip generic arguments for import purposes
|
|
232
|
+
const importName = extractBaseName(node.name);
|
|
233
|
+
if (
|
|
234
|
+
!this.genericParamSymbols.has(importName) &&
|
|
235
|
+
!isBuiltinType(importName)
|
|
236
|
+
) {
|
|
237
|
+
// Use trackReferencedType to properly resolve import path
|
|
238
|
+
this.typeTracker.trackReferencedType(importName);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
for (const prop of Object.values(node.properties)) {
|
|
243
|
+
this.collectTypeReferencesFromNode(prop.node);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Check if a type name appears to be a generic type parameter of the referenced type.
|
|
250
|
+
* This detects cases like ref="Bar<AnyAsset>" where "AnyAsset" is Bar's type parameter,
|
|
251
|
+
* not a concrete type to import.
|
|
252
|
+
*
|
|
253
|
+
* @param argName - The name of the type argument being checked
|
|
254
|
+
* @param parentRef - The parent ref string that contains the generic usage
|
|
255
|
+
* @returns true if argName appears to be a type parameter in parentRef
|
|
256
|
+
*/
|
|
257
|
+
private isTypeParamOfRef(argName: string, parentRef: string): boolean {
|
|
258
|
+
// Extract the generic parameters portion from the ref string
|
|
259
|
+
// e.g., "Bar<AnyAsset>" -> "AnyAsset", "Map<K, V>" -> "K, V"
|
|
260
|
+
const genericMatch = parentRef.match(/<(.+)>/);
|
|
261
|
+
if (!genericMatch) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const genericPart = genericMatch[1];
|
|
266
|
+
|
|
267
|
+
// Split by comma while respecting nested generics
|
|
268
|
+
// and check if argName matches any parameter
|
|
269
|
+
let depth = 0;
|
|
270
|
+
let current = "";
|
|
271
|
+
const params: string[] = [];
|
|
272
|
+
|
|
273
|
+
for (const char of genericPart) {
|
|
274
|
+
if (char === "<") {
|
|
275
|
+
depth++;
|
|
276
|
+
current += char;
|
|
277
|
+
} else if (char === ">") {
|
|
278
|
+
depth--;
|
|
279
|
+
current += char;
|
|
280
|
+
} else if (char === "," && depth === 0) {
|
|
281
|
+
params.push(current.trim());
|
|
282
|
+
current = "";
|
|
283
|
+
} else {
|
|
284
|
+
current += char;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (current.trim()) {
|
|
288
|
+
params.push(current.trim());
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Check if argName matches any parameter exactly or is the base of a constrained param
|
|
292
|
+
return params.some(
|
|
293
|
+
(param) => param === argName || param.startsWith(`${argName} `),
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { TsMorphTypeDefinitionFinder } from "./ts-morph-type-finder";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TypeScript context for automatic import resolution.
|
|
7
|
+
* When provided, the generator uses TypeScript's module resolution to determine
|
|
8
|
+
* where types should be imported from.
|
|
9
|
+
*/
|
|
10
|
+
export interface TypeScriptContext {
|
|
11
|
+
/** The TypeScript program */
|
|
12
|
+
program: ts.Program;
|
|
13
|
+
/** The source file containing the type being generated */
|
|
14
|
+
sourceFile: ts.SourceFile;
|
|
15
|
+
/** The output directory where generated files will be written */
|
|
16
|
+
outputDir: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Information about an unexported type that needs to be exported
|
|
21
|
+
*/
|
|
22
|
+
export interface UnexportedTypeInfo {
|
|
23
|
+
/** The type name that needs to be exported */
|
|
24
|
+
typeName: string;
|
|
25
|
+
/** The file path where the type is declared */
|
|
26
|
+
filePath: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Result from type resolution.
|
|
31
|
+
* - sameFile: type is defined in the same file as the main type
|
|
32
|
+
* - notFound: type couldn't be resolved anywhere
|
|
33
|
+
* - string: import path for the type (package name or relative path)
|
|
34
|
+
*/
|
|
35
|
+
export type TypeResolutionResult = "sameFile" | "notFound" | string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if a file path is a TypeScript lib/built-in declaration file.
|
|
39
|
+
* These are files like lib.dom.d.ts that contain built-in type definitions.
|
|
40
|
+
*/
|
|
41
|
+
export function isBuiltInDeclarationPath(filePath: string): boolean {
|
|
42
|
+
// TypeScript lib files
|
|
43
|
+
if (filePath.includes("/typescript/lib/lib.")) return true;
|
|
44
|
+
// Node.js built-in types
|
|
45
|
+
if (filePath.includes("/@types/node/")) return true;
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if a declaration node is exported.
|
|
51
|
+
*/
|
|
52
|
+
export function isDeclarationExported(
|
|
53
|
+
node: ts.Declaration,
|
|
54
|
+
typescript: typeof ts,
|
|
55
|
+
): boolean {
|
|
56
|
+
// Check for export modifier on the declaration itself
|
|
57
|
+
const modifiers = typescript.canHaveModifiers(node)
|
|
58
|
+
? typescript.getModifiers(node)
|
|
59
|
+
: undefined;
|
|
60
|
+
if (modifiers) {
|
|
61
|
+
for (const modifier of modifiers) {
|
|
62
|
+
if (modifier.kind === typescript.SyntaxKind.ExportKeyword) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check if this declaration is part of an export statement
|
|
69
|
+
const parent = node.parent;
|
|
70
|
+
if (parent && typescript.isExportDeclaration(parent)) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Creates an import resolver using TypeScript's type checker and
|
|
79
|
+
* TsMorphTypeDefinitionFinder for tracing types through imports.
|
|
80
|
+
* This handles cases where types come through extends, Pick, re-exports, etc.
|
|
81
|
+
*/
|
|
82
|
+
export function createTypeScriptResolver(tsContext: TypeScriptContext): {
|
|
83
|
+
resolveTypePath: (typeName: string) => TypeResolutionResult;
|
|
84
|
+
getUnexportedTypes: () => UnexportedTypeInfo[];
|
|
85
|
+
} {
|
|
86
|
+
const { program, sourceFile, outputDir } = tsContext;
|
|
87
|
+
const typeChecker = program.getTypeChecker();
|
|
88
|
+
|
|
89
|
+
// Create the type definition finder for recursive search
|
|
90
|
+
const finder = new TsMorphTypeDefinitionFinder();
|
|
91
|
+
|
|
92
|
+
// Cache resolved paths
|
|
93
|
+
const resolvedCache = new Map<string, TypeResolutionResult>();
|
|
94
|
+
|
|
95
|
+
// Track types that exist but aren't exported
|
|
96
|
+
const unexportedTypes: UnexportedTypeInfo[] = [];
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Resolve a type name to its import path.
|
|
100
|
+
* Returns:
|
|
101
|
+
* - "sameFile": type is in the same file as the source
|
|
102
|
+
* - "notFound": type couldn't be resolved anywhere
|
|
103
|
+
* - string (import path): type should be imported from this path
|
|
104
|
+
*/
|
|
105
|
+
function resolveTypePath(typeName: string): TypeResolutionResult {
|
|
106
|
+
if (resolvedCache.has(typeName)) {
|
|
107
|
+
return resolvedCache.get(typeName)!;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// First, try to find the type using TsMorphTypeDefinitionFinder
|
|
111
|
+
// This recursively searches through imports
|
|
112
|
+
const typeFilePath = finder.findTypeSourceFile(
|
|
113
|
+
typeName,
|
|
114
|
+
sourceFile.fileName,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (typeFilePath) {
|
|
118
|
+
// Check if it's from the same file
|
|
119
|
+
if (typeFilePath === sourceFile.fileName) {
|
|
120
|
+
resolvedCache.set(typeName, "sameFile");
|
|
121
|
+
return "sameFile";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check if it's a built-in declaration
|
|
125
|
+
if (isBuiltInDeclarationPath(typeFilePath)) {
|
|
126
|
+
resolvedCache.set(typeName, "sameFile");
|
|
127
|
+
return "sameFile";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Check if it's from node_modules (external package)
|
|
131
|
+
if (typeFilePath.includes("node_modules")) {
|
|
132
|
+
const nodeModulesIdx = typeFilePath.lastIndexOf("node_modules/");
|
|
133
|
+
const afterNodeModules = typeFilePath.slice(
|
|
134
|
+
nodeModulesIdx + "node_modules/".length,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Handle scoped packages (@scope/package)
|
|
138
|
+
let packageName: string;
|
|
139
|
+
if (afterNodeModules.startsWith("@")) {
|
|
140
|
+
const parts = afterNodeModules.split("/");
|
|
141
|
+
packageName = `${parts[0]}/${parts[1]}`;
|
|
142
|
+
} else {
|
|
143
|
+
packageName = afterNodeModules.split("/")[0];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
resolvedCache.set(typeName, packageName);
|
|
147
|
+
return packageName;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// It's a local file - compute relative path from outputDir
|
|
151
|
+
let relativePath = path.relative(outputDir, typeFilePath);
|
|
152
|
+
relativePath = relativePath.replace(/\.tsx?$/, ".js");
|
|
153
|
+
if (!relativePath.startsWith(".")) {
|
|
154
|
+
relativePath = "./" + relativePath;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
resolvedCache.set(typeName, relativePath);
|
|
158
|
+
return relativePath;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// If TsMorphTypeDefinitionFinder didn't find it, fall back to symbol resolution
|
|
162
|
+
// This handles cases where types are in scope but not through imports
|
|
163
|
+
const symbols = typeChecker.getSymbolsInScope(
|
|
164
|
+
sourceFile,
|
|
165
|
+
ts.SymbolFlags.Type | ts.SymbolFlags.Interface | ts.SymbolFlags.TypeAlias,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const matchingSymbols = symbols.filter((s) => s.getName() === typeName);
|
|
169
|
+
|
|
170
|
+
let symbol: ts.Symbol | undefined;
|
|
171
|
+
let validDeclaration: ts.Declaration | undefined;
|
|
172
|
+
let unexportedDeclarationFile: string | undefined;
|
|
173
|
+
|
|
174
|
+
for (const s of matchingSymbols) {
|
|
175
|
+
const declarations = s.getDeclarations();
|
|
176
|
+
if (declarations && declarations.length > 0) {
|
|
177
|
+
const decl = declarations[0];
|
|
178
|
+
const declFile = decl.getSourceFile().fileName;
|
|
179
|
+
|
|
180
|
+
// Skip built-in declarations
|
|
181
|
+
if (isBuiltInDeclarationPath(declFile)) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Check if the declaration is exported (has export modifier or is in node_modules)
|
|
186
|
+
const isFromNodeModules = declFile.includes("node_modules");
|
|
187
|
+
const isExported = isFromNodeModules || isDeclarationExported(decl, ts);
|
|
188
|
+
|
|
189
|
+
if (isExported) {
|
|
190
|
+
symbol = s;
|
|
191
|
+
validDeclaration = decl;
|
|
192
|
+
break;
|
|
193
|
+
} else {
|
|
194
|
+
// Track unexported declaration for warning
|
|
195
|
+
unexportedDeclarationFile = declFile;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!symbol || !validDeclaration) {
|
|
201
|
+
// Type exists but is not exported - track for warning
|
|
202
|
+
if (unexportedDeclarationFile) {
|
|
203
|
+
// Check if we already tracked this type
|
|
204
|
+
const alreadyTracked = unexportedTypes.some(
|
|
205
|
+
(t) =>
|
|
206
|
+
t.typeName === typeName && t.filePath === unexportedDeclarationFile,
|
|
207
|
+
);
|
|
208
|
+
if (!alreadyTracked) {
|
|
209
|
+
unexportedTypes.push({
|
|
210
|
+
typeName,
|
|
211
|
+
filePath: unexportedDeclarationFile,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
// Type exists but isn't exported - treat as "not found" for import purposes
|
|
215
|
+
// It will be added to the same-file import, which will cause a type error,
|
|
216
|
+
// but the warning system will tell users what to export
|
|
217
|
+
resolvedCache.set(typeName, "sameFile");
|
|
218
|
+
return "sameFile";
|
|
219
|
+
}
|
|
220
|
+
// Type truly not found
|
|
221
|
+
resolvedCache.set(typeName, "notFound");
|
|
222
|
+
return "notFound";
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const declSourceFile = validDeclaration.getSourceFile();
|
|
226
|
+
const declFilePath = declSourceFile.fileName;
|
|
227
|
+
|
|
228
|
+
if (declFilePath === sourceFile.fileName) {
|
|
229
|
+
resolvedCache.set(typeName, "sameFile");
|
|
230
|
+
return "sameFile";
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (declFilePath.includes("node_modules")) {
|
|
234
|
+
const nodeModulesIdx = declFilePath.lastIndexOf("node_modules/");
|
|
235
|
+
const afterNodeModules = declFilePath.slice(
|
|
236
|
+
nodeModulesIdx + "node_modules/".length,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
let packageName: string;
|
|
240
|
+
if (afterNodeModules.startsWith("@")) {
|
|
241
|
+
const parts = afterNodeModules.split("/");
|
|
242
|
+
packageName = `${parts[0]}/${parts[1]}`;
|
|
243
|
+
} else {
|
|
244
|
+
packageName = afterNodeModules.split("/")[0];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
resolvedCache.set(typeName, packageName);
|
|
248
|
+
return packageName;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let relativePath = path.relative(outputDir, declFilePath);
|
|
252
|
+
relativePath = relativePath.replace(/\.tsx?$/, ".js");
|
|
253
|
+
if (!relativePath.startsWith(".")) {
|
|
254
|
+
relativePath = "./" + relativePath;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
resolvedCache.set(typeName, relativePath);
|
|
258
|
+
return relativePath;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function getUnexportedTypes(): UnexportedTypeInfo[] {
|
|
262
|
+
return [...unexportedTypes];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return { resolveTypePath, getUnexportedTypes };
|
|
266
|
+
}
|