@openwebf/webf 0.23.0 → 0.23.7
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 +5 -1
- package/TYPING_GUIDE.md +17 -1
- package/bin/webf.js +1 -0
- package/dist/analyzer.js +135 -17
- package/dist/commands.js +251 -99
- package/dist/constants.js +242 -0
- package/dist/dart.js +91 -25
- package/dist/declaration.js +14 -1
- package/dist/generator.js +80 -12
- package/dist/react.js +281 -23
- package/dist/vue.js +114 -11
- package/package.json +1 -1
- package/src/IDLBlob.ts +2 -2
- package/src/analyzer.ts +142 -28
- package/src/commands.ts +363 -197
- package/src/dart.ts +95 -20
- package/src/declaration.ts +16 -0
- package/src/generator.ts +81 -13
- package/src/react.ts +300 -28
- package/src/vue.ts +131 -14
- package/templates/class.dart.tpl +1 -1
- package/templates/react.package.json.tpl +1 -0
- package/templates/vue.components.d.ts.tpl +9 -4
- package/test/commands.test.ts +82 -2
- package/test/dart-nullable-props.test.ts +58 -0
- package/test/react-consts.test.ts +30 -0
- package/test/react-vue-nullable-props.test.ts +66 -0
- package/test/react.test.ts +46 -4
- package/test/vue.test.ts +34 -2
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:
|
|
@@ -121,6 +160,8 @@ function toWebFTagName(className) {
|
|
|
121
160
|
function generateReactComponent(blob, packageName, relativeDir) {
|
|
122
161
|
const classObjects = blob.objects.filter(obj => obj instanceof declaration_1.ClassObject);
|
|
123
162
|
const typeAliases = blob.objects.filter(obj => obj instanceof declaration_1.TypeAliasObject);
|
|
163
|
+
const constObjects = blob.objects.filter(obj => obj instanceof declaration_1.ConstObject);
|
|
164
|
+
const enumObjects = blob.objects.filter(obj => obj instanceof declaration_1.EnumObject);
|
|
124
165
|
const classObjectDictionary = Object.fromEntries(classObjects.map(object => {
|
|
125
166
|
return [object.name, object];
|
|
126
167
|
}));
|
|
@@ -142,33 +183,72 @@ function generateReactComponent(blob, packageName, relativeDir) {
|
|
|
142
183
|
const typeAliasDeclarations = typeAliases.map(typeAlias => {
|
|
143
184
|
return `type ${typeAlias.name} = ${typeAlias.type};`;
|
|
144
185
|
}).join('\n');
|
|
186
|
+
// Include declare const values as ambient exports for type usage (e.g., unique symbol branding)
|
|
187
|
+
const constDeclarations = constObjects.map(c => `export declare const ${c.name}: ${c.type};`).join('\n');
|
|
188
|
+
// Include enums as concrete exports (no declare) so they are usable as values
|
|
189
|
+
const enumDeclarations = enumObjects.map(e => {
|
|
190
|
+
const members = e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ');
|
|
191
|
+
return `export enum ${e.name} { ${members} }`;
|
|
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
|
+
]);
|
|
145
200
|
const dependencies = [
|
|
146
201
|
typeAliasDeclarations,
|
|
202
|
+
constDeclarations,
|
|
203
|
+
enumDeclarations,
|
|
147
204
|
// Include Methods interfaces as dependencies
|
|
148
205
|
methods.map(object => {
|
|
149
206
|
const methodDeclarations = object.methods.map(method => {
|
|
150
207
|
return generateMethodDeclarationWithDocs(method, ' ');
|
|
151
208
|
}).join('\n');
|
|
152
|
-
|
|
153
|
-
if (object.documentation) {
|
|
154
|
-
|
|
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(' */');
|
|
155
216
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
217
|
+
lines.push(`interface ${object.name} {`);
|
|
218
|
+
lines.push(methodDeclarations);
|
|
219
|
+
lines.push('}');
|
|
220
|
+
return lines.join('\n');
|
|
159
221
|
}).join('\n\n'),
|
|
160
222
|
others.map(object => {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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(' */');
|
|
164
243
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
|
|
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')
|
|
172
252
|
].filter(Boolean).join('\n\n');
|
|
173
253
|
// Generate all components from this file
|
|
174
254
|
const components = [];
|
|
@@ -221,17 +301,114 @@ interface ${object.name} {
|
|
|
221
301
|
}
|
|
222
302
|
const templateContent = readTemplate('react.component.tsx')
|
|
223
303
|
.replace('import { createWebFComponent, WebFElementWithMethods } from "@openwebf/react-core-ui";', createWebFComponentImport);
|
|
224
|
-
|
|
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)({
|
|
225
402
|
className: className,
|
|
226
403
|
properties: component.properties,
|
|
227
404
|
events: component.events,
|
|
228
405
|
methods: component.methods,
|
|
229
406
|
classObjectDictionary,
|
|
230
|
-
dependencies,
|
|
407
|
+
dependencies: dependenciesWithImports,
|
|
231
408
|
blob,
|
|
232
409
|
toReactEventName,
|
|
233
410
|
toWebFTagName,
|
|
234
|
-
generateReturnType,
|
|
411
|
+
generateReturnType: genRT,
|
|
235
412
|
generateMethodDeclaration,
|
|
236
413
|
generateMethodDeclarationWithDocs,
|
|
237
414
|
generateEventHandlerType,
|
|
@@ -257,6 +434,81 @@ interface ${object.name} {
|
|
|
257
434
|
createWebFComponentImport = `import { createWebFComponent, WebFElementWithMethods } from "@openwebf/react-core-ui";`;
|
|
258
435
|
}
|
|
259
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
|
+
};
|
|
260
512
|
const content = lodash_1.default.template(readTemplate('react.component.tsx'))({
|
|
261
513
|
className: className,
|
|
262
514
|
properties: component.properties,
|
|
@@ -267,7 +519,7 @@ interface ${object.name} {
|
|
|
267
519
|
blob,
|
|
268
520
|
toReactEventName,
|
|
269
521
|
toWebFTagName,
|
|
270
|
-
generateReturnType,
|
|
522
|
+
generateReturnType: genRT,
|
|
271
523
|
generateMethodDeclaration,
|
|
272
524
|
generateMethodDeclarationWithDocs,
|
|
273
525
|
generateEventHandlerType,
|
|
@@ -281,9 +533,15 @@ interface ${object.name} {
|
|
|
281
533
|
componentDefinitions.push(withoutImports);
|
|
282
534
|
});
|
|
283
535
|
// Combine with shared imports at the top
|
|
284
|
-
|
|
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 = [
|
|
285
542
|
'import React from "react";',
|
|
286
543
|
createWebFComponentImport,
|
|
544
|
+
typesImport,
|
|
287
545
|
'',
|
|
288
546
|
dependencies,
|
|
289
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;
|
|
@@ -131,6 +190,17 @@ interface ${object.name} {
|
|
|
131
190
|
}).join('\n');
|
|
132
191
|
return result;
|
|
133
192
|
}
|
|
193
|
+
function toVueTagName(className) {
|
|
194
|
+
if (className.startsWith('WebF')) {
|
|
195
|
+
const withoutPrefix = className.substring(4);
|
|
196
|
+
return 'web-f-' + lodash_1.default.kebabCase(withoutPrefix);
|
|
197
|
+
}
|
|
198
|
+
else if (className.startsWith('Flutter')) {
|
|
199
|
+
const withoutPrefix = className.substring(7);
|
|
200
|
+
return 'flutter-' + lodash_1.default.kebabCase(withoutPrefix);
|
|
201
|
+
}
|
|
202
|
+
return lodash_1.default.kebabCase(className);
|
|
203
|
+
}
|
|
134
204
|
function generateVueTypings(blobs) {
|
|
135
205
|
const componentNames = blobs.map(blob => {
|
|
136
206
|
const classObjects = blob.objects;
|
|
@@ -160,13 +230,46 @@ function generateVueTypings(blobs) {
|
|
|
160
230
|
}).filter(component => {
|
|
161
231
|
return component.length > 0;
|
|
162
232
|
}).join('\n\n');
|
|
233
|
+
// Collect declare consts across blobs and render as exported ambient declarations
|
|
234
|
+
const consts = blobs
|
|
235
|
+
.flatMap(blob => blob.objects)
|
|
236
|
+
.filter(obj => obj instanceof declaration_1.ConstObject);
|
|
237
|
+
// Deduplicate by name keeping first occurrence
|
|
238
|
+
const uniqueConsts = new Map();
|
|
239
|
+
consts.forEach(c => {
|
|
240
|
+
if (!uniqueConsts.has(c.name))
|
|
241
|
+
uniqueConsts.set(c.name, c);
|
|
242
|
+
});
|
|
243
|
+
const constDeclarations = Array.from(uniqueConsts.values())
|
|
244
|
+
.map(c => `export declare const ${c.name}: ${c.type};`)
|
|
245
|
+
.join('\n');
|
|
246
|
+
// Collect declare enums across blobs
|
|
247
|
+
const enums = blobs
|
|
248
|
+
.flatMap(blob => blob.objects)
|
|
249
|
+
.filter(obj => obj instanceof declaration_1.EnumObject);
|
|
250
|
+
const enumDeclarations = enums.map(e => {
|
|
251
|
+
const members = e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ');
|
|
252
|
+
return `export declare enum ${e.name} { ${members} }`;
|
|
253
|
+
}).join('\n');
|
|
254
|
+
// Always import the types namespace to support typeof references
|
|
255
|
+
const typesImport = `import * as __webfTypes from './src/types';`;
|
|
256
|
+
(0, logger_1.debug)(`[vue] Generating typings; importing types from ./src/types`);
|
|
257
|
+
// Build mapping of template tag names to class names for GlobalComponents
|
|
258
|
+
const componentMetas = componentNames.map(className => ({
|
|
259
|
+
className,
|
|
260
|
+
tagName: toVueTagName(className),
|
|
261
|
+
}));
|
|
163
262
|
const content = lodash_1.default.template(readTemplate('vue.components.d.ts'), {
|
|
164
263
|
interpolate: /<%=([\s\S]+?)%>/g,
|
|
165
264
|
evaluate: /<%([\s\S]+?)%>/g,
|
|
166
265
|
escape: /<%-([\s\S]+?)%>/g
|
|
167
266
|
})({
|
|
168
267
|
componentNames,
|
|
268
|
+
componentMetas,
|
|
169
269
|
components,
|
|
270
|
+
consts: constDeclarations,
|
|
271
|
+
enums: enumDeclarations,
|
|
272
|
+
typesImport,
|
|
170
273
|
});
|
|
171
274
|
return content.split('\n').filter(str => {
|
|
172
275
|
return str.trim().length > 0;
|
package/package.json
CHANGED
package/src/IDLBlob.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {ClassObject, FunctionObject, TypeAliasObject} from "./declaration";
|
|
1
|
+
import {ClassObject, FunctionObject, TypeAliasObject, EnumObject} from "./declaration";
|
|
2
2
|
|
|
3
3
|
export class IDLBlob {
|
|
4
4
|
raw: string = '';
|
|
@@ -7,7 +7,7 @@ export class IDLBlob {
|
|
|
7
7
|
filename: string;
|
|
8
8
|
implement: string;
|
|
9
9
|
relativeDir: string = '';
|
|
10
|
-
objects: (ClassObject | FunctionObject | TypeAliasObject)[] = [];
|
|
10
|
+
objects: (ClassObject | FunctionObject | TypeAliasObject | EnumObject)[] = [];
|
|
11
11
|
|
|
12
12
|
constructor(source: string, dist: string, filename: string, implement: string, relativeDir: string = '') {
|
|
13
13
|
this.source = source;
|