diff --git a/web_generator/lib/src/ast/declarations.dart b/web_generator/lib/src/ast/declarations.dart index a0756cc5..4f88dfa8 100644 --- a/web_generator/lib/src/ast/declarations.dart +++ b/web_generator/lib/src/ast/declarations.dart @@ -481,7 +481,7 @@ class EnumMember { String? dartName; } -class TypeAliasDeclaration extends NamedDeclaration +class TypeAliasDeclaration extends NestableDeclaration implements ExportableDeclaration, DocumentedDeclaration { @override String name; @@ -507,7 +507,8 @@ class TypeAliasDeclaration extends NamedDeclaration this.typeParameters = const [], required this.type, required this.exported, - this.documentation}) + this.documentation, + this.parent}) : dartName = null; @override @@ -518,11 +519,14 @@ class TypeAliasDeclaration extends NamedDeclaration return TypeDef((t) => t ..docs.addAll([...doc]) ..annotations.addAll([...annotations]) - ..name = name + ..name = completedDartName ..types .addAll(typeParameters.map((t) => t.emit(options?.toTypeOptions()))) ..definition = type.emit(options?.toTypeOptions())); } + + @override + NestableDeclaration? parent; } /// The declaration node for a TypeScript Namespace diff --git a/web_generator/lib/src/ast/merger.dart b/web_generator/lib/src/ast/merger.dart index 32d36050..baf10de1 100644 --- a/web_generator/lib/src/ast/merger.dart +++ b/web_generator/lib/src/ast/merger.dart @@ -66,6 +66,7 @@ import 'types.dart'; ClassDeclaration? classDecl; // there can be only 1 class TypeAliasDeclaration? typealiasDecl; // there can be only 1 typedef final namespaces = []; + final otherVariableDeclarations = []; final varDeclarations = []; final varDeclarationsWithBuiltinTypes = []; final otherDeclarations = []; @@ -97,6 +98,8 @@ import 'types.dart'; ) when modifier == VariableModifier.$var: varDeclarationsWithBuiltinTypes.add(decl); + case final VariableDeclaration v: + otherVariableDeclarations.add(v); default: otherDeclarations.add(decl); break; @@ -115,7 +118,7 @@ import 'types.dart'; assert(interfaces.isEmpty && namespaces.isEmpty, 'Typedefs in TS do not allow other decls'); - for (final varDecl in varDeclarations) { + for (final varDecl in [...varDeclarations, ...otherVariableDeclarations]) { final VariableDeclaration( type: varDeclType, name: varDeclName, @@ -126,10 +129,11 @@ import 'types.dart'; referredDecl.name == typealiasDecl.name) { // change type of var decl varDecl.type = referredDecl.type; + output.add(varDecl); } } - output.addAll([...functions, ...varDeclarations]); + output.addAll([...functions]); } else if (mergedNamespace != null) { var mergedComposite = mergedNamespace.asComposite ..mergeType(mergedInterface); @@ -158,7 +162,7 @@ import 'types.dart'; additionals.addAll([if (enumDecl != null) enumDecl]); - output.add(mergedComposite); + output.addAll([mergedComposite, ...otherVariableDeclarations]); } else if (mergedInterface != null) { // merge em and vars mergedInterface = _mergeInterfaceWithVars(mergedInterface, varDeclarations) @@ -182,7 +186,7 @@ import 'types.dart'; } // that's it - output.addAll(functions); + output.addAll([...functions, ...otherVariableDeclarations]); } else { return (declarations, additionals: []); } diff --git a/web_generator/lib/src/interop_gen/transform.dart b/web_generator/lib/src/interop_gen/transform.dart index b0e75045..62805c47 100644 --- a/web_generator/lib/src/interop_gen/transform.dart +++ b/web_generator/lib/src/interop_gen/transform.dart @@ -177,8 +177,16 @@ class ProgramMap { /// The files in the given project final p.PathSet files; + /// A list of declarations to include final List filterDeclSet; + /// The declarations as globs + List get filterDeclSetPatterns => filterDeclSet.map((decl) { + final escapedDecl = RegExp.escape(decl); + if (escapedDecl == decl) return RegExp('^$decl\$'); + return RegExp(decl); + }).toList(); + final bool generateAll; final bool strictUnsupported; @@ -201,28 +209,42 @@ class ProgramMap { } else { final src = program.getSourceFile(file); - final transformer = - _activeTransformers.putIfAbsent(file, () => Transformer(this, src)); + if (src == null && !strictUnsupported) { + // print warning + print('WARN: Could not find file $file'); + // try to transform by yourself + final anonymousTransformer = _activeTransformers.putIfAbsent( + file, () => Transformer(this, null, file: file)); + + // TODO: Replace with .transformAndReturn once #388 lands + return anonymousTransformer.transformAndReturn(node); + } else { + final transformer = + _activeTransformers.putIfAbsent(file, () => Transformer(this, src)); - if (!transformer.nodes.contains(node)) { - if (declName case final d? - when transformer.nodeMap.findByName(d).isEmpty) { - // find the source file decl - if (src == null) return null; + if (!transformer.nodes.contains(node)) { + if (declName case final d? + when transformer.nodeMap.findByName(d).isEmpty) { + // find the source file decl + if (src == null) return null; - final symbol = typeChecker.getSymbolAtLocation(src)!; - final exports = symbol.exports?.toDart ?? {}; + final symbol = typeChecker.getSymbolAtLocation(src)!; + final exports = symbol.exports?.toDart ?? {}; - final targetSymbol = exports[d.toJS]!; + final targetSymbol = exports[d.toJS]!; - transformer.transform(targetSymbol.getDeclarations()!.toDart.first); - } else { - transformer.transform(node); + for (final decl in targetSymbol.getDeclarations()?.toDart ?? + []) { + transformer.transform(decl); + } + } else { + transformer.transform(node); + } } - } - nodeMap = transformer.processAndReturn(); - _activeTransformers[file] = transformer; + nodeMap = transformer.processAndReturn(); + _activeTransformers[file] = transformer; + } } final name = declName ?? (node as TSNamedDeclaration).name?.text; @@ -287,8 +309,14 @@ class ProgramMap { } else { final exportedSymbols = sourceSymbol.exports?.toDart; - for (final MapEntry(value: symbol) + for (final MapEntry(key: symbolName, value: symbol) in exportedSymbols?.entries ?? >[]) { + // if there are decls to filter by and it does not match any, skip + if (!filterDeclSetPatterns + .any((f) => f.hasMatch(symbolName.toDart)) && + filterDeclSet.isNotEmpty) { + continue; + } final decls = symbol.getDeclarations()?.toDart ?? []; try { final aliasedSymbol = typeChecker.getAliasedSymbol(symbol); diff --git a/web_generator/lib/src/interop_gen/transform/transformer.dart b/web_generator/lib/src/interop_gen/transform/transformer.dart index bc141adc..fd411ff3 100644 --- a/web_generator/lib/src/interop_gen/transform/transformer.dart +++ b/web_generator/lib/src/interop_gen/transform/transformer.dart @@ -14,6 +14,7 @@ import '../../ast/helpers.dart'; import '../../ast/merger.dart'; import '../../ast/types.dart'; import '../../js/annotations.dart'; +import '../../js/filesystem_api.dart'; import '../../js/helpers.dart'; import '../../js/typescript.dart' as ts; import '../../js/typescript.types.dart'; @@ -102,14 +103,14 @@ class Transformer { void transform(TSNode node) { if (nodes.contains(node)) return; - final decls = _transform(node); + final decls = transformAndReturn(node); nodeMap.addAll({for (final d in decls) d.id.toString(): d}); nodes.add(node); } - List _transform(TSNode node, + List transformAndReturn(TSNode node, {Set? exportSet, UniqueNamer? namer, NamespaceDeclaration? parent}) { @@ -288,6 +289,52 @@ class Transformer { } } + void transformDeclAndAppendParent( + NamespaceDeclaration outputNamespace, TSNode decl) { + if (outputNamespace.nodes.contains(decl)) return; + if (decl.kind == TSSyntaxKind.EnumMember) { + final tsEnum = (decl as TSEnumMember).parent; + // parse whole enum + final transformedEnum = _transformEnum(tsEnum, namer: namer); + + // add enum + if (parent != null) { + parent.nestableDeclarations.add(transformedEnum); + parent.nodes.add(tsEnum); + } else { + nodes.add(tsEnum); + nodeMap.add(transformedEnum); + } + + // add all members to namespace + outputNamespace.nodes.addAll(tsEnum.members.toDart); + } else { + final outputDecls = transformAndReturn(decl, + namer: scopedNamer, parent: outputNamespace); + switch (decl.kind) { + case TSSyntaxKind.ClassDeclaration || + TSSyntaxKind.InterfaceDeclaration: + final outputDecl = outputDecls.single as TypeDeclaration; + outputDecl.parent = outputNamespace; + outputNamespace.nestableDeclarations.add(outputDecl); + case TSSyntaxKind.EnumDeclaration: + final outputDecl = outputDecls.single as EnumDeclaration; + outputDecl.parent = outputNamespace; + outputNamespace.nestableDeclarations.add(outputDecl); + case TSSyntaxKind.TypeAliasDeclaration: + final outputDecl = outputDecls.single as TypeAliasDeclaration; + outputDecl.parent = outputNamespace; + outputNamespace.nestableDeclarations.add(outputDecl); + default: + outputNamespace.topLevelDeclarations.addAll(outputDecls); + } + outputNamespace.nodes.add(decl); + } + + // update namespace state + updateNSInParent(); + } + // preload nodemap updateNSInParent(); @@ -305,44 +352,9 @@ class Transformer { // throws error if no aliased symbol, so ignore } for (final decl in decls) { - if (outputNamespace.nodes.contains(decl)) continue; - if (decl.kind == TSSyntaxKind.EnumMember) { - final tsEnum = (decl as TSEnumMember).parent; - // parse whole enum - final transformedEnum = _transformEnum(tsEnum, namer: namer); - - // add enum - if (parent != null) { - parent.nestableDeclarations.add(transformedEnum); - parent.nodes.add(tsEnum); - } else { - nodes.add(tsEnum); - nodeMap.add(transformedEnum); - } - - // add all members to namespace - outputNamespace.nodes.addAll(tsEnum.members.toDart); - } else { - final outputDecls = - _transform(decl, namer: scopedNamer, parent: outputNamespace); - switch (decl.kind) { - case TSSyntaxKind.ClassDeclaration || - TSSyntaxKind.InterfaceDeclaration: - final outputDecl = outputDecls.first as TypeDeclaration; - outputDecl.parent = outputNamespace; - outputNamespace.nestableDeclarations.add(outputDecl); - case TSSyntaxKind.EnumDeclaration: - final outputDecl = outputDecls.first as EnumDeclaration; - outputDecl.parent = outputNamespace; - outputNamespace.nestableDeclarations.add(outputDecl); - default: - outputNamespace.topLevelDeclarations.addAll(outputDecls); - } - outputNamespace.nodes.add(decl); - } - - // update namespace state - updateNSInParent(); + // TODO: We could also ignore namespace decls with the same name, as + // a single instance should consider such non-necessary + transformDeclAndAppendParent(outputNamespace, decl); } } // fallback @@ -351,24 +363,7 @@ class Transformer { when namespaceBody.kind == TSSyntaxKind.ModuleBlock) { for (final statement in (namespaceBody as TSModuleBlock).statements.toDart) { - final outputDecls = _transform(statement, - namer: scopedNamer, parent: outputNamespace); - switch (statement.kind) { - case TSSyntaxKind.ClassDeclaration || - TSSyntaxKind.InterfaceDeclaration: - final outputDecl = outputDecls.first as TypeDeclaration; - outputDecl.parent = outputNamespace; - outputNamespace.nestableDeclarations.add(outputDecl); - case TSSyntaxKind.EnumDeclaration: - final outputDecl = outputDecls.first as EnumDeclaration; - outputDecl.parent = outputNamespace; - outputNamespace.nestableDeclarations.add(outputDecl); - default: - outputNamespace.topLevelDeclarations.addAll(outputDecls); - } - - // update namespace state - updateNSInParent(); + transformDeclAndAppendParent(outputNamespace, statement); } } else if (namespace.body case final namespaceBody?) { // namespace import @@ -462,19 +457,17 @@ class Transformer { for (final member in typeDecl.members.toDart) { switch (member.kind) { - case TSSyntaxKind.PropertySignature: - case TSSyntaxKind.PropertyDeclaration: + case TSSyntaxKind.PropertySignature || TSSyntaxKind.PropertyDeclaration: final prop = _transformProperty(member as TSPropertyEntity, parentNamer: typeNamer, parent: outputType); outputType.properties.add(prop); break; - case TSSyntaxKind.MethodSignature: - final method = _transformMethod(member as TSMethodSignature, - parentNamer: typeNamer, parent: outputType); - outputType.methods.add(method); - break; - case TSSyntaxKind.MethodDeclaration: - final method = _transformMethod(member as TSMethodDeclaration, + // TODO: Support methods with computed and string property names + // (e.g) [Symbol.iterator], "foo-bar" + case TSSyntaxKind.MethodSignature || TSSyntaxKind.MethodDeclaration + when (member as TSMethodEntity).name.kind == + TSSyntaxKind.Identifier: + final method = _transformMethod(member, parentNamer: typeNamer, parent: outputType); outputType.methods.add(method); break; @@ -737,24 +730,32 @@ class Transformer { } final doc = _parseAndTransformDocumentation(indexSignature); + final transformedParameters = params.map(_transformParameter).toList(); + final type = indexerType ?? _transformType(indexSignature.type); + final transformedTypeParams = + typeParams?.map(_transformTypeParamDeclaration).toList() ?? []; final getOperatorDeclaration = OperatorDeclaration( kind: OperatorKind.squareBracket, - parameters: params.map(_transformParameter).toList(), - returnType: indexerType ?? _transformType(indexSignature.type), + parameters: transformedParameters, + returnType: type, scope: scope, - typeParameters: - typeParams?.map(_transformTypeParamDeclaration).toList() ?? [], + typeParameters: transformedTypeParams, static: isStatic, documentation: doc); final setOperatorDeclaration = isReadonly ? OperatorDeclaration( kind: OperatorKind.squareBracketSet, - parameters: params.map(_transformParameter).toList(), - returnType: indexerType ?? _transformType(indexSignature.type), + parameters: [ + ...transformedParameters, + ParameterDeclaration( + name: 'newValue', + type: type, + ) + ], + returnType: BuiltinType.$voidType, scope: scope, - typeParameters: - typeParams?.map(_transformTypeParamDeclaration).toList() ?? [], + typeParameters: transformedTypeParams, static: isStatic, documentation: doc) : null; @@ -1070,7 +1071,8 @@ class Transformer { default: // TODO: Support Destructured Object Parameters // and Destructured Array Parameters - throw Exception('Unsupported Parameter Name kind ${parameter.kind}'); + throw Exception( + 'Unsupported Parameter Name kind ${parameter.name.kind}'); } } @@ -1611,7 +1613,7 @@ class Transformer { // TODO: multi-decls final transformedDecls = - _transform(declaration, namer: namer, parent: parent); + transformAndReturn(declaration, namer: namer, parent: parent); if (parent != null) { switch (declaration.kind) { @@ -1938,8 +1940,15 @@ class Transformer { isNullable: isNullable); } else { // if import there and not this file, imported from specified file - final importUrl = - !nameImport.endsWith('.d.ts') ? '$nameImport.d.ts' : nameImport; + final importUrl = !nameImport.endsWith('.d.ts') && + fs + .existsSync((p.isAbsolute('$nameImport.d.ts') + ? '$nameImport.d.ts' + : p.join(p.dirname(file), '$nameImport.d.ts')) + .toJS) + .toDart + ? '$nameImport.d.ts' + : nameImport; final relativePath = programMap.files.contains(importUrl) ? p.relative(importUrl, from: p.dirname(file)) : null; @@ -1963,7 +1972,9 @@ class Transformer { } } - throw Exception('Could not resolve type for node'); + throw Exception( + 'Could not resolve type for node ${fullyQualifiedName.asName}' + '${nameImport == null ? '' : ' from $nameImport'}'); } /// Get the type of a type node named [typeName] by referencing its @@ -2193,33 +2204,37 @@ class Transformer { if (filteredDeclarations.isEmpty) return filteredDeclarations; - final declGroups = - groupBy(filteredDeclarations.values, (decl) => decl.id.name); + final otherDecls = filteredDeclarations.entries + .map((e) => _getDependenciesOfDecl(e.value)) + .reduce((value, element) => value..addAll(element)); + + final completedDecls = NodeMap({...filteredDeclarations, ...otherDecls}); - final outputDeclSet = NodeMap(); + final declGroups = groupBy(completedDecls.values, (decl) => decl.id.name); + + final outputDeclSet = NodeMap(); for (final declSet in declGroups.values) { - final (mergedDeclSet, :additionals) = mergeDeclarations(declSet); + final nodes = []; + final declarations = []; + + for (final d in declSet) { + if (d is Declaration) { + declarations.add(d); + } else { + nodes.add(d); + } + } + + final (mergedDeclSet, :additionals) = mergeDeclarations(declarations); outputDeclSet.addAll({ - for (final d in [...mergedDeclSet, ...additionals]) d.id.toString(): d + for (final d in [...mergedDeclSet, ...additionals, ...nodes]) + d.id.toString(): d }); } - // then filter for dependencies - final otherDecls = filteredDeclarations.entries - .map((e) => _getDependenciesOfDecl(e.value)) - .reduce((value, element) => value..addAll(element)); - - // if already in filtered declarations, we remove - // because they may have been updated in merge - otherDecls.removeWhere((key, value) { - final id = UniqueNamer.parse(key); - return value is! FunctionDeclaration && - outputDeclSet.values.any((v) => v.id.name == id.name); - }); - - return NodeMap({...outputDeclSet, ...otherDecls}); + return NodeMap(outputDeclSet); } /// Given an already filtered declaration [decl], diff --git a/web_generator/test/integration/interop_gen/classes_expected.dart b/web_generator/test/integration/interop_gen/classes_expected.dart index 05330aab..beea1802 100644 --- a/web_generator/test/integration/interop_gen/classes_expected.dart +++ b/web_generator/test/integration/interop_gen/classes_expected.dart @@ -303,16 +303,6 @@ extension type EpahsImpl._(_i1.JSObject _) @_i1.JS('toString') external String toString$(); } -extension type const AnonymousUnion_1113974._(String _) { - static const AnonymousUnion_1113974 circle = - AnonymousUnion_1113974._('circle'); - - static const AnonymousUnion_1113974 rectangle = - AnonymousUnion_1113974._('rectangle'); - - static const AnonymousUnion_1113974 polygon = - AnonymousUnion_1113974._('polygon'); -} extension type Epahs._(_i1.JSObject _) implements _i1.JSObject { external String name; @@ -323,6 +313,16 @@ extension type Epahs._(_i1.JSObject _) external String area$1(AnonymousUnion_1594664 unit); external _i1.JSFunction? get onUpdate; } +extension type const AnonymousUnion_1113974._(String _) { + static const AnonymousUnion_1113974 circle = + AnonymousUnion_1113974._('circle'); + + static const AnonymousUnion_1113974 rectangle = + AnonymousUnion_1113974._('rectangle'); + + static const AnonymousUnion_1113974 polygon = + AnonymousUnion_1113974._('polygon'); +} extension type const AnonymousUnion_1594664._(String _) { static const AnonymousUnion_1594664 cm2 = AnonymousUnion_1594664._('cm2'); diff --git a/web_generator/test/integration/interop_gen/namespaces_expected.dart b/web_generator/test/integration/interop_gen/namespaces_expected.dart index ea1d3579..758f843c 100644 --- a/web_generator/test/integration/interop_gen/namespaces_expected.dart +++ b/web_generator/test/integration/interop_gen/namespaces_expected.dart @@ -92,6 +92,16 @@ extension type Core_Internal._(_i1.JSObject _) implements _i1.JSObject { @_i1.JS() external static bool get devMode; } +typedef Core_Internal_Mode = AnonymousUnion_9945138; +extension type const AnonymousUnion_9945138._(String _) { + static const AnonymousUnion_9945138 debug = AnonymousUnion_9945138._('debug'); + + static const AnonymousUnion_9945138 profile = + AnonymousUnion_9945138._('profile'); + + static const AnonymousUnion_9945138 release = + AnonymousUnion_9945138._('release'); +} @_i1.JS('Security.IAuthToken') extension type Security_IAuthToken._(_i1.JSObject _) implements _i1.JSObject { external String token; diff --git a/web_generator/test/integration/interop_gen/namespaces_input.d.ts b/web_generator/test/integration/interop_gen/namespaces_input.d.ts index 724223f3..d6f42672 100644 --- a/web_generator/test/integration/interop_gen/namespaces_input.d.ts +++ b/web_generator/test/integration/interop_gen/namespaces_input.d.ts @@ -20,6 +20,7 @@ export declare namespace Core { export declare namespace Core.Internal { const internalName: string; const devMode: boolean; + type Mode = "debug" | "profile" | "release"; } export declare namespace Security { const TOKEN_LIFETIME_SECONDS: number; diff --git a/web_generator/test/integration/interop_gen/project/output/b.dart b/web_generator/test/integration/interop_gen/project/output/b.dart index dd1b6837..e4bb2ff4 100644 --- a/web_generator/test/integration/interop_gen/project/output/b.dart +++ b/web_generator/test/integration/interop_gen/project/output/b.dart @@ -276,16 +276,6 @@ extension type EpahsImpl._(_i1.JSObject _) external String toString$(); } extension type Point._(_i1.JSObject _) implements _i1.JSObject {} -extension type const AnonymousUnion_1113974._(String _) { - static const AnonymousUnion_1113974 circle = - AnonymousUnion_1113974._('circle'); - - static const AnonymousUnion_1113974 rectangle = - AnonymousUnion_1113974._('rectangle'); - - static const AnonymousUnion_1113974 polygon = - AnonymousUnion_1113974._('polygon'); -} extension type Epahs._(_i1.JSObject _) implements _i1.JSObject { external String name; @@ -296,6 +286,16 @@ extension type Epahs._(_i1.JSObject _) external String area$1(AnonymousUnion_1594664 unit); external _i1.JSFunction? get onUpdate; } +extension type const AnonymousUnion_1113974._(String _) { + static const AnonymousUnion_1113974 circle = + AnonymousUnion_1113974._('circle'); + + static const AnonymousUnion_1113974 rectangle = + AnonymousUnion_1113974._('rectangle'); + + static const AnonymousUnion_1113974 polygon = + AnonymousUnion_1113974._('polygon'); +} extension type const AnonymousUnion_1594664._(String _) { static const AnonymousUnion_1594664 cm2 = AnonymousUnion_1594664._('cm2'); 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 9b3ff96c..7c83d832 100644 --- a/web_generator/test/integration/interop_gen/ts_typing_expected.dart +++ b/web_generator/test/integration/interop_gen/ts_typing_expected.dart @@ -222,14 +222,6 @@ extension type AnonymousType_1358595._(_i1.JSObject _) implements _i1.JSObject { external double taxRate; } typedef Product = AnonymousType_2194029; -extension type AnonymousUnion_1189263._(_i1.JSObject _) - implements _i1.JSObject { - AnonymousType_2773310 get asAnonymousType_2773310 => - (_ as AnonymousType_2773310); - - AnonymousType_1487785 get asAnonymousType_1487785 => - (_ as AnonymousType_1487785); -} extension type AnonymousType_2773310._(_i1.JSObject _) implements _i1.JSObject { external AnonymousType_2773310({ String id, @@ -253,6 +245,14 @@ extension type AnonymousType_1487785._(_i1.JSObject _) implements _i1.JSObject { external _i1.JSAny? data; } +extension type AnonymousUnion_1189263._(_i1.JSObject _) + implements _i1.JSObject { + AnonymousType_2773310 get asAnonymousType_2773310 => + (_ as AnonymousType_2773310); + + AnonymousType_1487785 get asAnonymousType_1487785 => + (_ as AnonymousType_1487785); +} extension type _AnonymousConstructor_1059824._(_i1.JSFunction _) implements _i1.JSFunction { Product call(