@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/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, { content: string; sourceFile: ts.SourceFile }>();
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
- if (cacheEntry && cacheEntry.content === blob.raw) {
64
- // Cache hit with same content
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, { content: blob.raw, sourceFile });
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
- if (ts.isTypeReferenceNode(argument) && ts.isIdentifier(argument.typeName)) {
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 methodName = declaration.name.text;
920
- const type = declaration.type;
921
-
922
- if (!type || !ts.isFunctionTypeNode(type)) {
1022
+
1023
+ const varName = declaration.name.text;
1024
+ const typeNode = declaration.type;
1025
+
1026
+ if (!typeNode) {
923
1027
  return null;
924
1028
  }
925
-
926
- const functionObject = new FunctionObject();
927
- functionObject.declare = new FunctionDeclaration();
928
- functionObject.declare.name = methodName;
929
- functionObject.declare.args = type.parameters.map(param =>
930
- paramsNodeToArguments(param, unionTypeCollector)
931
- );
932
- functionObject.declare.returnType = getParameterType(type.type, unionTypeCollector);
933
-
934
- return functionObject;
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
+ }