import 'package:collection/collection.dart' show ListEquality;
import 'package:meta/meta.dart';
import 'generator_tools.dart';
import 'kotlin/kotlin_generator.dart'
show KotlinEventChannelOptions, KotlinProxyApiOptions;
import 'pigeon_lib.dart';
import 'swift/swift_generator.dart'
show SwiftEventChannelOptions, SwiftProxyApiOptions;
typedef _ListEquals = bool Function(List<Object?>, List<Object?>);
final _ListEquals _listEquals = const ListEquality<dynamic>().equals;
enum ApiLocation {
host,
flutter,
}
class Node {}
class Method extends Node {
Method({
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 = '',
this.taskQueueType = TaskQueueType.serial,
this.documentationComments = const <String>[],
});
String name;
TypeDeclaration returnType;
List<Parameter> parameters;
bool isAsynchronous;
int? offset;
String objcSelector;
String swiftFunction;
TaskQueueType taskQueueType;
List<String> documentationComments;
ApiLocation location;
bool isRequired;
bool isStatic;
@override
String toString() {
final String objcSelectorStr =
objcSelector.isEmpty ? '' : ' objcSelector:$objcSelector';
final String swiftFunctionStr =
swiftFunction.isEmpty ? '' : ' swiftFunction:$swiftFunction';
return '(Method name:$name returnType:$returnType parameters:$parameters isAsynchronous:$isAsynchronous$objcSelectorStr$swiftFunctionStr documentationComments:$documentationComments)';
}
}
class AstHostApi extends Api {
AstHostApi({
required super.name,
required super.methods,
super.documentationComments = const <String>[],
this.dartHostTestHandler,
});
String? dartHostTestHandler;
@override
String toString() {
return '(HostApi name:$name methods:$methods documentationComments:$documentationComments dartHostTestHandler:$dartHostTestHandler)';
}
}
class AstFlutterApi extends Api {
AstFlutterApi({
required super.name,
required super.methods,
super.documentationComments = const <String>[],
});
@override
String toString() {
return '(FlutterApi name:$name methods:$methods documentationComments:$documentationComments)';
}
}
class AstProxyApi extends Api {
AstProxyApi({
required super.name,
required super.methods,
super.documentationComments = const <String>[],
required this.constructors,
required this.fields,
this.superClass,
this.interfaces = const <TypeDeclaration>{},
this.swiftOptions,
this.kotlinOptions,
});
final List<Constructor> constructors;
List<ApiField> fields;
TypeDeclaration? superClass;
Set<TypeDeclaration> interfaces;
final SwiftProxyApiOptions? swiftOptions;
final KotlinProxyApiOptions? kotlinOptions;
Iterable<Method> get hostMethods => methods.where(
(Method method) => method.location == ApiLocation.host,
);
Iterable<Method> get flutterMethods => methods.where(
(Method method) => method.location == ApiLocation.flutter,
);
Iterable<ApiField> get attachedFields => fields.where(
(ApiField field) => field.isAttached,
);
Iterable<ApiField> get unattachedFields => fields.where(
(ApiField field) => !field.isAttached,
);
Iterable<AstProxyApi> allSuperClasses() {
final List<AstProxyApi> superClassChain = <AstProxyApi>[];
if (superClass != null && !superClass!.isProxyApi) {
throw ArgumentError(
'Could not find a ProxyApi for super class: ${superClass!.baseName}',
);
}
AstProxyApi? currentProxyApi = superClass?.associatedProxyApi;
while (currentProxyApi != null) {
if (superClassChain.contains(currentProxyApi)) {
throw ArgumentError(
'Loop found when processing super classes for a ProxyApi: '
'$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;
}
Iterable<AstProxyApi> apisOfInterfaces() => _recursiveFindAllInterfaceApis();
Iterable<Method> flutterMethodsFromInterfaces() sync* {
for (final AstProxyApi proxyApi in apisOfInterfaces()) {
yield* proxyApi.methods;
}
}
Iterable<Method> flutterMethodsFromSuperClasses() sync* {
for (final AstProxyApi proxyApi in allSuperClasses().toList().reversed) {
yield* proxyApi.flutterMethods;
}
if (superClass != null) {
final Set<AstProxyApi> interfaceApisFromSuperClasses =
superClass!.associatedProxyApi!._recursiveFindAllInterfaceApis();
for (final AstProxyApi proxyApi in interfaceApisFromSuperClasses) {
yield* proxyApi.methods;
}
}
}
bool hasCallbackConstructor() {
return flutterMethods
.followedBy(flutterMethodsFromSuperClasses())
.followedBy(flutterMethodsFromInterfaces())
.every((Method method) => !method.isRequired);
}
bool hasAnyHostMessageCalls() =>
constructors.isNotEmpty ||
attachedFields.isNotEmpty ||
hostMethods.isNotEmpty;
bool hasAnyFlutterMessageCalls() =>
hasCallbackConstructor() || flutterMethods.isNotEmpty;
bool hasMethodsRequiringImplementation() =>
hasAnyHostMessageCalls() || unattachedFields.isNotEmpty;
Set<AstProxyApi> _recursiveFindAllInterfaceApis([
Set<AstProxyApi> seenApis = const <AstProxyApi>{},
]) {
final Set<AstProxyApi> allInterfaces = <AstProxyApi>{};
allInterfaces.addAll(
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!;
},
),
);
final Set<AstProxyApi> newSeenApis = <AstProxyApi>{...seenApis, this};
for (final AstProxyApi interfaceApi in <AstProxyApi>{...allInterfaces}) {
allInterfaces.addAll(
interfaceApi._recursiveFindAllInterfaceApis(newSeenApis),
);
}
return allInterfaces;
}
@override
String toString() {
return '(ProxyApi name:$name methods:$methods field:$fields '
'documentationComments:$documentationComments '
'superClassName:$superClass interfacesNames:$interfaces)';
}
}
class AstEventChannelApi extends Api {
AstEventChannelApi({
required super.name,
required super.methods,
this.kotlinOptions,
this.swiftOptions,
super.documentationComments = const <String>[],
});
final KotlinEventChannelOptions? kotlinOptions;
final SwiftEventChannelOptions? swiftOptions;
@override
String toString() {
return '(EventChannelApi name:$name methods:$methods documentationComments:$documentationComments)';
}
}
class Constructor extends Method {
Constructor({
required super.name,
required super.parameters,
super.offset,
super.swiftFunction = '',
super.documentationComments = const <String>[],
}) : 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)';
}
}
class ApiField extends NamedType {
ApiField({
required super.name,
required super.type,
super.offset,
super.documentationComments,
this.isAttached = false,
this.isStatic = false,
}) : assert(!isStatic || isAttached);
final bool isAttached;
final bool isStatic;
@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)';
}
}
sealed class Api extends Node {
Api({
required this.name,
required this.methods,
this.documentationComments = const <String>[],
});
String name;
List<Method> methods;
List<String> documentationComments;
@override
String toString() {
return '(Api name:$name methods:$methods documentationComments:$documentationComments)';
}
}
@immutable
class TypeDeclaration {
const TypeDeclaration({
required this.baseName,
required this.isNullable,
this.associatedEnum,
this.associatedClass,
this.associatedProxyApi,
this.typeArguments = const <TypeDeclaration>[],
});
const TypeDeclaration.voidDeclaration()
: baseName = 'void',
isNullable = false,
associatedEnum = null,
associatedClass = null,
associatedProxyApi = null,
typeArguments = const <TypeDeclaration>[];
final String baseName;
bool get isVoid => baseName == 'void';
final List<TypeDeclaration> typeArguments;
final bool isNullable;
bool get isEnum => associatedEnum != null;
final Enum? associatedEnum;
bool get isClass => associatedClass != null;
final Class? associatedClass;
bool get isProxyApi => associatedProxyApi != null;
final AstProxyApi? associatedProxyApi;
@override
int get hashCode {
int hash = 17;
hash = hash * 37 + baseName.hashCode;
hash = hash * 37 + isNullable.hashCode;
for (final TypeDeclaration typeArgument in typeArguments) {
hash = hash * 37 + typeArgument.hashCode;
}
return hash;
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
} else {
return other is TypeDeclaration &&
baseName == other.baseName &&
isNullable == other.isNullable &&
_listEquals(typeArguments, other.typeArguments) &&
isEnum == other.isEnum &&
isClass == other.isClass &&
associatedClass == other.associatedClass &&
associatedEnum == other.associatedEnum;
}
}
TypeDeclaration copyWithEnum(Enum enumDefinition) {
return TypeDeclaration(
baseName: baseName,
isNullable: isNullable,
associatedEnum: enumDefinition,
typeArguments: typeArguments,
);
}
TypeDeclaration copyWithClass(Class classDefinition) {
return TypeDeclaration(
baseName: baseName,
isNullable: isNullable,
associatedClass: classDefinition,
typeArguments: typeArguments,
);
}
TypeDeclaration copyWithProxyApi(AstProxyApi proxyApiDefinition) {
return TypeDeclaration(
baseName: baseName,
isNullable: isNullable,
associatedProxyApi: proxyApiDefinition,
typeArguments: typeArguments,
);
}
TypeDeclaration copyWithTypeArguments(List<TypeDeclaration> types) {
return TypeDeclaration(
baseName: baseName,
isNullable: isNullable,
typeArguments: types,
associatedClass: associatedClass,
associatedEnum: associatedEnum,
associatedProxyApi: associatedProxyApi,
);
}
@override
String toString() {
final String typeArgumentsStr =
typeArguments.isEmpty ? '' : ' typeArguments:$typeArguments';
return '(TypeDeclaration baseName:$baseName isNullable:$isNullable$typeArgumentsStr isEnum:$isEnum isClass:$isClass isProxyApi:$isProxyApi)';
}
}
@immutable
class NamedType extends Node {
NamedType({
required this.name,
required this.type,
this.offset,
this.defaultValue,
this.documentationComments = const <String>[],
});
final String name;
final TypeDeclaration type;
final int? offset;
final String? defaultValue;
final List<String> documentationComments;
@mustBeOverridden
NamedType copyWithType(TypeDeclaration type) {
return NamedType(
name: name,
type: type,
offset: offset,
defaultValue: defaultValue,
documentationComments: documentationComments,
);
}
@override
String toString() {
return '(NamedType name:$name type:$type defaultValue:$defaultValue documentationComments:$documentationComments)';
}
}
@immutable
class Parameter extends NamedType {
Parameter({
required super.name,
required super.type,
super.offset,
super.defaultValue,
bool? isNamed,
bool? isOptional,
bool? isPositional,
bool? isRequired,
super.documentationComments,
}) : isNamed = isNamed ?? false,
isOptional = isOptional ?? false,
isPositional = isPositional ?? true,
isRequired = isRequired ?? true;
final bool isNamed;
final bool isOptional;
final bool isPositional;
final bool isRequired;
@override
Parameter copyWithType(TypeDeclaration type) {
return Parameter(
name: name,
type: type,
offset: offset,
defaultValue: defaultValue,
isNamed: isNamed,
isOptional: isOptional,
isPositional: isPositional,
isRequired: isRequired,
documentationComments: documentationComments,
);
}
@override
String toString() {
return '(Parameter name:$name type:$type isNamed:$isNamed isOptional:$isOptional isPositional:$isPositional isRequired:$isRequired documentationComments:$documentationComments)';
}
}
class Class extends Node {
Class({
required this.name,
required this.fields,
this.superClassName,
this.superClass,
this.isSealed = false,
this.isReferenced = true,
this.isSwiftClass = false,
this.documentationComments = const <String>[],
});
String name;
List<NamedType> fields;
String? superClassName;
Class? superClass;
List<Class> children = <Class>[];
bool isSealed;
bool isReferenced;
bool isSwiftClass;
List<String> documentationComments;
@override
String toString() {
return '(Class name:$name fields:$fields superClass:$superClassName children:$children isSealed:$isSealed isReferenced:$isReferenced documentationComments:$documentationComments)';
}
}
class Enum extends Node {
Enum({
required this.name,
required this.members,
this.documentationComments = const <String>[],
});
String name;
List<EnumMember> members;
List<String> documentationComments;
@override
String toString() {
return '(Enum name:$name members:$members documentationComments:$documentationComments)';
}
}
class EnumMember extends Node {
EnumMember({
required this.name,
this.documentationComments = const <String>[],
});
final String name;
final List<String> documentationComments;
@override
String toString() {
return '(EnumMember name:$name documentationComments:$documentationComments)';
}
}
class Root extends Node {
Root({
required this.classes,
required this.apis,
required this.enums,
this.containsHostApi = false,
this.containsFlutterApi = false,
this.containsProxyApi = false,
this.containsEventChannel = false,
});
factory Root.makeEmpty() {
return Root(apis: <Api>[], classes: <Class>[], enums: <Enum>[]);
}
List<Class> classes;
List<Api> apis;
List<Enum> enums;
bool containsHostApi;
bool containsFlutterApi;
bool containsProxyApi;
bool containsEventChannel;
bool get requiresOverflowClass =>
classes.length - _numberOfSealedClasses() + enums.length >=
totalCustomCodecKeysAllowed;
int _numberOfSealedClasses() => classes.where((Class c) => c.isSealed).length;
@override
String toString() {
return '(Root classes:$classes apis:$apis enums:$enums)';
}
}