@openwebf/webf 0.24.1 → 0.24.3
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 +21 -1
- package/dist/analyzer.js +183 -121
- package/dist/commands.js +54 -9
- package/dist/generator.js +39 -16
- package/dist/module.js +157 -5
- package/package.json +1 -1
- package/src/analyzer.ts +186 -114
- package/src/commands.ts +65 -11
- package/src/generator.ts +32 -12
- package/src/module.ts +222 -5
- package/templates/module.package.json.tpl +17 -6
- package/templates/{module.tsup.config.ts.tpl → module.tsdown.config.ts.tpl} +2 -7
- package/templates/react.component.tsx.tpl +18 -2
- package/templates/react.package.json.tpl +17 -5
- package/templates/{react.tsup.config.ts.tpl → react.tsdown.config.ts.tpl} +2 -4
- package/templates/vue.package.json.tpl +1 -1
- package/test/analyzer.test.ts +45 -1
- package/test/commands.test.ts +76 -4
- package/test/generator.test.ts +37 -0
- package/test/module-events.test.ts +83 -0
- package/test/react.test.ts +5 -0
package/dist/module.js
CHANGED
|
@@ -14,12 +14,12 @@ function parseModuleDefinition(modulePath) {
|
|
|
14
14
|
const sourceFile = typescript_1.default.createSourceFile(modulePath, sourceText, typescript_1.default.ScriptTarget.ES2020, true, typescript_1.default.ScriptKind.TS);
|
|
15
15
|
let interfaceDecl;
|
|
16
16
|
const supporting = [];
|
|
17
|
+
const webfInterfaceDecls = [];
|
|
17
18
|
for (const stmt of sourceFile.statements) {
|
|
18
19
|
if (typescript_1.default.isInterfaceDeclaration(stmt)) {
|
|
19
20
|
const name = stmt.name.text;
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
}
|
|
21
|
+
if (name.startsWith('WebF'))
|
|
22
|
+
webfInterfaceDecls.push(stmt);
|
|
23
23
|
supporting.push(stmt);
|
|
24
24
|
}
|
|
25
25
|
else if (typescript_1.default.isTypeAliasDeclaration(stmt) ||
|
|
@@ -28,6 +28,13 @@ function parseModuleDefinition(modulePath) {
|
|
|
28
28
|
supporting.push(stmt);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
// Prefer the "main module interface": first WebF* interface that declares methods.
|
|
32
|
+
if (!interfaceDecl) {
|
|
33
|
+
interfaceDecl = webfInterfaceDecls.find(decl => decl.members.some(member => typescript_1.default.isMethodSignature(member)));
|
|
34
|
+
}
|
|
35
|
+
if (!interfaceDecl) {
|
|
36
|
+
interfaceDecl = webfInterfaceDecls[0];
|
|
37
|
+
}
|
|
31
38
|
if (!interfaceDecl) {
|
|
32
39
|
throw new Error(`No interface starting with "WebF" found in module interface file: ${modulePath}`);
|
|
33
40
|
}
|
|
@@ -70,10 +77,65 @@ function parseModuleDefinition(modulePath) {
|
|
|
70
77
|
documentation,
|
|
71
78
|
});
|
|
72
79
|
}
|
|
80
|
+
// Optional module events declaration:
|
|
81
|
+
// `interface WebF<ModuleName>ModuleEvents { scanResult: [Event, Payload]; }`
|
|
82
|
+
const eventsInterfaceName = `${interfaceName}ModuleEvents`;
|
|
83
|
+
const eventsDecl = sourceFile.statements.find(stmt => typescript_1.default.isInterfaceDeclaration(stmt) && stmt.name.text === eventsInterfaceName);
|
|
84
|
+
let events;
|
|
85
|
+
if (eventsDecl) {
|
|
86
|
+
const eventSpecs = [];
|
|
87
|
+
for (const member of eventsDecl.members) {
|
|
88
|
+
if (!typescript_1.default.isPropertySignature(member) || !member.name)
|
|
89
|
+
continue;
|
|
90
|
+
const rawName = member.name.getText(sourceFile);
|
|
91
|
+
const eventName = rawName.replace(/['"]/g, '');
|
|
92
|
+
let eventTypeText = 'Event';
|
|
93
|
+
let extraTypeText = 'any';
|
|
94
|
+
if (member.type) {
|
|
95
|
+
if (typescript_1.default.isTupleTypeNode(member.type) && member.type.elements.length === 2) {
|
|
96
|
+
eventTypeText = printer.printNode(typescript_1.default.EmitHint.Unspecified, member.type.elements[0], sourceFile);
|
|
97
|
+
extraTypeText = printer.printNode(typescript_1.default.EmitHint.Unspecified, member.type.elements[1], sourceFile);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
eventTypeText = printer.printNode(typescript_1.default.EmitHint.Unspecified, member.type, sourceFile);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
let documentation;
|
|
104
|
+
const jsDocs = member.jsDoc;
|
|
105
|
+
if (jsDocs && jsDocs.length > 0) {
|
|
106
|
+
documentation = jsDocs
|
|
107
|
+
.map(doc => doc.comment)
|
|
108
|
+
.filter(Boolean)
|
|
109
|
+
.join('\n');
|
|
110
|
+
}
|
|
111
|
+
eventSpecs.push({
|
|
112
|
+
name: eventName,
|
|
113
|
+
eventTypeText,
|
|
114
|
+
extraTypeText,
|
|
115
|
+
documentation,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (eventSpecs.length > 0) {
|
|
119
|
+
events = {
|
|
120
|
+
interfaceName: eventsInterfaceName,
|
|
121
|
+
eventNameTypeName: `${interfaceName}ModuleEventName`,
|
|
122
|
+
eventArgsTypeName: `${interfaceName}ModuleEventArgs`,
|
|
123
|
+
listenerTypeName: `${interfaceName}ModuleEventListener`,
|
|
124
|
+
events: eventSpecs,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
73
128
|
if (methods.length === 0) {
|
|
74
129
|
throw new Error(`Interface ${interfaceName} in ${modulePath} does not declare any methods`);
|
|
75
130
|
}
|
|
76
|
-
return {
|
|
131
|
+
return {
|
|
132
|
+
interfaceName,
|
|
133
|
+
moduleName,
|
|
134
|
+
methods,
|
|
135
|
+
events,
|
|
136
|
+
supportingStatements: supporting,
|
|
137
|
+
sourceFile,
|
|
138
|
+
};
|
|
77
139
|
}
|
|
78
140
|
function buildTypesFile(def) {
|
|
79
141
|
const printer = typescript_1.default.createPrinter();
|
|
@@ -102,6 +164,19 @@ function buildTypesFile(def) {
|
|
|
102
164
|
lines.push(printed);
|
|
103
165
|
}
|
|
104
166
|
lines.push('');
|
|
167
|
+
if (def.events) {
|
|
168
|
+
const { interfaceName, eventNameTypeName, eventArgsTypeName, listenerTypeName } = def.events;
|
|
169
|
+
lines.push(`export type ${eventNameTypeName} = Extract<keyof ${interfaceName}, string>;`);
|
|
170
|
+
lines.push(`export type ${eventArgsTypeName}<K extends ${eventNameTypeName} = ${eventNameTypeName}> =`);
|
|
171
|
+
lines.push(` ${interfaceName}[K] extends readonly [infer E, infer X]`);
|
|
172
|
+
lines.push(` ? [event: (E & { type: K }), extra: X]`);
|
|
173
|
+
lines.push(` : [event: (${interfaceName}[K] & { type: K }), extra: any];`);
|
|
174
|
+
lines.push('');
|
|
175
|
+
lines.push(`export type ${listenerTypeName} = (...args: {`);
|
|
176
|
+
lines.push(` [K in ${eventNameTypeName}]: ${eventArgsTypeName}<K>;`);
|
|
177
|
+
lines.push(`}[${eventNameTypeName}]) => any;`);
|
|
178
|
+
lines.push('');
|
|
179
|
+
}
|
|
105
180
|
// Ensure file is treated as a module even if no declarations were emitted.
|
|
106
181
|
lines.push('export {};');
|
|
107
182
|
return lines.join('\n');
|
|
@@ -128,6 +203,10 @@ function buildIndexFile(def) {
|
|
|
128
203
|
typeImportNames.add(name);
|
|
129
204
|
}
|
|
130
205
|
}
|
|
206
|
+
if (def.events) {
|
|
207
|
+
typeImportNames.add(def.events.eventNameTypeName);
|
|
208
|
+
typeImportNames.add(def.events.eventArgsTypeName);
|
|
209
|
+
}
|
|
131
210
|
const typeImportsSorted = Array.from(typeImportNames).sort();
|
|
132
211
|
if (typeImportsSorted.length > 0) {
|
|
133
212
|
lines.push(`import type { ${typeImportsSorted.join(', ')} } from './types';`);
|
|
@@ -138,6 +217,49 @@ function buildIndexFile(def) {
|
|
|
138
217
|
lines.push(" return typeof webf !== 'undefined' && typeof (webf as any).invokeModuleAsync === 'function';");
|
|
139
218
|
lines.push(' }');
|
|
140
219
|
lines.push('');
|
|
220
|
+
if (def.events) {
|
|
221
|
+
lines.push(' private static _moduleListenerInstalled = false;');
|
|
222
|
+
lines.push(' private static _listeners: Record<string, Set<(event: Event, extra: any) => any>> = Object.create(null);');
|
|
223
|
+
lines.push('');
|
|
224
|
+
lines.push(` static addListener<K extends ${def.events.eventNameTypeName}>(type: K, listener: (...args: ${def.events.eventArgsTypeName}<K>) => any): () => void {`);
|
|
225
|
+
lines.push(" if (typeof webf === 'undefined' || typeof (webf as any).addWebfModuleListener !== 'function') {");
|
|
226
|
+
lines.push(" throw new Error('WebF module event API is not available. Make sure you are running in WebF runtime.');");
|
|
227
|
+
lines.push(' }');
|
|
228
|
+
lines.push('');
|
|
229
|
+
lines.push(' if (!this._moduleListenerInstalled) {');
|
|
230
|
+
lines.push(` (webf as any).addWebfModuleListener('${def.moduleName}', (event: Event, extra: any) => {`);
|
|
231
|
+
lines.push(' const set = this._listeners[event.type];');
|
|
232
|
+
lines.push(' if (!set) return;');
|
|
233
|
+
lines.push(' for (const fn of set) { fn(event, extra); }');
|
|
234
|
+
lines.push(' });');
|
|
235
|
+
lines.push(' this._moduleListenerInstalled = true;');
|
|
236
|
+
lines.push(' }');
|
|
237
|
+
lines.push('');
|
|
238
|
+
lines.push(' (this._listeners[type] ??= new Set()).add(listener as any);');
|
|
239
|
+
lines.push('');
|
|
240
|
+
lines.push(' const cls = this;');
|
|
241
|
+
lines.push(' return () => {');
|
|
242
|
+
lines.push(' const set = cls._listeners[type];');
|
|
243
|
+
lines.push(' if (!set) return;');
|
|
244
|
+
lines.push(' set.delete(listener as any);');
|
|
245
|
+
lines.push(' if (set.size === 0) { delete cls._listeners[type]; }');
|
|
246
|
+
lines.push('');
|
|
247
|
+
lines.push(' if (Object.keys(cls._listeners).length === 0) {');
|
|
248
|
+
lines.push(' cls.removeListener();');
|
|
249
|
+
lines.push(' }');
|
|
250
|
+
lines.push(' };');
|
|
251
|
+
lines.push(' }');
|
|
252
|
+
lines.push('');
|
|
253
|
+
lines.push(' static removeListener(): void {');
|
|
254
|
+
lines.push(' this._listeners = Object.create(null);');
|
|
255
|
+
lines.push(' this._moduleListenerInstalled = false;');
|
|
256
|
+
lines.push(" if (typeof webf === 'undefined' || typeof (webf as any).removeWebfModuleListener !== 'function') {");
|
|
257
|
+
lines.push(' return;');
|
|
258
|
+
lines.push(' }');
|
|
259
|
+
lines.push(` (webf as any).removeWebfModuleListener('${def.moduleName}');`);
|
|
260
|
+
lines.push(' }');
|
|
261
|
+
lines.push('');
|
|
262
|
+
}
|
|
141
263
|
for (const method of def.methods) {
|
|
142
264
|
if (method.documentation) {
|
|
143
265
|
lines.push(' /**');
|
|
@@ -173,6 +295,11 @@ function buildIndexFile(def) {
|
|
|
173
295
|
typeExportNames.add(stmt.name.text);
|
|
174
296
|
}
|
|
175
297
|
}
|
|
298
|
+
if (def.events) {
|
|
299
|
+
typeExportNames.add(def.events.eventNameTypeName);
|
|
300
|
+
typeExportNames.add(def.events.eventArgsTypeName);
|
|
301
|
+
typeExportNames.add(def.events.listenerTypeName);
|
|
302
|
+
}
|
|
176
303
|
const sorted = Array.from(typeExportNames).sort();
|
|
177
304
|
if (sorted.length) {
|
|
178
305
|
lines.push(' ' + sorted.join(','));
|
|
@@ -260,6 +387,7 @@ function mapTsPropertyTypeToDart(type, optional) {
|
|
|
260
387
|
}
|
|
261
388
|
}
|
|
262
389
|
function buildDartBindings(def, command) {
|
|
390
|
+
var _a;
|
|
263
391
|
const dartClassBase = `${def.moduleName}Module`;
|
|
264
392
|
const dartBindingsClass = `${dartClassBase}Bindings`;
|
|
265
393
|
const lines = [];
|
|
@@ -268,6 +396,9 @@ function buildDartBindings(def, command) {
|
|
|
268
396
|
lines.push('// Generated by `webf module-codegen`');
|
|
269
397
|
lines.push('');
|
|
270
398
|
lines.push("import 'package:webf/module.dart';");
|
|
399
|
+
if (def.events) {
|
|
400
|
+
lines.push("import 'package:webf/dom.dart';");
|
|
401
|
+
}
|
|
271
402
|
if (def.methods.some(m => m.params.some(p => isTsByteArrayUnion(p.typeText)))) {
|
|
272
403
|
lines.push("import 'package:webf/bridge.dart';");
|
|
273
404
|
}
|
|
@@ -275,7 +406,9 @@ function buildDartBindings(def, command) {
|
|
|
275
406
|
// Generate Dart classes for supporting TS interfaces (compound option types).
|
|
276
407
|
const optionInterfaces = [];
|
|
277
408
|
for (const stmt of def.supportingStatements) {
|
|
278
|
-
if (typescript_1.default.isInterfaceDeclaration(stmt) &&
|
|
409
|
+
if (typescript_1.default.isInterfaceDeclaration(stmt) &&
|
|
410
|
+
stmt.name.text !== def.interfaceName &&
|
|
411
|
+
stmt.name.text !== ((_a = def.events) === null || _a === void 0 ? void 0 : _a.interfaceName)) {
|
|
279
412
|
optionInterfaces.push(stmt);
|
|
280
413
|
}
|
|
281
414
|
}
|
|
@@ -365,6 +498,25 @@ function buildDartBindings(def, command) {
|
|
|
365
498
|
lines.push(` @override`);
|
|
366
499
|
lines.push(` String get name => '${def.moduleName}';`);
|
|
367
500
|
lines.push('');
|
|
501
|
+
if (def.events) {
|
|
502
|
+
for (const evt of def.events.events) {
|
|
503
|
+
const methodName = `emit${lodash_1.default.upperFirst(lodash_1.default.camelCase(evt.name))}`;
|
|
504
|
+
const mappedExtra = mapTsParamTypeToDart(evt.extraTypeText, optionTypeNames);
|
|
505
|
+
const dataParamType = mappedExtra.optionClassName
|
|
506
|
+
? `${mappedExtra.optionClassName}?`
|
|
507
|
+
: 'dynamic';
|
|
508
|
+
lines.push(` dynamic ${methodName}({Event? event, ${dataParamType} data}) {`);
|
|
509
|
+
if (mappedExtra.optionClassName) {
|
|
510
|
+
lines.push(' final mapped = data?.toMap();');
|
|
511
|
+
lines.push(` return dispatchEvent(event: event ?? Event('${evt.name}'), data: mapped);`);
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
lines.push(` return dispatchEvent(event: event ?? Event('${evt.name}'), data: data);`);
|
|
515
|
+
}
|
|
516
|
+
lines.push(' }');
|
|
517
|
+
lines.push('');
|
|
518
|
+
}
|
|
519
|
+
}
|
|
368
520
|
for (const method of def.methods) {
|
|
369
521
|
const dartMethodName = lodash_1.default.camelCase(method.name);
|
|
370
522
|
let dartReturnType = mapTsReturnTypeToDart(method.returnTypeText);
|
package/package.json
CHANGED
package/src/analyzer.ts
CHANGED
|
@@ -410,6 +410,188 @@ function handleGenericWrapper(typeReference: ts.TypeReferenceNode, mode?: Parame
|
|
|
410
410
|
return getParameterBaseType(argument, mode);
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
+
const customEventTypePrinter = ts.createPrinter({ removeComments: true });
|
|
414
|
+
|
|
415
|
+
function mapTypeReferenceIdentifierToTsType(identifier: string): string | null {
|
|
416
|
+
const mappedType = TYPE_REFERENCE_MAP[identifier];
|
|
417
|
+
if (mappedType === undefined) return null;
|
|
418
|
+
switch (mappedType) {
|
|
419
|
+
case FunctionArgumentType.boolean:
|
|
420
|
+
return 'boolean';
|
|
421
|
+
case FunctionArgumentType.dom_string:
|
|
422
|
+
return 'string';
|
|
423
|
+
case FunctionArgumentType.double:
|
|
424
|
+
case FunctionArgumentType.int:
|
|
425
|
+
return 'number';
|
|
426
|
+
case FunctionArgumentType.any:
|
|
427
|
+
return 'any';
|
|
428
|
+
case FunctionArgumentType.void:
|
|
429
|
+
return 'void';
|
|
430
|
+
case FunctionArgumentType.function:
|
|
431
|
+
return 'Function';
|
|
432
|
+
case FunctionArgumentType.promise:
|
|
433
|
+
return 'Promise<any>';
|
|
434
|
+
default:
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function getBasicTypeKindAsTsType(kind: ts.SyntaxKind): string | null {
|
|
440
|
+
const basicType = BASIC_TYPE_MAP[kind];
|
|
441
|
+
if (basicType === undefined) return null;
|
|
442
|
+
switch (basicType) {
|
|
443
|
+
case FunctionArgumentType.boolean:
|
|
444
|
+
return 'boolean';
|
|
445
|
+
case FunctionArgumentType.dom_string:
|
|
446
|
+
return 'string';
|
|
447
|
+
case FunctionArgumentType.double:
|
|
448
|
+
case FunctionArgumentType.int:
|
|
449
|
+
return 'number';
|
|
450
|
+
case FunctionArgumentType.any:
|
|
451
|
+
return 'any';
|
|
452
|
+
case FunctionArgumentType.void:
|
|
453
|
+
return 'void';
|
|
454
|
+
case FunctionArgumentType.null:
|
|
455
|
+
return 'null';
|
|
456
|
+
case FunctionArgumentType.undefined:
|
|
457
|
+
return 'undefined';
|
|
458
|
+
default:
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function stringifyEntityName(name: ts.EntityName): string {
|
|
464
|
+
if (ts.isIdentifier(name)) return name.text;
|
|
465
|
+
return `${stringifyEntityName(name.left)}.${name.right.text}`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function safePrintCustomEventNode(node: ts.Node): string {
|
|
469
|
+
const sourceFile = node.getSourceFile();
|
|
470
|
+
const printed = customEventTypePrinter.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
471
|
+
// Ensure WebF IDL-like aliases used in type definitions do not leak into generated TypeScript packages.
|
|
472
|
+
return printed.replace(/\bint\b/g, 'number').replace(/\bdouble\b/g, 'number');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function stringifyCustomEventGenericTypeNode(typeNode: ts.TypeNode): string | null {
|
|
476
|
+
if (ts.isParenthesizedTypeNode(typeNode)) {
|
|
477
|
+
const inner = stringifyCustomEventGenericTypeNode(typeNode.type);
|
|
478
|
+
return inner ? `(${inner})` : null;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (ts.isUnionTypeNode(typeNode)) {
|
|
482
|
+
const parts = typeNode.types.map(t => stringifyCustomEventGenericTypeNode(t)).filter((t): t is string => Boolean(t));
|
|
483
|
+
return parts.length === typeNode.types.length ? parts.join(' | ') : null;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (ts.isIntersectionTypeNode(typeNode)) {
|
|
487
|
+
const parts = typeNode.types.map(t => stringifyCustomEventGenericTypeNode(t)).filter((t): t is string => Boolean(t));
|
|
488
|
+
return parts.length === typeNode.types.length ? parts.join(' & ') : null;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (ts.isArrayTypeNode(typeNode)) {
|
|
492
|
+
const element = stringifyCustomEventGenericTypeNode(typeNode.elementType);
|
|
493
|
+
return element ? `${element}[]` : null;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (ts.isTupleTypeNode(typeNode)) {
|
|
497
|
+
const elements = typeNode.elements.map(e => stringifyCustomEventGenericTypeNode(e)).filter((t): t is string => Boolean(t));
|
|
498
|
+
return elements.length === typeNode.elements.length ? `[${elements.join(', ')}]` : null;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (ts.isLiteralTypeNode(typeNode)) {
|
|
502
|
+
const literal = typeNode.literal;
|
|
503
|
+
if (literal.kind === ts.SyntaxKind.NullKeyword) return 'null';
|
|
504
|
+
if (literal.kind === ts.SyntaxKind.UndefinedKeyword) return 'undefined';
|
|
505
|
+
if (literal.kind === ts.SyntaxKind.TrueKeyword) return 'true';
|
|
506
|
+
if (literal.kind === ts.SyntaxKind.FalseKeyword) return 'false';
|
|
507
|
+
if (ts.isStringLiteral(literal)) return JSON.stringify(literal.text);
|
|
508
|
+
if (ts.isNumericLiteral(literal)) return literal.text;
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const basic = getBasicTypeKindAsTsType(typeNode.kind);
|
|
513
|
+
if (basic) return basic;
|
|
514
|
+
|
|
515
|
+
if (ts.isTypeReferenceNode(typeNode)) {
|
|
516
|
+
const typeName = stringifyEntityName(typeNode.typeName);
|
|
517
|
+
|
|
518
|
+
// Unwrap internal helpers used by WebF typings.
|
|
519
|
+
if (typeName === 'DartImpl' && typeNode.typeArguments && typeNode.typeArguments[0]) {
|
|
520
|
+
return stringifyCustomEventGenericTypeNode(typeNode.typeArguments[0]);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (typeName === 'Promise') {
|
|
524
|
+
if (!typeNode.typeArguments || !typeNode.typeArguments[0]) return 'Promise<any>';
|
|
525
|
+
const inner = stringifyCustomEventGenericTypeNode(typeNode.typeArguments[0]);
|
|
526
|
+
return inner ? `Promise<${inner}>` : null;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const mapped = mapTypeReferenceIdentifierToTsType(typeName);
|
|
530
|
+
if (mapped) return mapped;
|
|
531
|
+
|
|
532
|
+
if (!typeNode.typeArguments || typeNode.typeArguments.length === 0) {
|
|
533
|
+
return typeName;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const args = typeNode.typeArguments
|
|
537
|
+
.map(arg => stringifyCustomEventGenericTypeNode(arg))
|
|
538
|
+
.filter((t): t is string => Boolean(t));
|
|
539
|
+
if (args.length !== typeNode.typeArguments.length) return null;
|
|
540
|
+
return `${typeName}<${args.join(', ')}>`;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (ts.isTypeLiteralNode(typeNode)) {
|
|
544
|
+
const members: string[] = [];
|
|
545
|
+
for (const member of typeNode.members) {
|
|
546
|
+
if (ts.isPropertySignature(member) && member.type) {
|
|
547
|
+
const typeString = stringifyCustomEventGenericTypeNode(member.type);
|
|
548
|
+
if (!typeString) return null;
|
|
549
|
+
let nameText: string;
|
|
550
|
+
if (ts.isIdentifier(member.name)) nameText = member.name.text;
|
|
551
|
+
else if (ts.isStringLiteral(member.name)) nameText = JSON.stringify(member.name.text);
|
|
552
|
+
else if (ts.isNumericLiteral(member.name)) nameText = member.name.text;
|
|
553
|
+
else nameText = member.name.getText();
|
|
554
|
+
const optional = member.questionToken ? '?' : '';
|
|
555
|
+
members.push(`${nameText}${optional}: ${typeString}`);
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
if (ts.isIndexSignatureDeclaration(member) && member.type && member.parameters.length === 1) {
|
|
559
|
+
const param = member.parameters[0];
|
|
560
|
+
const paramName = ts.isIdentifier(param.name) ? param.name.text : param.name.getText();
|
|
561
|
+
const paramType = param.type ? stringifyCustomEventGenericTypeNode(param.type) : 'string';
|
|
562
|
+
const valueType = stringifyCustomEventGenericTypeNode(member.type);
|
|
563
|
+
if (!paramType || !valueType) return null;
|
|
564
|
+
members.push(`[${paramName}: ${paramType}]: ${valueType}`);
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
// Fallback for uncommon members (call signatures, method signatures, etc.).
|
|
568
|
+
members.push(safePrintCustomEventNode(member));
|
|
569
|
+
}
|
|
570
|
+
return `{ ${members.join('; ')} }`;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (ts.isTypeOperatorNode(typeNode)) {
|
|
574
|
+
const inner = stringifyCustomEventGenericTypeNode(typeNode.type);
|
|
575
|
+
if (!inner) return null;
|
|
576
|
+
const operator =
|
|
577
|
+
typeNode.operator === ts.SyntaxKind.KeyOfKeyword ? 'keyof' :
|
|
578
|
+
typeNode.operator === ts.SyntaxKind.ReadonlyKeyword ? 'readonly' :
|
|
579
|
+
typeNode.operator === ts.SyntaxKind.UniqueKeyword ? 'unique' :
|
|
580
|
+
null;
|
|
581
|
+
return operator ? `${operator} ${inner}` : null;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (ts.isIndexedAccessTypeNode(typeNode)) {
|
|
585
|
+
const objectType = stringifyCustomEventGenericTypeNode(typeNode.objectType);
|
|
586
|
+
const indexType = stringifyCustomEventGenericTypeNode(typeNode.indexType);
|
|
587
|
+
if (!objectType || !indexType) return null;
|
|
588
|
+
return `${objectType}[${indexType}]`;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// As a last resort, keep the original syntax but normalize known WebF aliases.
|
|
592
|
+
return safePrintCustomEventNode(typeNode);
|
|
593
|
+
}
|
|
594
|
+
|
|
413
595
|
function handleCustomEventType(typeReference: ts.TypeReferenceNode): ParameterBaseType {
|
|
414
596
|
// Handle CustomEvent<T> by returning the full type with generic parameter
|
|
415
597
|
if (!typeReference.typeArguments || !typeReference.typeArguments[0]) {
|
|
@@ -417,121 +599,11 @@ function handleCustomEventType(typeReference: ts.TypeReferenceNode): ParameterBa
|
|
|
417
599
|
}
|
|
418
600
|
|
|
419
601
|
const argument = typeReference.typeArguments[0];
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const unionTypes = (argument as ts.UnionTypeNode | ts.IntersectionTypeNode).types ?? [];
|
|
425
|
-
const parts = unionTypes.map(t => {
|
|
426
|
-
// Literal union members: handle null/undefined explicitly
|
|
427
|
-
if (ts.isLiteralTypeNode(t)) {
|
|
428
|
-
const lit = t.literal;
|
|
429
|
-
if (lit.kind === ts.SyntaxKind.NullKeyword) return 'null';
|
|
430
|
-
if (lit.kind === ts.SyntaxKind.UndefinedKeyword) return 'undefined';
|
|
431
|
-
if (ts.isStringLiteral(lit)) return JSON.stringify(lit.text);
|
|
432
|
-
return 'any';
|
|
433
|
-
}
|
|
434
|
-
// Basic keywords: boolean, string, number, null, undefined
|
|
435
|
-
const basic = BASIC_TYPE_MAP[t.kind];
|
|
436
|
-
if (basic !== undefined) {
|
|
437
|
-
switch (basic) {
|
|
438
|
-
case FunctionArgumentType.boolean:
|
|
439
|
-
return 'boolean';
|
|
440
|
-
case FunctionArgumentType.dom_string:
|
|
441
|
-
return 'string';
|
|
442
|
-
case FunctionArgumentType.double:
|
|
443
|
-
case FunctionArgumentType.int:
|
|
444
|
-
return 'number';
|
|
445
|
-
case FunctionArgumentType.null:
|
|
446
|
-
return 'null';
|
|
447
|
-
case FunctionArgumentType.undefined:
|
|
448
|
-
return 'undefined';
|
|
449
|
-
default:
|
|
450
|
-
return 'any';
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
// Literal null/undefined keywords that BASIC_TYPE_MAP may not cover
|
|
454
|
-
if (t.kind === ts.SyntaxKind.NullKeyword) return 'null';
|
|
455
|
-
if (t.kind === ts.SyntaxKind.UndefinedKeyword) return 'undefined';
|
|
456
|
-
// Fallback: rely on toString of node kind
|
|
457
|
-
return 'any';
|
|
458
|
-
});
|
|
459
|
-
genericType = parts.join(' | ');
|
|
460
|
-
} else if (ts.isTypeReferenceNode(argument) && ts.isIdentifier(argument.typeName)) {
|
|
461
|
-
const typeName = argument.typeName.text;
|
|
462
|
-
|
|
463
|
-
// Check if it's a mapped type reference like 'int' or 'double'
|
|
464
|
-
const mappedType = TYPE_REFERENCE_MAP[typeName];
|
|
465
|
-
if (mappedType !== undefined) {
|
|
466
|
-
switch (mappedType) {
|
|
467
|
-
case FunctionArgumentType.boolean:
|
|
468
|
-
genericType = 'boolean';
|
|
469
|
-
break;
|
|
470
|
-
case FunctionArgumentType.dom_string:
|
|
471
|
-
genericType = 'string';
|
|
472
|
-
break;
|
|
473
|
-
case FunctionArgumentType.double:
|
|
474
|
-
case FunctionArgumentType.int:
|
|
475
|
-
genericType = 'number';
|
|
476
|
-
break;
|
|
477
|
-
case FunctionArgumentType.any:
|
|
478
|
-
genericType = 'any';
|
|
479
|
-
break;
|
|
480
|
-
case FunctionArgumentType.void:
|
|
481
|
-
genericType = 'void';
|
|
482
|
-
break;
|
|
483
|
-
case FunctionArgumentType.function:
|
|
484
|
-
genericType = 'Function';
|
|
485
|
-
break;
|
|
486
|
-
case FunctionArgumentType.promise:
|
|
487
|
-
genericType = 'Promise<any>';
|
|
488
|
-
break;
|
|
489
|
-
default:
|
|
490
|
-
genericType = typeName;
|
|
491
|
-
}
|
|
492
|
-
} else {
|
|
493
|
-
// For other type references, use the type name directly
|
|
494
|
-
genericType = typeName;
|
|
495
|
-
}
|
|
496
|
-
} else if (ts.isLiteralTypeNode(argument) && ts.isStringLiteral(argument.literal)) {
|
|
497
|
-
genericType = argument.literal.text;
|
|
498
|
-
} else {
|
|
499
|
-
// Handle basic types (boolean, string, number, etc.)
|
|
500
|
-
const basicType = BASIC_TYPE_MAP[argument.kind];
|
|
501
|
-
if (basicType !== undefined) {
|
|
502
|
-
switch (basicType) {
|
|
503
|
-
case FunctionArgumentType.boolean:
|
|
504
|
-
genericType = 'boolean';
|
|
505
|
-
break;
|
|
506
|
-
case FunctionArgumentType.dom_string:
|
|
507
|
-
genericType = 'string';
|
|
508
|
-
break;
|
|
509
|
-
case FunctionArgumentType.double:
|
|
510
|
-
case FunctionArgumentType.int:
|
|
511
|
-
genericType = 'number';
|
|
512
|
-
break;
|
|
513
|
-
case FunctionArgumentType.any:
|
|
514
|
-
genericType = 'any';
|
|
515
|
-
break;
|
|
516
|
-
case FunctionArgumentType.void:
|
|
517
|
-
genericType = 'void';
|
|
518
|
-
break;
|
|
519
|
-
case FunctionArgumentType.null:
|
|
520
|
-
genericType = 'null';
|
|
521
|
-
break;
|
|
522
|
-
case FunctionArgumentType.undefined:
|
|
523
|
-
genericType = 'undefined';
|
|
524
|
-
break;
|
|
525
|
-
default:
|
|
526
|
-
genericType = 'any';
|
|
527
|
-
}
|
|
528
|
-
} else {
|
|
529
|
-
// For truly complex types, fallback to 'any' to avoid errors
|
|
530
|
-
console.warn('Complex generic type in CustomEvent, using any');
|
|
531
|
-
genericType = 'any';
|
|
532
|
-
}
|
|
602
|
+
const genericType = stringifyCustomEventGenericTypeNode(argument);
|
|
603
|
+
if (!genericType) {
|
|
604
|
+
console.warn('Complex generic type in CustomEvent, using any');
|
|
605
|
+
return 'CustomEvent<any>';
|
|
533
606
|
}
|
|
534
|
-
|
|
535
607
|
return `CustomEvent<${genericType}>`;
|
|
536
608
|
}
|
|
537
609
|
|