@openwebf/webf 0.1.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/CLAUDE.md +206 -0
- package/README-zhCN.md +256 -0
- package/README.md +232 -0
- package/bin/webf.js +25 -0
- package/coverage/clover.xml +1295 -0
- package/coverage/coverage-final.json +12 -0
- package/coverage/lcov-report/IDLBlob.ts.html +142 -0
- package/coverage/lcov-report/analyzer.ts.html +2158 -0
- package/coverage/lcov-report/analyzer_original.ts.html +1450 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/commands.ts.html +700 -0
- package/coverage/lcov-report/dart.ts.html +490 -0
- package/coverage/lcov-report/declaration.ts.html +337 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/generator.ts.html +1171 -0
- package/coverage/lcov-report/index.html +266 -0
- package/coverage/lcov-report/logger.ts.html +424 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/react.ts.html +619 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov-report/utils.ts.html +466 -0
- package/coverage/lcov-report/vue.ts.html +613 -0
- package/coverage/lcov.info +2149 -0
- package/global.d.ts +2 -0
- package/jest.config.js +24 -0
- package/package.json +36 -0
- package/src/IDLBlob.ts +20 -0
- package/src/analyzer.ts +692 -0
- package/src/commands.ts +645 -0
- package/src/dart.ts +170 -0
- package/src/declaration.ts +84 -0
- package/src/generator.ts +454 -0
- package/src/logger.ts +114 -0
- package/src/react.ts +186 -0
- package/src/utils.ts +127 -0
- package/src/vue.ts +176 -0
- package/templates/class.dart.tpl +86 -0
- package/templates/gitignore.tpl +2 -0
- package/templates/react.component.tsx.tpl +53 -0
- package/templates/react.createComponent.tpl +286 -0
- package/templates/react.index.ts.tpl +8 -0
- package/templates/react.package.json.tpl +26 -0
- package/templates/react.tsconfig.json.tpl +16 -0
- package/templates/react.tsup.config.ts.tpl +10 -0
- package/templates/tsconfig.json.tpl +8 -0
- package/templates/vue.component.partial.tpl +31 -0
- package/templates/vue.components.d.ts.tpl +49 -0
- package/templates/vue.package.json.tpl +11 -0
- package/templates/vue.tsconfig.json.tpl +15 -0
- package/test/IDLBlob.test.ts +75 -0
- package/test/analyzer.test.ts +370 -0
- package/test/commands.test.ts +1253 -0
- package/test/generator.test.ts +460 -0
- package/test/logger.test.ts +215 -0
- package/test/react.test.ts +49 -0
- package/test/utils.test.ts +316 -0
- package/tsconfig.json +30 -0
package/src/analyzer.ts
ADDED
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
import ts, {HeritageClause, ScriptTarget, VariableStatement} from 'typescript';
|
|
2
|
+
import {IDLBlob} from './IDLBlob';
|
|
3
|
+
import {
|
|
4
|
+
ClassObject,
|
|
5
|
+
ClassObjectKind,
|
|
6
|
+
FunctionArguments,
|
|
7
|
+
FunctionArgumentType,
|
|
8
|
+
FunctionDeclaration,
|
|
9
|
+
FunctionObject,
|
|
10
|
+
IndexedPropertyDeclaration,
|
|
11
|
+
ParameterMode,
|
|
12
|
+
PropsDeclaration,
|
|
13
|
+
} from './declaration';
|
|
14
|
+
import {isUnionType} from "./utils";
|
|
15
|
+
|
|
16
|
+
interface DefinedPropertyCollector {
|
|
17
|
+
properties: Set<string>;
|
|
18
|
+
files: Set<string>;
|
|
19
|
+
interfaces: Set<string>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface UnionTypeCollector {
|
|
23
|
+
types: Set<ParameterType[]>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Cache for parsed source files to avoid re-parsing
|
|
27
|
+
const sourceFileCache = new Map<string, ts.SourceFile>();
|
|
28
|
+
|
|
29
|
+
// Cache for type conversions to avoid redundant processing
|
|
30
|
+
const typeConversionCache = new Map<string, ParameterType>();
|
|
31
|
+
|
|
32
|
+
// Type mapping constants for better performance
|
|
33
|
+
const BASIC_TYPE_MAP: Partial<Record<ts.SyntaxKind, FunctionArgumentType>> = {
|
|
34
|
+
[ts.SyntaxKind.StringKeyword]: FunctionArgumentType.dom_string,
|
|
35
|
+
[ts.SyntaxKind.NumberKeyword]: FunctionArgumentType.double,
|
|
36
|
+
[ts.SyntaxKind.BooleanKeyword]: FunctionArgumentType.boolean,
|
|
37
|
+
[ts.SyntaxKind.AnyKeyword]: FunctionArgumentType.any,
|
|
38
|
+
[ts.SyntaxKind.ObjectKeyword]: FunctionArgumentType.object,
|
|
39
|
+
[ts.SyntaxKind.VoidKeyword]: FunctionArgumentType.void,
|
|
40
|
+
[ts.SyntaxKind.NullKeyword]: FunctionArgumentType.null,
|
|
41
|
+
[ts.SyntaxKind.UndefinedKeyword]: FunctionArgumentType.undefined,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const TYPE_REFERENCE_MAP: Record<string, FunctionArgumentType> = {
|
|
45
|
+
'Function': FunctionArgumentType.function,
|
|
46
|
+
'Promise': FunctionArgumentType.promise,
|
|
47
|
+
'JSArrayProtoMethod': FunctionArgumentType.js_array_proto_methods,
|
|
48
|
+
'int': FunctionArgumentType.int,
|
|
49
|
+
'double': FunctionArgumentType.double,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function analyzer(blob: IDLBlob, definedPropertyCollector: DefinedPropertyCollector, unionTypeCollector: UnionTypeCollector) {
|
|
53
|
+
try {
|
|
54
|
+
// Check cache first
|
|
55
|
+
let sourceFile = sourceFileCache.get(blob.source);
|
|
56
|
+
if (!sourceFile) {
|
|
57
|
+
sourceFile = ts.createSourceFile(blob.source, blob.raw, ScriptTarget.ES2020);
|
|
58
|
+
sourceFileCache.set(blob.source, sourceFile);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
blob.objects = sourceFile.statements
|
|
62
|
+
.map(statement => {
|
|
63
|
+
try {
|
|
64
|
+
return walkProgram(blob, statement, definedPropertyCollector, unionTypeCollector);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(`Error processing statement in ${blob.source}:`, error);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
.filter(o => o instanceof ClassObject || o instanceof FunctionObject) as (FunctionObject | ClassObject)[];
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(`Error analyzing ${blob.source}:`, error);
|
|
73
|
+
throw new Error(`Failed to analyze ${blob.source}: ${error instanceof Error ? error.message : String(error)}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function buildClassRelationship() {
|
|
78
|
+
const globalClassMap = ClassObject.globalClassMap;
|
|
79
|
+
const globalClassRelationMap = ClassObject.globalClassRelationMap;
|
|
80
|
+
|
|
81
|
+
// Use more efficient for...in loop
|
|
82
|
+
for (const key in globalClassMap) {
|
|
83
|
+
const obj = globalClassMap[key];
|
|
84
|
+
if (obj.parent) {
|
|
85
|
+
if (!globalClassRelationMap[obj.parent]) {
|
|
86
|
+
globalClassRelationMap[obj.parent] = [];
|
|
87
|
+
}
|
|
88
|
+
globalClassRelationMap[obj.parent].push(obj.name);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getInterfaceName(statement: ts.Statement): string {
|
|
94
|
+
if (!ts.isInterfaceDeclaration(statement)) {
|
|
95
|
+
throw new Error('Statement is not an interface declaration');
|
|
96
|
+
}
|
|
97
|
+
return statement.name.escapedText as string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getHeritageType(heritage: HeritageClause): string | null {
|
|
101
|
+
if (!heritage.types.length) return null;
|
|
102
|
+
|
|
103
|
+
const expression = heritage.types[0].expression;
|
|
104
|
+
if (ts.isIdentifier(expression)) {
|
|
105
|
+
return expression.escapedText as string;
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getMixins(heritage: HeritageClause): string[] | null {
|
|
111
|
+
if (heritage.types.length <= 1) return null;
|
|
112
|
+
|
|
113
|
+
const mixins: string[] = [];
|
|
114
|
+
for (let i = 1; i < heritage.types.length; i++) {
|
|
115
|
+
const expression = heritage.types[i].expression;
|
|
116
|
+
if (ts.isIdentifier(expression)) {
|
|
117
|
+
mixins.push(expression.escapedText as string);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return mixins.length > 0 ? mixins : null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getPropName(propName: ts.PropertyName, prop?: PropsDeclaration): string {
|
|
124
|
+
switch (propName.kind) {
|
|
125
|
+
case ts.SyntaxKind.Identifier:
|
|
126
|
+
return propName.escapedText.toString();
|
|
127
|
+
case ts.SyntaxKind.StringLiteral:
|
|
128
|
+
return propName.text;
|
|
129
|
+
case ts.SyntaxKind.NumericLiteral:
|
|
130
|
+
return propName.text;
|
|
131
|
+
case ts.SyntaxKind.ComputedPropertyName:
|
|
132
|
+
if (ts.isPropertyAccessExpression(propName.expression)) {
|
|
133
|
+
if (prop) prop.isSymbol = true;
|
|
134
|
+
const expression = propName.expression;
|
|
135
|
+
return `${expression.expression.getText()}_${expression.name.getText()}`;
|
|
136
|
+
}
|
|
137
|
+
throw new Error(`Computed property name of type ${ts.SyntaxKind[propName.expression.kind]} is not supported`);
|
|
138
|
+
default:
|
|
139
|
+
throw new Error(`Property name of type ${ts.SyntaxKind[propName.kind]} is not supported`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getParameterName(name: ts.BindingName): string {
|
|
144
|
+
if (ts.isIdentifier(name)) {
|
|
145
|
+
return name.escapedText.toString();
|
|
146
|
+
}
|
|
147
|
+
// Handle other binding patterns if needed
|
|
148
|
+
console.warn('Non-identifier parameter names are not fully supported');
|
|
149
|
+
return '';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export type ParameterBaseType = FunctionArgumentType | string;
|
|
153
|
+
export type ParameterType = {
|
|
154
|
+
isArray?: boolean;
|
|
155
|
+
value: ParameterType | ParameterType[] | ParameterBaseType;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
function getParameterBaseType(type: ts.TypeNode, mode?: ParameterMode): ParameterBaseType {
|
|
159
|
+
// Check basic types first (most common case)
|
|
160
|
+
const basicType = BASIC_TYPE_MAP[type.kind];
|
|
161
|
+
if (basicType !== undefined) {
|
|
162
|
+
return basicType;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (type.kind === ts.SyntaxKind.TypeReference) {
|
|
166
|
+
const typeReference = type as ts.TypeReferenceNode;
|
|
167
|
+
const typeName = typeReference.typeName;
|
|
168
|
+
|
|
169
|
+
if (!ts.isIdentifier(typeName)) {
|
|
170
|
+
console.warn('Non-identifier type references are not supported');
|
|
171
|
+
return FunctionArgumentType.any;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const identifier = typeName.text;
|
|
175
|
+
|
|
176
|
+
// Check simple type references
|
|
177
|
+
const mappedType = TYPE_REFERENCE_MAP[identifier];
|
|
178
|
+
if (mappedType !== undefined) {
|
|
179
|
+
return mappedType;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Handle special type wrappers
|
|
183
|
+
switch (identifier) {
|
|
184
|
+
case 'NewObject':
|
|
185
|
+
if (mode) mode.newObject = true;
|
|
186
|
+
if (typeReference.typeArguments && typeReference.typeArguments[0]) {
|
|
187
|
+
const argument = typeReference.typeArguments[0];
|
|
188
|
+
if (ts.isTypeReferenceNode(argument) && ts.isIdentifier(argument.typeName)) {
|
|
189
|
+
return argument.typeName.text;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return FunctionArgumentType.any;
|
|
193
|
+
|
|
194
|
+
case 'DartImpl':
|
|
195
|
+
if (mode) mode.dartImpl = true;
|
|
196
|
+
return handleDartImplType(typeReference, mode);
|
|
197
|
+
|
|
198
|
+
case 'DependentsOnLayout':
|
|
199
|
+
if (mode) mode.layoutDependent = true;
|
|
200
|
+
return handleDependentsOnLayoutType(typeReference, mode);
|
|
201
|
+
|
|
202
|
+
case 'StaticMember':
|
|
203
|
+
if (mode) mode.static = true;
|
|
204
|
+
return handleGenericWrapper(typeReference, mode);
|
|
205
|
+
|
|
206
|
+
case 'StaticMethod':
|
|
207
|
+
if (mode) mode.staticMethod = true;
|
|
208
|
+
return handleGenericWrapper(typeReference, mode);
|
|
209
|
+
|
|
210
|
+
default:
|
|
211
|
+
if (identifier.includes('SupportAsync')) {
|
|
212
|
+
return handleSupportAsyncType(identifier, typeReference, mode);
|
|
213
|
+
}
|
|
214
|
+
return identifier;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (type.kind === ts.SyntaxKind.LiteralType) {
|
|
219
|
+
// Handle literal types - check if it's a null literal
|
|
220
|
+
const literalType = type as ts.LiteralTypeNode;
|
|
221
|
+
if (literalType.literal.kind === ts.SyntaxKind.NullKeyword) {
|
|
222
|
+
return FunctionArgumentType.null;
|
|
223
|
+
}
|
|
224
|
+
return FunctionArgumentType.any;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return FunctionArgumentType.any;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function handleDartImplType(typeReference: ts.TypeReferenceNode, mode?: ParameterMode): ParameterBaseType {
|
|
231
|
+
if (!typeReference.typeArguments || !typeReference.typeArguments[0]) {
|
|
232
|
+
return FunctionArgumentType.any;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let argument = typeReference.typeArguments[0];
|
|
236
|
+
|
|
237
|
+
if (ts.isTypeReferenceNode(argument)) {
|
|
238
|
+
const typeName = argument.typeName;
|
|
239
|
+
if (ts.isIdentifier(typeName) && typeName.text === 'DependentsOnLayout') {
|
|
240
|
+
if (mode) mode.layoutDependent = true;
|
|
241
|
+
if (argument.typeArguments && argument.typeArguments[0]) {
|
|
242
|
+
argument = argument.typeArguments[0];
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return getParameterBaseType(argument, mode);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function handleDependentsOnLayoutType(typeReference: ts.TypeReferenceNode, mode?: ParameterMode): ParameterBaseType {
|
|
251
|
+
if (!typeReference.typeArguments || !typeReference.typeArguments[0]) {
|
|
252
|
+
return FunctionArgumentType.any;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const argument = typeReference.typeArguments[0];
|
|
256
|
+
return getParameterBaseType(argument, mode);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function handleGenericWrapper(typeReference: ts.TypeReferenceNode, mode?: ParameterMode): ParameterBaseType {
|
|
260
|
+
if (!typeReference.typeArguments || !typeReference.typeArguments[0]) {
|
|
261
|
+
return FunctionArgumentType.any;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const argument = typeReference.typeArguments[0];
|
|
265
|
+
return getParameterBaseType(argument, mode);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function handleSupportAsyncType(identifier: string, typeReference: ts.TypeReferenceNode, mode?: ParameterMode): ParameterBaseType {
|
|
269
|
+
if (mode) {
|
|
270
|
+
mode.supportAsync = true;
|
|
271
|
+
if (identifier === "SupportAsyncManual") {
|
|
272
|
+
mode.supportAsyncManual = true;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!typeReference.typeArguments || !typeReference.typeArguments[0]) {
|
|
277
|
+
return FunctionArgumentType.any;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
let argument = typeReference.typeArguments[0];
|
|
281
|
+
|
|
282
|
+
if (ts.isTypeReferenceNode(argument)) {
|
|
283
|
+
const typeName = argument.typeName;
|
|
284
|
+
if (ts.isIdentifier(typeName) && typeName.text === 'DartImpl') {
|
|
285
|
+
if (mode) mode.dartImpl = true;
|
|
286
|
+
if (argument.typeArguments && argument.typeArguments[0]) {
|
|
287
|
+
argument = argument.typeArguments[0];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
} else if (ts.isArrayTypeNode(argument)) {
|
|
291
|
+
if (mode) mode.supportAsyncArrayValue = true;
|
|
292
|
+
return getParameterBaseType(argument.elementType, mode);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return getParameterBaseType(argument, mode);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function getParameterType(type: ts.TypeNode, unionTypeCollector: UnionTypeCollector, mode?: ParameterMode): ParameterType {
|
|
299
|
+
// Generate cache key
|
|
300
|
+
const cacheKey = `${type.kind}_${type.pos}_${type.end}`;
|
|
301
|
+
|
|
302
|
+
// Check cache first (only for types without mode)
|
|
303
|
+
if (!mode) {
|
|
304
|
+
const cached = typeConversionCache.get(cacheKey);
|
|
305
|
+
if (cached) return cached;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
let result: ParameterType;
|
|
309
|
+
|
|
310
|
+
if (type.kind === ts.SyntaxKind.ParenthesizedType) {
|
|
311
|
+
const typeNode = type as ts.ParenthesizedTypeNode;
|
|
312
|
+
result = getParameterType(typeNode.type, unionTypeCollector, mode);
|
|
313
|
+
} else if (type.kind === ts.SyntaxKind.ArrayType) {
|
|
314
|
+
const arrayType = type as ts.ArrayTypeNode;
|
|
315
|
+
result = {
|
|
316
|
+
isArray: true,
|
|
317
|
+
value: getParameterType(arrayType.elementType, unionTypeCollector, mode)
|
|
318
|
+
};
|
|
319
|
+
} else if (type.kind === ts.SyntaxKind.UnionType) {
|
|
320
|
+
const unionType = type as ts.UnionTypeNode;
|
|
321
|
+
const types = unionType.types.map(t => getParameterType(t, unionTypeCollector, mode));
|
|
322
|
+
result = {
|
|
323
|
+
isArray: false,
|
|
324
|
+
value: types
|
|
325
|
+
};
|
|
326
|
+
if (isUnionType(result)) {
|
|
327
|
+
unionTypeCollector.types.add(result.value as ParameterType[]);
|
|
328
|
+
}
|
|
329
|
+
} else if (type.kind === ts.SyntaxKind.TypeReference) {
|
|
330
|
+
result = handleTypeReferenceForParameter(type as ts.TypeReferenceNode, unionTypeCollector, mode);
|
|
331
|
+
} else {
|
|
332
|
+
result = {
|
|
333
|
+
isArray: false,
|
|
334
|
+
value: getParameterBaseType(type, mode)
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Cache the result if no mode
|
|
339
|
+
if (!mode) {
|
|
340
|
+
typeConversionCache.set(cacheKey, result);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return result;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function handleTypeReferenceForParameter(typeReference: ts.TypeReferenceNode, unionTypeCollector: UnionTypeCollector, mode?: ParameterMode): ParameterType {
|
|
347
|
+
const typeName = typeReference.typeName;
|
|
348
|
+
|
|
349
|
+
if (!ts.isIdentifier(typeName)) {
|
|
350
|
+
return {
|
|
351
|
+
isArray: false,
|
|
352
|
+
value: FunctionArgumentType.any
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const identifier = typeName.text;
|
|
357
|
+
|
|
358
|
+
if (identifier.includes('SupportAsync') && typeReference.typeArguments && typeReference.typeArguments[0]) {
|
|
359
|
+
const argument = typeReference.typeArguments[0];
|
|
360
|
+
|
|
361
|
+
if (ts.isUnionTypeNode(argument)) {
|
|
362
|
+
if (mode) {
|
|
363
|
+
mode.supportAsync = true;
|
|
364
|
+
if (identifier === 'SupportAsyncManual') {
|
|
365
|
+
mode.supportAsyncManual = true;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const types = argument.types.map(t => getParameterType(t, unionTypeCollector, mode));
|
|
370
|
+
const result = {
|
|
371
|
+
isArray: false,
|
|
372
|
+
value: types
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
if (isUnionType(result)) {
|
|
376
|
+
unionTypeCollector.types.add(result.value as ParameterType[]);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return result;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
isArray: false,
|
|
385
|
+
value: getParameterBaseType(typeReference, mode)
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function paramsNodeToArguments(parameter: ts.ParameterDeclaration, unionTypeCollector: UnionTypeCollector): FunctionArguments {
|
|
390
|
+
const args = new FunctionArguments();
|
|
391
|
+
args.name = getParameterName(parameter.name);
|
|
392
|
+
|
|
393
|
+
if (!parameter.type) {
|
|
394
|
+
console.warn(`Parameter ${args.name} has no type annotation, defaulting to any`);
|
|
395
|
+
args.type = { isArray: false, value: FunctionArgumentType.any };
|
|
396
|
+
return args;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const typeMode = new ParameterMode();
|
|
400
|
+
args.type = getParameterType(parameter.type, unionTypeCollector, typeMode);
|
|
401
|
+
args.isDotDotDot = !!parameter.dotDotDotToken;
|
|
402
|
+
args.typeMode = typeMode;
|
|
403
|
+
args.required = !parameter.questionToken;
|
|
404
|
+
|
|
405
|
+
return args;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function isParamsReadOnly(m: ts.PropertySignature): boolean {
|
|
409
|
+
if (!m.modifiers) return false;
|
|
410
|
+
return m.modifiers.some(k => k.kind === ts.SyntaxKind.ReadonlyKeyword);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function walkProgram(blob: IDLBlob, statement: ts.Statement, definedPropertyCollector: DefinedPropertyCollector, unionTypeCollector: UnionTypeCollector) {
|
|
414
|
+
switch(statement.kind) {
|
|
415
|
+
case ts.SyntaxKind.InterfaceDeclaration:
|
|
416
|
+
return processInterfaceDeclaration(statement as ts.InterfaceDeclaration, blob, definedPropertyCollector, unionTypeCollector);
|
|
417
|
+
|
|
418
|
+
case ts.SyntaxKind.VariableStatement:
|
|
419
|
+
return processVariableStatement(statement as VariableStatement, unionTypeCollector);
|
|
420
|
+
|
|
421
|
+
default:
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function processInterfaceDeclaration(
|
|
427
|
+
statement: ts.InterfaceDeclaration,
|
|
428
|
+
blob: IDLBlob,
|
|
429
|
+
definedPropertyCollector: DefinedPropertyCollector,
|
|
430
|
+
unionTypeCollector: UnionTypeCollector
|
|
431
|
+
): ClassObject | null {
|
|
432
|
+
const interfaceName = statement.name.escapedText.toString();
|
|
433
|
+
const obj = new ClassObject();
|
|
434
|
+
|
|
435
|
+
// Process heritage clauses
|
|
436
|
+
if (statement.heritageClauses) {
|
|
437
|
+
const heritage = statement.heritageClauses[0];
|
|
438
|
+
const heritageType = getHeritageType(heritage);
|
|
439
|
+
const mixins = getMixins(heritage);
|
|
440
|
+
|
|
441
|
+
if (heritageType) obj.parent = heritageType;
|
|
442
|
+
if (mixins) obj.mixinParent = mixins;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
obj.name = interfaceName;
|
|
446
|
+
|
|
447
|
+
if (obj.kind === ClassObjectKind.interface) {
|
|
448
|
+
definedPropertyCollector.interfaces.add('QJS' + interfaceName);
|
|
449
|
+
definedPropertyCollector.files.add(blob.filename);
|
|
450
|
+
definedPropertyCollector.properties.add(interfaceName);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Process members in batches for better performance
|
|
454
|
+
const members = Array.from(statement.members);
|
|
455
|
+
processMembersBatch(members, obj, definedPropertyCollector, unionTypeCollector);
|
|
456
|
+
|
|
457
|
+
ClassObject.globalClassMap[interfaceName] = obj;
|
|
458
|
+
|
|
459
|
+
return obj;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function processMembersBatch(
|
|
463
|
+
members: ts.TypeElement[],
|
|
464
|
+
obj: ClassObject,
|
|
465
|
+
definedPropertyCollector: DefinedPropertyCollector,
|
|
466
|
+
unionTypeCollector: UnionTypeCollector
|
|
467
|
+
): void {
|
|
468
|
+
for (const member of members) {
|
|
469
|
+
try {
|
|
470
|
+
processMember(member, obj, definedPropertyCollector, unionTypeCollector);
|
|
471
|
+
} catch (error) {
|
|
472
|
+
console.error(`Error processing member:`, error);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function processMember(
|
|
478
|
+
member: ts.TypeElement,
|
|
479
|
+
obj: ClassObject,
|
|
480
|
+
definedPropertyCollector: DefinedPropertyCollector,
|
|
481
|
+
unionTypeCollector: UnionTypeCollector
|
|
482
|
+
): void {
|
|
483
|
+
switch(member.kind) {
|
|
484
|
+
case ts.SyntaxKind.PropertySignature:
|
|
485
|
+
processPropertySignature(member as ts.PropertySignature, obj, definedPropertyCollector, unionTypeCollector);
|
|
486
|
+
break;
|
|
487
|
+
|
|
488
|
+
case ts.SyntaxKind.MethodSignature:
|
|
489
|
+
processMethodSignature(member as ts.MethodSignature, obj, unionTypeCollector);
|
|
490
|
+
break;
|
|
491
|
+
|
|
492
|
+
case ts.SyntaxKind.IndexSignature:
|
|
493
|
+
processIndexSignature(member as ts.IndexSignatureDeclaration, obj, unionTypeCollector);
|
|
494
|
+
break;
|
|
495
|
+
|
|
496
|
+
case ts.SyntaxKind.ConstructSignature:
|
|
497
|
+
processConstructSignature(member as ts.ConstructSignatureDeclaration, obj, unionTypeCollector);
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function processPropertySignature(
|
|
503
|
+
member: ts.PropertySignature,
|
|
504
|
+
obj: ClassObject,
|
|
505
|
+
definedPropertyCollector: DefinedPropertyCollector,
|
|
506
|
+
unionTypeCollector: UnionTypeCollector
|
|
507
|
+
): void {
|
|
508
|
+
const prop = new PropsDeclaration();
|
|
509
|
+
prop.name = getPropName(member.name!, prop);
|
|
510
|
+
prop.readonly = isParamsReadOnly(member);
|
|
511
|
+
|
|
512
|
+
definedPropertyCollector.properties.add(prop.name);
|
|
513
|
+
|
|
514
|
+
if (!member.type) {
|
|
515
|
+
console.warn(`Property ${prop.name} has no type annotation`);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const mode = new ParameterMode();
|
|
520
|
+
prop.type = getParameterType(member.type, unionTypeCollector, mode);
|
|
521
|
+
prop.typeMode = mode;
|
|
522
|
+
prop.optional = !!member.questionToken;
|
|
523
|
+
|
|
524
|
+
if (prop.type.value === FunctionArgumentType.function && ts.isFunctionTypeNode(member.type)) {
|
|
525
|
+
const functionProps = prop as FunctionDeclaration;
|
|
526
|
+
functionProps.args = member.type.parameters.map(params =>
|
|
527
|
+
paramsNodeToArguments(params, unionTypeCollector)
|
|
528
|
+
);
|
|
529
|
+
obj.methods.push(functionProps);
|
|
530
|
+
} else {
|
|
531
|
+
obj.props.push(prop);
|
|
532
|
+
|
|
533
|
+
// Handle async properties
|
|
534
|
+
if (prop.typeMode.supportAsync) {
|
|
535
|
+
const asyncProp = createAsyncProperty(prop, mode);
|
|
536
|
+
definedPropertyCollector.properties.add(asyncProp.name);
|
|
537
|
+
obj.props.push(asyncProp);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function createAsyncProperty(prop: PropsDeclaration, mode: ParameterMode): PropsDeclaration {
|
|
543
|
+
const asyncProp = Object.assign({}, prop);
|
|
544
|
+
const syncMode = Object.assign({}, mode);
|
|
545
|
+
|
|
546
|
+
syncMode.supportAsync = false;
|
|
547
|
+
syncMode.supportAsyncManual = false;
|
|
548
|
+
prop.typeMode = syncMode;
|
|
549
|
+
|
|
550
|
+
asyncProp.name = asyncProp.name + '_async';
|
|
551
|
+
asyncProp.async_type = {
|
|
552
|
+
isArray: false,
|
|
553
|
+
value: FunctionArgumentType.promise
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
return asyncProp;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function processMethodSignature(
|
|
560
|
+
member: ts.MethodSignature,
|
|
561
|
+
obj: ClassObject,
|
|
562
|
+
unionTypeCollector: UnionTypeCollector
|
|
563
|
+
): void {
|
|
564
|
+
const f = new FunctionDeclaration();
|
|
565
|
+
f.name = getPropName(member.name!);
|
|
566
|
+
f.args = member.parameters.map(params =>
|
|
567
|
+
paramsNodeToArguments(params, unionTypeCollector)
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
if (member.type) {
|
|
571
|
+
const mode = new ParameterMode();
|
|
572
|
+
f.returnType = getParameterType(member.type, unionTypeCollector, mode);
|
|
573
|
+
|
|
574
|
+
if (mode.supportAsyncArrayValue) {
|
|
575
|
+
f.returnType = {
|
|
576
|
+
isArray: true,
|
|
577
|
+
value: f.returnType
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
f.returnTypeMode = mode;
|
|
582
|
+
|
|
583
|
+
if (f.returnTypeMode.staticMethod) {
|
|
584
|
+
obj.staticMethods.push(f);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
obj.methods.push(f);
|
|
589
|
+
|
|
590
|
+
// Handle async methods
|
|
591
|
+
if (f.returnTypeMode?.supportAsync) {
|
|
592
|
+
const asyncFunc = createAsyncMethod(member, f, unionTypeCollector);
|
|
593
|
+
obj.methods.push(asyncFunc);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function createAsyncMethod(
|
|
598
|
+
member: ts.MethodSignature,
|
|
599
|
+
originalFunc: FunctionDeclaration,
|
|
600
|
+
unionTypeCollector: UnionTypeCollector
|
|
601
|
+
): FunctionDeclaration {
|
|
602
|
+
const asyncFunc = Object.assign({}, originalFunc);
|
|
603
|
+
const mode = Object.assign({}, originalFunc.returnTypeMode);
|
|
604
|
+
|
|
605
|
+
mode.supportAsync = false;
|
|
606
|
+
mode.supportAsyncManual = false;
|
|
607
|
+
originalFunc.returnTypeMode = mode;
|
|
608
|
+
|
|
609
|
+
asyncFunc.name = getPropName(member.name!) + '_async';
|
|
610
|
+
asyncFunc.args = member.parameters.map(params =>
|
|
611
|
+
paramsNodeToArguments(params, unionTypeCollector)
|
|
612
|
+
);
|
|
613
|
+
asyncFunc.async_returnType = {
|
|
614
|
+
isArray: false,
|
|
615
|
+
value: FunctionArgumentType.promise
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
return asyncFunc;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function processIndexSignature(
|
|
622
|
+
member: ts.IndexSignatureDeclaration,
|
|
623
|
+
obj: ClassObject,
|
|
624
|
+
unionTypeCollector: UnionTypeCollector
|
|
625
|
+
): void {
|
|
626
|
+
const prop = new IndexedPropertyDeclaration();
|
|
627
|
+
const modifier = member.modifiers;
|
|
628
|
+
prop.readonly = !!(modifier && modifier[0].kind === ts.SyntaxKind.ReadonlyKeyword);
|
|
629
|
+
|
|
630
|
+
const params = member.parameters;
|
|
631
|
+
if (params.length > 0 && params[0].type) {
|
|
632
|
+
prop.indexKeyType = params[0].type.kind === ts.SyntaxKind.NumberKeyword ? 'number' : 'string';
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const mode = new ParameterMode();
|
|
636
|
+
prop.type = getParameterType(member.type, unionTypeCollector, mode);
|
|
637
|
+
prop.typeMode = mode;
|
|
638
|
+
obj.indexedProp = prop;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function processConstructSignature(
|
|
642
|
+
member: ts.ConstructSignatureDeclaration,
|
|
643
|
+
obj: ClassObject,
|
|
644
|
+
unionTypeCollector: UnionTypeCollector
|
|
645
|
+
): void {
|
|
646
|
+
const c = new FunctionDeclaration();
|
|
647
|
+
c.name = 'constructor';
|
|
648
|
+
c.args = member.parameters.map(params =>
|
|
649
|
+
paramsNodeToArguments(params, unionTypeCollector)
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
if (member.type) {
|
|
653
|
+
c.returnType = getParameterType(member.type, unionTypeCollector);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
obj.construct = c;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function processVariableStatement(
|
|
660
|
+
statement: VariableStatement,
|
|
661
|
+
unionTypeCollector: UnionTypeCollector
|
|
662
|
+
): FunctionObject | null {
|
|
663
|
+
const declaration = statement.declarationList.declarations[0];
|
|
664
|
+
|
|
665
|
+
if (!ts.isIdentifier(declaration.name)) {
|
|
666
|
+
console.warn('Variable declaration with non-identifier name is not supported');
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const methodName = declaration.name.text;
|
|
671
|
+
const type = declaration.type;
|
|
672
|
+
|
|
673
|
+
if (!type || !ts.isFunctionTypeNode(type)) {
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const functionObject = new FunctionObject();
|
|
678
|
+
functionObject.declare = new FunctionDeclaration();
|
|
679
|
+
functionObject.declare.name = methodName;
|
|
680
|
+
functionObject.declare.args = type.parameters.map(param =>
|
|
681
|
+
paramsNodeToArguments(param, unionTypeCollector)
|
|
682
|
+
);
|
|
683
|
+
functionObject.declare.returnType = getParameterType(type.type, unionTypeCollector);
|
|
684
|
+
|
|
685
|
+
return functionObject;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Clear caches when needed (e.g., between runs)
|
|
689
|
+
export function clearCaches() {
|
|
690
|
+
sourceFileCache.clear();
|
|
691
|
+
typeConversionCache.clear();
|
|
692
|
+
}
|