@openwebf/webf 0.23.2 → 0.23.10
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 +33 -1
- package/bin/webf.js +13 -1
- package/dist/analyzer.js +65 -1
- package/dist/commands.js +437 -99
- package/dist/dart.js +91 -25
- package/dist/declaration.js +1 -0
- package/dist/generator.js +28 -18
- package/dist/module.js +458 -0
- package/dist/react.js +272 -25
- package/dist/vue.js +89 -11
- package/package.json +2 -2
- package/src/analyzer.ts +58 -2
- package/src/commands.ts +587 -199
- package/src/dart.ts +95 -20
- package/src/declaration.ts +1 -0
- package/src/generator.ts +27 -19
- package/src/module.ts +600 -0
- package/src/react.ts +288 -29
- package/src/vue.ts +100 -13
- package/templates/class.dart.tpl +1 -1
- package/templates/module.package.json.tpl +36 -0
- package/templates/module.tsconfig.json.tpl +25 -0
- package/templates/module.tsup.config.ts.tpl +13 -0
- package/templates/vue.components.d.ts.tpl +2 -0
- package/test/commands.test.ts +86 -4
- package/test/dart-nullable-props.test.ts +58 -0
- package/test/generator.test.ts +16 -14
- package/test/react-consts.test.ts +1 -1
- package/test/react-vue-nullable-props.test.ts +66 -0
- package/test/react.test.ts +46 -4
package/dist/react.js
CHANGED
|
@@ -11,19 +11,58 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
11
11
|
const path_1 = __importDefault(require("path"));
|
|
12
12
|
const declaration_1 = require("./declaration");
|
|
13
13
|
const utils_1 = require("./utils");
|
|
14
|
+
const logger_1 = require("./logger");
|
|
14
15
|
function readTemplate(name) {
|
|
15
16
|
return fs_1.default.readFileSync(path_1.default.join(__dirname, '../templates/' + name + '.tpl'), { encoding: 'utf-8' });
|
|
16
17
|
}
|
|
17
18
|
function generateReturnType(type) {
|
|
18
19
|
if ((0, utils_1.isUnionType)(type)) {
|
|
19
|
-
|
|
20
|
+
const values = type.value;
|
|
21
|
+
return values.map(v => {
|
|
22
|
+
if (v.value === declaration_1.FunctionArgumentType.null) {
|
|
23
|
+
return 'null';
|
|
24
|
+
}
|
|
25
|
+
// String literal unions: 'left' | 'center' | 'right'
|
|
26
|
+
if (typeof v.value === 'string') {
|
|
27
|
+
return `'${v.value}'`;
|
|
28
|
+
}
|
|
29
|
+
return 'any';
|
|
30
|
+
}).join(' | ');
|
|
31
|
+
}
|
|
32
|
+
// Handle non-literal unions such as boolean | null, number | null, CustomType | null
|
|
33
|
+
if (Array.isArray(type.value)) {
|
|
34
|
+
const values = type.value;
|
|
35
|
+
const hasNull = values.some(v => v.value === declaration_1.FunctionArgumentType.null);
|
|
36
|
+
if (hasNull) {
|
|
37
|
+
const nonNulls = values.filter(v => v.value !== declaration_1.FunctionArgumentType.null);
|
|
38
|
+
if (nonNulls.length === 0) {
|
|
39
|
+
return 'null';
|
|
40
|
+
}
|
|
41
|
+
const parts = nonNulls.map(v => generateReturnType(v));
|
|
42
|
+
// Deduplicate and append null
|
|
43
|
+
const unique = Array.from(new Set(parts));
|
|
44
|
+
unique.push('null');
|
|
45
|
+
return unique.join(' | ');
|
|
46
|
+
}
|
|
47
|
+
// Complex non-null unions are rare for React typings; fall back to any
|
|
48
|
+
return 'any';
|
|
20
49
|
}
|
|
21
50
|
if ((0, utils_1.isPointerType)(type)) {
|
|
22
51
|
const pointerType = (0, utils_1.getPointerType)(type);
|
|
52
|
+
// Map Dart's `Type` (from TS typeof) to TS `any`
|
|
53
|
+
if (pointerType === 'Type')
|
|
54
|
+
return 'any';
|
|
23
55
|
return pointerType;
|
|
24
56
|
}
|
|
25
57
|
if (type.isArray && typeof type.value === 'object' && !Array.isArray(type.value)) {
|
|
26
|
-
|
|
58
|
+
const elemType = (0, utils_1.getPointerType)(type.value);
|
|
59
|
+
// Map arrays of Dart `Type` to `any[]` in TS; parenthesize typeof
|
|
60
|
+
if (elemType === 'Type')
|
|
61
|
+
return 'any[]';
|
|
62
|
+
if (typeof elemType === 'string' && elemType.startsWith('typeof ')) {
|
|
63
|
+
return `(${elemType})[]`;
|
|
64
|
+
}
|
|
65
|
+
return `${elemType}[]`;
|
|
27
66
|
}
|
|
28
67
|
switch (type.value) {
|
|
29
68
|
case declaration_1.FunctionArgumentType.int:
|
|
@@ -146,11 +185,18 @@ function generateReactComponent(blob, packageName, relativeDir) {
|
|
|
146
185
|
}).join('\n');
|
|
147
186
|
// Include declare const values as ambient exports for type usage (e.g., unique symbol branding)
|
|
148
187
|
const constDeclarations = constObjects.map(c => `export declare const ${c.name}: ${c.type};`).join('\n');
|
|
149
|
-
// Include enums
|
|
188
|
+
// Include enums as concrete exports (no declare) so they are usable as values
|
|
150
189
|
const enumDeclarations = enumObjects.map(e => {
|
|
151
190
|
const members = e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ');
|
|
152
|
-
return `export
|
|
191
|
+
return `export enum ${e.name} { ${members} }`;
|
|
153
192
|
}).join('\n');
|
|
193
|
+
// Names declared within this blob (so we shouldn't prefix them with __webfTypes)
|
|
194
|
+
const localTypeNames = new Set([
|
|
195
|
+
...others.map(o => o.name),
|
|
196
|
+
...typeAliases.map(t => t.name),
|
|
197
|
+
...constObjects.map(c => c.name),
|
|
198
|
+
...enumObjects.map(e => e.name),
|
|
199
|
+
]);
|
|
154
200
|
const dependencies = [
|
|
155
201
|
typeAliasDeclarations,
|
|
156
202
|
constDeclarations,
|
|
@@ -160,26 +206,49 @@ function generateReactComponent(blob, packageName, relativeDir) {
|
|
|
160
206
|
const methodDeclarations = object.methods.map(method => {
|
|
161
207
|
return generateMethodDeclarationWithDocs(method, ' ');
|
|
162
208
|
}).join('\n');
|
|
163
|
-
|
|
164
|
-
if (object.documentation) {
|
|
165
|
-
|
|
209
|
+
const lines = [];
|
|
210
|
+
if (object.documentation && object.documentation.trim().length > 0) {
|
|
211
|
+
lines.push('/**');
|
|
212
|
+
object.documentation.split('\n').forEach(line => {
|
|
213
|
+
lines.push(` * ${line}`);
|
|
214
|
+
});
|
|
215
|
+
lines.push(' */');
|
|
166
216
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
217
|
+
lines.push(`interface ${object.name} {`);
|
|
218
|
+
lines.push(methodDeclarations);
|
|
219
|
+
lines.push('}');
|
|
220
|
+
return lines.join('\n');
|
|
170
221
|
}).join('\n\n'),
|
|
171
222
|
others.map(object => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
223
|
+
if (!object || !object.props || object.props.length === 0) {
|
|
224
|
+
return '';
|
|
225
|
+
}
|
|
226
|
+
const interfaceLines = [];
|
|
227
|
+
if (object.documentation && object.documentation.trim().length > 0) {
|
|
228
|
+
interfaceLines.push('/**');
|
|
229
|
+
object.documentation.split('\n').forEach(line => {
|
|
230
|
+
interfaceLines.push(` * ${line}`);
|
|
231
|
+
});
|
|
232
|
+
interfaceLines.push(' */');
|
|
233
|
+
}
|
|
234
|
+
interfaceLines.push(`interface ${object.name} {`);
|
|
235
|
+
const propLines = object.props.map(prop => {
|
|
236
|
+
const lines = [];
|
|
237
|
+
if (prop.documentation && prop.documentation.trim().length > 0) {
|
|
238
|
+
lines.push(' /**');
|
|
239
|
+
prop.documentation.split('\n').forEach(line => {
|
|
240
|
+
lines.push(` * ${line}`);
|
|
241
|
+
});
|
|
242
|
+
lines.push(' */');
|
|
175
243
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
244
|
+
const optionalToken = prop.optional ? '?' : '';
|
|
245
|
+
lines.push(` ${prop.name}${optionalToken}: ${generateReturnType(prop.type)};`);
|
|
246
|
+
return lines.join('\n');
|
|
247
|
+
});
|
|
248
|
+
interfaceLines.push(propLines.join('\n'));
|
|
249
|
+
interfaceLines.push('}');
|
|
250
|
+
return interfaceLines.join('\n');
|
|
251
|
+
}).filter(Boolean).join('\n\n')
|
|
183
252
|
].filter(Boolean).join('\n\n');
|
|
184
253
|
// Generate all components from this file
|
|
185
254
|
const components = [];
|
|
@@ -232,17 +301,114 @@ interface ${object.name} {
|
|
|
232
301
|
}
|
|
233
302
|
const templateContent = readTemplate('react.component.tsx')
|
|
234
303
|
.replace('import { createWebFComponent, WebFElementWithMethods } from "@openwebf/react-core-ui";', createWebFComponentImport);
|
|
235
|
-
|
|
304
|
+
// Generate return type mapping; always use __webfTypes namespace for typeof
|
|
305
|
+
const genRT = (type) => {
|
|
306
|
+
if ((0, utils_1.isUnionType)(type)) {
|
|
307
|
+
const values = type.value;
|
|
308
|
+
return values.map(v => {
|
|
309
|
+
if (v.value === declaration_1.FunctionArgumentType.null) {
|
|
310
|
+
return 'null';
|
|
311
|
+
}
|
|
312
|
+
if (typeof v.value === 'string') {
|
|
313
|
+
return `'${v.value}'`;
|
|
314
|
+
}
|
|
315
|
+
return 'any';
|
|
316
|
+
}).join(' | ');
|
|
317
|
+
}
|
|
318
|
+
if (Array.isArray(type.value)) {
|
|
319
|
+
const values = type.value;
|
|
320
|
+
const hasNull = values.some(v => v.value === declaration_1.FunctionArgumentType.null);
|
|
321
|
+
if (hasNull) {
|
|
322
|
+
const nonNulls = values.filter(v => v.value !== declaration_1.FunctionArgumentType.null);
|
|
323
|
+
if (nonNulls.length === 0) {
|
|
324
|
+
return 'null';
|
|
325
|
+
}
|
|
326
|
+
const parts = nonNulls.map(v => genRT(v));
|
|
327
|
+
const unique = Array.from(new Set(parts));
|
|
328
|
+
unique.push('null');
|
|
329
|
+
return unique.join(' | ');
|
|
330
|
+
}
|
|
331
|
+
return 'any';
|
|
332
|
+
}
|
|
333
|
+
if ((0, utils_1.isPointerType)(type)) {
|
|
334
|
+
const pointerType = (0, utils_1.getPointerType)(type);
|
|
335
|
+
if (pointerType === 'Type')
|
|
336
|
+
return 'any';
|
|
337
|
+
if (typeof pointerType === 'string' && pointerType.startsWith('typeof ')) {
|
|
338
|
+
const ident = pointerType.substring('typeof '.length).trim();
|
|
339
|
+
return `typeof __webfTypes.${ident}`;
|
|
340
|
+
}
|
|
341
|
+
// Prefix external pointer types with __webfTypes unless locally declared
|
|
342
|
+
if (typeof pointerType === 'string' && /^(?:[A-Za-z_][A-Za-z0-9_]*)(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(pointerType)) {
|
|
343
|
+
const base = pointerType.split('.')[0];
|
|
344
|
+
if (!localTypeNames.has(base)) {
|
|
345
|
+
return `__webfTypes.${pointerType}`;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return pointerType;
|
|
349
|
+
}
|
|
350
|
+
if (type.isArray && typeof type.value === 'object' && !Array.isArray(type.value)) {
|
|
351
|
+
const elemType = (0, utils_1.getPointerType)(type.value);
|
|
352
|
+
if (elemType === 'Type')
|
|
353
|
+
return 'any[]';
|
|
354
|
+
if (typeof elemType === 'string' && elemType.startsWith('typeof ')) {
|
|
355
|
+
const ident = elemType.substring('typeof '.length).trim();
|
|
356
|
+
return `(typeof __webfTypes.${ident})[]`;
|
|
357
|
+
}
|
|
358
|
+
if (typeof elemType === 'string' && /^(?:[A-Za-z_][A-Za-z0-9_]*)(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(elemType)) {
|
|
359
|
+
const base = elemType.split('.')[0];
|
|
360
|
+
if (!localTypeNames.has(base)) {
|
|
361
|
+
return `__webfTypes.${elemType}[]`;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return `${elemType}[]`;
|
|
365
|
+
}
|
|
366
|
+
switch (type.value) {
|
|
367
|
+
case declaration_1.FunctionArgumentType.int:
|
|
368
|
+
case declaration_1.FunctionArgumentType.double:
|
|
369
|
+
return 'number';
|
|
370
|
+
case declaration_1.FunctionArgumentType.any:
|
|
371
|
+
return 'any';
|
|
372
|
+
case declaration_1.FunctionArgumentType.boolean:
|
|
373
|
+
return 'boolean';
|
|
374
|
+
case declaration_1.FunctionArgumentType.dom_string:
|
|
375
|
+
return 'string';
|
|
376
|
+
case declaration_1.FunctionArgumentType.void:
|
|
377
|
+
default:
|
|
378
|
+
return 'void';
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
// Compute relative import path to src/types
|
|
382
|
+
const depth = (relativeDir || '').split('/').filter(p => p).length;
|
|
383
|
+
const upPath = '../'.repeat(depth);
|
|
384
|
+
// Always import the types namespace for typeof references
|
|
385
|
+
const typesImport = `import * as __webfTypes from "${upPath}types";\n\n`;
|
|
386
|
+
// Debug: collect typeof references from props for this component
|
|
387
|
+
const typeofRefs = new Set();
|
|
388
|
+
if (component.properties) {
|
|
389
|
+
component.properties.props.forEach(p => {
|
|
390
|
+
const t = p.type;
|
|
391
|
+
if (!t)
|
|
392
|
+
return;
|
|
393
|
+
if (!t.isArray && typeof t.value === 'string' && String(t.value).startsWith('typeof ')) {
|
|
394
|
+
const ident = String(t.value).substring('typeof '.length).trim();
|
|
395
|
+
typeofRefs.add(ident);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
(0, logger_1.debug)(`[react] Generating ${className} (${blob.relativeDir}/${blob.filename}.tsx) typeof refs: ${Array.from(typeofRefs).join(', ') || '(none)'}; types import: ${upPath}types`);
|
|
400
|
+
const dependenciesWithImports = `${typesImport}${dependencies}`;
|
|
401
|
+
let content = lodash_1.default.template(templateContent)({
|
|
236
402
|
className: className,
|
|
237
403
|
properties: component.properties,
|
|
238
404
|
events: component.events,
|
|
239
405
|
methods: component.methods,
|
|
240
406
|
classObjectDictionary,
|
|
241
|
-
dependencies,
|
|
407
|
+
dependencies: dependenciesWithImports,
|
|
242
408
|
blob,
|
|
243
409
|
toReactEventName,
|
|
244
410
|
toWebFTagName,
|
|
245
|
-
generateReturnType,
|
|
411
|
+
generateReturnType: genRT,
|
|
246
412
|
generateMethodDeclaration,
|
|
247
413
|
generateMethodDeclarationWithDocs,
|
|
248
414
|
generateEventHandlerType,
|
|
@@ -268,6 +434,81 @@ interface ${object.name} {
|
|
|
268
434
|
createWebFComponentImport = `import { createWebFComponent, WebFElementWithMethods } from "@openwebf/react-core-ui";`;
|
|
269
435
|
}
|
|
270
436
|
componentEntries.forEach(([className, component]) => {
|
|
437
|
+
const genRT = (type) => {
|
|
438
|
+
if ((0, utils_1.isUnionType)(type)) {
|
|
439
|
+
const values = type.value;
|
|
440
|
+
return values.map(v => {
|
|
441
|
+
if (v.value === declaration_1.FunctionArgumentType.null) {
|
|
442
|
+
return 'null';
|
|
443
|
+
}
|
|
444
|
+
if (typeof v.value === 'string') {
|
|
445
|
+
return `'${v.value}'`;
|
|
446
|
+
}
|
|
447
|
+
return 'any';
|
|
448
|
+
}).join(' | ');
|
|
449
|
+
}
|
|
450
|
+
if (Array.isArray(type.value)) {
|
|
451
|
+
const values = type.value;
|
|
452
|
+
const hasNull = values.some(v => v.value === declaration_1.FunctionArgumentType.null);
|
|
453
|
+
if (hasNull) {
|
|
454
|
+
const nonNulls = values.filter(v => v.value !== declaration_1.FunctionArgumentType.null);
|
|
455
|
+
if (nonNulls.length === 0) {
|
|
456
|
+
return 'null';
|
|
457
|
+
}
|
|
458
|
+
const parts = nonNulls.map(v => genRT(v));
|
|
459
|
+
const unique = Array.from(new Set(parts));
|
|
460
|
+
unique.push('null');
|
|
461
|
+
return unique.join(' | ');
|
|
462
|
+
}
|
|
463
|
+
return 'any';
|
|
464
|
+
}
|
|
465
|
+
if ((0, utils_1.isPointerType)(type)) {
|
|
466
|
+
const pointerType = (0, utils_1.getPointerType)(type);
|
|
467
|
+
if (pointerType === 'Type')
|
|
468
|
+
return 'any';
|
|
469
|
+
if (typeof pointerType === 'string' && pointerType.startsWith('typeof ')) {
|
|
470
|
+
const ident = pointerType.substring('typeof '.length).trim();
|
|
471
|
+
return `typeof __webfTypes.${ident}`;
|
|
472
|
+
}
|
|
473
|
+
if (typeof pointerType === 'string' && /^(?:[A-Za-z_][A-Za-z0-9_]*)(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(pointerType)) {
|
|
474
|
+
const base = pointerType.split('.')[0];
|
|
475
|
+
if (!localTypeNames.has(base)) {
|
|
476
|
+
return `__webfTypes.${pointerType}`;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return pointerType;
|
|
480
|
+
}
|
|
481
|
+
if (type.isArray && typeof type.value === 'object' && !Array.isArray(type.value)) {
|
|
482
|
+
const elemType = (0, utils_1.getPointerType)(type.value);
|
|
483
|
+
if (elemType === 'Type')
|
|
484
|
+
return 'any[]';
|
|
485
|
+
if (typeof elemType === 'string' && elemType.startsWith('typeof ')) {
|
|
486
|
+
const ident = elemType.substring('typeof '.length).trim();
|
|
487
|
+
return `(typeof __webfTypes.${ident})[]`;
|
|
488
|
+
}
|
|
489
|
+
if (typeof elemType === 'string' && /^(?:[A-Za-z_][A-Za-z0-9_]*)(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(elemType)) {
|
|
490
|
+
const base = elemType.split('.')[0];
|
|
491
|
+
if (!localTypeNames.has(base)) {
|
|
492
|
+
return `__webfTypes.${elemType}[]`;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return `${elemType}[]`;
|
|
496
|
+
}
|
|
497
|
+
switch (type.value) {
|
|
498
|
+
case declaration_1.FunctionArgumentType.int:
|
|
499
|
+
case declaration_1.FunctionArgumentType.double:
|
|
500
|
+
return 'number';
|
|
501
|
+
case declaration_1.FunctionArgumentType.any:
|
|
502
|
+
return 'any';
|
|
503
|
+
case declaration_1.FunctionArgumentType.boolean:
|
|
504
|
+
return 'boolean';
|
|
505
|
+
case declaration_1.FunctionArgumentType.dom_string:
|
|
506
|
+
return 'string';
|
|
507
|
+
case declaration_1.FunctionArgumentType.void:
|
|
508
|
+
default:
|
|
509
|
+
return 'void';
|
|
510
|
+
}
|
|
511
|
+
};
|
|
271
512
|
const content = lodash_1.default.template(readTemplate('react.component.tsx'))({
|
|
272
513
|
className: className,
|
|
273
514
|
properties: component.properties,
|
|
@@ -278,7 +519,7 @@ interface ${object.name} {
|
|
|
278
519
|
blob,
|
|
279
520
|
toReactEventName,
|
|
280
521
|
toWebFTagName,
|
|
281
|
-
generateReturnType,
|
|
522
|
+
generateReturnType: genRT,
|
|
282
523
|
generateMethodDeclaration,
|
|
283
524
|
generateMethodDeclarationWithDocs,
|
|
284
525
|
generateEventHandlerType,
|
|
@@ -292,9 +533,15 @@ interface ${object.name} {
|
|
|
292
533
|
componentDefinitions.push(withoutImports);
|
|
293
534
|
});
|
|
294
535
|
// Combine with shared imports at the top
|
|
295
|
-
|
|
536
|
+
// Compute relative import path to src/types and always include namespace import
|
|
537
|
+
const depth = (relativeDir || '').split('/').filter(p => p).length;
|
|
538
|
+
const upPath = '../'.repeat(depth);
|
|
539
|
+
const typesImport = `import * as __webfTypes from "${upPath}types";`;
|
|
540
|
+
(0, logger_1.debug)(`[react] Generating combined components for ${blob.filename}.tsx; types import: ${upPath}types`);
|
|
541
|
+
let result = [
|
|
296
542
|
'import React from "react";',
|
|
297
543
|
createWebFComponentImport,
|
|
544
|
+
typesImport,
|
|
298
545
|
'',
|
|
299
546
|
dependencies,
|
|
300
547
|
'',
|
package/dist/vue.js
CHANGED
|
@@ -8,17 +8,61 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const declaration_1 = require("./declaration");
|
|
11
|
+
const logger_1 = require("./logger");
|
|
11
12
|
const utils_1 = require("./utils");
|
|
12
13
|
function readTemplate(name) {
|
|
13
14
|
return fs_1.default.readFileSync(path_1.default.join(__dirname, '../templates/' + name + '.tpl'), { encoding: 'utf-8' });
|
|
14
15
|
}
|
|
15
16
|
function generateReturnType(type) {
|
|
17
|
+
if ((0, utils_1.isUnionType)(type)) {
|
|
18
|
+
const values = type.value;
|
|
19
|
+
return values.map(v => {
|
|
20
|
+
if (v.value === declaration_1.FunctionArgumentType.null) {
|
|
21
|
+
return 'null';
|
|
22
|
+
}
|
|
23
|
+
if (typeof v.value === 'string') {
|
|
24
|
+
return `'${v.value}'`;
|
|
25
|
+
}
|
|
26
|
+
return 'any';
|
|
27
|
+
}).join(' | ');
|
|
28
|
+
}
|
|
29
|
+
// Handle unions like boolean | null, number | null, CustomType | null
|
|
30
|
+
if (Array.isArray(type.value)) {
|
|
31
|
+
const values = type.value;
|
|
32
|
+
const hasNull = values.some(v => v.value === declaration_1.FunctionArgumentType.null);
|
|
33
|
+
if (hasNull) {
|
|
34
|
+
const nonNulls = values.filter(v => v.value !== declaration_1.FunctionArgumentType.null);
|
|
35
|
+
if (nonNulls.length === 0) {
|
|
36
|
+
return 'null';
|
|
37
|
+
}
|
|
38
|
+
const parts = nonNulls.map(v => generateReturnType(v));
|
|
39
|
+
const unique = Array.from(new Set(parts));
|
|
40
|
+
unique.push('null');
|
|
41
|
+
return unique.join(' | ');
|
|
42
|
+
}
|
|
43
|
+
// Complex non-null unions are rare; fall back to any
|
|
44
|
+
return 'any';
|
|
45
|
+
}
|
|
16
46
|
if ((0, utils_1.isPointerType)(type)) {
|
|
17
47
|
const pointerType = (0, utils_1.getPointerType)(type);
|
|
48
|
+
// Map Dart's `Type` (from TS typeof) to TS `any`
|
|
49
|
+
if (pointerType === 'Type')
|
|
50
|
+
return 'any';
|
|
51
|
+
if (typeof pointerType === 'string' && pointerType.startsWith('typeof ')) {
|
|
52
|
+
const ident = pointerType.substring('typeof '.length).trim();
|
|
53
|
+
return `typeof __webfTypes.${ident}`;
|
|
54
|
+
}
|
|
18
55
|
return pointerType;
|
|
19
56
|
}
|
|
20
57
|
if (type.isArray && typeof type.value === 'object' && !Array.isArray(type.value)) {
|
|
21
|
-
|
|
58
|
+
const elemType = (0, utils_1.getPointerType)(type.value);
|
|
59
|
+
if (elemType === 'Type')
|
|
60
|
+
return 'any[]';
|
|
61
|
+
if (typeof elemType === 'string' && elemType.startsWith('typeof ')) {
|
|
62
|
+
const ident = elemType.substring('typeof '.length).trim();
|
|
63
|
+
return `(typeof __webfTypes.${ident})[]`;
|
|
64
|
+
}
|
|
65
|
+
return `${elemType}[]`;
|
|
22
66
|
}
|
|
23
67
|
switch (type.value) {
|
|
24
68
|
case declaration_1.FunctionArgumentType.int:
|
|
@@ -87,19 +131,34 @@ function generateVueComponent(blob) {
|
|
|
87
131
|
&& !object.name.endsWith('Events');
|
|
88
132
|
});
|
|
89
133
|
const dependencies = others.map(object => {
|
|
90
|
-
if (!object || !object.props) {
|
|
134
|
+
if (!object || !object.props || object.props.length === 0) {
|
|
91
135
|
return '';
|
|
92
136
|
}
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
137
|
+
const interfaceLines = [];
|
|
138
|
+
if (object.documentation && object.documentation.trim().length > 0) {
|
|
139
|
+
interfaceLines.push('/**');
|
|
140
|
+
object.documentation.split('\n').forEach(line => {
|
|
141
|
+
interfaceLines.push(` * ${line}`);
|
|
142
|
+
});
|
|
143
|
+
interfaceLines.push(' */');
|
|
144
|
+
}
|
|
145
|
+
interfaceLines.push(`interface ${object.name} {`);
|
|
146
|
+
const propLines = object.props.map(prop => {
|
|
147
|
+
const lines = [];
|
|
148
|
+
if (prop.documentation && prop.documentation.trim().length > 0) {
|
|
149
|
+
lines.push(' /**');
|
|
150
|
+
prop.documentation.split('\n').forEach(line => {
|
|
151
|
+
lines.push(` * ${line}`);
|
|
152
|
+
});
|
|
153
|
+
lines.push(' */');
|
|
96
154
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
155
|
+
const optionalToken = prop.optional ? '?' : '';
|
|
156
|
+
lines.push(` ${prop.name}${optionalToken}: ${generateReturnType(prop.type)};`);
|
|
157
|
+
return lines.join('\n');
|
|
158
|
+
});
|
|
159
|
+
interfaceLines.push(propLines.join('\n'));
|
|
160
|
+
interfaceLines.push('}');
|
|
161
|
+
return interfaceLines.join('\n');
|
|
103
162
|
}).filter(dep => dep.trim() !== '').join('\n\n');
|
|
104
163
|
const componentProperties = properties.length > 0 ? properties[0] : undefined;
|
|
105
164
|
const componentEvents = events.length > 0 ? events[0] : undefined;
|
|
@@ -192,6 +251,24 @@ function generateVueTypings(blobs) {
|
|
|
192
251
|
const members = e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ');
|
|
193
252
|
return `export declare enum ${e.name} { ${members} }`;
|
|
194
253
|
}).join('\n');
|
|
254
|
+
// Compute relative import path from the generated typings file (index.d.ts at dist root)
|
|
255
|
+
// to the aggregated React types module (src/types.ts) when present.
|
|
256
|
+
let typesImportPath = './src/types';
|
|
257
|
+
try {
|
|
258
|
+
if (blobs.length > 0) {
|
|
259
|
+
const distRoot = blobs[0].dist;
|
|
260
|
+
const typingsDir = distRoot; // index.d.ts is written directly under distRoot
|
|
261
|
+
const typesFilePath = path_1.default.join(distRoot, 'src', 'types');
|
|
262
|
+
const rel = path_1.default.relative(typingsDir, typesFilePath).replace(/\\/g, '/');
|
|
263
|
+
typesImportPath = rel.startsWith('.') ? rel : `./${rel}`;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (_a) {
|
|
267
|
+
typesImportPath = './src/types';
|
|
268
|
+
}
|
|
269
|
+
// Always import the types namespace to support typeof references
|
|
270
|
+
const typesImport = `import * as __webfTypes from '${typesImportPath}';`;
|
|
271
|
+
(0, logger_1.debug)(`[vue] Generating typings; importing types from ${typesImportPath}`);
|
|
195
272
|
// Build mapping of template tag names to class names for GlobalComponents
|
|
196
273
|
const componentMetas = componentNames.map(className => ({
|
|
197
274
|
className,
|
|
@@ -207,6 +284,7 @@ function generateVueTypings(blobs) {
|
|
|
207
284
|
components,
|
|
208
285
|
consts: constDeclarations,
|
|
209
286
|
enums: enumDeclarations,
|
|
287
|
+
typesImport,
|
|
210
288
|
});
|
|
211
289
|
return content.split('\n').filter(str => {
|
|
212
290
|
return str.trim().length > 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openwebf/webf",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.10",
|
|
4
4
|
"description": "Command line tools for WebF",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"keywords": [],
|
|
26
26
|
"author": "",
|
|
27
|
-
"license": "
|
|
27
|
+
"license": "Apache-2.0",
|
|
28
28
|
"type": "commonjs",
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/inquirer": "^8.2.11",
|
package/src/analyzer.ts
CHANGED
|
@@ -286,6 +286,20 @@ function getParameterBaseType(type: ts.TypeNode, mode?: ParameterMode): Paramete
|
|
|
286
286
|
return basicType;
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
+
// Handle `typeof SomeIdentifier` (TypeQuery) by preserving the textual form
|
|
290
|
+
// so React/Vue can keep strong typing (e.g., `typeof CupertinoIcons`).
|
|
291
|
+
// Dart mapping will convert this to `dynamic` later.
|
|
292
|
+
if (type.kind === ts.SyntaxKind.TypeQuery) {
|
|
293
|
+
const tq = type as ts.TypeQueryNode;
|
|
294
|
+
const getEntityNameText = (name: ts.EntityName): string => {
|
|
295
|
+
if (ts.isIdentifier(name)) return name.text;
|
|
296
|
+
// Qualified name: A.B.C
|
|
297
|
+
return `${getEntityNameText(name.left)}.${name.right.text}`;
|
|
298
|
+
};
|
|
299
|
+
const nameText = getEntityNameText(tq.exprName);
|
|
300
|
+
return `typeof ${nameText}`;
|
|
301
|
+
}
|
|
302
|
+
|
|
289
303
|
if (type.kind === ts.SyntaxKind.TypeReference) {
|
|
290
304
|
const typeReference = type as ts.TypeReferenceNode;
|
|
291
305
|
const typeName = typeReference.typeName;
|
|
@@ -405,7 +419,45 @@ function handleCustomEventType(typeReference: ts.TypeReferenceNode): ParameterBa
|
|
|
405
419
|
const argument = typeReference.typeArguments[0];
|
|
406
420
|
let genericType: string;
|
|
407
421
|
|
|
408
|
-
|
|
422
|
+
// Preserve simple union/compound generic types (e.g., boolean | null)
|
|
423
|
+
if (ts.isUnionTypeNode(argument) || ts.isIntersectionTypeNode(argument)) {
|
|
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)) {
|
|
409
461
|
const typeName = argument.typeName.text;
|
|
410
462
|
|
|
411
463
|
// Check if it's a mapped type reference like 'int' or 'double'
|
|
@@ -699,7 +751,11 @@ function processEnumDeclaration(
|
|
|
699
751
|
}
|
|
700
752
|
return mem;
|
|
701
753
|
});
|
|
702
|
-
|
|
754
|
+
|
|
755
|
+
// Register globally for cross-file lookups (e.g., Dart mapping decisions)
|
|
756
|
+
try {
|
|
757
|
+
EnumObject.globalEnumSet.add(enumObj.name);
|
|
758
|
+
} catch {}
|
|
703
759
|
return enumObj;
|
|
704
760
|
}
|
|
705
761
|
|