From a30c9bed01e0d27f4c5797cbf9e8f17c2dbe112b Mon Sep 17 00:00:00 2001 From: Nike Okoronkwo Date: Tue, 15 Jul 2025 18:36:57 -0400 Subject: [PATCH 1/4] Interop Gen: Support `typeof` type declarations Fixes #383 --- web_generator/lib/src/ast/declarations.dart | 15 ++++++++ web_generator/lib/src/ast/types.dart | 19 ++++++++++ .../interop_gen/transform/transformer.dart | 31 ++++++++++++++++ .../lib/src/js/typescript.types.dart | 16 +++++++++ .../interop_gen/ts_typing_expected.dart | 36 +++++++++++++++++++ .../interop_gen/ts_typing_input.d.ts | 17 +++++++++ 6 files changed, 134 insertions(+) create mode 100644 web_generator/test/integration/interop_gen/ts_typing_expected.dart create mode 100644 web_generator/test/integration/interop_gen/ts_typing_input.d.ts diff --git a/web_generator/lib/src/ast/declarations.dart b/web_generator/lib/src/ast/declarations.dart index a57a129a..a7d2770f 100644 --- a/web_generator/lib/src/ast/declarations.dart +++ b/web_generator/lib/src/ast/declarations.dart @@ -6,6 +6,7 @@ import 'package:code_builder/code_builder.dart'; import '../interop_gen/namer.dart'; import 'base.dart'; +import 'builtin.dart'; import 'helpers.dart'; import 'types.dart'; @@ -52,6 +53,12 @@ class VariableDeclaration extends NamedDeclaration @override String? get dartName => null; + + @override + ReferredType asReferredType([List? typeArgs]) { + return ReferredType.fromType(type, this, + typeParams: typeArgs ?? []); + } } enum VariableModifier { let, $const, $var } @@ -115,6 +122,14 @@ class FunctionDeclaration extends NamedDeclaration ..requiredParameters.addAll(requiredParams) ..optionalParameters.addAll(optionalParams)); } + + @override + ReferredType asReferredType([List? typeArgs]) { + // TODO: We could do better here and make the function type typed + return ReferredType.fromType( + BuiltinType.referred('Function', typeParams: typeArgs ?? [])!, this, + typeParams: typeArgs ?? []); + } } class ParameterDeclaration { diff --git a/web_generator/lib/src/ast/types.dart b/web_generator/lib/src/ast/types.dart index 7ba0e7fe..e0358da6 100644 --- a/web_generator/lib/src/ast/types.dart +++ b/web_generator/lib/src/ast/types.dart @@ -8,6 +8,7 @@ import 'base.dart'; import 'builtin.dart'; import 'declarations.dart'; +/// A type referring to a type in the TypeScript AST class ReferredType extends Type { @override String name; @@ -24,6 +25,9 @@ class ReferredType extends Type { required this.declaration, this.typeParams = const []}); + factory ReferredType.fromType(Type type, T declaration, + {List typeParams}) = ReferredDeclarationType; + @override Reference emit([TypeOptions? options]) { // TODO: Support referred types imported from URL @@ -34,6 +38,21 @@ class ReferredType extends Type { } } +class ReferredDeclarationType extends ReferredType { + Type type; + + @override + String get name => type.name ?? declaration.name; + + ReferredDeclarationType(this.type, T declaration, {super.typeParams}) + : super(name: declaration.name, declaration: declaration); + + @override + Reference emit([covariant TypeOptions? options]) { + return type.emit(options); + } +} + // TODO(https://github.com/dart-lang/web/issues/385): Implement Support for UnionType (including implementing `emit`) class UnionType extends Type { final List types; diff --git a/web_generator/lib/src/interop_gen/transform/transformer.dart b/web_generator/lib/src/interop_gen/transform/transformer.dart index 4a8ba4a5..eb803ca1 100644 --- a/web_generator/lib/src/interop_gen/transform/transformer.dart +++ b/web_generator/lib/src/interop_gen/transform/transformer.dart @@ -419,6 +419,37 @@ class Transformer { _ => throw UnimplementedError( 'Unsupported Literal Kind ${literal.kind}') }); + case TSSyntaxKind.TypeQuery: + final typeQuery = type as TSTypeQueryNode; + + // TODO(nikeokoronkwo): Refactor this once #402 lands, https://github.com/dart-lang/web/pull/415 + final exprName = typeQuery.exprName; + final name = exprName.text; + final typeArguments = typeQuery.typeArguments?.toDart; + + var declarationsMatching = nodeMap.findByName(name); + if (declarationsMatching.isEmpty) { + final symbol = typeChecker.getSymbolAtLocation(exprName); + final declarations = symbol?.getDeclarations(); + final declaration = declarations?.toDart.first; + + if (declaration == null) { + throw Exception('Found no declaration matching $name'); + } + + transform(declaration); + + declarationsMatching = nodeMap.findByName(name); + } + + final firstNode = + declarationsMatching.whereType().first; + + return firstNode.asReferredType( + (typeArguments ?? []) + .map((type) => _transformType(type, typeArg: true)) + .toList(), + ); case TSSyntaxKind.ArrayType: return BuiltinType.primitiveType(PrimitiveType.array, typeParams: [ getJSTypeAlternative( diff --git a/web_generator/lib/src/js/typescript.types.dart b/web_generator/lib/src/js/typescript.types.dart index c4acbd03..d047a66c 100644 --- a/web_generator/lib/src/js/typescript.types.dart +++ b/web_generator/lib/src/js/typescript.types.dart @@ -59,6 +59,7 @@ extension type const TSSyntaxKind._(num _) { static const TSSyntaxKind TypeReference = TSSyntaxKind._(183); static const TSSyntaxKind ArrayType = TSSyntaxKind._(188); static const TSSyntaxKind LiteralType = TSSyntaxKind._(201); + static const TSSyntaxKind TypeQuery = TSSyntaxKind._(186); /// Other static const TSSyntaxKind Identifier = TSSyntaxKind._(80); @@ -102,6 +103,21 @@ extension type TSUnionTypeNode._(JSObject _) implements TSTypeNode { external TSNodeArray get types; } +// TODO(nikeokoronkwo): Implements TSNodeWithTypeArguments +// once #402 and #409 are closed +@JS('TypeQueryNode') +extension type TSTypeQueryNode._(JSObject _) implements TSTypeNode { + @redeclare + TSSyntaxKind get kind => TSSyntaxKind.TypeQuery; + + // TODO(nikeokoronkwo): Change to EntityName to support + // qualified names, https://github.com/dart-lang/web/issues/416 + external TSIdentifier get exprName; + external TSNodeArray? get typeArguments; +} + +// TODO(nikeokoronkwo): Implements TSNodeWithTypeArguments +// once #402 and #409 are closed @JS('TypeReferenceNode') extension type TSTypeReferenceNode._(JSObject _) implements TSTypeNode { @redeclare diff --git a/web_generator/test/integration/interop_gen/ts_typing_expected.dart b/web_generator/test/integration/interop_gen/ts_typing_expected.dart new file mode 100644 index 00000000..4acebd3a --- /dev/null +++ b/web_generator/test/integration/interop_gen/ts_typing_expected.dart @@ -0,0 +1,36 @@ +// ignore_for_file: constant_identifier_names, non_constant_identifier_names + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:js_interop' as _i1; + +@_i1.JS() +external String get myString; +@_i1.JS() +external _i1.JSArray<_i1.JSNumber> get myNumberArray; +@_i1.JS() +external String get myCloneString; +@_i1.JS() +external _i1.JSArray<_i1.JSArray<_i1.JSNumber>> get muCloneNumberArray; +extension type const MyEnum._(int _) { + static const MyEnum A = MyEnum._(0); + + static const MyEnum B = MyEnum._(1); + + static const MyEnum C = MyEnum._(2); + + static const MyEnum D = MyEnum._(4); +} +@_i1.JS() +external MyEnum get myEnumValue; +@_i1.JS() +external MyEnum get myEnumValue2; +@_i1.JS() +external String myFunction(String param); +@_i1.JS() +external _i1.JSFunction myFunctionAlias; +@_i1.JS() +external _i1.JSFunction myFunctionAlias2; +@_i1.JS() +external String myEnclosingFunction(_i1.JSFunction func); +@_i1.JS() +external _i1.JSFunction get myEnclosingFunctionAlias; diff --git a/web_generator/test/integration/interop_gen/ts_typing_input.d.ts b/web_generator/test/integration/interop_gen/ts_typing_input.d.ts new file mode 100644 index 00000000..b2d7b677 --- /dev/null +++ b/web_generator/test/integration/interop_gen/ts_typing_input.d.ts @@ -0,0 +1,17 @@ +export declare const myString: string; +export declare const myNumberArray: number[]; +export declare const myCloneString: typeof myString; +export declare const muCloneNumberArray: typeof myNumberArray[]; +export declare enum MyEnum { + A = 0, + B = 1, + C = 2, + D = 4 +} +export declare const myEnumValue: MyEnum; +export declare const myEnumValue2: typeof MyEnum; +export declare function myFunction(param: string): string; +export declare let myFunctionAlias: typeof myFunction; +export declare let myFunctionAlias2: typeof myFunctionAlias; +export declare function myEnclosingFunction(func: typeof myFunction): string; +export declare const myEnclosingFunctionAlias: typeof myEnclosingFunction; \ No newline at end of file From af6e58fcad0407315cd4bf1caa2a28abe5c9b4e7 Mon Sep 17 00:00:00 2001 From: Nike Okoronkwo Date: Thu, 17 Jul 2025 21:50:33 -0400 Subject: [PATCH 2/4] updated examples and code transformation --- .../interop_gen/transform/transformer.dart | 101 ++++++++---------- .../interop_gen/ts_typing_expected.dart | 20 +++- .../interop_gen/ts_typing_input.d.ts | 12 ++- .../interop_gen/web_types_expected.dart | 17 +++ .../interop_gen/web_types_input.d.ts | 14 +++ 5 files changed, 102 insertions(+), 62 deletions(-) diff --git a/web_generator/lib/src/interop_gen/transform/transformer.dart b/web_generator/lib/src/interop_gen/transform/transformer.dart index 3cf53077..a8484112 100644 --- a/web_generator/lib/src/interop_gen/transform/transformer.dart +++ b/web_generator/lib/src/interop_gen/transform/transformer.dart @@ -71,7 +71,6 @@ class Transformer { _transformClassOrInterface(node as TSObjectDeclaration), _ => throw Exception('Unsupported Declaration Kind: ${node.kind}') }; - // ignore: dead_code This line will not be dead in future decl additions nodeMap.add(decl); } @@ -697,8 +696,6 @@ class Transformer { } /// Parses the type - /// - /// TODO(https://github.com/dart-lang/web/issues/383): Add support for `typeof` types Type _transformType(TSTypeNode type, {bool parameter = false, bool typeArg = false}) { switch (type.kind) { @@ -802,29 +799,8 @@ class Transformer { final name = exprName.text; final typeArguments = typeQuery.typeArguments?.toDart; - var declarationsMatching = nodeMap.findByName(name); - if (declarationsMatching.isEmpty) { - final symbol = typeChecker.getSymbolAtLocation(exprName); - final declarations = symbol?.getDeclarations(); - final declaration = declarations?.toDart.first; - - if (declaration == null) { - throw Exception('Found no declaration matching $name'); - } - - transform(declaration); - - declarationsMatching = nodeMap.findByName(name); - } - - final firstNode = - declarationsMatching.whereType().first; - - return firstNode.asReferredType( - (typeArguments ?? []) - .map((type) => _transformType(type, typeArg: true)) - .toList(), - ); + return _getTypeFromDeclaration( + exprName, typeArguments, typeArg: typeArg, declarationOnly: true); case TSSyntaxKind.ArrayType: return BuiltinType.primitiveType(PrimitiveType.array, typeParams: [ getJSTypeAlternative( @@ -871,7 +847,7 @@ class Transformer { Type _getTypeFromDeclaration( TSIdentifier typeName, List? typeArguments, - {bool typeArg = false}) { + {bool typeArg = false, bool declarationOnly = false}) { final name = typeName.text; var declarationsMatching = nodeMap.findByName(name); @@ -880,12 +856,14 @@ class Transformer { // TODO(https://github.com/dart-lang/web/issues/380): A better name // for this, and adding support for "supported declarations" // (also a better name for that) - final supportedType = BuiltinType.referred(name, + if (!declarationOnly) { + final supportedType = BuiltinType.referred(name, typeParams: (typeArguments ?? []) .map((t) => getJSTypeAlternative(_transformType(t))) .toList()); - if (supportedType case final resultType?) { - return resultType; + if (supportedType case final resultType?) { + return resultType; + } } final symbol = typeChecker.getSymbolAtLocation(typeName); @@ -901,22 +879,23 @@ class Transformer { throw Exception('Found no declaration matching $name'); } - // check if this is from dom - final declarationSource = declaration.getSourceFile().fileName; - if (p.basename(declarationSource) == 'lib.dom.d.ts' || - declarationSource.contains('dom')) { - // dom declaration: supported by package:web - // TODO(nikeokoronkwo): It is possible that we may get a type - // that isn't in `package:web` - return PackageWebType.parse(name, - typeParams: (typeArguments ?? []) - .map(_transformType) - .map(getJSTypeAlternative) - .toList()); - } + if (!declarationOnly) { + // check if this is from dom + final declarationSource = declaration.getSourceFile().fileName; + if (p.basename(declarationSource) == 'lib.dom.d.ts' || + declarationSource.contains('dom')) { + // dom declaration: supported by package:web + // TODO(nikeokoronkwo): It is possible that we may get a type + // that isn't in `package:web` + return PackageWebType.parse(name, + typeParams: typeArguments + ?.map((t) => getJSTypeAlternative(_transformType(t))) + .toList() ?? []); + } - if (declaration.kind == TSSyntaxKind.TypeParameter) { - return GenericType(name: name); + if (declaration.kind == TSSyntaxKind.TypeParameter) { + return GenericType(name: name); + } } transform(declaration); @@ -927,20 +906,29 @@ class Transformer { // TODO: In the case of overloading, should/shouldn't we handle more than one declaration? final firstNode = declarationsMatching.whereType().first; - // For Typealiases, we can either return the type itself - // or the JS Alternative (if its underlying type isn't a JS type) - switch (firstNode) { - case TypeAliasDeclaration(type: final t): - case EnumDeclaration(baseType: final t): - final jsType = getJSTypeAlternative(t); - if (jsType != t && typeArg) return jsType; + if (!declarationOnly) { + // For Typealiases, we can either return the type itself + // or the JS Alternative (if its underlying type isn't a JS type) + switch (firstNode) { + case TypeAliasDeclaration(type: final t): + case EnumDeclaration(baseType: final t): + final jsType = getJSTypeAlternative(t); + if (jsType != t && typeArg) return jsType; + } } - return firstNode.asReferredType( + final asReferredType = firstNode.asReferredType( (typeArguments ?? []) .map((type) => _transformType(type, typeArg: true)) .toList(), ); + + if (asReferredType case ReferredDeclarationType(type: final type) when type is BuiltinType) { + final jsType = getJSTypeAlternative(type); + if (jsType != type && typeArg) asReferredType.type = jsType; + } + + return asReferredType; } NodeMap filter() { @@ -1057,8 +1045,11 @@ class Transformer { t.id.toString(): t }); break; - case final BuiltinType _: - // primitive types are generated by default + case BuiltinType(typeParams: final typeParams) when typeParams.isNotEmpty: + filteredDeclarations.addAll({ + for (final t in typeParams.where((t) => t is! BuiltinType)) + t.id.toString() : t + }); break; case final ReferredType r: filteredDeclarations.add(r.declaration); diff --git a/web_generator/test/integration/interop_gen/ts_typing_expected.dart b/web_generator/test/integration/interop_gen/ts_typing_expected.dart index 4acebd3a..c4c96786 100644 --- a/web_generator/test/integration/interop_gen/ts_typing_expected.dart +++ b/web_generator/test/integration/interop_gen/ts_typing_expected.dart @@ -10,7 +10,7 @@ external _i1.JSArray<_i1.JSNumber> get myNumberArray; @_i1.JS() external String get myCloneString; @_i1.JS() -external _i1.JSArray<_i1.JSArray<_i1.JSNumber>> get muCloneNumberArray; +external _i1.JSArray<_i1.JSArray<_i1.JSNumber>> get myCloneNumberArray; extension type const MyEnum._(int _) { static const MyEnum A = MyEnum._(0); @@ -21,16 +21,26 @@ extension type const MyEnum._(int _) { static const MyEnum D = MyEnum._(4); } @_i1.JS() +external String myFunction(String param); +@_i1.JS() +external String myEnclosingFunction(_i1.JSFunction func); +@_i1.JS() +external _i1.JSFunction copyOfmyEnclosingFunction; +@_i1.JS() external MyEnum get myEnumValue; @_i1.JS() external MyEnum get myEnumValue2; @_i1.JS() -external String myFunction(String param); -@_i1.JS() external _i1.JSFunction myFunctionAlias; @_i1.JS() external _i1.JSFunction myFunctionAlias2; @_i1.JS() -external String myEnclosingFunction(_i1.JSFunction func); -@_i1.JS() external _i1.JSFunction get myEnclosingFunctionAlias; +@_i1.JS() +external ComposedType get myComposedType; +@_i1.JS() +external ComposedType<_i1.JSString> get myComposedMyString; +extension type ComposedType._(_i1.JSObject _) + implements _i1.JSObject { + external T enclosed; +} diff --git a/web_generator/test/integration/interop_gen/ts_typing_input.d.ts b/web_generator/test/integration/interop_gen/ts_typing_input.d.ts index b2d7b677..e665d780 100644 --- a/web_generator/test/integration/interop_gen/ts_typing_input.d.ts +++ b/web_generator/test/integration/interop_gen/ts_typing_input.d.ts @@ -1,17 +1,25 @@ export declare const myString: string; export declare const myNumberArray: number[]; export declare const myCloneString: typeof myString; -export declare const muCloneNumberArray: typeof myNumberArray[]; +export declare const myCloneNumberArray: typeof myNumberArray[]; export declare enum MyEnum { A = 0, B = 1, C = 2, D = 4 } +interface ComposedType { + enclosed: T; +} +export declare let copyOfmyEnclosingFunction: typeof myEnclosingFunction; export declare const myEnumValue: MyEnum; export declare const myEnumValue2: typeof MyEnum; export declare function myFunction(param: string): string; export declare let myFunctionAlias: typeof myFunction; export declare let myFunctionAlias2: typeof myFunctionAlias; +/** @todo [@nikeokoronkwo] support var declarations as well as var statements */ +// export declare let myPreClone: typeof myComposedType; export declare function myEnclosingFunction(func: typeof myFunction): string; -export declare const myEnclosingFunctionAlias: typeof myEnclosingFunction; \ No newline at end of file +export declare const myEnclosingFunctionAlias: typeof myEnclosingFunction; +export declare const myComposedType: ComposedType; +export declare const myComposedMyString: ComposedType; diff --git a/web_generator/test/integration/interop_gen/web_types_expected.dart b/web_generator/test/integration/interop_gen/web_types_expected.dart index afd1db61..95cc26a6 100644 --- a/web_generator/test/integration/interop_gen/web_types_expected.dart +++ b/web_generator/test/integration/interop_gen/web_types_expected.dart @@ -25,3 +25,20 @@ external _i2.HTMLDivElement get output; external void handleButtonClick(_i2.MouseEvent event); @_i1.JS() external void handleInputChange(_i2.Event event); +@_i1.JS() +external _i1.JSAny? transformElements( + _i1.JSArray<_i2.HTMLElement> el, + HTMLTransformFunc<_i2.HTMLElement, _i2.HTMLElement> transformer, +); +@_i1.JS() +external _i1.JSAny? handleEvents( + _i2.Event event, + _i1.JSArray onCallbacks, +); +extension type HTMLTransformFunc._(_i1.JSObject _) implements _i1.JSObject { + external R call(T element); +} +extension type EventManipulationFunc._(_i1.JSObject _) implements _i1.JSObject { + external _i1.JSAny? call(_i2.Event event); +} diff --git a/web_generator/test/integration/interop_gen/web_types_input.d.ts b/web_generator/test/integration/interop_gen/web_types_input.d.ts index 7e1a497d..366cf538 100644 --- a/web_generator/test/integration/interop_gen/web_types_input.d.ts +++ b/web_generator/test/integration/interop_gen/web_types_input.d.ts @@ -1,3 +1,15 @@ +interface HTMLTransformFunc { + (element: T): R; +} +interface EventManipulationFunc { + (event: Event): any; +} +interface ElementStamp { + readonly target: T; + readonly stampedAt: Date; + id: string; + stampType: "emit" | "none"; +} export declare const myCustomEvent: CustomEvent; export declare var myShadowRoot: ShadowRoot; declare let myURL: URL; @@ -10,3 +22,5 @@ declare const input: HTMLInputElement; export declare const output: HTMLDivElement; export declare function handleButtonClick(event: MouseEvent): void; export declare function handleInputChange(event: Event): void; +export declare function transformElements(el: HTMLElement[], transformer: HTMLTransformFunc); +export declare function handleEvents(event: Event, onCallbacks: EventManipulationFunc[]): any; From c2bc8db8eb899949d9d098c5880d6fc2c8c1d29c Mon Sep 17 00:00:00 2001 From: Nike Okoronkwo Date: Thu, 17 Jul 2025 21:52:35 -0400 Subject: [PATCH 3/4] formatting --- .../interop_gen/transform/transformer.dart | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/web_generator/lib/src/interop_gen/transform/transformer.dart b/web_generator/lib/src/interop_gen/transform/transformer.dart index a8484112..ea054065 100644 --- a/web_generator/lib/src/interop_gen/transform/transformer.dart +++ b/web_generator/lib/src/interop_gen/transform/transformer.dart @@ -796,11 +796,10 @@ class Transformer { // TODO(nikeokoronkwo): Refactor this once #402 lands, https://github.com/dart-lang/web/pull/415 final exprName = typeQuery.exprName; - final name = exprName.text; final typeArguments = typeQuery.typeArguments?.toDart; - return _getTypeFromDeclaration( - exprName, typeArguments, typeArg: typeArg, declarationOnly: true); + return _getTypeFromDeclaration(exprName, typeArguments, + typeArg: typeArg, declarationOnly: true); case TSSyntaxKind.ArrayType: return BuiltinType.primitiveType(PrimitiveType.array, typeParams: [ getJSTypeAlternative( @@ -858,9 +857,9 @@ class Transformer { // (also a better name for that) if (!declarationOnly) { final supportedType = BuiltinType.referred(name, - typeParams: (typeArguments ?? []) - .map((t) => getJSTypeAlternative(_transformType(t))) - .toList()); + typeParams: (typeArguments ?? []) + .map((t) => getJSTypeAlternative(_transformType(t))) + .toList()); if (supportedType case final resultType?) { return resultType; } @@ -889,8 +888,9 @@ class Transformer { // that isn't in `package:web` return PackageWebType.parse(name, typeParams: typeArguments - ?.map((t) => getJSTypeAlternative(_transformType(t))) - .toList() ?? []); + ?.map((t) => getJSTypeAlternative(_transformType(t))) + .toList() ?? + []); } if (declaration.kind == TSSyntaxKind.TypeParameter) { @@ -922,12 +922,13 @@ class Transformer { .map((type) => _transformType(type, typeArg: true)) .toList(), ); - - if (asReferredType case ReferredDeclarationType(type: final type) when type is BuiltinType) { + + if (asReferredType case ReferredDeclarationType(type: final type) + when type is BuiltinType) { final jsType = getJSTypeAlternative(type); if (jsType != type && typeArg) asReferredType.type = jsType; } - + return asReferredType; } @@ -1048,7 +1049,7 @@ class Transformer { case BuiltinType(typeParams: final typeParams) when typeParams.isNotEmpty: filteredDeclarations.addAll({ for (final t in typeParams.where((t) => t is! BuiltinType)) - t.id.toString() : t + t.id.toString(): t }); break; case final ReferredType r: From dd21c2c1c078d90ebbebfb64ab2787c50c8a7f69 Mon Sep 17 00:00:00 2001 From: Nike Okoronkwo Date: Fri, 18 Jul 2025 15:17:36 -0400 Subject: [PATCH 4/4] added documentation --- .../lib/src/interop_gen/transform.dart | 2 +- .../interop_gen/transform/transformer.dart | 54 ++++++++++++++++--- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/web_generator/lib/src/interop_gen/transform.dart b/web_generator/lib/src/interop_gen/transform.dart index 48831a41..0290c4f5 100644 --- a/web_generator/lib/src/interop_gen/transform.dart +++ b/web_generator/lib/src/interop_gen/transform.dart @@ -115,7 +115,7 @@ void transformFile(ts.TSProgram program, String file, }).toJS as ts.TSNodeCallback); // filter - final resolvedMap = transformer.filter(); + final resolvedMap = transformer.filterAndReturn(); programDeclarationMap.addAll({file: resolvedMap}); } diff --git a/web_generator/lib/src/interop_gen/transform/transformer.dart b/web_generator/lib/src/interop_gen/transform/transformer.dart index ea054065..87eef42d 100644 --- a/web_generator/lib/src/interop_gen/transform/transformer.dart +++ b/web_generator/lib/src/interop_gen/transform/transformer.dart @@ -51,6 +51,7 @@ class Transformer { filterDeclSet = filterDeclSet.toList(), namer = UniqueNamer(); + /// Transforms a TypeScript AST Node [TSNode] into a Dart representable [Node] void transform(TSNode node) { if (nodes.contains(node)) return; @@ -695,7 +696,16 @@ class Transformer { constraint: getJSTypeAlternative(constraint)); } - /// Parses the type + /// Parses a TypeScript AST Type Node [TSTypeNode] into a [Type] Node + /// used to represent a type + /// + /// [parameter] represents whether the [TSTypeNode] is being passed in + /// the context of a parameter, which is mainly used to differentiate between + /// using [num] and [double] in the context of a [JSNumber] + /// + /// [typeArg] represents whether the [TSTypeNode] is being passed in the + /// context of a type argument, as Dart core types are not allowed in + /// type arguments Type _transformType(TSTypeNode type, {bool parameter = false, bool typeArg = false}) { switch (type.kind) { @@ -799,7 +809,7 @@ class Transformer { final typeArguments = typeQuery.typeArguments?.toDart; return _getTypeFromDeclaration(exprName, typeArguments, - typeArg: typeArg, declarationOnly: true); + typeArg: typeArg, isNotTypableDeclaration: true); case TSSyntaxKind.ArrayType: return BuiltinType.primitiveType(PrimitiveType.array, typeParams: [ getJSTypeAlternative( @@ -844,9 +854,27 @@ class Transformer { } } + /// Get the type of a type node named [typeName] by referencing its + /// declaration + /// + /// This method uses the TypeScript type checker [ts.TSTypeChecker] to get the + /// declaration associated with the [TSTypeNode] using its [typeName], and + /// refer to that type either as a [ReferredType] if defined in the file, or + /// not directly supported by `dart:js_interop`, or as a [BuiltinType] if + /// supported by `dart:js_interop` + /// + /// [typeArg] represents whether the [TSTypeNode] is being passed in the + /// context of a type argument, as Dart core types are not allowed in + /// type arguments + /// + /// [isNotTypableDeclaration] represents whether the declaration to search for + /// or refer to is not a typable declaration (i.e a declaration suitable for + /// use in a `typeof` type node, such as a variable). This reduces checks on + /// supported `dart:js_interop` types and related [EnumDeclaration]-like and + /// [TypeDeclaration]-like checks Type _getTypeFromDeclaration( TSIdentifier typeName, List? typeArguments, - {bool typeArg = false, bool declarationOnly = false}) { + {bool typeArg = false, bool isNotTypableDeclaration = false}) { final name = typeName.text; var declarationsMatching = nodeMap.findByName(name); @@ -855,7 +883,7 @@ class Transformer { // TODO(https://github.com/dart-lang/web/issues/380): A better name // for this, and adding support for "supported declarations" // (also a better name for that) - if (!declarationOnly) { + if (!isNotTypableDeclaration) { final supportedType = BuiltinType.referred(name, typeParams: (typeArguments ?? []) .map((t) => getJSTypeAlternative(_transformType(t))) @@ -878,7 +906,7 @@ class Transformer { throw Exception('Found no declaration matching $name'); } - if (!declarationOnly) { + if (!isNotTypableDeclaration) { // check if this is from dom final declarationSource = declaration.getSourceFile().fileName; if (p.basename(declarationSource) == 'lib.dom.d.ts' || @@ -906,7 +934,7 @@ class Transformer { // TODO: In the case of overloading, should/shouldn't we handle more than one declaration? final firstNode = declarationsMatching.whereType().first; - if (!declarationOnly) { + if (!isNotTypableDeclaration) { // For Typealiases, we can either return the type itself // or the JS Alternative (if its underlying type isn't a JS type) switch (firstNode) { @@ -932,7 +960,19 @@ class Transformer { return asReferredType; } - NodeMap filter() { + /// Filters out the declarations generated from the [transform] function and + /// returns the declarations needed based on: + /// + /// - Whether they are exported (contains the `export` keyword, or is in an + /// export declaration captured by [exportSet]) + /// - Whether they are denoted to be included in configuration + /// ([filterDeclSet]) + /// + /// The function also goes through declaration dependencies and filters those + /// in too + /// + /// Returns a [NodeMap] containing a map of the declared nodes and IDs. + NodeMap filterAndReturn() { final filteredDeclarations = NodeMap(); // filter out for export declarations