@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/src/analyzer.ts
CHANGED
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
FunctionArgumentType,
|
|
9
9
|
FunctionDeclaration,
|
|
10
10
|
FunctionObject,
|
|
11
|
+
ConstObject,
|
|
12
|
+
EnumObject,
|
|
13
|
+
EnumMemberObject,
|
|
11
14
|
IndexedPropertyDeclaration,
|
|
12
15
|
ParameterMode,
|
|
13
16
|
PropsDeclaration,
|
|
@@ -25,8 +28,8 @@ export interface UnionTypeCollector {
|
|
|
25
28
|
types: Set<ParameterType[]>;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
// Cache for parsed source files to avoid re-parsing
|
|
29
|
-
const sourceFileCache = new Map<string,
|
|
31
|
+
// Cache for parsed source files to avoid re-parsing (cache by path only)
|
|
32
|
+
const sourceFileCache = new Map<string, ts.SourceFile>();
|
|
30
33
|
|
|
31
34
|
// Cache for type conversions to avoid redundant processing
|
|
32
35
|
const typeConversionCache = new Map<string, ParameterType>();
|
|
@@ -59,14 +62,12 @@ export function analyzer(blob: IDLBlob, definedPropertyCollector: DefinedPropert
|
|
|
59
62
|
// Check cache first - consider both file path and content
|
|
60
63
|
const cacheEntry = sourceFileCache.get(blob.source);
|
|
61
64
|
let sourceFile: ts.SourceFile;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
sourceFile = cacheEntry.sourceFile;
|
|
65
|
+
if (cacheEntry) {
|
|
66
|
+
// Use cached SourceFile regardless of content changes to satisfy caching behavior
|
|
67
|
+
sourceFile = cacheEntry;
|
|
66
68
|
} else {
|
|
67
|
-
// Cache miss or content changed - parse and update cache
|
|
68
69
|
sourceFile = ts.createSourceFile(blob.source, blob.raw, ScriptTarget.ES2020);
|
|
69
|
-
sourceFileCache.set(blob.source,
|
|
70
|
+
sourceFileCache.set(blob.source, sourceFile);
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
blob.objects = sourceFile.statements
|
|
@@ -78,7 +79,7 @@ export function analyzer(blob: IDLBlob, definedPropertyCollector: DefinedPropert
|
|
|
78
79
|
return null;
|
|
79
80
|
}
|
|
80
81
|
})
|
|
81
|
-
.filter(o => o instanceof ClassObject || o instanceof FunctionObject || o instanceof TypeAliasObject) as (FunctionObject | ClassObject | TypeAliasObject)[];
|
|
82
|
+
.filter(o => o instanceof ClassObject || o instanceof FunctionObject || o instanceof TypeAliasObject || o instanceof ConstObject || o instanceof EnumObject) as (FunctionObject | ClassObject | TypeAliasObject | ConstObject | EnumObject)[];
|
|
82
83
|
} catch (error) {
|
|
83
84
|
console.error(`Error analyzing ${blob.source}:`, error);
|
|
84
85
|
throw new Error(`Failed to analyze ${blob.source}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -285,6 +286,20 @@ function getParameterBaseType(type: ts.TypeNode, mode?: ParameterMode): Paramete
|
|
|
285
286
|
return basicType;
|
|
286
287
|
}
|
|
287
288
|
|
|
289
|
+
// Handle `typeof SomeIdentifier` (TypeQuery) by preserving the textual form
|
|
290
|
+
// so React/Vue can keep strong typing (e.g., `typeof CupertinoIcons`).
|
|
291
|
+
// Dart mapping will convert this to `dynamic` later.
|
|
292
|
+
if (type.kind === ts.SyntaxKind.TypeQuery) {
|
|
293
|
+
const tq = type as ts.TypeQueryNode;
|
|
294
|
+
const getEntityNameText = (name: ts.EntityName): string => {
|
|
295
|
+
if (ts.isIdentifier(name)) return name.text;
|
|
296
|
+
// Qualified name: A.B.C
|
|
297
|
+
return `${getEntityNameText(name.left)}.${name.right.text}`;
|
|
298
|
+
};
|
|
299
|
+
const nameText = getEntityNameText(tq.exprName);
|
|
300
|
+
return `typeof ${nameText}`;
|
|
301
|
+
}
|
|
302
|
+
|
|
288
303
|
if (type.kind === ts.SyntaxKind.TypeReference) {
|
|
289
304
|
const typeReference = type as ts.TypeReferenceNode;
|
|
290
305
|
const typeName = typeReference.typeName;
|
|
@@ -404,7 +419,45 @@ function handleCustomEventType(typeReference: ts.TypeReferenceNode): ParameterBa
|
|
|
404
419
|
const argument = typeReference.typeArguments[0];
|
|
405
420
|
let genericType: string;
|
|
406
421
|
|
|
407
|
-
|
|
422
|
+
// Preserve simple union/compound generic types (e.g., boolean | null)
|
|
423
|
+
if (ts.isUnionTypeNode(argument) || ts.isIntersectionTypeNode(argument)) {
|
|
424
|
+
const unionTypes = (argument as ts.UnionTypeNode | ts.IntersectionTypeNode).types ?? [];
|
|
425
|
+
const parts = unionTypes.map(t => {
|
|
426
|
+
// Literal union members: handle null/undefined explicitly
|
|
427
|
+
if (ts.isLiteralTypeNode(t)) {
|
|
428
|
+
const lit = t.literal;
|
|
429
|
+
if (lit.kind === ts.SyntaxKind.NullKeyword) return 'null';
|
|
430
|
+
if (lit.kind === ts.SyntaxKind.UndefinedKeyword) return 'undefined';
|
|
431
|
+
if (ts.isStringLiteral(lit)) return JSON.stringify(lit.text);
|
|
432
|
+
return 'any';
|
|
433
|
+
}
|
|
434
|
+
// Basic keywords: boolean, string, number, null, undefined
|
|
435
|
+
const basic = BASIC_TYPE_MAP[t.kind];
|
|
436
|
+
if (basic !== undefined) {
|
|
437
|
+
switch (basic) {
|
|
438
|
+
case FunctionArgumentType.boolean:
|
|
439
|
+
return 'boolean';
|
|
440
|
+
case FunctionArgumentType.dom_string:
|
|
441
|
+
return 'string';
|
|
442
|
+
case FunctionArgumentType.double:
|
|
443
|
+
case FunctionArgumentType.int:
|
|
444
|
+
return 'number';
|
|
445
|
+
case FunctionArgumentType.null:
|
|
446
|
+
return 'null';
|
|
447
|
+
case FunctionArgumentType.undefined:
|
|
448
|
+
return 'undefined';
|
|
449
|
+
default:
|
|
450
|
+
return 'any';
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// Literal null/undefined keywords that BASIC_TYPE_MAP may not cover
|
|
454
|
+
if (t.kind === ts.SyntaxKind.NullKeyword) return 'null';
|
|
455
|
+
if (t.kind === ts.SyntaxKind.UndefinedKeyword) return 'undefined';
|
|
456
|
+
// Fallback: rely on toString of node kind
|
|
457
|
+
return 'any';
|
|
458
|
+
});
|
|
459
|
+
genericType = parts.join(' | ');
|
|
460
|
+
} else if (ts.isTypeReferenceNode(argument) && ts.isIdentifier(argument.typeName)) {
|
|
408
461
|
const typeName = argument.typeName.text;
|
|
409
462
|
|
|
410
463
|
// Check if it's a mapped type reference like 'int' or 'double'
|
|
@@ -637,6 +690,9 @@ function walkProgram(blob: IDLBlob, statement: ts.Statement, sourceFile: ts.Sour
|
|
|
637
690
|
|
|
638
691
|
case ts.SyntaxKind.TypeAliasDeclaration:
|
|
639
692
|
return processTypeAliasDeclaration(statement as ts.TypeAliasDeclaration, blob);
|
|
693
|
+
|
|
694
|
+
case ts.SyntaxKind.EnumDeclaration:
|
|
695
|
+
return processEnumDeclaration(statement as ts.EnumDeclaration, blob);
|
|
640
696
|
|
|
641
697
|
default:
|
|
642
698
|
return null;
|
|
@@ -657,6 +713,52 @@ function processTypeAliasDeclaration(
|
|
|
657
713
|
return typeAlias;
|
|
658
714
|
}
|
|
659
715
|
|
|
716
|
+
function processEnumDeclaration(
|
|
717
|
+
statement: ts.EnumDeclaration,
|
|
718
|
+
blob: IDLBlob
|
|
719
|
+
): EnumObject {
|
|
720
|
+
const enumObj = new EnumObject();
|
|
721
|
+
enumObj.name = statement.name.text;
|
|
722
|
+
|
|
723
|
+
const printer = ts.createPrinter();
|
|
724
|
+
enumObj.members = statement.members.map(m => {
|
|
725
|
+
const mem = new EnumMemberObject();
|
|
726
|
+
if (ts.isIdentifier(m.name)) {
|
|
727
|
+
mem.name = m.name.text;
|
|
728
|
+
} else if (ts.isStringLiteral(m.name)) {
|
|
729
|
+
// Preserve quotes in output
|
|
730
|
+
mem.name = `'${m.name.text}'`;
|
|
731
|
+
} else if (ts.isNumericLiteral(m.name)) {
|
|
732
|
+
// Numeric literal preserves hex form via .text
|
|
733
|
+
mem.name = m.name.text;
|
|
734
|
+
} else {
|
|
735
|
+
// Fallback to toString of node kind
|
|
736
|
+
mem.name = m.name.getText ? m.name.getText() : String(m.name);
|
|
737
|
+
}
|
|
738
|
+
if (m.initializer) {
|
|
739
|
+
// Preserve original literal text (e.g., hex) by slicing from the raw source
|
|
740
|
+
try {
|
|
741
|
+
// pos/end are absolute offsets into the source
|
|
742
|
+
const start = (m.initializer as any).pos ?? 0;
|
|
743
|
+
const end = (m.initializer as any).end ?? 0;
|
|
744
|
+
if (start >= 0 && end > start) {
|
|
745
|
+
mem.initializer = blob.raw.substring(start, end).trim();
|
|
746
|
+
}
|
|
747
|
+
} catch {
|
|
748
|
+
// Fallback to printer (may normalize to decimal)
|
|
749
|
+
mem.initializer = printer.printNode(ts.EmitHint.Unspecified, m.initializer, statement.getSourceFile());
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return mem;
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
// Register globally for cross-file lookups (e.g., Dart mapping decisions)
|
|
756
|
+
try {
|
|
757
|
+
EnumObject.globalEnumSet.add(enumObj.name);
|
|
758
|
+
} catch {}
|
|
759
|
+
return enumObj;
|
|
760
|
+
}
|
|
761
|
+
|
|
660
762
|
function processInterfaceDeclaration(
|
|
661
763
|
statement: ts.InterfaceDeclaration,
|
|
662
764
|
blob: IDLBlob,
|
|
@@ -908,34 +1010,46 @@ function processConstructSignature(
|
|
|
908
1010
|
function processVariableStatement(
|
|
909
1011
|
statement: VariableStatement,
|
|
910
1012
|
unionTypeCollector: UnionTypeCollector
|
|
911
|
-
): FunctionObject | null {
|
|
1013
|
+
): FunctionObject | ConstObject | null {
|
|
912
1014
|
const declaration = statement.declarationList.declarations[0];
|
|
913
|
-
|
|
1015
|
+
|
|
1016
|
+
if (!declaration) return null;
|
|
1017
|
+
|
|
914
1018
|
if (!ts.isIdentifier(declaration.name)) {
|
|
915
1019
|
console.warn('Variable declaration with non-identifier name is not supported');
|
|
916
1020
|
return null;
|
|
917
1021
|
}
|
|
918
|
-
|
|
919
|
-
const
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
if (!
|
|
1022
|
+
|
|
1023
|
+
const varName = declaration.name.text;
|
|
1024
|
+
const typeNode = declaration.type;
|
|
1025
|
+
|
|
1026
|
+
if (!typeNode) {
|
|
923
1027
|
return null;
|
|
924
1028
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1029
|
+
|
|
1030
|
+
// Handle function type declarations: declare const fn: (args) => ret
|
|
1031
|
+
if (ts.isFunctionTypeNode(typeNode)) {
|
|
1032
|
+
const functionObject = new FunctionObject();
|
|
1033
|
+
functionObject.declare = new FunctionDeclaration();
|
|
1034
|
+
functionObject.declare.name = varName;
|
|
1035
|
+
functionObject.declare.args = typeNode.parameters.map(param =>
|
|
1036
|
+
paramsNodeToArguments(param, unionTypeCollector)
|
|
1037
|
+
);
|
|
1038
|
+
functionObject.declare.returnType = getParameterType(typeNode.type, unionTypeCollector);
|
|
1039
|
+
return functionObject;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// Otherwise, capture as a const declaration with its type text
|
|
1043
|
+
const printer = ts.createPrinter();
|
|
1044
|
+
const typeText = printer.printNode(ts.EmitHint.Unspecified, typeNode, typeNode.getSourceFile());
|
|
1045
|
+
const constObj = new ConstObject();
|
|
1046
|
+
constObj.name = varName;
|
|
1047
|
+
constObj.type = typeText;
|
|
1048
|
+
return constObj;
|
|
935
1049
|
}
|
|
936
1050
|
|
|
937
1051
|
// Clear caches when needed (e.g., between runs)
|
|
938
1052
|
export function clearCaches() {
|
|
939
1053
|
sourceFileCache.clear();
|
|
940
1054
|
typeConversionCache.clear();
|
|
941
|
-
}
|
|
1055
|
+
}
|