@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/dart.js
CHANGED
|
@@ -79,19 +79,55 @@ ${enumValues};
|
|
|
79
79
|
String toString() => value;
|
|
80
80
|
}`;
|
|
81
81
|
}
|
|
82
|
+
function hasNullInUnion(type) {
|
|
83
|
+
if (!Array.isArray(type.value))
|
|
84
|
+
return false;
|
|
85
|
+
return type.value.some(t => t.value === declaration_1.FunctionArgumentType.null);
|
|
86
|
+
}
|
|
87
|
+
function isBooleanType(type) {
|
|
88
|
+
if (Array.isArray(type.value)) {
|
|
89
|
+
return type.value.some(t => t.value === declaration_1.FunctionArgumentType.boolean);
|
|
90
|
+
}
|
|
91
|
+
return type.value === declaration_1.FunctionArgumentType.boolean;
|
|
92
|
+
}
|
|
82
93
|
function generateReturnType(type, enumName) {
|
|
83
94
|
// Handle union types first (e.g., 'left' | 'center' | 'right')
|
|
84
95
|
// so we don't incorrectly treat string literal unions as pointer types.
|
|
85
96
|
if (Array.isArray(type.value)) {
|
|
86
|
-
// If we have an enum name, use it
|
|
87
|
-
|
|
97
|
+
// If we have an enum name, always use it (nullable handled separately)
|
|
98
|
+
if (enumName) {
|
|
99
|
+
return enumName;
|
|
100
|
+
}
|
|
101
|
+
// If this is a union that includes null and exactly one non-null type,
|
|
102
|
+
// generate the Dart type from the non-null part instead of falling back to String.
|
|
103
|
+
const trimmed = (0, utils_1.trimNullTypeFromType)(type);
|
|
104
|
+
if (!Array.isArray(trimmed.value)) {
|
|
105
|
+
return generateReturnType(trimmed, enumName);
|
|
106
|
+
}
|
|
107
|
+
// Fallback for complex unions: use String
|
|
108
|
+
return 'String';
|
|
88
109
|
}
|
|
89
110
|
if ((0, utils_1.isPointerType)(type)) {
|
|
90
111
|
const pointerType = (0, utils_1.getPointerType)(type);
|
|
112
|
+
// Map TS typeof expressions to Dart dynamic
|
|
113
|
+
if (typeof pointerType === 'string' && pointerType.startsWith('typeof ')) {
|
|
114
|
+
return 'dynamic';
|
|
115
|
+
}
|
|
116
|
+
// Map references to known string enums to String in Dart
|
|
117
|
+
if (typeof pointerType === 'string' && declaration_1.EnumObject.globalEnumSet.has(pointerType)) {
|
|
118
|
+
return 'String';
|
|
119
|
+
}
|
|
91
120
|
return pointerType;
|
|
92
121
|
}
|
|
93
122
|
if (type.isArray && typeof type.value === 'object' && !Array.isArray(type.value)) {
|
|
94
|
-
|
|
123
|
+
const elem = (0, utils_1.getPointerType)(type.value);
|
|
124
|
+
if (typeof elem === 'string' && elem.startsWith('typeof ')) {
|
|
125
|
+
return `dynamic[]`;
|
|
126
|
+
}
|
|
127
|
+
if (typeof elem === 'string' && declaration_1.EnumObject.globalEnumSet.has(elem)) {
|
|
128
|
+
return 'String[]';
|
|
129
|
+
}
|
|
130
|
+
return `${elem}[]`;
|
|
95
131
|
}
|
|
96
132
|
// Handle when type.value is a ParameterType object (nested type)
|
|
97
133
|
if (typeof type.value === 'object' && !Array.isArray(type.value) && type.value !== null) {
|
|
@@ -106,7 +142,8 @@ function generateReturnType(type, enumName) {
|
|
|
106
142
|
return 'double';
|
|
107
143
|
}
|
|
108
144
|
case declaration_1.FunctionArgumentType.any: {
|
|
109
|
-
|
|
145
|
+
// Dart doesn't have `any`; use `dynamic`.
|
|
146
|
+
return 'dynamic';
|
|
110
147
|
}
|
|
111
148
|
case declaration_1.FunctionArgumentType.boolean: {
|
|
112
149
|
return 'bool';
|
|
@@ -135,41 +172,59 @@ function generateEventHandlerType(type) {
|
|
|
135
172
|
}
|
|
136
173
|
function generateAttributeSetter(propName, type, enumName) {
|
|
137
174
|
// Attributes from HTML are always strings, so we need to convert them
|
|
175
|
+
const unionHasNull = hasNullInUnion(type);
|
|
138
176
|
// Handle enum types
|
|
139
177
|
if (enumName && Array.isArray(type.value)) {
|
|
178
|
+
if (unionHasNull) {
|
|
179
|
+
return `${propName} = value == 'null' ? null : ${enumName}.parse(value)`;
|
|
180
|
+
}
|
|
140
181
|
return `${propName} = ${enumName}.parse(value)`;
|
|
141
182
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
183
|
+
const effectiveType = Array.isArray(type.value) && unionHasNull
|
|
184
|
+
? (0, utils_1.trimNullTypeFromType)(type)
|
|
185
|
+
: type;
|
|
186
|
+
const baseSetter = (() => {
|
|
187
|
+
switch (effectiveType.value) {
|
|
188
|
+
case declaration_1.FunctionArgumentType.boolean:
|
|
189
|
+
return `${propName} = value == 'true' || value == ''`;
|
|
190
|
+
case declaration_1.FunctionArgumentType.int:
|
|
191
|
+
return `${propName} = int.tryParse(value) ?? 0`;
|
|
192
|
+
case declaration_1.FunctionArgumentType.double:
|
|
193
|
+
return `${propName} = double.tryParse(value) ?? 0.0`;
|
|
194
|
+
default:
|
|
195
|
+
// String and other types can be assigned directly
|
|
196
|
+
return `${propName} = value`;
|
|
197
|
+
}
|
|
198
|
+
})();
|
|
199
|
+
if (unionHasNull) {
|
|
200
|
+
const assignmentPrefix = `${propName} = `;
|
|
201
|
+
const rhs = baseSetter.startsWith(assignmentPrefix)
|
|
202
|
+
? baseSetter.slice(assignmentPrefix.length)
|
|
203
|
+
: 'value';
|
|
204
|
+
return `${propName} = value == 'null' ? null : (${rhs})`;
|
|
152
205
|
}
|
|
206
|
+
return baseSetter;
|
|
153
207
|
}
|
|
154
|
-
function generateAttributeGetter(propName, type,
|
|
208
|
+
function generateAttributeGetter(propName, type, isNullable, enumName) {
|
|
155
209
|
// Handle enum types
|
|
156
210
|
if (enumName && Array.isArray(type.value)) {
|
|
157
|
-
return
|
|
211
|
+
return isNullable ? `${propName}?.value` : `${propName}.value`;
|
|
158
212
|
}
|
|
159
213
|
// Handle nullable properties - they should return null if the value is null
|
|
160
|
-
if (
|
|
214
|
+
if (isNullable) {
|
|
161
215
|
// For nullable properties, we need to handle null values properly
|
|
162
216
|
return `${propName}?.toString()`;
|
|
163
217
|
}
|
|
164
|
-
// For non-nullable properties
|
|
218
|
+
// For non-nullable properties, always convert to string
|
|
165
219
|
return `${propName}.toString()`;
|
|
166
220
|
}
|
|
167
221
|
function generateAttributeDeleter(propName, type, optional) {
|
|
168
222
|
// When deleting an attribute, we should reset it to its default value
|
|
223
|
+
if (isBooleanType(type)) {
|
|
224
|
+
// Booleans (including unions with null) default to false
|
|
225
|
+
return `${propName} = false`;
|
|
226
|
+
}
|
|
169
227
|
switch (type.value) {
|
|
170
|
-
case declaration_1.FunctionArgumentType.boolean:
|
|
171
|
-
// Booleans default to false
|
|
172
|
-
return `${propName} = false`;
|
|
173
228
|
case declaration_1.FunctionArgumentType.int:
|
|
174
229
|
// Integers default to 0
|
|
175
230
|
return `${propName} = 0`;
|
|
@@ -201,10 +256,20 @@ function generateMethodDeclaration(method) {
|
|
|
201
256
|
return `${methodName}(${args}): ${returnType};`;
|
|
202
257
|
}
|
|
203
258
|
function shouldMakeNullable(prop) {
|
|
204
|
-
|
|
205
|
-
|
|
259
|
+
const type = prop.type;
|
|
260
|
+
// Boolean properties are only nullable in Dart when explicitly unioned with `null`.
|
|
261
|
+
if (isBooleanType(type)) {
|
|
262
|
+
return hasNullInUnion(type);
|
|
263
|
+
}
|
|
264
|
+
// Dynamic (any) should not use nullable syntax; dynamic already allows null
|
|
265
|
+
if (type.value === declaration_1.FunctionArgumentType.any) {
|
|
206
266
|
return false;
|
|
207
267
|
}
|
|
268
|
+
// Properties with an explicit `null` in their type should be nullable,
|
|
269
|
+
// even if they are not marked optional in TypeScript.
|
|
270
|
+
if (hasNullInUnion(type)) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
208
273
|
// Other optional properties remain nullable
|
|
209
274
|
return prop.optional;
|
|
210
275
|
}
|
|
@@ -287,8 +352,9 @@ interface ${object.name} {
|
|
|
287
352
|
generateAttributeSetter: (propName, type) => {
|
|
288
353
|
return generateAttributeSetter(propName, type, enumMap.get(propName));
|
|
289
354
|
},
|
|
290
|
-
generateAttributeGetter: (propName, type, optional) => {
|
|
291
|
-
|
|
355
|
+
generateAttributeGetter: (propName, type, optional, prop) => {
|
|
356
|
+
const isNullable = prop ? shouldMakeNullable(prop) : optional;
|
|
357
|
+
return generateAttributeGetter(propName, type, isNullable, enumMap.get(propName));
|
|
292
358
|
},
|
|
293
359
|
generateAttributeDeleter,
|
|
294
360
|
shouldMakeNullable,
|
package/dist/declaration.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TypeAliasObject = exports.FunctionObject = exports.ClassObject = exports.ClassObjectKind = exports.FunctionDeclaration = exports.IndexedPropertyDeclaration = exports.PropsDeclaration = exports.ParameterMode = exports.FunctionArguments = exports.FunctionArgumentType = void 0;
|
|
3
|
+
exports.EnumObject = exports.EnumMemberObject = exports.ConstObject = exports.TypeAliasObject = exports.FunctionObject = exports.ClassObject = exports.ClassObjectKind = exports.FunctionDeclaration = exports.IndexedPropertyDeclaration = exports.PropsDeclaration = exports.ParameterMode = exports.FunctionArguments = exports.FunctionArgumentType = void 0;
|
|
4
4
|
var FunctionArgumentType;
|
|
5
5
|
(function (FunctionArgumentType) {
|
|
6
6
|
// Basic types
|
|
@@ -61,3 +61,16 @@ exports.FunctionObject = FunctionObject;
|
|
|
61
61
|
class TypeAliasObject {
|
|
62
62
|
}
|
|
63
63
|
exports.TypeAliasObject = TypeAliasObject;
|
|
64
|
+
class ConstObject {
|
|
65
|
+
}
|
|
66
|
+
exports.ConstObject = ConstObject;
|
|
67
|
+
class EnumMemberObject {
|
|
68
|
+
}
|
|
69
|
+
exports.EnumMemberObject = EnumMemberObject;
|
|
70
|
+
class EnumObject {
|
|
71
|
+
constructor() {
|
|
72
|
+
this.members = [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.EnumObject = EnumObject;
|
|
76
|
+
EnumObject.globalEnumSet = new Set();
|
package/dist/generator.js
CHANGED
|
@@ -187,12 +187,17 @@ function dartGen(_a) {
|
|
|
187
187
|
if (!fs_1.default.existsSync(outputDir)) {
|
|
188
188
|
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
189
189
|
}
|
|
190
|
-
// Generate Dart file
|
|
190
|
+
// Generate Dart file (skip if empty)
|
|
191
191
|
const genFilePath = path_1.default.join(outputDir, lodash_1.default.snakeCase(blob.filename));
|
|
192
192
|
const fullPath = genFilePath + '_bindings_generated.dart';
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
if (result && result.trim().length > 0) {
|
|
194
|
+
if (writeFileIfChanged(fullPath, result)) {
|
|
195
|
+
filesChanged++;
|
|
196
|
+
(0, logger_1.debug)(`Generated: ${path_1.default.basename(fullPath)}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
(0, logger_1.debug)(`Skipped ${path_1.default.basename(fullPath)} - empty bindings`);
|
|
196
201
|
}
|
|
197
202
|
// Copy the original .d.ts file to the output directory
|
|
198
203
|
const dtsOutputPath = path_1.default.join(outputDir, blob.filename + '.d.ts');
|
|
@@ -205,13 +210,8 @@ function dartGen(_a) {
|
|
|
205
210
|
(0, logger_1.error)(`Error generating Dart code for ${blob.filename}`, err);
|
|
206
211
|
}
|
|
207
212
|
}));
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
const indexDtsPath = path_1.default.join(normalizedTarget, 'index.d.ts');
|
|
211
|
-
if (writeFileIfChanged(indexDtsPath, indexDtsContent)) {
|
|
212
|
-
filesChanged++;
|
|
213
|
-
(0, logger_1.debug)('Generated: index.d.ts');
|
|
214
|
-
}
|
|
213
|
+
// Note: We no longer generate a root index.d.ts for Dart codegen
|
|
214
|
+
// as it is not necessary for the codegen workflow.
|
|
215
215
|
(0, logger_1.timeEnd)('dartGen');
|
|
216
216
|
(0, logger_1.success)(`Dart code generation completed. ${filesChanged} files changed.`);
|
|
217
217
|
(0, logger_1.info)(`Output directory: ${normalizedTarget}`);
|
|
@@ -282,6 +282,8 @@ function reactGen(_a) {
|
|
|
282
282
|
if (writeFileIfChanged(fullPath, result)) {
|
|
283
283
|
filesChanged++;
|
|
284
284
|
(0, logger_1.debug)(`Generated: ${path_1.default.basename(fullPath)}`);
|
|
285
|
+
// Emit a short preview for debugging when WEBF_DEBUG is on
|
|
286
|
+
(0, logger_1.debug)(`Preview (${path_1.default.basename(fullPath)}):\n` + result.split('\n').slice(0, 12).join('\n'));
|
|
285
287
|
}
|
|
286
288
|
}
|
|
287
289
|
catch (err) {
|
|
@@ -290,6 +292,8 @@ function reactGen(_a) {
|
|
|
290
292
|
}));
|
|
291
293
|
// Generate/merge index file
|
|
292
294
|
const indexFilePath = path_1.default.join(normalizedTarget, 'src', 'index.ts');
|
|
295
|
+
// Always build the full index content string for downstream tooling/logging
|
|
296
|
+
const newExports = (0, react_1.generateReactIndex)(blobs);
|
|
293
297
|
// Build desired export map: moduleSpecifier -> Set of names
|
|
294
298
|
const desiredExports = new Map();
|
|
295
299
|
const components = blobs.flatMap(blob => {
|
|
@@ -321,7 +325,6 @@ function reactGen(_a) {
|
|
|
321
325
|
}
|
|
322
326
|
if (!fs_1.default.existsSync(indexFilePath)) {
|
|
323
327
|
// No index.ts -> generate fresh file from template
|
|
324
|
-
const newExports = (0, react_1.generateReactIndex)(blobs);
|
|
325
328
|
if (writeFileIfChanged(indexFilePath, newExports)) {
|
|
326
329
|
filesChanged++;
|
|
327
330
|
(0, logger_1.debug)(`Generated: index.ts`);
|
|
@@ -378,6 +381,71 @@ function reactGen(_a) {
|
|
|
378
381
|
(0, logger_1.success)(`React code generation completed. ${filesChanged} files changed.`);
|
|
379
382
|
(0, logger_1.info)(`Output directory: ${normalizedTarget}`);
|
|
380
383
|
(0, logger_1.info)('You can now import these components in your React project.');
|
|
384
|
+
// Aggregate standalone type declarations (consts/enums/type aliases) into a single types.ts
|
|
385
|
+
try {
|
|
386
|
+
const consts = blobs.flatMap(b => b.objects.filter(o => o instanceof declaration_1.ConstObject));
|
|
387
|
+
const enums = blobs.flatMap(b => b.objects.filter(o => o instanceof declaration_1.EnumObject));
|
|
388
|
+
const typeAliases = blobs.flatMap(b => b.objects.filter(o => o instanceof declaration_1.TypeAliasObject));
|
|
389
|
+
// Deduplicate by name
|
|
390
|
+
const constMap = new Map();
|
|
391
|
+
consts.forEach(c => { if (!constMap.has(c.name))
|
|
392
|
+
constMap.set(c.name, c); });
|
|
393
|
+
const typeAliasMap = new Map();
|
|
394
|
+
typeAliases.forEach(t => { if (!typeAliasMap.has(t.name))
|
|
395
|
+
typeAliasMap.set(t.name, t); });
|
|
396
|
+
const hasAny = constMap.size > 0 || enums.length > 0 || typeAliasMap.size > 0;
|
|
397
|
+
if (hasAny) {
|
|
398
|
+
const constDecl = Array.from(constMap.values())
|
|
399
|
+
.map(c => `export declare const ${c.name}: ${c.type};`)
|
|
400
|
+
.join('\n');
|
|
401
|
+
const enumDecl = enums
|
|
402
|
+
.map(e => `export enum ${e.name} { ${e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ')} }`)
|
|
403
|
+
.join('\n');
|
|
404
|
+
const typeAliasDecl = Array.from(typeAliasMap.values())
|
|
405
|
+
.map(t => `export type ${t.name} = ${t.type};`)
|
|
406
|
+
.join('\n');
|
|
407
|
+
const typesContent = [
|
|
408
|
+
'/* Generated by WebF CLI - aggregated type declarations */',
|
|
409
|
+
typeAliasDecl,
|
|
410
|
+
constDecl,
|
|
411
|
+
enumDecl,
|
|
412
|
+
''
|
|
413
|
+
].filter(Boolean).join('\n');
|
|
414
|
+
const typesPath = path_1.default.join(normalizedTarget, 'src', 'types.ts');
|
|
415
|
+
if (writeFileIfChanged(typesPath, typesContent)) {
|
|
416
|
+
filesChanged++;
|
|
417
|
+
(0, logger_1.debug)(`Generated: src/types.ts`);
|
|
418
|
+
try {
|
|
419
|
+
const constNames = Array.from(constMap.keys());
|
|
420
|
+
const aliasNames = Array.from(typeAliasMap.keys());
|
|
421
|
+
const enumNames = enums.map(e => e.name);
|
|
422
|
+
(0, logger_1.debug)(`[react] Aggregated types - consts: ${constNames.join(', ') || '(none)'}; typeAliases: ${aliasNames.join(', ') || '(none)'}; enums: ${enumNames.join(', ') || '(none)'}\n`);
|
|
423
|
+
(0, logger_1.debug)(`[react] src/types.ts preview:\n` + typesContent.split('\n').slice(0, 20).join('\n'));
|
|
424
|
+
}
|
|
425
|
+
catch (_b) { }
|
|
426
|
+
}
|
|
427
|
+
// Ensure index.ts re-exports these types so consumers get them on import.
|
|
428
|
+
const indexFilePath = path_1.default.join(normalizedTarget, 'src', 'index.ts');
|
|
429
|
+
try {
|
|
430
|
+
let current = '';
|
|
431
|
+
if (fs_1.default.existsSync(indexFilePath)) {
|
|
432
|
+
current = fs_1.default.readFileSync(indexFilePath, 'utf-8');
|
|
433
|
+
}
|
|
434
|
+
const exportLine = `export * from './types';`;
|
|
435
|
+
if (!current.includes(exportLine)) {
|
|
436
|
+
const updated = current.trim().length ? `${current.trim()}\n${exportLine}\n` : `${exportLine}\n`;
|
|
437
|
+
if (writeFileIfChanged(indexFilePath, updated)) {
|
|
438
|
+
filesChanged++;
|
|
439
|
+
(0, logger_1.debug)(`Updated: src/index.ts to export aggregated types`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
catch (_c) { }
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch (e) {
|
|
447
|
+
(0, logger_1.warn)('Failed to generate aggregated React types');
|
|
448
|
+
}
|
|
381
449
|
});
|
|
382
450
|
}
|
|
383
451
|
function vueGen(_a) {
|