diff --git a/web_generator/lib/src/ast/declarations.dart b/web_generator/lib/src/ast/declarations.dart index 1b02774b..1c6bc012 100644 --- a/web_generator/lib/src/ast/declarations.dart +++ b/web_generator/lib/src/ast/declarations.dart @@ -138,3 +138,97 @@ class ParameterDeclaration { ..type = type.emit(TypeOptions(nullable: optional))); } } + +class EnumDeclaration extends NamedDeclaration + implements ExportableDeclaration { + @override + final String name; + + @override + final bool exported; + + /// The underlying type of the enum (usually a number) + Type baseType; + + final List members; + + @override + String? dartName; + + EnumDeclaration( + {required this.name, + required this.baseType, + required this.members, + required this.exported, + this.dartName}); + + @override + Spec emit([DeclarationOptions? options]) { + final baseTypeIsJSType = getJSTypeAlternative(baseType) == baseType; + final externalMember = members.any((m) => m.isExternal); + final shouldUseJSRepType = externalMember || baseTypeIsJSType; + + return ExtensionType((e) => e + ..annotations.addAll([ + if (dartName != null && dartName != name && externalMember) + generateJSAnnotation(name) + ]) + ..constant = !shouldUseJSRepType + ..name = dartName ?? name + ..primaryConstructorName = '_' + ..representationDeclaration = RepresentationDeclaration((r) => r + ..declaredRepresentationType = ( + // if any member doesn't have a value, we have to use external + // so such type should be the JS rep type + shouldUseJSRepType ? getJSTypeAlternative(baseType) : baseType) + .emit(options?.toTypeOptions()) + ..name = '_') + ..fields + .addAll(members.map((member) => member.emit(shouldUseJSRepType)))); + } + + @override + ID get id => ID(type: 'enum', name: name); +} + +class EnumMember { + final String name; + + final Type? type; + + final Object? value; + + final String parent; + + bool get isExternal => value == null; + + EnumMember(this.name, this.value, + {this.type, required this.parent, this.dartName}); + + Field emit([bool? shouldUseJSRepType]) { + final jsRep = shouldUseJSRepType ?? (value == null); + return Field((f) { + // TODO(nikeokoronkwo): This does not render correctly on `code_builder`. + // Until the update is made, we will omit examples concerning this + // Luckily, not many real-world instances of enums use this anyways, https://github.com/dart-lang/tools/issues/2118 + if (!isExternal) { + f.modifier = (!jsRep ? FieldModifier.constant : FieldModifier.final$); + } + if (dartName != null && name != dartName && isExternal) { + f.annotations.add(generateJSAnnotation(name)); + } + f + ..name = dartName ?? name + ..type = refer(parent) + ..external = value == null + ..static = true + ..assignment = value == null + ? null + : refer(parent).property('_').call([ + jsRep ? literal(value).property('toJS') : literal(value) + ]).code; + }); + } + + String? dartName; +} diff --git a/web_generator/lib/src/ast/types.dart b/web_generator/lib/src/ast/types.dart index e3d789b6..7ba0e7fe 100644 --- a/web_generator/lib/src/ast/types.dart +++ b/web_generator/lib/src/ast/types.dart @@ -5,6 +5,8 @@ import 'package:code_builder/code_builder.dart'; import '../interop_gen/namer.dart'; import 'base.dart'; +import 'builtin.dart'; +import 'declarations.dart'; class ReferredType extends Type { @override @@ -24,27 +26,76 @@ class ReferredType extends Type { @override Reference emit([TypeOptions? options]) { - // TODO: implement emit - throw UnimplementedError(); + // TODO: Support referred types imported from URL + return TypeReference((t) => t + ..symbol = declaration.name + ..types.addAll(typeParams.map((t) => t.emit(options))) + ..isNullable = options?.nullable); } } // TODO(https://github.com/dart-lang/web/issues/385): Implement Support for UnionType (including implementing `emit`) class UnionType extends Type { - List types; + final List types; UnionType({required this.types}); @override - ID get id => ID(type: 'type', name: types.map((t) => t.id).join('|')); + ID get id => ID(type: 'type', name: types.map((t) => t.id.name).join('|')); + + @override + String? get name => null; @override Reference emit([TypeOptions? options]) { throw UnimplementedError('TODO: Implement UnionType.emit'); } +} + +// TODO: Handle naming anonymous declarations +// TODO: Extract having a declaration associated with a type to its own type +// (e.g DeclarationAssociatedType) +class HomogenousEnumType + extends UnionType { + final List _types; @override - String? get name => null; + List get types => _types; + + final Type baseType; + + final bool isNullable; + + String declarationName; + + HomogenousEnumType( + {required List types, this.isNullable = false, required String name}) + : declarationName = name, + _types = types, + baseType = types.first.baseType, + super(types: types); + + EnumDeclaration get declaration => EnumDeclaration( + name: declarationName, + dartName: UniqueNamer.makeNonConflicting(declarationName), + baseType: baseType, + members: types.map((t) { + final name = t.value.toString(); + return EnumMember( + name, + t.value, + dartName: UniqueNamer.makeNonConflicting(name), + parent: UniqueNamer.makeNonConflicting(declarationName), + ); + }).toList(), + exported: true); + + @override + Reference emit([TypeOptions? options]) { + return TypeReference((t) => t + ..symbol = declarationName + ..isNullable = options?.nullable ?? isNullable); + } } /// The base class for a type generic (like 'T') @@ -58,13 +109,62 @@ class GenericType extends Type { GenericType({required this.name, this.constraint, this.parent}); + @override + ID get id => + ID(type: 'generic-type', name: '$name@${parent?.id ?? "(anonymous)"}'); + @override Reference emit([TypeOptions? options]) => TypeReference((t) => t ..symbol = name ..bound = constraint?.emit() ..isNullable = options?.nullable); +} + +/// A type representing a bare literal, such as `null`, a string or number +class LiteralType extends Type { + final LiteralKind kind; + + final Object? value; @override - ID get id => - ID(type: 'generic-type', name: '$name@${parent?.id ?? "(anonymous)"}'); + String get name => switch (kind) { + LiteralKind.$null => 'null', + LiteralKind.int || LiteralKind.double => 'number', + LiteralKind.string => 'string', + LiteralKind.$true => 'true', + LiteralKind.$false => 'false' + }; + + BuiltinType get baseType { + final primitive = kind.primitive; + + return BuiltinType.primitiveType(primitive); + } + + LiteralType({required this.kind, required this.value}); + + @override + Reference emit([TypeOptions? options]) { + return baseType.emit(options); + } + + @override + ID get id => ID(type: 'type', name: name); +} + +enum LiteralKind { + $null, + string, + double, + $true, + $false, + int; + + PrimitiveType get primitive => switch (this) { + LiteralKind.$null => PrimitiveType.undefined, + LiteralKind.string => PrimitiveType.string, + LiteralKind.int => PrimitiveType.num, + LiteralKind.double => PrimitiveType.double, + LiteralKind.$true || LiteralKind.$false => PrimitiveType.boolean + }; } diff --git a/web_generator/lib/src/interop_gen/namer.dart b/web_generator/lib/src/interop_gen/namer.dart index dd07115d..085fb820 100644 --- a/web_generator/lib/src/interop_gen/namer.dart +++ b/web_generator/lib/src/interop_gen/namer.dart @@ -23,6 +23,22 @@ class UniqueNamer { UniqueNamer([Iterable used = const []]) : _usedNames = used.toSet(); + /// Makes a name that does not conflict with dart keywords + static String makeNonConflicting(String name) { + if (int.tryParse(name) != null) { + return '\$$name'; + } else if (double.tryParse(name) != null) { + return '\$${name.splitMapJoin( + '.', + onMatch: (p0) => 'dot', + )}'; + } else if (keywords.contains(name)) { + return '$name\$'; + } else { + return name; + } + } + /// Creates a unique name and ID for a given declaration to prevent /// name collisions in Dart applications /// @@ -33,10 +49,7 @@ class UniqueNamer { name = 'unnamed'; } - var newName = name; - if (keywords.contains(newName)) { - newName = '$newName\$'; - } + var newName = UniqueNamer.makeNonConflicting(name); var i = 0; while (_usedNames.contains(newName)) { diff --git a/web_generator/lib/src/interop_gen/transform.dart b/web_generator/lib/src/interop_gen/transform.dart index 663786b6..08009ced 100644 --- a/web_generator/lib/src/interop_gen/transform.dart +++ b/web_generator/lib/src/interop_gen/transform.dart @@ -32,8 +32,14 @@ class TransformResult { final Type _ => null, }; }).whereType(); - final lib = Library((l) => l..body.addAll(specs)); - return MapEntry(file, formatter.format('${lib.accept(emitter)}')); + final lib = Library((l) => l + ..ignoreForFile.addAll( + ['constant_identifier_names', 'non_constant_identifier_names']) + ..body.addAll(specs)); + return MapEntry( + file, + formatter.format('${lib.accept(emitter)}' + .replaceAll('static external', 'external static'))); }); } } diff --git a/web_generator/lib/src/interop_gen/transform/transformer.dart b/web_generator/lib/src/interop_gen/transform/transformer.dart index 404c2992..cd121358 100644 --- a/web_generator/lib/src/interop_gen/transform/transformer.dart +++ b/web_generator/lib/src/interop_gen/transform/transformer.dart @@ -44,9 +44,11 @@ class Transformer { final decs = _transformVariable(node as TSVariableStatement); nodeMap.addAll({for (final d in decs) d.id.toString(): d}); default: - final Declaration decl = switch (node.kind) { + final decl = switch (node.kind) { TSSyntaxKind.FunctionDeclaration => _transformFunction(node as TSFunctionDeclaration), + TSSyntaxKind.EnumDeclaration => + _transformEnum(node as TSEnumDeclaration), _ => throw Exception('Unsupported Declaration Kind: ${node.kind}') }; // ignore: dead_code This line will not be dead in future decl additions @@ -56,7 +58,91 @@ class Transformer { nodes.add(node); } - List _transformVariable(TSVariableStatement variable) { + EnumDeclaration _transformEnum(TSEnumDeclaration enumeration) { + final modifiers = enumeration.modifiers?.toDart; + final isExported = modifiers?.any((m) { + return m.kind == TSSyntaxKind.ExportKeyword; + }) ?? + false; + + // get the name + final name = enumeration.name.text; + + // get the members and the rep type + final enumMembers = enumeration.members.toDart; + + final members = []; + PrimitiveType? enumRepType; + + for (final member in enumMembers) { + final memName = member.name.text; + final dartMemName = UniqueNamer.makeNonConflicting(memName); + final memInitializer = member.initializer; + + // check the type of the initializer + if (memInitializer != null) { + switch (memInitializer.kind) { + case TSSyntaxKind.NumericLiteral: + // parse numeric literal + final value = + _parseNumericLiteral(memInitializer as TSNumericLiteral); + final primitiveType = + value is int ? PrimitiveType.int : PrimitiveType.double; + members.add(EnumMember(memName, value, + type: BuiltinType.primitiveType(primitiveType), + parent: name, + dartName: dartMemName)); + if (enumRepType == null && + !(primitiveType == PrimitiveType.int && + enumRepType == PrimitiveType.double)) { + enumRepType = primitiveType; + } else if (enumRepType != primitiveType) { + enumRepType = PrimitiveType.any; + } + break; + case TSSyntaxKind.StringLiteral: + // parse string literal + final value = + _parseStringLiteral(memInitializer as TSStringLiteral); + const primitiveType = PrimitiveType.string; + members.add(EnumMember(memName, value, + type: BuiltinType.primitiveType(primitiveType), + parent: name, + dartName: dartMemName)); + if (enumRepType == null) { + enumRepType = primitiveType; + } else if (enumRepType != primitiveType) { + enumRepType = PrimitiveType.any; + } + break; + default: + // unsupported + + break; + } + } else { + // get the type + members.add( + EnumMember(memName, null, parent: name, dartName: dartMemName)); + } + } + + return EnumDeclaration( + name: name, + baseType: BuiltinType.primitiveType(enumRepType ?? PrimitiveType.num), + members: members, + exported: isExported); + } + + num _parseNumericLiteral(TSNumericLiteral numericLiteral) { + return num.parse(numericLiteral.text); + } + + String _parseStringLiteral(TSStringLiteral stringLiteral) { + return stringLiteral.text; + } + + List _transformVariable(TSVariableStatement variable) { // get the modifier of the declaration final modifiers = variable.modifiers.toDart; final isExported = modifiers.any((m) { @@ -154,80 +240,149 @@ class Transformer { /// TODO(https://github.com/dart-lang/web/issues/384): Add support for literals (i.e individual booleans and `null`) /// TODO(https://github.com/dart-lang/web/issues/383): Add support for `typeof` types Type _transformType(TSTypeNode type, {bool parameter = false}) { - if (type.kind == TSSyntaxKind.UnionType) { - final unionType = type as TSUnionTypeNode; - return UnionType( - types: unionType.types.toDart.map(_transformType).toList()); - } - - if (type.kind == TSSyntaxKind.TypeReference) { - final refType = type as TSTypeReferenceNode; + switch (type.kind) { + case TSSyntaxKind.TypeReference: + final refType = type as TSTypeReferenceNode; + + final name = refType.typeName.text; + final typeArguments = refType.typeArguments?.toDart; + + 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 = getSupportedType( + name, (typeArguments ?? []).map(_transformType).toList()); + if (supportedType != null) { + return supportedType; + } - final name = refType.typeName.text; - final typeArguments = refType.typeArguments?.toDart; + // TODO: In the case of overloading, should/shouldn't we handle more than one declaration? + final declaration = _getDeclarationByName(refType.typeName); - var declarationsMatching = nodeMap.findByName(name); + if (declaration == null) { + throw Exception('Found no declaration matching $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 = getSupportedType( - name, (typeArguments ?? []).map(_transformType).toList()); - if (supportedType != null) { - return supportedType; - } + if (declaration.kind == TSSyntaxKind.TypeParameter) { + return GenericType(name: name); + } - // TODO: In the case of overloading, should/shouldn't we handle more than one declaration? - final declaration = _getDeclarationByName(refType.typeName); + transform(declaration); - if (declaration == null) { - throw Exception('Found no declaration matching $name'); + declarationsMatching = nodeMap.findByName(name); } - if (declaration.kind == TSSyntaxKind.TypeParameter) { - return GenericType(name: name); + // TODO: In the case of overloading, should/shouldn't we handle more than one declaration? + final firstNode = + declarationsMatching.whereType().first; + + return firstNode.asReferredType( + (typeArguments ?? []).map(_transformType).toList(), + ); + // TODO: Union types are also anonymous by design + // Unless we are making typedefs for them, we should + // try to handle not making multiple of them for a given use-case + case TSSyntaxKind.UnionType: + final unionType = type as TSUnionTypeNode; + // TODO: Unions + final types = unionType.types.toDart.map(_transformType).toList(); + + var isHomogenous = true; + final nonNullLiteralTypes = []; + var onlyContainsBooleanTypes = true; + var isNullable = false; + LiteralType? firstNonNullablePrimitiveType; + + for (final type in types) { + if (type is LiteralType) { + if (type.kind == LiteralKind.$null) { + isNullable = true; + continue; + } + firstNonNullablePrimitiveType ??= type; + onlyContainsBooleanTypes &= (type.kind == LiteralKind.$true) || + (type.kind == LiteralKind.$false); + if (type.kind.primitive != + firstNonNullablePrimitiveType.kind.primitive) { + isHomogenous = false; + } + nonNullLiteralTypes.add(type); + } else { + isHomogenous = false; + } } - transform(declaration); + // check if it is a union of literals + if (isHomogenous) { + if (nonNullLiteralTypes.isNotEmpty && onlyContainsBooleanTypes) { + return BuiltinType.primitiveType(PrimitiveType.boolean, + isNullable: isNullable); + } - declarationsMatching = nodeMap.findByName(name); - } + final (id: _, name: name) = + namer.makeUnique('AnonymousUnion', 'type'); - // TODO: In the case of overloading, should/shouldn't we handle more than one declaration? - final firstNode = - declarationsMatching.whereType().first; + // TODO: Handle similar types here... + return HomogenousEnumType( + types: nonNullLiteralTypes, isNullable: isNullable, name: name); + } - return firstNode.asReferredType( - (typeArguments ?? []).map(_transformType).toList(), - ); - } + return UnionType(types: types); + case TSSyntaxKind.LiteralType: + final literalType = type as TSLiteralTypeNode; + final literal = literalType.literal; + + return LiteralType( + kind: switch (literal.kind) { + // TODO: Will we support Regex? + TSSyntaxKind.NumericLiteral => num.parse(literal.text) is int + ? LiteralKind.int + : LiteralKind.double, + TSSyntaxKind.StringLiteral => LiteralKind.string, + TSSyntaxKind.TrueKeyword => LiteralKind.$true, + TSSyntaxKind.FalseKeyword => LiteralKind.$false, + TSSyntaxKind.NullKeyword => LiteralKind.$null, + _ => throw UnimplementedError( + 'Unsupported Literal Kind ${literal.kind}') + }, + value: switch (literal.kind) { + // TODO: Will we support Regex? + TSSyntaxKind.NumericLiteral => num.parse(literal.text), + TSSyntaxKind.StringLiteral => literal.text, + TSSyntaxKind.TrueKeyword => true, + TSSyntaxKind.FalseKeyword => false, + TSSyntaxKind.NullKeyword => null, + _ => throw UnimplementedError( + 'Unsupported Literal Kind ${literal.kind}') + }); + case TSSyntaxKind.ArrayType: + return BuiltinType.primitiveType(PrimitiveType.array, typeParams: [ + getJSTypeAlternative( + _transformType((type as TSArrayTypeNode).elementType)) + ]); + default: + // check for primitive type via its kind + final primitiveType = switch (type.kind) { + TSSyntaxKind.ArrayType => PrimitiveType.array, + TSSyntaxKind.StringKeyword => PrimitiveType.string, + TSSyntaxKind.AnyKeyword => PrimitiveType.any, + TSSyntaxKind.ObjectKeyword => PrimitiveType.object, + TSSyntaxKind.NumberKeyword => + (parameter ? PrimitiveType.num : PrimitiveType.double), + TSSyntaxKind.UndefinedKeyword => PrimitiveType.undefined, + TSSyntaxKind.UnknownKeyword => PrimitiveType.unknown, + TSSyntaxKind.BooleanKeyword => PrimitiveType.boolean, + TSSyntaxKind.VoidKeyword => PrimitiveType.$void, + _ => throw UnsupportedError( + 'The given type with kind ${type.kind} is not supported yet') + }; - if (type.kind == TSSyntaxKind.ArrayType) { - return BuiltinType.primitiveType(PrimitiveType.array, typeParams: [ - getJSTypeAlternative( - _transformType((type as TSArrayTypeNode).elementType)) - ]); + return BuiltinType.primitiveType(primitiveType); } - - // check for primitive type via its kind - final primitiveType = switch (type.kind) { - TSSyntaxKind.ArrayType => PrimitiveType.array, - TSSyntaxKind.StringKeyword => PrimitiveType.string, - TSSyntaxKind.AnyKeyword => PrimitiveType.any, - TSSyntaxKind.ObjectKeyword => PrimitiveType.object, - TSSyntaxKind.NumberKeyword => - (parameter ? PrimitiveType.num : PrimitiveType.double), - TSSyntaxKind.UndefinedKeyword => PrimitiveType.undefined, - TSSyntaxKind.UnknownKeyword => PrimitiveType.unknown, - TSSyntaxKind.BooleanKeyword => PrimitiveType.boolean, - TSSyntaxKind.VoidKeyword => PrimitiveType.$void, - _ => throw UnsupportedError( - 'The given type with kind ${type.kind} is not supported yet') - }; - - return BuiltinType.primitiveType(primitiveType); } NodeMap filter() { @@ -246,18 +401,16 @@ class Transformer { filteredDeclarations.add(e); } break; - case final BuiltinType _: - // primitive types are generated by default - break; - case Type(): - // TODO: Handle this case. - throw UnimplementedError(); case Declaration(): // TODO: Handle this case. throw UnimplementedError(); + default: + break; } }); + if (filteredDeclarations.isEmpty) return filteredDeclarations; + // then filter for dependencies final otherDecls = filteredDeclarations.entries .map((e) => _getDependenciesOfDecl(e.value)) @@ -290,6 +443,13 @@ class Transformer { node.id.toString(): node }); break; + case final EnumDeclaration _: + break; + // TODO: We can make (DeclarationAssociatedType) and use that + // rather than individual type names + case final HomogenousEnumType hu: + filteredDeclarations.add(hu.declaration); + break; case final UnionType u: filteredDeclarations.addAll({ for (final t in u.types.where((t) => t is! BuiltinType)) @@ -299,6 +459,8 @@ class Transformer { case final BuiltinType _: // primitive types are generated by default break; + case final ReferredType r: + filteredDeclarations.add(r.declaration); default: print('WARN: The given node type ${decl.runtimeType.toString()} ' 'is not supported for filtering. Skipping...'); diff --git a/web_generator/lib/src/js/typescript.types.dart b/web_generator/lib/src/js/typescript.types.dart index ed8855df..25bda5ce 100644 --- a/web_generator/lib/src/js/typescript.types.dart +++ b/web_generator/lib/src/js/typescript.types.dart @@ -25,6 +25,14 @@ extension type const TSSyntaxKind._(num _) { static const TSSyntaxKind FunctionDeclaration = TSSyntaxKind._(262); static const TSSyntaxKind ExportDeclaration = TSSyntaxKind._(278); static const TSSyntaxKind Parameter = TSSyntaxKind._(169); + static const TSSyntaxKind EnumDeclaration = TSSyntaxKind._(266); + + /// expressions + static const TSSyntaxKind NumericLiteral = TSSyntaxKind._(9); + static const TSSyntaxKind StringLiteral = TSSyntaxKind._(11); + static const TSSyntaxKind NullKeyword = TSSyntaxKind._(106); + static const TSSyntaxKind TrueKeyword = TSSyntaxKind._(112); + static const TSSyntaxKind FalseKeyword = TSSyntaxKind._(97); /// keywords static const TSSyntaxKind ExportKeyword = TSSyntaxKind._(95); @@ -47,6 +55,7 @@ extension type const TSSyntaxKind._(num _) { static const TSSyntaxKind UnionType = TSSyntaxKind._(192); static const TSSyntaxKind TypeReference = TSSyntaxKind._(183); static const TSSyntaxKind ArrayType = TSSyntaxKind._(188); + static const TSSyntaxKind LiteralType = TSSyntaxKind._(201); /// Other static const TSSyntaxKind Identifier = TSSyntaxKind._(80); @@ -98,9 +107,42 @@ extension type TSTypeReferenceNode._(JSObject _) implements TSTypeNode { external TSNodeArray? get typeArguments; } +@JS('LiteralTypeNode') +extension type TSLiteralTypeNode._(JSObject _) implements TSTypeNode { + @redeclare + TSSyntaxKind get kind => TSSyntaxKind.LiteralType; + + external TSLiteral get literal; +} + +@JS('Expression') +extension type TSExpression._(JSObject _) implements TSNode {} + +@JS('LiteralExpression') +extension type TSLiteralExpression._(JSObject _) implements TSExpression { + external String text; + external bool? isUnterminated; +} + @JS('Declaration') extension type TSDeclaration._(JSObject _) implements TSNode {} +@JS() +extension type TSLiteral._(JSObject _) + implements TSLiteralExpression, TSDeclaration {} + +@JS('NumericLiteral') +extension type TSNumericLiteral._(JSObject _) implements TSLiteral { + @redeclare + TSSyntaxKind get kind => TSSyntaxKind.NumericLiteral; +} + +@JS('StringLiteral') +extension type TSStringLiteral._(JSObject _) implements TSLiteral { + @redeclare + TSSyntaxKind get kind => TSSyntaxKind.StringLiteral; +} + @JS('Statement') extension type TSStatement._(JSObject _) implements TSNode {} @@ -152,6 +194,20 @@ extension type TSTypeParameterDeclaration._(JSObject _) external TSTypeNode? get constraint; } +@JS('EnumDeclaration') +extension type TSEnumDeclaration._(JSObject _) + implements TSDeclaration, TSStatement { + external TSIdentifier get name; + external TSNodeArray? get modifiers; + external TSNodeArray get members; +} + +@JS('EnumMember') +extension type TSEnumMember._(JSObject _) implements TSDeclaration { + external TSIdentifier get name; + external TSExpression? get initializer; +} + @JS('NodeArray') extension type TSNodeArray._(JSArray _) implements JSArray {} diff --git a/web_generator/test/integration/interop_gen/enum_expected.dart b/web_generator/test/integration/interop_gen/enum_expected.dart new file mode 100644 index 00000000..57f30934 --- /dev/null +++ b/web_generator/test/integration/interop_gen/enum_expected.dart @@ -0,0 +1,166 @@ +// 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 const Direction._(int _) { + static const Direction Up = Direction._(0); + + static const Direction Down = Direction._(1); + + static const Direction Left = Direction._(2); + + static const Direction Right = Direction._(3); +} +extension type const ResponseCode._(int _) { + static const ResponseCode Success = ResponseCode._(200); + + static const ResponseCode NotFound = ResponseCode._(404); + + static const ResponseCode ServerError = ResponseCode._(500); +} +extension type const Fractions._(double _) { + static const Fractions Half = Fractions._(0.5); + + static const Fractions Quarter = Fractions._(0.25); + + static const Fractions Fifth = Fractions._(0.2); + + static const Fractions Tenth = Fractions._(0.1); + + static const Fractions Third = Fractions._(0.3333333333333333); +} +extension type const LogLevel._(String _) { + static const LogLevel Info = LogLevel._('INFO'); + + static const LogLevel Warn = LogLevel._('WARN'); + + static const LogLevel Error = LogLevel._('ERROR'); + + static const LogLevel Debug = LogLevel._('DEBUG'); +} +extension type const HttpMethod._(String _) { + static const HttpMethod GET = HttpMethod._('GET'); + + static const HttpMethod POST = HttpMethod._('POST'); + + static const HttpMethod DELETE = HttpMethod._('DELETE'); +} +extension type BooleanLike._(_i1.JSAny? _) { + static final BooleanLike No = BooleanLike._(0.toJS); + + 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); + + static const HttpStatus BadRequest = HttpStatus._(400); + + static const HttpStatus Unauthorized = HttpStatus._(401); + + static const HttpStatus Forbidden = HttpStatus._(403); +} +@_i1.JS() +external HttpStatus get statusCode; +extension type MathConstants._(_i1.JSNumber _) { + static final MathConstants PI = MathConstants._(3.14.toJS); + + static final MathConstants TwoPI = MathConstants._(6.28.toJS); + + external static MathConstants Random; + + external static MathConstants Length; +} +extension type SomeRandomEnumValues._(_i1.JSAny? _) { + static final SomeRandomEnumValues moment = SomeRandomEnumValues._(2.toJS); + + static final SomeRandomEnumValues true$ = SomeRandomEnumValues._(6.28.toJS); + + @_i1.JS('default') + external static SomeRandomEnumValues default$; + + 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() +external AnonymousUnion currentTheme; +@_i1.JS() +external AnonymousUnion$1 buttonState; +@_i1.JS() +external AnonymousUnion$2 retriesLeft; +@_i1.JS() +external AnonymousUnion$3? get direction; +@_i1.JS() +external AnonymousUnion$4 get someUnionEnum; +@_i1.JS() +external bool get myBooleanEnum; +extension type const AnonymousUnion._(String _) { + static const AnonymousUnion light = AnonymousUnion._('light'); + + static const AnonymousUnion dark = AnonymousUnion._('dark'); + + static const AnonymousUnion system = AnonymousUnion._('system'); +} +extension type const AnonymousUnion$1._(String _) { + static const AnonymousUnion$1 default$ = AnonymousUnion$1._('default'); + + static const AnonymousUnion$1 hovered = AnonymousUnion$1._('hovered'); + + static const AnonymousUnion$1 pressed = AnonymousUnion$1._('pressed'); + + static const AnonymousUnion$1 disabled = AnonymousUnion$1._('disabled'); +} +extension type const AnonymousUnion$2._(num _) { + static const AnonymousUnion$2 $0 = AnonymousUnion$2._(0); + + static const AnonymousUnion$2 $1 = AnonymousUnion$2._(1); + + static const AnonymousUnion$2 $2 = AnonymousUnion$2._(2); + + static const AnonymousUnion$2 $3 = AnonymousUnion$2._(3); +} +extension type const AnonymousUnion$3._(String _) { + static const AnonymousUnion$3 N = AnonymousUnion$3._('N'); + + static const AnonymousUnion$3 S = AnonymousUnion$3._('S'); + + static const AnonymousUnion$3 E = AnonymousUnion$3._('E'); + + static const AnonymousUnion$3 W = AnonymousUnion$3._('W'); +} +extension type const AnonymousUnion$4._(num _) { + static const AnonymousUnion$4 $2 = AnonymousUnion$4._(2); + + static const AnonymousUnion$4 $4 = AnonymousUnion$4._(4); + + static const AnonymousUnion$4 $6 = AnonymousUnion$4._(6); + + static const AnonymousUnion$4 $8 = AnonymousUnion$4._(8); + + static const AnonymousUnion$4 $10 = AnonymousUnion$4._(10); +} diff --git a/web_generator/test/integration/interop_gen/enum_input.d.ts b/web_generator/test/integration/interop_gen/enum_input.d.ts new file mode 100644 index 00000000..b4b720ec --- /dev/null +++ b/web_generator/test/integration/interop_gen/enum_input.d.ts @@ -0,0 +1,85 @@ +export declare enum Direction { + Up = 0, + Down = 1, + Left = 2, + Right = 3 +} +export declare enum ResponseCode { + Success = 200, + NotFound = 404, + ServerError = 500 +} +export declare enum Fractions { + Half = 0.5, + Quarter = 0.25, + Fifth = 0.2, + Tenth = 0.1, + Third = 0.3333333333333333 +} +export declare enum LogLevel { + Info = "INFO", + Warn = "WARN", + Error = "ERROR", + Debug = "DEBUG" +} +export declare enum HttpMethod { + GET = "GET", + POST = "POST", + DELETE = "DELETE" +} +export declare enum BooleanLike { + No = 0, + Yes = "YES" +} +export declare enum Status { + Active = 1, + Inactive = 0, + Pending = 2 +} +declare const nameOfStatus: string; +export declare const statusFromName: Status; +export declare function logStatus(status: Status): void; +export declare function handleDirection(dir: Direction): string; +export declare const enum HttpStatus { + OK = 200, + BadRequest = 400, + Unauthorized = 401, + Forbidden = 403 +} +export declare const statusCode: HttpStatus; +declare enum ExternalLibResult { + OK = 0, + FAIL = 1 +} +declare enum DuplicateValues { + A = 1, + B = 2, + C = 1 +} +export declare enum MathConstants { + PI = 3.14, // constant + TwoPI = 6.28, + Random, // computed at compile time + Length, +} +export declare enum SomeRandomEnumValues { + moment = 2, + true = 6.28, + default, + unknown, +} +declare const statusKeys: string[]; +export declare enum Permissions { + Read = 1,// 0001 + Write = 2,// 0010 + Execute = 4,// 0100 + All = 7 +} +export declare function hasPermission(perm: Permissions, flag: Permissions): boolean; +export declare const userPermissions: Permissions; +export declare let currentTheme: "light" | "dark" | "system"; +export declare let buttonState: "default" | "hovered" | "pressed" | "disabled"; +export declare let retriesLeft: 0 | 1 | 2 | 3; +export declare const direction: "N" | "S" | "E" | "W" | null; +export declare const someUnionEnum: 2 | 4 | 6 | 8 | 10; +export declare const myBooleanEnum: true | false; diff --git a/web_generator/test/integration/interop_gen/functions_expected.dart b/web_generator/test/integration/interop_gen/functions_expected.dart index 4c8c2f8e..ce36fa8b 100644 --- a/web_generator/test/integration/interop_gen/functions_expected.dart +++ b/web_generator/test/integration/interop_gen/functions_expected.dart @@ -1,3 +1,5 @@ +// 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; diff --git a/web_generator/test/integration/interop_gen/variables_expected.dart b/web_generator/test/integration/interop_gen/variables_expected.dart index 1df172a0..4bf7c0f6 100644 --- a/web_generator/test/integration/interop_gen/variables_expected.dart +++ b/web_generator/test/integration/interop_gen/variables_expected.dart @@ -1,3 +1,5 @@ +// 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;