From b7f39bbeb52377a9042f2a53a41036e9f57b59e1 Mon Sep 17 00:00:00 2001 From: Nike Okoronkwo Date: Fri, 19 Sep 2025 23:27:28 -0500 Subject: [PATCH 1/3] [interop] Added support for destructured parameters Added support for working with destructured parameters, as well as other kind of parameters. These take the place of the param type, since destructuring in parameters is not allowed in Dart. However, this goes a step further and provides documentation on the parameter itself, providing the form of the param as documentation. Signed-off-by: Nike Okoronkwo --- web_generator/lib/src/ast/base.dart | 12 +- .../interop_gen/transform/transformer.dart | 114 +++++++++++++++--- .../lib/src/js/typescript.types.dart | 20 +++ .../interop_gen/ts_typing_expected.dart | 28 +++++ .../interop_gen/ts_typing_input.d.ts | 3 + 5 files changed, 159 insertions(+), 18 deletions(-) diff --git a/web_generator/lib/src/ast/base.dart b/web_generator/lib/src/ast/base.dart index e3c43504..a8064e75 100644 --- a/web_generator/lib/src/ast/base.dart +++ b/web_generator/lib/src/ast/base.dart @@ -6,6 +6,7 @@ import 'package:code_builder/code_builder.dart'; import '../interop_gen/namer.dart'; import 'documentation.dart'; +import 'helpers.dart'; import 'types.dart'; class GlobalOptions { @@ -130,7 +131,7 @@ abstract class DeclarationType extends Type { String get declarationName; } -class ParameterDeclaration { +class ParameterDeclaration implements DocumentedDeclaration { final String name; final bool optional; @@ -139,15 +140,22 @@ class ParameterDeclaration { final bool variadic; + @override + Documentation? documentation; + ParameterDeclaration( {required this.name, this.optional = false, required this.type, - this.variadic = false}); + this.variadic = false, + this.documentation}); Parameter emit([DeclarationOptions? options]) { + final (doc, annotations) = generateFromDocumentation(documentation); return Parameter((p) => p ..name = name + ..annotations.addAll(annotations) + ..docs.addAll(doc) ..type = type.emit(TypeOptions(nullable: optional))); } } diff --git a/web_generator/lib/src/interop_gen/transform/transformer.dart b/web_generator/lib/src/interop_gen/transform/transformer.dart index 3237e4ee..07105b17 100644 --- a/web_generator/lib/src/interop_gen/transform/transformer.dart +++ b/web_generator/lib/src/interop_gen/transform/transformer.dart @@ -597,7 +597,7 @@ class Transformer { id: id, scope: scope, static: isStatic, - parameters: params.map((t) { + parameters: params.mapIndexed((index, t) { ReferredType? paramType; final paramRawType = t.type; if (paramRawType case final ty? when ts.isTypeReferenceNode(ty)) { @@ -612,7 +612,7 @@ class Transformer { paramType = parent?.asReferredType( parent.typeParameters.map((t) => GenericType(name: t.name))); } - return _transformParameter(t, paramType); + return _transformParameter(t, type: paramType, index: index); }).toList(), typeParameters: typeParams?.map(_transformTypeParamDeclaration).toList() ?? [], @@ -654,7 +654,9 @@ class Transformer { id: id, dartName: dartName.trim().isEmpty ? null : dartName.trim(), name: name, - parameters: params.map(_transformParameter).toList(), + parameters: params + .mapIndexed((index, p) => _transformParameter(p, index: index)) + .toList(), scope: scope, documentation: _parseAndTransformDocumentation(constructor)); } @@ -689,7 +691,9 @@ class Transformer { name: 'call', dartName: dartName, id: id, - parameters: params.map(_transformParameter).toList(), + parameters: params + .mapIndexed((index, p) => _transformParameter(p, index: index)) + .toList(), typeParameters: typeParams?.map(_transformTypeParamDeclaration).toList() ?? [], returnType: methodType ?? @@ -730,7 +734,9 @@ class Transformer { } final doc = _parseAndTransformDocumentation(indexSignature); - final transformedParameters = params.map(_transformParameter).toList(); + final transformedParameters = params + .mapIndexed((index, p) => _transformParameter(p, index: index)) + .toList(); final type = indexerType ?? _transformType(indexSignature.type); final transformedTypeParams = typeParams?.map(_transformTypeParamDeclaration).toList() ?? []; @@ -800,7 +806,9 @@ class Transformer { id: id, kind: MethodKind.getter, scope: scope, - parameters: params.map(_transformParameter).toList(), + parameters: params + .mapIndexed((index, p) => _transformParameter(p, index: index)) + .toList(), typeParameters: typeParams?.map(_transformTypeParamDeclaration).toList() ?? [], returnType: methodType ?? @@ -830,7 +838,7 @@ class Transformer { dartName: dartName, kind: MethodKind.setter, id: id, - parameters: params.map((t) { + parameters: params.mapIndexed((index, t) { ReferredType? paramType; final paramRawType = t.type; if (paramRawType case final ty? when ts.isTypeReferenceNode(ty)) { @@ -845,7 +853,7 @@ class Transformer { paramType = parent?.asReferredType( parent.typeParameters.map((t) => GenericType(name: t.name))); } - return _transformParameter(t, paramType); + return _transformParameter(t, type: paramType, index: index); }).toList(), scope: scope, typeParameters: @@ -880,7 +888,9 @@ class Transformer { id: id, dartName: uniqueName, exported: isExported, - parameters: params.map(_transformParameter).toList(), + parameters: params + .mapIndexed((index, p) => _transformParameter(p, index: index)) + .toList(), typeParameters: typeParams?.map(_transformTypeParamDeclaration).toList() ?? [], returnType: function.type != null @@ -1053,7 +1063,7 @@ class Transformer { } ParameterDeclaration _transformParameter(TSParameterDeclaration parameter, - [Type? type]) { + {Type? type, int? index}) { type ??= parameter.type != null ? _transformType(parameter.type!, parameter: true) : BuiltinType.anyType; @@ -1068,11 +1078,82 @@ class Transformer { type: type, variadic: isVariadic, optional: isOptional); + case TSSyntaxKind.ObjectBindingPattern || + TSSyntaxKind.ArrayBindingPattern: + Iterable expandBindingPatterns( + @UnionOf( + [TSIdentifier, TSObjectBindingPattern, TSArrayBindingPattern]) + TSNode name) { + switch (name.kind) { + case TSSyntaxKind.Identifier: + return [name as TSIdentifier]; + case TSSyntaxKind.ObjectBindingPattern || + TSSyntaxKind.ArrayBindingPattern: + return (name as TSBindingPattern) + .elements + .toDart + .map((e) => e.name == null + ? [] + : expandBindingPatterns(e.name!)) + .flattenedToList; + default: + return []; + } + } + final name = parameter.name as TSBindingPattern; + // just return the object, + final elements = expandBindingPatterns(name); + final elementText = name.getText(); + final documentation = isVariadic + ? null + : Documentation(docs: 'Parameter is of the form: $elementText'); + + final rearWord = + parameter.name.kind == TSSyntaxKind.ObjectBindingPattern + ? 'obj' + : 'arr'; + + if (elements.isEmpty) { + return ParameterDeclaration( + name: 'unknown$rearWord', + type: type, + variadic: isVariadic, + optional: isOptional, + documentation: documentation); + } else if (elements.singleOrNull case final singleEl?) { + final singleElName = singleEl.kind == TSSyntaxKind.Identifier + ? (singleEl as TSIdentifier).text + : (singleEl as TSNamedDeclaration).name?.text ?? 'unknown'; + return ParameterDeclaration( + name: '$singleElName$rearWord', + type: type, + variadic: isVariadic, + optional: isOptional, + documentation: documentation); + } else { + final hash = AnonymousHasher.hashTuple(elements + .map((e) => e.kind == TSSyntaxKind.Identifier + ? (e as TSIdentifier).text + : (e as TSNamedDeclaration).name?.text ?? '') + .toList()); + return ParameterDeclaration( + name: '$rearWord${hash.substring(0, 3)}', + type: type, + variadic: isVariadic, + optional: isOptional, + documentation: documentation); + } default: - // TODO: Support Destructured Object Parameters - // and Destructured Array Parameters - throw Exception( - 'Unsupported Parameter Name kind ${parameter.name.kind}'); + final elementText = parameter.name.getText(); + final documentation = isVariadic + ? null + : Documentation(docs: 'Parameter is of the form: $elementText'); + return ParameterDeclaration( + name: 'unknown${index ?? ""}', + type: type, + variadic: isVariadic, + optional: isOptional, + documentation: documentation); } } @@ -1247,8 +1328,9 @@ class Transformer { case TSSyntaxKind.ConstructorType || TSSyntaxKind.FunctionType: final funType = type as TSFunctionOrConstructorTypeNodeBase; - final parameters = - funType.parameters.toDart.map(_transformParameter).toList(); + final parameters = funType.parameters.toDart + .mapIndexed((index, p) => _transformParameter(p, index: index)) + .toList(); final typeParameters = funType.typeParameters?.toDart .map(_transformTypeParamDeclaration) diff --git a/web_generator/lib/src/js/typescript.types.dart b/web_generator/lib/src/js/typescript.types.dart index 2a81435a..4140a9d9 100644 --- a/web_generator/lib/src/js/typescript.types.dart +++ b/web_generator/lib/src/js/typescript.types.dart @@ -632,6 +632,26 @@ extension type TSParameterDeclaration._(JSObject _) implements TSDeclaration { external TSNode? get dotDotDotToken; } +@JS('BindingPattern') +extension type TSBindingPattern._(JSObject _) + implements TSNode { + external TSNodeArray get elements; +} + +@JS('ObjectBindingPattern') +extension type TSObjectBindingPattern._(JSObject _) + implements TSBindingPattern {} + +@JS('ArrayBindingPattern') +extension type TSArrayBindingPattern._(JSObject _) + implements TSBindingPattern {} + +/** We do not need much from this other than its name */ +@JS('BindingElement') +extension type TSBindingElement._(JSObject _) implements TSNamedDeclaration { + external TSNode get name; +} + @JS('TypeParameterDeclaration') extension type TSTypeParameterDeclaration._(JSObject _) implements TSDeclaration { 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 91048bba..434fc358 100644 --- a/web_generator/test/integration/interop_gen/ts_typing_expected.dart +++ b/web_generator/test/integration/interop_gen/ts_typing_expected.dart @@ -27,6 +27,24 @@ external ProductOrrandomNonTypedProduct objectAsProduct( external _i1.JSArray> indexedArray(_i1.JSArray arr); @_i1.JS() +external double firstTwoNumbers( + + /// Parameter is of the form: [a, b] + _i1.JSArray<_i1.JSNumber> arr117); +@_i1.JS() +external String productInfo( + /// Parameter is of the form: { name, id } + Product obj157, [ + AnonymousType_9686701? options, +]); +@_i1.JS() +external String shortendedProductCartInfo( + _i1.JSArray arr903, [ + _i1.JSArray arr9032, + _i1.JSArray arr9033, + _i1.JSArray arr9034, +]); +@_i1.JS() external String get myString; @_i1.JS() external _i1.JSArray<_i1.JSNumber> get myNumberArray; @@ -163,6 +181,16 @@ extension type AnonymousType_9143117._(_i1.JSObject _) external T value; } +extension type AnonymousType_9686701._(_i1.JSObject _) implements _i1.JSObject { + external AnonymousType_9686701({ + bool search, + bool showId, + }); + + external bool? search; + + external bool? showId; +} extension type MyEnum_EnumType._(_i1.JSObject _) implements _i1.JSObject { static const MyEnum A = MyEnum._(0); 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 5e2b5b8f..defdc986 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 @@ -99,3 +99,6 @@ export declare const myThirdIntersection: string & { export declare const myTypeGymnastic: ({ a: number } | { b: string }) & ({ c: boolean } | ({ d: bigint } & { e: symbol })); +export declare function firstTwoNumbers([a, b]: number[]): number; +export declare function productInfo({ name, id }: Product, options?: { search?: boolean, showId?: boolean }): string; +export declare function shortendedProductCartInfo(...[{ name: prod1, id: prod1ID }, { name: prod2, id: prod2ID }]: Product[]): string; From a0e373939be2bb3db24f2ef2bc8809e274ac3421 Mon Sep 17 00:00:00 2001 From: Nikechukwu <150845642+nikeokoronkwo@users.noreply.github.com> Date: Mon, 22 Sep 2025 21:36:53 +0000 Subject: [PATCH 2/3] removed comma in comment --- web_generator/lib/src/interop_gen/transform/transformer.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_generator/lib/src/interop_gen/transform/transformer.dart b/web_generator/lib/src/interop_gen/transform/transformer.dart index 07105b17..71ec6429 100644 --- a/web_generator/lib/src/interop_gen/transform/transformer.dart +++ b/web_generator/lib/src/interop_gen/transform/transformer.dart @@ -1101,7 +1101,7 @@ class Transformer { } } final name = parameter.name as TSBindingPattern; - // just return the object, + // just return the object final elements = expandBindingPatterns(name); final elementText = name.getText(); final documentation = isVariadic From 5f5417fce21d47700022ecd9a6b184cbb8518567 Mon Sep 17 00:00:00 2001 From: Nikechukwu <150845642+nikeokoronkwo@users.noreply.github.com> Date: Mon, 22 Sep 2025 21:47:39 +0000 Subject: [PATCH 3/3] added unknown case --- .../interop_gen/ts_typing_expected.dart | 15 ++++++++++----- .../integration/interop_gen/ts_typing_input.d.ts | 1 + 2 files changed, 11 insertions(+), 5 deletions(-) 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 434fc358..b898cd35 100644 --- a/web_generator/test/integration/interop_gen/ts_typing_expected.dart +++ b/web_generator/test/integration/interop_gen/ts_typing_expected.dart @@ -32,6 +32,11 @@ external double firstTwoNumbers( /// Parameter is of the form: [a, b] _i1.JSArray<_i1.JSNumber> arr117); @_i1.JS() +external double pointlessArrayFunction( + + /// Parameter is of the form: [{}] + _i1.JSArray unknownarr); +@_i1.JS() external String productInfo( /// Parameter is of the form: { name, id } Product obj157, [ @@ -181,6 +186,11 @@ extension type AnonymousType_9143117._(_i1.JSObject _) external T value; } +extension type AnonymousType_4207514._(_i1.JSObject _) implements _i1.JSObject { + external AnonymousType_4207514({double a}); + + external double a; +} extension type AnonymousType_9686701._(_i1.JSObject _) implements _i1.JSObject { external AnonymousType_9686701({ bool search, @@ -404,11 +414,6 @@ extension type AnonymousIntersection_1711585._(_i1.JSAny _) AnonymousUnion_5737239 get asAnonymousUnion_5737239 => (_ as AnonymousUnion_5737239); } -extension type AnonymousType_4207514._(_i1.JSObject _) implements _i1.JSObject { - external AnonymousType_4207514({double a}); - - external double a; -} extension type AnonymousType_1806035._(_i1.JSObject _) implements _i1.JSObject { external AnonymousType_1806035({String b}); 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 defdc986..2d357ae8 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 @@ -100,5 +100,6 @@ export declare const myTypeGymnastic: ({ a: number } | { b: string }) & ({ c: boolean } | ({ d: bigint } & { e: symbol })); export declare function firstTwoNumbers([a, b]: number[]): number; +export declare function pointlessArrayFunction([{}]: {a: number}[]): number; export declare function productInfo({ name, id }: Product, options?: { search?: boolean, showId?: boolean }): string; export declare function shortendedProductCartInfo(...[{ name: prod1, id: prod1ID }, { name: prod2, id: prod2ID }]: Product[]): string;