@openwebf/webf 0.22.13 → 0.23.2
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/TYPING_GUIDE.md +17 -1
- package/dist/analyzer.js +70 -16
- package/dist/commands.js +64 -6
- package/dist/constants.js +242 -0
- package/dist/dart.js +9 -7
- package/dist/declaration.js +13 -1
- package/dist/generator.js +148 -30
- package/dist/react.js +11 -0
- package/dist/vue.js +40 -0
- package/package.json +3 -2
- package/src/IDLBlob.ts +2 -2
- package/src/analyzer.ts +85 -27
- package/src/commands.ts +73 -6
- package/src/dart.ts +10 -8
- package/src/declaration.ts +15 -0
- package/src/generator.ts +146 -31
- package/src/react.ts +14 -1
- package/src/vue.ts +46 -1
- package/templates/class.dart.tpl +1 -1
- package/templates/react.package.json.tpl +1 -0
- package/templates/vue.components.d.ts.tpl +7 -4
- package/test/react-consts.test.ts +30 -0
- package/test/vue.test.ts +34 -2
- package/tsconfig.json +4 -1
package/src/dart.ts
CHANGED
|
@@ -83,6 +83,13 @@ ${enumValues};
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
function generateReturnType(type: ParameterType, enumName?: string) {
|
|
86
|
+
// Handle union types first (e.g., 'left' | 'center' | 'right')
|
|
87
|
+
// so we don't incorrectly treat string literal unions as pointer types.
|
|
88
|
+
if (Array.isArray(type.value)) {
|
|
89
|
+
// If we have an enum name, use it; otherwise fall back to String
|
|
90
|
+
return enumName || 'String';
|
|
91
|
+
}
|
|
92
|
+
|
|
86
93
|
if (isPointerType(type)) {
|
|
87
94
|
const pointerType = getPointerType(type);
|
|
88
95
|
return pointerType;
|
|
@@ -91,12 +98,6 @@ function generateReturnType(type: ParameterType, enumName?: string) {
|
|
|
91
98
|
return `${getPointerType(type.value)}[]`;
|
|
92
99
|
}
|
|
93
100
|
|
|
94
|
-
// Handle union types (e.g., 'left' | 'center' | 'right')
|
|
95
|
-
if (Array.isArray(type.value)) {
|
|
96
|
-
// If we have an enum name, use it; otherwise fall back to String
|
|
97
|
-
return enumName || 'String';
|
|
98
|
-
}
|
|
99
|
-
|
|
100
101
|
// Handle when type.value is a ParameterType object (nested type)
|
|
101
102
|
if (typeof type.value === 'object' && !Array.isArray(type.value) && type.value !== null) {
|
|
102
103
|
// This might be a nested ParameterType, recurse
|
|
@@ -278,7 +279,7 @@ interface ${object.name} {
|
|
|
278
279
|
|
|
279
280
|
// Generate enums for union types
|
|
280
281
|
const enums: { name: string; definition: string }[] = [];
|
|
281
|
-
const enumMap: Map<string, string> = new Map(); // prop name -> enum name
|
|
282
|
+
const enumMap: Map<string, string> = new Map(); // camelCase prop name -> enum name
|
|
282
283
|
|
|
283
284
|
if (componentProperties) {
|
|
284
285
|
for (const prop of componentProperties.props) {
|
|
@@ -290,7 +291,8 @@ interface ${object.name} {
|
|
|
290
291
|
name: enumName,
|
|
291
292
|
definition: generateDartEnum(enumName, values)
|
|
292
293
|
});
|
|
293
|
-
|
|
294
|
+
// Store by camelCase prop name to match template usage
|
|
295
|
+
enumMap.set(_.camelCase(prop.name), enumName);
|
|
294
296
|
}
|
|
295
297
|
}
|
|
296
298
|
}
|
package/src/declaration.ts
CHANGED
|
@@ -89,3 +89,18 @@ export class TypeAliasObject {
|
|
|
89
89
|
name: string;
|
|
90
90
|
type: string;
|
|
91
91
|
}
|
|
92
|
+
|
|
93
|
+
export class ConstObject {
|
|
94
|
+
name: string;
|
|
95
|
+
type: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export class EnumMemberObject {
|
|
99
|
+
name: string;
|
|
100
|
+
initializer?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export class EnumObject {
|
|
104
|
+
name: string;
|
|
105
|
+
members: EnumMemberObject[] = [];
|
|
106
|
+
}
|
package/src/generator.ts
CHANGED
|
@@ -5,12 +5,13 @@ import _ from 'lodash';
|
|
|
5
5
|
import { glob } from 'glob';
|
|
6
6
|
import yaml from 'yaml';
|
|
7
7
|
import { IDLBlob } from './IDLBlob';
|
|
8
|
-
import { ClassObject } from './declaration';
|
|
8
|
+
import { ClassObject, ConstObject, EnumObject, TypeAliasObject } from './declaration';
|
|
9
9
|
import { analyzer, ParameterType, clearCaches } from './analyzer';
|
|
10
10
|
import { generateDartClass } from './dart';
|
|
11
11
|
import { generateReactComponent, generateReactIndex } from './react';
|
|
12
12
|
import { generateVueTypings } from './vue';
|
|
13
13
|
import { logger, debug, info, success, warn, error, group, progress, time, timeEnd } from './logger';
|
|
14
|
+
import ts from 'typescript';
|
|
14
15
|
|
|
15
16
|
// Cache for file content to avoid redundant reads
|
|
16
17
|
const fileContentCache = new Map<string, string>();
|
|
@@ -204,13 +205,17 @@ export async function dartGen({ source, target, command, exclude }: GenerateOpti
|
|
|
204
205
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
205
206
|
}
|
|
206
207
|
|
|
207
|
-
// Generate Dart file
|
|
208
|
+
// Generate Dart file (skip if empty)
|
|
208
209
|
const genFilePath = path.join(outputDir, _.snakeCase(blob.filename));
|
|
209
210
|
const fullPath = genFilePath + '_bindings_generated.dart';
|
|
210
211
|
|
|
211
|
-
if (
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
if (result && result.trim().length > 0) {
|
|
213
|
+
if (writeFileIfChanged(fullPath, result)) {
|
|
214
|
+
filesChanged++;
|
|
215
|
+
debug(`Generated: ${path.basename(fullPath)}`);
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
debug(`Skipped ${path.basename(fullPath)} - empty bindings`);
|
|
214
219
|
}
|
|
215
220
|
|
|
216
221
|
// Copy the original .d.ts file to the output directory
|
|
@@ -224,13 +229,8 @@ export async function dartGen({ source, target, command, exclude }: GenerateOpti
|
|
|
224
229
|
}
|
|
225
230
|
});
|
|
226
231
|
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
const indexDtsPath = path.join(normalizedTarget, 'index.d.ts');
|
|
230
|
-
if (writeFileIfChanged(indexDtsPath, indexDtsContent)) {
|
|
231
|
-
filesChanged++;
|
|
232
|
-
debug('Generated: index.d.ts');
|
|
233
|
-
}
|
|
232
|
+
// Note: We no longer generate a root index.d.ts for Dart codegen
|
|
233
|
+
// as it is not necessary for the codegen workflow.
|
|
234
234
|
|
|
235
235
|
timeEnd('dartGen');
|
|
236
236
|
success(`Dart code generation completed. ${filesChanged} files changed.`);
|
|
@@ -321,40 +321,155 @@ export async function reactGen({ source, target, exclude, packageName }: Generat
|
|
|
321
321
|
}
|
|
322
322
|
});
|
|
323
323
|
|
|
324
|
-
// Generate index file
|
|
325
|
-
// Avoid overriding a user-managed index.ts. Only write when:
|
|
326
|
-
// - index.ts does not exist, or
|
|
327
|
-
// - it contains the auto-generated marker from our template
|
|
324
|
+
// Generate/merge index file
|
|
328
325
|
const indexFilePath = path.join(normalizedTarget, 'src', 'index.ts');
|
|
326
|
+
// Always build the full index content string for downstream tooling/logging
|
|
329
327
|
const newExports = generateReactIndex(blobs);
|
|
330
328
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
329
|
+
// Build desired export map: moduleSpecifier -> Set of names
|
|
330
|
+
const desiredExports = new Map<string, Set<string>>();
|
|
331
|
+
const components = blobs.flatMap(blob => {
|
|
332
|
+
const classObjects = blob.objects.filter(obj => obj instanceof ClassObject) as ClassObject[];
|
|
333
|
+
const properties = classObjects.filter(object => object.name.endsWith('Properties'));
|
|
334
|
+
const events = classObjects.filter(object => object.name.endsWith('Events'));
|
|
335
|
+
const componentMap = new Map<string, boolean>();
|
|
336
|
+
properties.forEach(prop => componentMap.set(prop.name.replace(/Properties$/, ''), true));
|
|
337
|
+
events.forEach(evt => componentMap.set(evt.name.replace(/Events$/, ''), true));
|
|
338
|
+
return Array.from(componentMap.keys()).map(className => ({
|
|
339
|
+
className,
|
|
340
|
+
fileName: blob.filename,
|
|
341
|
+
relativeDir: blob.relativeDir,
|
|
342
|
+
}));
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Deduplicate by className
|
|
346
|
+
const unique = new Map<string, { className: string; fileName: string; relativeDir: string }>();
|
|
347
|
+
for (const c of components) {
|
|
348
|
+
if (!unique.has(c.className)) unique.set(c.className, c);
|
|
349
|
+
}
|
|
350
|
+
for (const { className, fileName, relativeDir } of unique.values()) {
|
|
351
|
+
const spec = `./${relativeDir ? `${relativeDir}/` : ''}${fileName}`;
|
|
352
|
+
if (!desiredExports.has(spec)) desiredExports.set(spec, new Set());
|
|
353
|
+
const set = desiredExports.get(spec)!;
|
|
354
|
+
set.add(className);
|
|
355
|
+
set.add(`${className}Element`);
|
|
345
356
|
}
|
|
346
357
|
|
|
347
|
-
if (
|
|
358
|
+
if (!fs.existsSync(indexFilePath)) {
|
|
359
|
+
// No index.ts -> generate fresh file from template
|
|
348
360
|
if (writeFileIfChanged(indexFilePath, newExports)) {
|
|
349
361
|
filesChanged++;
|
|
350
362
|
debug(`Generated: index.ts`);
|
|
351
363
|
}
|
|
364
|
+
} else {
|
|
365
|
+
// Merge into existing index.ts without removing user code
|
|
366
|
+
try {
|
|
367
|
+
const existing = fs.readFileSync(indexFilePath, 'utf-8');
|
|
368
|
+
const sourceFile = ts.createSourceFile(indexFilePath, existing, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
|
|
369
|
+
|
|
370
|
+
// Track which names already exported per module
|
|
371
|
+
for (const stmt of sourceFile.statements) {
|
|
372
|
+
if (ts.isExportDeclaration(stmt) && stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {
|
|
373
|
+
const moduleSpecifier = stmt.moduleSpecifier && ts.isStringLiteral(stmt.moduleSpecifier)
|
|
374
|
+
? stmt.moduleSpecifier.text
|
|
375
|
+
: undefined;
|
|
376
|
+
if (!moduleSpecifier) continue;
|
|
377
|
+
const desired = desiredExports.get(moduleSpecifier);
|
|
378
|
+
if (!desired) continue;
|
|
379
|
+
for (const el of stmt.exportClause.elements) {
|
|
380
|
+
const name = el.name.getText(sourceFile);
|
|
381
|
+
if (desired.has(name)) desired.delete(name);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Prepare new export lines for any remaining names
|
|
387
|
+
const lines: string[] = [];
|
|
388
|
+
for (const [spec, names] of desiredExports) {
|
|
389
|
+
const missing = Array.from(names);
|
|
390
|
+
if (missing.length === 0) continue;
|
|
391
|
+
const specEscaped = spec.replace(/\\/g, '/');
|
|
392
|
+
lines.push(`export { ${missing.join(', ')} } from "${specEscaped}";`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (lines.length > 0) {
|
|
396
|
+
const appended = (existing.endsWith('\n') ? '' : '\n') + lines.join('\n') + '\n';
|
|
397
|
+
if (writeFileIfChanged(indexFilePath, existing + appended)) {
|
|
398
|
+
filesChanged++;
|
|
399
|
+
debug(`Merged exports into existing index.ts`);
|
|
400
|
+
}
|
|
401
|
+
} else {
|
|
402
|
+
debug(`index.ts is up to date; no merge needed.`);
|
|
403
|
+
}
|
|
404
|
+
} catch (err) {
|
|
405
|
+
warn(`Failed to merge into existing index.ts. Skipping modifications: ${indexFilePath}`);
|
|
406
|
+
}
|
|
352
407
|
}
|
|
353
408
|
|
|
354
409
|
timeEnd('reactGen');
|
|
355
410
|
success(`React code generation completed. ${filesChanged} files changed.`);
|
|
356
411
|
info(`Output directory: ${normalizedTarget}`);
|
|
357
412
|
info('You can now import these components in your React project.');
|
|
413
|
+
|
|
414
|
+
// Aggregate standalone type declarations (consts/enums/type aliases) into a single types.d.ts
|
|
415
|
+
try {
|
|
416
|
+
const consts = blobs.flatMap(b => b.objects.filter(o => o instanceof ConstObject) as ConstObject[]);
|
|
417
|
+
const enums = blobs.flatMap(b => b.objects.filter(o => o instanceof EnumObject) as EnumObject[]);
|
|
418
|
+
const typeAliases = blobs.flatMap(b => b.objects.filter(o => o instanceof TypeAliasObject) as TypeAliasObject[]);
|
|
419
|
+
|
|
420
|
+
// Deduplicate by name
|
|
421
|
+
const constMap = new Map<string, ConstObject>();
|
|
422
|
+
consts.forEach(c => { if (!constMap.has(c.name)) constMap.set(c.name, c); });
|
|
423
|
+
const typeAliasMap = new Map<string, TypeAliasObject>();
|
|
424
|
+
typeAliases.forEach(t => { if (!typeAliasMap.has(t.name)) typeAliasMap.set(t.name, t); });
|
|
425
|
+
|
|
426
|
+
const hasAny = constMap.size > 0 || enums.length > 0 || typeAliasMap.size > 0;
|
|
427
|
+
if (hasAny) {
|
|
428
|
+
const constDecl = Array.from(constMap.values())
|
|
429
|
+
.map(c => `export declare const ${c.name}: ${c.type};`)
|
|
430
|
+
.join('\n');
|
|
431
|
+
const enumDecl = enums
|
|
432
|
+
.map(e => `export declare enum ${e.name} { ${e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ')} }`)
|
|
433
|
+
.join('\n');
|
|
434
|
+
const typeAliasDecl = Array.from(typeAliasMap.values())
|
|
435
|
+
.map(t => `export type ${t.name} = ${t.type};`)
|
|
436
|
+
.join('\n');
|
|
437
|
+
|
|
438
|
+
const typesContent = [
|
|
439
|
+
'/* Generated by WebF CLI - aggregated type declarations */',
|
|
440
|
+
typeAliasDecl,
|
|
441
|
+
constDecl,
|
|
442
|
+
enumDecl,
|
|
443
|
+
''
|
|
444
|
+
].filter(Boolean).join('\n');
|
|
445
|
+
|
|
446
|
+
const typesPath = path.join(normalizedTarget, 'src', 'types.d.ts');
|
|
447
|
+
if (writeFileIfChanged(typesPath, typesContent)) {
|
|
448
|
+
filesChanged++;
|
|
449
|
+
debug(`Generated: src/types.d.ts`);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Try to help TypeScript pick up additional declarations by adding a reference comment.
|
|
453
|
+
// This avoids bundler resolution errors from importing a .d.ts file.
|
|
454
|
+
const indexFilePath = path.join(normalizedTarget, 'src', 'index.ts');
|
|
455
|
+
try {
|
|
456
|
+
if (fs.existsSync(indexFilePath)) {
|
|
457
|
+
let current = fs.readFileSync(indexFilePath, 'utf-8');
|
|
458
|
+
const refLine = `/// <reference path="./types.d.ts" />`;
|
|
459
|
+
if (!current.includes(refLine)) {
|
|
460
|
+
// Place the reference at the very top, before any code
|
|
461
|
+
const updated = `${refLine}\n${current}`;
|
|
462
|
+
if (writeFileIfChanged(indexFilePath, updated)) {
|
|
463
|
+
filesChanged++;
|
|
464
|
+
debug(`Updated: src/index.ts with reference to types.d.ts`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
} catch {}
|
|
469
|
+
}
|
|
470
|
+
} catch (e) {
|
|
471
|
+
warn('Failed to generate aggregated React types.d.ts');
|
|
472
|
+
}
|
|
358
473
|
}
|
|
359
474
|
|
|
360
475
|
export async function vueGen({ source, target, exclude }: GenerateOptions) {
|
package/src/react.ts
CHANGED
|
@@ -2,7 +2,7 @@ import _ from "lodash";
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import {ParameterType} from "./analyzer";
|
|
5
|
-
import {ClassObject, FunctionArgumentType, FunctionDeclaration, TypeAliasObject} from "./declaration";
|
|
5
|
+
import {ClassObject, FunctionArgumentType, FunctionDeclaration, TypeAliasObject, ConstObject, EnumObject} from "./declaration";
|
|
6
6
|
import {IDLBlob} from "./IDLBlob";
|
|
7
7
|
import {getPointerType, isPointerType, isUnionType} from "./utils";
|
|
8
8
|
|
|
@@ -125,6 +125,8 @@ export function toWebFTagName(className: string): string {
|
|
|
125
125
|
export function generateReactComponent(blob: IDLBlob, packageName?: string, relativeDir?: string) {
|
|
126
126
|
const classObjects = blob.objects.filter(obj => obj instanceof ClassObject) as ClassObject[];
|
|
127
127
|
const typeAliases = blob.objects.filter(obj => obj instanceof TypeAliasObject) as TypeAliasObject[];
|
|
128
|
+
const constObjects = blob.objects.filter(obj => obj instanceof ConstObject) as ConstObject[];
|
|
129
|
+
const enumObjects = blob.objects.filter(obj => obj instanceof EnumObject) as EnumObject[];
|
|
128
130
|
|
|
129
131
|
const classObjectDictionary = Object.fromEntries(
|
|
130
132
|
classObjects.map(object => {
|
|
@@ -153,8 +155,19 @@ export function generateReactComponent(blob: IDLBlob, packageName?: string, rela
|
|
|
153
155
|
return `type ${typeAlias.name} = ${typeAlias.type};`;
|
|
154
156
|
}).join('\n');
|
|
155
157
|
|
|
158
|
+
// Include declare const values as ambient exports for type usage (e.g., unique symbol branding)
|
|
159
|
+
const constDeclarations = constObjects.map(c => `export declare const ${c.name}: ${c.type};`).join('\n');
|
|
160
|
+
|
|
161
|
+
// Include enums
|
|
162
|
+
const enumDeclarations = enumObjects.map(e => {
|
|
163
|
+
const members = e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ');
|
|
164
|
+
return `export declare enum ${e.name} { ${members} }`;
|
|
165
|
+
}).join('\n');
|
|
166
|
+
|
|
156
167
|
const dependencies = [
|
|
157
168
|
typeAliasDeclarations,
|
|
169
|
+
constDeclarations,
|
|
170
|
+
enumDeclarations,
|
|
158
171
|
// Include Methods interfaces as dependencies
|
|
159
172
|
methods.map(object => {
|
|
160
173
|
const methodDeclarations = object.methods.map(method => {
|
package/src/vue.ts
CHANGED
|
@@ -2,7 +2,7 @@ import _ from "lodash";
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import {ParameterType} from "./analyzer";
|
|
5
|
-
import {ClassObject, FunctionArgumentType, FunctionDeclaration} from "./declaration";
|
|
5
|
+
import {ClassObject, FunctionArgumentType, FunctionDeclaration, ConstObject, EnumObject} from "./declaration";
|
|
6
6
|
import {IDLBlob} from "./IDLBlob";
|
|
7
7
|
import {getPointerType, isPointerType} from "./utils";
|
|
8
8
|
|
|
@@ -145,6 +145,17 @@ interface ${object.name} {
|
|
|
145
145
|
return result;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
function toVueTagName(className: string): string {
|
|
149
|
+
if (className.startsWith('WebF')) {
|
|
150
|
+
const withoutPrefix = className.substring(4);
|
|
151
|
+
return 'web-f-' + _.kebabCase(withoutPrefix);
|
|
152
|
+
} else if (className.startsWith('Flutter')) {
|
|
153
|
+
const withoutPrefix = className.substring(7);
|
|
154
|
+
return 'flutter-' + _.kebabCase(withoutPrefix);
|
|
155
|
+
}
|
|
156
|
+
return _.kebabCase(className);
|
|
157
|
+
}
|
|
158
|
+
|
|
148
159
|
export function generateVueTypings(blobs: IDLBlob[]) {
|
|
149
160
|
const componentNames = blobs.map(blob => {
|
|
150
161
|
const classObjects = blob.objects as ClassObject[];
|
|
@@ -177,13 +188,47 @@ export function generateVueTypings(blobs: IDLBlob[]) {
|
|
|
177
188
|
return component.length > 0;
|
|
178
189
|
}).join('\n\n');
|
|
179
190
|
|
|
191
|
+
// Collect declare consts across blobs and render as exported ambient declarations
|
|
192
|
+
const consts = blobs
|
|
193
|
+
.flatMap(blob => blob.objects)
|
|
194
|
+
.filter(obj => obj instanceof ConstObject) as ConstObject[];
|
|
195
|
+
|
|
196
|
+
// Deduplicate by name keeping first occurrence
|
|
197
|
+
const uniqueConsts = new Map<string, ConstObject>();
|
|
198
|
+
consts.forEach(c => {
|
|
199
|
+
if (!uniqueConsts.has(c.name)) uniqueConsts.set(c.name, c);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const constDeclarations = Array.from(uniqueConsts.values())
|
|
203
|
+
.map(c => `export declare const ${c.name}: ${c.type};`)
|
|
204
|
+
.join('\n');
|
|
205
|
+
|
|
206
|
+
// Collect declare enums across blobs
|
|
207
|
+
const enums = blobs
|
|
208
|
+
.flatMap(blob => blob.objects)
|
|
209
|
+
.filter(obj => obj instanceof EnumObject) as EnumObject[];
|
|
210
|
+
|
|
211
|
+
const enumDeclarations = enums.map(e => {
|
|
212
|
+
const members = e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ');
|
|
213
|
+
return `export declare enum ${e.name} { ${members} }`;
|
|
214
|
+
}).join('\n');
|
|
215
|
+
|
|
216
|
+
// Build mapping of template tag names to class names for GlobalComponents
|
|
217
|
+
const componentMetas = componentNames.map(className => ({
|
|
218
|
+
className,
|
|
219
|
+
tagName: toVueTagName(className),
|
|
220
|
+
}));
|
|
221
|
+
|
|
180
222
|
const content = _.template(readTemplate('vue.components.d.ts'), {
|
|
181
223
|
interpolate: /<%=([\s\S]+?)%>/g,
|
|
182
224
|
evaluate: /<%([\s\S]+?)%>/g,
|
|
183
225
|
escape: /<%-([\s\S]+?)%>/g
|
|
184
226
|
})({
|
|
185
227
|
componentNames,
|
|
228
|
+
componentMetas,
|
|
186
229
|
components,
|
|
230
|
+
consts: constDeclarations,
|
|
231
|
+
enums: enumDeclarations,
|
|
187
232
|
});
|
|
188
233
|
|
|
189
234
|
return content.split('\n').filter(str => {
|
package/templates/class.dart.tpl
CHANGED
|
@@ -19,6 +19,9 @@ type VueEventListeners<T extends EventMap> = {
|
|
|
19
19
|
[K in keyof T as `on${Capitalize<string & K>}`]?: (event: T[K]) => any
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
<%= consts %>
|
|
23
|
+
<%= enums %>
|
|
24
|
+
|
|
22
25
|
type DefineCustomElement<
|
|
23
26
|
ElementType,
|
|
24
27
|
Events extends EventMap = {},
|
|
@@ -44,10 +47,10 @@ type DefineCustomElement<
|
|
|
44
47
|
|
|
45
48
|
declare module 'vue' {
|
|
46
49
|
interface GlobalComponents {
|
|
47
|
-
<%
|
|
48
|
-
'<%=
|
|
49
|
-
<%=
|
|
50
|
-
<%=
|
|
50
|
+
<% componentMetas.forEach(comp => { %>
|
|
51
|
+
'<%= comp.tagName %>': DefineCustomElement<
|
|
52
|
+
<%= comp.className %>Props,
|
|
53
|
+
<%= comp.className %>Events
|
|
51
54
|
>
|
|
52
55
|
<% }) %>
|
|
53
56
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { generateReactComponent } from '../src/react';
|
|
2
|
+
import { IDLBlob } from '../src/IDLBlob';
|
|
3
|
+
import { ConstObject, EnumObject, EnumMemberObject } from '../src/declaration';
|
|
4
|
+
|
|
5
|
+
describe('React generator - declare const support', () => {
|
|
6
|
+
it('emits export declare const for constants from typings', () => {
|
|
7
|
+
const blob = new IDLBlob('/test/source', '/test/target', 'ConstOnly', 'test', '');
|
|
8
|
+
const constObj = new ConstObject();
|
|
9
|
+
constObj.name = 'WEBF_CUPERTINO_SYMBOL';
|
|
10
|
+
constObj.type = 'unique symbol';
|
|
11
|
+
|
|
12
|
+
blob.objects = [constObj as any];
|
|
13
|
+
|
|
14
|
+
const output = generateReactComponent(blob);
|
|
15
|
+
expect(output).toContain('export declare const WEBF_CUPERTINO_SYMBOL: unique symbol;');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('emits export declare enum for enums from typings', () => {
|
|
19
|
+
const blob = new IDLBlob('/test/source', '/test/target', 'EnumOnly', 'test', '');
|
|
20
|
+
const eo = new EnumObject();
|
|
21
|
+
eo.name = 'CupertinoColors';
|
|
22
|
+
const m1 = new EnumMemberObject(); m1.name = "'red'"; m1.initializer = '0x0f';
|
|
23
|
+
const m2 = new EnumMemberObject(); m2.name = "'bbb'"; m2.initializer = '0x00';
|
|
24
|
+
eo.members = [m1, m2];
|
|
25
|
+
blob.objects = [eo as any];
|
|
26
|
+
|
|
27
|
+
const output = generateReactComponent(blob);
|
|
28
|
+
expect(output).toContain("export declare enum CupertinoColors { 'red' = 0x0f, 'bbb' = 0x00 }");
|
|
29
|
+
});
|
|
30
|
+
});
|
package/test/vue.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { generateVueTypings } from '../src/vue';
|
|
2
2
|
import { IDLBlob } from '../src/IDLBlob';
|
|
3
|
-
import { ClassObject, ClassObjectKind, PropsDeclaration } from '../src/declaration';
|
|
3
|
+
import { ClassObject, ClassObjectKind, PropsDeclaration, ConstObject } from '../src/declaration';
|
|
4
4
|
|
|
5
5
|
describe('Vue Generator', () => {
|
|
6
6
|
describe('generateVueTypings', () => {
|
|
@@ -153,5 +153,37 @@ describe('Vue Generator', () => {
|
|
|
153
153
|
expect(result).toContain("'class'?: string;");
|
|
154
154
|
expect(result).toContain("'style'?: string | Record<string, any>;");
|
|
155
155
|
});
|
|
156
|
+
|
|
157
|
+
it('should include declare const variables as exported declarations', () => {
|
|
158
|
+
const blob = new IDLBlob('/test/source', '/test/target', 'ConstOnly', 'test', '');
|
|
159
|
+
|
|
160
|
+
const constObj = new ConstObject();
|
|
161
|
+
constObj.name = 'WEBF_UNIQUE';
|
|
162
|
+
constObj.type = 'unique symbol';
|
|
163
|
+
|
|
164
|
+
blob.objects = [constObj as any];
|
|
165
|
+
|
|
166
|
+
const result = generateVueTypings([blob]);
|
|
167
|
+
|
|
168
|
+
expect(result).toContain('export declare const WEBF_UNIQUE: unique symbol;');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should include declare enum as exported declaration', () => {
|
|
172
|
+
const blob = new IDLBlob('/test/source', '/test/target', 'EnumOnly', 'test', '');
|
|
173
|
+
// Build a minimal faux EnumObject via analyzer by simulating ast is heavy; create a shape
|
|
174
|
+
// We'll reuse analyzer classes by importing EnumObject is cumbersome in test; instead
|
|
175
|
+
// craft an object literal compatible with instanceof check by constructing real class.
|
|
176
|
+
const { EnumObject, EnumMemberObject } = require('../src/declaration');
|
|
177
|
+
const e = new EnumObject();
|
|
178
|
+
e.name = 'CupertinoColors';
|
|
179
|
+
const m1 = new EnumMemberObject(); m1.name = "'red'"; m1.initializer = '0x0f';
|
|
180
|
+
const m2 = new EnumMemberObject(); m2.name = "'bbb'"; m2.initializer = '0x00';
|
|
181
|
+
e.members = [m1, m2];
|
|
182
|
+
|
|
183
|
+
blob.objects = [e as any];
|
|
184
|
+
|
|
185
|
+
const result = generateVueTypings([blob]);
|
|
186
|
+
expect(result).toContain("export declare enum CupertinoColors { 'red' = 0x0f, 'bbb' = 0x00 }");
|
|
187
|
+
});
|
|
156
188
|
});
|
|
157
|
-
});
|
|
189
|
+
});
|
package/tsconfig.json
CHANGED