@openwebf/webf 0.23.2 → 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/bin/webf.js +1 -0
- package/dist/analyzer.js +65 -1
- package/dist/commands.js +198 -98
- package/dist/dart.js +91 -25
- package/dist/declaration.js +1 -0
- package/dist/generator.js +26 -17
- package/dist/react.js +272 -25
- package/dist/vue.js +74 -11
- package/package.json +1 -1
- package/src/analyzer.ts +58 -2
- package/src/commands.ts +300 -196
- package/src/dart.ts +95 -20
- package/src/declaration.ts +1 -0
- package/src/generator.ts +24 -16
- package/src/react.ts +288 -29
- package/src/vue.ts +85 -13
- package/templates/class.dart.tpl +1 -1
- package/templates/vue.components.d.ts.tpl +2 -0
- package/test/commands.test.ts +82 -2
- package/test/dart-nullable-props.test.ts +58 -0
- 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/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
package/dist/generator.js
CHANGED
|
@@ -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) {
|
|
@@ -379,7 +381,7 @@ function reactGen(_a) {
|
|
|
379
381
|
(0, logger_1.success)(`React code generation completed. ${filesChanged} files changed.`);
|
|
380
382
|
(0, logger_1.info)(`Output directory: ${normalizedTarget}`);
|
|
381
383
|
(0, logger_1.info)('You can now import these components in your React project.');
|
|
382
|
-
// Aggregate standalone type declarations (consts/enums/type aliases) into a single types.
|
|
384
|
+
// Aggregate standalone type declarations (consts/enums/type aliases) into a single types.ts
|
|
383
385
|
try {
|
|
384
386
|
const consts = blobs.flatMap(b => b.objects.filter(o => o instanceof declaration_1.ConstObject));
|
|
385
387
|
const enums = blobs.flatMap(b => b.objects.filter(o => o instanceof declaration_1.EnumObject));
|
|
@@ -397,7 +399,7 @@ function reactGen(_a) {
|
|
|
397
399
|
.map(c => `export declare const ${c.name}: ${c.type};`)
|
|
398
400
|
.join('\n');
|
|
399
401
|
const enumDecl = enums
|
|
400
|
-
.map(e => `export
|
|
402
|
+
.map(e => `export enum ${e.name} { ${e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ')} }`)
|
|
401
403
|
.join('\n');
|
|
402
404
|
const typeAliasDecl = Array.from(typeAliasMap.values())
|
|
403
405
|
.map(t => `export type ${t.name} = ${t.type};`)
|
|
@@ -409,33 +411,40 @@ function reactGen(_a) {
|
|
|
409
411
|
enumDecl,
|
|
410
412
|
''
|
|
411
413
|
].filter(Boolean).join('\n');
|
|
412
|
-
const typesPath = path_1.default.join(normalizedTarget, 'src', 'types.
|
|
414
|
+
const typesPath = path_1.default.join(normalizedTarget, 'src', 'types.ts');
|
|
413
415
|
if (writeFileIfChanged(typesPath, typesContent)) {
|
|
414
416
|
filesChanged++;
|
|
415
|
-
(0, logger_1.debug)(`Generated: src/types.
|
|
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) { }
|
|
416
426
|
}
|
|
417
|
-
//
|
|
418
|
-
// This avoids bundler resolution errors from importing a .d.ts file.
|
|
427
|
+
// Ensure index.ts re-exports these types so consumers get them on import.
|
|
419
428
|
const indexFilePath = path_1.default.join(normalizedTarget, 'src', 'index.ts');
|
|
420
429
|
try {
|
|
430
|
+
let current = '';
|
|
421
431
|
if (fs_1.default.existsSync(indexFilePath)) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
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`);
|
|
431
440
|
}
|
|
432
441
|
}
|
|
433
442
|
}
|
|
434
|
-
catch (
|
|
443
|
+
catch (_c) { }
|
|
435
444
|
}
|
|
436
445
|
}
|
|
437
446
|
catch (e) {
|
|
438
|
-
(0, logger_1.warn)('Failed to generate aggregated React types
|
|
447
|
+
(0, logger_1.warn)('Failed to generate aggregated React types');
|
|
439
448
|
}
|
|
440
449
|
});
|
|
441
450
|
}
|
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
|
'',
|