@knapsack/spec-utils 4.78.13--canary.5646.9581069.0 → 4.78.13
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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-test.log +50 -0
- package/CHANGELOG.md +12 -0
- package/dist/align/align.vtest.d.ts +2 -0
- package/dist/align/align.vtest.d.ts.map +1 -0
- package/dist/align/align.vtest.js +46 -0
- package/dist/align/align.vtest.js.map +1 -0
- package/dist/analyze-exports.sandbox-components.vtest.d.ts +2 -0
- package/dist/analyze-exports.sandbox-components.vtest.d.ts.map +1 -0
- package/dist/analyze-exports.sandbox-components.vtest.js +51 -0
- package/dist/analyze-exports.sandbox-components.vtest.js.map +1 -0
- package/dist/analyze-exports.vtest.d.ts +2 -0
- package/dist/analyze-exports.vtest.d.ts.map +1 -0
- package/dist/analyze-exports.vtest.js +160 -0
- package/dist/analyze-exports.vtest.js.map +1 -0
- package/dist/convert-to-spec.vtest.d.ts +2 -0
- package/dist/convert-to-spec.vtest.d.ts.map +1 -0
- package/dist/convert-to-spec.vtest.js +131 -0
- package/dist/convert-to-spec.vtest.js.map +1 -0
- package/dist/get-ts-config.vtest.d.ts +2 -0
- package/dist/get-ts-config.vtest.d.ts.map +1 -0
- package/dist/get-ts-config.vtest.js +9 -0
- package/dist/get-ts-config.vtest.js.map +1 -0
- package/dist/resolve.vtest.d.ts +2 -0
- package/dist/resolve.vtest.d.ts.map +1 -0
- package/dist/resolve.vtest.js +57 -0
- package/dist/resolve.vtest.js.map +1 -0
- package/dist/utils.vtest.d.ts +2 -0
- package/dist/utils.vtest.d.ts.map +1 -0
- package/dist/utils.vtest.js +37 -0
- package/dist/utils.vtest.js.map +1 -0
- package/package.json +10 -10
- package/src/align/align.vtest.ts +56 -0
- package/src/align/get-exports.bench.ts +28 -0
- package/src/align/resolve.bench.ts +20 -0
- package/src/align/utils.ts +14 -0
- package/src/analyze-exports.sandbox-components.vtest.ts +53 -0
- package/src/analyze-exports.ts +54 -0
- package/src/analyze-exports.vtest.ts +178 -0
- package/src/analyze-symbol.ts +213 -0
- package/src/analyze-type.ts +316 -0
- package/src/boot.ts +31 -0
- package/src/convert-to-spec.ts +196 -0
- package/src/convert-to-spec.vtest.ts +136 -0
- package/src/get-exports.ts +70 -0
- package/src/get-ts-config.ts +96 -0
- package/src/get-ts-config.vtest.ts +9 -0
- package/src/index.ts +5 -0
- package/src/resolve.ts +54 -0
- package/src/resolve.vtest.ts +69 -0
- package/src/test-fixtures/basics.ts +17 -0
- package/src/test-fixtures/functions.ts +50 -0
- package/src/test-fixtures/index.ts +2 -0
- package/src/types.ts +66 -0
- package/src/utils.ts +61 -0
- package/src/utils.vtest.ts +39 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import { analyzeType } from './analyze-type.js';
|
|
3
|
+
import { SymbolInfo, TypeInfo } from './types.js';
|
|
4
|
+
import { getSetFlags } from './utils.js';
|
|
5
|
+
|
|
6
|
+
export function analyzeSymbol({
|
|
7
|
+
symbol,
|
|
8
|
+
checker,
|
|
9
|
+
symbolsVisited = new Set(),
|
|
10
|
+
}: {
|
|
11
|
+
symbol: ts.Symbol;
|
|
12
|
+
checker: ts.TypeChecker;
|
|
13
|
+
/** use to prevent infinite recursion */
|
|
14
|
+
symbolsVisited?: Set<ts.Symbol>;
|
|
15
|
+
}): SymbolInfo {
|
|
16
|
+
symbolsVisited.add(symbol);
|
|
17
|
+
const type = symbol.valueDeclaration
|
|
18
|
+
? checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration)
|
|
19
|
+
: checker.getTypeOfSymbol(symbol);
|
|
20
|
+
const description = ts.displayPartsToString(
|
|
21
|
+
symbol.getDocumentationComment(checker),
|
|
22
|
+
);
|
|
23
|
+
const jsDocTags = symbol.getJsDocTags().map(({ name, text }) => ({
|
|
24
|
+
name,
|
|
25
|
+
text: text ? ts.displayPartsToString(text) : undefined,
|
|
26
|
+
}));
|
|
27
|
+
const {
|
|
28
|
+
// Here in case a `Node` is needed
|
|
29
|
+
valueDeclaration: node,
|
|
30
|
+
} = symbol;
|
|
31
|
+
|
|
32
|
+
const symbolFlags = getSetFlags({
|
|
33
|
+
flags: symbol.flags,
|
|
34
|
+
enumObject: ts.SymbolFlags,
|
|
35
|
+
});
|
|
36
|
+
const typeFlags = getSetFlags({
|
|
37
|
+
flags: type.flags,
|
|
38
|
+
enumObject: ts.TypeFlags,
|
|
39
|
+
});
|
|
40
|
+
const isOptional = symbolFlags.includes('Optional');
|
|
41
|
+
const isArray = checker.isArrayType(type) || checker.isArrayLikeType(type);
|
|
42
|
+
|
|
43
|
+
// set in `if` blocks below
|
|
44
|
+
let typeInfo: TypeInfo;
|
|
45
|
+
const baseSymbolInfo: Omit<SymbolInfo, 'typeInfo'> = {
|
|
46
|
+
tsMetadata: {
|
|
47
|
+
symbol,
|
|
48
|
+
type,
|
|
49
|
+
symbolFlags,
|
|
50
|
+
},
|
|
51
|
+
jsDoc:
|
|
52
|
+
description || jsDocTags.length
|
|
53
|
+
? {
|
|
54
|
+
description,
|
|
55
|
+
jsDocTags,
|
|
56
|
+
}
|
|
57
|
+
: undefined,
|
|
58
|
+
name: symbol.getName(),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Data I have seen in debugger, but am not sure how to safely access:
|
|
62
|
+
// type.members - reliably indicates an "object" by providing a Map of Symbols of its properties. The check for Object below is not as reliable since everything in JS is an object.
|
|
63
|
+
// type.objectFlags - seems to indicate the type of the object, but not sure how to safely access it.
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A function can have multiple call signatures - this is done when using "overloads", like this:
|
|
67
|
+
* ```ts
|
|
68
|
+
* export function sayHello(msg: string[]): string;
|
|
69
|
+
* export function sayHello(msg: string): string;
|
|
70
|
+
* export function sayHello(msg: unknown): string {
|
|
71
|
+
* return `Hello ${Array.isArray(msg) ? msg.join(' ') : msg}`;
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* Handling that seems like too much (and doubtfully useful) so we only handle the first call signature.
|
|
76
|
+
*/
|
|
77
|
+
const [callSignature] = type.getCallSignatures();
|
|
78
|
+
// consider using over `callSignature`
|
|
79
|
+
const _isFunction = symbolFlags.includes('Function');
|
|
80
|
+
// if it's callable, it's a function
|
|
81
|
+
if (callSignature) {
|
|
82
|
+
const returnType = callSignature.getReturnType();
|
|
83
|
+
const typeParameters = callSignature.getTypeParameters();
|
|
84
|
+
typeInfo = {
|
|
85
|
+
type: 'function',
|
|
86
|
+
parameters: callSignature.parameters
|
|
87
|
+
.filter((paramSymbol) => !symbolsVisited.has(paramSymbol))
|
|
88
|
+
.map((paramSymbol) => {
|
|
89
|
+
return analyzeSymbol({
|
|
90
|
+
symbol: paramSymbol,
|
|
91
|
+
checker,
|
|
92
|
+
symbolsVisited,
|
|
93
|
+
});
|
|
94
|
+
}),
|
|
95
|
+
typeParameters: typeParameters?.map((typeParameter) => {
|
|
96
|
+
return analyzeType({ type: typeParameter, checker });
|
|
97
|
+
}),
|
|
98
|
+
// returnType: returnType
|
|
99
|
+
// ? analyzeType({ type: returnType, checker })
|
|
100
|
+
// : undefined,
|
|
101
|
+
returnType: analyzeType({ type: returnType, checker }),
|
|
102
|
+
tsRawType: checker.typeToString(
|
|
103
|
+
type,
|
|
104
|
+
node, // remove if it looks weird
|
|
105
|
+
ts.TypeFormatFlags.NoTruncation,
|
|
106
|
+
),
|
|
107
|
+
isOptional,
|
|
108
|
+
tsMetadata: {
|
|
109
|
+
typeFlags: getSetFlags({
|
|
110
|
+
flags: type.flags,
|
|
111
|
+
enumObject: ts.TypeFlags,
|
|
112
|
+
}),
|
|
113
|
+
type,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
} else if (symbolFlags.includes('Class')) {
|
|
117
|
+
const [constructSignature] = type.getConstructSignatures();
|
|
118
|
+
const prototype = type.getProperty('prototype');
|
|
119
|
+
if (!prototype) {
|
|
120
|
+
throw new Error(`${symbol.getName()} has no prototype`);
|
|
121
|
+
}
|
|
122
|
+
typeInfo = {
|
|
123
|
+
type: 'class',
|
|
124
|
+
tsRawType: checker.typeToString(
|
|
125
|
+
type,
|
|
126
|
+
undefined,
|
|
127
|
+
ts.TypeFormatFlags.NoTruncation,
|
|
128
|
+
),
|
|
129
|
+
isOptional,
|
|
130
|
+
tsMetadata: {
|
|
131
|
+
typeFlags: getSetFlags({
|
|
132
|
+
flags: type.flags,
|
|
133
|
+
enumObject: ts.TypeFlags,
|
|
134
|
+
}),
|
|
135
|
+
type,
|
|
136
|
+
},
|
|
137
|
+
prototype: analyzeSymbol({
|
|
138
|
+
symbol: prototype,
|
|
139
|
+
checker,
|
|
140
|
+
symbolsVisited,
|
|
141
|
+
}),
|
|
142
|
+
properties: type.getProperties().reduce((acc, propSymbol) => {
|
|
143
|
+
if (symbolsVisited.has(propSymbol)) return acc;
|
|
144
|
+
const symbolInfo = analyzeSymbol({
|
|
145
|
+
symbol: propSymbol,
|
|
146
|
+
checker,
|
|
147
|
+
symbolsVisited,
|
|
148
|
+
});
|
|
149
|
+
acc[symbolInfo.name] = symbolInfo;
|
|
150
|
+
return acc;
|
|
151
|
+
}, {} as Extract<TypeInfo, { type: 'object' }>['properties']),
|
|
152
|
+
// constructorReturnType: constructSignature
|
|
153
|
+
// ? analyzeType({ type: constructSignature.getReturnType(), checker })
|
|
154
|
+
// : undefined,
|
|
155
|
+
constructorReturnType: analyzeType({
|
|
156
|
+
type: constructSignature.getReturnType(),
|
|
157
|
+
checker,
|
|
158
|
+
}),
|
|
159
|
+
|
|
160
|
+
constructorParameters: constructSignature
|
|
161
|
+
?.getParameters()
|
|
162
|
+
.map((constructParam) => {
|
|
163
|
+
return analyzeSymbol({
|
|
164
|
+
symbol: constructParam,
|
|
165
|
+
checker,
|
|
166
|
+
symbolsVisited,
|
|
167
|
+
});
|
|
168
|
+
}),
|
|
169
|
+
};
|
|
170
|
+
} else if (
|
|
171
|
+
// this check could be better: lots of things are `ts.TypeFlags.Object`
|
|
172
|
+
typeFlags.includes('Object') &&
|
|
173
|
+
// here we exclude things that are technically `ts.TypeFlags.Object` but are not "objects" in JS
|
|
174
|
+
!isArray
|
|
175
|
+
) {
|
|
176
|
+
typeInfo = {
|
|
177
|
+
type: 'object',
|
|
178
|
+
tsRawType: checker.typeToString(
|
|
179
|
+
type,
|
|
180
|
+
undefined,
|
|
181
|
+
ts.TypeFormatFlags.NoTruncation,
|
|
182
|
+
),
|
|
183
|
+
properties: type.getProperties().reduce((acc, propSymbol) => {
|
|
184
|
+
if (symbolsVisited.has(propSymbol)) return acc;
|
|
185
|
+
const symbolInfo = analyzeSymbol({
|
|
186
|
+
symbol: propSymbol,
|
|
187
|
+
checker,
|
|
188
|
+
symbolsVisited,
|
|
189
|
+
});
|
|
190
|
+
acc[symbolInfo.name] = symbolInfo;
|
|
191
|
+
return acc;
|
|
192
|
+
}, {} as Extract<TypeInfo, { type: 'object' }>['properties']),
|
|
193
|
+
isOptional,
|
|
194
|
+
tsMetadata: {
|
|
195
|
+
typeFlags: getSetFlags({
|
|
196
|
+
flags: type.flags,
|
|
197
|
+
enumObject: ts.TypeFlags,
|
|
198
|
+
}),
|
|
199
|
+
type,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
} else {
|
|
203
|
+
typeInfo = analyzeType({
|
|
204
|
+
type,
|
|
205
|
+
checker,
|
|
206
|
+
isOptional,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
...baseSymbolInfo,
|
|
211
|
+
typeInfo,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import { TypeInfo, TypeInfoCommon } from './types.js';
|
|
3
|
+
import { getSetFlags } from './utils.js';
|
|
4
|
+
|
|
5
|
+
// Cache for type IDs to improve performance
|
|
6
|
+
const typeIdMap = new WeakMap<ts.Type, string>();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generates a consistent identifier for a TypeScript type
|
|
10
|
+
* Uses the type's string representation to ensure identical types get the same ID
|
|
11
|
+
*/
|
|
12
|
+
function getTypeId(type: ts.Type, checker: ts.TypeChecker): string {
|
|
13
|
+
// Check if we already have an ID for this type
|
|
14
|
+
const existingId = typeIdMap.get(type);
|
|
15
|
+
if (existingId) {
|
|
16
|
+
return existingId;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Generate a new ID based on the type's string representation
|
|
20
|
+
// This ensures identical types get the same ID
|
|
21
|
+
const typeString = checker.typeToString(
|
|
22
|
+
type,
|
|
23
|
+
undefined,
|
|
24
|
+
// eslint-disable-next-line no-bitwise
|
|
25
|
+
ts.TypeFormatFlags.NoTruncation,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Add some additional information to help distinguish types
|
|
29
|
+
// that might have the same string representation
|
|
30
|
+
const idComponents = [typeString];
|
|
31
|
+
|
|
32
|
+
// Add symbol name if available
|
|
33
|
+
const symbol = type.getSymbol();
|
|
34
|
+
if (symbol) {
|
|
35
|
+
idComponents.push(symbol.getName());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Add type flags to help distinguish types
|
|
39
|
+
idComponents.push(String(type.flags));
|
|
40
|
+
|
|
41
|
+
const newId = idComponents.join('_');
|
|
42
|
+
typeIdMap.set(type, newId);
|
|
43
|
+
return newId;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Analyzes a TypeScript type and returns a structured TypeInfo representation
|
|
48
|
+
*/
|
|
49
|
+
export function analyzeType({
|
|
50
|
+
type,
|
|
51
|
+
checker,
|
|
52
|
+
isOptional,
|
|
53
|
+
visitedTypes = new Map<string, TypeInfo>(),
|
|
54
|
+
depth = 0,
|
|
55
|
+
/**
|
|
56
|
+
* The maximum depth of type analysis.
|
|
57
|
+
* This is used to avoid infinite recursion.
|
|
58
|
+
*/
|
|
59
|
+
maxDepth = 3,
|
|
60
|
+
}: {
|
|
61
|
+
type: ts.Type;
|
|
62
|
+
checker: ts.TypeChecker;
|
|
63
|
+
isOptional?: boolean;
|
|
64
|
+
visitedTypes?: Map<string, TypeInfo>;
|
|
65
|
+
depth?: number;
|
|
66
|
+
maxDepth?: number;
|
|
67
|
+
}): TypeInfo {
|
|
68
|
+
// Generate a unique key for the type
|
|
69
|
+
const typeId = getTypeId(type, checker);
|
|
70
|
+
|
|
71
|
+
// Check if we've already analyzed this type
|
|
72
|
+
if (visitedTypes.has(typeId)) {
|
|
73
|
+
return visitedTypes.get(typeId)!;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const tsRawType = checker.typeToString(
|
|
77
|
+
type,
|
|
78
|
+
undefined,
|
|
79
|
+
// eslint-disable-next-line no-bitwise
|
|
80
|
+
ts.TypeFormatFlags.MultilineObjectLiterals |
|
|
81
|
+
ts.TypeFormatFlags.NoTruncation,
|
|
82
|
+
);
|
|
83
|
+
const typeFlags = getSetFlags({
|
|
84
|
+
flags: type.flags,
|
|
85
|
+
enumObject: ts.TypeFlags,
|
|
86
|
+
});
|
|
87
|
+
const typeInfoCommon: TypeInfoCommon = {
|
|
88
|
+
tsRawType,
|
|
89
|
+
tsMetadata: {
|
|
90
|
+
typeFlags,
|
|
91
|
+
type,
|
|
92
|
+
},
|
|
93
|
+
isOptional,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// If we've reached max depth, return a simplified type
|
|
97
|
+
if (depth >= maxDepth) {
|
|
98
|
+
const result: TypeInfo = {
|
|
99
|
+
type: 'misc',
|
|
100
|
+
...typeInfoCommon,
|
|
101
|
+
};
|
|
102
|
+
visitedTypes.set(typeId, result);
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let result: TypeInfo;
|
|
107
|
+
|
|
108
|
+
// Handle primitive types first
|
|
109
|
+
if (
|
|
110
|
+
tsRawType === 'number' ||
|
|
111
|
+
tsRawType === 'boolean' ||
|
|
112
|
+
tsRawType === 'string'
|
|
113
|
+
) {
|
|
114
|
+
result = {
|
|
115
|
+
type: tsRawType,
|
|
116
|
+
...typeInfoCommon,
|
|
117
|
+
};
|
|
118
|
+
visitedTypes.set(typeId, result);
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Handle literal types
|
|
123
|
+
if (type.isStringLiteral()) {
|
|
124
|
+
result = {
|
|
125
|
+
type: 'stringLiteral',
|
|
126
|
+
value: type.value,
|
|
127
|
+
...typeInfoCommon,
|
|
128
|
+
};
|
|
129
|
+
visitedTypes.set(typeId, result);
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (type.isNumberLiteral()) {
|
|
134
|
+
result = {
|
|
135
|
+
type: 'numberLiteral',
|
|
136
|
+
value: type.value,
|
|
137
|
+
...typeInfoCommon,
|
|
138
|
+
};
|
|
139
|
+
visitedTypes.set(typeId, result);
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Create a placeholder to avoid infinite recursion
|
|
144
|
+
const placeholder: TypeInfo = {
|
|
145
|
+
type: 'misc',
|
|
146
|
+
...typeInfoCommon,
|
|
147
|
+
};
|
|
148
|
+
visitedTypes.set(typeId, placeholder);
|
|
149
|
+
|
|
150
|
+
// Handle unions
|
|
151
|
+
if (type.isUnion()) {
|
|
152
|
+
result = {
|
|
153
|
+
type: 'union',
|
|
154
|
+
items: type.types.map((t) =>
|
|
155
|
+
analyzeType({
|
|
156
|
+
type: t,
|
|
157
|
+
checker,
|
|
158
|
+
isOptional,
|
|
159
|
+
visitedTypes,
|
|
160
|
+
depth: depth + 1,
|
|
161
|
+
maxDepth,
|
|
162
|
+
}),
|
|
163
|
+
),
|
|
164
|
+
...typeInfoCommon,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
// Handle arrays
|
|
168
|
+
else if (checker.isArrayType(type) || checker.isArrayLikeType(type)) {
|
|
169
|
+
const indexInfos = checker.getIndexInfosOfType(type);
|
|
170
|
+
|
|
171
|
+
result = {
|
|
172
|
+
type: 'array',
|
|
173
|
+
items:
|
|
174
|
+
indexInfos.length === 1
|
|
175
|
+
? analyzeType({
|
|
176
|
+
type: indexInfos[0]?.type,
|
|
177
|
+
checker,
|
|
178
|
+
isOptional,
|
|
179
|
+
visitedTypes,
|
|
180
|
+
depth: depth + 1,
|
|
181
|
+
maxDepth,
|
|
182
|
+
})
|
|
183
|
+
: { type: 'misc', ...typeInfoCommon },
|
|
184
|
+
...typeInfoCommon,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
// Handle intersections
|
|
188
|
+
// This is where ForwardRef is being inferred
|
|
189
|
+
// @TODO: We need to find a way to handle depth here. Currently we bring in all of the properties
|
|
190
|
+
// from each type in the intersection, but we may want to limit the depth of the intersection
|
|
191
|
+
// to avoid bringing in too much.
|
|
192
|
+
// https://linear.app/knapsack/issue/KSP-5885/update-handling-of-intersection-types-in-spec-utils
|
|
193
|
+
else if (type.isIntersection()) {
|
|
194
|
+
const properties = type.getProperties();
|
|
195
|
+
if (properties.length > 0) {
|
|
196
|
+
const analyzedProperties = properties.reduce((acc, prop) => {
|
|
197
|
+
try {
|
|
198
|
+
// Get the type directly from the symbol - this is the most reliable method
|
|
199
|
+
const propType = checker.getTypeOfSymbol(prop);
|
|
200
|
+
|
|
201
|
+
if (propType) {
|
|
202
|
+
const propAnalysis = analyzeType({
|
|
203
|
+
type: propType,
|
|
204
|
+
checker,
|
|
205
|
+
// eslint-disable-next-line no-bitwise
|
|
206
|
+
isOptional: !!(prop.flags & ts.SymbolFlags.Optional),
|
|
207
|
+
visitedTypes,
|
|
208
|
+
depth: depth + 1,
|
|
209
|
+
maxDepth,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
acc[prop.getName()] = {
|
|
213
|
+
name: prop.getName(),
|
|
214
|
+
typeInfo: propAnalysis,
|
|
215
|
+
tsMetadata: {
|
|
216
|
+
symbol: prop,
|
|
217
|
+
type: propType,
|
|
218
|
+
symbolFlags: getSetFlags({
|
|
219
|
+
flags: prop.flags,
|
|
220
|
+
enumObject: ts.SymbolFlags,
|
|
221
|
+
}),
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.debug(`Error analyzing property ${prop.getName()}: ${error}`);
|
|
227
|
+
}
|
|
228
|
+
return acc;
|
|
229
|
+
}, {} as Record<string, any>);
|
|
230
|
+
|
|
231
|
+
if (Object.keys(analyzedProperties).length > 0) {
|
|
232
|
+
result = {
|
|
233
|
+
type: 'object',
|
|
234
|
+
properties: analyzedProperties,
|
|
235
|
+
...typeInfoCommon,
|
|
236
|
+
};
|
|
237
|
+
} else {
|
|
238
|
+
result = placeholder;
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
result = placeholder;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Handle function types
|
|
245
|
+
else if (
|
|
246
|
+
typeFlags.includes('Object') &&
|
|
247
|
+
type.getCallSignatures().length > 0
|
|
248
|
+
) {
|
|
249
|
+
result = {
|
|
250
|
+
type: 'function',
|
|
251
|
+
parameters: [],
|
|
252
|
+
returnType: { type: 'misc', ...typeInfoCommon },
|
|
253
|
+
...typeInfoCommon,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Handle object types
|
|
258
|
+
// @TODO: update handling of object type inference
|
|
259
|
+
// https://linear.app/knapsack/issue/KSP-5880/handle-arrays-of-objects-in-react-renderer-infer-spec
|
|
260
|
+
// else if (typeFlags.includes('Object')) {
|
|
261
|
+
// const properties = type.getProperties();
|
|
262
|
+
// if (properties.length > 0) {
|
|
263
|
+
// const analyzedProperties = properties.reduce((acc, prop) => {
|
|
264
|
+
// try {
|
|
265
|
+
// const propType = checker.getTypeOfSymbol(prop);
|
|
266
|
+
// if (propType) {
|
|
267
|
+
// const propAnalysis = analyzeType({
|
|
268
|
+
// type: propType,
|
|
269
|
+
// checker,
|
|
270
|
+
// // eslint-disable-next-line no-bitwise
|
|
271
|
+
// isOptional: !!(prop.flags & ts.SymbolFlags.Optional),
|
|
272
|
+
// visitedTypes,
|
|
273
|
+
// depth: depth + 1,
|
|
274
|
+
// maxDepth,
|
|
275
|
+
// });
|
|
276
|
+
// acc[prop.getName()] = {
|
|
277
|
+
// name: prop.getName(),
|
|
278
|
+
// typeInfo: propAnalysis,
|
|
279
|
+
// tsMetadata: {
|
|
280
|
+
// symbol: prop,
|
|
281
|
+
// type: propType,
|
|
282
|
+
// symbolFlags: getSetFlags({
|
|
283
|
+
// flags: prop.flags,
|
|
284
|
+
// enumObject: ts.SymbolFlags,
|
|
285
|
+
// }),
|
|
286
|
+
// },
|
|
287
|
+
// };
|
|
288
|
+
// }
|
|
289
|
+
// } catch (error) {
|
|
290
|
+
// console.debug(`Error analyzing property ${prop.getName()}: ${error}`);
|
|
291
|
+
// }
|
|
292
|
+
// return acc;
|
|
293
|
+
// }, {} as Record<string, any>);
|
|
294
|
+
|
|
295
|
+
// if (Object.keys(analyzedProperties).length > 0) {
|
|
296
|
+
// result = {
|
|
297
|
+
// type: 'object',
|
|
298
|
+
// properties: analyzedProperties,
|
|
299
|
+
// ...typeInfoCommon,
|
|
300
|
+
// };
|
|
301
|
+
// } else {
|
|
302
|
+
// result = placeholder;
|
|
303
|
+
// }
|
|
304
|
+
// } else {
|
|
305
|
+
// result = placeholder;
|
|
306
|
+
// }
|
|
307
|
+
// }
|
|
308
|
+
// Default to misc type for anything we can't properly analyze
|
|
309
|
+
else {
|
|
310
|
+
result = placeholder;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Update the cache with the actual result
|
|
314
|
+
visitedTypes.set(typeId, result);
|
|
315
|
+
return result;
|
|
316
|
+
}
|
package/src/boot.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createCompilerHost, createProgram, Program } from 'typescript';
|
|
2
|
+
import { getTsConfigCompilerOptions } from './get-ts-config.js';
|
|
3
|
+
|
|
4
|
+
export function prepTypeScriptBoot({
|
|
5
|
+
configSrc,
|
|
6
|
+
}: {
|
|
7
|
+
configSrc: Parameters<typeof getTsConfigCompilerOptions>[0];
|
|
8
|
+
}) {
|
|
9
|
+
const compilerOptions = getTsConfigCompilerOptions(configSrc);
|
|
10
|
+
const compilerHost = createCompilerHost(compilerOptions);
|
|
11
|
+
return { compilerOptions, compilerHost };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function bootTypescript({
|
|
15
|
+
configSrc,
|
|
16
|
+
rootFiles,
|
|
17
|
+
oldProgram,
|
|
18
|
+
}: {
|
|
19
|
+
configSrc: Parameters<typeof getTsConfigCompilerOptions>[0];
|
|
20
|
+
rootFiles: string[];
|
|
21
|
+
oldProgram?: Program;
|
|
22
|
+
}) {
|
|
23
|
+
const { compilerOptions, compilerHost } = prepTypeScriptBoot({ configSrc });
|
|
24
|
+
const program = createProgram({
|
|
25
|
+
options: compilerOptions,
|
|
26
|
+
rootNames: rootFiles,
|
|
27
|
+
oldProgram,
|
|
28
|
+
});
|
|
29
|
+
const checker = program.getTypeChecker();
|
|
30
|
+
return { compilerHost, compilerOptions, program, checker };
|
|
31
|
+
}
|