diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index 6c26d0287fde..80b759b3f75e 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,3 +1,7 @@ +## 16.0.5 + +* Adds ProxyApi to AST generation. + ## 16.0.4 * [swift] Improve style of Swift output. diff --git a/packages/pigeon/lib/ast.dart b/packages/pigeon/lib/ast.dart index 9d79a781afbd..0c48faa45456 100644 --- a/packages/pigeon/lib/ast.dart +++ b/packages/pigeon/lib/ast.dart @@ -29,7 +29,10 @@ class Method extends Node { required this.name, required this.returnType, required this.parameters, + required this.location, + this.isRequired = true, this.isAsynchronous = false, + this.isStatic = false, this.offset, this.objcSelector = '', this.swiftFunction = '', @@ -68,6 +71,18 @@ class Method extends Node { /// For example: [" List of documentation comments, separated by line.", ...] List documentationComments; + /// Where the implementation of this method is located, host or Flutter. + ApiLocation location; + + /// Whether this method is required to be implemented. + /// + /// This flag is typically used to determine whether a callback method for + /// a `ProxyApi` is nullable or not. + bool isRequired; + + /// Whether this is a static method of a ProxyApi. + bool isStatic; + @override String toString() { final String objcSelectorStr = @@ -78,29 +93,177 @@ class Method extends Node { } } -/// Represents a collection of [Method]s that are hosted on a given [location]. -class Api extends Node { +/// Represents a collection of [Method]s that are implemented on the platform +/// side. +class AstHostApi extends Api { + /// Parametric constructor for [AstHostApi]. + AstHostApi({ + required super.name, + required super.methods, + super.documentationComments = const [], + this.dartHostTestHandler, + }); + + /// The name of the Dart test interface to generate to help with testing. + String? dartHostTestHandler; + + @override + String toString() { + return '(HostApi name:$name methods:$methods documentationComments:$documentationComments dartHostTestHandler:$dartHostTestHandler)'; + } +} + +/// Represents a collection of [Method]s that are hosted on the Flutter side. +class AstFlutterApi extends Api { + /// Parametric constructor for [AstFlutterApi]. + AstFlutterApi({ + required super.name, + required super.methods, + super.documentationComments = const [], + }); + + @override + String toString() { + return '(FlutterApi name:$name methods:$methods documentationComments:$documentationComments)'; + } +} + +/// Represents an API that wraps a native class. +class AstProxyApi extends Api { + /// Parametric constructor for [AstProxyApi]. + AstProxyApi({ + required super.name, + required super.methods, + super.documentationComments = const [], + required this.constructors, + required this.fields, + this.superClass, + this.interfaces = const {}, + }); + + /// List of constructors inside the API. + final List constructors; + + /// List of fields inside the API. + List fields; + + /// Name of the class this class considers the super class. + TypeDeclaration? superClass; + + /// Name of the classes this class considers to be implemented. + Set interfaces; + + /// Methods implemented in the host platform language. + Iterable get hostMethods => methods.where( + (Method method) => method.location == ApiLocation.host, + ); + + /// Methods implemented in Flutter. + Iterable get flutterMethods => methods.where( + (Method method) => method.location == ApiLocation.flutter, + ); + + /// All fields that are attached. + /// + /// See [attached]. + Iterable get attachedFields => fields.where( + (ApiField field) => field.isAttached, + ); + + /// All fields that are not attached. + /// + /// See [attached]. + Iterable get unattachedFields => fields.where( + (ApiField field) => !field.isAttached, + ); + + @override + String toString() { + return '(ProxyApi name:$name methods:$methods field:$fields ' + 'documentationComments:$documentationComments ' + 'superClassName:$superClass interfacesNames:$interfaces)'; + } +} + +/// Represents a constructor for an API. +class Constructor extends Method { + /// Parametric constructor for [Constructor]. + Constructor({ + required super.name, + required super.parameters, + super.offset, + super.swiftFunction = '', + super.documentationComments = const [], + }) : super( + returnType: const TypeDeclaration.voidDeclaration(), + location: ApiLocation.host, + ); + + @override + String toString() { + final String swiftFunctionStr = + swiftFunction.isEmpty ? '' : ' swiftFunction:$swiftFunction'; + return '(Constructor name:$name parameters:$parameters $swiftFunctionStr documentationComments:$documentationComments)'; + } +} + +/// Represents a field of an API. +class ApiField extends NamedType { + /// Constructor for [ApiField]. + ApiField({ + required super.name, + required super.type, + super.offset, + super.documentationComments, + this.isAttached = false, + this.isStatic = false, + }) : assert(!isStatic || isAttached); + + /// Whether this is an attached field for a [AstProxyApi]. + /// + /// See [attached]. + final bool isAttached; + + /// Whether this is a static field of a [AstProxyApi]. + /// + /// A static field must also be attached. See [attached]. + final bool isStatic; + + /// Returns a copy of [Parameter] instance with new attached [TypeDeclaration]. + @override + ApiField copyWithType(TypeDeclaration type) { + return ApiField( + name: name, + type: type, + offset: offset, + documentationComments: documentationComments, + isAttached: isAttached, + isStatic: isStatic, + ); + } + + @override + String toString() { + return '(Field name:$name type:$type isAttached:$isAttached ' + 'isStatic:$isStatic documentationComments:$documentationComments)'; + } +} + +/// Represents a collection of [Method]s. +sealed class Api extends Node { /// Parametric constructor for [Api]. Api({ required this.name, - required this.location, required this.methods, - this.dartHostTestHandler, this.documentationComments = const [], }); /// The name of the API. String name; - /// Where the API's implementation is located, host or Flutter. - ApiLocation location; - /// List of methods inside the API. List methods; - /// The name of the Dart test interface to generate to help with testing. - String? dartHostTestHandler; - /// List of documentation comments, separated by line. /// /// Lines should not include the comment marker itself, but should include any @@ -110,7 +273,7 @@ class Api extends Node { @override String toString() { - return '(Api name:$name location:$location methods:$methods documentationComments:$documentationComments)'; + return '(Api name:$name methods:$methods documentationComments:$documentationComments)'; } } @@ -123,6 +286,7 @@ class TypeDeclaration { required this.isNullable, this.associatedEnum, this.associatedClass, + this.associatedProxyApi, this.typeArguments = const [], }); @@ -132,6 +296,7 @@ class TypeDeclaration { isNullable = false, associatedEnum = null, associatedClass = null, + associatedProxyApi = null, typeArguments = const []; /// The base name of the [TypeDeclaration] (ex 'Foo' to 'Foo?'). @@ -158,6 +323,12 @@ class TypeDeclaration { /// Associated [Class], if any. final Class? associatedClass; + /// Whether the [TypeDeclaration] has an [associatedProxyApi]. + bool get isProxyApi => associatedProxyApi != null; + + /// Associated [AstProxyApi], if any. + final AstProxyApi? associatedProxyApi; + @override int get hashCode { // This has to be implemented because TypeDeclaration is used as a Key to a @@ -207,11 +378,21 @@ class TypeDeclaration { ); } + /// Returns duplicated `TypeDeclaration` with attached `associatedProxyApi` value. + TypeDeclaration copyWithProxyApi(AstProxyApi proxyApiDefinition) { + return TypeDeclaration( + baseName: baseName, + isNullable: isNullable, + associatedProxyApi: proxyApiDefinition, + typeArguments: typeArguments, + ); + } + @override String toString() { final String typeArgumentsStr = typeArguments.isEmpty ? '' : 'typeArguments:$typeArguments'; - return '(TypeDeclaration baseName:$baseName isNullable:$isNullable$typeArgumentsStr isEnum:$isEnum isClass:$isClass)'; + return '(TypeDeclaration baseName:$baseName isNullable:$isNullable$typeArgumentsStr isEnum:$isEnum isClass:$isClass isProxyApi:$isProxyApi)'; } } @@ -247,6 +428,7 @@ class NamedType extends Node { final List documentationComments; /// Returns a copy of [NamedType] instance with new attached [TypeDeclaration]. + @mustBeOverridden NamedType copyWithType(TypeDeclaration type) { return NamedType( name: name, diff --git a/packages/pigeon/lib/cpp_generator.dart b/packages/pigeon/lib/cpp_generator.dart index 5247f424052e..419695b4ed2b 100644 --- a/packages/pigeon/lib/cpp_generator.dart +++ b/packages/pigeon/lib/cpp_generator.dart @@ -191,14 +191,21 @@ class CppHeaderGenerator extends StructuredGenerator { Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.host); - final bool hasFlutterApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.flutter); + final bool hasHostApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); + final bool hasFlutterApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); _writeFlutterError(indent); if (hasHostApi) { - _writeErrorOr(indent, friends: root.apis.map((Api api) => api.name)); + _writeErrorOr( + indent, + friends: root.apis + .where((Api api) => api is AstFlutterApi || api is AstHostApi) + .map((Api api) => api.name), + ); } if (hasFlutterApi) { // Nothing yet. @@ -291,7 +298,8 @@ class CppHeaderGenerator extends StructuredGenerator { indent.writeln('friend class ${friend.name};'); } } - for (final Api api in root.apis) { + for (final Api api in root.apis + .where((Api api) => api is AstFlutterApi || api is AstHostApi)) { // TODO(gaaclarke): Find a way to be more precise with our // friendships. indent.writeln('friend class ${api.name};'); @@ -317,10 +325,9 @@ class CppHeaderGenerator extends StructuredGenerator { CppOptions generatorOptions, Root root, Indent indent, - Api api, { + AstFlutterApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.flutter); if (getCodecClasses(api, root).isNotEmpty) { _writeCodec(generatorOptions, root, indent, api); } @@ -370,10 +377,9 @@ class CppHeaderGenerator extends StructuredGenerator { CppOptions generatorOptions, Root root, Indent indent, - Api api, { + AstHostApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.host); if (getCodecClasses(api, root).isNotEmpty) { _writeCodec(generatorOptions, root, indent, api); } @@ -823,10 +829,9 @@ class CppSourceGenerator extends StructuredGenerator { CppOptions generatorOptions, Root root, Indent indent, - Api api, { + AstFlutterApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.flutter); if (getCodecClasses(api, root).isNotEmpty) { _writeCodec(generatorOptions, root, indent, api); } @@ -937,10 +942,9 @@ class CppSourceGenerator extends StructuredGenerator { CppOptions generatorOptions, Root root, Indent indent, - Api api, { + AstHostApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.host); if (getCodecClasses(api, root).isNotEmpty) { _writeCodec(generatorOptions, root, indent, api); } diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart index 27a44659269f..8cdea012554b 100644 --- a/packages/pigeon/lib/dart_generator.dart +++ b/packages/pigeon/lib/dart_generator.dart @@ -312,12 +312,11 @@ $resultAt != null DartOptions generatorOptions, Root root, Indent indent, - Api api, { + AstFlutterApi api, { String Function(Method)? channelNameFunc, bool isMockHandler = false, required String dartPackageName, }) { - assert(api.location == ApiLocation.flutter); String codecName = _standardMessageCodec; if (getCodecClasses(api, root).isNotEmpty) { codecName = _getCodecName(api); @@ -391,10 +390,9 @@ $resultAt != null DartOptions generatorOptions, Root root, Indent indent, - Api api, { + AstHostApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.host); String codecName = _standardMessageCodec; if (getCodecClasses(api, root).isNotEmpty) { codecName = _getCodecName(api); @@ -469,13 +467,11 @@ final BinaryMessenger? ${_varNamePrefix}binaryMessenger; relativeDartPath.replaceFirst(RegExp(r'^.*/lib/'), ''); indent.writeln("import 'package:$dartOutputPackageName/$path';"); } - for (final Api api in root.apis) { - if (api.location == ApiLocation.host && api.dartHostTestHandler != null) { - final Api mockApi = Api( + for (final AstHostApi api in root.apis.whereType()) { + if (api.dartHostTestHandler != null) { + final AstFlutterApi mockApi = AstFlutterApi( name: api.dartHostTestHandler!, methods: api.methods, - location: ApiLocation.flutter, - dartHostTestHandler: api.dartHostTestHandler, documentationComments: api.documentationComments, ); writeFlutterApi( @@ -525,10 +521,10 @@ final BinaryMessenger? ${_varNamePrefix}binaryMessenger; Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.host); - final bool hasFlutterApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.flutter); + final bool hasHostApi = + root.apis.any((Api api) => api.methods.isNotEmpty && api is AstHostApi); + final bool hasFlutterApi = root.apis + .any((Api api) => api.methods.isNotEmpty && api is AstFlutterApi); if (hasHostApi) { _writeCreateConnectionError(indent); diff --git a/packages/pigeon/lib/generator.dart b/packages/pigeon/lib/generator.dart index 1bd083078745..3711fd28dbe3 100644 --- a/packages/pigeon/lib/generator.dart +++ b/packages/pigeon/lib/generator.dart @@ -63,6 +63,24 @@ abstract class StructuredGenerator extends Generator { dartPackageName: dartPackageName, ); + if (root.apis.any((Api api) => api is AstProxyApi)) { + writeInstanceManager( + generatorOptions, + root, + indent, + dartPackageName: dartPackageName, + ); + + writeInstanceManagerApi( + generatorOptions, + root, + indent, + dartPackageName: dartPackageName, + ); + + writeProxyApiBaseCodec(generatorOptions, root, indent); + } + writeEnums( generatorOptions, root, @@ -224,22 +242,31 @@ abstract class StructuredGenerator extends Generator { required String dartPackageName, }) { for (final Api api in root.apis) { - if (api.location == ApiLocation.host) { - writeHostApi( - generatorOptions, - root, - indent, - api, - dartPackageName: dartPackageName, - ); - } else if (api.location == ApiLocation.flutter) { - writeFlutterApi( - generatorOptions, - root, - indent, - api, - dartPackageName: dartPackageName, - ); + switch (api) { + case AstHostApi(): + writeHostApi( + generatorOptions, + root, + indent, + api, + dartPackageName: dartPackageName, + ); + case AstFlutterApi(): + writeFlutterApi( + generatorOptions, + root, + indent, + api, + dartPackageName: dartPackageName, + ); + case AstProxyApi(): + writeProxyApi( + generatorOptions, + root, + indent, + api, + dartPackageName: dartPackageName, + ); } } } @@ -249,7 +276,7 @@ abstract class StructuredGenerator extends Generator { T generatorOptions, Root root, Indent indent, - Api api, { + AstFlutterApi api, { required String dartPackageName, }); @@ -258,7 +285,49 @@ abstract class StructuredGenerator extends Generator { T generatorOptions, Root root, Indent indent, - Api api, { + AstHostApi api, { required String dartPackageName, }); + + /// Writes the implementation of an `InstanceManager` to [indent]. + void writeInstanceManager( + T generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) {} + + /// Writes the implementation of the API for the `InstanceManager` to + /// [indent]. + void writeInstanceManagerApi( + T generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) {} + + /// Writes the base codec to be used by all ProxyApis. + /// + /// This codec should use `128` as the identifier for objects that exist in + /// an `InstanceManager`. The write implementation should convert an instance + /// to an identifier. The read implementation should covert the identifier + /// to an instance. + /// + /// This will serve as the default codec for all ProxyApis. If a ProxyApi + /// needs to create its own codec (it has methods/fields/constructor that use + /// a data class) it should extend this codec and not `StandardMessageCodec`. + void writeProxyApiBaseCodec( + T generatorOptions, + Root root, + Indent indent, + ) {} + + /// Writes a single Proxy Api to [indent]. + void writeProxyApi( + T generatorOptions, + Root root, + Indent indent, + AstProxyApi api, { + required String dartPackageName, + }) {} } diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index a399c62f1bea..b9aec2b27994 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/generator_tools.dart @@ -13,7 +13,7 @@ import 'ast.dart'; /// The current version of pigeon. /// /// This must match the version in pubspec.yaml. -const String pigeonVersion = '16.0.4'; +const String pigeonVersion = '16.0.5'; /// Read all the content from [stdin] to a String. String readStdin() { @@ -282,6 +282,24 @@ String getGeneratedCodeWarning() { /// String to be printed after `getGeneratedCodeWarning()'s warning`. const String seeAlsoWarning = 'See also: https://pub.dev/packages/pigeon'; +/// Prefix for utility classes generated for ProxyApis. +/// +/// This lowers the chances of variable name collisions with user defined +/// parameters. +const String classNamePrefix = 'Pigeon_'; + +/// Name for the generated InstanceManager for ProxyApis. +/// +/// This lowers the chances of variable name collisions with user defined +/// parameters. +const String instanceManagerClassName = '${classNamePrefix}InstanceManager'; + +/// Prefix for class member names not defined by the user. +/// +/// This lowers the chances of variable name collisions with user defined +/// parameters. +const String classMemberNamePrefix = 'pigeon_'; + /// Collection of keys used in dictionaries across generators. class Keys { /// The key in the result hash for the 'result' value. @@ -419,6 +437,19 @@ Map> getReferencedTypes( } references.addMany(_getTypeArguments(method.returnType), method.offset); } + if (api is AstProxyApi) { + for (final Constructor constructor in api.constructors) { + for (final NamedType parameter in constructor.parameters) { + references.addMany( + _getTypeArguments(parameter.type), + parameter.offset, + ); + } + } + for (final ApiField field in api.fields) { + references.addMany(_getTypeArguments(field.type), field.offset); + } + } } final Set referencedTypeNames = @@ -588,6 +619,92 @@ String? deducePackageName(String mainDartFile) { } } +/// Recursively search for all the interfaces apis from a list of names of +/// interfaces. +/// +/// This method assumes that all interfaces are ProxyApis and an api doesn't +/// contains itself as an interface. Otherwise, throws an [ArgumentError]. +Set recursiveFindAllInterfaceApis( + AstProxyApi api, { + Set seenApis = const {}, +}) { + final Set allInterfaces = {}; + + allInterfaces.addAll( + api.interfaces.map( + (TypeDeclaration type) { + if (!type.isProxyApi) { + throw ArgumentError( + 'Could not find a valid ProxyApi for an interface: $type', + ); + } else if (seenApis.contains(type.associatedProxyApi)) { + throw ArgumentError( + 'A ProxyApi cannot be a super class of itself: ${type.baseName}', + ); + } + return type.associatedProxyApi!; + }, + ), + ); + + // Adds the current api since it would be invalid for it to be an interface + // of itself. + final Set newSeenApis = {...seenApis, api}; + + for (final AstProxyApi interfaceApi in {...allInterfaces}) { + allInterfaces.addAll(recursiveFindAllInterfaceApis( + interfaceApi, + seenApis: newSeenApis, + )); + } + + return allInterfaces; +} + +/// Creates a list of ProxyApis where each `extends` the ProxyApi that follows +/// it. +/// +/// Returns an empty list if [proxyApi] does not extend a ProxyApi. +/// +/// This method assumes the super classes of each ProxyApi doesn't create a +/// loop. Throws a [ArgumentError] if a loop is found. +/// +/// This method also assumes that all super classes are ProxyApis. Otherwise, +/// throws an [ArgumentError]. +List recursiveGetSuperClassApisChain(AstProxyApi api) { + final List superClassChain = []; + + if (api.superClass != null && !api.superClass!.isProxyApi) { + throw ArgumentError( + 'Could not find a ProxyApi for super class: ${api.superClass!.baseName}', + ); + } + + AstProxyApi? currentProxyApi = api.superClass?.associatedProxyApi; + while (currentProxyApi != null) { + if (superClassChain.contains(currentProxyApi)) { + throw ArgumentError( + 'Loop found when processing super classes for a ProxyApi: ' + '${api.name}, ${superClassChain.map((AstProxyApi api) => api.name)}', + ); + } + + superClassChain.add(currentProxyApi); + + if (currentProxyApi.superClass != null && + !currentProxyApi.superClass!.isProxyApi) { + throw ArgumentError( + 'Could not find a ProxyApi for super class: ' + '${currentProxyApi.superClass!.baseName}', + ); + } + + currentProxyApi = currentProxyApi.superClass?.associatedProxyApi; + } + + return superClassChain; +} + /// Enum to specify api type when generating code. enum ApiType { /// Flutter api. diff --git a/packages/pigeon/lib/java_generator.dart b/packages/pigeon/lib/java_generator.dart index 43b42c40e909..0758071e9435 100644 --- a/packages/pigeon/lib/java_generator.dart +++ b/packages/pigeon/lib/java_generator.dart @@ -400,7 +400,7 @@ class JavaGenerator extends StructuredGenerator { JavaOptions generatorOptions, Root root, Indent indent, - Api api, { + AstFlutterApi api, { required String dartPackageName, }) { /// Returns an argument name that can be used in a context where it is possible to collide @@ -414,7 +414,6 @@ class JavaGenerator extends StructuredGenerator { return '${_getArgumentName(count, argument)}Arg'; } - assert(api.location == ApiLocation.flutter); if (getCodecClasses(api, root).isNotEmpty) { _writeCodec(indent, api, root); } @@ -556,9 +555,9 @@ class JavaGenerator extends StructuredGenerator { required String dartPackageName, }) { if (root.apis.any((Api api) => - api.location == ApiLocation.host && + api is AstHostApi && api.methods.any((Method it) => it.isAsynchronous) || - api.location == ApiLocation.flutter)) { + api is AstFlutterApi)) { indent.newln(); _writeResultInterfaces(indent); } @@ -577,10 +576,9 @@ class JavaGenerator extends StructuredGenerator { JavaOptions generatorOptions, Root root, Indent indent, - Api api, { + AstHostApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.host); if (getCodecClasses(api, root).isNotEmpty) { _writeCodec(indent, api, root); } @@ -979,10 +977,12 @@ protected static ArrayList wrapError(@NonNull Throwable exception) { Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.host); - final bool hasFlutterApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.flutter); + final bool hasHostApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); + final bool hasFlutterApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); indent.newln(); _writeErrorClass(indent); diff --git a/packages/pigeon/lib/kotlin_generator.dart b/packages/pigeon/lib/kotlin_generator.dart index a61eb42865dd..97dd68ac644a 100644 --- a/packages/pigeon/lib/kotlin_generator.dart +++ b/packages/pigeon/lib/kotlin_generator.dart @@ -307,7 +307,7 @@ class KotlinGenerator extends StructuredGenerator { required String dartPackageName, }) { if (root.apis.any((Api api) => - api.location == ApiLocation.host && + api is AstHostApi && api.methods.any((Method it) => it.isAsynchronous))) { indent.newln(); } @@ -325,10 +325,9 @@ class KotlinGenerator extends StructuredGenerator { KotlinOptions generatorOptions, Root root, Indent indent, - Api api, { + AstFlutterApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.flutter); final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; if (isCustomCodec) { _writeCodec(indent, api, root); @@ -388,11 +387,9 @@ class KotlinGenerator extends StructuredGenerator { KotlinOptions generatorOptions, Root root, Indent indent, - Api api, { + AstHostApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.host); - final String apiName = api.name; final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; @@ -573,10 +570,12 @@ class KotlinGenerator extends StructuredGenerator { Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.host); - final bool hasFlutterApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.flutter); + final bool hasHostApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); + final bool hasFlutterApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); if (hasHostApi) { _writeWrapResult(indent); diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart index 903957d083c2..bc26495e650b 100644 --- a/packages/pigeon/lib/objc_generator.dart +++ b/packages/pigeon/lib/objc_generator.dart @@ -617,10 +617,9 @@ class ObjcSourceGenerator extends StructuredGenerator { ObjcOptions generatorOptions, Root root, Indent indent, - Api api, { + AstFlutterApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.flutter); final String apiName = _className(generatorOptions.prefix, api.name); _writeCodecAndGetter(generatorOptions, root, indent, api); @@ -649,10 +648,9 @@ class ObjcSourceGenerator extends StructuredGenerator { ObjcOptions generatorOptions, Root root, Indent indent, - Api api, { + AstHostApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.host); final String apiName = _className(generatorOptions.prefix, api.name); _writeCodecAndGetter(generatorOptions, root, indent, api); @@ -702,10 +700,12 @@ class ObjcSourceGenerator extends StructuredGenerator { Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.host); - final bool hasFlutterApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.flutter); + final bool hasHostApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); + final bool hasFlutterApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); if (hasHostApi) { _writeWrapError(indent); diff --git a/packages/pigeon/lib/pigeon.dart b/packages/pigeon/lib/pigeon.dart index 7f2fb7505fa8..a2c6026e3117 100644 --- a/packages/pigeon/lib/pigeon.dart +++ b/packages/pigeon/lib/pigeon.dart @@ -9,5 +9,7 @@ export 'dart_generator.dart' show DartOptions; export 'java_generator.dart' show JavaOptions; export 'kotlin_generator.dart' show KotlinOptions; export 'objc_generator.dart' show ObjcOptions; -export 'pigeon_lib.dart'; +// TODO(bparrishMines): Remove hide once implementation of the api is finished +// for Dart and one host language. +export 'pigeon_lib.dart' hide ProxyApi; export 'swift_generator.dart' show SwiftOptions; diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index 5acc4f1fa2dd..187aa3466b2a 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -39,9 +39,47 @@ class _Asynchronous { const _Asynchronous(); } +class _Attached { + const _Attached(); +} + +class _Static { + const _Static(); +} + /// Metadata to annotate a Api method as asynchronous const Object async = _Asynchronous(); +/// Metadata to annotate the field of a ProxyApi as an Attached Field. +/// +/// Attached fields provide a synchronous [ProxyApi] instance as a field for +/// another [ProxyApi]. +/// +/// Attached fields: +/// * Must be nonnull. +/// * Must be a ProxyApi (a class annotated with `@ProxyApi()`). +/// * Must not contain any unattached fields. +/// * Must not have a required callback Flutter method. +/// +/// Example generated code: +/// +/// ```dart +/// class MyProxyApi { +/// final MyOtherProxyApi myField = __pigeon_myField(). +/// } +/// ``` +/// +/// The field provides access to the value synchronously, but the native +/// instance is stored in the native `InstanceManager` asynchronously. Similar +/// to how constructors are implemented. +const Object attached = _Attached(); + +/// Metadata to annotate a field of a ProxyApi as static. +/// +/// Static fields are the same as [attached] fields except the field is static +/// and not attached to any instance of the ProxyApi. +const Object static = _Static(); + /// Metadata annotation used to configure how Pigeon will generate code. class ConfigurePigeon { /// Constructor for ConfigurePigeon. @@ -88,6 +126,30 @@ class FlutterApi { const FlutterApi(); } +/// Metadata to annotate a Pigeon API that wraps a native class. +/// +/// The abstract class with this annotation groups a collection of Dart↔host +/// constructors, fields, methods and host↔Dart methods used to wrap a native +/// class. +/// +/// The generated Dart class acts as a proxy to a native type and maintains +/// instances automatically with an `InstanceManager`. The generated host +/// language class implements methods to interact with class instances or static +/// methods. +class ProxyApi { + /// Parametric constructor for [ProxyApi]. + const ProxyApi({this.superClass}); + + /// The proxy api that is a super class to this one. + /// + /// This provides an alternative to calling `extends` on a class since this + /// requires calling the super class constructor. + /// + /// Note that using this instead of `extends` can cause unexpected conflicts + /// with inherited method names. + final Type? superClass; +} + /// Metadata to annotation methods to control the selector used for objc output. /// The number of components in the provided selector must match the number of /// arguments in the annotated method. @@ -788,7 +850,16 @@ List _validateAst(Root root, String source) { } } } + for (final Api api in root.apis) { + if (api is AstProxyApi) { + result.addAll(_validateProxyApi( + api, + source, + customClasses: customClasses.toSet(), + proxyApis: root.apis.whereType().toSet(), + )); + } for (final Method method in api.methods) { for (final Parameter param in method.parameters) { if (param.type.baseName.isEmpty) { @@ -797,20 +868,20 @@ List _validateAst(Root root, String source) { 'Parameters must specify their type in method "${method.name}" in API: "${api.name}"', lineNumber: _calculateLineNumberNullable(source, param.offset), )); - } else if (param.name.startsWith('__pigeon_')) { - result.add(Error( - message: - 'Parameter name must not begin with "__pigeon_" in method "${method.name}" in API: "${api.name}"', - lineNumber: _calculateLineNumberNullable(source, param.offset), - )); - } else if (param.name == 'pigeonChannelCodec') { - result.add(Error( - message: - 'Parameter name must not be "pigeonChannelCodec" in method "${method.name}" in API: "${api.name}"', - lineNumber: _calculateLineNumberNullable(source, param.offset), - )); + } else { + final String? matchingPrefix = _findMatchingPrefixOrNull( + method.name, + prefixes: ['__pigeon_', 'pigeonChannelCodec'], + ); + if (matchingPrefix != null) { + result.add(Error( + message: + 'Parameter name must not begin with "$matchingPrefix" in method "${method.name} in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, param.offset), + )); + } } - if (api.location == ApiLocation.flutter) { + if (api is AstFlutterApi) { if (!param.isPositional) { result.add(Error( message: @@ -848,7 +919,7 @@ List _validateAst(Root root, String source) { } } if (method.taskQueueType != TaskQueueType.serial && - api.location != ApiLocation.host) { + method.location == ApiLocation.flutter) { result.add(Error( message: 'Unsupported TaskQueue specification on ${method.name}', lineNumber: _calculateLineNumberNullable(source, method.offset), @@ -860,6 +931,277 @@ List _validateAst(Root root, String source) { return result; } +List _validateProxyApi( + AstProxyApi api, + String source, { + required Set customClasses, + required Set proxyApis, +}) { + final List result = []; + + bool isDataClass(NamedType type) => + customClasses.contains(type.type.baseName); + bool isProxyApi(NamedType type) => proxyApis.any( + (AstProxyApi api) => api.name == type.type.baseName, + ); + Error unsupportedDataClassError(NamedType type) { + return Error( + message: 'ProxyApis do not support data classes: ${type.type.baseName}.', + lineNumber: _calculateLineNumberNullable(source, type.offset), + ); + } + + AstProxyApi? directSuperClass; + + // Validate direct super class is another ProxyApi + if (api.superClass != null) { + directSuperClass = proxyApis.firstWhereOrNull( + (AstProxyApi proxyApi) => proxyApi.name == api.superClass?.baseName, + ); + if (directSuperClass == null) { + result.add( + Error( + message: 'Super class of ${api.name} is not marked as a @ProxyApi: ' + '${api.superClass?.baseName}', + ), + ); + } + } + + // Validate that the api does not inherit an unattached field from its super class. + if (directSuperClass != null && + directSuperClass.unattachedFields.isNotEmpty) { + result.add(Error( + message: + 'Unattached fields can not be inherited. Unattached field found for parent class: ${directSuperClass.unattachedFields.first.name}', + lineNumber: _calculateLineNumberNullable( + source, + directSuperClass.unattachedFields.first.offset, + ), + )); + } + + // Validate all interfaces are other ProxyApis + final Iterable interfaceNames = api.interfaces.map( + (TypeDeclaration type) => type.baseName, + ); + for (final String interfaceName in interfaceNames) { + if (!proxyApis.any((AstProxyApi api) => api.name == interfaceName)) { + result.add(Error( + message: + 'Interface of ${api.name} is not marked as a @ProxyApi: $interfaceName', + )); + } + } + + final bool hasUnattachedField = api.unattachedFields.isNotEmpty; + final bool hasRequiredFlutterMethod = + api.flutterMethods.any((Method method) => method.isRequired); + for (final AstProxyApi proxyApi in proxyApis) { + // Validate this api is not used as an attached field while either: + // 1. Having an unattached field. + // 2. Having a required Flutter method. + if (hasUnattachedField || hasRequiredFlutterMethod) { + for (final ApiField field in proxyApi.attachedFields) { + if (field.type.baseName == api.name) { + if (hasUnattachedField) { + result.add(Error( + message: + 'ProxyApis with unattached fields can not be used as attached fields: ${field.name}', + lineNumber: _calculateLineNumberNullable( + source, + field.offset, + ), + )); + } + if (hasRequiredFlutterMethod) { + result.add(Error( + message: + 'ProxyApis with required callback methods can not be used as attached fields: ${field.name}', + lineNumber: _calculateLineNumberNullable( + source, + field.offset, + ), + )); + } + } + } + } + + // Validate this api isn't used as an interface and contains anything except + // Flutter methods. + final bool isValidInterfaceProxyApi = api.hostMethods.isEmpty && + api.constructors.isEmpty && + api.fields.isEmpty; + if (!isValidInterfaceProxyApi) { + final Iterable interfaceNames = proxyApi.interfaces.map( + (TypeDeclaration type) => type.baseName, + ); + for (final String interfaceName in interfaceNames) { + if (interfaceName == api.name) { + result.add(Error( + message: + 'ProxyApis used as interfaces can only have callback methods: `${proxyApi.name}` implements `${api.name}`', + )); + } + } + } + } + + // Validate constructor parameters + for (final Constructor constructor in api.constructors) { + for (final Parameter parameter in constructor.parameters) { + if (isDataClass(parameter)) { + result.add(unsupportedDataClassError(parameter)); + } + + if (api.fields.any((ApiField field) => field.name == parameter.name) || + api.flutterMethods + .any((Method method) => method.name == parameter.name)) { + result.add(Error( + message: + 'Parameter names must not share a name with a field or callback method in constructor "${constructor.name}" in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, parameter.offset), + )); + } + + if (parameter.type.baseName.isEmpty) { + result.add(Error( + message: + 'Parameters must specify their type in constructor "${constructor.name}" in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, parameter.offset), + )); + } else { + final String? matchingPrefix = _findMatchingPrefixOrNull( + parameter.name, + prefixes: [ + '__pigeon_', + 'pigeonChannelCodec', + classNamePrefix, + classMemberNamePrefix, + ], + ); + if (matchingPrefix != null) { + result.add(Error( + message: + 'Parameter name must not begin with "$matchingPrefix" in constructor "${constructor.name} in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, parameter.offset), + )); + } + } + } + if (constructor.swiftFunction.isNotEmpty) { + final RegExp signatureRegex = + RegExp('\\w+ *\\((\\w+:){${constructor.parameters.length}}\\)'); + if (!signatureRegex.hasMatch(constructor.swiftFunction)) { + result.add(Error( + message: + 'Invalid constructor signature, expected ${constructor.parameters.length} parameters.', + lineNumber: _calculateLineNumberNullable(source, constructor.offset), + )); + } + } + } + + // Validate method parameters + for (final Method method in api.methods) { + for (final Parameter parameter in method.parameters) { + if (isDataClass(parameter)) { + result.add(unsupportedDataClassError(parameter)); + } + + final String? matchingPrefix = _findMatchingPrefixOrNull( + parameter.name, + prefixes: [ + classNamePrefix, + classMemberNamePrefix, + ], + ); + if (matchingPrefix != null) { + result.add(Error( + message: + 'Parameter name must not begin with "$matchingPrefix" in method "${method.name} in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, parameter.offset), + )); + } + } + + if (method.location == ApiLocation.flutter && method.isStatic) { + result.add(Error( + message: 'Static callback methods are not supported: ${method.name}.', + lineNumber: _calculateLineNumberNullable(source, method.offset), + )); + } + } + + // Validate fields + for (final ApiField field in api.fields) { + if (isDataClass(field)) { + result.add(unsupportedDataClassError(field)); + } else if (field.isStatic) { + if (!isProxyApi(field)) { + result.add(Error( + message: + 'Static fields are considered attached fields and must be a ProxyApi: ${field.type.baseName}', + lineNumber: _calculateLineNumberNullable(source, field.offset), + )); + } else if (field.type.isNullable) { + result.add(Error( + message: + 'Static fields are considered attached fields and must not be nullable: ${field.type.baseName}?', + lineNumber: _calculateLineNumberNullable(source, field.offset), + )); + } + } else if (field.isAttached) { + if (!isProxyApi(field)) { + result.add(Error( + message: 'Attached fields must be a ProxyApi: ${field.type.baseName}', + lineNumber: _calculateLineNumberNullable(source, field.offset), + )); + } + if (field.type.isNullable) { + result.add(Error( + message: + 'Attached fields must not be nullable: ${field.type.baseName}?', + lineNumber: _calculateLineNumberNullable(source, field.offset), + )); + } + } + + final String? matchingPrefix = _findMatchingPrefixOrNull( + field.name, + prefixes: [ + '__pigeon_', + 'pigeonChannelCodec', + classNamePrefix, + classMemberNamePrefix, + ], + ); + if (matchingPrefix != null) { + result.add(Error( + message: + 'Field name must not begin with "$matchingPrefix" in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, field.offset), + )); + } + } + + return result; +} + +String? _findMatchingPrefixOrNull( + String value, { + required List prefixes, +}) { + for (final String prefix in prefixes) { + if (value.startsWith(prefix)) { + return prefix; + } + } + + return null; +} + class _FindInitializer extends dart_ast_visitor.RecursiveAstVisitor { dart_ast.Expression? initializer; @override @@ -928,6 +1270,10 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { !referencedEnums .map((Enum e) => e.name) .contains(element.key.baseName) && + !_apis + .whereType() + .map((AstProxyApi e) => e.name) + .contains(element.key.baseName) && !validTypes.contains(element.key.baseName) && !element.key.isVoid && element.key.baseName != 'dynamic' && @@ -942,21 +1288,34 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { } } for (final Class classDefinition in referencedClasses) { - final List fields = []; - for (final NamedType field in classDefinition.fields) { - fields.add(field.copyWithType(_attachClassesAndEnums(field.type))); - } - classDefinition.fields = fields; + classDefinition.fields = _attachAssociatedDefinitions( + classDefinition.fields, + ); } for (final Api api in _apis) { for (final Method func in api.methods) { - final List paramList = []; - for (final Parameter param in func.parameters) { - paramList.add(param.copyWithType(_attachClassesAndEnums(param.type))); + func.parameters = _attachAssociatedDefinitions(func.parameters); + func.returnType = _attachAssociatedDefinition(func.returnType); + } + if (api is AstProxyApi) { + for (final Constructor constructor in api.constructors) { + constructor.parameters = _attachAssociatedDefinitions( + constructor.parameters, + ); + } + + api.fields = _attachAssociatedDefinitions(api.fields); + + if (api.superClass != null) { + api.superClass = _attachAssociatedDefinition(api.superClass!); } - func.parameters = paramList; - func.returnType = _attachClassesAndEnums(func.returnType); + + final Set newInterfaceSet = {}; + for (final TypeDeclaration interface in api.interfaces) { + newInterfaceSet.add(_attachAssociatedDefinition(interface)); + } + api.interfaces = newInterfaceSet; } } @@ -969,19 +1328,35 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { ); } - TypeDeclaration _attachClassesAndEnums(TypeDeclaration type) { + TypeDeclaration _attachAssociatedDefinition(TypeDeclaration type) { final Enum? assocEnum = _enums.firstWhereOrNull( (Enum enumDefinition) => enumDefinition.name == type.baseName); final Class? assocClass = _classes.firstWhereOrNull( (Class classDefinition) => classDefinition.name == type.baseName); + final AstProxyApi? assocProxyApi = + _apis.whereType().firstWhereOrNull( + (Api apiDefinition) => apiDefinition.name == type.baseName, + ); if (assocClass != null) { return type.copyWithClass(assocClass); } else if (assocEnum != null) { return type.copyWithEnum(assocEnum); + } else if (assocProxyApi != null) { + return type.copyWithProxyApi(assocProxyApi); } return type; } + List _attachAssociatedDefinitions(Iterable types) { + final List result = []; + for (final NamedType type in types) { + result.add( + type.copyWithType(_attachAssociatedDefinition(type.type)) as T, + ); + } + return result; + } + Object _expressionToMap(dart_ast.Expression expression) { if (expression is dart_ast.MethodInvocation) { final Map result = {}; @@ -1004,6 +1379,8 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { return expression.value!; } else if (expression is dart_ast.BooleanLiteral) { return expression.value; + } else if (expression is dart_ast.SimpleIdentifier) { + return expression.name; } else if (expression is dart_ast.ListLiteral) { final List list = []; for (final dart_ast.CollectionElement element in expression.elements) { @@ -1017,6 +1394,19 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { } } return list; + } else if (expression is dart_ast.SetOrMapLiteral) { + final Set set = {}; + for (final dart_ast.CollectionElement element in expression.elements) { + if (element is dart_ast.Expression) { + set.add(_expressionToMap(element)); + } else { + _errors.add(Error( + message: 'expected Expression but found $element', + lineNumber: _calculateLineNumber(source, element.offset), + )); + } + } + return set; } else { _errors.add(Error( message: @@ -1082,19 +1472,75 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { } } } - _currentApi = Api( + + _currentApi = AstHostApi( name: node.name.lexeme, - location: ApiLocation.host, methods: [], dartHostTestHandler: dartHostTestHandler, documentationComments: _documentationCommentsParser(node.documentationComment?.tokens), ); } else if (_hasMetadata(node.metadata, 'FlutterApi')) { - _currentApi = Api( + _currentApi = AstFlutterApi( + name: node.name.lexeme, + methods: [], + documentationComments: + _documentationCommentsParser(node.documentationComment?.tokens), + ); + } else if (_hasMetadata(node.metadata, 'ProxyApi')) { + final dart_ast.Annotation proxyApiAnnotation = node.metadata.firstWhere( + (dart_ast.Annotation element) => element.name.name == 'ProxyApi', + ); + + final Map annotationMap = {}; + for (final dart_ast.Expression expression + in proxyApiAnnotation.arguments!.arguments) { + if (expression is dart_ast.NamedExpression) { + annotationMap[expression.name.label.name] = + _expressionToMap(expression.expression); + } + } + + final String? superClassName = annotationMap['superClass'] as String?; + TypeDeclaration? superClass; + if (superClassName != null && node.extendsClause != null) { + _errors.add( + Error( + message: + 'ProxyApis should either set the super class in the annotation OR use extends: ("${node.name.lexeme}").', + lineNumber: _calculateLineNumber(source, node.offset), + ), + ); + } else if (superClassName != null) { + superClass = TypeDeclaration( + baseName: superClassName, + isNullable: false, + ); + } else if (node.extendsClause != null) { + superClass = TypeDeclaration( + baseName: node.extendsClause!.superclass.name2.lexeme, + isNullable: false, + ); + } + + final Set interfaces = {}; + if (node.implementsClause != null) { + for (final dart_ast.NamedType type + in node.implementsClause!.interfaces) { + interfaces.add(TypeDeclaration( + baseName: type.name2.lexeme, + isNullable: false, + )); + } + } + + _currentApi = AstProxyApi( name: node.name.lexeme, - location: ApiLocation.flutter, methods: [], + constructors: [], + fields: [], + superClass: superClass, + interfaces: interfaces, documentationComments: _documentationCommentsParser(node.documentationComment?.tokens), ); @@ -1205,6 +1651,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { final List arguments = parameters.parameters.map(formalParameterToPigeonParameter).toList(); final bool isAsynchronous = _hasMetadata(node.metadata, 'async'); + final bool isStatic = _hasMetadata(node.metadata, 'static'); final String objcSelector = _findMetadata(node.metadata, 'ObjCSelector') ?.arguments ?.arguments @@ -1244,6 +1691,12 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { typeAnnotationsToTypeArguments(returnType.typeArguments), isNullable: returnType.question != null), parameters: arguments, + isStatic: isStatic, + location: switch (_currentApi!) { + AstHostApi() => ApiLocation.host, + AstProxyApi() => ApiLocation.host, + AstFlutterApi() => ApiLocation.flutter, + }, isAsynchronous: isAsynchronous, objcSelector: objcSelector, swiftFunction: swiftFunction, @@ -1299,8 +1752,8 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { @override Object? visitFieldDeclaration(dart_ast.FieldDeclaration node) { + final dart_ast.TypeAnnotation? type = node.fields.type; if (_currentClass != null) { - final dart_ast.TypeAnnotation? type = node.fields.type; if (node.isStatic) { _errors.add(Error( message: @@ -1336,6 +1789,8 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { message: 'Expected a named type but found "$node".', lineNumber: _calculateLineNumber(source, node.offset))); } + } else if (_currentApi is AstProxyApi) { + _addProxyApiField(type, node); } else if (_currentApi != null) { _errors.add(Error( message: 'Fields aren\'t supported in Pigeon API classes ("$node").', @@ -1347,7 +1802,30 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { @override Object? visitConstructorDeclaration(dart_ast.ConstructorDeclaration node) { - if (_currentApi != null) { + if (_currentApi is AstProxyApi) { + final dart_ast.FormalParameterList parameters = node.parameters; + final List arguments = + parameters.parameters.map(formalParameterToPigeonParameter).toList(); + final String swiftFunction = _findMetadata(node.metadata, 'SwiftFunction') + ?.arguments + ?.arguments + .first + .asNullable() + ?.value ?? + ''; + + (_currentApi as AstProxyApi?)!.constructors.add( + Constructor( + name: node.name?.lexeme ?? '', + parameters: arguments, + swiftFunction: swiftFunction, + offset: node.offset, + documentationComments: _documentationCommentsParser( + node.documentationComment?.tokens, + ), + ), + ); + } else if (_currentApi != null) { _errors.add(Error( message: 'Constructors aren\'t supported in API classes ("$node").', lineNumber: _calculateLineNumber(source, node.offset))); @@ -1385,6 +1863,91 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { } return node.name2.lexeme; } + + void _addProxyApiField( + dart_ast.TypeAnnotation? type, + dart_ast.FieldDeclaration node, + ) { + final bool isStatic = _hasMetadata(node.metadata, 'static'); + if (type is dart_ast.GenericFunctionType) { + final List parameters = type.parameters.parameters + .map(formalParameterToPigeonParameter) + .toList(); + final String swiftFunction = _findMetadata(node.metadata, 'SwiftFunction') + ?.arguments + ?.arguments + .first + .asNullable() + ?.value ?? + ''; + final dart_ast.ArgumentList? taskQueueArguments = + _findMetadata(node.metadata, 'TaskQueue')?.arguments; + final String? taskQueueTypeName = taskQueueArguments == null + ? null + : getFirstChildOfType(taskQueueArguments) + ?.expression + .asNullable() + ?.name; + final TaskQueueType taskQueueType = + _stringToEnum(TaskQueueType.values, taskQueueTypeName) ?? + TaskQueueType.serial; + + // Methods without named return types aren't supported. + final dart_ast.TypeAnnotation returnType = type.returnType!; + returnType as dart_ast.NamedType; + + _currentApi!.methods.add( + Method( + name: node.fields.variables[0].name.lexeme, + returnType: TypeDeclaration( + baseName: _getNamedTypeQualifiedName(returnType), + typeArguments: + typeAnnotationsToTypeArguments(returnType.typeArguments), + isNullable: returnType.question != null, + ), + location: ApiLocation.flutter, + isRequired: type.question == null, + isStatic: isStatic, + parameters: parameters, + isAsynchronous: _hasMetadata(node.metadata, 'async'), + swiftFunction: swiftFunction, + offset: node.offset, + taskQueueType: taskQueueType, + documentationComments: + _documentationCommentsParser(node.documentationComment?.tokens), + ), + ); + } else if (type is dart_ast.NamedType) { + final _FindInitializer findInitializerVisitor = _FindInitializer(); + node.visitChildren(findInitializerVisitor); + if (findInitializerVisitor.initializer != null) { + _errors.add(Error( + message: + 'Initialization isn\'t supported for fields in ProxyApis ("$node"), just use nullable types with no initializer (example "int? x;").', + lineNumber: _calculateLineNumber(source, node.offset))); + } else { + final dart_ast.TypeArgumentList? typeArguments = type.typeArguments; + (_currentApi as AstProxyApi?)!.fields.add( + ApiField( + type: TypeDeclaration( + baseName: _getNamedTypeQualifiedName(type), + isNullable: type.question != null, + typeArguments: typeAnnotationsToTypeArguments( + typeArguments, + ), + ), + name: node.fields.variables[0].name.lexeme, + isAttached: _hasMetadata(node.metadata, 'attached') || isStatic, + isStatic: isStatic, + offset: node.offset, + documentationComments: _documentationCommentsParser( + node.documentationComment?.tokens, + ), + ), + ); + } + } + } } int? _calculateLineNumberNullable(String contents, int? offset) { diff --git a/packages/pigeon/lib/swift_generator.dart b/packages/pigeon/lib/swift_generator.dart index 91319402c4fe..61e2ea20faf3 100644 --- a/packages/pigeon/lib/swift_generator.dart +++ b/packages/pigeon/lib/swift_generator.dart @@ -240,7 +240,7 @@ class SwiftGenerator extends StructuredGenerator { required String dartPackageName, }) { if (root.apis.any((Api api) => - api.location == ApiLocation.host && + api is AstHostApi && api.methods.any((Method it) => it.isAsynchronous))) { indent.newln(); } @@ -260,11 +260,9 @@ class SwiftGenerator extends StructuredGenerator { SwiftOptions generatorOptions, Root root, Indent indent, - Api api, { + AstFlutterApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.flutter); - /// Returns an argument name that can be used in a context where it is possible to collide. String getEnumSafeArgumentExpression( Root root, int count, NamedType argument) { @@ -384,11 +382,9 @@ class SwiftGenerator extends StructuredGenerator { SwiftOptions generatorOptions, Root root, Indent indent, - Api api, { + AstHostApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.host); - final String apiName = api.name; final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; @@ -812,10 +808,12 @@ private func nilOrValue(_ value: Any?) -> T? { Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.host); - final bool hasFlutterApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.flutter); + final bool hasHostApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); + final bool hasFlutterApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); if (hasHostApi) { _writeWrapResult(indent); diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index 6ac3bb294491..e077f7d81324 100644 --- a/packages/pigeon/pubspec.yaml +++ b/packages/pigeon/pubspec.yaml @@ -2,7 +2,7 @@ name: pigeon description: Code generator tool to make communication between Flutter and the host platform type-safe and easier. repository: https://github.com/flutter/packages/tree/main/packages/pigeon issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+pigeon%22 -version: 16.0.4 # This must match the version in lib/generator_tools.dart +version: 16.0.5 # This must match the version in lib/generator_tools.dart environment: sdk: ">=3.0.0 <4.0.0" @@ -11,7 +11,7 @@ dependencies: analyzer: ">=5.13.0 <7.0.0" args: ^2.1.0 collection: ^1.15.0 - meta: ^1.7.0 + meta: ^1.9.0 path: ^1.8.0 yaml: ^3.1.1 diff --git a/packages/pigeon/test/cpp_generator_test.dart b/packages/pigeon/test/cpp_generator_test.dart index cbd290b42fa3..42aa28c5361a 100644 --- a/packages/pigeon/test/cpp_generator_test.dart +++ b/packages/pigeon/test/cpp_generator_test.dart @@ -25,7 +25,7 @@ final Enum emptyEnum = Enum( void main() { test('gen one api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', parameters: [ @@ -37,6 +37,7 @@ void main() { ), name: 'input') ], + location: ApiLocation.host, returnType: TypeDeclaration( baseName: 'Output', isNullable: false, @@ -101,7 +102,7 @@ void main() { test('naming follows style', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', parameters: [ @@ -113,6 +114,7 @@ void main() { ), name: 'someInput') ], + location: ApiLocation.host, returnType: TypeDeclaration( baseName: 'Output', isNullable: false, @@ -179,9 +181,10 @@ void main() { test('FlutterError fields are private with public accessors', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: const TypeDeclaration( @@ -230,9 +233,10 @@ void main() { test('Error field is private with public accessors', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: const TypeDeclaration( @@ -273,9 +277,10 @@ void main() { test('Spaces before {', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -350,9 +355,10 @@ void main() { test('include blocks follow style', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: const TypeDeclaration( @@ -423,9 +429,10 @@ void main() { test('namespaces follows style', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: const TypeDeclaration( @@ -478,9 +485,10 @@ void main() { test('data classes handle nullable fields', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -661,9 +669,10 @@ void main() { test('data classes handle non-nullable fields', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -804,9 +813,10 @@ void main() { test('host nullable return types map correctly', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'returnNullableBool', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'bool', @@ -815,6 +825,7 @@ void main() { ), Method( name: 'returnNullableInt', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'int', @@ -823,6 +834,7 @@ void main() { ), Method( name: 'returnNullableString', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'String', @@ -831,6 +843,7 @@ void main() { ), Method( name: 'returnNullableList', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'List', @@ -845,6 +858,7 @@ void main() { ), Method( name: 'returnNullableMap', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'Map', @@ -863,6 +877,7 @@ void main() { ), Method( name: 'returnNullableDataClass', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'ReturnData', @@ -921,9 +936,10 @@ void main() { test('host non-nullable return types map correctly', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'returnBool', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'bool', @@ -932,6 +948,7 @@ void main() { ), Method( name: 'returnInt', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'int', @@ -940,6 +957,7 @@ void main() { ), Method( name: 'returnString', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'String', @@ -948,6 +966,7 @@ void main() { ), Method( name: 'returnList', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'List', @@ -962,6 +981,7 @@ void main() { ), Method( name: 'returnMap', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'Map', @@ -980,6 +1000,7 @@ void main() { ), Method( name: 'returnDataClass', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'ReturnData', @@ -1024,9 +1045,10 @@ void main() { test('host nullable arguments map correctly', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( name: 'aBool', @@ -1179,9 +1201,10 @@ void main() { test('host non-nullable arguments map correctly', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( name: 'aBool', @@ -1329,9 +1352,10 @@ void main() { test('flutter nullable arguments map correctly', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'aBool', @@ -1488,9 +1512,10 @@ void main() { test('flutter non-nullable arguments map correctly', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'aBool', @@ -1621,9 +1646,10 @@ void main() { test('host API argument extraction uses references', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( name: 'anArg', @@ -1661,9 +1687,10 @@ void main() { test('enum argument', () { final Root root = Root( apis: [ - Api(name: 'Bar', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Bar', methods: [ Method( name: 'bar', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1705,13 +1732,13 @@ void main() { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, documentationComments: [comments[count++]], methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), documentationComments: [comments[count++]], parameters: [ @@ -1785,12 +1812,12 @@ void main() { test("doesn't create codecs if no custom datatypes", () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1827,9 +1854,10 @@ void main() { test('creates custom codecs if custom datatypes present', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -1884,9 +1912,10 @@ void main() { test('Does not send unwrapped EncodableLists', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( name: 'aBool', @@ -1966,15 +1995,17 @@ void main() { test('does not keep unowned references in async handlers', () { final Root root = Root(apis: [ - Api(name: 'HostApi', location: ApiLocation.host, methods: [ + AstHostApi(name: 'HostApi', methods: [ Method( name: 'noop', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration.voidDeclaration(), isAsynchronous: true, ), Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: const TypeDeclaration( @@ -1988,15 +2019,17 @@ void main() { isAsynchronous: true, ), ]), - Api(name: 'FlutterApi', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'FlutterApi', methods: [ Method( name: 'noop', + location: ApiLocation.flutter, parameters: [], returnType: const TypeDeclaration.voidDeclaration(), isAsynchronous: true, ), Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: const TypeDeclaration( @@ -2038,12 +2071,12 @@ void main() { test('connection error contains channel name', () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart index eb55b7a494ba..4c96bd7d74af 100644 --- a/packages/pigeon/test/dart_generator_test.dart +++ b/packages/pigeon/test/dart_generator_test.dart @@ -85,9 +85,10 @@ void main() { test('gen one host api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -137,9 +138,10 @@ void main() { test('host multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.host, parameters: [ Parameter( name: 'x', @@ -170,9 +172,10 @@ void main() { test('flutter multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -305,9 +308,10 @@ void main() { test('flutterApi', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -358,9 +362,10 @@ void main() { test('host void', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -398,9 +403,10 @@ void main() { test('flutter void return', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -441,9 +447,10 @@ void main() { test('flutter void argument', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -477,9 +484,10 @@ void main() { test('flutter enum argument with enum class', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -531,9 +539,10 @@ void main() { test('primitive enum host', () { final Root root = Root(apis: [ - Api(name: 'Bar', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Bar', methods: [ Method( name: 'bar', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -567,9 +576,10 @@ void main() { test('flutter non-nullable enum argument with enum class', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -620,9 +630,10 @@ void main() { test('host void argument', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -655,42 +666,40 @@ void main() { test('mock dart handler', () { final Root root = Root(apis: [ - Api( - name: 'Api', + AstHostApi(name: 'Api', dartHostTestHandler: 'ApiMock', methods: [ + Method( + name: 'doSomething', location: ApiLocation.host, - dartHostTestHandler: 'ApiMock', - methods: [ - Method( - name: 'doSomething', - parameters: [ - Parameter( - type: TypeDeclaration( - baseName: 'Input', - isNullable: false, - associatedClass: emptyClass, - ), - name: '') - ], - returnType: TypeDeclaration( - baseName: 'Output', - isNullable: false, - associatedClass: emptyClass, - ), - ), - Method( - name: 'voidReturner', - parameters: [ - Parameter( - type: TypeDeclaration( - baseName: 'Input', - isNullable: false, - associatedClass: emptyClass, - ), - name: '') - ], - returnType: const TypeDeclaration.voidDeclaration(), - ) - ]) + parameters: [ + Parameter( + type: TypeDeclaration( + baseName: 'Input', + isNullable: false, + associatedClass: emptyClass, + ), + name: '') + ], + returnType: TypeDeclaration( + baseName: 'Output', + isNullable: false, + associatedClass: emptyClass, + ), + ), + Method( + name: 'voidReturner', + location: ApiLocation.host, + parameters: [ + Parameter( + type: TypeDeclaration( + baseName: 'Input', + isNullable: false, + associatedClass: emptyClass, + ), + name: '') + ], + returnType: const TypeDeclaration.voidDeclaration(), + ) + ]) ], classes: [ Class(name: 'Input', fields: [ NamedType( @@ -748,9 +757,10 @@ void main() { test('gen one async Flutter Api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -803,9 +813,10 @@ void main() { test('gen one async Flutter Api with void return', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -853,9 +864,10 @@ void main() { test('gen one async Host Api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -906,9 +918,10 @@ void main() { test('async host void argument', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -1027,9 +1040,10 @@ void main() { test('host generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1061,9 +1075,10 @@ void main() { test('flutter generics argument with void return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1095,9 +1110,10 @@ void main() { test('host generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -1129,9 +1145,10 @@ void main() { test('flutter generics argument non void return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -1173,9 +1190,10 @@ void main() { test('return nullable host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1202,9 +1220,10 @@ void main() { test('return nullable collection host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'List', isNullable: true, @@ -1236,9 +1255,10 @@ void main() { test('return nullable async host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1266,9 +1286,10 @@ void main() { test('return nullable flutter', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1295,9 +1316,10 @@ void main() { test('return nullable async flutter', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1325,9 +1347,10 @@ void main() { test('platform error for return nil on nonnull', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: false, @@ -1356,9 +1379,10 @@ void main() { test('nullable argument host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1388,9 +1412,10 @@ void main() { test('nullable argument flutter', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1420,9 +1445,10 @@ void main() { test('named argument flutter', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1502,13 +1528,13 @@ name: foobar final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, documentationComments: [comments[count++]], methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), documentationComments: [comments[count++]], parameters: [ @@ -1577,12 +1603,12 @@ name: foobar test("doesn't create codecs if no custom datatypes", () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1615,9 +1641,10 @@ name: foobar test('creates custom codecs if custom datatypes present', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -1668,13 +1695,13 @@ name: foobar test('host test code handles enums', () { final Root root = Root( apis: [ - Api( + AstHostApi( name: 'Api', - location: ApiLocation.host, dartHostTestHandler: 'ApiMock', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1721,9 +1748,10 @@ name: foobar test('connection error contains channel name', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'method', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration(baseName: 'Output', isNullable: false), @@ -1750,15 +1778,16 @@ name: foobar test('generate wrapResponse if is generating tests', () { final Root root = Root( apis: [ - Api( + AstHostApi( name: 'Api', - location: ApiLocation.host, dartHostTestHandler: 'ApiMock', methods: [ Method( - name: 'foo', - returnType: const TypeDeclaration.voidDeclaration(), - parameters: []) + name: 'foo', + location: ApiLocation.host, + returnType: const TypeDeclaration.voidDeclaration(), + parameters: [], + ) ]) ], classes: [], diff --git a/packages/pigeon/test/generator_tools_test.dart b/packages/pigeon/test/generator_tools_test.dart index 4570ac633aaa..6b43b93eecc5 100644 --- a/packages/pigeon/test/generator_tools_test.dart +++ b/packages/pigeon/test/generator_tools_test.dart @@ -78,10 +78,10 @@ void main() { }); test('get codec classes from argument type arguments', () { - final Api api = - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + final AstFlutterApi api = AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -123,10 +123,10 @@ void main() { }); test('get codec classes from return value type arguments', () { - final Api api = - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + final AstFlutterApi api = AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -168,10 +168,10 @@ void main() { }); test('get codec classes from all arguments', () { - final Api api = - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + final AstFlutterApi api = AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -216,9 +216,10 @@ void main() { test('getCodecClasses: nested type arguments', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'foo', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -272,12 +273,12 @@ void main() { test('getCodecClasses: with Object', () { final Root root = Root(apis: [ - Api( + AstFlutterApi( name: 'Api1', - location: ApiLocation.flutter, methods: [ Method( name: 'foo', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -311,12 +312,12 @@ void main() { test('getCodecClasses: unique entries', () { final Root root = Root(apis: [ - Api( + AstFlutterApi( name: 'Api1', - location: ApiLocation.flutter, methods: [ Method( name: 'foo', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -330,12 +331,12 @@ void main() { ) ], ), - Api( + AstHostApi( name: 'Api2', - location: ApiLocation.host, methods: [ Method( name: 'foo', + location: ApiLocation.host, parameters: [ Parameter( name: 'x', @@ -372,4 +373,159 @@ void main() { expect(dartPackageName, 'pigeon'); }); + + test('recursiveGetSuperClassApisChain', () { + final AstProxyApi superClassOfSuperClassApi = AstProxyApi( + name: 'Api3', + methods: [], + constructors: [], + fields: [], + ); + final AstProxyApi superClassApi = AstProxyApi( + name: 'Api2', + methods: [], + constructors: [], + fields: [], + superClass: TypeDeclaration( + baseName: 'Api3', + isNullable: false, + associatedProxyApi: superClassOfSuperClassApi, + ), + ); + final AstProxyApi api = AstProxyApi( + name: 'Api', + methods: [], + constructors: [], + fields: [], + superClass: TypeDeclaration( + baseName: 'Api2', + isNullable: false, + associatedProxyApi: superClassApi, + ), + ); + + final List apiChain = recursiveGetSuperClassApisChain(api); + + expect( + apiChain, + containsAllInOrder([ + superClassApi, + superClassOfSuperClassApi, + ]), + ); + }); + + test('recursiveFindAllInterfacesApis', () { + final AstProxyApi interfaceOfInterfaceApi2 = AstProxyApi( + name: 'Api5', + methods: [], + constructors: [], + fields: [], + ); + final AstProxyApi interfaceOfInterfaceApi = AstProxyApi( + name: 'Api4', + methods: [], + constructors: [], + fields: [], + ); + final AstProxyApi interfaceApi2 = AstProxyApi( + name: 'Api3', + methods: [], + constructors: [], + fields: [], + interfaces: { + TypeDeclaration( + baseName: 'Api5', + isNullable: false, + associatedProxyApi: interfaceOfInterfaceApi2, + ), + }, + ); + final AstProxyApi interfaceApi = AstProxyApi( + name: 'Api2', + methods: [], + constructors: [], + fields: [], + interfaces: { + TypeDeclaration( + baseName: 'Api4', + isNullable: false, + associatedProxyApi: interfaceOfInterfaceApi, + ), + TypeDeclaration( + baseName: 'Api5', + isNullable: false, + associatedProxyApi: interfaceOfInterfaceApi2, + ), + }, + ); + final AstProxyApi api = AstProxyApi( + name: 'Api', + methods: [], + constructors: [], + fields: [], + interfaces: { + TypeDeclaration( + baseName: 'Api2', + isNullable: false, + associatedProxyApi: interfaceApi, + ), + TypeDeclaration( + baseName: 'Api3', + isNullable: false, + associatedProxyApi: interfaceApi2, + ), + }, + ); + + final Set allInterfaces = recursiveFindAllInterfaceApis(api); + + expect( + allInterfaces, + containsAll([ + interfaceApi, + interfaceApi2, + interfaceOfInterfaceApi, + interfaceOfInterfaceApi2, + ]), + ); + }); + + test( + 'recursiveFindAllInterfacesApis throws error if api recursively implements itself', + () { + final AstProxyApi a = AstProxyApi( + name: 'A', + methods: [], + constructors: [], + fields: [], + ); + final AstProxyApi b = AstProxyApi( + name: 'B', + methods: [], + constructors: [], + fields: [], + ); + final AstProxyApi c = AstProxyApi( + name: 'C', + methods: [], + constructors: [], + fields: [], + ); + + a.interfaces = { + TypeDeclaration(baseName: 'B', isNullable: false, associatedProxyApi: b), + }; + b.interfaces = { + TypeDeclaration(baseName: 'C', isNullable: false, associatedProxyApi: c), + }; + c.interfaces = { + TypeDeclaration(baseName: 'A', isNullable: false, associatedProxyApi: a), + }; + + expect( + () => recursiveFindAllInterfaceApis(a), + throwsArgumentError, + ); + }); } diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart index cba2d702255c..97cbfe8b4d09 100644 --- a/packages/pigeon/test/java_generator_test.dart +++ b/packages/pigeon/test/java_generator_test.dart @@ -123,9 +123,10 @@ void main() { test('gen one host api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -265,9 +266,10 @@ void main() { test('gen one flutter api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -318,9 +320,10 @@ void main() { test('gen host void api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -359,9 +362,10 @@ void main() { test('gen flutter void return api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -403,9 +407,10 @@ void main() { test('gen host void argument api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -440,9 +445,10 @@ void main() { test('gen flutter void argument api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -578,9 +584,10 @@ void main() { test('gen one async Host Api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -639,9 +646,10 @@ void main() { test('gen one async Flutter Api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -745,9 +753,10 @@ void main() { test('primitive enum host', () { final Root root = Root(apis: [ - Api(name: 'Bar', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Bar', methods: [ Method( name: 'bar', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -875,9 +884,10 @@ void main() { test('host generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -910,9 +920,10 @@ void main() { test('flutter generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -945,9 +956,10 @@ void main() { test('host generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -977,9 +989,10 @@ void main() { test('flutter generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -1010,9 +1023,10 @@ void main() { test('flutter int return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration(baseName: 'int', isNullable: false), parameters: [], @@ -1041,9 +1055,10 @@ void main() { test('host multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.host, parameters: [ Parameter( name: 'x', @@ -1082,9 +1097,10 @@ void main() { test('if host argType is Object not cast', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'objectTest', + location: ApiLocation.host, parameters: [ Parameter( name: 'x', @@ -1110,9 +1126,10 @@ void main() { test('flutter multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -1152,9 +1169,10 @@ void main() { test('flutter single args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'send', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -1184,9 +1202,10 @@ void main() { test('return nullable host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1213,9 +1232,10 @@ void main() { test('return nullable host async', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1244,9 +1264,10 @@ void main() { test('nullable argument host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1277,9 +1298,10 @@ void main() { test('nullable argument flutter', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1313,9 +1335,10 @@ void main() { test('background platform channel', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1415,13 +1438,13 @@ void main() { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'api', - location: ApiLocation.flutter, documentationComments: [comments[count++]], methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), documentationComments: [comments[count++]], parameters: [ @@ -1496,12 +1519,12 @@ void main() { test("doesn't create codecs if no custom datatypes", () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1535,9 +1558,10 @@ void main() { test('creates custom codecs if custom datatypes present', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -1587,8 +1611,7 @@ void main() { }); test('creates api error class for custom errors', () { - final Api api = - Api(name: 'Api', location: ApiLocation.host, methods: []); + final Api api = AstHostApi(name: 'Api', methods: []); final Root root = Root( apis: [api], classes: [], @@ -1610,12 +1633,12 @@ void main() { test('connection error contains channel name', () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( diff --git a/packages/pigeon/test/kotlin_generator_test.dart b/packages/pigeon/test/kotlin_generator_test.dart index f51c68b32bcc..e680e3ac66d2 100644 --- a/packages/pigeon/test/kotlin_generator_test.dart +++ b/packages/pigeon/test/kotlin_generator_test.dart @@ -140,9 +140,10 @@ void main() { test('primitive enum host', () { final Root root = Root(apis: [ - Api(name: 'Bar', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Bar', methods: [ Method( name: 'bar', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -176,9 +177,10 @@ void main() { test('gen one host api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -405,9 +407,10 @@ void main() { test('gen one flutter api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -462,9 +465,10 @@ void main() { test('gen host void api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -505,9 +509,10 @@ void main() { test('gen flutter void return api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -550,9 +555,10 @@ void main() { test('gen host void argument api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -590,9 +596,10 @@ void main() { test('gen flutter void argument api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -732,9 +739,10 @@ void main() { test('gen one async Host Api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -790,9 +798,10 @@ void main() { test('gen one async Flutter Api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -979,9 +988,10 @@ void main() { test('host generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1015,9 +1025,10 @@ void main() { test('flutter generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1051,9 +1062,10 @@ void main() { test('host generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -1084,9 +1096,10 @@ void main() { test('flutter generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -1116,9 +1129,10 @@ void main() { test('host multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.host, parameters: [ Parameter( name: 'x', @@ -1159,9 +1173,10 @@ void main() { test('flutter multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -1203,9 +1218,10 @@ void main() { test('return nullable host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1232,9 +1248,10 @@ void main() { test('return nullable host async', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1262,9 +1279,10 @@ void main() { test('nullable argument host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1298,9 +1316,10 @@ void main() { test('nullable argument flutter', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1333,9 +1352,10 @@ void main() { test('nonnull fields', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -1389,13 +1409,13 @@ void main() { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'api', - location: ApiLocation.flutter, documentationComments: [comments[count++]], methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), documentationComments: [comments[count++]], parameters: [ @@ -1470,12 +1490,12 @@ void main() { test("doesn't create codecs if no custom datatypes", () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1509,9 +1529,10 @@ void main() { test('creates custom codecs if custom datatypes present', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -1563,10 +1584,11 @@ void main() { test('creates api error class for custom errors', () { final Method method = Method( name: 'doSomething', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: []); - final Api api = Api( - name: 'SomeApi', location: ApiLocation.host, methods: [method]); + final AstHostApi api = + AstHostApi(name: 'SomeApi', methods: [method]); final Root root = Root( apis: [api], classes: [], @@ -1593,12 +1615,12 @@ void main() { test('connection error contains channel name', () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1639,12 +1661,12 @@ void main() { test('gen host uses default error class', () { final Root root = Root( apis: [ - Api( + AstHostApi( name: 'Api', - location: ApiLocation.host, methods: [ Method( name: 'method', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1678,12 +1700,12 @@ void main() { test('gen flutter uses default error class', () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1717,12 +1739,12 @@ void main() { test('gen host uses error class', () { final Root root = Root( apis: [ - Api( + AstHostApi( name: 'Api', - location: ApiLocation.host, methods: [ Method( name: 'method', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1759,12 +1781,12 @@ void main() { test('gen flutter uses error class', () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart index f77f186ac8df..267747e1e913 100644 --- a/packages/pigeon/test/objc_generator_test.dart +++ b/packages/pigeon/test/objc_generator_test.dart @@ -188,9 +188,10 @@ void main() { test('primitive enum host', () { final Root root = Root(apis: [ - Api(name: 'Bar', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Bar', methods: [ Method( name: 'bar', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -251,9 +252,10 @@ void main() { test('validate nullable primitive enum', () { final Root root = Root(apis: [ - Api(name: 'Bar', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Bar', methods: [ Method( name: 'bar', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -328,9 +330,10 @@ void main() { test('gen one api header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -383,9 +386,10 @@ void main() { test('gen one api source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -656,9 +660,10 @@ void main() { test('prefix nested class header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -711,9 +716,10 @@ void main() { test('prefix nested class source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -766,9 +772,10 @@ void main() { test('gen flutter api header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -821,9 +828,10 @@ void main() { test('gen flutter api source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -872,9 +880,10 @@ void main() { test('gen host void header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -913,9 +922,10 @@ void main() { test('gen host void source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -956,9 +966,10 @@ void main() { test('gen flutter void return header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -997,9 +1008,10 @@ void main() { test('gen flutter void return source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -1039,9 +1051,10 @@ void main() { test('gen host void arg header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -1076,9 +1089,10 @@ void main() { test('gen host void arg source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -1113,9 +1127,10 @@ void main() { test('gen flutter void arg header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -1153,9 +1168,10 @@ void main() { test('gen flutter void arg source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -1281,9 +1297,10 @@ void main() { test('gen map argument with object', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1317,9 +1334,10 @@ void main() { test('async void (input) HostApi header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -1367,9 +1385,10 @@ void main() { test('async output(input) HostApi header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -1421,9 +1440,10 @@ void main() { test('async output(void) HostApi header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -1462,9 +1482,10 @@ void main() { test('async void (void) HostApi header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration.voidDeclaration(), isAsynchronous: true) @@ -1493,9 +1514,10 @@ void main() { test('async output(input) HostApi source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -1547,9 +1569,10 @@ void main() { test('async void (input) HostApi source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -1597,9 +1620,10 @@ void main() { test('async void (void) HostApi source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration.voidDeclaration(), isAsynchronous: true) @@ -1628,9 +1652,10 @@ void main() { test('async output(void) HostApi source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -1755,9 +1780,10 @@ void main() { test('host generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1818,9 +1844,10 @@ void main() { test('flutter generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1878,9 +1905,10 @@ void main() { test('host nested generic argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1926,9 +1954,10 @@ void main() { test('host generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -1983,9 +2012,10 @@ void main() { test('flutter generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -2040,9 +2070,10 @@ void main() { test('host multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.host, parameters: [ Parameter( name: 'x', @@ -2110,9 +2141,10 @@ void main() { test('host multiple args async', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.host, parameters: [ Parameter( name: 'x', @@ -2180,9 +2212,10 @@ void main() { test('flutter multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -2245,25 +2278,48 @@ void main() { Root getDivideRoot(ApiLocation location) => Root( apis: [ - Api(name: 'Api', location: location, methods: [ - Method( - name: 'divide', - objcSelector: 'divideValue:by:', - parameters: [ - Parameter( - type: const TypeDeclaration( - baseName: 'int', isNullable: false), - name: 'x', - ), - Parameter( - type: const TypeDeclaration( - baseName: 'int', isNullable: false), - name: 'y', - ), - ], - returnType: const TypeDeclaration( - baseName: 'double', isNullable: false)) - ]) + switch (location) { + ApiLocation.host => AstHostApi(name: 'Api', methods: [ + Method( + name: 'divide', + location: location, + objcSelector: 'divideValue:by:', + parameters: [ + Parameter( + type: const TypeDeclaration( + baseName: 'int', isNullable: false), + name: 'x', + ), + Parameter( + type: const TypeDeclaration( + baseName: 'int', isNullable: false), + name: 'y', + ), + ], + returnType: const TypeDeclaration( + baseName: 'double', isNullable: false)) + ]), + ApiLocation.flutter => AstFlutterApi(name: 'Api', methods: [ + Method( + name: 'divide', + location: location, + objcSelector: 'divideValue:by:', + parameters: [ + Parameter( + type: const TypeDeclaration( + baseName: 'int', isNullable: false), + name: 'x', + ), + Parameter( + type: const TypeDeclaration( + baseName: 'int', isNullable: false), + name: 'y', + ), + ], + returnType: const TypeDeclaration( + baseName: 'double', isNullable: false)) + ]), + } ], classes: [], enums: [], @@ -2378,9 +2434,10 @@ void main() { test('return nullable flutter header', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -2414,9 +2471,10 @@ void main() { test('return nullable flutter source', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -2447,9 +2505,10 @@ void main() { test('return nullable host header', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -2480,9 +2539,10 @@ void main() { test('nullable argument host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -2537,9 +2597,10 @@ void main() { test('nullable argument flutter', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -2593,9 +2654,10 @@ void main() { test('background platform channel', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -2644,13 +2706,13 @@ void main() { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'api', - location: ApiLocation.flutter, documentationComments: [comments[count++]], methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), documentationComments: [comments[count++]], parameters: [ @@ -2725,12 +2787,12 @@ void main() { test("doesn't create codecs if no custom datatypes", () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -2767,9 +2829,10 @@ void main() { test('creates custom codecs if custom datatypes present', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -2825,12 +2888,12 @@ void main() { test('connection error contains channel name', () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart index 5d60f92bf69d..34516e8c0dea 100644 --- a/packages/pigeon/test/pigeon_lib_test.dart +++ b/packages/pigeon/test/pigeon_lib_test.dart @@ -313,7 +313,7 @@ abstract class AFlutterApi { expect(results.errors.length, equals(0)); expect(results.root.apis.length, equals(1)); expect(results.root.apis[0].name, equals('AFlutterApi')); - expect(results.root.apis[0].location, equals(ApiLocation.flutter)); + expect(results.root.apis[0], isA()); }); test('void host api', () { @@ -370,8 +370,10 @@ abstract class ApiWithMockDartClass { final ParseResults results = parseSource(code); expect(results.errors.length, equals(0)); expect(results.root.apis.length, equals(1)); - expect(results.root.apis[0].dartHostTestHandler, - equals('ApiWithMockDartClassMock')); + expect( + (results.root.apis[0] as AstHostApi).dartHostTestHandler, + equals('ApiWithMockDartClassMock'), + ); }); test('only visible from nesting', () { @@ -1352,4 +1354,215 @@ abstract class Api { expect(results.errors[0].message, contains('FlutterApi method parameters must not be optional')); }); + + test('simple parse ProxyApi', () { + const String code = ''' +@ProxyApi() +abstract class MyClass { + MyClass(); + late String aField; + late void Function() aCallbackMethod; + void aMethod(); +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors.length, equals(0)); + final Root root = parseResult.root; + expect(root.apis.length, equals(1)); + + final AstProxyApi proxyApi = root.apis.single as AstProxyApi; + expect(proxyApi.name, equals('MyClass')); + expect(proxyApi.constructors.single.name, equals('')); + expect(proxyApi.methods.length, equals(2)); + + for (final Method method in proxyApi.methods) { + if (method.location == ApiLocation.host) { + expect(method.name, equals('aMethod')); + } else if (method.location == ApiLocation.flutter) { + expect(method.name, equals('aCallbackMethod')); + } + } + }); + + group('ProxyApi validation', () { + test('error with using data class', () { + const String code = ''' +class DataClass { + late int input; +} + +@ProxyApi() +abstract class MyClass { + MyClass(DataClass input); +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors.length, equals(1)); + expect( + parseResult.errors.single.message, + contains('ProxyApis do not support data classes'), + ); + }); + + test('super class must be proxy api', () { + const String code = ''' +class DataClass { + late int input; +} + +@ProxyApi() +abstract class MyClass extends DataClass { + void aMethod(); +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains('Super class of MyClass is not marked as a @ProxyApi'), + ); + }); + + test('interface must be proxy api', () { + const String code = ''' +class DataClass { + late int input; +} + +@ProxyApi() +abstract class MyClass implements DataClass { + void aMethod(); +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains('Interface of MyClass is not marked as a @ProxyApi'), + ); + }); + + test('unattached fields can not be inherited', () { + const String code = ''' +@ProxyApi() +abstract class MyClass extends MyOtherClass { } + +@ProxyApi() +abstract class MyOtherClass { + late int aField; +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains( + 'Unattached fields can not be inherited. Unattached field found for parent class: aField', + ), + ); + }); + + test( + 'api is not used as an attached field while having an unattached field', + () { + const String code = ''' +@ProxyApi() +abstract class MyClass { + @attached + late MyOtherClass anAttachedField; +} + +@ProxyApi() +abstract class MyOtherClass { + late int aField; +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains( + 'ProxyApis with unattached fields can not be used as attached fields: anAttachedField', + ), + ); + }); + + test( + 'api is not used as an attached field while having a required Flutter method', + () { + const String code = ''' +@ProxyApi() +abstract class MyClass { + @attached + late MyOtherClass anAttachedField; +} + +@ProxyApi() +abstract class MyOtherClass { + late void Function() aCallbackMethod; +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains( + 'ProxyApis with required callback methods can not be used as attached fields: anAttachedField', + ), + ); + }); + + test('interfaces can only have callback methods', () { + const String code = ''' +@ProxyApi() +abstract class MyClass implements MyOtherClass { +} + +@ProxyApi() +abstract class MyOtherClass { + MyOtherClass(); +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains( + 'ProxyApis used as interfaces can only have callback methods: `MyClass` implements `MyOtherClass`', + ), + ); + }); + + test('attached fields must be a ProxyApi', () { + const String code = ''' +@ProxyApi() +abstract class MyClass { + @attached + late int aField; +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains('Attached fields must be a ProxyApi: int'), + ); + }); + + test('attached fields must not be nullable', () { + const String code = ''' +@ProxyApi() +abstract class MyClass { + @attached + late MyClass? aField; +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains('Attached fields must not be nullable: MyClass?'), + ); + }); + }); } diff --git a/packages/pigeon/test/swift_generator_test.dart b/packages/pigeon/test/swift_generator_test.dart index b39909be32ba..944a4bb2cefb 100644 --- a/packages/pigeon/test/swift_generator_test.dart +++ b/packages/pigeon/test/swift_generator_test.dart @@ -86,9 +86,10 @@ void main() { test('primitive enum host', () { final Root root = Root(apis: [ - Api(name: 'Bar', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Bar', methods: [ Method( name: 'bar', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -123,9 +124,10 @@ void main() { test('gen one host api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -252,9 +254,10 @@ void main() { test('gen one flutter api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -308,9 +311,10 @@ void main() { test('gen host void api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -350,9 +354,10 @@ void main() { test('gen flutter void return api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -393,9 +398,10 @@ void main() { test('gen host void argument api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -432,9 +438,10 @@ void main() { test('gen flutter void argument api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -575,9 +582,10 @@ void main() { test('gen one async Host Api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -631,9 +639,10 @@ void main() { test('gen one async Flutter Api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -814,9 +823,10 @@ void main() { test('host generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -849,9 +859,10 @@ void main() { test('flutter generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -884,9 +895,10 @@ void main() { test('host generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -917,9 +929,10 @@ void main() { test('flutter generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -952,9 +965,10 @@ void main() { test('host multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.host, parameters: [ Parameter( name: 'x', @@ -995,9 +1009,10 @@ void main() { test('flutter multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -1039,9 +1054,10 @@ void main() { test('return nullable host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1068,9 +1084,10 @@ void main() { test('return nullable host async', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1101,9 +1118,10 @@ void main() { test('nullable argument host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1137,9 +1155,10 @@ void main() { test('nullable argument flutter', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1172,9 +1191,10 @@ void main() { test('nonnull fields', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -1226,13 +1246,13 @@ void main() { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'api', - location: ApiLocation.flutter, documentationComments: [comments[count++]], methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), documentationComments: [comments[count++]], parameters: [ @@ -1303,12 +1323,12 @@ void main() { test("doesn't create codecs if no custom datatypes", () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1341,9 +1361,10 @@ void main() { test('creates custom codecs if custom datatypes present', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -1395,9 +1416,10 @@ void main() { test('swift function signature', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'set', + location: ApiLocation.host, parameters: [ Parameter( type: const TypeDeclaration( @@ -1438,9 +1460,10 @@ void main() { test('swift function signature with same name argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'set', + location: ApiLocation.host, parameters: [ Parameter( type: const TypeDeclaration( @@ -1474,9 +1497,10 @@ void main() { test('swift function signature with no arguments', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'clear', + location: ApiLocation.host, parameters: [], swiftFunction: 'removeAll()', returnType: const TypeDeclaration.voidDeclaration(), @@ -1502,12 +1526,12 @@ void main() { test('connection error contains channel name', () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter(