diff --git a/web_generator/lib/src/ast/base.dart b/web_generator/lib/src/ast/base.dart index c9d9296a..fc5e53ec 100644 --- a/web_generator/lib/src/ast/base.dart +++ b/web_generator/lib/src/ast/base.dart @@ -26,8 +26,9 @@ class DeclarationOptions extends Options { class TypeOptions extends Options { bool nullable; + String? url; - TypeOptions({this.nullable = false}); + TypeOptions({this.nullable = false, this.url}); } class ASTOptions { @@ -42,7 +43,7 @@ class ASTOptions { } sealed class Node { - abstract final String? name; + String? get name; abstract final ID id; String? get dartName; @@ -53,20 +54,30 @@ sealed class Node { abstract class Declaration extends Node { @override - abstract final String name; + String get name; @override Spec emit([covariant DeclarationOptions? options]); } abstract class NamedDeclaration extends Declaration { - ReferredType asReferredType([List? typeArgs]) => - ReferredType(name: name, declaration: this, typeParams: typeArgs ?? []); + @override + abstract String name; + + ReferredType asReferredType([List? typeArgs, String? url]) => + ReferredType( + name: name, declaration: this, typeParams: typeArgs ?? [], url: url); } abstract interface class ExportableDeclaration extends Declaration { /// Whether this declaration is exported. bool get exported; + + @override + abstract String? dartName; + + @override + abstract String name; } abstract class Type extends Node { diff --git a/web_generator/lib/src/ast/declarations.dart b/web_generator/lib/src/ast/declarations.dart index 8aae8966..2494b5ee 100644 --- a/web_generator/lib/src/ast/declarations.dart +++ b/web_generator/lib/src/ast/declarations.dart @@ -17,10 +17,10 @@ import 'types.dart'; sealed class TypeDeclaration extends NamedDeclaration implements ExportableDeclaration { @override - final String name; + String name; @override - final String? dartName; + String? dartName; @override final bool exported; @@ -139,6 +139,9 @@ class VariableDeclaration extends FieldDeclaration @override ID get id => ID(type: 'var', name: name); + @override + String? dartName; + @override Spec emit([DeclarationOptions? options]) { if (modifier == VariableModifier.$const) { @@ -159,12 +162,10 @@ class VariableDeclaration extends FieldDeclaration } @override - String? get dartName => null; - - @override - ReferredType asReferredType([List? typeArgs]) { + ReferredType asReferredType( + [List? typeArgs, String? url]) { return ReferredType.fromType(type, this, - typeParams: typeArgs ?? []); + typeParams: typeArgs ?? [], url: url); } } @@ -173,10 +174,10 @@ enum VariableModifier { let, $const, $var } class FunctionDeclaration extends CallableDeclaration implements ExportableDeclaration { @override - final String name; + String name; @override - final String? dartName; + String? dartName; @override final List parameters; @@ -234,18 +235,19 @@ class FunctionDeclaration extends CallableDeclaration } @override - ReferredType asReferredType([List? typeArgs]) { + ReferredType asReferredType( + [List? typeArgs, String? url]) { // TODO: We could do better here and make the function type typed return ReferredType.fromType( BuiltinType.referred('Function', typeParams: typeArgs ?? [])!, this, - typeParams: typeArgs ?? []); + typeParams: typeArgs ?? [], url: url); } } class EnumDeclaration extends NamedDeclaration implements ExportableDeclaration { @override - final String name; + String name; @override final bool exported; @@ -339,14 +341,14 @@ class EnumMember { class TypeAliasDeclaration extends NamedDeclaration implements ExportableDeclaration { @override - final String name; + String name; final List typeParameters; final Type type; @override - final String? dartName; + String? dartName; @override bool exported; @@ -447,13 +449,13 @@ class InterfaceDeclaration extends TypeDeclaration { class PropertyDeclaration extends FieldDeclaration implements MemberDeclaration { @override - final String name; + String name; @override final ID id; @override - final String? dartName; + String? dartName; @override late final TypeDeclaration parent; @@ -696,6 +698,9 @@ class OperatorDeclaration extends CallableDeclaration @override String get name => kind.expression; + @override + set name(String? name) {} + OperatorKind kind; @override diff --git a/web_generator/lib/src/ast/types.dart b/web_generator/lib/src/ast/types.dart index e0358da6..26b909ee 100644 --- a/web_generator/lib/src/ast/types.dart +++ b/web_generator/lib/src/ast/types.dart @@ -20,21 +20,24 @@ class ReferredType extends Type { List typeParams; + String? url; + ReferredType( {required this.name, required this.declaration, - this.typeParams = const []}); + this.typeParams = const [], + this.url}); factory ReferredType.fromType(Type type, T declaration, - {List typeParams}) = ReferredDeclarationType; + {List typeParams, String? url}) = ReferredDeclarationType; @override Reference emit([TypeOptions? options]) { - // TODO: Support referred types imported from URL return TypeReference((t) => t - ..symbol = declaration.name + ..symbol = declaration.dartName ?? declaration.name ..types.addAll(typeParams.map((t) => t.emit(options))) - ..isNullable = options?.nullable); + ..isNullable = options?.nullable + ..url = options?.url ?? url); } } @@ -44,11 +47,15 @@ class ReferredDeclarationType extends ReferredType { @override String get name => type.name ?? declaration.name; - ReferredDeclarationType(this.type, T declaration, {super.typeParams}) + ReferredDeclarationType(this.type, T declaration, + {super.typeParams, super.url}) : super(name: declaration.name, declaration: declaration); @override Reference emit([covariant TypeOptions? options]) { + options ??= TypeOptions(); + options.url = super.url; + return type.emit(options); } } diff --git a/web_generator/lib/src/dart_main.dart b/web_generator/lib/src/dart_main.dart index 38e88b47..26f34d56 100644 --- a/web_generator/lib/src/dart_main.dart +++ b/web_generator/lib/src/dart_main.dart @@ -70,7 +70,10 @@ Future generateJSInteropBindings(Config config) async { final jsDeclarations = parseDeclarationFiles(config.input); // transform declarations - final dartDeclarations = transform(jsDeclarations, config: config); + final manager = + TransformerManager.fromParsedResults(jsDeclarations, config: config); + + final dartDeclarations = manager.transform(); // generate final generatedCodeMap = dartDeclarations.generate(config); diff --git a/web_generator/lib/src/interop_gen/transform.dart b/web_generator/lib/src/interop_gen/transform.dart index 0290c4f5..9a6df011 100644 --- a/web_generator/lib/src/interop_gen/transform.dart +++ b/web_generator/lib/src/interop_gen/transform.dart @@ -7,9 +7,11 @@ import 'dart:js_interop'; import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; +import 'package:path/path.dart' as p; import '../ast/base.dart'; import '../config.dart'; +import '../js/helpers.dart'; import '../js/typescript.dart' as ts; import '../js/typescript.types.dart'; import 'namer.dart'; @@ -20,21 +22,24 @@ void _setGlobalOptions(Config config) { GlobalOptions.variadicArgsCount = config.functions?.varArgs ?? 4; } +typedef ProgramDeclarationMap = Map; + class TransformResult { - ProgramDeclarationMap programMap; + ProgramDeclarationMap programDeclarationMap; - TransformResult._(this.programMap); + TransformResult._(this.programDeclarationMap); // TODO(https://github.com/dart-lang/web/issues/388): Handle union of overloads // (namespaces + functions, multiple interfaces, etc) Map generate(Config config) { - final emitter = DartEmitter.scoped(useNullSafetySyntax: true); final formatter = DartFormatter( languageVersion: DartFormatter.latestShortStyleLanguageVersion); _setGlobalOptions(config); - return programMap.map((file, declMap) { + return programDeclarationMap.map((file, declMap) { + final emitter = + DartEmitter.scoped(useNullSafetySyntax: true, orderDirectives: true); final specs = declMap.decls.values.map((d) { return switch (d) { final Declaration n => n.emit(), @@ -56,7 +61,7 @@ class TransformResult { ..body.addAll(specs); }); return MapEntry( - file, + file.replaceAll('.d.ts', '.dart'), formatter.format('${lib.accept(emitter)}' .replaceAll('static external', 'external static'))); }); @@ -69,7 +74,9 @@ extension type NodeMap._(Map decls) implements Map { List findByName(String name) { return decls.entries - .where((e) => UniqueNamer.parse(e.key).name == name) + .where((e) { + return UniqueNamer.parse(e.key).name == name; + }) .map((e) => e.value) .toList(); } @@ -77,45 +84,168 @@ extension type NodeMap._(Map decls) implements Map { void add(Node decl) => decls[decl.id.toString()] = decl; } -typedef ProgramDeclarationMap = Map; - -TransformResult transform(ParserResult parsedDeclarations, - {required Config config}) { - final programDeclarationMap = {}; +/// A program map is a map used for handling the context of +/// transforming and resolving declarations across files in the project. +/// +/// This helps us to work with imports and exports across files, and allow for +/// quick transformation of declarations in files without having to re-transform +/// declarations already generated for. +/// +/// It keeps references of transformers and nodemaps (if already built) of files +/// in the project using [p.PathMap]s (to allow easy indexing). +/// +/// It also contains the program context [program] and declarations to filter +/// out for via [filterDeclSet] +/// +/// It is responsible for generating and updating/memoizing the individual transformer +/// for a given file +class ProgramMap { + /// A map of files to already generated [NodeMap]s + /// + /// If a file is not included here, its node map is not complete + /// and should be generated via [_activeTransformers] + final p.PathMap _pathMap = p.PathMap.of({}); + + final p.PathMap _activeTransformers = p.PathMap.of({}); + + /// The typescript program for the given project + final ts.TSProgram program; + + /// The type checker for the given program + /// + /// It is generated as this to prevent having to regenerate it multiple times + final ts.TSTypeChecker typeChecker; + + /// The files in the given project + final List files; + + List get absoluteFiles => + files.map((f) => p.normalize(p.absolute(f))).toList(); + + final List filterDeclSet; + + ProgramMap(this.program, this.files, {this.filterDeclSet = const []}) + : typeChecker = program.getTypeChecker(); + + /// Find the node definition for a given declaration named [declName] + /// or associated with a TypeScript node [node] from the map of files + List? getDeclarationRef(String file, TSNode node, [String? declName]) { + // check + NodeMap nodeMap; + if (_pathMap.containsKey(file)) { + nodeMap = _pathMap[file]!; + } else { + final src = program.getSourceFile(file); + + 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; + + final symbol = typeChecker.getSymbolAtLocation(src)!; + final exports = symbol.exports?.toDart ?? {}; + + final targetSymbol = exports[d.toJS]!; + transformer.transform(targetSymbol.getDeclarations()!.toDart.first); + } else { + transformer.transform(node); + } + } - for (final file in parsedDeclarations.files) { - if (programDeclarationMap.containsKey(file)) continue; + nodeMap = transformer.filterAndReturn(); + _activeTransformers[file] = transformer; + } - transformFile(parsedDeclarations.program, file, programDeclarationMap, - config: config); + final name = declName ?? (node as TSNamedDeclaration).name?.text; + return name == null ? null : nodeMap.findByName(name); } - return TransformResult._(programDeclarationMap); + /// Get the node map for a given [file], + /// transforming it and generating it if needed. + NodeMap getNodeMap(String file) { + final absolutePath = p.normalize(p.absolute(file)); + return _pathMap.putIfAbsent(absolutePath, () { + final src = program.getSourceFile(file); + + if (src == null) return NodeMap({}); + + final sourceSymbol = typeChecker.getSymbolAtLocation(src); + + // transform file + _activeTransformers.putIfAbsent( + absolutePath, + () => Transformer( + this, + src, + file: file, + )); + if (sourceSymbol == null) { + // fallback to transforming each node + // TODO: This is a temporary fix to running this with @types/web + ts.forEachChild( + src, + ((TSNode node) { + // ignore end of file + if (node.kind == TSSyntaxKind.EndOfFileToken) return; + + _activeTransformers[absolutePath]!.transform(node); + }).toJS as ts.TSNodeCallback); + } else { + final exportedSymbols = sourceSymbol.exports?.toDart; + + for (final MapEntry(value: symbol) + in exportedSymbols?.entries ?? >[]) { + final decls = symbol.getDeclarations()?.toDart ?? []; + try { + final aliasedSymbol = typeChecker.getAliasedSymbol(symbol); + decls.addAll(aliasedSymbol.getDeclarations()?.toDart ?? []); + } catch (_) { + // throws error if no aliased symbol, so ignore + } + for (final decl in decls) { + _activeTransformers[absolutePath]!.transform(decl); + } + } + } + + return _activeTransformers[absolutePath]!.filterAndReturn(); + }); + } } -void transformFile(ts.TSProgram program, String file, - Map programDeclarationMap, - {required Config config}) { - final src = program.getSourceFile(file); +/// A transform manager is used for transforming the results from parsing +/// the TS files. It uses [ProgramMap] under the hood to manage the +/// transformation context while transforming through each file +class TransformerManager { + final ProgramMap programMap; - if (src == null) return; + List get inputFiles => programMap.files; - final typeChecker = program.getTypeChecker(); + ts.TSProgram get program => programMap.program; - final transformer = Transformer(programDeclarationMap, typeChecker, - filterDeclSet: config.includedDeclarations); + ts.TSTypeChecker get typeChecker => programMap.typeChecker; - ts.forEachChild( - src, - ((TSNode node) { - // ignore end of file - if (node.kind == TSSyntaxKind.EndOfFileToken) return; + TransformerManager(ts.TSProgram program, List inputFiles, + {List filterDeclSet = const []}) + : programMap = + ProgramMap(program, inputFiles, filterDeclSet: filterDeclSet); - transformer.transform(node); - }).toJS as ts.TSNodeCallback); + TransformerManager.fromParsedResults(ParserResult result, {Config? config}) + : programMap = ProgramMap(result.program, result.files.toList(), + filterDeclSet: config?.includedDeclarations ?? []); - // filter - final resolvedMap = transformer.filterAndReturn(); + TransformResult transform() { + final outputNodeMap = {}; + // run through each file + for (final file in inputFiles) { + // transform + outputNodeMap[file] = programMap.getNodeMap(file); + } - programDeclarationMap.addAll({file: resolvedMap}); + return TransformResult._(outputNodeMap); + } } diff --git a/web_generator/lib/src/interop_gen/transform/transformer.dart b/web_generator/lib/src/interop_gen/transform/transformer.dart index 87eef42d..90b755f6 100644 --- a/web_generator/lib/src/interop_gen/transform/transformer.dart +++ b/web_generator/lib/src/interop_gen/transform/transformer.dart @@ -14,6 +14,31 @@ import '../../js/typescript.types.dart'; import '../namer.dart'; import '../transform.dart'; +class ExportReference { + final String name; + final String as; + final bool defaultExport; + + const ExportReference(this.name, + {required this.as, this.defaultExport = false}); + + @override + bool operator ==(Object other) => + other is ExportReference && + name == other.name && + as == other.as && + defaultExport == other.defaultExport; + + @override + int get hashCode => Object.hash(name, as, defaultExport); +} + +/// A class for transforming nodes in a given [file] +/// +/// It references a [ProgramMap] in order to keep track of dependencies across +/// files in the project +// TODO(nikeokoronkwo): Add support for dynamic imports. +// TODO: Add support for import = require() and export = class Transformer { /// A set of already resolved TS Nodes final Set nodes = {}; @@ -24,14 +49,17 @@ class Transformer { /// A map of types final NodeMap typeMap = NodeMap(); + /// The program map + final ProgramMap programMap; + /// The type checker for the given program - final ts.TSTypeChecker typeChecker; + ts.TSTypeChecker get typeChecker => programMap.typeChecker; /// A set of declarations to export updated during transformation - final Set exportSet; + final Set exportSet; /// A set of declarations to filter for - final List filterDeclSet; + List get filterDeclSet => programMap.filterDeclSet; /// The declarations as globs List get filterDeclSetPatterns => filterDeclSet.map((decl) { @@ -43,24 +71,41 @@ class Transformer { /// namer, for giving elements unique names final UniqueNamer namer; - final ProgramDeclarationMap programMap; + final ts.TSSourceFile? _sourceFile; + final String? _fileName; + + /// Get the current file handled by this transformer + String get file => (_sourceFile?.fileName ?? _fileName)!; - Transformer(this.programMap, this.typeChecker, - {Set exportSet = const {}, List filterDeclSet = const []}) - : exportSet = exportSet.toSet(), - filterDeclSet = filterDeclSet.toList(), - namer = UniqueNamer(); + Transformer(this.programMap, this._sourceFile, + {Set exportSet = const {}, String? file}) + : exportSet = exportSet.map((e) => ExportReference(e, as: e)).toSet(), + namer = UniqueNamer(), + _fileName = file, + assert( + _sourceFile != null || file != null, 'Source file must be known'); + // TODO(nikeokoronkwo): Handle default exports /// Transforms a TypeScript AST Node [TSNode] into a Dart representable [Node] void transform(TSNode node) { if (nodes.contains(node)) return; switch (node.kind) { + case TSSyntaxKind.ImportDeclaration || TSSyntaxKind.ImportSpecifier: + // We do not parse import declarations by default + // so that generated code only makes use of declarations we need. + break; + case TSSyntaxKind.ExportSpecifier: + _parseExportSpecifier(node as TSExportSpecifier); + case TSSyntaxKind.ExportDeclaration: + _parseExportDeclaration(node as TSExportDeclaration); case TSSyntaxKind.VariableStatement: final decs = _transformVariable(node as TSVariableStatement); nodeMap.addAll({for (final d in decs) d.id.toString(): d}); default: final decl = switch (node.kind) { + TSSyntaxKind.VariableDeclaration => + _transformVariableDecl(node as TSVariableDeclaration), TSSyntaxKind.FunctionDeclaration => _transformFunction(node as TSFunctionDeclaration), TSSyntaxKind.EnumDeclaration => @@ -78,6 +123,44 @@ class Transformer { nodes.add(node); } + void _parseExportSpecifier(TSExportSpecifier specifier) { + final actualName = specifier.propertyName ?? specifier.name; + + final dartName = specifier.name; + + final decl = nodeMap.findByName(actualName.text); + + // This just guarantees the declaration is transformed before adding the + // export reference + if (decl.isEmpty) _getTypeFromDeclaration(actualName, []); + + exportSet.removeWhere((e) => e.name == actualName.text); + exportSet.add(ExportReference(actualName.text, as: dartName.text)); + } + + /// Parses an export declaration and converts it into an [ExportReference] + /// to be handled when returning results + void _parseExportDeclaration(TSExportDeclaration export) { + // TODO(nikeokoronkwo): Support namespace exports + if (export.exportClause?.kind == TSSyntaxKind.NamedExports) { + // named exports + final exports = (export.exportClause as TSNamedExports).elements.toDart; + + for (final exp in exports) { + // the name of the declaration in TS (name) + final actualName = (exp.propertyName ?? exp.name).text; + + // The exported name to use + final dartName = exp.name.text; + + exportSet.removeWhere((e) => e.name == actualName); + exportSet.add(ExportReference(actualName, as: dartName)); + } + } + } + + /// Transforms a TS Class or Interface declaration into a node representing + /// a class or interface respectively. TypeDeclaration _transformClassOrInterface(TSObjectDeclaration typeDecl) { final name = typeDecl.name.text; @@ -549,15 +632,31 @@ class Transformer { } return variable.declarationList.declarations.toDart.map((d) { - namer.markUsed(d.name.text); - return VariableDeclaration( - name: d.name.text, - type: d.type == null ? BuiltinType.anyType : _transformType(d.type!), - modifier: modifier, - exported: isExported); + return _transformVariableDecl(d, modifier, isExported); }).toList(); } + VariableDeclaration _transformVariableDecl(TSVariableDeclaration d, + [VariableModifier? modifier, bool? isExported]) { + final statement = d.parent.parent; + isExported ??= statement.modifiers.toDart.any((m) { + return m.kind == TSSyntaxKind.ExportKeyword; + }); + modifier ??= switch (statement.declarationList.flags) { + final TSNodeFlags f when f & TSNodeFlags.Const != 0 => + VariableModifier.$const, + final TSNodeFlags f when f & TSNodeFlags.Let != 0 => VariableModifier.let, + _ => VariableModifier.$var + }; + + namer.markUsed(d.name.text); + return VariableDeclaration( + name: d.name.text, + type: d.type == null ? BuiltinType.anyType : _transformType(d.type!), + modifier: modifier, + exported: isExported); + } + EnumDeclaration _transformEnum(TSEnumDeclaration enumeration) { final modifiers = enumeration.modifiers?.toDart; final isExported = modifiers?.any((m) { @@ -706,17 +805,19 @@ class Transformer { /// [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 + // TODO(nikeokoronkwo): Add support for constructor and function types, + // https://github.com/dart-lang/web/issues/410 + // https://github.com/dart-lang/web/issues/422 Type _transformType(TSTypeNode type, {bool parameter = false, bool typeArg = false}) { switch (type.kind) { + case TSSyntaxKind.ParenthesizedType: + return _transformType((type as TSParenthesizedTypeNode).type, + parameter: parameter, typeArg: typeArg); case TSSyntaxKind.TypeReference: final refType = type as TSTypeReferenceNode; - final typeName = refType.typeName; - final typeArguments = refType.typeArguments?.toDart; - - return _getTypeFromDeclaration(typeName, typeArguments, - typeArg: typeArg); + return _getTypeFromTypeRefNode(refType, typeArg: typeArg); case TSSyntaxKind.UnionType: final unionType = type as TSUnionTypeNode; // TODO: Unions @@ -804,7 +905,6 @@ class Transformer { 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 typeArguments = typeQuery.typeArguments?.toDart; @@ -848,12 +948,75 @@ class Transformer { _getTypeFromDeclaration(identifier, type.typeArguments?.toDart); return getTypeFromDeclaration; + } else if (type.expression.kind == TSSyntaxKind.PropertyAccessExpression) { + // TODO(nikeokoronkwo): Support Globbed Imports and Exports, https://github.com/dart-lang/web/issues/420 + throw UnimplementedError("The given type expression's expression of kind " + '${type.expression.kind} is not supported yet'); } else { throw UnimplementedError("The given type expression's expression of kind " '${type.expression.kind} is not supported yet'); } } + /// Get the type of a type node [node] by gettings its type from + /// the node itself via the [ts.TSTypeChecker] + /// + /// This has similar options as [_getTypeFromDeclaration] + Type _getTypeFromTypeRefNode(TSTypeReferenceNode node, + {List? typeArguments, + bool typeArg = false, + bool isNotTypableDeclaration = false}) { + typeArguments ??= node.typeArguments?.toDart; + final name = node.typeName.text; + + var declarationsMatching = nodeMap.findByName(name); + if (declarationsMatching.isEmpty) { + // check if builtin + // 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, + typeParams: (typeArguments ?? []) + .map((t) => getJSTypeAlternative(_transformType(t))) + .toList()); + if (supportedType case final resultType?) { + return resultType; + } + + var symbol = typeChecker.getSymbolAtLocation(node.typeName); + if (symbol == null) { + final type = typeChecker.getTypeFromTypeNode(node); + symbol = type?.aliasSymbol ?? type?.symbol; + } + + final (derivedType, newName) = _deriveTypeOrTransform( + symbol!, name, typeArguments, typeArg, isNotTypableDeclaration); + + if (derivedType != null) return derivedType; + + declarationsMatching = + nodeMap.findByName(newName != name ? newName : name); + } + + // 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; + } + + return firstNode.asReferredType( + (typeArguments ?? []) + .map((type) => _transformType(type, typeArg: true)) + .toList(), + ); + } + /// Get the type of a type node named [typeName] by referencing its /// declaration /// @@ -894,41 +1057,17 @@ class Transformer { } final symbol = typeChecker.getSymbolAtLocation(typeName); - final declarations = symbol?.getDeclarations(); + if (symbol case final s?) { + final (derivedType, newName) = _deriveTypeOrTransform( + s, name, typeArguments, typeArg, isNotTypableDeclaration); - // TODO: In the case of overloading, should/shouldn't we handle more than one declaration? - // TODO(https://github.com/dart-lang/web/issues/387): Some declarations may not be defined on file, - // and may be from an import statement - // We should be able to handle these - final declaration = declarations?.toDart.first; + if (derivedType != null) return derivedType; - if (declaration == null) { - throw Exception('Found no declaration matching $name'); - } - - if (!isNotTypableDeclaration) { - // 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); - } + declarationsMatching = + nodeMap.findByName(newName != name ? newName : name); + } else { + declarationsMatching = nodeMap.findByName(name); } - - transform(declaration); - - declarationsMatching = nodeMap.findByName(name); } // TODO: In the case of overloading, should/shouldn't we handle more than one declaration? @@ -960,6 +1099,147 @@ class Transformer { return asReferredType; } + /// Either derives the type referenced by the [symbol], or + /// transforms the declaration(s) associated with the [symbol]. + /// + /// Returns a record containing the type, if any, and the name of + /// the symbol, which, if not renamed by an export declaration, will + /// be the same as [name]. + (Type?, String) _deriveTypeOrTransform(TSSymbol symbol, + [String? name, + List? typeArguments, + bool typeArg = false, + bool isNotTypableDeclaration = false]) { + name ??= symbol.name; + + final declarations = symbol.getDeclarations(); + if (declarations == null) { + throw Exception('Found no declaration matching $name'); + } + + final declaration = declarations.toDart.first; + + if (declaration.kind == TSSyntaxKind.TypeParameter) { + return (GenericType(name: name), name); + } else if (declaration.kind == TSSyntaxKind.ImportSpecifier) { + // unravel import statement to get source file + final importSpecifier = declaration as TSImportSpecifer; + final importDecl = importSpecifier.parent.parent.parent; + var importUrl = importDecl.moduleSpecifier.text; + if (!importUrl.endsWith('ts')) importUrl = '$importUrl.d.ts'; + + final importedFile = + p.normalize(p.absolute(p.join(p.dirname(file), importUrl))); + + // get declaration in source file + final decl = programMap.getDeclarationRef( + importedFile, declaration, importSpecifier.name.text); + + if (decl == null) { + throw Exception( + 'Imported File not included in compilation: $importedFile'); + } + + final firstNode = + decl.firstWhere((d) => d is NamedDeclaration) as NamedDeclaration; + + if (!isNotTypableDeclaration && typeArg) { + switch (firstNode) { + case TypeAliasDeclaration(type: final t): + case EnumDeclaration(baseType: final t): + final jsType = getJSTypeAlternative(t); + if (jsType != t) return (jsType, name); + } + } + + final asReferredType = firstNode.asReferredType( + (typeArguments ?? []) + .map((type) => _transformType(type, typeArg: true)) + .toList(), + programMap.absoluteFiles.contains(importedFile) + ? p.normalize(importUrl.replaceFirst('.d.ts', '.dart')) + : null); + + if (asReferredType case ReferredDeclarationType(type: final type) + when type is BuiltinType && typeArg) { + final jsType = getJSTypeAlternative(type); + if (jsType != type) asReferredType.type = jsType; + } + + return (asReferredType, name); + } else if (declaration.kind == TSSyntaxKind.ExportSpecifier) { + // in order to prevent recursion, we need to find the source of the export + // specifier + final aliasedSymbol = typeChecker.getAliasedSymbol(symbol); + final aliasedSymbolName = aliasedSymbol.name; + + exportSet.removeWhere((e) => e.name == aliasedSymbolName); + exportSet.add(ExportReference(aliasedSymbolName, as: name)); + return _deriveTypeOrTransform(aliasedSymbol, aliasedSymbolName, + typeArguments, typeArg, isNotTypableDeclaration); + } + + // check if this is from dom + final declarationSource = declaration.getSourceFile().fileName; + + if ((p.basename(declarationSource) == 'lib.dom.d.ts' || + declarationSource.contains('dom')) && + !isNotTypableDeclaration) { + // 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()), + name + ); + } else if (declarationSource != file && + programMap.absoluteFiles.contains(declarationSource)) { + // get declaration from file source + // file path is absolute + // TODO: Handle star imports (`import * as Utils from "./utils"`) + final relativePath = p.relative(declarationSource, from: p.dirname(file)); + final referencedDeclarations = + programMap.getDeclarationRef(declarationSource, declaration, name); + + // TODO: In the case of overloading, should/shouldn't we handle more than one declaration? + final firstNode = + referencedDeclarations?.whereType().first; + if (!isNotTypableDeclaration && typeArg) { + // 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) return (jsType, name); + } + } + + if (firstNode case final node?) { + final outputType = node.asReferredType( + (typeArguments ?? []) + .map((type) => _transformType(type, typeArg: true)) + .toList(), + relativePath.replaceFirst('.d.ts', '.dart')); + + if (outputType case ReferredDeclarationType(type: final type) + when type is BuiltinType && typeArg) { + final jsType = getJSTypeAlternative(type); + if (jsType != type) outputType.type = jsType; + } + + return (outputType, name); + } + } + + transform(declaration); + return (null, name); + } + /// Filters out the declarations generated from the [transform] function and /// returns the declarations needed based on: /// @@ -975,6 +1255,29 @@ class Transformer { NodeMap filterAndReturn() { final filteredDeclarations = NodeMap(); + for (final ExportReference(name: exportName, as: exportDartName) + in exportSet) { + if (filterDeclSet.isEmpty || + filterDeclSetPatterns.any( + (p) => p.hasMatch(exportName) || p.hasMatch(exportDartName))) { + final nodes = nodeMap.findByName(exportName); + for (final exportedNode in nodes) { + // TODO: Is there a better way of handling name changes than having + // to make the properties non-final and override get/set? + // the actual decl name is `exportName` (dartName) + // while the name we want to use for @JS is `exportDartName` (name) + if (exportedNode case final ExportableDeclaration decl) { + filteredDeclarations.add(decl + ..name = exportDartName + ..dartName = + decl.dartName ?? UniqueNamer.makeNonConflicting(exportName)); + } else { + continue; + } + } + } + } + // filter out for export declarations nodeMap.forEach((id, node) { // get decls with `export` keyword @@ -1002,7 +1305,9 @@ class Transformer { .map((e) => _getDependenciesOfDecl(e.value)) .reduce((value, element) => value..addAll(element)); - return filteredDeclarations..addAll(otherDecls); + filteredDeclarations.addAll(otherDecls); + + return filteredDeclarations; } /// Given an already filtered declaration [decl], @@ -1093,7 +1398,9 @@ class Transformer { }); break; case final ReferredType r: - filteredDeclarations.add(r.declaration); + if (r.url == null) filteredDeclarations.add(r.declaration); + break; + case BuiltinType() || GenericType(): break; default: print('WARN: The given node type ${decl.runtimeType.toString()} ' diff --git a/web_generator/lib/src/js/helpers.dart b/web_generator/lib/src/js/helpers.dart new file mode 100644 index 00000000..f840a01a --- /dev/null +++ b/web_generator/lib/src/js/helpers.dart @@ -0,0 +1,142 @@ +// TODO: Add the following necessary types and extensions to `dart:js_interop` + +import 'dart:collection'; +import 'dart:js_interop'; + +/// A JS [Iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator) +/// There is no definition of the Iterator protocol in JS as it is **hidden**, +/// and implementations inherit from its prototype +/// +/// This can be used as the base type used for implementing iterators used in +/// Arrays, Maps, etc +/// which can then be translated to iterator interfaces and more used in +/// `dart:core`, `dart:async` and `dart:typed_data` +/// +/// The thing is: JavaScript handles iterators and their usage differently from +/// how Dart does +/// +/// When doing `for of` loops in JS, you use the `Iterator` itself, while in +/// Dart, you use an `Iterable` (or `Stream` for `await for of`) +/// +/// Implementations converting an `Iterator` to both a Dart `Iterator` and +/// `Iterable` are available +@JS('Iterator') +extension type JSIterator._(JSObject _) implements JSObject { + external factory JSIterator.from(JSIterator object); + + external JSIteratorResult next([T value]); + + // MDN says the rest are optional, but they are here for completeness. + // I've seen that `return` is useful when converting to Dart types, but most + // likely not `throw` + @JS('return') + external JSIteratorResult return$([T value]); +} + +extension type JSIteratorResult._(JSObject _) + implements JSObject { + external JSIteratorResult({bool done, T value}); + external bool get done; + external T get value; +} + +extension ToDartIterableIterator on JSIterator { + Iterator get toDart => JSIteratorRep._(this); + // TODO: Upgrade to SDK 3.8 to use `Iterable.withIterator` + Iterable get toDartIterable => iterableFromIterator(toDart); +} + +Iterable iterableFromIterator(Iterator iterator) sync* { + while (iterator.moveNext()) { + yield iterator.current; + } +} + +class JSIteratorRep implements Iterator { + JSIterator iterator; + + bool isDone = false; + T? _currentValue; + + JSIteratorRep._(this.iterator); + + @override + T get current { + if (isDone) { + throw Exception('Stream Iterator Done'); + } else if (_currentValue == null) { + throw Exception('No value'); + } else { + return _currentValue!; + } + } + + @override + bool moveNext() { + final value = iterator.next(); + if (value.done) { + _currentValue = null; + isDone = true; + } else { + _currentValue = value.value; + } + return !value.done; + } +} + +/// A JS [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) +/// +@JS('Map') +extension type JSMap._(JSObject _) + implements JSIterator { + external JSMap([JSArray> iterable]); + + external int get size; + + external bool delete(K key); + external bool has(K key); + external V? get(K key); + external JSMap set(K key, V value); + external void clear(); + + external JSIterator keys(); + external JSIterator values(); +} + +extension JSMapToMap on JSMap { + Map get toDart => JSMapRep._(this); +} + +// TODO: Should we make use of a `DelegatingMap`: +// https://pub.dev/documentation/collection/latest/collection/DelegatingMap-class.html +class JSMapRep extends MapBase { + final JSMap _map; + + JSMapRep._(this._map); + + @override + V? operator [](covariant K? key) => key == null ? null : _map.get(key); + + @override + void operator []=(K key, V value) => _map.set(key, value); + + @override + void clear() => _map.clear(); + + @override + Iterable get keys => _map.keys().toDartIterable; + + @override + Iterable get values => _map.values().toDartIterable; + + @override + V? remove(covariant K? key) { + if (key == null) { + return null; + } else { + final value = _map.get(key); + _map.delete(key); + return value; + } + } +} diff --git a/web_generator/lib/src/js/typescript.dart b/web_generator/lib/src/js/typescript.dart index af8fd98c..759cc01e 100644 --- a/web_generator/lib/src/js/typescript.dart +++ b/web_generator/lib/src/js/typescript.dart @@ -46,11 +46,16 @@ extension type TSProgram._(JSObject _) implements JSObject { @JS('TypeChecker') extension type TSTypeChecker._(JSObject _) implements JSObject { external TSSymbol? getSymbolAtLocation(TSNode node); + external TSSymbol getAliasedSymbol(TSSymbol symbol); + external TSType? getTypeFromTypeNode(TSTypeNode type); } +// TODO: Can we make use of `FileReference`s @JS('SourceFile') extension type TSSourceFile._(JSObject _) implements TSDeclaration { external String fileName; + external String? moduleName; + external String text; } extension type TSNodeCallback._(JSObject _) diff --git a/web_generator/lib/src/js/typescript.types.dart b/web_generator/lib/src/js/typescript.types.dart index bf75e374..8f86e899 100644 --- a/web_generator/lib/src/js/typescript.types.dart +++ b/web_generator/lib/src/js/typescript.types.dart @@ -11,6 +11,7 @@ import 'dart:js_interop'; import 'package:meta/meta.dart'; +import 'helpers.dart'; import 'typescript.dart'; extension type const TSSyntaxKind._(num _) { @@ -29,6 +30,9 @@ extension type const TSSyntaxKind._(num _) { static const TSSyntaxKind EnumDeclaration = TSSyntaxKind._(266); static const TSSyntaxKind PropertyDeclaration = TSSyntaxKind._(172); static const TSSyntaxKind MethodDeclaration = TSSyntaxKind._(174); + static const TSSyntaxKind ImportDeclaration = TSSyntaxKind._(272); + static const TSSyntaxKind ImportSpecifier = TSSyntaxKind._(276); + static const TSSyntaxKind Constructor = TSSyntaxKind._(176); static const TSSyntaxKind GetAccessor = TSSyntaxKind._(177); static const TSSyntaxKind SetAccessor = TSSyntaxKind._(178); @@ -37,6 +41,7 @@ extension type const TSSyntaxKind._(num _) { static const TSSyntaxKind MethodSignature = TSSyntaxKind._(173); static const TSSyntaxKind CallSignature = TSSyntaxKind._(179); static const TSSyntaxKind ConstructSignature = TSSyntaxKind._(180); + static const TSSyntaxKind ExportAssignment = TSSyntaxKind._(277); /// expressions static const TSSyntaxKind NumericLiteral = TSSyntaxKind._(9); @@ -50,6 +55,8 @@ extension type const TSSyntaxKind._(num _) { static const TSSyntaxKind DeclareKeyword = TSSyntaxKind._(138); static const TSSyntaxKind ExtendsKeyword = TSSyntaxKind._(96); static const TSSyntaxKind ImplementsKeyword = TSSyntaxKind._(119); + static const TSSyntaxKind WithKeyword = TSSyntaxKind._(118); + static const TSSyntaxKind AssertKeyword = TSSyntaxKind._(132); static const TSSyntaxKind AbstractKeyword = TSSyntaxKind._(128); // keywords for scope @@ -79,14 +86,19 @@ extension type const TSSyntaxKind._(num _) { static const TSSyntaxKind LiteralType = TSSyntaxKind._(201); static const TSSyntaxKind ThisType = TSSyntaxKind._(197); static const TSSyntaxKind TypeQuery = TSSyntaxKind._(186); + static const TSSyntaxKind ParenthesizedType = TSSyntaxKind._(196); /// Other static const TSSyntaxKind Identifier = TSSyntaxKind._(80); + static const TSSyntaxKind PropertyAccessExpression = TSSyntaxKind._(211); static const TSSyntaxKind ObjectBindingPattern = TSSyntaxKind._(206); static const TSSyntaxKind ArrayBindingPattern = TSSyntaxKind._(207); static const TSSyntaxKind TypeParameter = TSSyntaxKind._(168); static const TSSyntaxKind HeritageClause = TSSyntaxKind._(298); static const TSSyntaxKind ExpressionWithTypeArguments = TSSyntaxKind._(233); + static const TSSyntaxKind NamespaceExport = TSSyntaxKind._(280); + static const TSSyntaxKind NamedExports = TSSyntaxKind._(279); + static const TSSyntaxKind ExportSpecifier = TSSyntaxKind._(281); } extension type const TSNodeFlags._(int _) implements int { @@ -128,8 +140,6 @@ 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 @@ -141,8 +151,6 @@ extension type TSTypeQueryNode._(JSObject _) implements TSTypeNode { external TSNodeArray? get typeArguments; } -// TODO(nikeokoronkwo): Implements TSNodeWithTypeArguments -// once #402 and #409 are closed @JS('TypeReferenceNode') extension type TSTypeReferenceNode._(JSObject _) implements TSTypeNode { @redeclare @@ -160,6 +168,13 @@ extension type TSLiteralTypeNode._(JSObject _) implements TSTypeNode { external TSLiteral get literal; } +@JS('ParenthesizedTypeNode') +extension type TSParenthesizedTypeNode._(JSObject _) implements TSTypeNode { + @redeclare + TSSyntaxKind get kind => TSSyntaxKind.ParenthesizedType; + external TSTypeNode get type; +} + @JS('Expression') extension type TSExpression._(JSObject _) implements TSNode {} @@ -169,6 +184,14 @@ extension type TSLiteralExpression._(JSObject _) implements TSExpression { external bool? isUnterminated; } +@JS('PropertyAccessExpression') +extension type TSPropertyAccessExpression._(JSObject _) + implements TSNamedDeclaration, TSExpression { + external TSExpression get expression; + external TSToken? get questionDotToken; + external TSIdentifier get name; +} + @JS('Declaration') extension type TSDeclaration._(JSObject _) implements TSNode {} @@ -196,6 +219,97 @@ extension type TSIdentifier._(JSObject _) implements TSDeclaration { external String get text; } +@JS('NamedDeclaration') +extension type TSNamedDeclaration._(JSObject _) implements TSNode { + // TODO: Support other name specifiers + external TSIdentifier? get name; +} + +@JS('DeclarationStatement') +extension type TSDeclarationStatement._(JSObject _) + implements TSNamedDeclaration, TSStatement { + external TSIdentifier? get name; +} + +@JS('ImportAttribute') +extension type TSImportAttribute._(JSObject _) implements TSNode { + external TSIdentifier get name; + external TSExpression get value; +} + +@JS('ImportAttributes') +extension type TSImportAttributes._(JSObject _) implements TSNode { + external TSSyntaxKind get token; + external TSNodeArray get elements; +} + +@JS('NamedImports') +extension type TSNamedImports._(JSObject _) implements TSNode { + @redeclare + external TSImportClause get parent; + external TSNodeArray get elements; +} + +@JS('NamespaceExport') +extension type TSNamespaceExport._(JSObject _) implements TSNamedDeclaration { + external TSIdentifier get name; +} + +@JS('NamedExports') +extension type TSNamedExports._(JSObject _) implements TSNode { + external TSNodeArray get elements; +} + +@JS('ExportSpecifier') +extension type TSExportSpecifier._(JSObject _) implements TSNamedDeclaration { + external bool get isTypeOnly; + external TSIdentifier get name; + external TSIdentifier? get propertyName; +} + +@JS('ImportSpecifer') +extension type TSImportSpecifer._(JSObject _) implements TSNamedDeclaration { + external TSIdentifier get name; + external TSIdentifier? get propertyName; + external bool get isTypeOnly; + @redeclare + external TSNamedImports get parent; +} + +@JS('ImportDeclaration') +extension type TSImportDeclaration._(JSObject _) implements TSStatement { + external TSNodeArray? get modifiers; + external TSStringLiteral get moduleSpecifier; + external TSImportAttributes? get attributes; +} + +@JS('ImportClause') +extension type TSImportClause._(JSObject _) implements TSNamedDeclaration { + external bool get isTypeOnly; + external TSIdentifier? get name; + external TSNamedDeclaration? get namedBindings; + @redeclare + external TSImportDeclaration get parent; +} + +@JS('ExportDeclaration') +extension type TSExportDeclaration._(JSObject _) + implements TSDeclarationStatement { + external TSNodeArray? get modifiers; + external bool get isTypeOnly; + external TSNode? get exportClause; + external TSExpression? get moduleSpecifier; + external TSImportAttributes? get attributes; +} + +@JS('ExportAssignment') +extension type TSExportAssignment._(JSObject _) + implements TSDeclarationStatement { + external TSNodeArray? get modifiers; + external bool? get isExportEquals; + external TSExpression get expression; +} + @JS('VariableStatement') extension type TSVariableStatement._(JSObject _) implements TSStatement { external TSVariableDeclarationList get declarationList; @@ -204,18 +318,23 @@ extension type TSVariableStatement._(JSObject _) implements TSStatement { @JS('VariableDeclarationList') extension type TSVariableDeclarationList._(JSObject _) implements TSNode { + @redeclare + external TSVariableStatement get parent; external TSNodeArray get declarations; } @JS('VariableDeclaration') -extension type TSVariableDeclaration._(JSObject _) implements TSDeclaration { +extension type TSVariableDeclaration._(JSObject _) + implements TSNamedDeclaration { + @redeclare + external TSVariableDeclarationList get parent; external TSIdentifier get name; external TSTypeNode? get type; } @JS('SignatureDeclarationBase') extension type TSSignatureDeclarationBase._(JSObject _) - implements TSDeclaration { + implements TSNamedDeclaration { external TSNodeArray get parameters; external TSNodeArray? get typeParameters; external TSTypeNode? get type; @@ -423,4 +542,13 @@ extension type TSNodeArray._(JSArray _) extension type TSSymbol._(JSObject _) implements JSObject { external String get name; external JSArray? getDeclarations(); + external TSSymbolTable? get exports; +} + +typedef TSSymbolTable = JSMap; + +@JS('Type') +extension type TSType._(JSObject _) implements JSObject { + external TSSymbol get symbol; + external TSSymbol? get aliasSymbol; } diff --git a/web_generator/test/assets/test_config.dart b/web_generator/test/assets/test_config.dart index d1564c96..f40ca94f 100644 --- a/web_generator/test/assets/test_config.dart +++ b/web_generator/test/assets/test_config.dart @@ -8,8 +8,6 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:js_interop' as _i1; -@_i1.JS() -external String get APP_NAME; @_i1.JS() external User loginUser( String username, @@ -49,6 +47,8 @@ external void logoutUser(User user); @_i1.JS() external void toggleAdminMode(); @_i1.JS() +external String get APP_NAME; +@_i1.JS() external User get user1; @_i1.JS() external User get user2; diff --git a/web_generator/test/assets/test_no_config.dart b/web_generator/test/assets/test_no_config.dart index 305d2c6e..833ff6f6 100644 --- a/web_generator/test/assets/test_no_config.dart +++ b/web_generator/test/assets/test_no_config.dart @@ -3,10 +3,6 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:js_interop' as _i1; -@_i1.JS() -external String get APP_NAME; -@_i1.JS() -external String get APP_NAMES; @_i1.JS() external void initializeApp(); @_i1.JS() @@ -44,6 +40,10 @@ external void toggleAdminMode(); @_i1.JS() external void processHttpRequest(HttpStatusCode statusCode); @_i1.JS() +external String get APP_NAME; +@_i1.JS() +external String get APP_NAMES; +@_i1.JS() external User get user1; @_i1.JS() external User get user2; diff --git a/web_generator/test/integration/interop_gen/classes_expected.dart b/web_generator/test/integration/interop_gen/classes_expected.dart index 10859352..244db760 100644 --- a/web_generator/test/integration/interop_gen/classes_expected.dart +++ b/web_generator/test/integration/interop_gen/classes_expected.dart @@ -2,42 +2,13 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:js_interop' as _i1; -import 'package:meta/meta.dart' as _i2; - -extension type Configuration._(_i1.JSObject _) implements _i1.JSObject { - external Configuration( - String version, - String apiUrl, - ); - external String get version; - external String get apiUrl; -} -extension type Product._(_i1.JSObject _) implements _i1.JSObject { - external Product( - String name, - num price, - num quantity, - ); - - external String get name; - external set price(num newPrice); - external double get price; - external set quantity(num newQuantity); - external double get quantity; - external double get totalPrice; -} -extension type User._(_i1.JSObject _) implements _i1.JSObject { - external User( - num id, - String username, - String email, - ); +import 'package:meta/meta.dart' as _i2; - external double id; +extension type Point2D._(_i1.JSObject _) implements _i1.JSObject { + external double x; - external String greet(); - external String getEmail(); + external double y; } extension type Shape._(_i1.JSObject _) implements _i1.JSObject {} extension type Shape2D._(_i1.JSObject _) implements Shape { @@ -45,10 +16,21 @@ extension type Shape2D._(_i1.JSObject _) implements Shape { external double get perimeter; external double get area; } -extension type Shape3D._(_i1.JSObject _) implements Shape { - external double get volume; - external double surfaceArea(); +extension type Circle._(_i1.JSObject _) implements Shape2D { + external Circle(num radius); + + external double radius; + + @_i2.redeclare + external double get area; + @_i2.redeclare + external double get perimeter; } +@_i1.JS() +external Circle drawCircle( + Point2D center, + num radius, +); extension type Rectangle._(_i1.JSObject _) implements Shape2D { external Rectangle( num length, @@ -71,15 +53,69 @@ extension type Square._(_i1.JSObject _) implements Rectangle { external double length; } -extension type Circle._(_i1.JSObject _) implements Shape2D { - external Circle(num radius); +@_i1.JS() +external Square drawSquare( + Point2D start, + num length, [ + num? angle, +]); +extension type Point3D._(_i1.JSObject _) implements _i1.JSObject { + external double x; + + external double y; + + external double z; +} +extension type Shape3D._(_i1.JSObject _) implements Shape { + external double get volume; + external double surfaceArea(); +} +extension type Sphere._(_i1.JSObject _) implements Shape3D { + external Sphere(num radius); external double radius; @_i2.redeclare - external double get area; + external double get volume; @_i2.redeclare - external double get perimeter; + external double surfaceArea(); +} +@_i1.JS() +external Sphere drawSphere(Point3D center); +extension type Configuration._(_i1.JSObject _) implements _i1.JSObject { + external Configuration( + String version, + String apiUrl, + ); + + external String get version; + external String get apiUrl; +} +extension type Product._(_i1.JSObject _) implements _i1.JSObject { + external Product( + String name, + num price, + num quantity, + ); + + external String get name; + external set price(num newPrice); + external double get price; + external set quantity(num newQuantity); + external double get quantity; + external double get totalPrice; +} +extension type User._(_i1.JSObject _) implements _i1.JSObject { + external User( + num id, + String username, + String email, + ); + + external double id; + + external String greet(); + external String getEmail(); } extension type Prism._(_i1.JSObject _) implements Shape3D { external Prism( @@ -156,16 +192,6 @@ extension type Cone._(_i1.JSObject _) implements Pyramid { @_i2.redeclare external double surfaceArea(); } -extension type Sphere._(_i1.JSObject _) implements Shape3D { - external Sphere(num radius); - - external double radius; - - @_i2.redeclare - external double get volume; - @_i2.redeclare - external double surfaceArea(); -} extension type Hemi._(_i1.JSObject _) implements Shape3D { external Hemi(S shape); @@ -177,18 +203,6 @@ extension type Hemi._(_i1.JSObject _) implements Shape3D { external double surfaceArea(); } typedef HemiSphere = Hemi; -extension type Point2D._(_i1.JSObject _) implements _i1.JSObject { - external double x; - - external double y; -} -extension type Point3D._(_i1.JSObject _) implements _i1.JSObject { - external double x; - - external double y; - - external double z; -} @_i1.JS() external Point2D get origin2D; @_i1.JS() @@ -258,19 +272,6 @@ extension type Vector3D._(_i1.JSObject _) implements Vector { Point3D end, ); } -@_i1.JS() -external Circle drawCircle( - Point2D center, - num radius, -); -@_i1.JS() -external Square drawSquare( - Point2D start, - num length, [ - num? angle, -]); -@_i1.JS() -external Sphere drawSphere(Point3D center); extension type EpahsImpl._(_i1.JSObject _) implements Epahs { external EpahsImpl( diff --git a/web_generator/test/integration/interop_gen/enum_expected.dart b/web_generator/test/integration/interop_gen/enum_expected.dart index dc775e93..8a495184 100644 --- a/web_generator/test/integration/interop_gen/enum_expected.dart +++ b/web_generator/test/integration/interop_gen/enum_expected.dart @@ -3,6 +3,15 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:js_interop' as _i1; +extension type const Status._(int _) { + static const Status Active = Status._(1); + + static const Status Inactive = Status._(0); + + static const Status Pending = Status._(2); +} +@_i1.JS() +external void logStatus(Status status); extension type const Direction._(int _) { static const Direction Up = Direction._(0); @@ -12,6 +21,22 @@ extension type const Direction._(int _) { static const Direction Right = Direction._(3); } +@_i1.JS() +external String handleDirection(Direction dir); +extension type const Permissions._(int _) { + static const Permissions Read = Permissions._(1); + + static const Permissions Write = Permissions._(2); + + static const Permissions Execute = Permissions._(4); + + static const Permissions All = Permissions._(7); +} +@_i1.JS() +external bool hasPermission( + Permissions perm, + Permissions flag, +); extension type const ResponseCode._(int _) { static const ResponseCode Success = ResponseCode._(200); @@ -51,19 +76,8 @@ extension type BooleanLike._(_i1.JSAny? _) { static final BooleanLike Yes = BooleanLike._('YES'.toJS); } -extension type const Status._(int _) { - static const Status Active = Status._(1); - - static const Status Inactive = Status._(0); - - static const Status Pending = Status._(2); -} @_i1.JS() external Status get statusFromName; -@_i1.JS() -external void logStatus(Status status); -@_i1.JS() -external String handleDirection(Direction dir); extension type const HttpStatus._(int _) { static const HttpStatus OK = HttpStatus._(200); @@ -94,20 +108,6 @@ extension type SomeRandomEnumValues._(_i1.JSAny? _) { external static SomeRandomEnumValues unknown; } -extension type const Permissions._(int _) { - static const Permissions Read = Permissions._(1); - - static const Permissions Write = Permissions._(2); - - static const Permissions Execute = Permissions._(4); - - static const Permissions All = Permissions._(7); -} -@_i1.JS() -external bool hasPermission( - Permissions perm, - Permissions flag, -); @_i1.JS() external Permissions get userPermissions; @_i1.JS() diff --git a/web_generator/test/integration/interop_gen/functions_expected.dart b/web_generator/test/integration/interop_gen/functions_expected.dart index f615bb69..3cd8ad8a 100644 --- a/web_generator/test/integration/interop_gen/functions_expected.dart +++ b/web_generator/test/integration/interop_gen/functions_expected.dart @@ -19,6 +19,8 @@ external _i1.JSPromise delay( ]); @_i1.JS() external _i1.JSArray<_i1.JSNumber> toArray(num a); +@_i1.JS('toArray') +external _i1.JSArray<_i1.JSString> toArray$1(String a); @_i1.JS() external double square(num a); @_i1.JS() @@ -28,8 +30,6 @@ external double pow$1( num a, num power, ); -@_i1.JS('toArray') -external _i1.JSArray<_i1.JSString> toArray$1(String a); @_i1.JS() external _i1.JSObject createUser( String name, [ diff --git a/web_generator/test/integration/interop_gen/import_export_expected.dart b/web_generator/test/integration/interop_gen/import_export_expected.dart new file mode 100644 index 00000000..6679669b --- /dev/null +++ b/web_generator/test/integration/interop_gen/import_export_expected.dart @@ -0,0 +1,163 @@ +// 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; + +import 'package:meta/meta.dart' as _i2; + +@_i1.JS() +external Point2D get origin; +@_i1.JS() +external Point3D get origin3D; +@_i1.JS('scalarProduct') +external V dotProduct( + V v1, + V v2, +); +@_i1.JS('vectorProduct') +external Vector3D crossProduct( + Vector3D v1, + Vector3D v2, +); +@_i1.JS() +external Vector3D mapTo3D(Vector2D v); +extension type TransformerMatrix._(_i1.JSObject _) + implements Matrix { + external V call(V v); +} +extension type ComparatorMatrix._(_i1.JSObject _) + implements Matrix, Comparator {} +@_i1.JS() +external _i1.JSArray get unitVectors; +@_i1.JS('PolarPoint') +extension type PolarCoordinate._(_i1.JSObject _) implements _i1.JSObject { + external double magnitude; + + external double angle; +} +@_i1.JS('CylindricalPoint') +extension type CylindricalCoordinate._(_i1.JSObject _) implements _i1.JSObject { + external double radius; + + external double angle; + + external double z; +} +@_i1.JS('SphericalPoint') +extension type SphericalCoordinate._(_i1.JSObject _) implements _i1.JSObject { + external double magnitude; + + external double theta; + + external double tau; +} +@_i1.JS() +external PolarCoordinate toPolarCoordinate(Point2D point); +@_i1.JS() +external SphericalCoordinate toSphericalCoordinate(Point3D point); +@_i1.JS('toSphericalCoordinate') +external SphericalCoordinate toSphericalCoordinate$1( + CylindricalCoordinate point); +@_i1.JS() +external CylindricalCoordinate toCylindricalCoordinate(Point3D point); +@_i1.JS() +external Vector2D get unitI2D; +@_i1.JS() +external Vector2D get unitJ2D; +@_i1.JS() +external Vector3D get unitI3D; +@_i1.JS() +external Vector3D get unitJ3D; +@_i1.JS() +external Vector3D get unitK3D; +extension type Point2D._(_i1.JSObject _) implements _i1.JSObject { + external double x; + + external double y; +} +extension type Point3D._(_i1.JSObject _) implements _i1.JSObject { + external double x; + + external double y; + + external double z; +} +extension type Vector._(_i1.JSObject _) implements _i1.JSObject { + external double get magnitude; + external double get directionAngle; +} +extension type Vector3D._(_i1.JSObject _) implements Vector { + external Vector3D( + num x, + num y, + num z, + ); + + external double x; + + external double y; + + external double z; + + external Vector3D unit(); + @_i2.redeclare + external double get magnitude; + external DirectionAngles get directionAngles; + @_i2.redeclare + external double get directionAngle; + external Point3D moveFrom(Point3D point); + external static Vector3D from( + num magnitude, + DirectionAngles at, + ); + external static Vector3D fromPoints( + Point3D start, + Point3D end, + ); +} +extension type DirectionAngles._(_i1.JSObject _) implements _i1.JSObject { + external double alpha; + + external double beta; + + external double gamma; +} +extension type Vector2D._(_i1.JSObject _) implements Vector { + external Vector2D( + num x, + num y, + ); + + external double x; + + external double y; + + external Vector2D unit(); + @_i2.redeclare + external double get magnitude; + @_i2.redeclare + external double get directionAngle; + external Point2D moveFrom(Point2D point); + external static Vector2D from( + num magnitude, + num at, + ); + external static Vector2D fromPoints( + Point2D start, + Point2D end, + ); +} +extension type Matrix._(_i1.JSObject _) implements _i1.JSObject { + external double rows; + + external double columns; + + external _i1.JSArray<_i1.JSNumber> operator [](num index); +} +extension type Comparator._(_i1.JSObject _) + implements _i1.JSObject { + external double call( + T a, + T b, + ); +} diff --git a/web_generator/test/integration/interop_gen/import_export_input.d.ts b/web_generator/test/integration/interop_gen/import_export_input.d.ts new file mode 100644 index 00000000..89a55399 --- /dev/null +++ b/web_generator/test/integration/interop_gen/import_export_input.d.ts @@ -0,0 +1,51 @@ +import { Vector, Vector2D, Vector3D, Point2D, Point3D, origin2D as origin, origin3D } from "./classes_input" +import { Comparator } from "./interfaces_input" +interface PolarCoordinate { + magnitude: number; + angle: number; +} +interface CylindricalCoordinate { + radius: number; + angle: number; + z: number; +} +interface SphericalCoordinate { + magnitude: number; + theta: number; + tau: number; +} +interface Matrix { + [index: number]: number[]; + rows: number; + columns: number; +} +interface TransformerMatrix extends Matrix { + (v: V): V; +} +interface ComparatorMatrix extends Matrix, Comparator {} +declare function dotProduct(v1: V, v2: V): V; +declare function crossProduct(v1: Vector3D, v2: Vector3D): Vector3D; +declare function mapTo3D(v: Vector2D): Vector3D; +declare function toPolarCoordinate(point: Point2D): PolarCoordinate; +declare function toCylindricalCoordinate(point: Point3D): CylindricalCoordinate; +declare function toSphericalCoordinate(point: Point3D): SphericalCoordinate; +declare function toSphericalCoordinate(point: CylindricalCoordinate): SphericalCoordinate; +export declare const unitI2D: Vector2D; +export declare const unitJ2D: Vector2D; +export declare const unitI3D: Vector3D; +export declare const unitJ3D: Vector3D; +export declare const unitK3D: Vector3D; +declare const ijk: Vector3D[]; +export { + origin, origin3D, dotProduct as scalarProduct, crossProduct as vectorProduct, mapTo3D, + TransformerMatrix, ComparatorMatrix, + ijk as unitVectors +} +export { + PolarCoordinate as PolarPoint, + CylindricalCoordinate as CylindricalPoint, + SphericalCoordinate as SphericalPoint, + toPolarCoordinate, + toSphericalCoordinate, + toCylindricalCoordinate +} diff --git a/web_generator/test/integration/interop_gen/project/config.yaml b/web_generator/test/integration/interop_gen/project/config.yaml new file mode 100644 index 00000000..4439924d --- /dev/null +++ b/web_generator/test/integration/interop_gen/project/config.yaml @@ -0,0 +1,7 @@ +name: Interop Gen Integration Test +# TODO: Support globs and dirs +input: + - input/a.d.ts + - input/b.d.ts + - input/c.d.ts +output: ../../../../.dart_tool/interop_gen_project diff --git a/web_generator/test/integration/interop_gen/project/input/a.d.ts b/web_generator/test/integration/interop_gen/project/input/a.d.ts new file mode 100644 index 00000000..9445ac49 --- /dev/null +++ b/web_generator/test/integration/interop_gen/project/input/a.d.ts @@ -0,0 +1,68 @@ +import { Vector, Vector2D, Vector3D, Point2D, Point3D, origin2D as origin, origin3D, CoordinateSystem, origin2D } from "./b" +import { Comparator } from "./c" +interface PolarCoordinate { + magnitude: number; + angle: number; +} +interface CylindricalCoordinate { + radius: number; + angle: number; + z: number; +} +interface SphericalCoordinate { + magnitude: number; + theta: number; + tau: number; +} +interface Matrix { + [index: number]: number[]; + rows: number; + columns: number; +} +interface TransformerMatrix extends Matrix { + (v: V): V; +} +export declare class CoordinateSystem2D implements CoordinateSystem { + constructor(origin: typeof origin2D); + points: Point2D[]; + readonly origin: Point2D; + addPoint(point: Point2D): void; + addVector(vector: Vector2D, start?: Point2D): void; + static get xAxis(): typeof unitI2D; + static get yAxis(): typeof unitJ2D; +} +export declare class CoordinateSystem3D implements CoordinateSystem { + constructor(origin: typeof origin3D); + points: Point3D[]; + readonly origin: Point3D; + addPoint(point: Point3D): void; + addVector(vector: Vector3D, start?: Point3D): void; + static get xAxis(): typeof unitI3D; + static get yAxis(): typeof unitJ3D; + static get zAxis(): typeof unitK3D; +} +interface ComparatorMatrix extends Matrix, Comparator {} +declare function dotProduct(v1: V, v2: V): V; +declare function crossProduct(v1: Vector3D, v2: Vector3D): Vector3D; +declare function mapTo3D(v: Vector2D): Vector3D; +declare function toPolarCoordinate(point: Point2D): PolarCoordinate; +declare function toCylindricalCoordinate(point: Point3D): CylindricalCoordinate; +declare function toSphericalCoordinate(point: Point3D): SphericalCoordinate; +declare function toSphericalCoordinate(point: CylindricalCoordinate): SphericalCoordinate; +export declare const unitI2D: Vector2D; +export declare const unitJ2D: Vector2D; +export declare const unitI3D: Vector3D; +export declare const unitJ3D: Vector3D; +export declare const unitK3D: Vector3D; +export { + origin, origin3D, dotProduct, crossProduct, mapTo3D, + TransformerMatrix, ComparatorMatrix +} +export { + PolarCoordinate as PolarPoint, + CylindricalCoordinate as CylindricalPoint, + SphericalCoordinate as SphericalPoint, + toPolarCoordinate, + toSphericalCoordinate, + toCylindricalCoordinate +} diff --git a/web_generator/test/integration/interop_gen/project/input/b.d.ts b/web_generator/test/integration/interop_gen/project/input/b.d.ts new file mode 100644 index 00000000..539393a6 --- /dev/null +++ b/web_generator/test/integration/interop_gen/project/input/b.d.ts @@ -0,0 +1,156 @@ +export interface Shape { +} +export interface Shape2D extends Shape { + get perimeter(): number; + get area(): number; + readonly sides?: number; +} +export interface Shape3D extends Shape { + get volume(): number; + surfaceArea(): number; +} +export declare class Rectangle implements Shape2D { + length: number; + width: number; + sides: number; + constructor(length: number, width: number); + get area(): number; + get perimeter(): number; +} +export declare class Square extends Rectangle { + length: number; + constructor(length: number); +} +export declare class Circle implements Shape2D { + radius: number; + constructor(radius: number); + get area(): number; + get perimeter(): number; +} +export declare class Prism implements Shape3D { + readonly surface: S; + height: number; + constructor(surface: S, height: number); + get volume(): number; + surfaceArea(): number; +} +export declare class Pyramid implements Shape3D { + readonly surface: S; + height: number; + constructor(surface: S, height: number); + get volume(): number; + surfaceArea(): number; +} +export declare class Cylinder extends Prism { + radius: number; + constructor(radius: number, height: number); + surfaceArea(): number; +} +export declare class Cuboid extends Prism { + length: number; + width: number; + height: number; + constructor(length: number, width: number, height: number); +} +export declare class Cube extends Prism { + length: number; + constructor(length: number); + get volume(): number; + surfaceArea(): number; +} +export declare class Cone extends Pyramid { + radius: number; + height: number; + constructor(radius: number, height: number); + surfaceArea(): number; +} +export declare class Sphere implements Shape3D { + radius: number; + constructor(radius: number); + get volume(): number; + surfaceArea(): number; +} +export declare class Hemi implements Shape3D { + readonly shape: S; + constructor(shape: S); + static prism(p: Prism): Prism; + get volume(): number; + surfaceArea(): number; +} +export type HemiSphere = Hemi; +interface Point {} +export interface Point2D extends Point { + x: number; + y: number; +} +export interface Point3D extends Point { + x: number; + y: number; + z: number; +} +export declare const origin2D: Point2D; +export declare const origin3D: Point3D; +export interface Vector { + get magnitude(): number; + get directionAngle(): number; +} +export declare class Vector2D implements Vector { + x: number; + y: number; + constructor(x: number, y: number); + unit(): Vector2D; + get magnitude(): number; + get directionAngle(): number; + moveFrom(point: Point2D): Point2D; + static from(magnitude: number, at: number): Vector2D; + static fromPoints(start: Point2D, end: Point2D): Vector2D; +} +export declare class Vector3D implements Vector { + x: number; + y: number; + z: number; + constructor(x: number, y: number, z: number); + unit(): Vector3D; + get magnitude(): number; + get directionAngles(): DirectionAngles; + get directionAngle(): number; + moveFrom(point: Point3D): Point3D; + static from(magnitude: number, at: DirectionAngles): Vector3D; + static fromPoints(start: Point3D, end: Point3D): Vector3D; +} +export interface DirectionAngles { + alpha: number; + beta: number; + gamma: number; +} +export declare function drawCircle(center: Point2D, radius: number): Circle; +export declare function drawSquare(start: Point2D, length: number, angle?: number): Square; +export declare function drawSphere(center: Point3D): Sphere; + +interface Epahs { + readonly id: string; + name: string; + area(): number; + area(unit: 'cm2' | 'in2'): string; + onUpdate?(prev: Epahs): void; +} +export declare class EpahsImpl implements Epahs { + readonly id: string; + name: string; + /* other decls in Shape */ + metadata?: TMeta; + constructor(name: string, type?: 'circle' | 'rectangle' | 'polygon'); + onUpdate?(prev: Epahs): void; + constructor(config: Epahs); + get location(): string; + set location(value: string); + area(): number; + area(unit: 'cm2' | 'in2'): string; + static getById(id: string): EpahsImpl; + toString(): string; +} +export interface CoordinateSystem

{ + readonly origin: P; + points: P[]; + addPoint(point: P): void; +} diff --git a/web_generator/test/integration/interop_gen/project/input/c.d.ts b/web_generator/test/integration/interop_gen/project/input/c.d.ts new file mode 100644 index 00000000..63dcadab --- /dev/null +++ b/web_generator/test/integration/interop_gen/project/input/c.d.ts @@ -0,0 +1,49 @@ +export interface ILogger { + readonly name: string; + level?: "debug" | "info" | "warn" | "error"; + log(message: string): void; + error(message: string): void; + flush?(): Promise; +} +export interface Dictionary { + [key: string]: string; +} +export interface Comparator { + (a: T, b: T): number; +} +export interface Repository { + findById(id: string): T; + save(entity: T): void; +} +export interface RepoConstructor { + new (args: string[]): any; +} +export interface AsyncService { + fetchData(url: string): Promise; + updateData(id: string, payload: string): Promise; +} +export interface User { + id: string; + email: string; + describe?(): string; +} +export interface Admin extends User, ILogger { + role: string; + grantPermission(permission: string): void; +} +export interface Config { + env: string; +} +export interface Config { + debug: boolean; +} +export interface SecureResource { + accessToken: string; + authenticate(): boolean; +} +interface LinkedList { + next(): this; +} +export declare const dict: Dictionary; +export declare const rootList: LinkedList; +export declare const compareNumbers: Comparator; diff --git a/web_generator/test/integration/interop_gen/project/input/e.d.ts b/web_generator/test/integration/interop_gen/project/input/e.d.ts new file mode 100644 index 00000000..8dcbc7a7 --- /dev/null +++ b/web_generator/test/integration/interop_gen/project/input/e.d.ts @@ -0,0 +1,27 @@ +export declare class Configuration { + readonly version: string; + readonly apiUrl: string; + constructor(version: string, apiUrl: string); +} +export declare class Product { + private _name; + private _price; + private _quantity; + constructor(name: string, price: number, quantity: number); + get name(): string; + set price(newPrice: number); + get price(): number; + set quantity(newQuantity: number); + get quantity(): number; + get totalPrice(): number; +} +export declare class User { + id: number; + protected username: string; + private email; + constructor(id: number, // Public property + username: string, // Protected property + email: string); + greet(): string; + getEmail(): string; +} diff --git a/web_generator/test/integration/interop_gen/project/output/a.dart b/web_generator/test/integration/interop_gen/project/output/a.dart new file mode 100644 index 00000000..91ffd510 --- /dev/null +++ b/web_generator/test/integration/interop_gen/project/output/a.dart @@ -0,0 +1,115 @@ +// 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; + +import 'package:meta/meta.dart' as _i4; + +import 'b.dart' as _i2; +import 'c.dart' as _i3; + +@_i1.JS() +external _i2.Point2D get origin; +@_i1.JS() +external _i2.Point3D get origin3D; +@_i1.JS() +external V dotProduct( + V v1, + V v2, +); +@_i1.JS() +external _i2.Vector3D crossProduct( + _i2.Vector3D v1, + _i2.Vector3D v2, +); +@_i1.JS() +external _i2.Vector3D mapTo3D(_i2.Vector2D v); +extension type TransformerMatrix._(_i1.JSObject _) + implements Matrix { + external V call(V v); +} +extension type ComparatorMatrix._(_i1.JSObject _) + implements Matrix, _i3.Comparator {} +@_i1.JS('PolarPoint') +extension type PolarCoordinate._(_i1.JSObject _) implements _i1.JSObject { + external double magnitude; + + external double angle; +} +@_i1.JS('CylindricalPoint') +extension type CylindricalCoordinate._(_i1.JSObject _) implements _i1.JSObject { + external double radius; + + external double angle; + + external double z; +} +@_i1.JS('SphericalPoint') +extension type SphericalCoordinate._(_i1.JSObject _) implements _i1.JSObject { + external double magnitude; + + external double theta; + + external double tau; +} +@_i1.JS() +external PolarCoordinate toPolarCoordinate(_i2.Point2D point); +@_i1.JS() +external SphericalCoordinate toSphericalCoordinate(_i2.Point3D point); +@_i1.JS('toSphericalCoordinate') +external SphericalCoordinate toSphericalCoordinate$1( + CylindricalCoordinate point); +@_i1.JS() +external CylindricalCoordinate toCylindricalCoordinate(_i2.Point3D point); +@_i1.JS() +external _i2.Vector2D get unitI2D; +@_i1.JS() +external _i2.Vector2D get unitJ2D; +extension type CoordinateSystem2D._(_i1.JSObject _) + implements _i2.CoordinateSystem<_i2.Point2D> { + external CoordinateSystem2D(_i2.Point2D origin); + + external _i1.JSArray<_i2.Point2D> points; + + @_i4.redeclare + external _i2.Point2D get origin; + @_i4.redeclare + external void addPoint(_i2.Point2D point); + external void addVector( + _i2.Vector2D vector, [ + _i2.Point2D? start, + ]); + external _i2.Vector2D get xAxis; + external _i2.Vector2D get yAxis; +} +@_i1.JS() +external _i2.Vector3D get unitI3D; +@_i1.JS() +external _i2.Vector3D get unitJ3D; +@_i1.JS() +external _i2.Vector3D get unitK3D; +extension type CoordinateSystem3D._(_i1.JSObject _) + implements _i2.CoordinateSystem<_i2.Point3D> { + external CoordinateSystem3D(_i2.Point3D origin); + + external _i1.JSArray<_i2.Point3D> points; + + @_i4.redeclare + external _i2.Point3D get origin; + @_i4.redeclare + external void addPoint(_i2.Point3D point); + external void addVector( + _i2.Vector3D vector, [ + _i2.Point3D? start, + ]); + external _i2.Vector3D get xAxis; + external _i2.Vector3D get yAxis; + external _i2.Vector3D get zAxis; +} +extension type Matrix._(_i1.JSObject _) implements _i1.JSObject { + external double rows; + + external double columns; + + external _i1.JSArray<_i1.JSNumber> operator [](num index); +} diff --git a/web_generator/test/integration/interop_gen/project/output/b.dart b/web_generator/test/integration/interop_gen/project/output/b.dart new file mode 100644 index 00000000..46e3a010 --- /dev/null +++ b/web_generator/test/integration/interop_gen/project/output/b.dart @@ -0,0 +1,296 @@ +// 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; + +import 'package:meta/meta.dart' as _i2; + +extension type Point2D._(_i1.JSObject _) implements Point { + external double x; + + external double y; +} +extension type CoordinateSystem

._(_i1.JSObject _) + implements _i1.JSObject { + external _i1.JSArray

points; + + external P get origin; + external void addPoint(P point); +} +@_i1.JS() +external Point2D get origin2D; +extension type Vector._(_i1.JSObject _) implements _i1.JSObject { + external double get magnitude; + external double get directionAngle; +} +extension type Vector2D._(_i1.JSObject _) implements Vector { + external Vector2D( + num x, + num y, + ); + + external double x; + + external double y; + + external Vector2D unit(); + @_i2.redeclare + external double get magnitude; + @_i2.redeclare + external double get directionAngle; + external Point2D moveFrom(Point2D point); + external static Vector2D from( + num magnitude, + num at, + ); + external static Vector2D fromPoints( + Point2D start, + Point2D end, + ); +} +extension type Point3D._(_i1.JSObject _) implements Point { + external double x; + + external double y; + + external double z; +} +@_i1.JS() +external Point3D get origin3D; +extension type DirectionAngles._(_i1.JSObject _) implements _i1.JSObject { + external double alpha; + + external double beta; + + external double gamma; +} +extension type Vector3D._(_i1.JSObject _) implements Vector { + external Vector3D( + num x, + num y, + num z, + ); + + external double x; + + external double y; + + external double z; + + external Vector3D unit(); + @_i2.redeclare + external double get magnitude; + external DirectionAngles get directionAngles; + @_i2.redeclare + external double get directionAngle; + external Point3D moveFrom(Point3D point); + external static Vector3D from( + num magnitude, + DirectionAngles at, + ); + external static Vector3D fromPoints( + Point3D start, + Point3D end, + ); +} +extension type Shape._(_i1.JSObject _) implements _i1.JSObject {} +extension type Shape2D._(_i1.JSObject _) implements Shape { + external double? get sides; + external double get perimeter; + external double get area; +} +extension type Circle._(_i1.JSObject _) implements Shape2D { + external Circle(num radius); + + external double radius; + + @_i2.redeclare + external double get area; + @_i2.redeclare + external double get perimeter; +} +@_i1.JS() +external Circle drawCircle( + Point2D center, + num radius, +); +extension type Rectangle._(_i1.JSObject _) implements Shape2D { + external Rectangle( + num length, + num width, + ); + + external double length; + + external double width; + + external double sides; + + @_i2.redeclare + external double get area; + @_i2.redeclare + external double get perimeter; +} +extension type Square._(_i1.JSObject _) implements Rectangle { + external Square(num length); + + external double length; +} +@_i1.JS() +external Square drawSquare( + Point2D start, + num length, [ + num? angle, +]); +extension type Shape3D._(_i1.JSObject _) implements Shape { + external double get volume; + external double surfaceArea(); +} +extension type Sphere._(_i1.JSObject _) implements Shape3D { + external Sphere(num radius); + + external double radius; + + @_i2.redeclare + external double get volume; + @_i2.redeclare + external double surfaceArea(); +} +@_i1.JS() +external Sphere drawSphere(Point3D center); +extension type Prism._(_i1.JSObject _) implements Shape3D { + external Prism( + S surface, + num height, + ); + + external double height; + + external S get surface; + @_i2.redeclare + external double get volume; + @_i2.redeclare + external double surfaceArea(); +} +extension type Pyramid._(_i1.JSObject _) implements Shape3D { + external Pyramid( + S surface, + num height, + ); + + external double height; + + external S get surface; + @_i2.redeclare + external double get volume; + @_i2.redeclare + external double surfaceArea(); +} +extension type Cylinder._(_i1.JSObject _) implements Prism { + external Cylinder( + num radius, + num height, + ); + + external double radius; + + @_i2.redeclare + external double surfaceArea(); +} +extension type Cuboid._(_i1.JSObject _) implements Prism { + external Cuboid( + num length, + num width, + num height, + ); + + external double length; + + external double width; + + external double height; +} +extension type Cube._(_i1.JSObject _) implements Prism { + external Cube(num length); + + external double length; + + @_i2.redeclare + external double get volume; + @_i2.redeclare + external double surfaceArea(); +} +extension type Cone._(_i1.JSObject _) implements Pyramid { + external Cone( + num radius, + num height, + ); + + external double radius; + + external double height; + + @_i2.redeclare + external double surfaceArea(); +} +extension type Hemi._(_i1.JSObject _) implements Shape3D { + external Hemi(S shape); + + external S get shape; + external static Prism prism(Prism p); + @_i2.redeclare + external double get volume; + @_i2.redeclare + external double surfaceArea(); +} +typedef HemiSphere = Hemi; +extension type EpahsImpl._(_i1.JSObject _) + implements Epahs { + external EpahsImpl( + String name, [ + AnonymousUnion$1? type, + ]); + + external factory EpahsImpl.$1(Epahs config); + + external String name; + + external TMeta? metadata; + + @_i2.redeclare + external String get id; + @_i2.redeclare + external void onUpdate(Epahs prev); + external String get location; + external set location(String value); + @_i2.redeclare + external double area(); + @_i1.JS('area') + external String area$1(AnonymousUnion unit); + external static EpahsImpl getById(String id); + @_i1.JS('toString') + external String toString$(); +} +extension type Point._(_i1.JSObject _) implements _i1.JSObject {} +extension type const AnonymousUnion$1._(String _) { + static const AnonymousUnion$1 circle = AnonymousUnion$1._('circle'); + + static const AnonymousUnion$1 rectangle = AnonymousUnion$1._('rectangle'); + + static const AnonymousUnion$1 polygon = AnonymousUnion$1._('polygon'); +} +extension type Epahs._(_i1.JSObject _) + implements _i1.JSObject { + external String name; + + external String get id; + external double area(); + @_i1.JS('area') + external String area$1(AnonymousUnion unit); + external _i1.JSFunction? get onUpdate; +} +extension type const AnonymousUnion._(String _) { + static const AnonymousUnion cm2 = AnonymousUnion._('cm2'); + + static const AnonymousUnion in2 = AnonymousUnion._('in2'); +} diff --git a/web_generator/test/integration/interop_gen/project/output/c.dart b/web_generator/test/integration/interop_gen/project/output/c.dart new file mode 100644 index 00000000..5179b4be --- /dev/null +++ b/web_generator/test/integration/interop_gen/project/output/c.dart @@ -0,0 +1,80 @@ +// 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; + +extension type Comparator._(_i1.JSObject _) + implements _i1.JSObject { + external double call( + T a, + T b, + ); +} +extension type ILogger._(_i1.JSObject _) implements _i1.JSObject { + external AnonymousUnion? level; + + external String get name; + external void log(String message); + external void error(String message); + external _i1.JSFunction? get flush; +} +extension type Dictionary._(_i1.JSObject _) implements _i1.JSObject { + external String operator [](String key); +} +extension type Repository._(_i1.JSObject _) + implements _i1.JSObject { + external T findById(String id); + external void save(T entity); +} +extension type RepoConstructor._(_i1.JSObject _) implements _i1.JSObject { + external RepoConstructor(_i1.JSArray<_i1.JSString> args); +} +extension type AsyncService._(_i1.JSObject _) implements _i1.JSObject { + external _i1.JSPromise<_i1.JSAny?> fetchData(String url); + external _i1.JSPromise<_i1.JSBoolean> updateData( + String id, + String payload, + ); +} +extension type User._(_i1.JSObject _) implements _i1.JSObject { + external String id; + + external String email; + + external _i1.JSFunction? get describe; +} +extension type Admin._(_i1.JSObject _) implements User, ILogger { + external String role; + + external void grantPermission(String permission); +} +extension type Config._(_i1.JSObject _) implements _i1.JSObject { + external String env; +} +@_i1.JS('Config') +extension type Config$1._(_i1.JSObject _) implements _i1.JSObject { + external bool debug; +} +extension type SecureResource._(_i1.JSObject _) implements _i1.JSObject { + external String accessToken; + + external bool authenticate(); +} +@_i1.JS() +external Dictionary get dict; +@_i1.JS() +external LinkedList get rootList; +@_i1.JS() +external Comparator<_i1.JSNumber> get compareNumbers; +extension type const AnonymousUnion._(String _) { + static const AnonymousUnion debug = AnonymousUnion._('debug'); + + static const AnonymousUnion info = AnonymousUnion._('info'); + + static const AnonymousUnion warn = AnonymousUnion._('warn'); + + static const AnonymousUnion error = AnonymousUnion._('error'); +} +extension type LinkedList._(_i1.JSObject _) implements _i1.JSObject { + external LinkedList next(); +} 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 c4c96786..65e7fe92 100644 --- a/web_generator/test/integration/interop_gen/ts_typing_expected.dart +++ b/web_generator/test/integration/interop_gen/ts_typing_expected.dart @@ -3,6 +3,10 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:js_interop' as _i1; +@_i1.JS() +external String myFunction(String param); +@_i1.JS() +external String myEnclosingFunction(_i1.JSFunction func); @_i1.JS() external String get myString; @_i1.JS() @@ -21,10 +25,6 @@ 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; diff --git a/web_generator/test/integration/interop_gen/typealias_expected.dart b/web_generator/test/integration/interop_gen/typealias_expected.dart index 61cf2f2c..39cd7cee 100644 --- a/web_generator/test/integration/interop_gen/typealias_expected.dart +++ b/web_generator/test/integration/interop_gen/typealias_expected.dart @@ -3,16 +3,24 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:js_interop' as _i1; +typedef Shape2D = String; +typedef PrismFromShape2D = _i1.JSArray; +@_i1.JS() +external PrismFromShape2D<_i1.JSString> makePrism(Shape2D shape); +@_i1.JS('makePrism') +external PrismFromShape2D makePrism$1(S shape); +typedef PromisedArray> + = _i1.JSPromise; +@_i1.JS() +external PromisedArray<_i1.JSString, _i1.JSArray<_i1.JSString>> fetchNames(); +typedef IsActive = bool; +@_i1.JS() +external String isUserActive(IsActive status); typedef Username = String; typedef Age = double; -typedef IsActive = bool; typedef Tags = _i1.JSArray<_i1.JSString>; typedef List = _i1.JSArray; typedef Box = _i1.JSArray<_i1.JSArray>; -typedef PromisedArray> - = _i1.JSPromise; -typedef Shape2D = String; -typedef PrismFromShape2D = _i1.JSArray; typedef Logger = LoggerType; typedef Direction = AnonymousUnion; typedef Method = AnonymousUnion$1; @@ -32,14 +40,6 @@ external _i1.JSArray get tagArray; external List<_i1.JSString> get users; @_i1.JS() external Box<_i1.JSNumber> get matrix; -@_i1.JS() -external PrismFromShape2D<_i1.JSString> makePrism(Shape2D shape); -@_i1.JS('makePrism') -external PrismFromShape2D makePrism$1(S shape); -@_i1.JS() -external PromisedArray<_i1.JSString, _i1.JSArray<_i1.JSString>> fetchNames(); -@_i1.JS() -external String isUserActive(IsActive status); extension type const LoggerType._(int _) { static const LoggerType Noop = LoggerType._(0); 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 95cc26a6..3de6a1d5 100644 --- a/web_generator/test/integration/interop_gen/web_types_expected.dart +++ b/web_generator/test/integration/interop_gen/web_types_expected.dart @@ -2,12 +2,9 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:js_interop' as _i1; + import 'package:web/web.dart' as _i2; -@_i1.JS() -external _i2.CustomEvent get myCustomEvent; -@_i1.JS() -external _i2.ShadowRoot myShadowRoot; @_i1.JS() external void handleMouseEvent(_i2.MouseEvent event); @_i1.JS() @@ -18,10 +15,6 @@ external _i1.JSPromise<_i2.WebGLBuffer> convertToWebGL( @_i1.JS() external String getHTMLElementContent(T element); @_i1.JS() -external _i2.HTMLButtonElement get button; -@_i1.JS() -external _i2.HTMLDivElement get output; -@_i1.JS() external void handleButtonClick(_i2.MouseEvent event); @_i1.JS() external void handleInputChange(_i2.Event event); @@ -35,6 +28,14 @@ external _i1.JSAny? handleEvents( _i2.Event event, _i1.JSArray onCallbacks, ); +@_i1.JS() +external _i2.CustomEvent get myCustomEvent; +@_i1.JS() +external _i2.ShadowRoot myShadowRoot; +@_i1.JS() +external _i2.HTMLButtonElement get button; +@_i1.JS() +external _i2.HTMLDivElement get output; extension type HTMLTransformFunc._(_i1.JSObject _) implements _i1.JSObject { external R call(T element); diff --git a/web_generator/test/integration/interop_gen_test.dart b/web_generator/test/integration/interop_gen_test.dart index c0e7ec81..b898ac57 100644 --- a/web_generator/test/integration/interop_gen_test.dart +++ b/web_generator/test/integration/interop_gen_test.dart @@ -63,4 +63,42 @@ void main() { }); } }); + + group('Interop Gen Integration Test (with config)', () { + final testGenFolder = + p.join('test', 'integration', 'interop_gen', 'project'); + final inputConfig = File(p.join(testGenFolder, 'config.yaml')); + final outputDir = Directory(p.join('.dart_tool', 'interop_gen_project')); + final outputExpectedPath = p.join(testGenFolder, 'output'); + + setUpAll(() async { + // set up npm + await runProc('npm', ['install'], workingDirectory: bindingsGenPath); + + // compile file + await compileDartMain(dir: bindingsGenPath); + + await outputDir.create(recursive: true); + }); + + test('Project Test', () async { + final inputConfigPath = + p.relative(inputConfig.path, from: bindingsGenPath); + // run the entrypoint + await runProc( + 'node', ['main.mjs', '--config=$inputConfigPath', '--declaration'], + workingDirectory: bindingsGenPath); + + // read files + for (final output in outputDir.listSync().whereType()) { + final outputContents = output.readAsStringSync(); + + final expectedOutput = + File(p.join(outputExpectedPath, p.basename(output.path))); + final expectedContents = expectedOutput.readAsStringSync(); + + expect(outputContents, expectedContents); + } + }); + }); }