diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index 1c47219f08c..2bf8a72c634 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.0.0 + +* Creates StructuredGenerator class and implements it on all platforms. + ## 5.0.1 * [c++] Fixes undefined behavior in `@async` methods. diff --git a/packages/pigeon/lib/cpp_generator.dart b/packages/pigeon/lib/cpp_generator.dart index ae5f91224ae..105f6bd0f0e 100644 --- a/packages/pigeon/lib/cpp_generator.dart +++ b/packages/pigeon/lib/cpp_generator.dart @@ -72,105 +72,348 @@ class CppOptions { /// Class that manages all Cpp code generation. class CppGenerator extends Generator> { - /// Instantiates a Cpp Generator. - CppGenerator(); + /// Constructor. + const CppGenerator(); - /// Generates Cpp files with specified [OutputFileOptions] + /// Generates C++ file of type specified in [generatorOptions] @override - void generate(OutputFileOptions languageOptions, Root root, + void generate(OutputFileOptions generatorOptions, Root root, StringSink sink) { - final FileType fileType = languageOptions.fileType; - assert(fileType == FileType.header || fileType == FileType.source); - if (fileType == FileType.header) { - generateCppHeader(languageOptions.languageOptions, root, sink); - } else { - generateCppSource(languageOptions.languageOptions, root, sink); + assert(generatorOptions.fileType == FileType.header || + generatorOptions.fileType == FileType.source); + if (generatorOptions.fileType == FileType.header) { + const CppHeaderGenerator() + .generate(generatorOptions.languageOptions, root, sink); + } else if (generatorOptions.fileType == FileType.source) { + const CppSourceGenerator() + .generate(generatorOptions.languageOptions, root, sink); } } } -String _getCodecSerializerName(Api api) => '${api.name}CodecSerializer'; +/// Writes C++ header (.h) file to sink. +class CppHeaderGenerator extends StructuredGenerator { + /// Constructor. + const CppHeaderGenerator(); -const String _pointerPrefix = 'pointer'; -const String _encodablePrefix = 'encodable'; + @override + void writeFilePrologue( + CppOptions generatorOptions, Root root, Indent indent) { + if (generatorOptions.copyrightHeader != null) { + addLines(indent, generatorOptions.copyrightHeader!, linePrefix: '// '); + } + indent.writeln('$_commentPrefix $generatedCodeWarning'); + indent.writeln('$_commentPrefix $seeAlsoWarning'); + indent.addln(''); + } + + @override + void writeFileImports(CppOptions generatorOptions, Root root, Indent indent) { + final String guardName = _getGuardName(generatorOptions.headerIncludePath); + indent.writeln('#ifndef $guardName'); + indent.writeln('#define $guardName'); + + _writeSystemHeaderIncludeBlock(indent, [ + 'flutter/basic_message_channel.h', + 'flutter/binary_messenger.h', + 'flutter/encodable_value.h', + 'flutter/standard_message_codec.h', + ]); + indent.addln(''); + _writeSystemHeaderIncludeBlock(indent, [ + 'map', + 'string', + 'optional', + ]); + indent.addln(''); + if (generatorOptions.namespace != null) { + indent.writeln('namespace ${generatorOptions.namespace} {'); + } + indent.addln(''); + if (generatorOptions.namespace?.endsWith('_pigeontest') ?? false) { + final String testFixtureClass = + '${_pascalCaseFromSnakeCase(generatorOptions.namespace!.replaceAll('_pigeontest', ''))}Test'; + indent.writeln('class $testFixtureClass;'); + } + indent.addln(''); + indent.writeln('$_commentPrefix Generated class from Pigeon.'); + } + + @override + void writeEnum( + CppOptions generatorOptions, Root root, Indent indent, Enum anEnum) { + indent.writeln(''); + addDocumentationComments( + indent, anEnum.documentationComments, _docCommentSpec); + indent.write('enum class ${anEnum.name} '); + indent.scoped('{', '};', () { + enumerate(anEnum.members, (int index, final EnumMember member) { + addDocumentationComments( + indent, member.documentationComments, _docCommentSpec); + indent.writeln( + '${member.name} = $index${index == anEnum.members.length - 1 ? '' : ','}'); + }); + }); + } + + @override + void writeGeneralUtilities( + CppOptions generatorOptions, Root root, Indent indent) { + _writeErrorOr(indent, friends: root.apis.map((Api api) => api.name)); + } -void _writeCodecHeader(Indent indent, Api api, Root root) { - assert(getCodecClasses(api, root).isNotEmpty); - final String codeSerializerName = _getCodecSerializerName(api); - indent.write('class $codeSerializerName : public $_defaultCodecSerializer '); - indent.scoped('{', '};', () { - indent.scoped(' public:', '', () { - indent.writeln(''); - indent.format(''' + @override + void writeDataClass( + CppOptions generatorOptions, Root root, Indent indent, Class klass) { + // When generating for a Pigeon unit test, add a test fixture friend class to + // allow unit testing private methods, since testing serialization via public + // methods is essentially an end-to-end test. + String? testFixtureClass; + if (generatorOptions.namespace?.endsWith('_pigeontest') ?? false) { + testFixtureClass = + '${_pascalCaseFromSnakeCase(generatorOptions.namespace!.replaceAll('_pigeontest', ''))}Test'; + } + indent.addln(''); + + const List generatedMessages = [ + ' Generated class from Pigeon that represents data sent in messages.' + ]; + + addDocumentationComments( + indent, klass.documentationComments, _docCommentSpec, + generatorComments: generatedMessages); + + indent.write('class ${klass.name} '); + indent.scoped('{', '};', () { + indent.scoped(' public:', '', () { + indent.writeln('${klass.name}();'); + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + addDocumentationComments( + indent, field.documentationComments, _docCommentSpec); + final HostDatatype baseDatatype = getFieldHostDatatype( + field, + root.classes, + root.enums, + (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); + indent.writeln( + '${_getterReturnType(baseDatatype)} ${_makeGetterName(field)}() const;'); + indent.writeln( + 'void ${_makeSetterName(field)}(${_unownedArgumentType(baseDatatype)} value_arg);'); + if (field.type.isNullable) { + // Add a second setter that takes the non-nullable version of the + // argument for convenience, since setting literal values with the + // pointer version is non-trivial. + final HostDatatype nonNullType = _nonNullableType(baseDatatype); + indent.writeln( + 'void ${_makeSetterName(field)}(${_unownedArgumentType(nonNullType)} value_arg);'); + } + indent.addln(''); + } + }); + + indent.scoped(' private:', '', () { + indent.writeln('${klass.name}(const flutter::EncodableList& list);'); + indent.writeln('flutter::EncodableList ToEncodableList() const;'); + for (final Class friend in root.classes) { + if (friend != klass && + friend.fields.any( + (NamedType element) => element.type.baseName == klass.name)) { + indent.writeln('friend class ${friend.name};'); + } + } + for (final Api api in root.apis) { + // TODO(gaaclarke): Find a way to be more precise with our + // friendships. + indent.writeln('friend class ${api.name};'); + indent.writeln('friend class ${_getCodecSerializerName(api)};'); + } + if (testFixtureClass != null) { + indent.writeln('friend class $testFixtureClass;'); + } + + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + final HostDatatype hostDatatype = getFieldHostDatatype( + field, + root.classes, + root.enums, + (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); + indent.writeln( + '${_valueType(hostDatatype)} ${_makeInstanceVariableName(field)};'); + } + }); + }, nestCount: 0); + indent.writeln(''); + } + + @override + void writeFlutterApi( + CppOptions generatorOptions, + Root root, + Indent indent, + Api api, + ) { + assert(api.location == ApiLocation.flutter); + if (getCodecClasses(api, root).isNotEmpty) { + _writeCodec(generatorOptions, root, indent, api); + } + const List generatedMessages = [ + ' Generated class from Pigeon that represents Flutter messages that can be called from C++.' + ]; + addDocumentationComments(indent, api.documentationComments, _docCommentSpec, + generatorComments: generatedMessages); + indent.write('class ${api.name} '); + indent.scoped('{', '};', () { + indent.scoped(' private:', '', () { + indent.writeln('flutter::BinaryMessenger* binary_messenger_;'); + }); + indent.scoped(' public:', '', () { + indent + .write('${api.name}(flutter::BinaryMessenger* binary_messenger);'); + indent.writeln(''); + indent + .writeln('static const flutter::StandardMessageCodec& GetCodec();'); + for (final Method func in api.methods) { + final String returnType = func.returnType.isVoid + ? 'void' + : _nullSafeCppTypeForDartType(func.returnType); + final String callback = 'std::function&& callback'; + addDocumentationComments( + indent, func.documentationComments, _docCommentSpec); + if (func.arguments.isEmpty) { + indent.writeln('void ${func.name}($callback);'); + } else { + final Iterable argTypes = func.arguments + .map((NamedType e) => _nullSafeCppTypeForDartType(e.type)); + final Iterable argNames = + indexMap(func.arguments, _getSafeArgumentName); + final String argsSignature = + map2(argTypes, argNames, (String x, String y) => '$x $y') + .join(', '); + indent.writeln('void ${func.name}($argsSignature, $callback);'); + } + } + }); + }, nestCount: 0); + indent.writeln(''); + } + + @override + void writeHostApi( + CppOptions generatorOptions, + Root root, + Indent indent, + Api api, + ) { + assert(api.location == ApiLocation.host); + if (getCodecClasses(api, root).isNotEmpty) { + _writeCodec(generatorOptions, root, indent, api); + } + const List generatedMessages = [ + ' Generated interface from Pigeon that represents a handler of messages from Flutter.' + ]; + addDocumentationComments(indent, api.documentationComments, _docCommentSpec, + generatorComments: generatedMessages); + indent.write('class ${api.name} '); + indent.scoped('{', '};', () { + indent.scoped(' public:', '', () { + indent.writeln('${api.name}(const ${api.name}&) = delete;'); + indent.writeln('${api.name}& operator=(const ${api.name}&) = delete;'); + indent.writeln('virtual ~${api.name}() { };'); + for (final Method method in api.methods) { + final HostDatatype returnType = getHostDatatype( + method.returnType, + root.classes, + root.enums, + (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); + final String returnTypeName = _apiReturnType(returnType); + + final List argSignature = []; + if (method.arguments.isNotEmpty) { + final Iterable argTypes = + method.arguments.map((NamedType arg) { + final HostDatatype hostType = getFieldHostDatatype( + arg, + root.classes, + root.enums, + (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); + return _hostApiArgumentType(hostType); + }); + final Iterable argNames = + method.arguments.map((NamedType e) => _makeVariableName(e)); + argSignature.addAll( + map2(argTypes, argNames, (String argType, String argName) { + return '$argType $argName'; + })); + } + + addDocumentationComments( + indent, method.documentationComments, _docCommentSpec); + + if (method.isAsynchronous) { + argSignature + .add('std::function result'); + indent.writeln( + 'virtual void ${_makeMethodName(method)}(${argSignature.join(', ')}) = 0;'); + } else { + indent.writeln( + 'virtual $returnTypeName ${_makeMethodName(method)}(${argSignature.join(', ')}) = 0;'); + } + } + indent.addln(''); + indent.writeln('$_commentPrefix The codec used by ${api.name}.'); + indent + .writeln('static const flutter::StandardMessageCodec& GetCodec();'); + indent.writeln( + '$_commentPrefix Sets up an instance of `${api.name}` to handle messages through the `binary_messenger`.'); + indent.writeln( + 'static void SetUp(flutter::BinaryMessenger* binary_messenger, ${api.name}* api);'); + indent.writeln( + 'static flutter::EncodableValue WrapError(std::string_view error_message);'); + indent.writeln( + 'static flutter::EncodableValue WrapError(const FlutterError& error);'); + }); + indent.scoped(' protected:', '', () { + indent.writeln('${api.name}() = default;'); + }); + }, nestCount: 0); + } + + void _writeCodec( + CppOptions generatorOptions, Root root, Indent indent, Api api) { + assert(getCodecClasses(api, root).isNotEmpty); + final String codeSerializerName = _getCodecSerializerName(api); + indent + .write('class $codeSerializerName : public $_defaultCodecSerializer '); + indent.scoped('{', '};', () { + indent.scoped(' public:', '', () { + indent.writeln(''); + indent.format(''' inline static $codeSerializerName& GetInstance() { \tstatic $codeSerializerName sInstance; \treturn sInstance; } '''); - indent.writeln('$codeSerializerName();'); - }); - indent.writeScoped(' public:', '', () { - indent.writeln( - 'void WriteValue(const flutter::EncodableValue& value, flutter::ByteStreamWriter* stream) const override;'); - }); - indent.writeScoped(' protected:', '', () { - indent.writeln( - 'flutter::EncodableValue ReadValueOfType(uint8_t type, flutter::ByteStreamReader* stream) const override;'); - }); - }, nestCount: 0); -} - -void _writeCodecSource(Indent indent, Api api, Root root) { - assert(getCodecClasses(api, root).isNotEmpty); - final String codeSerializerName = _getCodecSerializerName(api); - indent.writeln('$codeSerializerName::$codeSerializerName() {}'); - indent.write( - 'flutter::EncodableValue $codeSerializerName::ReadValueOfType(uint8_t type, flutter::ByteStreamReader* stream) const '); - indent.scoped('{', '}', () { - indent.write('switch (type) '); - indent.scoped('{', '}', () { - for (final EnumeratedClass customClass in getCodecClasses(api, root)) { - indent.write('case ${customClass.enumeration}:'); - indent.writeScoped('', '', () { - indent.writeln( - 'return flutter::CustomEncodableValue(${customClass.name}(std::get(ReadValue(stream))));'); - }); - } - indent.write('default:'); - indent.writeScoped('', '', () { + indent.writeln('$codeSerializerName();'); + }); + indent.writeScoped(' public:', '', () { indent.writeln( - 'return $_defaultCodecSerializer::ReadValueOfType(type, stream);'); - }, addTrailingNewline: false); - }); - }); - indent.writeln(''); - indent.write( - 'void $codeSerializerName::WriteValue(const flutter::EncodableValue& value, flutter::ByteStreamWriter* stream) const '); - indent.writeScoped('{', '}', () { - indent.write( - 'if (const flutter::CustomEncodableValue* custom_value = std::get_if(&value)) '); - indent.scoped('{', '}', () { - for (final EnumeratedClass customClass in getCodecClasses(api, root)) { - indent - .write('if (custom_value->type() == typeid(${customClass.name})) '); - indent.scoped('{', '}', () { - indent.writeln('stream->WriteByte(${customClass.enumeration});'); - indent.writeln( - 'WriteValue(flutter::EncodableValue(std::any_cast<${customClass.name}>(*custom_value).ToEncodableList()), stream);'); - indent.writeln('return;'); - }); - } - }); - indent.writeln('$_defaultCodecSerializer::WriteValue(value, stream);'); - }); -} + 'void WriteValue(const flutter::EncodableValue& value, flutter::ByteStreamWriter* stream) const override;'); + }); + indent.writeScoped(' protected:', '', () { + indent.writeln( + 'flutter::EncodableValue ReadValueOfType(uint8_t type, flutter::ByteStreamReader* stream) const override;'); + }); + }, nestCount: 0); + indent.addln(''); + } + + void _writeErrorOr(Indent indent, + {Iterable friends = const []}) { + final String friendLines = friends + .map((String className) => '\tfriend class $className;') + .join('\n'); + indent.format(''' -void _writeErrorOr(Indent indent, - {Iterable friends = const []}) { - final String friendLines = friends - .map((String className) => '\tfriend class $className;') - .join('\n'); - indent.format(''' class FlutterError { public: \texplicit FlutterError(const std::string& code) @@ -208,104 +451,566 @@ $friendLines \tErrorOr() = default; \tT TakeValue() && { return std::get(std::move(v_)); } -\tstd::variant v_; -}; -'''); +\tstd::variant v_; +}; +'''); + } + + @override + void writeCloseNamespace( + CppOptions generatorOptions, Root root, Indent indent) { + if (generatorOptions.namespace != null) { + indent.writeln('} // namespace ${generatorOptions.namespace}'); + } + final String guardName = _getGuardName(generatorOptions.headerIncludePath); + indent.writeln('#endif // $guardName'); + } +} + +/// Writes C++ source (.cpp) file to sink. +class CppSourceGenerator extends StructuredGenerator { + /// Constructor. + const CppSourceGenerator(); + + @override + void writeFilePrologue( + CppOptions generatorOptions, Root root, Indent indent) { + if (generatorOptions.copyrightHeader != null) { + addLines(indent, generatorOptions.copyrightHeader!, linePrefix: '// '); + } + indent.writeln('$_commentPrefix $generatedCodeWarning'); + indent.writeln('$_commentPrefix $seeAlsoWarning'); + indent.addln(''); + indent.addln('#undef _HAS_EXCEPTIONS'); + indent.addln(''); + } + + @override + void writeFileImports(CppOptions generatorOptions, Root root, Indent indent) { + indent.writeln('#include "${generatorOptions.headerIncludePath}"'); + indent.addln(''); + _writeSystemHeaderIncludeBlock(indent, [ + 'flutter/basic_message_channel.h', + 'flutter/binary_messenger.h', + 'flutter/encodable_value.h', + 'flutter/standard_message_codec.h', + ]); + indent.addln(''); + _writeSystemHeaderIncludeBlock(indent, [ + 'map', + 'string', + 'optional', + ]); + indent.addln(''); + } + + @override + void writeOpenNamespace( + CppOptions generatorOptions, Root root, Indent indent) { + if (generatorOptions.namespace != null) { + indent.writeln('namespace ${generatorOptions.namespace} {'); + } + } + + @override + void writeDataClass( + CppOptions generatorOptions, Root root, Indent indent, Class klass) { + final Set customClassNames = + root.classes.map((Class x) => x.name).toSet(); + final Set customEnumNames = + root.enums.map((Enum x) => x.name).toSet(); + + indent.addln(''); + indent.writeln('$_commentPrefix ${klass.name}'); + indent.addln(''); + + // Getters and setters. + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + _writeCppSourceClassField(generatorOptions, root, indent, klass, field); + } + + // Serialization. + writeClassEncode(generatorOptions, root, indent, klass, customClassNames, + customEnumNames); + + // Default constructor. + indent.writeln('${klass.name}::${klass.name}() {}'); + indent.addln(''); + + // Deserialization. + writeClassDecode(generatorOptions, root, indent, klass, customClassNames, + customEnumNames); + } + + @override + void writeClassEncode( + CppOptions generatorOptions, + Root root, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) { + indent.write( + 'flutter::EncodableList ${klass.name}::ToEncodableList() const '); + indent.scoped('{', '}', () { + indent.scoped('return flutter::EncodableList{', '};', () { + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + final HostDatatype hostDatatype = getFieldHostDatatype( + field, + root.classes, + root.enums, + (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); + + final String instanceVariable = _makeInstanceVariableName(field); + + String encodableValue = ''; + if (!hostDatatype.isBuiltin && + customClassNames.contains(field.type.baseName)) { + final String operator = field.type.isNullable ? '->' : '.'; + encodableValue = + 'flutter::EncodableValue($instanceVariable${operator}ToEncodableList())'; + } else if (!hostDatatype.isBuiltin && + customEnumNames.contains(field.type.baseName)) { + final String nonNullValue = field.type.isNullable + ? '(*$instanceVariable)' + : instanceVariable; + encodableValue = 'flutter::EncodableValue((int)$nonNullValue)'; + } else { + final String operator = field.type.isNullable ? '*' : ''; + encodableValue = + 'flutter::EncodableValue($operator$instanceVariable)'; + } + + if (field.type.isNullable) { + encodableValue = + '$instanceVariable ? $encodableValue : flutter::EncodableValue()'; + } + + indent.writeln('$encodableValue,'); + } + }); + }); + indent.addln(''); + } + + @override + void writeClassDecode( + CppOptions generatorOptions, + Root root, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) { + indent.write( + '${klass.name}::${klass.name}(const flutter::EncodableList& list) '); + indent.scoped('{', '}', () { + enumerate(getFieldsInSerializationOrder(klass), + (int index, final NamedType field) { + final String instanceVariableName = _makeInstanceVariableName(field); + final String pointerFieldName = + '${_pointerPrefix}_${_makeVariableName(field)}'; + final String encodableFieldName = + '${_encodablePrefix}_${_makeVariableName(field)}'; + indent.writeln('auto& $encodableFieldName = list[$index];'); + if (customEnumNames.contains(field.type.baseName)) { + indent.writeln( + 'if (const int32_t* $pointerFieldName = std::get_if(&$encodableFieldName))\t$instanceVariableName = (${field.type.baseName})*$pointerFieldName;'); + } else { + final HostDatatype hostDatatype = getFieldHostDatatype( + field, + root.classes, + root.enums, + (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); + if (field.type.baseName == 'int') { + indent.format(''' +if (const int32_t* $pointerFieldName = std::get_if(&$encodableFieldName)) +\t$instanceVariableName = *$pointerFieldName; +else if (const int64_t* ${pointerFieldName}_64 = std::get_if(&$encodableFieldName)) +\t$instanceVariableName = *${pointerFieldName}_64;'''); + } else if (!hostDatatype.isBuiltin && + root.classes + .map((Class x) => x.name) + .contains(field.type.baseName)) { + indent.write( + 'if (const flutter::EncodableList* $pointerFieldName = std::get_if(&$encodableFieldName)) '); + indent.scoped('{', '}', () { + indent.writeln( + '$instanceVariableName = ${hostDatatype.datatype}(*$pointerFieldName);'); + }); + } else { + indent.write( + 'if (const ${hostDatatype.datatype}* $pointerFieldName = std::get_if<${hostDatatype.datatype}>(&$encodableFieldName)) '); + indent.scoped('{', '}', () { + indent.writeln('$instanceVariableName = *$pointerFieldName;'); + }); + } + } + }); + }); + indent.addln(''); + } + + @override + void writeFlutterApi( + CppOptions generatorOptions, + Root root, + Indent indent, + Api api, + ) { + assert(api.location == ApiLocation.flutter); + if (getCodecClasses(api, root).isNotEmpty) { + _writeCodec(generatorOptions, root, indent, api); + } + indent.writeln( + '$_commentPrefix Generated class from Pigeon that represents Flutter messages that can be called from C++.'); + indent.write( + '${api.name}::${api.name}(flutter::BinaryMessenger* binary_messenger) '); + indent.scoped('{', '}', () { + indent.writeln('this->binary_messenger_ = binary_messenger;'); + }); + indent.writeln(''); + final String codeSerializerName = getCodecClasses(api, root).isNotEmpty + ? _getCodecSerializerName(api) + : _defaultCodecSerializer; + indent.format(''' +const flutter::StandardMessageCodec& ${api.name}::GetCodec() { +\treturn flutter::StandardMessageCodec::GetInstance(&$codeSerializerName::GetInstance()); +} +'''); + for (final Method func in api.methods) { + final String channelName = makeChannelName(api, func); + final String returnType = func.returnType.isVoid + ? 'void' + : _nullSafeCppTypeForDartType(func.returnType); + String sendArgument; + final String callback = 'std::function&& callback'; + if (func.arguments.isEmpty) { + indent.write('void ${api.name}::${func.name}($callback) '); + sendArgument = 'flutter::EncodableValue()'; + } else { + final Iterable argTypes = func.arguments + .map((NamedType e) => _nullSafeCppTypeForDartType(e.type)); + final Iterable argNames = + indexMap(func.arguments, _getSafeArgumentName); + sendArgument = + 'flutter::EncodableList { ${argNames.map((String arg) => 'flutter::CustomEncodableValue($arg)').join(', ')} }'; + final String argsSignature = + map2(argTypes, argNames, (String x, String y) => '$x $y') + .join(', '); + indent.write( + 'void ${api.name}::${func.name}($argsSignature, $callback) '); + } + indent.scoped('{', '}', () { + const String channel = 'channel'; + indent.writeln( + 'auto channel = std::make_unique>('); + indent.inc(); + indent.inc(); + indent.writeln('binary_messenger_, "$channelName", &GetCodec());'); + indent.dec(); + indent.dec(); + indent.write( + '$channel->Send($sendArgument, [callback](const uint8_t* reply, size_t reply_size) '); + indent.scoped('{', '});', () { + if (func.returnType.isVoid) { + indent.writeln('callback();'); + } else { + indent.writeln( + 'std::unique_ptr decoded_reply = GetCodec().DecodeMessage(reply, reply_size);'); + indent.writeln( + 'flutter::EncodableValue args = *(flutter::EncodableValue*)(decoded_reply.release());'); + const String output = 'output'; + + final bool isBuiltin = + _baseCppTypeForBuiltinDartType(func.returnType) != null; + final String returnTypeName = + _baseCppTypeForDartType(func.returnType); + if (func.returnType.isNullable) { + indent.writeln('$returnType $output{};'); + } else { + indent.writeln('$returnTypeName $output{};'); + } + const String pointerVariable = '${_pointerPrefix}_$output'; + if (func.returnType.baseName == 'int') { + indent.format(''' +if (const int32_t* $pointerVariable = std::get_if(&args)) +\t$output = *$pointerVariable; +else if (const int64_t* ${pointerVariable}_64 = std::get_if(&args)) +\t$output = *${pointerVariable}_64;'''); + } else if (!isBuiltin) { + indent.write( + 'if (const flutter::EncodableList* $pointerVariable = std::get_if(&args)) '); + indent.scoped('{', '}', () { + indent.writeln('$output = $returnTypeName(*$pointerVariable);'); + }); + } else { + indent.write( + 'if (const $returnTypeName* $pointerVariable = std::get_if<$returnTypeName>(&args)) '); + indent.scoped('{', '}', () { + indent.writeln('$output = *$pointerVariable;'); + }); + } + + indent.writeln('callback($output);'); + } + }); + }); + } + } + + @override + void writeHostApi( + CppOptions generatorOptions, Root root, Indent indent, Api api) { + assert(api.location == ApiLocation.host); + if (getCodecClasses(api, root).isNotEmpty) { + _writeCodec(generatorOptions, root, indent, api); + } + + final String codeSerializerName = getCodecClasses(api, root).isNotEmpty + ? _getCodecSerializerName(api) + : _defaultCodecSerializer; + indent.format(''' +/// The codec used by ${api.name}. +const flutter::StandardMessageCodec& ${api.name}::GetCodec() { +\treturn flutter::StandardMessageCodec::GetInstance(&$codeSerializerName::GetInstance()); +} +'''); + indent.writeln( + '$_commentPrefix Sets up an instance of `${api.name}` to handle messages through the `binary_messenger`.'); + indent.write( + 'void ${api.name}::SetUp(flutter::BinaryMessenger* binary_messenger, ${api.name}* api) '); + indent.scoped('{', '}', () { + for (final Method method in api.methods) { + final String channelName = makeChannelName(api, method); + indent.write(''); + indent.scoped('{', '}', () { + indent.writeln( + 'auto channel = std::make_unique>('); + indent.inc(); + indent.inc(); + indent.writeln('binary_messenger, "$channelName", &GetCodec());'); + indent.dec(); + indent.dec(); + indent.write('if (api != nullptr) '); + indent.scoped('{', '} else {', () { + indent.write( + 'channel->SetMessageHandler([api](const flutter::EncodableValue& message, const flutter::MessageReply& reply) '); + indent.scoped('{', '});', () { + indent.write('try '); + indent.scoped('{', '}', () { + final List methodArgument = []; + if (method.arguments.isNotEmpty) { + indent.writeln( + 'const auto& args = std::get(message);'); + + // Writes the code to declare and populate a variable called + // [argName] to use as a parameter to an API method call from + // an existing EncodablValue variable called [encodableArgName] + // which corresponds to [arg] in the API definition. + void extractEncodedArgument( + String argName, + String encodableArgName, + NamedType arg, + HostDatatype hostType) { + if (arg.type.isNullable) { + // Nullable arguments are always pointers, with nullptr + // corresponding to null. + if (hostType.datatype == 'int64_t') { + // The EncodableValue will either be an int32_t or an + // int64_t depending on the value, but the generated API + // requires an int64_t so that it can handle any case. + // Create a local variable for the 64-bit value... + final String valueVarName = '${argName}_value'; + indent.writeln( + 'const int64_t $valueVarName = $encodableArgName.IsNull() ? 0 : $encodableArgName.LongValue();'); + // ... then declare the arg as a reference to that local. + indent.writeln( + 'const auto* $argName = $encodableArgName.IsNull() ? nullptr : &$valueVarName;'); + } else if (hostType.datatype == + 'flutter::EncodableValue') { + // Generic objects just pass the EncodableValue through + // directly. + indent.writeln( + 'const auto* $argName = &$encodableArgName;'); + } else if (hostType.isBuiltin) { + indent.writeln( + 'const auto* $argName = std::get_if<${hostType.datatype}>(&$encodableArgName);'); + } else { + indent.writeln( + 'const auto* $argName = &(std::any_cast(std::get($encodableArgName)));'); + } + } else { + // Non-nullable arguments are either passed by value or + // reference, but the extraction doesn't need to distinguish + // since those are the same at the call site. + if (hostType.datatype == 'int64_t') { + // The EncodableValue will either be an int32_t or an + // int64_t depending on the value, but the generated API + // requires an int64_t so that it can handle any case. + indent.writeln( + 'const int64_t $argName = $encodableArgName.LongValue();'); + } else if (hostType.datatype == + 'flutter::EncodableValue') { + // Generic objects just pass the EncodableValue through + // directly. This creates an alias just to avoid having to + // special-case the argName/encodableArgName distinction + // at a higher level. + indent.writeln( + 'const auto& $argName = $encodableArgName;'); + } else if (hostType.isBuiltin) { + indent.writeln( + 'const auto& $argName = std::get<${hostType.datatype}>($encodableArgName);'); + } else { + indent.writeln( + 'const auto& $argName = std::any_cast(std::get($encodableArgName));'); + } + } + } + + enumerate(method.arguments, (int index, NamedType arg) { + final HostDatatype hostType = getHostDatatype( + arg.type, + root.classes, + root.enums, + (TypeDeclaration x) => + _baseCppTypeForBuiltinDartType(x)); + final String argName = _getSafeArgumentName(index, arg); + + final String encodableArgName = + '${_encodablePrefix}_$argName'; + indent.writeln( + 'const auto& $encodableArgName = args.at($index);'); + if (!arg.type.isNullable) { + indent.write('if ($encodableArgName.IsNull()) '); + indent.scoped('{', '}', () { + indent.writeln( + 'reply(WrapError("$argName unexpectedly null."));'); + indent.writeln('return;'); + }); + } + extractEncodedArgument( + argName, encodableArgName, arg, hostType); + methodArgument.add(argName); + }); + } + + final HostDatatype returnType = getHostDatatype( + method.returnType, + root.classes, + root.enums, + (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); + final String returnTypeName = _apiReturnType(returnType); + if (method.isAsynchronous) { + methodArgument.add( + '[reply]($returnTypeName&& output) {${indent.newline}' + '${_wrapResponse(indent, root, method.returnType, prefix: '\t')}${indent.newline}' + '}', + ); + } + final String call = + 'api->${_makeMethodName(method)}(${methodArgument.join(', ')})'; + if (method.isAsynchronous) { + indent.format('$call;'); + } else { + indent.writeln('$returnTypeName output = $call;'); + indent.format(_wrapResponse(indent, root, method.returnType)); + } + }); + indent.write('catch (const std::exception& exception) '); + indent.scoped('{', '}', () { + // There is a potential here for `reply` to be called twice, which + // is a violation of the API contract, because there's no way of + // knowing whether or not the plugin code called `reply` before + // throwing. Since use of `@async` suggests that the reply is + // probably not sent within the scope of the stack, err on the + // side of potential double-call rather than no call (which is + // also an API violation) so that unexpected errors have a better + // chance of being caught and handled in a useful way. + indent.writeln('reply(WrapError(exception.what()));'); + }); + }); + }); + indent.scoped(null, '}', () { + indent.writeln('channel->SetMessageHandler(nullptr);'); + }); + }); + } + }); + + indent.addln(''); + indent.format(''' +flutter::EncodableValue ${api.name}::WrapError(std::string_view error_message) { +\treturn flutter::EncodableValue(flutter::EncodableList{ +\t\tflutter::EncodableValue(std::string(error_message)), +\t\tflutter::EncodableValue("Error"), +\t\tflutter::EncodableValue() +\t}); } +flutter::EncodableValue ${api.name}::WrapError(const FlutterError& error) { +\treturn flutter::EncodableValue(flutter::EncodableList{ +\t\tflutter::EncodableValue(error.message()), +\t\tflutter::EncodableValue(error.code()), +\t\terror.details() +\t}); +}'''); + indent.addln(''); + } -/// Writes the declaration for the custom class [klass]. -/// -/// See [_writeDataClassImplementation] for the corresponding declaration. -/// This is intended to be added to the header. -void _writeDataClassDeclaration(Indent indent, Class klass, Root root, - {String? testFriend}) { - indent.addln(''); - - const List generatedMessages = [ - ' Generated class from Pigeon that represents data sent in messages.' - ]; - - addDocumentationComments(indent, klass.documentationComments, _docCommentSpec, - generatorComments: generatedMessages); - - indent.write('class ${klass.name} '); - indent.scoped('{', '};', () { - indent.scoped(' public:', '', () { - indent.writeln('${klass.name}();'); - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - addDocumentationComments( - indent, field.documentationComments, _docCommentSpec); - final HostDatatype baseDatatype = getFieldHostDatatype( - field, - root.classes, - root.enums, - (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); - indent.writeln( - '${_getterReturnType(baseDatatype)} ${_makeGetterName(field)}() const;'); - indent.writeln( - 'void ${_makeSetterName(field)}(${_unownedArgumentType(baseDatatype)} value_arg);'); - if (field.type.isNullable) { - // Add a second setter that takes the non-nullable version of the - // argument for convenience, since setting literal values with the - // pointer version is non-trivial. - final HostDatatype nonNullType = _nonNullableType(baseDatatype); - indent.writeln( - 'void ${_makeSetterName(field)}(${_unownedArgumentType(nonNullType)} value_arg);'); + void _writeCodec( + CppOptions generatorOptions, + Root root, + Indent indent, + Api api, + ) { + assert(getCodecClasses(api, root).isNotEmpty); + final String codeSerializerName = _getCodecSerializerName(api); + indent.writeln('$codeSerializerName::$codeSerializerName() {}'); + indent.write( + 'flutter::EncodableValue $codeSerializerName::ReadValueOfType(uint8_t type, flutter::ByteStreamReader* stream) const '); + indent.scoped('{', '}', () { + indent.write('switch (type) '); + indent.scoped('{', '}', () { + for (final EnumeratedClass customClass in getCodecClasses(api, root)) { + indent.write('case ${customClass.enumeration}:'); + indent.writeScoped('', '', () { + indent.writeln( + 'return flutter::CustomEncodableValue(${customClass.name}(std::get(ReadValue(stream))));'); + }); } - indent.addln(''); - } + indent.write('default:'); + indent.writeScoped('', '', () { + indent.writeln( + 'return $_defaultCodecSerializer::ReadValueOfType(type, stream);'); + }, addTrailingNewline: false); + }); }); - - indent.scoped(' private:', '', () { - indent.writeln('${klass.name}(const flutter::EncodableList& list);'); - indent.writeln('flutter::EncodableList ToEncodableList() const;'); - for (final Class friend in root.classes) { - if (friend != klass && - friend.fields.any( - (NamedType element) => element.type.baseName == klass.name)) { - indent.writeln('friend class ${friend.name};'); + indent.writeln(''); + indent.write( + 'void $codeSerializerName::WriteValue(const flutter::EncodableValue& value, flutter::ByteStreamWriter* stream) const '); + indent.writeScoped('{', '}', () { + indent.write( + 'if (const flutter::CustomEncodableValue* custom_value = std::get_if(&value)) '); + indent.scoped('{', '}', () { + for (final EnumeratedClass customClass in getCodecClasses(api, root)) { + indent.write( + 'if (custom_value->type() == typeid(${customClass.name})) '); + indent.scoped('{', '}', () { + indent.writeln('stream->WriteByte(${customClass.enumeration});'); + indent.writeln( + 'WriteValue(flutter::EncodableValue(std::any_cast<${customClass.name}>(*custom_value).ToEncodableList()), stream);'); + indent.writeln('return;'); + }); } - } - for (final Api api in root.apis) { - // TODO(gaaclarke): Find a way to be more precise with our - // friendships. - indent.writeln('friend class ${api.name};'); - indent.writeln('friend class ${_getCodecSerializerName(api)};'); - } - if (testFriend != null) { - indent.writeln('friend class $testFriend;'); - } - - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - final HostDatatype hostDatatype = getFieldHostDatatype( - field, - root.classes, - root.enums, - (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); - indent.writeln( - '${_valueType(hostDatatype)} ${_makeInstanceVariableName(field)};'); - } + }); + indent.writeln('$_defaultCodecSerializer::WriteValue(value, stream);'); }); - }, nestCount: 0); - indent.writeln(''); -} + indent.writeln(''); + } -/// Writes the implementation for the custom class [klass]. -/// -/// See [_writeDataClassDeclaration] for the corresponding declaration. -/// This is intended to be added to the implementation file. -void _writeDataClassImplementation(Indent indent, Class klass, Root root) { - final Set rootClassNameSet = - root.classes.map((Class x) => x.name).toSet(); - final Set rootEnumNameSet = - root.enums.map((Enum x) => x.name).toSet(); - - indent.addln(''); - indent.writeln('$_commentPrefix ${klass.name}'); - indent.addln(''); - - // Getters and setters. - for (final NamedType field in getFieldsInSerializationOrder(klass)) { + void _writeCppSourceClassField(CppOptions generatorOptions, Root root, + Indent indent, Class klass, NamedType field) { final HostDatatype hostDatatype = getFieldHostDatatype(field, root.classes, root.enums, (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); final String instanceVariableName = _makeInstanceVariableName(field); @@ -333,7 +1038,7 @@ void _writeDataClassImplementation(Indent indent, Class klass, Root root) { '{ return $returnExpression; }'); indent.writeln(makeSetter(hostDatatype)); if (hostDatatype.isNullable) { - // Write the non-nullable variant; see _writeDataClassDeclaration. + // Write the non-nullable variant; see _writeCppHeaderDataClass. final HostDatatype nonNullType = _nonNullableType(hostDatatype); indent.writeln(makeSetter(nonNullType)); } @@ -341,344 +1046,46 @@ void _writeDataClassImplementation(Indent indent, Class klass, Root root) { indent.addln(''); } - // Serialization. - indent - .write('flutter::EncodableList ${klass.name}::ToEncodableList() const '); - indent.scoped('{', '}', () { - indent.scoped('return flutter::EncodableList{', '};', () { - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - final HostDatatype hostDatatype = getFieldHostDatatype( - field, - root.classes, - root.enums, - (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); - - final String instanceVariable = _makeInstanceVariableName(field); - - String encodableValue = ''; - if (!hostDatatype.isBuiltin && - rootClassNameSet.contains(field.type.baseName)) { - final String operator = field.type.isNullable ? '->' : '.'; - encodableValue = - 'flutter::EncodableValue($instanceVariable${operator}ToEncodableList())'; - } else if (!hostDatatype.isBuiltin && - rootEnumNameSet.contains(field.type.baseName)) { - final String nonNullValue = - field.type.isNullable ? '(*$instanceVariable)' : instanceVariable; - encodableValue = 'flutter::EncodableValue((int)$nonNullValue)'; - } else { - final String operator = field.type.isNullable ? '*' : ''; - encodableValue = - 'flutter::EncodableValue($operator$instanceVariable)'; - } - - if (field.type.isNullable) { - encodableValue = - '$instanceVariable ? $encodableValue : flutter::EncodableValue()'; - } - - indent.writeln('$encodableValue,'); - } - }); - }); - indent.addln(''); - - // Default constructor. - indent.writeln('${klass.name}::${klass.name}() {}'); - indent.addln(''); - - // Deserialization. - indent.write( - '${klass.name}::${klass.name}(const flutter::EncodableList& list) '); - indent.scoped('{', '}', () { - enumerate(getFieldsInSerializationOrder(klass), - (int index, final NamedType field) { - final String instanceVariableName = _makeInstanceVariableName(field); - final String pointerFieldName = - '${_pointerPrefix}_${_makeVariableName(field)}'; - final String encodableFieldName = - '${_encodablePrefix}_${_makeVariableName(field)}'; - indent.writeln('auto& $encodableFieldName = list[$index];'); - if (rootEnumNameSet.contains(field.type.baseName)) { - indent.writeln( - 'if (const int32_t* $pointerFieldName = std::get_if(&$encodableFieldName))\t$instanceVariableName = (${field.type.baseName})*$pointerFieldName;'); - } else { - final HostDatatype hostDatatype = getFieldHostDatatype( - field, - root.classes, - root.enums, - (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); - if (field.type.baseName == 'int') { - indent.format(''' -if (const int32_t* $pointerFieldName = std::get_if(&$encodableFieldName)) -\t$instanceVariableName = *$pointerFieldName; -else if (const int64_t* ${pointerFieldName}_64 = std::get_if(&$encodableFieldName)) -\t$instanceVariableName = *${pointerFieldName}_64;'''); - } else if (!hostDatatype.isBuiltin && - root.classes - .map((Class x) => x.name) - .contains(field.type.baseName)) { - indent.write( - 'if (const flutter::EncodableList* $pointerFieldName = std::get_if(&$encodableFieldName)) '); - indent.scoped('{', '}', () { - indent.writeln( - '$instanceVariableName = ${hostDatatype.datatype}(*$pointerFieldName);'); - }); - } else { - indent.write( - 'if (const ${hostDatatype.datatype}* $pointerFieldName = std::get_if<${hostDatatype.datatype}>(&$encodableFieldName)) '); - indent.scoped('{', '}', () { - indent.writeln('$instanceVariableName = *$pointerFieldName;'); - }); - } - } - }); - }); - indent.addln(''); -} - -void _writeHostApiHeader(Indent indent, Api api, Root root) { - assert(api.location == ApiLocation.host); - - const List generatedMessages = [ - ' Generated interface from Pigeon that represents a handler of messages from Flutter.' - ]; - addDocumentationComments(indent, api.documentationComments, _docCommentSpec, - generatorComments: generatedMessages); - indent.write('class ${api.name} '); - indent.scoped('{', '};', () { - indent.scoped(' public:', '', () { - indent.writeln('${api.name}(const ${api.name}&) = delete;'); - indent.writeln('${api.name}& operator=(const ${api.name}&) = delete;'); - indent.writeln('virtual ~${api.name}() { };'); - for (final Method method in api.methods) { - final HostDatatype returnType = getHostDatatype( - method.returnType, - root.classes, - root.enums, - (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); - final String returnTypeName = _apiReturnType(returnType); - - final List argSignature = []; - if (method.arguments.isNotEmpty) { - final Iterable argTypes = - method.arguments.map((NamedType arg) { - final HostDatatype hostType = getFieldHostDatatype( - arg, - root.classes, - root.enums, - (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); - return _hostApiArgumentType(hostType); - }); - final Iterable argNames = - method.arguments.map((NamedType e) => _makeVariableName(e)); - argSignature.addAll( - map2(argTypes, argNames, (String argType, String argName) { - return '$argType $argName'; - })); - } - - addDocumentationComments( - indent, method.documentationComments, _docCommentSpec); - - if (method.isAsynchronous) { - argSignature.add('std::function result'); - indent.writeln( - 'virtual void ${_makeMethodName(method)}(${argSignature.join(', ')}) = 0;'); - } else { - indent.writeln( - 'virtual $returnTypeName ${_makeMethodName(method)}(${argSignature.join(', ')}) = 0;'); - } - } - indent.addln(''); - indent.writeln('$_commentPrefix The codec used by ${api.name}.'); - indent.writeln('static const flutter::StandardMessageCodec& GetCodec();'); - indent.writeln( - '$_commentPrefix Sets up an instance of `${api.name}` to handle messages through the `binary_messenger`.'); - indent.writeln( - 'static void SetUp(flutter::BinaryMessenger* binary_messenger, ${api.name}* api);'); - indent.writeln( - 'static flutter::EncodableValue WrapError(std::string_view error_message);'); - indent.writeln( - 'static flutter::EncodableValue WrapError(const FlutterError& error);'); - }); - indent.scoped(' protected:', '', () { - indent.writeln('${api.name}() = default;'); - }); - }, nestCount: 0); -} - -void _writeHostApiSource(Indent indent, Api api, Root root) { - assert(api.location == ApiLocation.host); - - final String codeSerializerName = getCodecClasses(api, root).isNotEmpty - ? _getCodecSerializerName(api) - : _defaultCodecSerializer; - indent.format(''' -/// The codec used by ${api.name}. -const flutter::StandardMessageCodec& ${api.name}::GetCodec() { -\treturn flutter::StandardMessageCodec::GetInstance(&$codeSerializerName::GetInstance()); -} -'''); - indent.writeln( - '$_commentPrefix Sets up an instance of `${api.name}` to handle messages through the `binary_messenger`.'); - indent.write( - 'void ${api.name}::SetUp(flutter::BinaryMessenger* binary_messenger, ${api.name}* api) '); - indent.scoped('{', '}', () { - for (final Method method in api.methods) { - final String channelName = makeChannelName(api, method); - indent.write(''); - indent.scoped('{', '}', () { - indent.writeln( - 'auto channel = std::make_unique>('); - indent.inc(); - indent.inc(); - indent.writeln('binary_messenger, "$channelName", &GetCodec());'); - indent.dec(); - indent.dec(); - indent.write('if (api != nullptr) '); - indent.scoped('{', '} else {', () { - indent.write( - 'channel->SetMessageHandler([api](const flutter::EncodableValue& message, const flutter::MessageReply& reply) '); - indent.scoped('{', '});', () { - indent.write('try '); - indent.scoped('{', '}', () { - final List methodArgument = []; - if (method.arguments.isNotEmpty) { - indent.writeln( - 'const auto& args = std::get(message);'); - - // Writes the code to declare and populate a variable called - // [argName] to use as a parameter to an API method call from - // an existing EncodablValue variable called [encodableArgName] - // which corresponds to [arg] in the API definition. - void extractEncodedArgument( - String argName, - String encodableArgName, - NamedType arg, - HostDatatype hostType) { - if (arg.type.isNullable) { - // Nullable arguments are always pointers, with nullptr - // corresponding to null. - if (hostType.datatype == 'int64_t') { - // The EncodableValue will either be an int32_t or an - // int64_t depending on the value, but the generated API - // requires an int64_t so that it can handle any case. - // Create a local variable for the 64-bit value... - final String valueVarName = '${argName}_value'; - indent.writeln( - 'const int64_t $valueVarName = $encodableArgName.IsNull() ? 0 : $encodableArgName.LongValue();'); - // ... then declare the arg as a reference to that local. - indent.writeln( - 'const auto* $argName = $encodableArgName.IsNull() ? nullptr : &$valueVarName;'); - } else if (hostType.datatype == 'flutter::EncodableValue') { - // Generic objects just pass the EncodableValue through - // directly. - indent.writeln( - 'const auto* $argName = &$encodableArgName;'); - } else if (hostType.isBuiltin) { - indent.writeln( - 'const auto* $argName = std::get_if<${hostType.datatype}>(&$encodableArgName);'); - } else { - indent.writeln( - 'const auto* $argName = &(std::any_cast(std::get($encodableArgName)));'); - } - } else { - // Non-nullable arguments are either passed by value or - // reference, but the extraction doesn't need to distinguish - // since those are the same at the call site. - if (hostType.datatype == 'int64_t') { - // The EncodableValue will either be an int32_t or an - // int64_t depending on the value, but the generated API - // requires an int64_t so that it can handle any case. - indent.writeln( - 'const int64_t $argName = $encodableArgName.LongValue();'); - } else if (hostType.datatype == 'flutter::EncodableValue') { - // Generic objects just pass the EncodableValue through - // directly. This creates an alias just to avoid having to - // special-case the argName/encodableArgName distinction - // at a higher level. - indent - .writeln('const auto& $argName = $encodableArgName;'); - } else if (hostType.isBuiltin) { - indent.writeln( - 'const auto& $argName = std::get<${hostType.datatype}>($encodableArgName);'); - } else { - indent.writeln( - 'const auto& $argName = std::any_cast(std::get($encodableArgName));'); - } - } - } - - enumerate(method.arguments, (int index, NamedType arg) { - final HostDatatype hostType = getHostDatatype( - arg.type, - root.classes, - root.enums, - (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); - final String argName = _getSafeArgumentName(index, arg); - - final String encodableArgName = - '${_encodablePrefix}_$argName'; - indent.writeln( - 'const auto& $encodableArgName = args.at($index);'); - if (!arg.type.isNullable) { - indent.write('if ($encodableArgName.IsNull()) '); - indent.scoped('{', '}', () { - indent.writeln( - 'reply(WrapError("$argName unexpectedly null."));'); - indent.writeln('return;'); - }); - } - extractEncodedArgument( - argName, encodableArgName, arg, hostType); - methodArgument.add(argName); - }); - } - - String wrapResponse(TypeDeclaration returnType, - {String prefix = ''}) { - final String nonErrorPath; - final String errorCondition; - final String errorGetter; - const String nullValue = 'flutter::EncodableValue()'; - - if (returnType.isVoid) { - nonErrorPath = '${prefix}wrapped.push_back($nullValue);'; - errorCondition = 'output.has_value()'; - errorGetter = 'value'; - } else { - final HostDatatype hostType = getHostDatatype( - returnType, - root.classes, - root.enums, - (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); - const String extractedValue = 'std::move(output).TakeValue()'; - final String wrapperType = hostType.isBuiltin - ? 'flutter::EncodableValue' - : 'flutter::CustomEncodableValue'; - if (returnType.isNullable) { - // The value is a std::optional, so needs an extra layer of - // handling. - nonErrorPath = ''' + String _wrapResponse(Indent indent, Root root, TypeDeclaration returnType, + {String prefix = ''}) { + final String nonErrorPath; + final String errorCondition; + final String errorGetter; + + const String nullValue = 'flutter::EncodableValue()'; + if (returnType.isVoid) { + nonErrorPath = '${prefix}wrapped.push_back($nullValue);'; + errorCondition = 'output.has_value()'; + errorGetter = 'value'; + } else { + final HostDatatype hostType = getHostDatatype(returnType, root.classes, + root.enums, (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); + const String extractedValue = 'std::move(output).TakeValue()'; + final String wrapperType = hostType.isBuiltin + ? 'flutter::EncodableValue' + : 'flutter::CustomEncodableValue'; + if (returnType.isNullable) { + // The value is a std::optional, so needs an extra layer of + // handling. + nonErrorPath = ''' ${prefix}auto output_optional = $extractedValue; ${prefix}if (output_optional) { $prefix\twrapped.push_back($wrapperType(std::move(output_optional).value())); $prefix} else { $prefix\twrapped.push_back($nullValue); $prefix}'''; - } else { - nonErrorPath = - '${prefix}wrapped.push_back($wrapperType($extractedValue));'; - } - errorCondition = 'output.has_error()'; - errorGetter = 'error'; - } - // Ideally this code would use an initializer list to create - // an EncodableList inline, which would be less code. However, - // that would always copy the element, so the slightly more - // verbose create-and-push approach is used instead. - return ''' + } else { + nonErrorPath = + '${prefix}wrapped.push_back($wrapperType($extractedValue));'; + } + errorCondition = 'output.has_error()'; + errorGetter = 'error'; + } + // Ideally this code would use an initializer list to create + // an EncodableList inline, which would be less code. However, + // that would always copy the element, so the slightly more + // verbose create-and-push approach is used instead. + return ''' ${prefix}if ($errorCondition) { $prefix\treply(WrapError(output.$errorGetter())); $prefix\treturn; @@ -686,52 +1093,22 @@ $prefix} ${prefix}flutter::EncodableList wrapped; $nonErrorPath ${prefix}reply(flutter::EncodableValue(std::move(wrapped)));'''; - } + } - final HostDatatype returnType = getHostDatatype( - method.returnType, - root.classes, - root.enums, - (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); - final String returnTypeName = _apiReturnType(returnType); - if (method.isAsynchronous) { - methodArgument.add( - '[reply]($returnTypeName&& output) {${indent.newline}' - '${wrapResponse(method.returnType, prefix: '\t')}${indent.newline}' - '}', - ); - } - final String call = - 'api->${_makeMethodName(method)}(${methodArgument.join(', ')})'; - if (method.isAsynchronous) { - indent.format('$call;'); - } else { - indent.writeln('$returnTypeName output = $call;'); - indent.format(wrapResponse(method.returnType)); - } - }); - indent.write('catch (const std::exception& exception) '); - indent.scoped('{', '}', () { - // There is a potential here for `reply` to be called twice, which - // is a violation of the API contract, because there's no way of - // knowing whether or not the plugin code called `reply` before - // throwing. Since use of `@async` suggests that the reply is - // probably not sent within the scope of the stack, err on the - // side of potential double-call rather than no call (which is - // also an API violation) so that unexpected errors have a better - // chance of being caught and handled in a useful way. - indent.writeln('reply(WrapError(exception.what()));'); - }); - }); - }); - indent.scoped(null, '}', () { - indent.writeln('channel->SetMessageHandler(nullptr);'); - }); - }); + @override + void writeCloseNamespace( + CppOptions generatorOptions, Root root, Indent indent) { + if (generatorOptions.namespace != null) { + indent.writeln('} // namespace ${generatorOptions.namespace}'); } - }); + } } +String _getCodecSerializerName(Api api) => '${api.name}CodecSerializer'; + +const String _pointerPrefix = 'pointer'; +const String _encodablePrefix = 'encodable'; + String _getArgumentName(int count, NamedType argument) => argument.name.isEmpty ? 'arg$count' : _makeVariableName(argument); @@ -739,145 +1116,6 @@ String _getArgumentName(int count, NamedType argument) => String _getSafeArgumentName(int count, NamedType argument) => '${_getArgumentName(count, argument)}_arg'; -void _writeFlutterApiHeader(Indent indent, Api api) { - assert(api.location == ApiLocation.flutter); - - const List generatedMessages = [ - ' Generated class from Pigeon that represents Flutter messages that can be called from C++.' - ]; - addDocumentationComments(indent, api.documentationComments, _docCommentSpec, - generatorComments: generatedMessages); - indent.write('class ${api.name} '); - indent.scoped('{', '};', () { - indent.scoped(' private:', '', () { - indent.writeln('flutter::BinaryMessenger* binary_messenger_;'); - }); - indent.scoped(' public:', '', () { - indent.write('${api.name}(flutter::BinaryMessenger* binary_messenger);'); - indent.writeln(''); - indent.writeln('static const flutter::StandardMessageCodec& GetCodec();'); - for (final Method func in api.methods) { - final String returnType = func.returnType.isVoid - ? 'void' - : _nullSafeCppTypeForDartType(func.returnType); - final String callback = 'std::function&& callback'; - addDocumentationComments( - indent, func.documentationComments, _docCommentSpec); - if (func.arguments.isEmpty) { - indent.writeln('void ${func.name}($callback);'); - } else { - final Iterable argTypes = func.arguments - .map((NamedType e) => _nullSafeCppTypeForDartType(e.type)); - final Iterable argNames = - indexMap(func.arguments, _getSafeArgumentName); - final String argsSignature = - map2(argTypes, argNames, (String x, String y) => '$x $y') - .join(', '); - indent.writeln('void ${func.name}($argsSignature, $callback);'); - } - } - }); - }, nestCount: 0); -} - -void _writeFlutterApiSource(Indent indent, Api api, Root root) { - assert(api.location == ApiLocation.flutter); - indent.writeln( - '$_commentPrefix Generated class from Pigeon that represents Flutter messages that can be called from C++.'); - indent.write( - '${api.name}::${api.name}(flutter::BinaryMessenger* binary_messenger) '); - indent.scoped('{', '}', () { - indent.writeln('this->binary_messenger_ = binary_messenger;'); - }); - indent.writeln(''); - final String codeSerializerName = getCodecClasses(api, root).isNotEmpty - ? _getCodecSerializerName(api) - : _defaultCodecSerializer; - indent.format(''' -const flutter::StandardMessageCodec& ${api.name}::GetCodec() { -\treturn flutter::StandardMessageCodec::GetInstance(&$codeSerializerName::GetInstance()); -} -'''); - for (final Method func in api.methods) { - final String channelName = makeChannelName(api, func); - final String returnType = func.returnType.isVoid - ? 'void' - : _nullSafeCppTypeForDartType(func.returnType); - String sendArgument; - final String callback = 'std::function&& callback'; - if (func.arguments.isEmpty) { - indent.write('void ${api.name}::${func.name}($callback) '); - sendArgument = 'flutter::EncodableValue()'; - } else { - final Iterable argTypes = func.arguments - .map((NamedType e) => _nullSafeCppTypeForDartType(e.type)); - final Iterable argNames = - indexMap(func.arguments, _getSafeArgumentName); - sendArgument = - 'flutter::EncodableList { ${argNames.map((String arg) => 'flutter::CustomEncodableValue($arg)').join(', ')} }'; - final String argsSignature = - map2(argTypes, argNames, (String x, String y) => '$x $y').join(', '); - indent - .write('void ${api.name}::${func.name}($argsSignature, $callback) '); - } - indent.scoped('{', '}', () { - const String channel = 'channel'; - indent.writeln( - 'auto channel = std::make_unique>('); - indent.inc(); - indent.inc(); - indent.writeln('binary_messenger_, "$channelName", &GetCodec());'); - indent.dec(); - indent.dec(); - indent.write( - '$channel->Send($sendArgument, [callback](const uint8_t* reply, size_t reply_size) '); - indent.scoped('{', '});', () { - if (func.returnType.isVoid) { - indent.writeln('callback();'); - } else { - indent.writeln( - 'std::unique_ptr decoded_reply = GetCodec().DecodeMessage(reply, reply_size);'); - indent.writeln( - 'flutter::EncodableValue args = *(flutter::EncodableValue*)(decoded_reply.release());'); - const String output = 'output'; - - final bool isBuiltin = - _baseCppTypeForBuiltinDartType(func.returnType) != null; - final String returnTypeName = - _baseCppTypeForDartType(func.returnType); - if (func.returnType.isNullable) { - indent.writeln('$returnType $output{};'); - } else { - indent.writeln('$returnTypeName $output{};'); - } - const String pointerVariable = '${_pointerPrefix}_$output'; - if (func.returnType.baseName == 'int') { - indent.format(''' -if (const int32_t* $pointerVariable = std::get_if(&args)) -\t$output = *$pointerVariable; -else if (const int64_t* ${pointerVariable}_64 = std::get_if(&args)) -\t$output = *${pointerVariable}_64;'''); - } else if (!isBuiltin) { - indent.write( - 'if (const flutter::EncodableList* $pointerVariable = std::get_if(&args)) '); - indent.scoped('{', '}', () { - indent.writeln('$output = $returnTypeName(*$pointerVariable);'); - }); - } else { - indent.write( - 'if (const $returnTypeName* $pointerVariable = std::get_if<$returnTypeName>(&args)) '); - indent.scoped('{', '}', () { - indent.writeln('$output = *$pointerVariable;'); - }); - } - - indent.writeln('callback($output);'); - } - }); - }); - } -} - /// Returns a non-nullable variant of [type]. HostDatatype _nonNullableType(HostDatatype type) { return HostDatatype( @@ -1035,14 +1273,11 @@ String _nullSafeCppTypeForDartType(TypeDeclaration type, } } -String _getGuardName(String? headerFileName, String? namespace) { +String _getGuardName(String? headerFileName) { String guardName = 'PIGEON_'; if (headerFileName != null) { guardName += '${headerFileName.replaceAll('.', '_').toUpperCase()}_'; } - if (namespace != null) { - guardName += '${namespace.toUpperCase()}_'; - } return '${guardName}H_'; } @@ -1053,169 +1288,6 @@ void _writeSystemHeaderIncludeBlock(Indent indent, List headers) { } } -/// Generates the ".h" file for the AST represented by [root] to [sink] with the -/// provided [options] and [headerFileName]. -void generateCppHeader(CppOptions options, Root root, StringSink sink) { - final String? headerFileName = options.headerOutPath; - final Indent indent = Indent(sink); - if (options.copyrightHeader != null) { - addLines(indent, options.copyrightHeader!, linePrefix: '// '); - } - indent.writeln('$_commentPrefix $generatedCodeWarning'); - indent.writeln('$_commentPrefix $seeAlsoWarning'); - indent.addln(''); - final String guardName = _getGuardName(headerFileName, options.namespace); - indent.writeln('#ifndef $guardName'); - indent.writeln('#define $guardName'); - - _writeSystemHeaderIncludeBlock(indent, [ - 'flutter/basic_message_channel.h', - 'flutter/binary_messenger.h', - 'flutter/encodable_value.h', - 'flutter/standard_message_codec.h', - ]); - indent.addln(''); - _writeSystemHeaderIncludeBlock(indent, [ - 'map', - 'string', - 'optional', - ]); - indent.addln(''); - - if (options.namespace != null) { - indent.writeln('namespace ${options.namespace} {'); - } - - // When generating for a Pigeon unit test, add a test fixture friend class to - // allow unit testing private methods, since testing serialization via public - // methods is essentially an end-to-end test. - String? testFixtureClass; - if (options.namespace?.endsWith('_pigeontest') ?? false) { - testFixtureClass = - '${_pascalCaseFromSnakeCase(options.namespace!.replaceAll('_pigeontest', ''))}Test'; - indent.writeln('class $testFixtureClass;'); - } - - indent.addln(''); - indent.writeln('$_commentPrefix Generated class from Pigeon.'); - - for (final Enum anEnum in root.enums) { - indent.writeln(''); - addDocumentationComments( - indent, anEnum.documentationComments, _docCommentSpec); - indent.write('enum class ${anEnum.name} '); - indent.scoped('{', '};', () { - enumerate(anEnum.members, (int index, final EnumMember member) { - addDocumentationComments( - indent, member.documentationComments, _docCommentSpec); - indent.writeln( - '${member.name} = $index${index == anEnum.members.length - 1 ? '' : ','}'); - }); - }); - } - - indent.addln(''); - - _writeErrorOr(indent, friends: root.apis.map((Api api) => api.name)); - - for (final Class klass in root.classes) { - _writeDataClassDeclaration(indent, klass, root, - // Add a hook for unit testing data classes when using the namespace - // used by pigeon tests. - testFriend: testFixtureClass); - } - - for (final Api api in root.apis) { - if (getCodecClasses(api, root).isNotEmpty) { - _writeCodecHeader(indent, api, root); - } - indent.addln(''); - if (api.location == ApiLocation.host) { - _writeHostApiHeader(indent, api, root); - } else if (api.location == ApiLocation.flutter) { - _writeFlutterApiHeader(indent, api); - } - } - - if (options.namespace != null) { - indent.writeln('} // namespace ${options.namespace}'); - } - - indent.writeln('#endif // $guardName'); -} - -/// Generates the ".cpp" file for the AST represented by [root] to [sink] with the -/// provided [options]. -void generateCppSource(CppOptions options, Root root, StringSink sink) { - final Indent indent = Indent(sink); - if (options.copyrightHeader != null) { - addLines(indent, options.copyrightHeader!, linePrefix: '// '); - } - indent.writeln('$_commentPrefix $generatedCodeWarning'); - indent.writeln('$_commentPrefix $seeAlsoWarning'); - indent.addln(''); - indent.addln('#undef _HAS_EXCEPTIONS'); - indent.addln(''); - - indent.writeln('#include "${options.headerIncludePath}"'); - indent.addln(''); - _writeSystemHeaderIncludeBlock(indent, [ - 'flutter/basic_message_channel.h', - 'flutter/binary_messenger.h', - 'flutter/encodable_value.h', - 'flutter/standard_message_codec.h', - ]); - indent.addln(''); - _writeSystemHeaderIncludeBlock(indent, [ - 'map', - 'string', - 'optional', - ]); - indent.addln(''); - - if (options.namespace != null) { - indent.writeln('namespace ${options.namespace} {'); - } - - for (final Class klass in root.classes) { - _writeDataClassImplementation(indent, klass, root); - } - - for (final Api api in root.apis) { - if (getCodecClasses(api, root).isNotEmpty) { - _writeCodecSource(indent, api, root); - indent.addln(''); - } - if (api.location == ApiLocation.host) { - _writeHostApiSource(indent, api, root); - - indent.addln(''); - indent.format(''' -flutter::EncodableValue ${api.name}::WrapError(std::string_view error_message) { -\treturn flutter::EncodableValue(flutter::EncodableList{ -\t\tflutter::EncodableValue(std::string(error_message)), -\t\tflutter::EncodableValue("Error"), -\t\tflutter::EncodableValue() -\t}); -} -flutter::EncodableValue ${api.name}::WrapError(const FlutterError& error) { -\treturn flutter::EncodableValue(flutter::EncodableList{ -\t\tflutter::EncodableValue(error.message()), -\t\tflutter::EncodableValue(error.code()), -\t\terror.details() -\t}); -}'''); - indent.addln(''); - } else if (api.location == ApiLocation.flutter) { - _writeFlutterApiSource(indent, api, root); - } - } - - if (options.namespace != null) { - indent.writeln('} // namespace ${options.namespace}'); - } -} - /// Validates an AST to make sure the cpp generator supports everything. List validateCpp(CppOptions options, Root root) { final List result = []; diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart index e6f685052f1..3b62affc3b1 100644 --- a/packages/pigeon/lib/dart_generator.dart +++ b/packages/pigeon/lib/dart_generator.dart @@ -71,29 +71,557 @@ class DartOptions { } /// Class that manages all Dart code generation. -class DartGenerator extends Generator { +class DartGenerator extends StructuredGenerator { /// Instantiates a Dart Generator. - DartGenerator(); + const DartGenerator(); - /// Generates Dart files with specified [DartOptions] @override - void generate(DartOptions languageOptions, Root root, StringSink sink, - {FileType fileType = FileType.na}) { - assert(fileType == FileType.na); - generateDart(languageOptions, root, sink); + void writeFilePrologue( + DartOptions generatorOptions, Root root, Indent indent) { + if (generatorOptions.copyrightHeader != null) { + addLines(indent, generatorOptions.copyrightHeader!, linePrefix: '// '); + } + indent.writeln('// $generatedCodeWarning'); + indent.writeln('// $seeAlsoWarning'); + indent.writeln( + '// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import', + ); + indent.addln(''); + } + + @override + void writeFileImports( + DartOptions generatorOptions, Root root, Indent indent) { + indent.writeln("import 'dart:async';"); + indent.writeln( + "import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;", + ); + indent.addln(''); + indent.writeln( + "import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;"); + indent.writeln("import 'package:flutter/services.dart';"); + } + + @override + void writeEnum( + DartOptions generatorOptions, Root root, Indent indent, Enum anEnum) { + indent.writeln(''); + addDocumentationComments( + indent, anEnum.documentationComments, _docCommentSpec); + indent.write('enum ${anEnum.name} '); + indent.scoped('{', '}', () { + for (final EnumMember member in anEnum.members) { + addDocumentationComments( + indent, member.documentationComments, _docCommentSpec); + indent.writeln('${member.name},'); + } + }); + } + + @override + void writeDataClass( + DartOptions generatorOptions, Root root, Indent indent, Class klass) { + final Set customClassNames = + root.classes.map((Class x) => x.name).toSet(); + final Set customEnumNames = + root.enums.map((Enum x) => x.name).toSet(); + + indent.writeln(''); + addDocumentationComments( + indent, klass.documentationComments, _docCommentSpec); + + indent.write('class ${klass.name} '); + indent.scoped('{', '}', () { + _writeConstructor(indent, klass); + indent.addln(''); + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + addDocumentationComments( + indent, field.documentationComments, _docCommentSpec); + + final String datatype = _addGenericTypesNullable(field.type); + indent.writeln('$datatype ${field.name};'); + indent.writeln(''); + } + writeClassEncode(generatorOptions, root, indent, klass, customClassNames, + customEnumNames); + indent.writeln(''); + writeClassDecode(generatorOptions, root, indent, klass, customClassNames, + customEnumNames); + }); + } + + void _writeConstructor(Indent indent, Class klass) { + indent.write(klass.name); + indent.scoped('({', '});', () { + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + final String required = field.type.isNullable ? '' : 'required '; + indent.writeln('${required}this.${field.name},'); + } + }); + } + + @override + void writeClassEncode( + DartOptions generatorOptions, + Root root, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) { + indent.write('Object encode() '); + indent.scoped('{', '}', () { + indent.write( + 'return ', + ); + indent.scoped('[', '];', () { + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + final String conditional = field.type.isNullable ? '?' : ''; + if (customClassNames.contains(field.type.baseName)) { + indent.writeln( + '${field.name}$conditional.encode(),', + ); + } else if (customEnumNames.contains(field.type.baseName)) { + indent.writeln( + '${field.name}$conditional.index,', + ); + } else { + indent.writeln('${field.name},'); + } + } + }); + }); + } + + @override + void writeClassDecode( + DartOptions generatorOptions, + Root root, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) { + void writeValueDecode(NamedType field, int index) { + final String resultAt = 'result[$index]'; + if (customClassNames.contains(field.type.baseName)) { + final String nonNullValue = + '${field.type.baseName}.decode($resultAt! as List)'; + indent.format( + field.type.isNullable + ? ''' +$resultAt != null +\t\t? $nonNullValue +\t\t: null''' + : nonNullValue, + leadingSpace: false, + trailingNewline: false); + } else if (customEnumNames.contains(field.type.baseName)) { + final String nonNullValue = + '${field.type.baseName}.values[$resultAt! as int]'; + indent.format( + field.type.isNullable + ? ''' +$resultAt != null +\t\t? $nonNullValue +\t\t: null''' + : nonNullValue, + leadingSpace: false, + trailingNewline: false); + } else if (field.type.typeArguments.isNotEmpty) { + final String genericType = _makeGenericTypeArguments(field.type); + final String castCall = _makeGenericCastCall(field.type); + final String castCallPrefix = field.type.isNullable ? '?' : '!'; + indent.add( + '($resultAt as $genericType?)$castCallPrefix$castCall', + ); + } else { + final String genericdType = _addGenericTypesNullable(field.type); + if (field.type.isNullable) { + indent.add( + '$resultAt as $genericdType', + ); + } else { + indent.add( + '$resultAt! as $genericdType', + ); + } + } + } + + indent.write( + 'static ${klass.name} decode(Object result) ', + ); + indent.scoped('{', '}', () { + indent.writeln('result as List;'); + indent.write('return ${klass.name}'); + indent.scoped('(', ');', () { + enumerate(getFieldsInSerializationOrder(klass), + (int index, final NamedType field) { + indent.write('${field.name}: '); + writeValueDecode(field, index); + indent.addln(','); + }); + }); + }); + } + + /// Writes the code for host [Api], [api]. + /// Example: + /// class FooCodec extends StandardMessageCodec {...} + /// + /// abstract class Foo { + /// static const MessageCodec codec = FooCodec(); + /// int add(int x, int y); + /// static void setup(Foo api, {BinaryMessenger? binaryMessenger}) {...} + /// } + @override + void writeFlutterApi( + DartOptions generatorOptions, + Root root, + Indent indent, + Api api, { + String Function(Method)? channelNameFunc, + bool isMockHandler = false, + }) { + assert(api.location == ApiLocation.flutter); + final List customEnumNames = + root.enums.map((Enum x) => x.name).toList(); + String codecName = _standardMessageCodec; + if (getCodecClasses(api, root).isNotEmpty) { + codecName = _getCodecName(api); + _writeCodec(indent, codecName, api, root); + } + indent.addln(''); + addDocumentationComments( + indent, api.documentationComments, _docCommentSpec); + + indent.write('abstract class ${api.name} '); + indent.scoped('{', '}', () { + indent + .writeln('static const MessageCodec codec = $codecName();'); + indent.addln(''); + for (final Method func in api.methods) { + addDocumentationComments( + indent, func.documentationComments, _docCommentSpec); + + final bool isAsync = func.isAsynchronous; + final String returnType = isAsync + ? 'Future<${_addGenericTypesNullable(func.returnType)}>' + : _addGenericTypesNullable(func.returnType); + final String argSignature = _getMethodArgumentsSignature( + func, + _getArgumentName, + ); + indent.writeln('$returnType ${func.name}($argSignature);'); + indent.writeln(''); + } + indent.write( + 'static void setup(${api.name}? api, {BinaryMessenger? binaryMessenger}) '); + indent.scoped('{', '}', () { + for (final Method func in api.methods) { + indent.write(''); + indent.scoped('{', '}', () { + indent.writeln( + 'final BasicMessageChannel channel = BasicMessageChannel(', + ); + final String channelName = channelNameFunc == null + ? makeChannelName(api, func) + : channelNameFunc(func); + indent.nest(2, () { + indent.writeln("'$channelName', codec,"); + indent.writeln( + 'binaryMessenger: binaryMessenger);', + ); + }); + final String messageHandlerSetter = + isMockHandler ? 'setMockMessageHandler' : 'setMessageHandler'; + indent.write('if (api == null) '); + indent.scoped('{', '}', () { + indent.writeln('channel.$messageHandlerSetter(null);'); + }, addTrailingNewline: false); + indent.add(' else '); + indent.scoped('{', '}', () { + indent.write( + 'channel.$messageHandlerSetter((Object? message) async ', + ); + indent.scoped('{', '});', () { + final String returnType = + _addGenericTypesNullable(func.returnType); + final bool isAsync = func.isAsynchronous; + final String emptyReturnStatement = isMockHandler + ? 'return [];' + : func.returnType.isVoid + ? 'return;' + : 'return null;'; + String call; + if (func.arguments.isEmpty) { + indent.writeln('// ignore message'); + call = 'api.${func.name}()'; + } else { + indent.writeln('assert(message != null,'); + indent.writeln("'Argument for $channelName was null.');"); + const String argsArray = 'args'; + indent.writeln( + 'final List $argsArray = (message as List?)!;'); + String argNameFunc(int index, NamedType type) => + _getSafeArgumentName(index, type); + enumerate(func.arguments, (int count, NamedType arg) { + final String argType = _addGenericTypes(arg.type); + final String argName = argNameFunc(count, arg); + final String genericArgType = + _makeGenericTypeArguments(arg.type); + final String castCall = _makeGenericCastCall(arg.type); + + final String leftHandSide = 'final $argType? $argName'; + if (customEnumNames.contains(arg.type.baseName)) { + indent.writeln( + '$leftHandSide = $argsArray[$count] == null ? null : $argType.values[$argsArray[$count] as int];'); + } else { + indent.writeln( + '$leftHandSide = ($argsArray[$count] as $genericArgType?)${castCall.isEmpty ? '' : '?$castCall'};'); + } + if (!arg.type.isNullable) { + indent.writeln( + "assert($argName != null, 'Argument for $channelName was null, expected non-null $argType.');"); + } + }); + final Iterable argNames = + indexMap(func.arguments, (int index, NamedType field) { + final String name = _getSafeArgumentName(index, field); + return '$name${field.type.isNullable ? '' : '!'}'; + }); + call = 'api.${func.name}(${argNames.join(', ')})'; + } + if (func.returnType.isVoid) { + if (isAsync) { + indent.writeln('await $call;'); + } else { + indent.writeln('$call;'); + } + indent.writeln(emptyReturnStatement); + } else { + if (isAsync) { + indent.writeln('final $returnType output = await $call;'); + } else { + indent.writeln('final $returnType output = $call;'); + } + const String returnExpression = 'output'; + final String returnStatement = isMockHandler + ? 'return [$returnExpression];' + : 'return $returnExpression;'; + indent.writeln(returnStatement); + } + }); + }); + }); + } + }); + }); + } + + /// Writes the code for host [Api], [api]. + /// Example: + /// class FooCodec extends StandardMessageCodec {...} + /// + /// class Foo { + /// Foo(BinaryMessenger? binaryMessenger) {} + /// static const MessageCodec codec = FooCodec(); + /// Future add(int x, int y) async {...} + /// } + /// + /// Messages will be sent and received in a list. + /// + /// If the message recieved was succesful, + /// the result will be contained at the 0'th index. + /// + /// If the message was a failure, the list will contain 3 items: + /// a code, a message, and details in that order. + @override + void writeHostApi( + DartOptions generatorOptions, Root root, Indent indent, Api api) { + assert(api.location == ApiLocation.host); + String codecName = _standardMessageCodec; + if (getCodecClasses(api, root).isNotEmpty) { + codecName = _getCodecName(api); + _writeCodec(indent, codecName, api, root); + } + indent.addln(''); + bool first = true; + addDocumentationComments( + indent, api.documentationComments, _docCommentSpec); + indent.write('class ${api.name} '); + indent.scoped('{', '}', () { + indent.format(''' +/// Constructor for [${api.name}]. The [binaryMessenger] named argument is +/// available for dependency injection. If it is left null, the default +/// BinaryMessenger will be used which routes to the host platform. +${api.name}({BinaryMessenger? binaryMessenger}) +\t\t: _binaryMessenger = binaryMessenger; +final BinaryMessenger? _binaryMessenger; +'''); + + indent + .writeln('static const MessageCodec codec = $codecName();'); + indent.addln(''); + for (final Method func in api.methods) { + if (!first) { + indent.writeln(''); + } else { + first = false; + } + addDocumentationComments( + indent, func.documentationComments, _docCommentSpec); + String argSignature = ''; + String sendArgument = 'null'; + if (func.arguments.isNotEmpty) { + String argNameFunc(int index, NamedType type) => + _getSafeArgumentName(index, type); + final Iterable argExpressions = + indexMap(func.arguments, (int index, NamedType type) { + final String name = argNameFunc(index, type); + if (root.enums + .map((Enum e) => e.name) + .contains(type.type.baseName)) { + return '$name${type.type.isNullable ? '?' : ''}.index'; + } else { + return name; + } + }); + sendArgument = '[${argExpressions.join(', ')}]'; + argSignature = _getMethodArgumentsSignature(func, argNameFunc); + } + indent.write( + 'Future<${_addGenericTypesNullable(func.returnType)}> ${func.name}($argSignature) async ', + ); + indent.scoped('{', '}', () { + final String channelName = makeChannelName(api, func); + indent.writeln( + 'final BasicMessageChannel channel = BasicMessageChannel('); + indent.nest(2, () { + indent.writeln("'$channelName', codec,"); + indent.writeln('binaryMessenger: _binaryMessenger);'); + }); + final String returnType = _makeGenericTypeArguments(func.returnType); + final String genericCastCall = _makeGenericCastCall(func.returnType); + const String accessor = 'replyList[0]'; + // Avoid warnings from pointlessly casting to `Object?`. + final String nullablyTypedAccessor = + returnType == 'Object' ? accessor : '($accessor as $returnType?)'; + final String nullHandler = func.returnType.isNullable + ? (genericCastCall.isEmpty ? '' : '?') + : '!'; + final String returnStatement = func.returnType.isVoid + ? 'return;' + : 'return $nullablyTypedAccessor$nullHandler$genericCastCall;'; + indent.format(''' +final List? replyList = +\t\tawait channel.send($sendArgument) as List?; +if (replyList == null) { +\tthrow PlatformException( +\t\tcode: 'channel-error', +\t\tmessage: 'Unable to establish connection on channel.', +\t); +} else if (replyList.length > 1) { +\tthrow PlatformException( +\t\tcode: replyList[0]! as String, +\t\tmessage: replyList[1] as String?, +\t\tdetails: replyList[2], +\t);'''); + // On iOS we can return nil from functions to accommodate error + // handling. Returning a nil value and not returning an error is an + // exception. + if (!func.returnType.isNullable && !func.returnType.isVoid) { + indent.format(''' +} else if (replyList[0] == null) { +\tthrow PlatformException( +\t\tcode: 'null-error', +\t\tmessage: 'Host platform returned null value for non-null return value.', +\t);'''); + } + indent.format(''' +} else { +\t$returnStatement +}'''); + }); + } + }); + } + + /// Generates Dart source code for test support libraries based on the given AST + /// represented by [root], outputting the code to [sink]. [sourceOutPath] is the + /// path of the generated dart code to be tested. [testOutPath] is where the + /// test code will be generated. + void generateTest(DartOptions generatorOptions, Root root, StringSink sink) { + final Indent indent = Indent(sink); + final String sourceOutPath = generatorOptions.sourceOutPath ?? ''; + final String testOutPath = generatorOptions.testOutPath ?? ''; + _writeTestPrologue(generatorOptions, root, indent); + _writeTestImports(generatorOptions, root, indent); + final String relativeDartPath = + path.Context(style: path.Style.posix).relative( + _posixify(sourceOutPath), + from: _posixify(path.dirname(testOutPath)), + ); + late final String? packageName = _deducePackageName(sourceOutPath); + if (!relativeDartPath.contains('/lib/') || packageName == null) { + // If we can't figure out the package name or the relative path doesn't + // include a 'lib' directory, try relative path import which only works in + // certain (older) versions of Dart. + // TODO(gaaclarke): We should add a command-line parameter to override this import. + indent.writeln( + "import '${_escapeForDartSingleQuotedString(relativeDartPath)}';"); + } else { + final String path = + relativeDartPath.replaceFirst(RegExp(r'^.*/lib/'), ''); + indent.writeln("import 'package:$packageName/$path';"); + } + for (final Api api in root.apis) { + if (api.location == ApiLocation.host && api.dartHostTestHandler != null) { + final Api mockApi = Api( + name: api.dartHostTestHandler!, + methods: api.methods, + location: ApiLocation.flutter, + dartHostTestHandler: api.dartHostTestHandler, + documentationComments: api.documentationComments, + ); + indent.writeln(''); + writeFlutterApi( + generatorOptions, + root, + indent, + mockApi, + channelNameFunc: (Method func) => makeChannelName(api, func), + isMockHandler: true, + ); + } + } + } + + /// Writes file header to sink. + void _writeTestPrologue(DartOptions opt, Root root, Indent indent) { + if (opt.copyrightHeader != null) { + addLines(indent, opt.copyrightHeader!, linePrefix: '// '); + } + indent.writeln('// $generatedCodeWarning'); + indent.writeln('// $seeAlsoWarning'); + indent.writeln( + '// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import', + ); + indent.writeln('// ignore_for_file: avoid_relative_lib_imports'); } - /// Generates Dart files for testing with specified [DartOptions] - void generateTest(DartOptions languageOptions, Root root, StringSink sink) { - final String sourceOutPath = languageOptions.sourceOutPath ?? ''; - final String testOutPath = languageOptions.testOutPath ?? ''; - generateTestDart( - languageOptions, - root, - sink, - sourceOutPath: sourceOutPath, - testOutPath: testOutPath, + /// Writes file imports to sink. + void _writeTestImports(DartOptions opt, Root root, Indent indent) { + indent.writeln("import 'dart:async';"); + indent.writeln( + "import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;", ); + indent.writeln( + "import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;"); + indent.writeln("import 'package:flutter/services.dart';"); + indent.writeln("import 'package:flutter_test/flutter_test.dart';"); + indent.writeln(''); } } @@ -195,279 +723,6 @@ String _getMethodArgumentsSignature( }).join(', '); } -/// Writes the code for host [Api], [api]. -/// Example: -/// class FooCodec extends StandardMessageCodec {...} -/// -/// class Foo { -/// Foo(BinaryMessenger? binaryMessenger) {} -/// static const MessageCodec codec = FooCodec(); -/// Future add(int x, int y) async {...} -/// } -/// -/// Messages will be sent and received in a list. -/// -/// If the message recieved was succesful, -/// the result will be contained at the 0'th index. -/// -/// If the message was a failure, the list will contain 3 items: -/// a code, a message, and details in that order. -void _writeHostApi(DartOptions opt, Indent indent, Api api, Root root) { - assert(api.location == ApiLocation.host); - String codecName = _standardMessageCodec; - if (getCodecClasses(api, root).isNotEmpty) { - codecName = _getCodecName(api); - _writeCodec(indent, codecName, api, root); - } - indent.addln(''); - bool first = true; - addDocumentationComments(indent, api.documentationComments, _docCommentSpec); - indent.write('class ${api.name} '); - indent.scoped('{', '}', () { - indent.format(''' -/// Constructor for [${api.name}]. The [binaryMessenger] named argument is -/// available for dependency injection. If it is left null, the default -/// BinaryMessenger will be used which routes to the host platform. -${api.name}({BinaryMessenger? binaryMessenger}) -\t\t: _binaryMessenger = binaryMessenger; -final BinaryMessenger? _binaryMessenger; -'''); - - indent.writeln('static const MessageCodec codec = $codecName();'); - indent.addln(''); - for (final Method func in api.methods) { - if (!first) { - indent.writeln(''); - } else { - first = false; - } - addDocumentationComments( - indent, func.documentationComments, _docCommentSpec); - String argSignature = ''; - String sendArgument = 'null'; - if (func.arguments.isNotEmpty) { - String argNameFunc(int index, NamedType type) => - _getSafeArgumentName(index, type); - final Iterable argExpressions = - indexMap(func.arguments, (int index, NamedType type) { - final String name = argNameFunc(index, type); - if (root.enums.map((Enum e) => e.name).contains(type.type.baseName)) { - return '$name${type.type.isNullable ? '?' : ''}.index'; - } else { - return name; - } - }); - sendArgument = '[${argExpressions.join(', ')}]'; - argSignature = _getMethodArgumentsSignature(func, argNameFunc); - } - indent.write( - 'Future<${_addGenericTypesNullable(func.returnType)}> ${func.name}($argSignature) async ', - ); - indent.scoped('{', '}', () { - final String channelName = makeChannelName(api, func); - indent.writeln( - 'final BasicMessageChannel channel = BasicMessageChannel('); - indent.nest(2, () { - indent.writeln("'$channelName', codec,"); - indent.writeln('binaryMessenger: _binaryMessenger);'); - }); - final String returnType = _makeGenericTypeArguments(func.returnType); - final String genericCastCall = _makeGenericCastCall(func.returnType); - const String accessor = 'replyList[0]'; - // Avoid warnings from pointlessly casting to `Object?`. - final String nullablyTypedAccessor = - returnType == 'Object' ? accessor : '($accessor as $returnType?)'; - final String nullHandler = func.returnType.isNullable - ? (genericCastCall.isEmpty ? '' : '?') - : '!'; - final String returnStatement = func.returnType.isVoid - ? 'return;' - : 'return $nullablyTypedAccessor$nullHandler$genericCastCall;'; - indent.format(''' -final List? replyList = -\t\tawait channel.send($sendArgument) as List?; -if (replyList == null) { -\tthrow PlatformException( -\t\tcode: 'channel-error', -\t\tmessage: 'Unable to establish connection on channel.', -\t); -} else if (replyList.length > 1) { -\tthrow PlatformException( -\t\tcode: replyList[0]! as String, -\t\tmessage: replyList[1] as String?, -\t\tdetails: replyList[2], -\t);'''); - // On iOS we can return nil from functions to accommodate error - // handling. Returning a nil value and not returning an error is an - // exception. - if (!func.returnType.isNullable && !func.returnType.isVoid) { - indent.format(''' -} else if (replyList[0] == null) { -\tthrow PlatformException( -\t\tcode: 'null-error', -\t\tmessage: 'Host platform returned null value for non-null return value.', -\t);'''); - } - indent.format(''' -} else { -\t$returnStatement -}'''); - }); - } - }); -} - -/// Writes the code for host [Api], [api]. -/// Example: -/// class FooCodec extends StandardMessageCodec {...} -/// -/// abstract class Foo { -/// static const MessageCodec codec = FooCodec(); -/// int add(int x, int y); -/// static void setup(Foo api, {BinaryMessenger? binaryMessenger}) {...} -/// } -void _writeFlutterApi( - DartOptions opt, - Indent indent, - Api api, - Root root, { - String Function(Method)? channelNameFunc, - bool isMockHandler = false, -}) { - assert(api.location == ApiLocation.flutter); - final List customEnumNames = - root.enums.map((Enum x) => x.name).toList(); - String codecName = _standardMessageCodec; - if (getCodecClasses(api, root).isNotEmpty) { - codecName = _getCodecName(api); - _writeCodec(indent, codecName, api, root); - } - indent.addln(''); - addDocumentationComments(indent, api.documentationComments, _docCommentSpec); - - indent.write('abstract class ${api.name} '); - indent.scoped('{', '}', () { - indent.writeln('static const MessageCodec codec = $codecName();'); - indent.addln(''); - for (final Method func in api.methods) { - addDocumentationComments( - indent, func.documentationComments, _docCommentSpec); - - final bool isAsync = func.isAsynchronous; - final String returnType = isAsync - ? 'Future<${_addGenericTypesNullable(func.returnType)}>' - : _addGenericTypesNullable(func.returnType); - final String argSignature = _getMethodArgumentsSignature( - func, - _getArgumentName, - ); - indent.writeln('$returnType ${func.name}($argSignature);'); - indent.writeln(''); - } - indent.write( - 'static void setup(${api.name}? api, {BinaryMessenger? binaryMessenger}) '); - indent.scoped('{', '}', () { - for (final Method func in api.methods) { - indent.write(''); - indent.scoped('{', '}', () { - indent.writeln( - 'final BasicMessageChannel channel = BasicMessageChannel(', - ); - final String channelName = channelNameFunc == null - ? makeChannelName(api, func) - : channelNameFunc(func); - indent.nest(2, () { - indent.writeln("'$channelName', codec,"); - indent.writeln( - 'binaryMessenger: binaryMessenger);', - ); - }); - final String messageHandlerSetter = - isMockHandler ? 'setMockMessageHandler' : 'setMessageHandler'; - indent.write('if (api == null) '); - indent.scoped('{', '}', () { - indent.writeln('channel.$messageHandlerSetter(null);'); - }, addTrailingNewline: false); - indent.add(' else '); - indent.scoped('{', '}', () { - indent.write( - 'channel.$messageHandlerSetter((Object? message) async ', - ); - indent.scoped('{', '});', () { - final String returnType = - _addGenericTypesNullable(func.returnType); - final bool isAsync = func.isAsynchronous; - final String emptyReturnStatement = isMockHandler - ? 'return [];' - : func.returnType.isVoid - ? 'return;' - : 'return null;'; - String call; - if (func.arguments.isEmpty) { - indent.writeln('// ignore message'); - call = 'api.${func.name}()'; - } else { - indent.writeln('assert(message != null,'); - indent.writeln("'Argument for $channelName was null.');"); - const String argsArray = 'args'; - indent.writeln( - 'final List $argsArray = (message as List?)!;'); - String argNameFunc(int index, NamedType type) => - _getSafeArgumentName(index, type); - enumerate(func.arguments, (int count, NamedType arg) { - final String argType = _addGenericTypes(arg.type); - final String argName = argNameFunc(count, arg); - final String genericArgType = - _makeGenericTypeArguments(arg.type); - final String castCall = _makeGenericCastCall(arg.type); - - final String leftHandSide = 'final $argType? $argName'; - if (customEnumNames.contains(arg.type.baseName)) { - indent.writeln( - '$leftHandSide = $argsArray[$count] == null ? null : $argType.values[$argsArray[$count] as int];'); - } else { - indent.writeln( - '$leftHandSide = ($argsArray[$count] as $genericArgType?)${castCall.isEmpty ? '' : '?$castCall'};'); - } - if (!arg.type.isNullable) { - indent.writeln( - "assert($argName != null, 'Argument for $channelName was null, expected non-null $argType.');"); - } - }); - final Iterable argNames = - indexMap(func.arguments, (int index, NamedType field) { - final String name = _getSafeArgumentName(index, field); - return '$name${field.type.isNullable ? '' : '!'}'; - }); - call = 'api.${func.name}(${argNames.join(', ')})'; - } - if (func.returnType.isVoid) { - if (isAsync) { - indent.writeln('await $call;'); - } else { - indent.writeln('$call;'); - } - indent.writeln(emptyReturnStatement); - } else { - if (isAsync) { - indent.writeln('final $returnType output = await $call;'); - } else { - indent.writeln('final $returnType output = $call;'); - } - const String returnExpression = 'output'; - final String returnStatement = isMockHandler - ? 'return [$returnExpression];' - : 'return $returnExpression;'; - indent.writeln(returnStatement); - } - }); - }); - }); - } - }); - }); -} - /// Converts a [List] of [TypeDeclaration]s to a comma separated [String] to be /// used in Dart code. String _flattenTypeArguments(List args) { @@ -501,196 +756,6 @@ String _addGenericTypesNullable(TypeDeclaration type) { return type.isNullable ? '$genericdType?' : genericdType; } -/// Generates Dart source code for the given AST represented by [root], -/// outputting the code to [sink]. -void generateDart(DartOptions opt, Root root, StringSink sink) { - final List customClassNames = - root.classes.map((Class x) => x.name).toList(); - final List customEnumNames = - root.enums.map((Enum x) => x.name).toList(); - final Indent indent = Indent(sink); - - void writeHeader() { - if (opt.copyrightHeader != null) { - addLines(indent, opt.copyrightHeader!, linePrefix: '// '); - } - indent.writeln('// $generatedCodeWarning'); - indent.writeln('// $seeAlsoWarning'); - indent.writeln( - '// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import', - ); - } - - void writeEnums() { - for (final Enum anEnum in root.enums) { - indent.writeln(''); - addDocumentationComments( - indent, anEnum.documentationComments, _docCommentSpec); - indent.write('enum ${anEnum.name} '); - indent.scoped('{', '}', () { - for (final EnumMember member in anEnum.members) { - addDocumentationComments( - indent, member.documentationComments, _docCommentSpec); - indent.writeln('${member.name},'); - } - }); - } - } - - void writeImports() { - indent.writeln("import 'dart:async';"); - indent.writeln( - "import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;", - ); - indent.addln(''); - indent.writeln( - "import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;"); - indent.writeln("import 'package:flutter/services.dart';"); - } - - void writeDataClass(Class klass) { - void writeConstructor() { - indent.write(klass.name); - indent.scoped('({', '});', () { - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - final String required = field.type.isNullable ? '' : 'required '; - indent.writeln('${required}this.${field.name},'); - } - }); - } - - void writeEncode() { - indent.write('Object encode() '); - indent.scoped('{', '}', () { - indent.write( - 'return ', - ); - indent.scoped('[', '];', () { - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - final String conditional = field.type.isNullable ? '?' : ''; - if (customClassNames.contains(field.type.baseName)) { - indent.writeln( - '${field.name}$conditional.encode(),', - ); - } else if (customEnumNames.contains(field.type.baseName)) { - indent.writeln( - '${field.name}$conditional.index,', - ); - } else { - indent.writeln('${field.name},'); - } - } - }); - }); - } - - void writeDecode() { - void writeValueDecode(NamedType field, int index) { - final String resultAt = 'result[$index]'; - if (customClassNames.contains(field.type.baseName)) { - final String nonNullValue = - '${field.type.baseName}.decode($resultAt! as List)'; - indent.format( - field.type.isNullable - ? ''' -$resultAt != null -\t\t? $nonNullValue -\t\t: null''' - : nonNullValue, - leadingSpace: false, - trailingNewline: false); - } else if (customEnumNames.contains(field.type.baseName)) { - final String nonNullValue = - '${field.type.baseName}.values[$resultAt! as int]'; - indent.format( - field.type.isNullable - ? ''' -$resultAt != null -\t\t? $nonNullValue -\t\t: null''' - : nonNullValue, - leadingSpace: false, - trailingNewline: false); - } else if (field.type.typeArguments.isNotEmpty) { - final String genericType = _makeGenericTypeArguments(field.type); - final String castCall = _makeGenericCastCall(field.type); - final String castCallPrefix = field.type.isNullable ? '?' : '!'; - indent.add( - '($resultAt as $genericType?)$castCallPrefix$castCall', - ); - } else { - final String genericdType = _addGenericTypesNullable(field.type); - if (field.type.isNullable) { - indent.add( - '$resultAt as $genericdType', - ); - } else { - indent.add( - '$resultAt! as $genericdType', - ); - } - } - } - - indent.write( - 'static ${klass.name} decode(Object result) ', - ); - indent.scoped('{', '}', () { - indent.writeln('result as List;'); - indent.write('return ${klass.name}'); - indent.scoped('(', ');', () { - enumerate(getFieldsInSerializationOrder(klass), - (int index, final NamedType field) { - indent.write('${field.name}: '); - writeValueDecode(field, index); - indent.addln(','); - }); - }); - }); - } - - addDocumentationComments( - indent, klass.documentationComments, _docCommentSpec); - - indent.write('class ${klass.name} '); - indent.scoped('{', '}', () { - writeConstructor(); - indent.addln(''); - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - addDocumentationComments( - indent, field.documentationComments, _docCommentSpec); - - final String datatype = _addGenericTypesNullable(field.type); - indent.writeln('$datatype ${field.name};'); - indent.writeln(''); - } - writeEncode(); - indent.writeln(''); - writeDecode(); - }); - } - - void writeApi(Api api) { - if (api.location == ApiLocation.host) { - _writeHostApi(opt, indent, api, root); - } else if (api.location == ApiLocation.flutter) { - _writeFlutterApi(opt, indent, api, root); - } - } - - writeHeader(); - writeImports(); - writeEnums(); - for (final Class klass in root.classes) { - indent.writeln(''); - writeDataClass(klass); - } - for (final Api api in root.apis) { - indent.writeln(''); - writeApi(api); - } -} - /// Crawls up the path of [dartFilePath] until it finds a pubspec.yaml in a /// parent directory and returns its path. String? _findPubspecPath(String dartFilePath) { @@ -739,72 +804,3 @@ String _posixify(String inputPath) { final path.Context context = path.Context(style: path.Style.posix); return context.fromUri(path.toUri(path.absolute(inputPath))); } - -/// Generates Dart source code for test support libraries based on the given AST -/// represented by [root], outputting the code to [sink]. [sourceOutPath] is the -/// path of the generated dart code to be tested. [testOutPath] is where the -/// test code will be generated. -void generateTestDart( - DartOptions opt, - Root root, - StringSink sink, { - required String sourceOutPath, - required String testOutPath, -}) { - final Indent indent = Indent(sink); - if (opt.copyrightHeader != null) { - addLines(indent, opt.copyrightHeader!, linePrefix: '// '); - } - indent.writeln('// $generatedCodeWarning'); - indent.writeln('// $seeAlsoWarning'); - indent.writeln( - '// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import', - ); - indent.writeln('// ignore_for_file: avoid_relative_lib_imports'); - indent.writeln("import 'dart:async';"); - indent.writeln( - "import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;", - ); - indent.writeln( - "import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;"); - indent.writeln("import 'package:flutter/services.dart';"); - indent.writeln("import 'package:flutter_test/flutter_test.dart';"); - indent.writeln(''); - final String relativeDartPath = - path.Context(style: path.Style.posix).relative( - _posixify(sourceOutPath), - from: _posixify(path.dirname(testOutPath)), - ); - late final String? packageName = _deducePackageName(sourceOutPath); - if (!relativeDartPath.contains('/lib/') || packageName == null) { - // If we can't figure out the package name or the relative path doesn't - // include a 'lib' directory, try relative path import which only works in - // certain (older) versions of Dart. - // TODO(gaaclarke): We should add a command-line parameter to override this import. - indent.writeln( - "import '${_escapeForDartSingleQuotedString(relativeDartPath)}';"); - } else { - final String path = relativeDartPath.replaceFirst(RegExp(r'^.*/lib/'), ''); - indent.writeln("import 'package:$packageName/$path';"); - } - for (final Api api in root.apis) { - if (api.location == ApiLocation.host && api.dartHostTestHandler != null) { - final Api mockApi = Api( - name: api.dartHostTestHandler!, - methods: api.methods, - location: ApiLocation.flutter, - dartHostTestHandler: api.dartHostTestHandler, - documentationComments: api.documentationComments, - ); - indent.writeln(''); - _writeFlutterApi( - opt, - indent, - mockApi, - root, - channelNameFunc: (Method func) => makeChannelName(api, func), - isMockHandler: true, - ); - } - } -} diff --git a/packages/pigeon/lib/generator.dart b/packages/pigeon/lib/generator.dart index 02667e4a5c9..c730f92a714 100644 --- a/packages/pigeon/lib/generator.dart +++ b/packages/pigeon/lib/generator.dart @@ -3,11 +3,119 @@ // found in the LICENSE file. import 'ast.dart'; +import 'generator_tools.dart'; -/// A superclass of generator classes. +/// An abstract base class of generators. /// /// This provides the structure that is common across generators for different languages. abstract class Generator { - /// Generates files for specified language with specified [languageOptions] - void generate(T languageOptions, Root root, StringSink sink); + /// Constructor. + const Generator(); + + /// Generates files for specified language with specified [generatorOptions] + void generate(T generatorOptions, Root root, StringSink sink); +} + +/// An abstract base class that enforces code generation across platforms. +abstract class StructuredGenerator extends Generator { + /// Constructor. + const StructuredGenerator(); + + @override + void generate( + T generatorOptions, + Root root, + StringSink sink, + ) { + final Indent indent = Indent(sink); + + writeFilePrologue(generatorOptions, root, indent); + + writeFileImports(generatorOptions, root, indent); + + writeOpenNamespace(generatorOptions, root, indent); + + writeGeneralUtilities(generatorOptions, root, indent); + + writeEnums(generatorOptions, root, indent); + + writeDataClasses(generatorOptions, root, indent); + + writeApis(generatorOptions, root, indent); + + writeCloseNamespace(generatorOptions, root, indent); + } + + /// Adds specified headers to [indent]. + void writeFilePrologue(T generatorOptions, Root root, Indent indent); + + /// Writes specified imports to [indent]. + void writeFileImports(T generatorOptions, Root root, Indent indent); + + /// Writes code to [indent] that opens file namespace if needed. + /// + /// This method is not required, and does not need to be overridden. + void writeOpenNamespace(T generatorOptions, Root root, Indent indent) {} + + /// Writes code to [indent] that closes file namespace if needed. + /// + /// This method is not required, and does not need to be overridden. + void writeCloseNamespace(T generatorOptions, Root root, Indent indent) {} + + /// Writes any necessary helper utilities to [indent] if needed. + /// + /// This method is not required, and does not need to be overridden. + void writeGeneralUtilities(T generatorOptions, Root root, Indent indent) {} + + /// Writes all enums to [indent]. + /// + /// Can be overridden to add extra code before/after enums. + void writeEnums(T generatorOptions, Root root, Indent indent) { + for (final Enum anEnum in root.enums) { + writeEnum(generatorOptions, root, indent, anEnum); + } + } + + /// Writes a single Enum to [indent]. This is needed in most generators. + void writeEnum(T generatorOptions, Root root, Indent indent, Enum anEnum) {} + + /// Writes all data classes to [indent]. + /// + /// Can be overridden to add extra code before/after apis. + void writeDataClasses(T generatorOptions, Root root, Indent indent) { + for (final Class klass in root.classes) { + writeDataClass(generatorOptions, root, indent, klass); + } + } + + /// Writes a single data class to [indent]. + void writeDataClass( + T generatorOptions, Root root, Indent indent, Class klass); + + /// Writes a single class encode method to [indent]. + void writeClassEncode(T generatorOptions, Root root, Indent indent, + Class klass, Set customClassNames, Set customEnumNames) {} + + /// Writes a single class decode method to [indent]. + void writeClassDecode(T generatorOptions, Root root, Indent indent, + Class klass, Set customClassNames, Set customEnumNames) {} + + /// Writes all apis to [indent]. + /// + /// Can be overridden to add extra code before/after classes. + void writeApis(T generatorOptions, Root root, Indent indent) { + for (final Api api in root.apis) { + if (api.location == ApiLocation.host) { + writeHostApi(generatorOptions, root, indent, api); + } else if (api.location == ApiLocation.flutter) { + writeFlutterApi(generatorOptions, root, indent, api); + } + } + } + + /// Writes a single Flutter Api to [indent]. + void writeFlutterApi(T generatorOptions, Root root, Indent indent, Api api); + + /// Writes a single Host Api to [indent]. + void writeHostApi(T generatorOptions, Root root, Indent indent, Api api); } diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index 4355096e001..3dc1df4d513 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/generator_tools.dart @@ -9,7 +9,7 @@ import 'dart:mirrors'; import 'ast.dart'; /// The current version of pigeon. This must match the version in pubspec.yaml. -const String pigeonVersion = '5.0.1'; +const String pigeonVersion = '6.0.0'; /// Read all the content from [stdin] to a String. String readStdin() { @@ -510,7 +510,7 @@ enum FileType { na, } -/// Options for [Generator]'s that have multiple output file types. +/// Options for [Generator]s that have multiple output file types. /// /// Specifies which file to write as well as wraps all language options. class OutputFileOptions { diff --git a/packages/pigeon/lib/java_generator.dart b/packages/pigeon/lib/java_generator.dart index daca63876d2..e6e7f8c921b 100644 --- a/packages/pigeon/lib/java_generator.dart +++ b/packages/pigeon/lib/java_generator.dart @@ -86,94 +86,433 @@ class JavaOptions { } /// Class that manages all Java code generation. -class JavaGenerator extends Generator { +class JavaGenerator extends StructuredGenerator { /// Instantiates a Java Generator. - JavaGenerator(); + const JavaGenerator(); - /// Generates Java files with specified [JavaOptions] @override - void generate(JavaOptions languageOptions, Root root, StringSink sink, - {FileType fileType = FileType.na}) { - assert(fileType == FileType.na); - generateJava(languageOptions, root, sink); + void writeFilePrologue( + JavaOptions generatorOptions, Root root, Indent indent) { + if (generatorOptions.copyrightHeader != null) { + addLines(indent, generatorOptions.copyrightHeader!, linePrefix: '// '); + } + indent.writeln('// $generatedCodeWarning'); + indent.writeln('// $seeAlsoWarning'); + indent.addln(''); } -} -/// Calculates the name of the codec that will be generated for [api]. -String _getCodecName(Api api) => '${api.name}Codec'; + @override + void writeFileImports( + JavaOptions generatorOptions, Root root, Indent indent) { + if (generatorOptions.package != null) { + indent.writeln('package ${generatorOptions.package};'); + } + indent.writeln('import android.util.Log;'); + indent.writeln('import androidx.annotation.NonNull;'); + indent.writeln('import androidx.annotation.Nullable;'); + indent.writeln('import io.flutter.plugin.common.BasicMessageChannel;'); + indent.writeln('import io.flutter.plugin.common.BinaryMessenger;'); + indent.writeln('import io.flutter.plugin.common.MessageCodec;'); + indent.writeln('import io.flutter.plugin.common.StandardMessageCodec;'); + indent.writeln('import java.io.ByteArrayOutputStream;'); + indent.writeln('import java.nio.ByteBuffer;'); + indent.writeln('import java.util.Arrays;'); + indent.writeln('import java.util.ArrayList;'); + indent.writeln('import java.util.Collections;'); + indent.writeln('import java.util.List;'); + indent.writeln('import java.util.Map;'); + indent.writeln('import java.util.HashMap;'); + indent.addln(''); + } -/// Converts an expression that evaluates to an nullable int to an expression -/// that evaluates to a nullable enum. -String _intToEnum(String expression, String enumName) => - '$expression == null ? null : $enumName.values()[(int)$expression]'; + @override + void writeOpenNamespace( + JavaOptions generatorOptions, Root root, Indent indent) { + indent.writeln( + '$_docCommentPrefix Generated class from Pigeon.$_docCommentSuffix'); + indent.writeln( + '@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"})'); + if (generatorOptions.useGeneratedAnnotation ?? false) { + indent.writeln('@javax.annotation.Generated("dev.flutter.pigeon")'); + } + indent.writeln('public class ${generatorOptions.className!} {'); + indent.inc(); + } -/// Writes the codec class that will be used by [api]. -/// Example: -/// private static class FooCodec extends StandardMessageCodec {...} -void _writeCodec(Indent indent, Api api, Root root) { - assert(getCodecClasses(api, root).isNotEmpty); - final Iterable codecClasses = getCodecClasses(api, root); - final String codecName = _getCodecName(api); - indent - .write('private static class $codecName extends $_standardMessageCodec '); - indent.scoped('{', '}', () { - indent - .writeln('public static final $codecName INSTANCE = new $codecName();'); - indent.writeln('private $codecName() {}'); - indent.writeln('@Override'); - indent.write( - 'protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) '); + @override + void writeEnum( + JavaOptions generatorOptions, Root root, Indent indent, Enum anEnum) { + String camelToSnake(String camelCase) { + final RegExp regex = RegExp('([a-z])([A-Z]+)'); + return camelCase + .replaceAllMapped(regex, (Match m) => '${m[1]}_${m[2]}') + .toUpperCase(); + } + + indent.writeln(''); + addDocumentationComments( + indent, anEnum.documentationComments, _docCommentSpec); + + indent.write('public enum ${anEnum.name} '); indent.scoped('{', '}', () { - indent.write('switch (type) '); + enumerate(anEnum.members, (int index, final EnumMember member) { + addDocumentationComments( + indent, member.documentationComments, _docCommentSpec); + indent.writeln( + '${camelToSnake(member.name)}($index)${index == anEnum.members.length - 1 ? ';' : ','}'); + }); + indent.writeln(''); + indent.writeln('private final int index;'); + indent.write('private ${anEnum.name}(final int index) '); indent.scoped('{', '}', () { - for (final EnumeratedClass customClass in codecClasses) { - indent.write('case (byte)${customClass.enumeration}: '); - indent.writeScoped('', '', () { - indent.writeln( - 'return ${customClass.name}.fromList((ArrayList) readValue(buffer));'); - }); - } - indent.write('default:'); - indent.writeScoped('', '', () { - indent.writeln('return super.readValueOfType(type, buffer);'); - }); + indent.writeln('this.index = index;'); }); }); - indent.writeln('@Override'); - indent.write( - 'protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) '); - indent.writeScoped('{', '}', () { - for (final EnumeratedClass customClass in codecClasses) { - indent.write('if (value instanceof ${customClass.name}) '); - indent.scoped('{', '} else ', () { - indent.writeln('stream.write(${customClass.enumeration});'); + } + + @override + void writeDataClass( + JavaOptions generatorOptions, Root root, Indent indent, Class klass) { + final Set customClassNames = + root.classes.map((Class x) => x.name).toSet(); + final Set customEnumNames = + root.enums.map((Enum x) => x.name).toSet(); + + const List generatedMessages = [ + ' Generated class from Pigeon that represents data sent in messages.' + ]; + indent.addln(''); + addDocumentationComments( + indent, klass.documentationComments, _docCommentSpec, + generatorComments: generatedMessages); + + indent.write('public static class ${klass.name} '); + indent.scoped('{', '}', () { + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + _writeClassField(generatorOptions, root, indent, field); + indent.addln(''); + } + + if (getFieldsInSerializationOrder(klass) + .map((NamedType e) => !e.type.isNullable) + .any((bool e) => e)) { + indent.writeln( + '${_docCommentPrefix}Constructor is private to enforce null safety; use Builder.$_docCommentSuffix'); + indent.writeln('private ${klass.name}() {}'); + } + + _writeClassBuilder(generatorOptions, root, indent, klass); + writeClassEncode(generatorOptions, root, indent, klass, customClassNames, + customEnumNames); + writeClassDecode(generatorOptions, root, indent, klass, customClassNames, + customEnumNames); + }); + } + + void _writeClassField( + JavaOptions generatorOptions, Root root, Indent indent, NamedType field) { + final HostDatatype hostDatatype = getFieldHostDatatype(field, root.classes, + root.enums, (TypeDeclaration x) => _javaTypeForBuiltinDartType(x)); + final String nullability = field.type.isNullable ? '@Nullable' : '@NonNull'; + addDocumentationComments( + indent, field.documentationComments, _docCommentSpec); + + indent.writeln( + 'private $nullability ${hostDatatype.datatype} ${field.name};'); + indent.writeln( + 'public $nullability ${hostDatatype.datatype} ${_makeGetter(field)}() { return ${field.name}; }'); + indent.writeScoped( + 'public void ${_makeSetter(field)}($nullability ${hostDatatype.datatype} setterArg) {', + '}', () { + if (!field.type.isNullable) { + indent.writeScoped('if (setterArg == null) {', '}', () { indent.writeln( - 'writeValue(stream, ((${customClass.name}) value).toList());'); + 'throw new IllegalStateException("Nonnull field \\"${field.name}\\" is null.");'); }); } + indent.writeln('this.${field.name} = setterArg;'); + }); + } + + void _writeClassBuilder( + JavaOptions generatorOptions, + Root root, + Indent indent, + Class klass, + ) { + indent.write('public static final class Builder '); + indent.scoped('{', '}', () { + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + final HostDatatype hostDatatype = getFieldHostDatatype( + field, + root.classes, + root.enums, + (TypeDeclaration x) => _javaTypeForBuiltinDartType(x)); + final String nullability = + field.type.isNullable ? '@Nullable' : '@NonNull'; + indent.writeln( + 'private @Nullable ${hostDatatype.datatype} ${field.name};'); + indent.writeScoped( + 'public @NonNull Builder ${_makeSetter(field)}($nullability ${hostDatatype.datatype} setterArg) {', + '}', () { + indent.writeln('this.${field.name} = setterArg;'); + indent.writeln('return this;'); + }); + } + indent.write('public @NonNull ${klass.name} build() '); indent.scoped('{', '}', () { - indent.writeln('super.writeValue(stream, value);'); + const String returnVal = 'pigeonReturn'; + indent.writeln('${klass.name} $returnVal = new ${klass.name}();'); + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + indent.writeln('$returnVal.${_makeSetter(field)}(${field.name});'); + } + indent.writeln('return $returnVal;'); }); }); - }); -} + } -/// Write the java code that represents a host [Api], [api]. -/// Example: -/// public interface Foo { -/// int add(int x, int y); -/// static void setup(BinaryMessenger binaryMessenger, Foo api) {...} -/// } -void _writeHostApi(Indent indent, Api api, Root root) { - assert(api.location == ApiLocation.host); + @override + void writeClassEncode( + JavaOptions generatorOptions, + Root root, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) { + indent.write('@NonNull ArrayList toList() '); + indent.scoped('{', '}', () { + indent.writeln( + 'ArrayList toListResult = new ArrayList(${klass.fields.length});'); + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + final HostDatatype hostDatatype = getFieldHostDatatype( + field, + root.classes, + root.enums, + (TypeDeclaration x) => _javaTypeForBuiltinDartType(x)); + String toWriteValue = ''; + final String fieldName = field.name; + if (!hostDatatype.isBuiltin && + customClassNames.contains(field.type.baseName)) { + toWriteValue = '($fieldName == null) ? null : $fieldName.toList()'; + } else if (!hostDatatype.isBuiltin && + customEnumNames.contains(field.type.baseName)) { + toWriteValue = '$fieldName == null ? null : $fieldName.index'; + } else { + toWriteValue = field.name; + } + indent.writeln('toListResult.add($toWriteValue);'); + } + indent.writeln('return toListResult;'); + }); + } + + @override + void writeClassDecode( + JavaOptions generatorOptions, + Root root, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) { + indent.write( + 'static @NonNull ${klass.name} fromList(@NonNull ArrayList list) '); + indent.scoped('{', '}', () { + const String result = 'pigeonResult'; + indent.writeln('${klass.name} $result = new ${klass.name}();'); + enumerate(getFieldsInSerializationOrder(klass), + (int index, final NamedType field) { + final String fieldVariable = field.name; + final String setter = _makeSetter(field); + indent.writeln('Object $fieldVariable = list.get($index);'); + if (customEnumNames.contains(field.type.baseName)) { + indent.writeln( + '$result.$setter(${_intToEnum(fieldVariable, field.type.baseName)});'); + } else { + indent.writeln( + '$result.$setter(${_castObject(field, root.classes, root.enums, fieldVariable)});'); + } + }); + indent.writeln('return $result;'); + }); + } + + /// Writes the code for a flutter [Api], [api]. + /// Example: + /// public static class Foo { + /// public Foo(BinaryMessenger argBinaryMessenger) {...} + /// public interface Reply { + /// void reply(T reply); + /// } + /// public int add(int x, int y, Reply callback) {...} + /// } + @override + void writeFlutterApi( + JavaOptions generatorOptions, + Root root, + Indent indent, + Api api, + ) { + assert(api.location == ApiLocation.flutter); + if (getCodecClasses(api, root).isNotEmpty) { + _writeCodec(indent, api, root); + } + const List generatedMessages = [ + ' Generated class from Pigeon that represents Flutter messages that can be called from Java.' + ]; + addDocumentationComments(indent, api.documentationComments, _docCommentSpec, + generatorComments: generatedMessages); + + indent.write('public static class ${api.name} '); + indent.scoped('{', '}', () { + indent.writeln('private final BinaryMessenger binaryMessenger;'); + indent.write('public ${api.name}(BinaryMessenger argBinaryMessenger)'); + indent.scoped('{', '}', () { + indent.writeln('this.binaryMessenger = argBinaryMessenger;'); + }); + indent.write('public interface Reply '); + indent.scoped('{', '}', () { + indent.writeln('void reply(T reply);'); + }); + final String codecName = _getCodecName(api); + indent.writeln('/** The codec used by ${api.name}. */'); + indent.write('static MessageCodec getCodec() '); + indent.scoped('{', '}', () { + indent.write('return '); + if (getCodecClasses(api, root).isNotEmpty) { + indent.writeln('$codecName.INSTANCE;'); + } else { + indent.writeln('new $_standardMessageCodec();'); + } + }); + + for (final Method func in api.methods) { + final String channelName = makeChannelName(api, func); + final String returnType = func.returnType.isVoid + ? 'Void' + : _javaTypeForDartType(func.returnType); + String sendArgument; + addDocumentationComments( + indent, func.documentationComments, _docCommentSpec); + if (func.arguments.isEmpty) { + indent + .write('public void ${func.name}(Reply<$returnType> callback) '); + sendArgument = 'null'; + } else { + final Iterable argTypes = func.arguments + .map((NamedType e) => _nullsafeJavaTypeForDartType(e.type)); + final Iterable argNames = + indexMap(func.arguments, _getSafeArgumentName); + if (func.arguments.length == 1) { + sendArgument = + 'new ArrayList(Collections.singletonList(${argNames.first}))'; + } else { + sendArgument = + 'new ArrayList(Arrays.asList(${argNames.join(', ')}))'; + } + final String argsSignature = + map2(argTypes, argNames, (String x, String y) => '$x $y') + .join(', '); + indent.write( + 'public void ${func.name}($argsSignature, Reply<$returnType> callback) '); + } + indent.scoped('{', '}', () { + const String channel = 'channel'; + indent.writeln('BasicMessageChannel $channel ='); + indent.inc(); + indent.inc(); + indent.writeln( + 'new BasicMessageChannel<>(binaryMessenger, "$channelName", getCodec());'); + indent.dec(); + indent.dec(); + indent.write('$channel.send($sendArgument, channelReply -> '); + indent.scoped('{', '});', () { + if (func.returnType.isVoid) { + indent.writeln('callback.reply(null);'); + } else { + const String output = 'output'; + indent.writeln('@SuppressWarnings("ConstantConditions")'); + if (func.returnType.baseName == 'int') { + indent.writeln( + '$returnType $output = channelReply == null ? null : ((Number)channelReply).longValue();'); + } else { + indent.writeln( + '$returnType $output = ($returnType)channelReply;'); + } + indent.writeln('callback.reply($output);'); + } + }); + }); + } + }); + } + + @override + void writeApis(JavaOptions generatorOptions, Root root, Indent indent) { + if (root.apis.any((Api api) => + api.location == ApiLocation.host && + api.methods.any((Method it) => it.isAsynchronous))) { + indent.addln(''); + _writeResultInterface(indent); + } + super.writeApis(generatorOptions, root, indent); + } - bool isEnum(TypeDeclaration type) => - root.enums.map((Enum e) => e.name).contains(type.baseName); + /// Write the java code that represents a host [Api], [api]. + /// Example: + /// public interface Foo { + /// int add(int x, int y); + /// static void setup(BinaryMessenger binaryMessenger, Foo api) {...} + /// } + @override + void writeHostApi( + JavaOptions generatorOptions, Root root, Indent indent, Api api) { + assert(api.location == ApiLocation.host); + if (getCodecClasses(api, root).isNotEmpty) { + _writeCodec(indent, api, root); + } + const List generatedMessages = [ + ' Generated interface from Pigeon that represents a handler of messages from Flutter.' + ]; + addDocumentationComments(indent, api.documentationComments, _docCommentSpec, + generatorComments: generatedMessages); + + indent.write('public interface ${api.name} '); + indent.scoped('{', '}', () { + for (final Method method in api.methods) { + _writeInterfaceMethod(generatorOptions, root, indent, api, method); + } + indent.addln(''); + final String codecName = _getCodecName(api); + indent.writeln('/** The codec used by ${api.name}. */'); + indent.write('static MessageCodec getCodec() '); + indent.scoped('{', '}', () { + indent.write('return '); + if (getCodecClasses(api, root).isNotEmpty) { + indent.write('$codecName.INSTANCE;'); + } else { + indent.write('new $_standardMessageCodec();'); + } + }); + + indent.writeln( + '${_docCommentPrefix}Sets up an instance of `${api.name}` to handle messages through the `binaryMessenger`.$_docCommentSuffix'); + indent.write( + 'static void setup(BinaryMessenger binaryMessenger, ${api.name} api) '); + indent.scoped('{', '}', () { + for (final Method method in api.methods) { + _writeMethodSetup(generatorOptions, root, indent, api, method); + } + }); + }); + } /// Write a method in the interface. /// Example: /// int add(int x, int y); - void writeInterfaceMethod(final Method method) { + void _writeInterfaceMethod(JavaOptions generatorOptions, Root root, + Indent indent, Api api, final Method method) { final String returnType = method.isAsynchronous ? 'void' : _nullsafeJavaTypeForDartType(method.returnType); @@ -203,7 +542,8 @@ void _writeHostApi(Indent indent, Api api, Root root) { /// Write a static setup function in the interface. /// Example: /// static void setup(BinaryMessenger binaryMessenger, Foo api) {...} - void writeMethodSetup(final Method method) { + void _writeMethodSetup(JavaOptions generatorOptions, Root root, Indent indent, + Api api, final Method method) { final String channelName = makeChannelName(api, method); indent.write(''); indent.scoped('{', '}', () { @@ -252,7 +592,7 @@ void _writeHostApi(Indent indent, Api api, Root root) { ? '($argName == null) ? null : $argName.longValue()' : argName; String accessor = 'args.get($index)'; - if (isEnum(arg.type)) { + if (isEnum(root, arg.type)) { accessor = _intToEnum(accessor, arg.type.baseName); } else if (argType != 'Object') { accessor = '($argType)$accessor'; @@ -319,38 +659,99 @@ Result<$returnType> $resultName = new Result<$returnType>() { }); } - const List generatedMessages = [ - ' Generated interface from Pigeon that represents a handler of messages from Flutter.' - ]; - addDocumentationComments(indent, api.documentationComments, _docCommentSpec, - generatorComments: generatedMessages); - - indent.write('public interface ${api.name} '); - indent.scoped('{', '}', () { - api.methods.forEach(writeInterfaceMethod); - indent.addln(''); + /// Writes the codec class that will be used by [api]. + /// Example: + /// private static class FooCodec extends StandardMessageCodec {...} + void _writeCodec(Indent indent, Api api, Root root) { + assert(getCodecClasses(api, root).isNotEmpty); + final Iterable codecClasses = getCodecClasses(api, root); final String codecName = _getCodecName(api); - indent.writeln('/** The codec used by ${api.name}. */'); - indent.write('static MessageCodec getCodec() '); + indent.write( + 'private static class $codecName extends $_standardMessageCodec '); indent.scoped('{', '}', () { - indent.write('return '); - if (getCodecClasses(api, root).isNotEmpty) { - indent.write('$codecName.INSTANCE;'); - } else { - indent.write('new $_standardMessageCodec();'); - } + indent.writeln( + 'public static final $codecName INSTANCE = new $codecName();'); + indent.writeln('private $codecName() {}'); + indent.writeln('@Override'); + indent.write( + 'protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) '); + indent.scoped('{', '}', () { + indent.write('switch (type) '); + indent.scoped('{', '}', () { + for (final EnumeratedClass customClass in codecClasses) { + indent.write('case (byte)${customClass.enumeration}: '); + indent.writeScoped('', '', () { + indent.writeln( + 'return ${customClass.name}.fromList((ArrayList) readValue(buffer));'); + }); + } + indent.write('default:'); + indent.writeScoped('', '', () { + indent.writeln('return super.readValueOfType(type, buffer);'); + }); + }); + }); + indent.writeln('@Override'); + indent.write( + 'protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) '); + indent.writeScoped('{', '}', () { + for (final EnumeratedClass customClass in codecClasses) { + indent.write('if (value instanceof ${customClass.name}) '); + indent.scoped('{', '} else ', () { + indent.writeln('stream.write(${customClass.enumeration});'); + indent.writeln( + 'writeValue(stream, ((${customClass.name}) value).toList());'); + }); + } + indent.scoped('{', '}', () { + indent.writeln('super.writeValue(stream, value);'); + }); + }); }); + indent.addln(''); + } - indent.writeln( - '${_docCommentPrefix}Sets up an instance of `${api.name}` to handle messages through the `binaryMessenger`.$_docCommentSuffix'); - indent.write( - 'static void setup(BinaryMessenger binaryMessenger, ${api.name} api) '); + void _writeResultInterface(Indent indent) { + indent.write('public interface Result '); indent.scoped('{', '}', () { - api.methods.forEach(writeMethodSetup); + indent.writeln('void success(T result);'); + indent.writeln('void error(Throwable error);'); }); - }); + } + + void _writeWrapError(Indent indent) { + indent.format(''' +@NonNull private static ArrayList wrapError(@NonNull Throwable exception) { +\tArrayList errorList = new ArrayList<>(3); +\terrorList.add(exception.toString()); +\terrorList.add(exception.getClass().getSimpleName()); +\terrorList.add("Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); +\treturn errorList; +}'''); + } + + @override + void writeGeneralUtilities( + JavaOptions generatorOptions, Root root, Indent indent) { + _writeWrapError(indent); + } + + @override + void writeCloseNamespace( + JavaOptions generatorOptions, Root root, Indent indent) { + indent.dec(); + indent.addln('}'); + } } +/// Calculates the name of the codec that will be generated for [api]. +String _getCodecName(Api api) => '${api.name}Codec'; + +/// Converts an expression that evaluates to an nullable int to an expression +/// that evaluates to a nullable enum. +String _intToEnum(String expression, String enumName) => + '$expression == null ? null : $enumName.values()[(int)$expression]'; + String _getArgumentName(int count, NamedType argument) => argument.name.isEmpty ? 'arg$count' : argument.name; @@ -358,106 +759,6 @@ String _getArgumentName(int count, NamedType argument) => String _getSafeArgumentName(int count, NamedType argument) => '${_getArgumentName(count, argument)}Arg'; -/// Writes the code for a flutter [Api], [api]. -/// Example: -/// public static class Foo { -/// public Foo(BinaryMessenger argBinaryMessenger) {...} -/// public interface Reply { -/// void reply(T reply); -/// } -/// public int add(int x, int y, Reply callback) {...} -/// } -void _writeFlutterApi(Indent indent, Api api, Root root) { - assert(api.location == ApiLocation.flutter); - const List generatedMessages = [ - ' Generated class from Pigeon that represents Flutter messages that can be called from Java.' - ]; - addDocumentationComments(indent, api.documentationComments, _docCommentSpec, - generatorComments: generatedMessages); - - indent.write('public static class ${api.name} '); - indent.scoped('{', '}', () { - indent.writeln('private final BinaryMessenger binaryMessenger;'); - indent.write('public ${api.name}(BinaryMessenger argBinaryMessenger)'); - indent.scoped('{', '}', () { - indent.writeln('this.binaryMessenger = argBinaryMessenger;'); - }); - indent.write('public interface Reply '); - indent.scoped('{', '}', () { - indent.writeln('void reply(T reply);'); - }); - final String codecName = _getCodecName(api); - indent.writeln('/** The codec used by ${api.name}. */'); - indent.write('static MessageCodec getCodec() '); - indent.scoped('{', '}', () { - indent.write('return '); - if (getCodecClasses(api, root).isNotEmpty) { - indent.writeln('$codecName.INSTANCE;'); - } else { - indent.writeln('new $_standardMessageCodec();'); - } - }); - - for (final Method func in api.methods) { - final String channelName = makeChannelName(api, func); - final String returnType = func.returnType.isVoid - ? 'Void' - : _javaTypeForDartType(func.returnType); - String sendArgument; - addDocumentationComments( - indent, func.documentationComments, _docCommentSpec); - if (func.arguments.isEmpty) { - indent.write('public void ${func.name}(Reply<$returnType> callback) '); - sendArgument = 'null'; - } else { - final Iterable argTypes = func.arguments - .map((NamedType e) => _nullsafeJavaTypeForDartType(e.type)); - final Iterable argNames = - indexMap(func.arguments, _getSafeArgumentName); - if (func.arguments.length == 1) { - sendArgument = - 'new ArrayList(Collections.singletonList(${argNames.first}))'; - } else { - sendArgument = - 'new ArrayList(Arrays.asList(${argNames.join(', ')}))'; - } - final String argsSignature = - map2(argTypes, argNames, (String x, String y) => '$x $y') - .join(', '); - indent.write( - 'public void ${func.name}($argsSignature, Reply<$returnType> callback) '); - } - indent.scoped('{', '}', () { - const String channel = 'channel'; - indent.writeln('BasicMessageChannel $channel ='); - indent.inc(); - indent.inc(); - indent.writeln( - 'new BasicMessageChannel<>(binaryMessenger, "$channelName", getCodec());'); - indent.dec(); - indent.dec(); - indent.write('$channel.send($sendArgument, channelReply -> '); - indent.scoped('{', '});', () { - if (func.returnType.isVoid) { - indent.writeln('callback.reply(null);'); - } else { - const String output = 'output'; - indent.writeln('@SuppressWarnings("ConstantConditions")'); - if (func.returnType.baseName == 'int') { - indent.writeln( - '$returnType $output = channelReply == null ? null : ((Number)channelReply).longValue();'); - } else { - indent - .writeln('$returnType $output = ($returnType)channelReply;'); - } - indent.writeln('callback.reply($output);'); - } - }); - }); - } - }); -} - String _makeGetter(NamedType field) { final String uppercased = field.name.substring(0, 1).toUpperCase() + field.name.substring(1); @@ -535,279 +836,3 @@ String _castObject( return '(${hostDatatype.datatype})$varName'; } } - -/// Generates the ".java" file for the AST represented by [root] to [sink] with the -/// provided [options]. -void generateJava(JavaOptions options, Root root, StringSink sink) { - final Set rootClassNameSet = - root.classes.map((Class x) => x.name).toSet(); - final Set rootEnumNameSet = - root.enums.map((Enum x) => x.name).toSet(); - final Indent indent = Indent(sink); - - void writeHeader() { - if (options.copyrightHeader != null) { - addLines(indent, options.copyrightHeader!, linePrefix: '// '); - } - indent.writeln('// $generatedCodeWarning'); - indent.writeln('// $seeAlsoWarning'); - } - - void writeImports() { - indent.writeln('import android.util.Log;'); - indent.writeln('import androidx.annotation.NonNull;'); - indent.writeln('import androidx.annotation.Nullable;'); - indent.writeln('import io.flutter.plugin.common.BasicMessageChannel;'); - indent.writeln('import io.flutter.plugin.common.BinaryMessenger;'); - indent.writeln('import io.flutter.plugin.common.MessageCodec;'); - indent.writeln('import io.flutter.plugin.common.StandardMessageCodec;'); - indent.writeln('import java.io.ByteArrayOutputStream;'); - indent.writeln('import java.nio.ByteBuffer;'); - indent.writeln('import java.util.Arrays;'); - indent.writeln('import java.util.ArrayList;'); - indent.writeln('import java.util.Collections;'); - indent.writeln('import java.util.List;'); - indent.writeln('import java.util.Map;'); - indent.writeln('import java.util.HashMap;'); - } - - String camelToSnake(String camelCase) { - final RegExp regex = RegExp('([a-z])([A-Z]+)'); - return camelCase - .replaceAllMapped(regex, (Match m) => '${m[1]}_${m[2]}') - .toUpperCase(); - } - - void writeEnum(Enum anEnum) { - addDocumentationComments( - indent, anEnum.documentationComments, _docCommentSpec); - - indent.write('public enum ${anEnum.name} '); - indent.scoped('{', '}', () { - enumerate(anEnum.members, (int index, final EnumMember member) { - addDocumentationComments( - indent, member.documentationComments, _docCommentSpec); - indent.writeln( - '${camelToSnake(member.name)}($index)${index == anEnum.members.length - 1 ? ';' : ','}'); - }); - indent.writeln(''); - indent.writeln('private final int index;'); - indent.write('private ${anEnum.name}(final int index) '); - indent.scoped('{', '}', () { - indent.writeln('this.index = index;'); - }); - }); - } - - void writeDataClass(Class klass) { - void writeField(NamedType field) { - final HostDatatype hostDatatype = getFieldHostDatatype( - field, - root.classes, - root.enums, - (TypeDeclaration x) => _javaTypeForBuiltinDartType(x)); - final String nullability = - field.type.isNullable ? '@Nullable' : '@NonNull'; - addDocumentationComments( - indent, field.documentationComments, _docCommentSpec); - - indent.writeln( - 'private $nullability ${hostDatatype.datatype} ${field.name};'); - indent.writeln( - 'public $nullability ${hostDatatype.datatype} ${_makeGetter(field)}() { return ${field.name}; }'); - indent.writeScoped( - 'public void ${_makeSetter(field)}($nullability ${hostDatatype.datatype} setterArg) {', - '}', () { - if (!field.type.isNullable) { - indent.writeScoped('if (setterArg == null) {', '}', () { - indent.writeln( - 'throw new IllegalStateException("Nonnull field \\"${field.name}\\" is null.");'); - }); - } - indent.writeln('this.${field.name} = setterArg;'); - }); - } - - void writeToList() { - indent.write('@NonNull ArrayList toList() '); - indent.scoped('{', '}', () { - indent.writeln( - 'ArrayList toListResult = new ArrayList(${klass.fields.length});'); - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - final HostDatatype hostDatatype = getFieldHostDatatype( - field, - root.classes, - root.enums, - (TypeDeclaration x) => _javaTypeForBuiltinDartType(x)); - String toWriteValue = ''; - final String fieldName = field.name; - if (!hostDatatype.isBuiltin && - rootClassNameSet.contains(field.type.baseName)) { - toWriteValue = '($fieldName == null) ? null : $fieldName.toList()'; - } else if (!hostDatatype.isBuiltin && - rootEnumNameSet.contains(field.type.baseName)) { - toWriteValue = '$fieldName == null ? null : $fieldName.index'; - } else { - toWriteValue = field.name; - } - indent.writeln('toListResult.add($toWriteValue);'); - } - indent.writeln('return toListResult;'); - }); - } - - void writeFromList() { - indent.write( - 'static @NonNull ${klass.name} fromList(@NonNull ArrayList list) '); - indent.scoped('{', '}', () { - const String result = 'pigeonResult'; - indent.writeln('${klass.name} $result = new ${klass.name}();'); - enumerate(getFieldsInSerializationOrder(klass), - (int index, final NamedType field) { - final String fieldVariable = field.name; - final String setter = _makeSetter(field); - indent.writeln('Object $fieldVariable = list.get($index);'); - if (rootEnumNameSet.contains(field.type.baseName)) { - indent.writeln( - '$result.$setter(${_intToEnum(fieldVariable, field.type.baseName)});'); - } else { - indent.writeln( - '$result.$setter(${_castObject(field, root.classes, root.enums, fieldVariable)});'); - } - }); - indent.writeln('return $result;'); - }); - } - - void writeBuilder() { - indent.write('public static final class Builder '); - indent.scoped('{', '}', () { - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - final HostDatatype hostDatatype = getFieldHostDatatype( - field, - root.classes, - root.enums, - (TypeDeclaration x) => _javaTypeForBuiltinDartType(x)); - final String nullability = - field.type.isNullable ? '@Nullable' : '@NonNull'; - indent.writeln( - 'private @Nullable ${hostDatatype.datatype} ${field.name};'); - indent.writeScoped( - 'public @NonNull Builder ${_makeSetter(field)}($nullability ${hostDatatype.datatype} setterArg) {', - '}', () { - indent.writeln('this.${field.name} = setterArg;'); - indent.writeln('return this;'); - }); - } - indent.write('public @NonNull ${klass.name} build() '); - indent.scoped('{', '}', () { - const String returnVal = 'pigeonReturn'; - indent.writeln('${klass.name} $returnVal = new ${klass.name}();'); - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - indent.writeln('$returnVal.${_makeSetter(field)}(${field.name});'); - } - indent.writeln('return $returnVal;'); - }); - }); - } - - const List generatedMessages = [ - ' Generated class from Pigeon that represents data sent in messages.' - ]; - addDocumentationComments( - indent, klass.documentationComments, _docCommentSpec, - generatorComments: generatedMessages); - - indent.write('public static class ${klass.name} '); - indent.scoped('{', '}', () { - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - writeField(field); - indent.addln(''); - } - - if (getFieldsInSerializationOrder(klass) - .map((NamedType e) => !e.type.isNullable) - .any((bool e) => e)) { - indent.writeln( - '${_docCommentPrefix}Constructor is private to enforce null safety; use Builder.$_docCommentSuffix'); - indent.writeln('private ${klass.name}() {}'); - } - - writeBuilder(); - writeToList(); - writeFromList(); - }); - } - - void writeResultInterface() { - indent.write('public interface Result '); - indent.scoped('{', '}', () { - indent.writeln('void success(T result);'); - indent.writeln('void error(Throwable error);'); - }); - } - - void writeApi(Api api) { - if (api.location == ApiLocation.host) { - _writeHostApi(indent, api, root); - } else if (api.location == ApiLocation.flutter) { - _writeFlutterApi(indent, api, root); - } - } - - void writeWrapError() { - indent.format(''' -@NonNull private static ArrayList wrapError(@NonNull Throwable exception) { -\tArrayList errorList = new ArrayList<>(3); -\terrorList.add(exception.toString()); -\terrorList.add(exception.getClass().getSimpleName()); -\terrorList.add("Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); -\treturn errorList; -}'''); - } - - writeHeader(); - indent.addln(''); - if (options.package != null) { - indent.writeln('package ${options.package};'); - } - indent.addln(''); - writeImports(); - indent.addln(''); - indent.writeln( - '$_docCommentPrefix Generated class from Pigeon.$_docCommentSuffix'); - indent.writeln( - '@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"})'); - if (options.useGeneratedAnnotation ?? false) { - indent.writeln('@javax.annotation.Generated("dev.flutter.pigeon")'); - } - indent.write('public class ${options.className!} '); - indent.scoped('{', '}', () { - for (final Enum anEnum in root.enums) { - indent.writeln(''); - writeEnum(anEnum); - } - - for (final Class klass in root.classes) { - indent.addln(''); - writeDataClass(klass); - } - - if (root.apis.any((Api api) => - api.location == ApiLocation.host && - api.methods.any((Method it) => it.isAsynchronous))) { - indent.addln(''); - writeResultInterface(); - } - - for (final Api api in root.apis) { - if (getCodecClasses(api, root).isNotEmpty) { - _writeCodec(indent, api, root); - indent.addln(''); - } - writeApi(api); - } - - writeWrapError(); - }); -} diff --git a/packages/pigeon/lib/kotlin_generator.dart b/packages/pigeon/lib/kotlin_generator.dart index 18e08c7398a..76b270ba67e 100644 --- a/packages/pigeon/lib/kotlin_generator.dart +++ b/packages/pigeon/lib/kotlin_generator.dart @@ -66,309 +66,581 @@ class KotlinOptions { } /// Class that manages all Kotlin code generation. -class KotlinGenerator extends Generator { +class KotlinGenerator extends StructuredGenerator { /// Instantiates a Kotlin Generator. - KotlinGenerator(); + const KotlinGenerator(); - /// Generates Kotlin files with specified [KotlinOptions] @override - void generate(KotlinOptions languageOptions, Root root, StringSink sink, - {FileType fileType = FileType.na}) { - assert(fileType == FileType.na); - generateKotlin(languageOptions, root, sink); + void writeFilePrologue( + KotlinOptions generatorOptions, Root root, Indent indent) { + if (generatorOptions.copyrightHeader != null) { + addLines(indent, generatorOptions.copyrightHeader!, linePrefix: '// '); + } + indent.writeln('// $generatedCodeWarning'); + indent.writeln('// $seeAlsoWarning'); } -} -/// Calculates the name of the codec that will be generated for [api]. -String _getCodecName(Api api) => '${api.name}Codec'; + @override + void writeFileImports( + KotlinOptions generatorOptions, Root root, Indent indent) { + indent.addln(''); + if (generatorOptions.package != null) { + indent.writeln('package ${generatorOptions.package}'); + } + indent.addln(''); + indent.writeln('import android.util.Log'); + indent.writeln('import io.flutter.plugin.common.BasicMessageChannel'); + indent.writeln('import io.flutter.plugin.common.BinaryMessenger'); + indent.writeln('import io.flutter.plugin.common.MessageCodec'); + indent.writeln('import io.flutter.plugin.common.StandardMessageCodec'); + indent.writeln('import java.io.ByteArrayOutputStream'); + indent.writeln('import java.nio.ByteBuffer'); + } -/// Writes the codec class that will be used by [api]. -/// Example: -/// private static class FooCodec extends StandardMessageCodec {...} -void _writeCodec(Indent indent, Api api, Root root) { - assert(getCodecClasses(api, root).isNotEmpty); - final Iterable codecClasses = getCodecClasses(api, root); - final String codecName = _getCodecName(api); - indent.writeln('@Suppress("UNCHECKED_CAST")'); - indent.write('private object $codecName : StandardMessageCodec() '); - indent.scoped('{', '}', () { - indent.write( - 'override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? '); + @override + void writeEnum( + KotlinOptions generatorOptions, Root root, Indent indent, Enum anEnum) { + indent.writeln(''); + addDocumentationComments( + indent, anEnum.documentationComments, _docCommentSpec); + indent.write('enum class ${anEnum.name}(val raw: Int) '); indent.scoped('{', '}', () { - indent.write('return when (type) '); - indent.scoped('{', '}', () { - for (final EnumeratedClass customClass in codecClasses) { - indent.write('${customClass.enumeration}.toByte() -> '); - indent.scoped('{', '}', () { - indent.write('return (readValue(buffer) as? List)?.let '); - indent.scoped('{', '}', () { - indent.writeln('${customClass.name}.fromList(it)'); - }); - }); + enumerate(anEnum.members, (int index, final EnumMember member) { + addDocumentationComments( + indent, member.documentationComments, _docCommentSpec); + indent.write('${member.name.toUpperCase()}($index)'); + if (index != anEnum.members.length - 1) { + indent.addln(','); + } else { + indent.addln(';'); } - indent.writeln('else -> super.readValueOfType(type, buffer)'); }); - }); - indent.write( - 'override fun writeValue(stream: ByteArrayOutputStream, value: Any?) '); - indent.writeScoped('{', '}', () { - indent.write('when (value) '); + indent.writeln(''); + indent.write('companion object '); indent.scoped('{', '}', () { - for (final EnumeratedClass customClass in codecClasses) { - indent.write('is ${customClass.name} -> '); - indent.scoped('{', '}', () { - indent.writeln('stream.write(${customClass.enumeration})'); - indent.writeln('writeValue(stream, value.toList())'); - }); - } - indent.writeln('else -> super.writeValue(stream, value)'); + indent.write('fun ofRaw(raw: Int): ${anEnum.name}? '); + indent.scoped('{', '}', () { + indent.writeln('return values().firstOrNull { it.raw == raw }'); + }); }); }); - }); -} - -/// Write the kotlin code that represents a host [Api], [api]. -/// Example: -/// interface Foo { -/// Int add(x: Int, y: Int); -/// companion object { -/// fun setUp(binaryMessenger: BinaryMessenger, api: Api) {...} -/// } -/// } -void _writeHostApi(Indent indent, Api api, Root root) { - assert(api.location == ApiLocation.host); - - final String apiName = api.name; - - final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; - - const List generatedMessages = [ - ' Generated interface from Pigeon that represents a handler of messages from Flutter.' - ]; - addDocumentationComments(indent, api.documentationComments, _docCommentSpec, - generatorComments: generatedMessages); - - indent.write('interface $apiName '); - indent.scoped('{', '}', () { - for (final Method method in api.methods) { - final List argSignature = []; - if (method.arguments.isNotEmpty) { - final Iterable argTypes = method.arguments - .map((NamedType e) => _nullsafeKotlinTypeForDartType(e.type)); - final Iterable argNames = - method.arguments.map((NamedType e) => e.name); - argSignature - .addAll(map2(argTypes, argNames, (String argType, String argName) { - return '$argName: $argType'; - })); - } + } - final String returnType = method.returnType.isVoid - ? '' - : _nullsafeKotlinTypeForDartType(method.returnType); + @override + void writeDataClass( + KotlinOptions generatorOptions, Root root, Indent indent, Class klass) { + final Set customClassNames = + root.classes.map((Class x) => x.name).toSet(); + final Set customEnumNames = + root.enums.map((Enum x) => x.name).toSet(); - addDocumentationComments( - indent, method.documentationComments, _docCommentSpec); + const List generatedMessages = [ + ' Generated class from Pigeon that represents data sent in messages.' + ]; + indent.addln(''); + addDocumentationComments( + indent, klass.documentationComments, _docCommentSpec, + generatorComments: generatedMessages); - if (method.isAsynchronous) { - argSignature.add('callback: ($returnType) -> Unit'); - indent.writeln('fun ${method.name}(${argSignature.join(', ')})'); - } else if (method.returnType.isVoid) { - indent.writeln('fun ${method.name}(${argSignature.join(', ')})'); - } else { - indent.writeln( - 'fun ${method.name}(${argSignature.join(', ')}): $returnType'); + indent.write('data class ${klass.name} '); + indent.scoped('(', '', () { + for (final NamedType element in getFieldsInSerializationOrder(klass)) { + _writeClassField(indent, element); + if (getFieldsInSerializationOrder(klass).last != element) { + indent.addln(','); + } else { + indent.addln(''); + } } - } + }); - indent.addln(''); - indent.write('companion object '); + indent.scoped(') {', '}', () { + writeClassDecode(generatorOptions, root, indent, klass, customClassNames, + customEnumNames); + writeClassEncode(generatorOptions, root, indent, klass, customClassNames, + customEnumNames); + }); + } + + @override + void writeClassEncode( + KotlinOptions generatorOptions, + Root root, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) { + indent.write('fun toList(): List '); indent.scoped('{', '}', () { - indent.writeln('/** The codec used by $apiName. */'); - indent.write('val codec: MessageCodec by lazy '); - indent.scoped('{', '}', () { - if (isCustomCodec) { - indent.writeln(_getCodecName(api)); - } else { - indent.writeln('StandardMessageCodec()'); + indent.write('return listOf'); + indent.scoped('(', ')', () { + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + final HostDatatype hostDatatype = _getHostDatatype(root, field); + String toWriteValue = ''; + final String fieldName = field.name; + if (!hostDatatype.isBuiltin && + customClassNames.contains(field.type.baseName)) { + toWriteValue = '$fieldName?.toList()'; + } else if (!hostDatatype.isBuiltin && + customEnumNames.contains(field.type.baseName)) { + toWriteValue = '$fieldName?.raw'; + } else { + toWriteValue = fieldName; + } + indent.writeln('$toWriteValue,'); } }); - indent.writeln( - '/** Sets up an instance of `$apiName` to handle messages through the `binaryMessenger`. */'); + }); + } + + @override + void writeClassDecode( + KotlinOptions generatorOptions, + Root root, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) { + final String className = klass.name; + + indent.write('companion object '); + indent.scoped('{', '}', () { indent.writeln('@Suppress("UNCHECKED_CAST")'); - indent.write( - 'fun setUp(binaryMessenger: BinaryMessenger, api: $apiName?) '); + indent.write('fun fromList(list: List): $className '); + indent.scoped('{', '}', () { - for (final Method method in api.methods) { - indent.write('run '); - indent.scoped('{', '}', () { - String? taskQueue; - if (method.taskQueueType != TaskQueueType.serial) { - taskQueue = 'taskQueue'; - indent.writeln( - 'val $taskQueue = binaryMessenger.makeBackgroundTaskQueue()'); - } + enumerate(getFieldsInSerializationOrder(klass), + (int index, final NamedType field) { + final HostDatatype hostDatatype = _getHostDatatype(root, field); - final String channelName = makeChannelName(api, method); + // The StandardMessageCodec can give us [Integer, Long] for + // a Dart 'int'. To keep things simple we just use 64bit + // longs in Pigeon with Kotlin. + final bool isInt = field.type.baseName == 'int'; - indent.write( - 'val channel = BasicMessageChannel(binaryMessenger, "$channelName", codec'); + final String listValue = 'list[$index]'; + final String fieldType = _kotlinTypeForDartType(field.type); - if (taskQueue != null) { - indent.addln(', $taskQueue)'); + if (field.type.isNullable) { + if (!hostDatatype.isBuiltin && + customClassNames.contains(field.type.baseName)) { + indent.write('val ${field.name}: $fieldType? = '); + indent.add('($listValue as? List)?.let '); + indent.scoped('{', '}', () { + indent.writeln('$fieldType.fromList(it)'); + }); + } else if (!hostDatatype.isBuiltin && + customEnumNames.contains(field.type.baseName)) { + indent.write('val ${field.name}: $fieldType? = '); + indent.add('($listValue as? Int)?.let '); + indent.scoped('{', '}', () { + indent.writeln('$fieldType.ofRaw(it)'); + }); + } else if (isInt) { + indent.write('val ${field.name} = $listValue'); + indent.addln( + '.let { if (it is Int) it.toLong() else it as? Long }'); + } else { + indent.writeln('val ${field.name} = $listValue as? $fieldType'); + } + } else { + if (!hostDatatype.isBuiltin && + customClassNames.contains(field.type.baseName)) { + indent.writeln( + 'val ${field.name} = $fieldType.fromList($listValue as List)'); + } else if (!hostDatatype.isBuiltin && + customEnumNames.contains(field.type.baseName)) { + indent.write( + 'val ${field.name} = $fieldType.ofRaw($listValue as Int)!!'); } else { - indent.addln(')'); + indent.writeln('val ${field.name} = $listValue as $fieldType'); } + } + }); - indent.write('if (api != null) '); - indent.scoped('{', '}', () { - final String messageVarName = - method.arguments.isNotEmpty ? 'message' : '_'; - - indent.write('channel.setMessageHandler '); - indent.scoped('{ $messageVarName, reply ->', '}', () { - indent.writeln('var wrapped = listOf()'); - indent.write('try '); - indent.scoped('{', '}', () { - final List methodArgument = []; - if (method.arguments.isNotEmpty) { - indent.writeln('val args = message as List'); - enumerate(method.arguments, (int index, NamedType arg) { - final String argName = _getSafeArgumentName(index, arg); - final String argIndex = 'args[$index]'; - indent.writeln( - 'val $argName = ${_castForceUnwrap(argIndex, arg.type, root)}'); - methodArgument.add(argName); - }); - } - final String call = - 'api.${method.name}(${methodArgument.join(', ')})'; - if (method.isAsynchronous) { - indent.write('$call '); - final String resultValue = - method.returnType.isVoid ? 'null' : 'it'; - indent.scoped('{', '}', () { - indent.writeln('reply.reply(wrapResult($resultValue))'); - }); - } else if (method.returnType.isVoid) { - indent.writeln(call); - indent.writeln('wrapped = listOf(null)'); - } else { - indent.writeln('wrapped = listOf($call)'); - } - }, addTrailingNewline: false); - indent.add(' catch (exception: Error) '); - indent.scoped('{', '}', () { - indent.writeln('wrapped = wrapError(exception)'); - if (method.isAsynchronous) { - indent.writeln('reply.reply(wrapped)'); - } - }); - if (!method.isAsynchronous) { - indent.writeln('reply.reply(wrapped)'); - } - }); - }, addTrailingNewline: false); - indent.scoped(' else {', '}', () { - indent.writeln('channel.setMessageHandler(null)'); - }); - }); + indent.writeln(''); + indent.write('return $className('); + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + final String comma = + getFieldsInSerializationOrder(klass).last == field ? '' : ', '; + indent.add('${field.name}$comma'); } + indent.addln(')'); }); }); - }); -} + } -String _getArgumentName(int count, NamedType argument) => - argument.name.isEmpty ? 'arg$count' : argument.name; + void _writeClassField(Indent indent, NamedType field) { + addDocumentationComments( + indent, field.documentationComments, _docCommentSpec); + indent.write( + 'val ${field.name}: ${_nullsafeKotlinTypeForDartType(field.type)}'); + final String defaultNil = field.type.isNullable ? ' = null' : ''; + indent.add(defaultNil); + } -/// Returns an argument name that can be used in a context where it is possible to collide. -String _getSafeArgumentName(int count, NamedType argument) => - '${_getArgumentName(count, argument)}Arg'; + @override + void writeApis( + KotlinOptions generatorOptions, + Root root, + Indent indent, + ) { + if (root.apis.any((Api api) => + api.location == ApiLocation.host && + api.methods.any((Method it) => it.isAsynchronous))) { + indent.addln(''); + } + super.writeApis(generatorOptions, root, indent); + } -/// Writes the code for a flutter [Api], [api]. -/// Example: -/// class Foo(private val binaryMessenger: BinaryMessenger) { -/// fun add(x: Int, y: Int, callback: (Int?) -> Unit) {...} -/// } -void _writeFlutterApi(Indent indent, Api api, Root root) { - assert(api.location == ApiLocation.flutter); - final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; - - const List generatedMessages = [ - ' Generated class from Pigeon that represents Flutter messages that can be called from Kotlin.' - ]; - addDocumentationComments(indent, api.documentationComments, _docCommentSpec, - generatorComments: generatedMessages); - - final String apiName = api.name; - indent.writeln('@Suppress("UNCHECKED_CAST")'); - indent.write('class $apiName(private val binaryMessenger: BinaryMessenger) '); - indent.scoped('{', '}', () { - indent.write('companion object '); + /// Writes the code for a flutter [Api], [api]. + /// Example: + /// class Foo(private val binaryMessenger: BinaryMessenger) { + /// fun add(x: Int, y: Int, callback: (Int?) -> Unit) {...} + /// } + @override + void writeFlutterApi( + KotlinOptions generatorOptions, + Root root, + Indent indent, + Api api, + ) { + assert(api.location == ApiLocation.flutter); + final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; + if (isCustomCodec) { + _writeCodec(indent, api, root); + } + + const List generatedMessages = [ + ' Generated class from Pigeon that represents Flutter messages that can be called from Kotlin.' + ]; + addDocumentationComments(indent, api.documentationComments, _docCommentSpec, + generatorComments: generatedMessages); + + final String apiName = api.name; + indent.writeln('@Suppress("UNCHECKED_CAST")'); + indent + .write('class $apiName(private val binaryMessenger: BinaryMessenger) '); indent.scoped('{', '}', () { - indent.writeln('/** The codec used by $apiName. */'); - indent.write('val codec: MessageCodec by lazy '); + indent.write('companion object '); indent.scoped('{', '}', () { - if (isCustomCodec) { - indent.writeln(_getCodecName(api)); + indent.writeln('/** The codec used by $apiName. */'); + indent.write('val codec: MessageCodec by lazy '); + indent.scoped('{', '}', () { + if (isCustomCodec) { + indent.writeln(_getCodecName(api)); + } else { + indent.writeln('StandardMessageCodec()'); + } + }); + }); + + for (final Method func in api.methods) { + final String channelName = makeChannelName(api, func); + final String returnType = func.returnType.isVoid + ? '' + : _nullsafeKotlinTypeForDartType(func.returnType); + String sendArgument; + + addDocumentationComments( + indent, func.documentationComments, _docCommentSpec); + + if (func.arguments.isEmpty) { + indent.write('fun ${func.name}(callback: ($returnType) -> Unit) '); + sendArgument = 'null'; } else { - indent.writeln('StandardMessageCodec()'); + final Iterable argTypes = func.arguments + .map((NamedType e) => _nullsafeKotlinTypeForDartType(e.type)); + final Iterable argNames = + indexMap(func.arguments, _getSafeArgumentName); + sendArgument = 'listOf(${argNames.join(', ')})'; + final String argsSignature = map2(argTypes, argNames, + (String type, String name) => '$name: $type').join(', '); + if (func.returnType.isVoid) { + indent.write( + 'fun ${func.name}($argsSignature, callback: () -> Unit) '); + } else { + indent.write( + 'fun ${func.name}($argsSignature, callback: ($returnType) -> Unit) '); + } } - }); + indent.scoped('{', '}', () { + const String channel = 'channel'; + indent.writeln( + 'val $channel = BasicMessageChannel(binaryMessenger, "$channelName", codec)'); + indent.write('$channel.send($sendArgument) '); + if (func.returnType.isVoid) { + indent.scoped('{', '}', () { + indent.writeln('callback()'); + }); + } else { + final String forceUnwrap = func.returnType.isNullable ? '?' : ''; + indent.scoped('{', '}', () { + indent.writeln('val result = it as$forceUnwrap $returnType'); + indent.writeln('callback(result)'); + }); + } + }); + } }); + } + + /// Write the kotlin code that represents a host [Api], [api]. + /// Example: + /// interface Foo { + /// Int add(x: Int, y: Int); + /// companion object { + /// fun setUp(binaryMessenger: BinaryMessenger, api: Api) {...} + /// } + /// } + @override + void writeHostApi( + KotlinOptions generatorOptions, + Root root, + Indent indent, + Api api, + ) { + assert(api.location == ApiLocation.host); + + final String apiName = api.name; + + final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; + if (isCustomCodec) { + _writeCodec(indent, api, root); + } - for (final Method func in api.methods) { - final String channelName = makeChannelName(api, func); - final String returnType = func.returnType.isVoid - ? '' - : _nullsafeKotlinTypeForDartType(func.returnType); - String sendArgument; - - addDocumentationComments( - indent, func.documentationComments, _docCommentSpec); - - if (func.arguments.isEmpty) { - indent.write('fun ${func.name}(callback: ($returnType) -> Unit) '); - sendArgument = 'null'; - } else { - final Iterable argTypes = func.arguments - .map((NamedType e) => _nullsafeKotlinTypeForDartType(e.type)); - final Iterable argNames = - indexMap(func.arguments, _getSafeArgumentName); - sendArgument = 'listOf(${argNames.join(', ')})'; - final String argsSignature = map2(argTypes, argNames, - (String type, String name) => '$name: $type').join(', '); - if (func.returnType.isVoid) { - indent - .write('fun ${func.name}($argsSignature, callback: () -> Unit) '); + const List generatedMessages = [ + ' Generated interface from Pigeon that represents a handler of messages from Flutter.' + ]; + addDocumentationComments(indent, api.documentationComments, _docCommentSpec, + generatorComments: generatedMessages); + + indent.write('interface $apiName '); + indent.scoped('{', '}', () { + for (final Method method in api.methods) { + final List argSignature = []; + if (method.arguments.isNotEmpty) { + final Iterable argTypes = method.arguments + .map((NamedType e) => _nullsafeKotlinTypeForDartType(e.type)); + final Iterable argNames = + method.arguments.map((NamedType e) => e.name); + argSignature.addAll( + map2(argTypes, argNames, (String argType, String argName) { + return '$argName: $argType'; + })); + } + + final String returnType = method.returnType.isVoid + ? '' + : _nullsafeKotlinTypeForDartType(method.returnType); + + addDocumentationComments( + indent, method.documentationComments, _docCommentSpec); + + if (method.isAsynchronous) { + argSignature.add('callback: ($returnType) -> Unit'); + indent.writeln('fun ${method.name}(${argSignature.join(', ')})'); + } else if (method.returnType.isVoid) { + indent.writeln('fun ${method.name}(${argSignature.join(', ')})'); } else { - indent.write( - 'fun ${func.name}($argsSignature, callback: ($returnType) -> Unit) '); + indent.writeln( + 'fun ${method.name}(${argSignature.join(', ')}): $returnType'); } } + + indent.addln(''); + indent.write('companion object '); indent.scoped('{', '}', () { - const String channel = 'channel'; + indent.writeln('/** The codec used by $apiName. */'); + indent.write('val codec: MessageCodec by lazy '); + indent.scoped('{', '}', () { + if (isCustomCodec) { + indent.writeln(_getCodecName(api)); + } else { + indent.writeln('StandardMessageCodec()'); + } + }); indent.writeln( - 'val $channel = BasicMessageChannel(binaryMessenger, "$channelName", codec)'); - indent.write('$channel.send($sendArgument) '); - if (func.returnType.isVoid) { - indent.scoped('{', '}', () { - indent.writeln('callback()'); - }); - } else { - final String forceUnwrap = func.returnType.isNullable ? '?' : ''; - indent.scoped('{', '}', () { - indent.writeln('val result = it as$forceUnwrap $returnType'); - indent.writeln('callback(result)'); - }); - } + '/** Sets up an instance of `$apiName` to handle messages through the `binaryMessenger`. */'); + indent.writeln('@Suppress("UNCHECKED_CAST")'); + indent.write( + 'fun setUp(binaryMessenger: BinaryMessenger, api: $apiName?) '); + indent.scoped('{', '}', () { + for (final Method method in api.methods) { + indent.write('run '); + indent.scoped('{', '}', () { + String? taskQueue; + if (method.taskQueueType != TaskQueueType.serial) { + taskQueue = 'taskQueue'; + indent.writeln( + 'val $taskQueue = binaryMessenger.makeBackgroundTaskQueue()'); + } + + final String channelName = makeChannelName(api, method); + + indent.write( + 'val channel = BasicMessageChannel(binaryMessenger, "$channelName", codec'); + + if (taskQueue != null) { + indent.addln(', $taskQueue)'); + } else { + indent.addln(')'); + } + + indent.write('if (api != null) '); + indent.scoped('{', '}', () { + final String messageVarName = + method.arguments.isNotEmpty ? 'message' : '_'; + + indent.write('channel.setMessageHandler '); + indent.scoped('{ $messageVarName, reply ->', '}', () { + indent.writeln('var wrapped = listOf()'); + indent.write('try '); + indent.scoped('{', '}', () { + final List methodArgument = []; + if (method.arguments.isNotEmpty) { + indent.writeln('val args = message as List'); + enumerate(method.arguments, (int index, NamedType arg) { + final String argName = _getSafeArgumentName(index, arg); + final String argIndex = 'args[$index]'; + indent.writeln( + 'val $argName = ${_castForceUnwrap(argIndex, arg.type, root)}'); + methodArgument.add(argName); + }); + } + final String call = + 'api.${method.name}(${methodArgument.join(', ')})'; + if (method.isAsynchronous) { + indent.write('$call '); + final String resultValue = + method.returnType.isVoid ? 'null' : 'it'; + indent.scoped('{', '}', () { + indent.writeln('reply.reply(wrapResult($resultValue))'); + }); + } else if (method.returnType.isVoid) { + indent.writeln(call); + indent.writeln('wrapped = listOf(null)'); + } else { + indent.writeln('wrapped = listOf($call)'); + } + }, addTrailingNewline: false); + indent.add(' catch (exception: Error) '); + indent.scoped('{', '}', () { + indent.writeln('wrapped = wrapError(exception)'); + if (method.isAsynchronous) { + indent.writeln('reply.reply(wrapped)'); + } + }); + if (!method.isAsynchronous) { + indent.writeln('reply.reply(wrapped)'); + } + }); + }, addTrailingNewline: false); + indent.scoped(' else {', '}', () { + indent.writeln('channel.setMessageHandler(null)'); + }); + }); + } + }); }); - } - }); + }); + } + + /// Writes the codec class that will be used by [api]. + /// Example: + /// private static class FooCodec extends StandardMessageCodec {...} + void _writeCodec(Indent indent, Api api, Root root) { + assert(getCodecClasses(api, root).isNotEmpty); + final Iterable codecClasses = getCodecClasses(api, root); + final String codecName = _getCodecName(api); + indent.writeln('@Suppress("UNCHECKED_CAST")'); + indent.write('private object $codecName : StandardMessageCodec() '); + indent.scoped('{', '}', () { + indent.write( + 'override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? '); + indent.scoped('{', '}', () { + indent.write('return when (type) '); + indent.scoped('{', '}', () { + for (final EnumeratedClass customClass in codecClasses) { + indent.write('${customClass.enumeration}.toByte() -> '); + indent.scoped('{', '}', () { + indent.write('return (readValue(buffer) as? List)?.let '); + indent.scoped('{', '}', () { + indent.writeln('${customClass.name}.fromList(it)'); + }); + }); + } + indent.writeln('else -> super.readValueOfType(type, buffer)'); + }); + }); + + indent.write( + 'override fun writeValue(stream: ByteArrayOutputStream, value: Any?) '); + indent.writeScoped('{', '}', () { + indent.write('when (value) '); + indent.scoped('{', '}', () { + for (final EnumeratedClass customClass in codecClasses) { + indent.write('is ${customClass.name} -> '); + indent.scoped('{', '}', () { + indent.writeln('stream.write(${customClass.enumeration})'); + indent.writeln('writeValue(stream, value.toList())'); + }); + } + indent.writeln('else -> super.writeValue(stream, value)'); + }); + }); + }); + indent.addln(''); + } + + void _writeWrapResult(Indent indent) { + indent.addln(''); + indent.write('private fun wrapResult(result: Any?): List '); + indent.scoped('{', '}', () { + indent.writeln('return listOf(result)'); + }); + } + + void _writeWrapError(Indent indent) { + indent.addln(''); + indent.write('private fun wrapError(exception: Throwable): List '); + indent.scoped('{', '}', () { + indent.write('return '); + indent.scoped('listOf(', ')', () { + indent.writeln('exception.javaClass.simpleName,'); + indent.writeln('exception.toString(),'); + indent.writeln( + '"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)'); + }); + }); + } + + @override + void writeGeneralUtilities( + KotlinOptions generatorOptions, Root root, Indent indent) { + _writeWrapResult(indent); + _writeWrapError(indent); + } +} + +HostDatatype _getHostDatatype(Root root, NamedType field) { + return getFieldHostDatatype(field, root.classes, root.enums, + (TypeDeclaration x) => _kotlinTypeForBuiltinDartType(x)); } +/// Calculates the name of the codec that will be generated for [api]. +String _getCodecName(Api api) => '${api.name}Codec'; + +String _getArgumentName(int count, NamedType argument) => + argument.name.isEmpty ? 'arg$count' : argument.name; + +/// Returns an argument name that can be used in a context where it is possible to collide. +String _getSafeArgumentName(int count, NamedType argument) => + '${_getArgumentName(count, argument)}Arg'; + String _castForceUnwrap(String value, TypeDeclaration type, Root root) { if (isEnum(root, type)) { final String forceUnwrap = type.isNullable ? '' : '!!'; @@ -448,258 +720,3 @@ String _nullsafeKotlinTypeForDartType(TypeDeclaration type) { final String nullSafe = type.isNullable ? '?' : ''; return '${_kotlinTypeForDartType(type)}$nullSafe'; } - -/// Generates the ".kotlin" file for the AST represented by [root] to [sink] with the -/// provided [options]. -void generateKotlin(KotlinOptions options, Root root, StringSink sink) { - final Set rootClassNameSet = - root.classes.map((Class x) => x.name).toSet(); - final Set rootEnumNameSet = - root.enums.map((Enum x) => x.name).toSet(); - final Indent indent = Indent(sink); - - HostDatatype getHostDatatype(NamedType field) { - return getFieldHostDatatype(field, root.classes, root.enums, - (TypeDeclaration x) => _kotlinTypeForBuiltinDartType(x)); - } - - void writeHeader() { - if (options.copyrightHeader != null) { - addLines(indent, options.copyrightHeader!, linePrefix: '// '); - } - indent.writeln('// $generatedCodeWarning'); - indent.writeln('// $seeAlsoWarning'); - } - - void writeImports() { - indent.writeln('import android.util.Log'); - indent.writeln('import io.flutter.plugin.common.BasicMessageChannel'); - indent.writeln('import io.flutter.plugin.common.BinaryMessenger'); - indent.writeln('import io.flutter.plugin.common.MessageCodec'); - indent.writeln('import io.flutter.plugin.common.StandardMessageCodec'); - indent.writeln('import java.io.ByteArrayOutputStream'); - indent.writeln('import java.nio.ByteBuffer'); - } - - void writeEnum(Enum anEnum) { - addDocumentationComments( - indent, anEnum.documentationComments, _docCommentSpec); - indent.write('enum class ${anEnum.name}(val raw: Int) '); - indent.scoped('{', '}', () { - enumerate(anEnum.members, (int index, final EnumMember member) { - addDocumentationComments( - indent, member.documentationComments, _docCommentSpec); - indent.write('${member.name.toUpperCase()}($index)'); - if (index != anEnum.members.length - 1) { - indent.addln(','); - } else { - indent.addln(';'); - } - }); - - indent.writeln(''); - indent.write('companion object '); - indent.scoped('{', '}', () { - indent.write('fun ofRaw(raw: Int): ${anEnum.name}? '); - indent.scoped('{', '}', () { - indent.writeln('return values().firstOrNull { it.raw == raw }'); - }); - }); - }); - } - - void writeDataClass(Class klass) { - void writeField(NamedType field) { - addDocumentationComments( - indent, field.documentationComments, _docCommentSpec); - indent.write( - 'val ${field.name}: ${_nullsafeKotlinTypeForDartType(field.type)}'); - final String defaultNil = field.type.isNullable ? ' = null' : ''; - indent.add(defaultNil); - } - - void writeToList() { - indent.write('fun toList(): List '); - indent.scoped('{', '}', () { - indent.write('return listOf'); - indent.scoped('(', ')', () { - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - final HostDatatype hostDatatype = getHostDatatype(field); - String toWriteValue = ''; - final String fieldName = field.name; - if (!hostDatatype.isBuiltin && - rootClassNameSet.contains(field.type.baseName)) { - toWriteValue = '$fieldName?.toList()'; - } else if (!hostDatatype.isBuiltin && - rootEnumNameSet.contains(field.type.baseName)) { - toWriteValue = '$fieldName?.raw'; - } else { - toWriteValue = fieldName; - } - indent.writeln('$toWriteValue,'); - } - }); - }); - } - - void writeFromList() { - final String className = klass.name; - - indent.write('companion object '); - indent.scoped('{', '}', () { - indent.writeln('@Suppress("UNCHECKED_CAST")'); - indent.write('fun fromList(list: List): $className '); - - indent.scoped('{', '}', () { - enumerate(getFieldsInSerializationOrder(klass), - (int index, final NamedType field) { - final HostDatatype hostDatatype = getHostDatatype(field); - - // The StandardMessageCodec can give us [Integer, Long] for - // a Dart 'int'. To keep things simple we just use 64bit - // longs in Pigeon with Kotlin. - final bool isInt = field.type.baseName == 'int'; - - final String listValue = 'list[$index]'; - final String fieldType = _kotlinTypeForDartType(field.type); - - if (field.type.isNullable) { - if (!hostDatatype.isBuiltin && - rootClassNameSet.contains(field.type.baseName)) { - indent.write('val ${field.name}: $fieldType? = '); - indent.add('($listValue as? List)?.let '); - indent.scoped('{', '}', () { - indent.writeln('$fieldType.fromList(it)'); - }); - } else if (!hostDatatype.isBuiltin && - rootEnumNameSet.contains(field.type.baseName)) { - indent.write('val ${field.name}: $fieldType? = '); - indent.add('($listValue as? Int)?.let '); - indent.scoped('{', '}', () { - indent.writeln('$fieldType.ofRaw(it)'); - }); - } else if (isInt) { - indent.write('val ${field.name} = $listValue'); - indent.addln( - '.let { if (it is Int) it.toLong() else it as? Long }'); - } else { - indent.writeln('val ${field.name} = $listValue as? $fieldType'); - } - } else { - if (!hostDatatype.isBuiltin && - rootClassNameSet.contains(field.type.baseName)) { - indent.writeln( - 'val ${field.name} = $fieldType.fromList($listValue as List)'); - } else if (!hostDatatype.isBuiltin && - rootEnumNameSet.contains(field.type.baseName)) { - indent.write( - 'val ${field.name} = $fieldType.ofRaw($listValue as Int)!!'); - } else { - indent.writeln('val ${field.name} = $listValue as $fieldType'); - } - } - }); - - indent.writeln(''); - indent.write('return $className('); - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - final String comma = - getFieldsInSerializationOrder(klass).last == field ? '' : ', '; - indent.add('${field.name}$comma'); - } - indent.addln(')'); - }); - }); - } - - const List generatedMessages = [ - ' Generated class from Pigeon that represents data sent in messages.' - ]; - addDocumentationComments( - indent, klass.documentationComments, _docCommentSpec, - generatorComments: generatedMessages); - - indent.write('data class ${klass.name} '); - indent.scoped('(', '', () { - for (final NamedType element in getFieldsInSerializationOrder(klass)) { - writeField(element); - if (getFieldsInSerializationOrder(klass).last != element) { - indent.addln(','); - } else { - indent.addln(''); - } - } - }); - - indent.scoped(') {', '}', () { - writeFromList(); - writeToList(); - }); - } - - void writeApi(Api api) { - if (api.location == ApiLocation.host) { - _writeHostApi(indent, api, root); - } else if (api.location == ApiLocation.flutter) { - _writeFlutterApi(indent, api, root); - } - } - - void writeWrapResult() { - indent.write('private fun wrapResult(result: Any?): List '); - indent.scoped('{', '}', () { - indent.writeln('return listOf(result)'); - }); - } - - void writeWrapError() { - indent.write('private fun wrapError(exception: Throwable): List '); - indent.scoped('{', '}', () { - indent.write('return '); - indent.scoped('listOf(', ')', () { - indent.writeln('exception.javaClass.simpleName,'); - indent.writeln('exception.toString(),'); - indent.writeln( - '"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)'); - }); - }); - } - - writeHeader(); - indent.addln(''); - if (options.package != null) { - indent.writeln('package ${options.package}'); - } - indent.addln(''); - writeImports(); - indent.addln(''); - indent.writeln('/** Generated class from Pigeon. */'); - for (final Enum anEnum in root.enums) { - indent.writeln(''); - writeEnum(anEnum); - } - - for (final Class klass in root.classes) { - indent.addln(''); - writeDataClass(klass); - } - - if (root.apis.any((Api api) => - api.location == ApiLocation.host && - api.methods.any((Method it) => it.isAsynchronous))) { - indent.addln(''); - } - - for (final Api api in root.apis) { - if (getCodecClasses(api, root).isNotEmpty) { - _writeCodec(indent, api, root); - indent.addln(''); - } - writeApi(api); - } - - indent.addln(''); - writeWrapResult(); - indent.addln(''); - writeWrapError(); -} diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart index 3d7e3a64da2..2b165c91d1d 100644 --- a/packages/pigeon/lib/objc_generator.dart +++ b/packages/pigeon/lib/objc_generator.dart @@ -64,165 +64,95 @@ class ObjcOptions { } } -/// Class that manages all Objc header code generation. +/// Class that manages all Objc code generation. class ObjcGenerator extends Generator> { /// Instantiates a Objc Generator. - ObjcGenerator(); + const ObjcGenerator(); - /// Generates Objc files with specified [OutputFileOptions] + /// Generates Objc file of type specified in [generatorOptions] @override - void generate(OutputFileOptions languageOptions, Root root, + void generate(OutputFileOptions generatorOptions, Root root, StringSink sink) { - final FileType fileType = languageOptions.fileType; - assert(fileType == FileType.header || fileType == FileType.source); - - if (fileType == FileType.header) { - generateObjcHeader(languageOptions.languageOptions, root, sink); - } else { - generateObjcSource(languageOptions.languageOptions, root, sink); + if (generatorOptions.fileType == FileType.header) { + const ObjcHeaderGenerator() + .generate(generatorOptions.languageOptions, root, sink); + } else if (generatorOptions.fileType == FileType.source) { + const ObjcSourceGenerator() + .generate(generatorOptions.languageOptions, root, sink); } } } -/// Calculates the ObjC class name, possibly prefixed. -String _className(String? prefix, String className) { - if (prefix != null) { - return '$prefix$className'; - } else { - return className; - } -} - -/// Calculates callback block signature for for async methods. -String _callbackForType(TypeDeclaration type, _ObjcPtr objcType) { - return type.isVoid - ? 'void(^)(NSError *_Nullable)' - : 'void(^)(${objcType.ptr.trim()}_Nullable, NSError *_Nullable)'; -} - -/// Represents an ObjC pointer (ex 'id', 'NSString *'). -class _ObjcPtr { - const _ObjcPtr({required this.baseName}) : hasAsterisk = baseName != 'id'; - final String baseName; - final bool hasAsterisk; - String get ptr => '$baseName${hasAsterisk ? ' *' : ' '}'; -} - -/// Maps between Dart types to ObjC pointer types (ex 'String' => 'NSString *'). -const Map _objcTypeForDartTypeMap = { - 'bool': _ObjcPtr(baseName: 'NSNumber'), - 'int': _ObjcPtr(baseName: 'NSNumber'), - 'String': _ObjcPtr(baseName: 'NSString'), - 'double': _ObjcPtr(baseName: 'NSNumber'), - 'Uint8List': _ObjcPtr(baseName: 'FlutterStandardTypedData'), - 'Int32List': _ObjcPtr(baseName: 'FlutterStandardTypedData'), - 'Int64List': _ObjcPtr(baseName: 'FlutterStandardTypedData'), - 'Float64List': _ObjcPtr(baseName: 'FlutterStandardTypedData'), - 'List': _ObjcPtr(baseName: 'NSArray'), - 'Map': _ObjcPtr(baseName: 'NSDictionary'), - 'Object': _ObjcPtr(baseName: 'id'), -}; +/// Generates Objc .h file. +class ObjcHeaderGenerator extends StructuredGenerator { + /// Constructor. + const ObjcHeaderGenerator(); -/// Converts list of [TypeDeclaration] to a code string representing the type -/// arguments for use in generics. -/// Example: ('FOO', ['Foo', 'Bar']) -> 'FOOFoo *, FOOBar *'). -String _flattenTypeArguments(String? classPrefix, List args) { - final String result = args - .map((TypeDeclaration e) => - _objcTypeForDartType(classPrefix, e).ptr.trim()) - .join(', '); - return result; -} + @override + void writeFilePrologue( + ObjcOptions generatorOptions, Root root, Indent indent) { + if (generatorOptions.copyrightHeader != null) { + addLines(indent, generatorOptions.copyrightHeader!, linePrefix: '// '); + } + indent.writeln('// $generatedCodeWarning'); + indent.writeln('// $seeAlsoWarning'); + indent.addln(''); + } -String? _objcTypePtrForPrimitiveDartType( - String? classPrefix, TypeDeclaration type) { - return _objcTypeForDartTypeMap.containsKey(type.baseName) - ? _objcTypeForDartType(classPrefix, type).ptr - : null; -} + @override + void writeFileImports( + ObjcOptions generatorOptions, Root root, Indent indent) { + indent.writeln('#import '); + indent.addln(''); -/// Returns the objc type for a dart [type], prepending the [classPrefix] for -/// generated classes. For example: -/// _objcTypeForDartType(null, 'int') => 'NSNumber'. -_ObjcPtr _objcTypeForDartType(String? classPrefix, TypeDeclaration field) { - return _objcTypeForDartTypeMap.containsKey(field.baseName) - ? field.typeArguments.isEmpty - ? _objcTypeForDartTypeMap[field.baseName]! - : _ObjcPtr( - baseName: - '${_objcTypeForDartTypeMap[field.baseName]!.baseName}<${_flattenTypeArguments(classPrefix, field.typeArguments)}>') - : _ObjcPtr(baseName: _className(classPrefix, field.baseName)); -} + indent.writeln('@protocol FlutterBinaryMessenger;'); + indent.writeln('@protocol FlutterMessageCodec;'); + indent.writeln('@class FlutterError;'); + indent.writeln('@class FlutterStandardTypedData;'); + indent.addln(''); + indent.writeln('NS_ASSUME_NONNULL_BEGIN'); + } -/// Maps a type to a properties memory semantics (ie strong, copy). -String _propertyTypeForDartType(NamedType field) { - const Map propertyTypeForDartTypeMap = { - 'String': 'copy', - 'bool': 'strong', - 'int': 'strong', - 'double': 'strong', - 'Uint8List': 'strong', - 'Int32List': 'strong', - 'Int64List': 'strong', - 'Float64List': 'strong', - 'List': 'strong', - 'Map': 'strong', - }; + @override + void writeEnum( + ObjcOptions generatorOptions, Root root, Indent indent, Enum anEnum) { + final String enumName = _className(generatorOptions.prefix, anEnum.name); + indent.writeln(''); + addDocumentationComments( + indent, anEnum.documentationComments, _docCommentSpec); - final String? result = propertyTypeForDartTypeMap[field.type.baseName]; - if (result == null) { - return 'strong'; - } else { - return result; + indent.write('typedef NS_ENUM(NSUInteger, $enumName) '); + indent.scoped('{', '};', () { + enumerate(anEnum.members, (int index, final EnumMember member) { + addDocumentationComments( + indent, member.documentationComments, _docCommentSpec); + // Capitalized first letter to ensure Swift compatibility + indent.writeln( + '$enumName${member.name[0].toUpperCase()}${member.name.substring(1)} = $index,'); + }); + }); } -} - -bool _isNullable(HostDatatype hostDatatype, TypeDeclaration type) => - hostDatatype.datatype.contains('*') && type.isNullable; -/// Writes the method declaration for the initializer. -/// -/// Example '+ (instancetype)makeWithFoo:(NSString *)foo' -void _writeInitializerDeclaration(Indent indent, Class klass, - List classes, List enums, String? prefix) { - final List enumNames = enums.map((Enum x) => x.name).toList(); - indent.write('+ (instancetype)makeWith'); - bool isFirst = true; - indent.nest(2, () { - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - final String label = isFirst ? _capitalize(field.name) : field.name; - final void Function(String) printer = isFirst - ? indent.add - : (String x) { - indent.addln(''); - indent.write(x); - }; - isFirst = false; - final HostDatatype hostDatatype = getFieldHostDatatype( - field, - classes, - enums, - (TypeDeclaration x) => _objcTypePtrForPrimitiveDartType(prefix, x), - customResolver: enumNames.contains(field.type.baseName) - ? (String x) => _className(prefix, x) - : (String x) => '${_className(prefix, x)} *'); - final String nullable = - _isNullable(hostDatatype, field.type) ? 'nullable ' : ''; - printer('$label:($nullable${hostDatatype.datatype})${field.name}'); + @override + void writeDataClasses( + ObjcOptions generatorOptions, Root root, Indent indent) { + indent.writeln(''); + for (final Class klass in root.classes) { + indent.writeln( + '@class ${_className(generatorOptions.prefix, klass.name)};'); } - }); -} + indent.writeln(''); + super.writeDataClasses(generatorOptions, root, indent); + } + + @override + void writeDataClass( + ObjcOptions generatorOptions, Root root, Indent indent, Class klass) { + final List classes = root.classes; + final List enums = root.enums; + final String? prefix = generatorOptions.prefix; + final List customEnumNames = enums.map((Enum x) => x.name).toList(); -/// Writes the class declaration for a data class. -/// -/// Example: -/// @interface Foo : NSObject -/// @property (nonatomic, copy) NSString *bar; -/// @end -void _writeClassDeclarations( - Indent indent, List classes, List enums, String? prefix) { - final List enumNames = enums.map((Enum x) => x.name).toList(); - for (final Class klass in classes) { addDocumentationComments( indent, klass.documentationComments, _docCommentSpec); @@ -235,7 +165,8 @@ void _writeClassDeclarations( '$_docCommentPrefix `init` unavailable to enforce nonnull fields, see the `make` class method.'); indent.writeln('- (instancetype)init NS_UNAVAILABLE;'); } - _writeInitializerDeclaration(indent, klass, classes, enums, prefix); + _writeObjcSourceClassInitializerDeclaration( + indent, klass, classes, enums, prefix); indent.addln(';'); } for (final NamedType field in getFieldsInSerializationOrder(klass)) { @@ -244,13 +175,13 @@ void _writeClassDeclarations( classes, enums, (TypeDeclaration x) => _objcTypePtrForPrimitiveDartType(prefix, x), - customResolver: enumNames.contains(field.type.baseName) + customResolver: customEnumNames.contains(field.type.baseName) ? (String x) => _className(prefix, x) : (String x) => '${_className(prefix, x)} *'); late final String propertyType; addDocumentationComments( indent, field.documentationComments, _docCommentSpec); - if (enumNames.contains(field.type.baseName)) { + if (customEnumNames.contains(field.type.baseName)) { propertyType = 'assign'; } else { propertyType = _propertyTypeForDartType(field); @@ -263,440 +194,348 @@ void _writeClassDeclarations( indent.writeln('@end'); indent.writeln(''); } -} -/// Generates the name of the codec that will be generated. -String _getCodecName(String? prefix, String className) => - '${_className(prefix, className)}Codec'; + @override + void writeClassEncode( + ObjcOptions generatorOptions, + Root root, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) {} -/// Generates the name of the function for accessing the codec instance used by -/// the api class named [className]. -String _getCodecGetterName(String? prefix, String className) => - '${_className(prefix, className)}GetCodec'; + @override + void writeClassDecode( + ObjcOptions generatorOptions, + Root root, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) {} -/// Writes the codec that will be used for encoding messages for the [api]. -/// -/// Example: -/// @interface FooHostApiCodecReader : FlutterStandardReader -/// ... -/// @interface FooHostApiCodecWriter : FlutterStandardWriter -/// ... -/// @interface FooHostApiCodecReaderWriter : FlutterStandardReaderWriter -/// ... -/// NSObject *FooHostApiCodecGetCodec() {...} -void _writeCodec( - Indent indent, String name, ObjcOptions options, Api api, Root root) { - assert(getCodecClasses(api, root).isNotEmpty); - final Iterable codecClasses = getCodecClasses(api, root); - final String readerWriterName = '${name}ReaderWriter'; - final String readerName = '${name}Reader'; - final String writerName = '${name}Writer'; - indent.writeln('@interface $readerName : FlutterStandardReader'); - indent.writeln('@end'); - indent.writeln('@implementation $readerName'); - indent.writeln('- (nullable id)readValueOfType:(UInt8)type '); - indent.scoped('{', '}', () { - indent.write('switch (type) '); - indent.scoped('{', '}', () { - for (final EnumeratedClass customClass in codecClasses) { - indent.write('case ${customClass.enumeration}: '); - indent.writeScoped('', '', () { - indent.writeln( - 'return [${_className(options.prefix, customClass.name)} fromList:[self readValue]];'); - }); + @override + void writeApis(ObjcOptions generatorOptions, Root root, Indent indent) { + super.writeApis(generatorOptions, root, indent); + indent.writeln('NS_ASSUME_NONNULL_END'); + } + + @override + void writeFlutterApi( + ObjcOptions generatorOptions, + Root root, + Indent indent, + Api api, + ) { + indent.writeln( + '$_docCommentPrefix The codec used by ${_className(generatorOptions.prefix, api.name)}.'); + indent.writeln( + 'NSObject *${_getCodecGetterName(generatorOptions.prefix, api.name)}(void);'); + indent.addln(''); + final String apiName = _className(generatorOptions.prefix, api.name); + addDocumentationComments( + indent, api.documentationComments, _docCommentSpec); + + indent.writeln('@interface $apiName : NSObject'); + indent.writeln( + '- (instancetype)initWithBinaryMessenger:(id)binaryMessenger;'); + for (final Method func in api.methods) { + final _ObjcPtr returnType = + _objcTypeForDartType(generatorOptions.prefix, func.returnType); + final String callbackType = _callbackForType(func.returnType, returnType); + addDocumentationComments( + indent, func.documentationComments, _docCommentSpec); + + indent.writeln('${_makeObjcSignature( + func: func, + options: generatorOptions, + returnType: 'void', + lastArgName: 'completion', + lastArgType: callbackType, + isEnum: (TypeDeclaration t) => isEnum(root, t), + )};'); + } + indent.writeln('@end'); + indent.writeln(''); + } + + @override + void writeHostApi( + ObjcOptions generatorOptions, + Root root, + Indent indent, + Api api, + ) { + indent.writeln( + '$_docCommentPrefix The codec used by ${_className(generatorOptions.prefix, api.name)}.'); + indent.writeln( + 'NSObject *${_getCodecGetterName(generatorOptions.prefix, api.name)}(void);'); + indent.addln(''); + final String apiName = _className(generatorOptions.prefix, api.name); + addDocumentationComments( + indent, api.documentationComments, _docCommentSpec); + + indent.writeln('@protocol $apiName'); + for (final Method func in api.methods) { + final _ObjcPtr returnTypeName = + _objcTypeForDartType(generatorOptions.prefix, func.returnType); + + String? lastArgName; + String? lastArgType; + String? returnType; + if (func.isAsynchronous) { + returnType = 'void'; + if (func.returnType.isVoid) { + lastArgType = 'void(^)(FlutterError *_Nullable)'; + lastArgName = 'completion'; + } else { + lastArgType = + 'void(^)(${returnTypeName.ptr}_Nullable, FlutterError *_Nullable)'; + lastArgName = 'completion'; + } + } else { + returnType = func.returnType.isVoid + ? 'void' + : 'nullable ${returnTypeName.ptr.trim()}'; + lastArgType = 'FlutterError *_Nullable *_Nonnull'; + lastArgName = 'error'; } - indent.write('default:'); - indent.writeScoped('', '', () { - indent.writeln('return [super readValueOfType:type];'); - }); - }); - }); - indent.writeln('@end'); - indent.addln(''); - indent.writeln('@interface $writerName : FlutterStandardWriter'); - indent.writeln('@end'); - indent.writeln('@implementation $writerName'); - indent.writeln('- (void)writeValue:(id)value '); - indent.scoped('{', '}', () { - for (final EnumeratedClass customClass in codecClasses) { - indent.write( - 'if ([value isKindOfClass:[${_className(options.prefix, customClass.name)} class]]) '); - indent.scoped('{', '} else ', () { - indent.writeln('[self writeByte:${customClass.enumeration}];'); - indent.writeln('[self writeValue:[value toList]];'); - }); + final List generatorComments = []; + if (!func.returnType.isNullable && + !func.returnType.isVoid && + !func.isAsynchronous) { + generatorComments.add(' @return `nil` only when `error != nil`.'); + } + addDocumentationComments( + indent, func.documentationComments, _docCommentSpec, + generatorComments: generatorComments); + + final String signature = _makeObjcSignature( + func: func, + options: generatorOptions, + returnType: returnType, + lastArgName: lastArgName, + lastArgType: lastArgType, + isEnum: (TypeDeclaration t) => isEnum(root, t), + ); + indent.writeln('$signature;'); } - indent.scoped('{', '}', () { - indent.writeln('[super writeValue:value];'); - }); - }); - indent.writeln('@end'); - indent.addln(''); - indent.format(''' -@interface $readerWriterName : FlutterStandardReaderWriter -@end -@implementation $readerWriterName -- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { -\treturn [[$writerName alloc] initWithData:data]; -} -- (FlutterStandardReader *)readerWithData:(NSData *)data { -\treturn [[$readerName alloc] initWithData:data]; -} -@end -'''); + indent.writeln('@end'); + indent.writeln(''); + indent.writeln( + 'extern void ${apiName}Setup(id binaryMessenger, NSObject<$apiName> *_Nullable api);'); + indent.writeln(''); + } } -void _writeCodecGetter( - Indent indent, String name, ObjcOptions options, Api api, Root root) { - final String readerWriterName = '${name}ReaderWriter'; +/// Generates Objc .m file. +class ObjcSourceGenerator extends StructuredGenerator { + /// Constructor. + const ObjcSourceGenerator(); - indent.write( - 'NSObject *${_getCodecGetterName(options.prefix, api.name)}() '); - indent.scoped('{', '}', () { - indent.writeln('static FlutterStandardMessageCodec *sSharedObject = nil;'); - if (getCodecClasses(api, root).isNotEmpty) { - indent.writeln('static dispatch_once_t sPred = 0;'); - indent.write('dispatch_once(&sPred, ^'); - indent.scoped('{', '});', () { - indent.writeln( - '$readerWriterName *readerWriter = [[$readerWriterName alloc] init];'); - indent.writeln( - 'sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter];'); - }); - } else { - indent.writeln( - 'sSharedObject = [FlutterStandardMessageCodec sharedInstance];'); + @override + void writeFilePrologue( + ObjcOptions generatorOptions, Root root, Indent indent) { + if (generatorOptions.copyrightHeader != null) { + addLines(indent, generatorOptions.copyrightHeader!, linePrefix: '// '); } + indent.writeln('// $generatedCodeWarning'); + indent.writeln('// $seeAlsoWarning'); + indent.addln(''); + } - indent.writeln('return sSharedObject;'); - }); -} + @override + void writeFileImports( + ObjcOptions generatorOptions, Root root, Indent indent) { + indent.writeln('#import "${generatorOptions.headerIncludePath}"'); + indent.writeln('#import '); + indent.addln(''); -String _capitalize(String str) => - (str.isEmpty) ? '' : str[0].toUpperCase() + str.substring(1); - -/// Returns the components of the objc selector that will be generated from -/// [func], ie the strings between the semicolons. [lastSelectorComponent] is -/// the last component of the selector aka the label of the last parameter which -/// isn't included in [func]. -/// Example: -/// f('void add(int x, int y)', 'count') -> ['addX', 'y', 'count'] -Iterable _getSelectorComponents( - Method func, String lastSelectorComponent) sync* { - if (func.objcSelector.isEmpty) { - final Iterator it = func.arguments.iterator; - final bool hasArguments = it.moveNext(); - final String namePostfix = - (lastSelectorComponent.isNotEmpty && func.arguments.isEmpty) - ? 'With${_capitalize(lastSelectorComponent)}' - : ''; - yield '${func.name}${hasArguments ? _capitalize(func.arguments[0].name) : namePostfix}'; - while (it.moveNext()) { - yield it.current.name; - } - } else { - assert(':'.allMatches(func.objcSelector).length == func.arguments.length); - final Iterable customComponents = func.objcSelector - .split(':') - .where((String element) => element.isNotEmpty); - yield* customComponents; - } - if (lastSelectorComponent.isNotEmpty && func.arguments.isNotEmpty) { - yield lastSelectorComponent; - } -} - -/// Generates the objc source code method signature for [func]. [returnType] is -/// the return value of method, this may not match the return value in [func] -/// since [func] may be asynchronous. The function requires you specify a -/// [lastArgType] and [lastArgName] for arguments that aren't represented in -/// [func]. This is typically used for passing in 'error' or 'completion' -/// arguments that don't exist in the pigeon file but are required in the objc -/// output. [argNameFunc] is the function used to generate the argument name -/// [func.arguments]. -String _makeObjcSignature({ - required Method func, - required ObjcOptions options, - required String returnType, - required String lastArgType, - required String lastArgName, - required bool Function(TypeDeclaration) isEnum, - String Function(int, NamedType)? argNameFunc, -}) { - argNameFunc = argNameFunc ?? (int _, NamedType e) => e.name; - final Iterable argNames = - followedByOne(indexMap(func.arguments, argNameFunc), lastArgName); - final Iterable selectorComponents = - _getSelectorComponents(func, lastArgName); - final Iterable argTypes = followedByOne( - func.arguments.map((NamedType arg) { - if (isEnum(arg.type)) { - return _className(options.prefix, arg.type.baseName); - } else { - final String nullable = arg.type.isNullable ? 'nullable ' : ''; - final _ObjcPtr argType = _objcTypeForDartType(options.prefix, arg.type); - return '$nullable${argType.ptr.trim()}'; - } - }), - lastArgType, - ); - - final String argSignature = map3( - selectorComponents, - argTypes, - argNames, - (String component, String argType, String argName) => - '$component:($argType)$argName', - ).join(' '); - return '- ($returnType)$argSignature'; -} - -/// Writes the declaration for an host [Api]. -/// -/// Example: -/// @protocol Foo -/// - (NSInteger)add:(NSInteger)x to:(NSInteger)y error:(NSError**)error; -/// @end -/// -/// extern void FooSetup(id binaryMessenger, NSObject *_Nullable api); -void _writeHostApiDeclaration( - Indent indent, Api api, ObjcOptions options, Root root) { - final String apiName = _className(options.prefix, api.name); - addDocumentationComments(indent, api.documentationComments, _docCommentSpec); - - indent.writeln('@protocol $apiName'); - for (final Method func in api.methods) { - final _ObjcPtr returnTypeName = - _objcTypeForDartType(options.prefix, func.returnType); - - String? lastArgName; - String? lastArgType; - String? returnType; - if (func.isAsynchronous) { - returnType = 'void'; - if (func.returnType.isVoid) { - lastArgType = 'void(^)(FlutterError *_Nullable)'; - lastArgName = 'completion'; - } else { - lastArgType = - 'void(^)(${returnTypeName.ptr}_Nullable, FlutterError *_Nullable)'; - lastArgName = 'completion'; - } - } else { - returnType = func.returnType.isVoid - ? 'void' - : 'nullable ${returnTypeName.ptr.trim()}'; - lastArgType = 'FlutterError *_Nullable *_Nonnull'; - lastArgName = 'error'; - } - final List generatorComments = []; - if (!func.returnType.isNullable && - !func.returnType.isVoid && - !func.isAsynchronous) { - generatorComments.add(' @return `nil` only when `error != nil`.'); - } - addDocumentationComments( - indent, func.documentationComments, _docCommentSpec, - generatorComments: generatorComments); - - final String signature = _makeObjcSignature( - func: func, - options: options, - returnType: returnType, - lastArgName: lastArgName, - lastArgType: lastArgType, - isEnum: (TypeDeclaration t) => isEnum(root, t), - ); - indent.writeln('$signature;'); - } - indent.writeln('@end'); - indent.writeln(''); - indent.writeln( - 'extern void ${apiName}Setup(id binaryMessenger, NSObject<$apiName> *_Nullable api);'); - indent.writeln(''); -} - -/// Writes the declaration for an flutter [Api]. -/// -/// Example: -/// -/// @interface Foo : NSObject -/// - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; -/// - (void)add:(NSInteger)x to:(NSInteger)y completion:(void(^)(NSError *, NSInteger result)completion; -/// @end -void _writeFlutterApiDeclaration( - Indent indent, Api api, ObjcOptions options, Root root) { - final String apiName = _className(options.prefix, api.name); - addDocumentationComments(indent, api.documentationComments, _docCommentSpec); - - indent.writeln('@interface $apiName : NSObject'); - indent.writeln( - '- (instancetype)initWithBinaryMessenger:(id)binaryMessenger;'); - for (final Method func in api.methods) { - final _ObjcPtr returnType = - _objcTypeForDartType(options.prefix, func.returnType); - final String callbackType = _callbackForType(func.returnType, returnType); - addDocumentationComments( - indent, func.documentationComments, _docCommentSpec); - - indent.writeln('${_makeObjcSignature( - func: func, - options: options, - returnType: 'void', - lastArgName: 'completion', - lastArgType: callbackType, - isEnum: (TypeDeclaration t) => isEnum(root, t), - )};'); + indent.writeln('#if !__has_feature(objc_arc)'); + indent.writeln('#error File requires ARC to be enabled.'); + indent.writeln('#endif'); + indent.addln(''); } - indent.writeln('@end'); -} -/// Generates the ".h" file for the AST represented by [root] to [sink] with the -/// provided [options]. -void generateObjcHeader(ObjcOptions options, Root root, StringSink sink) { - final Indent indent = Indent(sink); + @override + void writeDataClasses( + ObjcOptions generatorOptions, Root root, Indent indent) { + _writeObjcSourceHelperFunctions(indent); + indent.addln(''); - void writeHeader() { - if (options.copyrightHeader != null) { - addLines(indent, options.copyrightHeader!, linePrefix: '// '); + for (final Class klass in root.classes) { + _writeObjcSourceDataClassExtension(generatorOptions, indent, klass); } - indent.writeln('// $generatedCodeWarning'); - indent.writeln('// $seeAlsoWarning'); + indent.writeln(''); + super.writeDataClasses(generatorOptions, root, indent); } - void writeImports() { - indent.writeln('#import '); - } + @override + void writeDataClass( + ObjcOptions generatorOptions, Root root, Indent indent, Class klass) { + final Set customClassNames = + root.classes.map((Class x) => x.name).toSet(); + final Set customEnumNames = + root.enums.map((Enum x) => x.name).toSet(); + final String className = _className(generatorOptions.prefix, klass.name); - void writeForwardDeclarations() { - indent.writeln('@protocol FlutterBinaryMessenger;'); - indent.writeln('@protocol FlutterMessageCodec;'); - indent.writeln('@class FlutterError;'); - indent.writeln('@class FlutterStandardTypedData;'); + indent.writeln('@implementation $className'); + _writeObjcSourceClassInitializer(generatorOptions, root, indent, klass, + customClassNames, customEnumNames, className); + writeClassDecode(generatorOptions, root, indent, klass, customClassNames, + customEnumNames); + writeClassEncode(generatorOptions, root, indent, klass, customClassNames, + customEnumNames); + indent.writeln('@end'); + indent.writeln(''); } - void writeEnum(Enum anEnum) { - final String enumName = _className(options.prefix, anEnum.name); - addDocumentationComments( - indent, anEnum.documentationComments, _docCommentSpec); - - indent.write('typedef NS_ENUM(NSUInteger, $enumName) '); - indent.scoped('{', '};', () { - enumerate(anEnum.members, (int index, final EnumMember member) { - addDocumentationComments( - indent, member.documentationComments, _docCommentSpec); - // Capitalized first letter to ensure Swift compatibility - indent.writeln( - '$enumName${member.name[0].toUpperCase()}${member.name.substring(1)} = $index,'); + @override + void writeClassEncode( + ObjcOptions generatorOptions, + Root root, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) { + indent.write('- (NSArray *)toList '); + indent.scoped('{', '}', () { + indent.write('return'); + indent.scoped(' @[', '];', () { + for (final NamedType field in klass.fields) { + indent.writeln( + '${_arrayValue(customClassNames, customEnumNames, field)},'); + } }); }); } - writeHeader(); - writeImports(); - writeForwardDeclarations(); - indent.writeln(''); - - indent.writeln('NS_ASSUME_NONNULL_BEGIN'); - - for (final Enum anEnum in root.enums) { - indent.writeln(''); - writeEnum(anEnum); - } - indent.writeln(''); - - for (final Class klass in root.classes) { - indent.writeln('@class ${_className(options.prefix, klass.name)};'); - } - - indent.writeln(''); - - _writeClassDeclarations(indent, root.classes, root.enums, options.prefix); + @override + void writeClassDecode( + ObjcOptions generatorOptions, + Root root, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) { + final String className = _className(generatorOptions.prefix, klass.name); + indent.write('+ ($className *)fromList:(NSArray *)list '); + indent.scoped('{', '}', () { + const String resultName = 'pigeonResult'; + indent.writeln('$className *$resultName = [[$className alloc] init];'); + enumerate(getFieldsInSerializationOrder(klass), + (int index, final NamedType field) { + if (customEnumNames.contains(field.type.baseName)) { + indent.writeln( + '$resultName.${field.name} = [${_listGetter(customClassNames, 'list', field, index, generatorOptions.prefix)} integerValue];'); + } else { + indent.writeln( + '$resultName.${field.name} = ${_listGetter(customClassNames, 'list', field, index, generatorOptions.prefix)};'); + if (!field.type.isNullable) { + indent.writeln('NSAssert($resultName.${field.name} != nil, @"");'); + } + } + }); + indent.writeln('return $resultName;'); + }); - for (final Api api in root.apis) { indent.writeln( - '$_docCommentPrefix The codec used by ${_className(options.prefix, api.name)}.'); - indent.writeln( - 'NSObject *${_getCodecGetterName(options.prefix, api.name)}(void);'); - indent.addln(''); - if (api.location == ApiLocation.host) { - _writeHostApiDeclaration(indent, api, options, root); - } else if (api.location == ApiLocation.flutter) { - _writeFlutterApiDeclaration(indent, api, options, root); - } + '+ (nullable $className *)nullableFromList:(NSArray *)list { return (list) ? [$className fromList:list] : nil; }'); } - indent.writeln('NS_ASSUME_NONNULL_END'); -} - -String _listGetter(List classNames, String list, NamedType field, - int index, String? prefix) { - if (classNames.contains(field.type.baseName)) { - String className = field.type.baseName; - if (prefix != null) { - className = '$prefix$className'; + void _writeCodecAndGetter( + ObjcOptions generatorOptions, Root root, Indent indent, Api api) { + final String codecName = _getCodecName(generatorOptions.prefix, api.name); + if (getCodecClasses(api, root).isNotEmpty) { + _writeCodec(indent, codecName, generatorOptions, api, root); + indent.addln(''); } - return '[$className nullableFromList:(GetNullableObjectAtIndex($list, $index))]'; - } else { - return 'GetNullableObjectAtIndex($list, $index)'; + _writeCodecGetter(indent, codecName, generatorOptions, api, root); + indent.addln(''); } -} -String _arrayValue( - List classNames, List enumNames, NamedType field) { - if (classNames.contains(field.type.baseName)) { - return '(self.${field.name} ? [self.${field.name} toList] : [NSNull null])'; - } else if (enumNames.contains(field.type.baseName)) { - return '@(self.${field.name})'; - } else { - return '(self.${field.name} ?: [NSNull null])'; + @override + void writeFlutterApi( + ObjcOptions generatorOptions, + Root root, + Indent indent, + Api api, + ) { + assert(api.location == ApiLocation.flutter); + final String apiName = _className(generatorOptions.prefix, api.name); + + _writeCodecAndGetter(generatorOptions, root, indent, api); + + _writeExtension(indent, apiName); + indent.addln(''); + indent.writeln('@implementation $apiName'); + indent.addln(''); + _writeInitializer(indent); + for (final Method func in api.methods) { + _writeMethod(generatorOptions, root, indent, api, func); + } + indent.writeln('@end'); + indent.writeln(''); } -} - -String _getSelector(Method func, String lastSelectorComponent) => - '${_getSelectorComponents(func, lastSelectorComponent).join(':')}:'; - -/// Returns an argument name that can be used in a context where it is possible to collide. -String _getSafeArgName(int count, NamedType arg) => - arg.name.isEmpty ? 'arg$count' : 'arg_${arg.name}'; - -/// Writes the definition code for a host [Api]. -/// See also: [_writeHostApiDeclaration] -void _writeHostApiSource( - Indent indent, ObjcOptions options, Api api, Root root) { - assert(api.location == ApiLocation.host); - final String apiName = _className(options.prefix, api.name); - void writeChannelAllocation(Method func, String varName, String? taskQueue) { - indent.writeln('FlutterBasicMessageChannel *$varName ='); - indent.inc(); - indent.writeln('[[FlutterBasicMessageChannel alloc]'); - indent.inc(); - indent.writeln('initWithName:@"${makeChannelName(api, func)}"'); - indent.writeln('binaryMessenger:binaryMessenger'); - indent.write('codec:'); - indent.add('${_getCodecGetterName(options.prefix, api.name)}()'); + @override + void writeHostApi( + ObjcOptions generatorOptions, + Root root, + Indent indent, + Api api, + ) { + assert(api.location == ApiLocation.host); + final String apiName = _className(generatorOptions.prefix, api.name); + + _writeCodecAndGetter(generatorOptions, root, indent, api); + + const String channelName = 'channel'; + indent.write( + 'void ${apiName}Setup(id binaryMessenger, NSObject<$apiName> *api) '); + indent.scoped('{', '}', () { + for (final Method func in api.methods) { + indent.write(''); + addDocumentationComments( + indent, func.documentationComments, _docCommentSpec); - if (taskQueue != null) { - indent.addln(''); - indent.addln('taskQueue:$taskQueue];'); - } else { - indent.addln('];'); - } - indent.dec(); - indent.dec(); + indent.scoped('{', '}', () { + String? taskQueue; + if (func.taskQueueType != TaskQueueType.serial) { + taskQueue = 'taskQueue'; + indent.writeln( + 'NSObject *$taskQueue = [binaryMessenger makeBackgroundTaskQueue];'); + } + _writeChannelAllocation( + generatorOptions, indent, api, func, channelName, taskQueue); + indent.write('if (api) '); + indent.scoped('{', '}', () { + _writeChannelApiBinding( + generatorOptions, root, indent, apiName, func, channelName); + }); + indent.write('else '); + indent.scoped('{', '}', () { + indent.writeln('[$channelName setMessageHandler:nil];'); + }); + }); + } + }); } - void writeChannelApiBinding(Method func, String channel) { + void _writeChannelApiBinding(ObjcOptions generatorOptions, Root root, + Indent indent, String apiName, Method func, String channel) { void unpackArgs(String variable, Iterable argNames) { indent.writeln('NSArray *args = $variable;'); map3(wholeNumbers.take(func.arguments.length), argNames, func.arguments, (int count, String argName, NamedType arg) { if (isEnum(root, arg.type)) { - return '${_className(options.prefix, arg.type.baseName)} $argName = [GetNullableObjectAtIndex(args, $count) integerValue];'; + return '${_className(generatorOptions.prefix, arg.type.baseName)} $argName = [GetNullableObjectAtIndex(args, $count) integerValue];'; } else { final _ObjcPtr argType = - _objcTypeForDartType(options.prefix, arg.type); + _objcTypeForDartType(generatorOptions.prefix, arg.type); return '${argType.ptr}$argName = GetNullableObjectAtIndex(args, $count);'; } }).forEach(indent.writeln); @@ -758,7 +597,7 @@ void _writeHostApiSource( '[$channel setMessageHandler:^(id _Nullable message, FlutterReply callback) '); indent.scoped('{', '}];', () { final _ObjcPtr returnType = - _objcTypeForDartType(options.prefix, func.returnType); + _objcTypeForDartType(generatorOptions.prefix, func.returnType); final Iterable selectorComponents = _getSelectorComponents(func, lastSelectorComponent); final Iterable argNames = @@ -782,146 +621,28 @@ void _writeHostApiSource( }); } - const String channelName = 'channel'; - indent.write( - 'void ${apiName}Setup(id binaryMessenger, NSObject<$apiName> *api) '); - indent.scoped('{', '}', () { - for (final Method func in api.methods) { - indent.write(''); - addDocumentationComments( - indent, func.documentationComments, _docCommentSpec); + void _writeChannelAllocation(ObjcOptions generatorOptions, Indent indent, + Api api, Method func, String varName, String? taskQueue) { + indent.writeln('FlutterBasicMessageChannel *$varName ='); + indent.inc(); + indent.writeln('[[FlutterBasicMessageChannel alloc]'); + indent.inc(); + indent.writeln('initWithName:@"${makeChannelName(api, func)}"'); + indent.writeln('binaryMessenger:binaryMessenger'); + indent.write('codec:'); + indent.add('${_getCodecGetterName(generatorOptions.prefix, api.name)}()'); - indent.scoped('{', '}', () { - String? taskQueue; - if (func.taskQueueType != TaskQueueType.serial) { - taskQueue = 'taskQueue'; - indent.writeln( - 'NSObject *$taskQueue = [binaryMessenger makeBackgroundTaskQueue];'); - } - writeChannelAllocation(func, channelName, taskQueue); - indent.write('if (api) '); - indent.scoped('{', '}', () { - writeChannelApiBinding(func, channelName); - }); - indent.write('else '); - indent.scoped('{', '}', () { - indent.writeln('[$channelName setMessageHandler:nil];'); - }); - }); + if (taskQueue != null) { + indent.addln(''); + indent.addln('taskQueue:$taskQueue];'); + } else { + indent.addln('];'); } - }); -} - -/// Writes the definition code for a flutter [Api]. -/// See also: [_writeFlutterApiDeclaration] -void _writeFlutterApiSource( - Indent indent, ObjcOptions options, Api api, Root root) { - assert(api.location == ApiLocation.flutter); - final String apiName = _className(options.prefix, api.name); - - void writeExtension() { - indent.writeln('@interface $apiName ()'); - indent.writeln( - '@property (nonatomic, strong) NSObject *binaryMessenger;'); - indent.writeln('@end'); + indent.dec(); + indent.dec(); } - void writeInitializer() { - indent.write( - '- (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger '); - indent.scoped('{', '}', () { - indent.writeln('self = [super init];'); - indent.write('if (self) '); - indent.scoped('{', '}', () { - indent.writeln('_binaryMessenger = binaryMessenger;'); - }); - indent.writeln('return self;'); - }); - } - - void writeMethod(Method func) { - final _ObjcPtr returnType = - _objcTypeForDartType(options.prefix, func.returnType); - final String callbackType = _callbackForType(func.returnType, returnType); - - String argNameFunc(int count, NamedType arg) => _getSafeArgName(count, arg); - final Iterable argNames = indexMap(func.arguments, argNameFunc); - String sendArgument; - if (func.arguments.isEmpty) { - sendArgument = 'nil'; - } else { - String makeVarOrNSNullExpression(String x) => '$x ?: [NSNull null]'; - sendArgument = '@[${argNames.map(makeVarOrNSNullExpression).join(', ')}]'; - } - indent.write(_makeObjcSignature( - func: func, - options: options, - returnType: 'void', - lastArgName: 'completion', - lastArgType: callbackType, - argNameFunc: argNameFunc, - isEnum: (TypeDeclaration t) => isEnum(root, t), - )); - indent.scoped(' {', '}', () { - indent.writeln('FlutterBasicMessageChannel *channel ='); - indent.inc(); - indent.writeln('[FlutterBasicMessageChannel'); - indent.inc(); - indent.writeln('messageChannelWithName:@"${makeChannelName(api, func)}"'); - indent.writeln('binaryMessenger:self.binaryMessenger'); - indent.write('codec:${_getCodecGetterName(options.prefix, api.name)}()'); - indent.addln('];'); - indent.dec(); - indent.dec(); - indent.write('[channel sendMessage:$sendArgument reply:^(id reply) '); - indent.scoped('{', '}];', () { - if (func.returnType.isVoid) { - indent.writeln('completion(nil);'); - } else { - indent.writeln('${returnType.ptr}output = reply;'); - indent.writeln('completion(output, nil);'); - } - }); - }); - } - - writeExtension(); - indent.addln(''); - indent.writeln('@implementation $apiName'); - indent.addln(''); - writeInitializer(); - api.methods.forEach(writeMethod); - indent.writeln('@end'); -} - -/// Generates the ".m" file for the AST represented by [root] to [sink] with the -/// provided [options]. -void generateObjcSource(ObjcOptions options, Root root, StringSink sink) { - final Indent indent = Indent(sink); - final List classNames = - root.classes.map((Class x) => x.name).toList(); - final List enumNames = root.enums.map((Enum x) => x.name).toList(); - - void writeHeader() { - if (options.copyrightHeader != null) { - addLines(indent, options.copyrightHeader!, linePrefix: '// '); - } - indent.writeln('// $generatedCodeWarning'); - indent.writeln('// $seeAlsoWarning'); - } - - void writeImports() { - indent.writeln('#import "${options.headerIncludePath}"'); - indent.writeln('#import '); - } - - void writeArcEnforcer() { - indent.writeln('#if !__has_feature(objc_arc)'); - indent.writeln('#error File requires ARC to be enabled.'); - indent.writeln('#endif'); - } - - void writeHelperFunctions() { + void _writeObjcSourceHelperFunctions(Indent indent) { indent.format(''' static NSArray *wrapResult(id result, FlutterError *error) { \tif (error) { @@ -941,105 +662,457 @@ static id GetNullableObjectAtIndex(NSArray* array, NSInteger key) { '''); } - void writeDataClassExtension(Class klass) { - final String className = _className(options.prefix, klass.name); + void _writeObjcSourceDataClassExtension( + ObjcOptions languageOptions, Indent indent, Class klass) { + final String className = _className(languageOptions.prefix, klass.name); indent.writeln('@interface $className ()'); indent.writeln('+ ($className *)fromList:(NSArray *)list;'); indent .writeln('+ (nullable $className *)nullableFromList:(NSArray *)list;'); indent.writeln('- (NSArray *)toList;'); indent.writeln('@end'); + indent.writeln(''); } - void writeDataClassImplementation(Class klass) { - final String className = _className(options.prefix, klass.name); - void writeInitializer() { - _writeInitializerDeclaration( - indent, klass, root.classes, root.enums, options.prefix); - indent.writeScoped(' {', '}', () { - const String result = 'pigeonResult'; - indent.writeln('$className* $result = [[$className alloc] init];'); - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - indent.writeln('$result.${field.name} = ${field.name};'); - } - indent.writeln('return $result;'); - }); - } + void _writeObjcSourceClassInitializer( + ObjcOptions languageOptions, + Root root, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + String className, + ) { + _writeObjcSourceClassInitializerDeclaration( + indent, klass, root.classes, root.enums, languageOptions.prefix); + indent.writeScoped(' {', '}', () { + const String result = 'pigeonResult'; + indent.writeln('$className* $result = [[$className alloc] init];'); + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + indent.writeln('$result.${field.name} = ${field.name};'); + } + indent.writeln('return $result;'); + }); + } - void writeFromList() { - indent.write('+ ($className *)fromList:(NSArray *)list '); + /// Writes the codec that will be used for encoding messages for the [api]. + /// + /// Example: + /// @interface FooHostApiCodecReader : FlutterStandardReader + /// ... + /// @interface FooHostApiCodecWriter : FlutterStandardWriter + /// ... + /// @interface FooHostApiCodecReaderWriter : FlutterStandardReaderWriter + /// ... + /// NSObject *FooHostApiCodecGetCodec() {...} + void _writeCodec( + Indent indent, String name, ObjcOptions options, Api api, Root root) { + assert(getCodecClasses(api, root).isNotEmpty); + final Iterable codecClasses = getCodecClasses(api, root); + final String readerWriterName = '${name}ReaderWriter'; + final String readerName = '${name}Reader'; + final String writerName = '${name}Writer'; + indent.writeln('@interface $readerName : FlutterStandardReader'); + indent.writeln('@end'); + indent.writeln('@implementation $readerName'); + indent.writeln('- (nullable id)readValueOfType:(UInt8)type '); + indent.scoped('{', '}', () { + indent.write('switch (type) '); indent.scoped('{', '}', () { - const String resultName = 'pigeonResult'; - indent.writeln('$className *$resultName = [[$className alloc] init];'); - enumerate(getFieldsInSerializationOrder(klass), - (int index, final NamedType field) { - if (enumNames.contains(field.type.baseName)) { - indent.writeln( - '$resultName.${field.name} = [${_listGetter(classNames, 'list', field, index, options.prefix)} integerValue];'); - } else { + for (final EnumeratedClass customClass in codecClasses) { + indent.write('case ${customClass.enumeration}: '); + indent.writeScoped('', '', () { indent.writeln( - '$resultName.${field.name} = ${_listGetter(classNames, 'list', field, index, options.prefix)};'); - if (!field.type.isNullable) { - indent - .writeln('NSAssert($resultName.${field.name} != nil, @"");'); - } - } + 'return [${_className(options.prefix, customClass.name)} fromList:[self readValue]];'); + }); + } + indent.write('default:'); + indent.writeScoped('', '', () { + indent.writeln('return [super readValueOfType:type];'); }); - indent.writeln('return $resultName;'); }); + }); + indent.writeln('@end'); + indent.addln(''); + indent.writeln('@interface $writerName : FlutterStandardWriter'); + indent.writeln('@end'); + indent.writeln('@implementation $writerName'); + indent.writeln('- (void)writeValue:(id)value '); + indent.scoped('{', '}', () { + for (final EnumeratedClass customClass in codecClasses) { + indent.write( + 'if ([value isKindOfClass:[${_className(options.prefix, customClass.name)} class]]) '); + indent.scoped('{', '} else ', () { + indent.writeln('[self writeByte:${customClass.enumeration}];'); + indent.writeln('[self writeValue:[value toList]];'); + }); + } + indent.scoped('{', '}', () { + indent.writeln('[super writeValue:value];'); + }); + }); + indent.writeln('@end'); + indent.addln(''); + indent.format(''' +@interface $readerWriterName : FlutterStandardReaderWriter +@end +@implementation $readerWriterName +- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { +\treturn [[$writerName alloc] initWithData:data]; +} +- (FlutterStandardReader *)readerWithData:(NSData *)data { +\treturn [[$readerName alloc] initWithData:data]; +} +@end +'''); + } - indent.writeln( - '+ (nullable $className *)nullableFromList:(NSArray *)list { return (list) ? [$className fromList:list] : nil; }'); - } + void _writeCodecGetter( + Indent indent, String name, ObjcOptions options, Api api, Root root) { + final String readerWriterName = '${name}ReaderWriter'; - void writeToList() { - indent.write('- (NSArray *)toList '); - indent.scoped('{', '}', () { - indent.write('return'); - indent.scoped(' @[', '];', () { - for (final NamedType field in klass.fields) { - indent.writeln('${_arrayValue(classNames, enumNames, field)},'); - } + indent.write( + 'NSObject *${_getCodecGetterName(options.prefix, api.name)}() '); + indent.scoped('{', '}', () { + indent + .writeln('static FlutterStandardMessageCodec *sSharedObject = nil;'); + if (getCodecClasses(api, root).isNotEmpty) { + indent.writeln('static dispatch_once_t sPred = 0;'); + indent.write('dispatch_once(&sPred, ^'); + indent.scoped('{', '});', () { + indent.writeln( + '$readerWriterName *readerWriter = [[$readerWriterName alloc] init];'); + indent.writeln( + 'sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter];'); }); - }); + } else { + indent.writeln( + 'sSharedObject = [FlutterStandardMessageCodec sharedInstance];'); + } + + indent.writeln('return sSharedObject;'); + }); + } +} + +/// Writes the method declaration for the initializer. +/// +/// Example '+ (instancetype)makeWithFoo:(NSString *)foo' +void _writeObjcSourceClassInitializerDeclaration(Indent indent, Class klass, + List classes, List enums, String? prefix) { + final List customEnumNames = enums.map((Enum x) => x.name).toList(); + indent.write('+ (instancetype)makeWith'); + bool isFirst = true; + indent.nest(2, () { + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + final String label = isFirst ? _capitalize(field.name) : field.name; + final void Function(String) printer = isFirst + ? indent.add + : (String x) { + indent.addln(''); + indent.write(x); + }; + isFirst = false; + final HostDatatype hostDatatype = getFieldHostDatatype( + field, + classes, + enums, + (TypeDeclaration x) => _objcTypePtrForPrimitiveDartType(prefix, x), + customResolver: customEnumNames.contains(field.type.baseName) + ? (String x) => _className(prefix, x) + : (String x) => '${_className(prefix, x)} *'); + final String nullable = + _isNullable(hostDatatype, field.type) ? 'nullable ' : ''; + printer('$label:($nullable${hostDatatype.datatype})${field.name}'); } + }); +} - indent.writeln('@implementation $className'); - writeInitializer(); - writeFromList(); - writeToList(); - indent.writeln('@end'); +/// Calculates the ObjC class name, possibly prefixed. +String _className(String? prefix, String className) { + if (prefix != null) { + return '$prefix$className'; + } else { + return className; } +} - void writeApi(Api api) { - final String codecName = _getCodecName(options.prefix, api.name); - if (getCodecClasses(api, root).isNotEmpty) { - _writeCodec(indent, codecName, options, api, root); - indent.addln(''); +/// Calculates callback block signature for for async methods. +String _callbackForType(TypeDeclaration type, _ObjcPtr objcType) { + return type.isVoid + ? 'void(^)(NSError *_Nullable)' + : 'void(^)(${objcType.ptr.trim()}_Nullable, NSError *_Nullable)'; +} + +/// Represents an ObjC pointer (ex 'id', 'NSString *'). +class _ObjcPtr { + const _ObjcPtr({required this.baseName}) : hasAsterisk = baseName != 'id'; + final String baseName; + final bool hasAsterisk; + String get ptr => '$baseName${hasAsterisk ? ' *' : ' '}'; +} + +/// Maps between Dart types to ObjC pointer types (ex 'String' => 'NSString *'). +const Map _objcTypeForDartTypeMap = { + 'bool': _ObjcPtr(baseName: 'NSNumber'), + 'int': _ObjcPtr(baseName: 'NSNumber'), + 'String': _ObjcPtr(baseName: 'NSString'), + 'double': _ObjcPtr(baseName: 'NSNumber'), + 'Uint8List': _ObjcPtr(baseName: 'FlutterStandardTypedData'), + 'Int32List': _ObjcPtr(baseName: 'FlutterStandardTypedData'), + 'Int64List': _ObjcPtr(baseName: 'FlutterStandardTypedData'), + 'Float64List': _ObjcPtr(baseName: 'FlutterStandardTypedData'), + 'List': _ObjcPtr(baseName: 'NSArray'), + 'Map': _ObjcPtr(baseName: 'NSDictionary'), + 'Object': _ObjcPtr(baseName: 'id'), +}; + +/// Converts list of [TypeDeclaration] to a code string representing the type +/// arguments for use in generics. +/// Example: ('FOO', ['Foo', 'Bar']) -> 'FOOFoo *, FOOBar *'). +String _flattenTypeArguments(String? classPrefix, List args) { + final String result = args + .map((TypeDeclaration e) => + _objcTypeForDartType(classPrefix, e).ptr.trim()) + .join(', '); + return result; +} + +String? _objcTypePtrForPrimitiveDartType( + String? classPrefix, TypeDeclaration type) { + return _objcTypeForDartTypeMap.containsKey(type.baseName) + ? _objcTypeForDartType(classPrefix, type).ptr + : null; +} + +/// Returns the objc type for a dart [type], prepending the [classPrefix] for +/// generated classes. For example: +/// _objcTypeForDartType(null, 'int') => 'NSNumber'. +_ObjcPtr _objcTypeForDartType(String? classPrefix, TypeDeclaration field) { + return _objcTypeForDartTypeMap.containsKey(field.baseName) + ? field.typeArguments.isEmpty + ? _objcTypeForDartTypeMap[field.baseName]! + : _ObjcPtr( + baseName: + '${_objcTypeForDartTypeMap[field.baseName]!.baseName}<${_flattenTypeArguments(classPrefix, field.typeArguments)}>') + : _ObjcPtr(baseName: _className(classPrefix, field.baseName)); +} + +/// Maps a type to a properties memory semantics (ie strong, copy). +String _propertyTypeForDartType(NamedType field) { + const Map propertyTypeForDartTypeMap = { + 'String': 'copy', + 'bool': 'strong', + 'int': 'strong', + 'double': 'strong', + 'Uint8List': 'strong', + 'Int32List': 'strong', + 'Int64List': 'strong', + 'Float64List': 'strong', + 'List': 'strong', + 'Map': 'strong', + }; + + final String? result = propertyTypeForDartTypeMap[field.type.baseName]; + if (result == null) { + return 'strong'; + } else { + return result; + } +} + +bool _isNullable(HostDatatype hostDatatype, TypeDeclaration type) => + hostDatatype.datatype.contains('*') && type.isNullable; + +/// Generates the name of the codec that will be generated. +String _getCodecName(String? prefix, String className) => + '${_className(prefix, className)}Codec'; + +/// Generates the name of the function for accessing the codec instance used by +/// the api class named [className]. +String _getCodecGetterName(String? prefix, String className) => + '${_className(prefix, className)}GetCodec'; + +String _capitalize(String str) => + (str.isEmpty) ? '' : str[0].toUpperCase() + str.substring(1); + +/// Returns the components of the objc selector that will be generated from +/// [func], ie the strings between the semicolons. [lastSelectorComponent] is +/// the last component of the selector aka the label of the last parameter which +/// isn't included in [func]. +/// Example: +/// f('void add(int x, int y)', 'count') -> ['addX', 'y', 'count'] +Iterable _getSelectorComponents( + Method func, String lastSelectorComponent) sync* { + if (func.objcSelector.isEmpty) { + final Iterator it = func.arguments.iterator; + final bool hasArguments = it.moveNext(); + final String namePostfix = + (lastSelectorComponent.isNotEmpty && func.arguments.isEmpty) + ? 'With${_capitalize(lastSelectorComponent)}' + : ''; + yield '${func.name}${hasArguments ? _capitalize(func.arguments[0].name) : namePostfix}'; + while (it.moveNext()) { + yield it.current.name; } - _writeCodecGetter(indent, codecName, options, api, root); - indent.addln(''); - if (api.location == ApiLocation.host) { - _writeHostApiSource(indent, options, api, root); - } else if (api.location == ApiLocation.flutter) { - _writeFlutterApiSource(indent, options, api, root); + } else { + assert(':'.allMatches(func.objcSelector).length == func.arguments.length); + final Iterable customComponents = func.objcSelector + .split(':') + .where((String element) => element.isNotEmpty); + yield* customComponents; + } + if (lastSelectorComponent.isNotEmpty && func.arguments.isNotEmpty) { + yield lastSelectorComponent; + } +} + +/// Generates the objc source code method signature for [func]. [returnType] is +/// the return value of method, this may not match the return value in [func] +/// since [func] may be asynchronous. The function requires you specify a +/// [lastArgType] and [lastArgName] for arguments that aren't represented in +/// [func]. This is typically used for passing in 'error' or 'completion' +/// arguments that don't exist in the pigeon file but are required in the objc +/// output. [argNameFunc] is the function used to generate the argument name +/// [func.arguments]. +String _makeObjcSignature({ + required Method func, + required ObjcOptions options, + required String returnType, + required String lastArgType, + required String lastArgName, + required bool Function(TypeDeclaration) isEnum, + String Function(int, NamedType)? argNameFunc, +}) { + argNameFunc = argNameFunc ?? (int _, NamedType e) => e.name; + final Iterable argNames = + followedByOne(indexMap(func.arguments, argNameFunc), lastArgName); + final Iterable selectorComponents = + _getSelectorComponents(func, lastArgName); + final Iterable argTypes = followedByOne( + func.arguments.map((NamedType arg) { + if (isEnum(arg.type)) { + return _className(options.prefix, arg.type.baseName); + } else { + final String nullable = arg.type.isNullable ? 'nullable ' : ''; + final _ObjcPtr argType = _objcTypeForDartType(options.prefix, arg.type); + return '$nullable${argType.ptr.trim()}'; + } + }), + lastArgType, + ); + + final String argSignature = map3( + selectorComponents, + argTypes, + argNames, + (String component, String argType, String argName) => + '$component:($argType)$argName', + ).join(' '); + return '- ($returnType)$argSignature'; +} + +/// Generates the ".h" file for the AST represented by [root] to [sink] with the +/// provided [options]. +void generateObjcHeader(ObjcOptions options, Root root, Indent indent) {} + +String _listGetter(Set customClassNames, String list, NamedType field, + int index, String? prefix) { + if (customClassNames.contains(field.type.baseName)) { + String className = field.type.baseName; + if (prefix != null) { + className = '$prefix$className'; } + return '[$className nullableFromList:(GetNullableObjectAtIndex($list, $index))]'; + } else { + return 'GetNullableObjectAtIndex($list, $index)'; } +} - writeHeader(); - writeImports(); - indent.writeln(''); - writeArcEnforcer(); - indent.addln(''); - writeHelperFunctions(); - indent.addln(''); - root.classes.forEach(writeDataClassExtension); - indent.writeln(''); - for (final Class klass in root.classes) { - writeDataClassImplementation(klass); - indent.writeln(''); +String _arrayValue(Set customClassNames, Set customEnumNames, + NamedType field) { + if (customClassNames.contains(field.type.baseName)) { + return '(self.${field.name} ? [self.${field.name} toList] : [NSNull null])'; + } else if (customEnumNames.contains(field.type.baseName)) { + return '@(self.${field.name})'; + } else { + return '(self.${field.name} ?: [NSNull null])'; } - root.apis.forEach(writeApi); +} + +String _getSelector(Method func, String lastSelectorComponent) => + '${_getSelectorComponents(func, lastSelectorComponent).join(':')}:'; + +/// Returns an argument name that can be used in a context where it is possible to collide. +String _getSafeArgName(int count, NamedType arg) => + arg.name.isEmpty ? 'arg$count' : 'arg_${arg.name}'; + +void _writeExtension(Indent indent, String apiName) { + indent.writeln('@interface $apiName ()'); + indent.writeln( + '@property (nonatomic, strong) NSObject *binaryMessenger;'); + indent.writeln('@end'); +} + +void _writeInitializer(Indent indent) { + indent.write( + '- (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger '); + indent.scoped('{', '}', () { + indent.writeln('self = [super init];'); + indent.write('if (self) '); + indent.scoped('{', '}', () { + indent.writeln('_binaryMessenger = binaryMessenger;'); + }); + indent.writeln('return self;'); + }); +} + +void _writeMethod(ObjcOptions languageOptions, Root root, Indent indent, + Api api, Method func) { + final _ObjcPtr returnType = + _objcTypeForDartType(languageOptions.prefix, func.returnType); + final String callbackType = _callbackForType(func.returnType, returnType); + + String argNameFunc(int count, NamedType arg) => _getSafeArgName(count, arg); + final Iterable argNames = indexMap(func.arguments, argNameFunc); + String sendArgument; + if (func.arguments.isEmpty) { + sendArgument = 'nil'; + } else { + String makeVarOrNSNullExpression(String x) => '$x ?: [NSNull null]'; + sendArgument = '@[${argNames.map(makeVarOrNSNullExpression).join(', ')}]'; + } + indent.write(_makeObjcSignature( + func: func, + options: languageOptions, + returnType: 'void', + lastArgName: 'completion', + lastArgType: callbackType, + argNameFunc: argNameFunc, + isEnum: (TypeDeclaration t) => isEnum(root, t), + )); + indent.scoped(' {', '}', () { + indent.writeln('FlutterBasicMessageChannel *channel ='); + indent.inc(); + indent.writeln('[FlutterBasicMessageChannel'); + indent.inc(); + indent.writeln('messageChannelWithName:@"${makeChannelName(api, func)}"'); + indent.writeln('binaryMessenger:self.binaryMessenger'); + indent.write( + 'codec:${_getCodecGetterName(languageOptions.prefix, api.name)}()'); + indent.addln('];'); + indent.dec(); + indent.dec(); + indent.write('[channel sendMessage:$sendArgument reply:^(id reply) '); + indent.scoped('{', '}];', () { + if (func.returnType.isVoid) { + indent.writeln('completion(nil);'); + } else { + indent.writeln('${returnType.ptr}output = reply;'); + indent.writeln('completion(output, nil);'); + } + }); + }); } /// Looks through the AST for features that aren't supported by the ObjC diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index 5082dd19e8e..04c0b95ab6f 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -426,7 +426,7 @@ class DartGeneratorAdapter implements GeneratorAdapter { StringSink sink, PigeonOptions options, Root root, FileType fileType) { final DartOptions dartOptionsWithHeader = _dartOptionsWithCopyrightHeader( options.dartOptions, options.copyrightHeader); - final DartGenerator generator = DartGenerator(); + const DartGenerator generator = DartGenerator(); generator.generate(dartOptionsWithHeader, root, sink); } @@ -455,12 +455,8 @@ class DartTestGeneratorAdapter implements GeneratorAdapter { dartOutPath: options.dartOut, testOutPath: options.dartTestOut, ); - final DartGenerator testGenerator = DartGenerator(); - testGenerator.generateTest( - dartOptionsWithHeader, - root, - sink, - ); + const DartGenerator testGenerator = DartGenerator(); + testGenerator.generateTest(dartOptionsWithHeader, root, sink); } @override @@ -497,7 +493,7 @@ class ObjcGeneratorAdapter implements GeneratorAdapter { final OutputFileOptions outputFileOptions = OutputFileOptions( fileType: fileType, languageOptions: objcOptionsWithHeader); - final ObjcGenerator generator = ObjcGenerator(); + const ObjcGenerator generator = ObjcGenerator(); generator.generate(outputFileOptions, root, sink); } @@ -532,7 +528,7 @@ class JavaGeneratorAdapter implements GeneratorAdapter { copyrightHeader: options.copyrightHeader != null ? _lineReader(options.copyrightHeader!) : null)); - final JavaGenerator generator = JavaGenerator(); + const JavaGenerator generator = JavaGenerator(); generator.generate(javaOptions, root, sink); } @@ -560,7 +556,7 @@ class SwiftGeneratorAdapter implements GeneratorAdapter { copyrightHeader: options.copyrightHeader != null ? _lineReader(options.copyrightHeader!) : null)); - final SwiftGenerator generator = SwiftGenerator(); + const SwiftGenerator generator = SwiftGenerator(); generator.generate(swiftOptions, root, sink); } @@ -593,7 +589,7 @@ class CppGeneratorAdapter implements GeneratorAdapter { final OutputFileOptions outputFileOptions = OutputFileOptions( fileType: fileType, languageOptions: cppOptionsWithHeader); - final CppGenerator generator = CppGenerator(); + const CppGenerator generator = CppGenerator(); generator.generate(outputFileOptions, root, sink); } @@ -627,7 +623,7 @@ class KotlinGeneratorAdapter implements GeneratorAdapter { copyrightHeader: options.copyrightHeader != null ? _lineReader(options.copyrightHeader!) : null)); - final KotlinGenerator generator = KotlinGenerator(); + const KotlinGenerator generator = KotlinGenerator(); generator.generate(kotlinOptions, root, sink); } diff --git a/packages/pigeon/lib/swift_generator.dart b/packages/pigeon/lib/swift_generator.dart index 77fff7e2fac..46e14005ca4 100644 --- a/packages/pigeon/lib/swift_generator.dart +++ b/packages/pigeon/lib/swift_generator.dart @@ -49,230 +49,565 @@ class SwiftOptions { } /// Class that manages all Swift code generation. -class SwiftGenerator extends Generator { +class SwiftGenerator extends StructuredGenerator { /// Instantiates a Swift Generator. - SwiftGenerator(); + const SwiftGenerator(); - /// Generates Swift files with specified [SwiftOptions] @override - void generate(SwiftOptions languageOptions, Root root, StringSink sink, - {FileType fileType = FileType.na}) { - assert(fileType == FileType.na); - generateSwift(languageOptions, root, sink); + void writeFilePrologue( + SwiftOptions generatorOptions, Root root, Indent indent) { + if (generatorOptions.copyrightHeader != null) { + addLines(indent, generatorOptions.copyrightHeader!, linePrefix: '// '); + } + indent.writeln('// $generatedCodeWarning'); + indent.writeln('// $seeAlsoWarning'); + indent.addln(''); } -} -/// Calculates the name of the codec that will be generated for [api]. -String _getCodecName(Api api) => '${api.name}Codec'; + @override + void writeFileImports( + SwiftOptions generatorOptions, Root root, Indent indent) { + indent.writeln('import Foundation'); + indent.format(''' +#if os(iOS) +import Flutter +#elseif os(macOS) +import FlutterMacOS +#else +#error("Unsupported platform.") +#endif +'''); + indent.writeln(''); + } -/// Writes the codec classwill be used for encoding messages for the [api]. -/// Example: -/// private class FooHostApiCodecReader: FlutterStandardReader {...} -/// private class FooHostApiCodecWriter: FlutterStandardWriter {...} -/// private class FooHostApiCodecReaderWriter: FlutterStandardReaderWriter {...} -void _writeCodec(Indent indent, Api api, Root root) { - assert(getCodecClasses(api, root).isNotEmpty); - final String codecName = _getCodecName(api); - final String readerWriterName = '${codecName}ReaderWriter'; - final String readerName = '${codecName}Reader'; - final String writerName = '${codecName}Writer'; - - // Generate Reader - indent.write('private class $readerName: FlutterStandardReader '); - indent.scoped('{', '}', () { - if (getCodecClasses(api, root).isNotEmpty) { - indent.write('override func readValue(ofType type: UInt8) -> Any? '); - indent.scoped('{', '}', () { - indent.write('switch type '); - indent.scoped('{', '}', () { - for (final EnumeratedClass customClass - in getCodecClasses(api, root)) { - indent.write('case ${customClass.enumeration}:'); - indent.scoped('', '', () { - indent.write( - 'return ${customClass.name}.fromList(self.readValue() as! [Any])'); - }); - } - indent.write('default:'); - indent.scoped('', '', () { - indent.writeln('return super.readValue(ofType: type)'); - }); - }); + @override + void writeEnum( + SwiftOptions generatorOptions, Root root, Indent indent, Enum anEnum) { + indent.writeln(''); + addDocumentationComments( + indent, anEnum.documentationComments, _docCommentSpec); + + indent.write('enum ${anEnum.name}: Int '); + indent.scoped('{', '}', () { + enumerate(anEnum.members, (int index, final EnumMember member) { + addDocumentationComments( + indent, member.documentationComments, _docCommentSpec); + indent.writeln('case ${_camelCase(member.name)} = $index'); }); - } - }); + }); + } - // Generate Writer - indent.write('private class $writerName: FlutterStandardWriter '); - indent.scoped('{', '}', () { - if (getCodecClasses(api, root).isNotEmpty) { - indent.write('override func writeValue(_ value: Any) '); - indent.scoped('{', '}', () { - indent.write(''); - for (final EnumeratedClass customClass in getCodecClasses(api, root)) { - indent.add('if let value = value as? ${customClass.name} '); - indent.scoped('{', '} else ', () { - indent.writeln('super.writeByte(${customClass.enumeration})'); - indent.writeln('super.writeValue(value.toList())'); - }, addTrailingNewline: false); - } - indent.scoped('{', '}', () { - indent.writeln('super.writeValue(value)'); - }); + @override + void writeDataClass( + SwiftOptions generatorOptions, Root root, Indent indent, Class klass) { + final Set customClassNames = + root.classes.map((Class x) => x.name).toSet(); + final Set customEnumNames = + root.enums.map((Enum x) => x.name).toSet(); + + const List generatedComments = [ + ' Generated class from Pigeon that represents data sent in messages.' + ]; + indent.addln(''); + addDocumentationComments( + indent, klass.documentationComments, _docCommentSpec, + generatorComments: generatedComments); + + indent.write('struct ${klass.name} '); + indent.scoped('{', '}', () { + getFieldsInSerializationOrder(klass).forEach((NamedType field) { + _writeClassField(indent, field); }); - } - }); - indent.writeln(''); - // Generate ReaderWriter - indent.write('private class $readerWriterName: FlutterStandardReaderWriter '); - indent.scoped('{', '}', () { - indent.write( - 'override func reader(with data: Data) -> FlutterStandardReader '); + indent.writeln(''); + writeClassDecode(generatorOptions, root, indent, klass, customClassNames, + customEnumNames); + writeClassEncode(generatorOptions, root, indent, klass, customClassNames, + customEnumNames); + }); + } + + @override + void writeClassEncode( + SwiftOptions generatorOptions, + Root root, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) { + indent.write('func toList() -> [Any?] '); indent.scoped('{', '}', () { - indent.writeln('return $readerName(data: data)'); + indent.write('return '); + indent.scoped('[', ']', () { + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + final HostDatatype hostDatatype = _getHostDatatype(root, field); + String toWriteValue = ''; + final String fieldName = field.name; + final String nullsafe = field.type.isNullable ? '?' : ''; + if (!hostDatatype.isBuiltin && + customClassNames.contains(field.type.baseName)) { + toWriteValue = '$fieldName$nullsafe.toList()'; + } else if (!hostDatatype.isBuiltin && + customEnumNames.contains(field.type.baseName)) { + toWriteValue = '$fieldName$nullsafe.rawValue'; + } else { + toWriteValue = field.name; + } + + indent.writeln('$toWriteValue,'); + } + }); }); - indent.writeln(''); - indent.write( - 'override func writer(with data: NSMutableData) -> FlutterStandardWriter '); + } + + @override + void writeClassDecode( + SwiftOptions generatorOptions, + Root root, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) { + final String className = klass.name; + indent.write('static func fromList(_ list: [Any?]) -> $className? '); + indent.scoped('{', '}', () { - indent.writeln('return $writerName(data: data)'); + enumerate(getFieldsInSerializationOrder(klass), + (int index, final NamedType field) { + final HostDatatype hostDatatype = _getHostDatatype(root, field); + + final String listValue = 'list[$index]'; + final String fieldType = _swiftTypeForDartType(field.type); + + if (field.type.isNullable) { + if (!hostDatatype.isBuiltin && + customClassNames.contains(field.type.baseName)) { + indent.writeln('var ${field.name}: $fieldType? = nil'); + indent.write('if let ${field.name}List = $listValue as? [Any?] '); + indent.scoped('{', '}', () { + indent.writeln( + '${field.name} = $fieldType.fromList(${field.name}List)'); + }); + } else if (!hostDatatype.isBuiltin && + customEnumNames.contains(field.type.baseName)) { + indent.writeln('var ${field.name}: $fieldType? = nil'); + indent.write('if let ${field.name}RawValue = $listValue as? Int '); + indent.scoped('{', '}', () { + indent.writeln( + '${field.name} = $fieldType(rawValue: ${field.name}RawValue)'); + }); + } else { + indent.writeln('let ${field.name} = $listValue as? $fieldType '); + } + } else { + if (!hostDatatype.isBuiltin && + customClassNames.contains(field.type.baseName)) { + indent.writeln( + 'let ${field.name} = $fieldType.fromList($listValue as! [Any?])!'); + } else if (!hostDatatype.isBuiltin && + customEnumNames.contains(field.type.baseName)) { + indent.writeln( + 'let ${field.name} = $fieldType(rawValue: $listValue as! Int)!'); + } else { + indent.writeln('let ${field.name} = $listValue as! $fieldType'); + } + } + }); + + indent.writeln(''); + indent.write('return '); + indent.scoped('$className(', ')', () { + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + final String comma = + getFieldsInSerializationOrder(klass).last == field ? '' : ','; + indent.writeln('${field.name}: ${field.name}$comma'); + } + }); }); - }); - indent.writeln(''); + } - // Generate Codec - indent.write('class $codecName: FlutterStandardMessageCodec '); - indent.scoped('{', '}', () { - indent.writeln( - 'static let shared = $codecName(readerWriter: $readerWriterName())'); - }); -} + void _writeClassField(Indent indent, NamedType field) { + addDocumentationComments( + indent, field.documentationComments, _docCommentSpec); + + indent.write( + 'var ${field.name}: ${_nullsafeSwiftTypeForDartType(field.type)}'); + final String defaultNil = field.type.isNullable ? ' = nil' : ''; + indent.addln(defaultNil); + } + + @override + void writeApis( + SwiftOptions generatorOptions, + Root root, + Indent indent, + ) { + if (root.apis.any((Api api) => + api.location == ApiLocation.host && + api.methods.any((Method it) => it.isAsynchronous))) { + indent.addln(''); + } + super.writeApis(generatorOptions, root, indent); + } + + /// Writes the code for a flutter [Api], [api]. + /// Example: + /// class Foo { + /// private let binaryMessenger: FlutterBinaryMessenger + /// init(binaryMessenger: FlutterBinaryMessenger) {...} + /// func add(x: Int32, y: Int32, completion: @escaping (Int32?) -> Void) {...} + /// } + @override + void writeFlutterApi( + SwiftOptions generatorOptions, + Root root, + Indent indent, + Api api, + ) { + assert(api.location == ApiLocation.flutter); + final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; + if (isCustomCodec) { + _writeCodec(indent, api, root); + } + const List generatedComments = [ + ' Generated class from Pigeon that represents Flutter messages that can be called from Swift.' + ]; + addDocumentationComments(indent, api.documentationComments, _docCommentSpec, + generatorComments: generatedComments); -/// Write the swift code that represents a host [Api], [api]. -/// Example: -/// protocol Foo { -/// Int32 add(x: Int32, y: Int32) -/// } -void _writeHostApi(Indent indent, Api api, Root root) { - assert(api.location == ApiLocation.host); - - final String apiName = api.name; - - const List generatedComments = [ - ' Generated protocol from Pigeon that represents a handler of messages from Flutter.' - ]; - addDocumentationComments(indent, api.documentationComments, _docCommentSpec, - generatorComments: generatedComments); - - indent.write('protocol $apiName '); - indent.scoped('{', '}', () { - for (final Method method in api.methods) { - final List argSignature = []; - if (method.arguments.isNotEmpty) { - final Iterable argTypes = method.arguments - .map((NamedType e) => _nullsafeSwiftTypeForDartType(e.type)); - final Iterable argNames = - method.arguments.map((NamedType e) => e.name); - argSignature - .addAll(map2(argTypes, argNames, (String argType, String argName) { - return '$argName: $argType'; - })); + indent.write('class ${api.name} '); + indent.scoped('{', '}', () { + indent.writeln('private let binaryMessenger: FlutterBinaryMessenger'); + indent.write('init(binaryMessenger: FlutterBinaryMessenger)'); + indent.scoped('{', '}', () { + indent.writeln('self.binaryMessenger = binaryMessenger'); + }); + final String codecName = _getCodecName(api); + String codecArgumentString = ''; + if (getCodecClasses(api, root).isNotEmpty) { + codecArgumentString = ', codec: codec'; + indent.write('var codec: FlutterStandardMessageCodec '); + indent.scoped('{', '}', () { + indent.writeln('return $codecName.shared'); + }); } + for (final Method func in api.methods) { + final String channelName = makeChannelName(api, func); + final String returnType = func.returnType.isVoid + ? '' + : _nullsafeSwiftTypeForDartType(func.returnType); + String sendArgument; + addDocumentationComments( + indent, func.documentationComments, _docCommentSpec); - final String returnType = method.returnType.isVoid - ? '' - : _nullsafeSwiftTypeForDartType(method.returnType); - addDocumentationComments( - indent, method.documentationComments, _docCommentSpec); - - if (method.isAsynchronous) { - argSignature.add('completion: @escaping ($returnType) -> Void'); - indent.writeln('func ${method.name}(${argSignature.join(', ')})'); - } else if (method.returnType.isVoid) { - indent.writeln('func ${method.name}(${argSignature.join(', ')})'); - } else { - indent.writeln( - 'func ${method.name}(${argSignature.join(', ')}) -> $returnType'); + if (func.arguments.isEmpty) { + indent.write( + 'func ${func.name}(completion: @escaping ($returnType) -> Void) '); + sendArgument = 'nil'; + } else { + final Iterable argTypes = func.arguments + .map((NamedType e) => _nullsafeSwiftTypeForDartType(e.type)); + final Iterable argLabels = + indexMap(func.arguments, _getArgumentName); + final Iterable argNames = + indexMap(func.arguments, _getSafeArgumentName); + sendArgument = '[${argNames.join(', ')}]'; + final String argsSignature = map3( + argTypes, + argLabels, + argNames, + (String type, String label, String name) => + '$label $name: $type').join(', '); + if (func.returnType.isVoid) { + indent.write( + 'func ${func.name}($argsSignature, completion: @escaping () -> Void) '); + } else { + indent.write( + 'func ${func.name}($argsSignature, completion: @escaping ($returnType) -> Void) '); + } + } + indent.scoped('{', '}', () { + const String channel = 'channel'; + indent.writeln( + 'let $channel = FlutterBasicMessageChannel(name: "$channelName", binaryMessenger: binaryMessenger$codecArgumentString)'); + indent.write('$channel.sendMessage($sendArgument) '); + if (func.returnType.isVoid) { + indent.scoped('{ _ in', '}', () { + indent.writeln('completion()'); + }); + } else { + indent.scoped('{ response in', '}', () { + indent.writeln( + 'let result = ${_castForceUnwrap("response", func.returnType, root)}'); + indent.writeln('completion(result)'); + }); + } + }); } - } - }); + }); + } - indent.addln(''); - indent.writeln( - '$_docCommentPrefix Generated setup class from Pigeon to handle messages through the `binaryMessenger`.'); - indent.write('class ${apiName}Setup '); - indent.scoped('{', '}', () { - final String codecName = _getCodecName(api); - indent.writeln('$_docCommentPrefix The codec used by $apiName.'); - String codecArgumentString = ''; - if (getCodecClasses(api, root).isNotEmpty) { - codecArgumentString = ', codec: codec'; - indent.writeln( - 'static var codec: FlutterStandardMessageCodec { $codecName.shared }'); + /// Write the swift code that represents a host [Api], [api]. + /// Example: + /// protocol Foo { + /// Int32 add(x: Int32, y: Int32) + /// } + @override + void writeHostApi( + SwiftOptions generatorOptions, + Root root, + Indent indent, + Api api, + ) { + assert(api.location == ApiLocation.host); + + final String apiName = api.name; + + final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; + if (isCustomCodec) { + _writeCodec(indent, api, root); } - indent.writeln( - '$_docCommentPrefix Sets up an instance of `$apiName` to handle messages through the `binaryMessenger`.'); - indent.write( - 'static func setUp(binaryMessenger: FlutterBinaryMessenger, api: $apiName?) '); + + const List generatedComments = [ + ' Generated protocol from Pigeon that represents a handler of messages from Flutter.' + ]; + addDocumentationComments(indent, api.documentationComments, _docCommentSpec, + generatorComments: generatedComments); + + indent.write('protocol $apiName '); indent.scoped('{', '}', () { for (final Method method in api.methods) { - final String channelName = makeChannelName(api, method); - final String varChannelName = '${method.name}Channel'; + final List argSignature = []; + if (method.arguments.isNotEmpty) { + final Iterable argTypes = method.arguments + .map((NamedType e) => _nullsafeSwiftTypeForDartType(e.type)); + final Iterable argNames = + method.arguments.map((NamedType e) => e.name); + argSignature.addAll( + map2(argTypes, argNames, (String argType, String argName) { + return '$argName: $argType'; + })); + } + + final String returnType = method.returnType.isVoid + ? '' + : _nullsafeSwiftTypeForDartType(method.returnType); addDocumentationComments( indent, method.documentationComments, _docCommentSpec); + if (method.isAsynchronous) { + argSignature.add('completion: @escaping ($returnType) -> Void'); + indent.writeln('func ${method.name}(${argSignature.join(', ')})'); + } else if (method.returnType.isVoid) { + indent.writeln('func ${method.name}(${argSignature.join(', ')})'); + } else { + indent.writeln( + 'func ${method.name}(${argSignature.join(', ')}) -> $returnType'); + } + } + }); + + indent.addln(''); + indent.writeln( + '$_docCommentPrefix Generated setup class from Pigeon to handle messages through the `binaryMessenger`.'); + indent.write('class ${apiName}Setup '); + indent.scoped('{', '}', () { + final String codecName = _getCodecName(api); + indent.writeln('$_docCommentPrefix The codec used by $apiName.'); + String codecArgumentString = ''; + if (getCodecClasses(api, root).isNotEmpty) { + codecArgumentString = ', codec: codec'; indent.writeln( - 'let $varChannelName = FlutterBasicMessageChannel(name: "$channelName", binaryMessenger: binaryMessenger$codecArgumentString)'); - indent.write('if let api = api '); - indent.scoped('{', '}', () { - indent.write('$varChannelName.setMessageHandler '); - final String messageVarName = - method.arguments.isNotEmpty ? 'message' : '_'; - indent.scoped('{ $messageVarName, reply in', '}', () { - final List methodArgument = []; - if (method.arguments.isNotEmpty) { - indent.writeln('let args = message as! [Any?]'); - enumerate(method.arguments, (int index, NamedType arg) { - final String argName = _getSafeArgumentName(index, arg); - final String argIndex = 'args[$index]'; - indent.writeln( - 'let $argName = ${_castForceUnwrap(argIndex, arg.type, root)}'); - methodArgument.add('${arg.name}: $argName'); - }); - } - final String call = - 'api.${method.name}(${methodArgument.join(', ')})'; - if (method.isAsynchronous) { - indent.write('$call '); - if (method.returnType.isVoid) { - indent.scoped('{', '}', () { - indent.writeln('reply(wrapResult(nil))'); - }); - } else { - indent.scoped('{ result in', '}', () { - indent.writeln('reply(wrapResult(result))'); + 'static var codec: FlutterStandardMessageCodec { $codecName.shared }'); + } + indent.writeln( + '$_docCommentPrefix Sets up an instance of `$apiName` to handle messages through the `binaryMessenger`.'); + indent.write( + 'static func setUp(binaryMessenger: FlutterBinaryMessenger, api: $apiName?) '); + indent.scoped('{', '}', () { + for (final Method method in api.methods) { + final String channelName = makeChannelName(api, method); + final String varChannelName = '${method.name}Channel'; + addDocumentationComments( + indent, method.documentationComments, _docCommentSpec); + + indent.writeln( + 'let $varChannelName = FlutterBasicMessageChannel(name: "$channelName", binaryMessenger: binaryMessenger$codecArgumentString)'); + indent.write('if let api = api '); + indent.scoped('{', '}', () { + indent.write('$varChannelName.setMessageHandler '); + final String messageVarName = + method.arguments.isNotEmpty ? 'message' : '_'; + indent.scoped('{ $messageVarName, reply in', '}', () { + final List methodArgument = []; + if (method.arguments.isNotEmpty) { + indent.writeln('let args = message as! [Any?]'); + enumerate(method.arguments, (int index, NamedType arg) { + final String argName = _getSafeArgumentName(index, arg); + final String argIndex = 'args[$index]'; + indent.writeln( + 'let $argName = ${_castForceUnwrap(argIndex, arg.type, root)}'); + methodArgument.add('${arg.name}: $argName'); }); } - } else { - if (method.returnType.isVoid) { - indent.writeln(call); - indent.writeln('reply(wrapResult(nil))'); + final String call = + 'api.${method.name}(${methodArgument.join(', ')})'; + if (method.isAsynchronous) { + indent.write('$call '); + if (method.returnType.isVoid) { + indent.scoped('{', '}', () { + indent.writeln('reply(wrapResult(nil))'); + }); + } else { + indent.scoped('{ result in', '}', () { + indent.writeln('reply(wrapResult(result))'); + }); + } } else { - indent.writeln('let result = $call'); - indent.writeln('reply(wrapResult(result))'); + if (method.returnType.isVoid) { + indent.writeln(call); + indent.writeln('reply(wrapResult(nil))'); + } else { + indent.writeln('let result = $call'); + indent.writeln('reply(wrapResult(result))'); + } } + }); + }, addTrailingNewline: false); + indent.scoped(' else {', '}', () { + indent.writeln('$varChannelName.setMessageHandler(nil)'); + }); + } + }); + }); + } + + /// Writes the codec classwill be used for encoding messages for the [api]. + /// Example: + /// private class FooHostApiCodecReader: FlutterStandardReader {...} + /// private class FooHostApiCodecWriter: FlutterStandardWriter {...} + /// private class FooHostApiCodecReaderWriter: FlutterStandardReaderWriter {...} + void _writeCodec(Indent indent, Api api, Root root) { + assert(getCodecClasses(api, root).isNotEmpty); + final String codecName = _getCodecName(api); + final String readerWriterName = '${codecName}ReaderWriter'; + final String readerName = '${codecName}Reader'; + final String writerName = '${codecName}Writer'; + + // Generate Reader + indent.write('private class $readerName: FlutterStandardReader '); + indent.scoped('{', '}', () { + if (getCodecClasses(api, root).isNotEmpty) { + indent.write('override func readValue(ofType type: UInt8) -> Any? '); + indent.scoped('{', '}', () { + indent.write('switch type '); + indent.scoped('{', '}', () { + for (final EnumeratedClass customClass + in getCodecClasses(api, root)) { + indent.write('case ${customClass.enumeration}:'); + indent.scoped('', '', () { + indent.write( + 'return ${customClass.name}.fromList(self.readValue() as! [Any])'); + }); } + indent.write('default:'); + indent.scoped('', '', () { + indent.writeln('return super.readValue(ofType: type)'); + }); }); - }, addTrailingNewline: false); - indent.scoped(' else {', '}', () { - indent.writeln('$varChannelName.setMessageHandler(nil)'); }); } }); - }); + + // Generate Writer + indent.write('private class $writerName: FlutterStandardWriter '); + indent.scoped('{', '}', () { + if (getCodecClasses(api, root).isNotEmpty) { + indent.write('override func writeValue(_ value: Any) '); + indent.scoped('{', '}', () { + indent.write(''); + for (final EnumeratedClass customClass + in getCodecClasses(api, root)) { + indent.add('if let value = value as? ${customClass.name} '); + indent.scoped('{', '} else ', () { + indent.writeln('super.writeByte(${customClass.enumeration})'); + indent.writeln('super.writeValue(value.toList())'); + }, addTrailingNewline: false); + } + indent.scoped('{', '}', () { + indent.writeln('super.writeValue(value)'); + }); + }); + } + }); + indent.writeln(''); + + // Generate ReaderWriter + indent + .write('private class $readerWriterName: FlutterStandardReaderWriter '); + indent.scoped('{', '}', () { + indent.write( + 'override func reader(with data: Data) -> FlutterStandardReader '); + indent.scoped('{', '}', () { + indent.writeln('return $readerName(data: data)'); + }); + indent.writeln(''); + indent.write( + 'override func writer(with data: NSMutableData) -> FlutterStandardWriter '); + indent.scoped('{', '}', () { + indent.writeln('return $writerName(data: data)'); + }); + }); + indent.writeln(''); + + // Generate Codec + indent.write('class $codecName: FlutterStandardMessageCodec '); + indent.scoped('{', '}', () { + indent.writeln( + 'static let shared = $codecName(readerWriter: $readerWriterName())'); + }); + indent.addln(''); + } + + void _writeWrapResult(Indent indent) { + indent.addln(''); + indent.write('private func wrapResult(_ result: Any?) -> [Any?] '); + indent.scoped('{', '}', () { + indent.writeln('return [result]'); + }); + } + + void _writeWrapError(Indent indent) { + indent.addln(''); + indent.write('private func wrapError(_ error: FlutterError) -> [Any?] '); + indent.scoped('{', '}', () { + indent.write('return '); + indent.scoped('[', ']', () { + indent.writeln('error.code,'); + indent.writeln('error.message,'); + indent.writeln('error.details'); + }); + }); + } + + @override + void writeGeneralUtilities( + SwiftOptions generatorOptions, Root root, Indent indent) { + _writeWrapResult(indent); + _writeWrapError(indent); + } +} + +HostDatatype _getHostDatatype(Root root, NamedType field) { + return getFieldHostDatatype(field, root.classes, root.enums, + (TypeDeclaration x) => _swiftTypeForBuiltinDartType(x)); } +/// Calculates the name of the codec that will be generated for [api]. +String _getCodecName(Api api) => '${api.name}Codec'; + String _getArgumentName(int count, NamedType argument) => argument.name.isEmpty ? 'arg$count' : argument.name; @@ -287,93 +622,6 @@ String _camelCase(String text) { return pascal[0].toLowerCase() + pascal.substring(1); } -/// Writes the code for a flutter [Api], [api]. -/// Example: -/// class Foo { -/// private let binaryMessenger: FlutterBinaryMessenger -/// init(binaryMessenger: FlutterBinaryMessenger) {...} -/// func add(x: Int32, y: Int32, completion: @escaping (Int32?) -> Void) {...} -/// } -void _writeFlutterApi(Indent indent, Api api, Root root) { - assert(api.location == ApiLocation.flutter); - const List generatedComments = [ - ' Generated class from Pigeon that represents Flutter messages that can be called from Swift.' - ]; - addDocumentationComments(indent, api.documentationComments, _docCommentSpec, - generatorComments: generatedComments); - - indent.write('class ${api.name} '); - indent.scoped('{', '}', () { - indent.writeln('private let binaryMessenger: FlutterBinaryMessenger'); - indent.write('init(binaryMessenger: FlutterBinaryMessenger)'); - indent.scoped('{', '}', () { - indent.writeln('self.binaryMessenger = binaryMessenger'); - }); - final String codecName = _getCodecName(api); - String codecArgumentString = ''; - if (getCodecClasses(api, root).isNotEmpty) { - codecArgumentString = ', codec: codec'; - indent.write('var codec: FlutterStandardMessageCodec '); - indent.scoped('{', '}', () { - indent.writeln('return $codecName.shared'); - }); - } - for (final Method func in api.methods) { - final String channelName = makeChannelName(api, func); - final String returnType = func.returnType.isVoid - ? '' - : _nullsafeSwiftTypeForDartType(func.returnType); - String sendArgument; - addDocumentationComments( - indent, func.documentationComments, _docCommentSpec); - - if (func.arguments.isEmpty) { - indent.write( - 'func ${func.name}(completion: @escaping ($returnType) -> Void) '); - sendArgument = 'nil'; - } else { - final Iterable argTypes = func.arguments - .map((NamedType e) => _nullsafeSwiftTypeForDartType(e.type)); - final Iterable argLabels = - indexMap(func.arguments, _getArgumentName); - final Iterable argNames = - indexMap(func.arguments, _getSafeArgumentName); - sendArgument = '[${argNames.join(', ')}]'; - final String argsSignature = map3( - argTypes, - argLabels, - argNames, - (String type, String label, String name) => - '$label $name: $type').join(', '); - if (func.returnType.isVoid) { - indent.write( - 'func ${func.name}($argsSignature, completion: @escaping () -> Void) '); - } else { - indent.write( - 'func ${func.name}($argsSignature, completion: @escaping ($returnType) -> Void) '); - } - } - indent.scoped('{', '}', () { - const String channel = 'channel'; - indent.writeln( - 'let $channel = FlutterBasicMessageChannel(name: "$channelName", binaryMessenger: binaryMessenger$codecArgumentString)'); - indent.write('$channel.sendMessage($sendArgument) '); - if (func.returnType.isVoid) { - indent.scoped('{ _ in', '}', () { - indent.writeln('completion()'); - }); - } else { - indent.scoped('{ response in', '}', () { - indent.writeln( - 'let result = ${_castForceUnwrap("response", func.returnType, root)}'); - indent.writeln('completion(result)'); - }); - } - }); - } - }); -} - String _castForceUnwrap(String value, TypeDeclaration type, Root root) { if (isEnum(root, type)) { final String forceUnwrap = type.isNullable ? '' : '!'; @@ -446,228 +694,3 @@ String _nullsafeSwiftTypeForDartType(TypeDeclaration type) { final String nullSafe = type.isNullable ? '?' : ''; return '${_swiftTypeForDartType(type)}$nullSafe'; } - -/// Generates the ".swift" file for the AST represented by [root] to [sink] with the -/// provided [options]. -void generateSwift(SwiftOptions options, Root root, StringSink sink) { - final Set rootClassNameSet = - root.classes.map((Class x) => x.name).toSet(); - final Set rootEnumNameSet = - root.enums.map((Enum x) => x.name).toSet(); - final Indent indent = Indent(sink); - - HostDatatype getHostDatatype(NamedType field) { - return getFieldHostDatatype(field, root.classes, root.enums, - (TypeDeclaration x) => _swiftTypeForBuiltinDartType(x)); - } - - void writeHeader() { - if (options.copyrightHeader != null) { - addLines(indent, options.copyrightHeader!, linePrefix: '// '); - } - indent.writeln('// $generatedCodeWarning'); - indent.writeln('// $seeAlsoWarning'); - } - - void writeImports() { - indent.writeln('import Foundation'); - indent.format(''' -#if os(iOS) -import Flutter -#elseif os(macOS) -import FlutterMacOS -#else -#error("Unsupported platform.") -#endif -'''); - } - - void writeEnum(Enum anEnum) { - addDocumentationComments( - indent, anEnum.documentationComments, _docCommentSpec); - - indent.write('enum ${anEnum.name}: Int '); - indent.scoped('{', '}', () { - enumerate(anEnum.members, (int index, final EnumMember member) { - addDocumentationComments( - indent, member.documentationComments, _docCommentSpec); - indent.writeln('case ${_camelCase(member.name)} = $index'); - }); - }); - } - - void writeDataClass(Class klass) { - void writeField(NamedType field) { - addDocumentationComments( - indent, field.documentationComments, _docCommentSpec); - - indent.write( - 'var ${field.name}: ${_nullsafeSwiftTypeForDartType(field.type)}'); - final String defaultNil = field.type.isNullable ? ' = nil' : ''; - indent.addln(defaultNil); - } - - void writeToList() { - indent.write('func toList() -> [Any?] '); - indent.scoped('{', '}', () { - indent.write('return '); - indent.scoped('[', ']', () { - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - final HostDatatype hostDatatype = getHostDatatype(field); - String toWriteValue = ''; - final String fieldName = field.name; - final String nullsafe = field.type.isNullable ? '?' : ''; - if (!hostDatatype.isBuiltin && - rootClassNameSet.contains(field.type.baseName)) { - toWriteValue = '$fieldName$nullsafe.toList()'; - } else if (!hostDatatype.isBuiltin && - rootEnumNameSet.contains(field.type.baseName)) { - toWriteValue = '$fieldName$nullsafe.rawValue'; - } else { - toWriteValue = field.name; - } - - indent.writeln('$toWriteValue,'); - } - }); - }); - } - - void writeFromList() { - final String className = klass.name; - indent.write('static func fromList(_ list: [Any?]) -> $className? '); - - indent.scoped('{', '}', () { - enumerate(getFieldsInSerializationOrder(klass), - (int index, final NamedType field) { - final HostDatatype hostDatatype = getHostDatatype(field); - - final String listValue = 'list[$index]'; - final String fieldType = _swiftTypeForDartType(field.type); - - if (field.type.isNullable) { - if (!hostDatatype.isBuiltin && - rootClassNameSet.contains(field.type.baseName)) { - indent.writeln('var ${field.name}: $fieldType? = nil'); - indent.write('if let ${field.name}List = $listValue as? [Any?] '); - indent.scoped('{', '}', () { - indent.writeln( - '${field.name} = $fieldType.fromList(${field.name}List)'); - }); - } else if (!hostDatatype.isBuiltin && - rootEnumNameSet.contains(field.type.baseName)) { - indent.writeln('var ${field.name}: $fieldType? = nil'); - indent - .write('if let ${field.name}RawValue = $listValue as? Int '); - indent.scoped('{', '}', () { - indent.writeln( - '${field.name} = $fieldType(rawValue: ${field.name}RawValue)'); - }); - } else { - indent.writeln('let ${field.name} = $listValue as? $fieldType '); - } - } else { - if (!hostDatatype.isBuiltin && - rootClassNameSet.contains(field.type.baseName)) { - indent.writeln( - 'let ${field.name} = $fieldType.fromList($listValue as! [Any?])!'); - } else if (!hostDatatype.isBuiltin && - rootEnumNameSet.contains(field.type.baseName)) { - indent.writeln( - 'let ${field.name} = $fieldType(rawValue: $listValue as! Int)!'); - } else { - indent.writeln('let ${field.name} = $listValue as! $fieldType'); - } - } - }); - - indent.writeln(''); - indent.write('return '); - indent.scoped('$className(', ')', () { - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - final String comma = - getFieldsInSerializationOrder(klass).last == field ? '' : ','; - indent.writeln('${field.name}: ${field.name}$comma'); - } - }); - }); - } - - const List generatedComments = [ - ' Generated class from Pigeon that represents data sent in messages.' - ]; - addDocumentationComments( - indent, klass.documentationComments, _docCommentSpec, - generatorComments: generatedComments); - - indent.write('struct ${klass.name} '); - indent.scoped('{', '}', () { - getFieldsInSerializationOrder(klass).forEach(writeField); - - indent.writeln(''); - writeFromList(); - writeToList(); - }); - } - - void writeApi(Api api, Root root) { - if (api.location == ApiLocation.host) { - _writeHostApi(indent, api, root); - } else if (api.location == ApiLocation.flutter) { - _writeFlutterApi(indent, api, root); - } - } - - void writeWrapResult() { - indent.write('private func wrapResult(_ result: Any?) -> [Any?] '); - indent.scoped('{', '}', () { - indent.writeln('return [result]'); - }); - } - - void writeWrapError() { - indent.write('private func wrapError(_ error: FlutterError) -> [Any?] '); - indent.scoped('{', '}', () { - indent.write('return '); - indent.scoped('[', ']', () { - indent.writeln('error.code,'); - indent.writeln('error.message,'); - indent.writeln('error.details'); - }); - }); - } - - writeHeader(); - indent.addln(''); - writeImports(); - indent.addln(''); - indent.writeln('$_docCommentPrefix Generated class from Pigeon.'); - for (final Enum anEnum in root.enums) { - indent.writeln(''); - writeEnum(anEnum); - } - - for (final Class klass in root.classes) { - indent.addln(''); - writeDataClass(klass); - } - - if (root.apis.any((Api api) => - api.location == ApiLocation.host && - api.methods.any((Method it) => it.isAsynchronous))) { - indent.addln(''); - } - - for (final Api api in root.apis) { - if (getCodecClasses(api, root).isNotEmpty) { - _writeCodec(indent, api, root); - indent.addln(''); - } - writeApi(api, root); - } - - indent.addln(''); - writeWrapResult(); - indent.addln(''); - writeWrapError(); -} diff --git a/packages/pigeon/mock_handler_tester/test/message.dart b/packages/pigeon/mock_handler_tester/test/message.dart index 4f6c9d14457..bf02b407137 100644 --- a/packages/pigeon/mock_handler_tester/test/message.dart +++ b/packages/pigeon/mock_handler_tester/test/message.dart @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.0), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import + import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; diff --git a/packages/pigeon/mock_handler_tester/test/test.dart b/packages/pigeon/mock_handler_tester/test/test.dart index 9b97826b605..5cc8473b05b 100644 --- a/packages/pigeon/mock_handler_tester/test/test.dart +++ b/packages/pigeon/mock_handler_tester/test/test.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.0), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import // ignore_for_file: avoid_relative_lib_imports diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java index ea8e343c37f..d97d0cc5df9 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon package com.example.alternate_language_test_plugin; @@ -24,6 +24,15 @@ /** Generated class from Pigeon. */ @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"}) public class CoreTests { + @NonNull + private static ArrayList wrapError(@NonNull Throwable exception) { + ArrayList errorList = new ArrayList<>(3); + errorList.add(exception.toString()); + errorList.add(exception.getClass().getSimpleName()); + errorList.add( + "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); + return errorList; + } public enum AnEnum { ONE(0), @@ -1647,14 +1656,4 @@ static void setup(BinaryMessenger binaryMessenger, HostTrivialApi api) { } } } - - @NonNull - private static ArrayList wrapError(@NonNull Throwable exception) { - ArrayList errorList = new ArrayList<>(3); - errorList.add(exception.toString()); - errorList.add(exception.getClass().getSimpleName()); - errorList.add( - "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); - return errorList; - } } diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h index 2db8f7122ae..bfab566ab69 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h @@ -2,9 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon + #import + @protocol FlutterBinaryMessenger; @protocol FlutterMessageCodec; @class FlutterError; @@ -207,6 +209,7 @@ NSObject *FlutterIntegrationCoreApiGetCodec(void); - (void)echoString:(NSString *)aString completion:(void (^)(NSString *_Nullable, NSError *_Nullable))completion; @end + /// The codec used by HostTrivialApi. NSObject *HostTrivialApiGetCodec(void); diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.m index 078bf32b501..7f73cf6a4db 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.m @@ -2,8 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon + #import "CoreTests.gen.h" #import @@ -33,11 +34,13 @@ + (AllTypes *)fromList:(NSArray *)list; + (nullable AllTypes *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end + @interface AllNullableTypes () + (AllNullableTypes *)fromList:(NSArray *)list; + (nullable AllNullableTypes *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end + @interface AllNullableTypesWrapper () + (AllNullableTypesWrapper *)fromList:(NSArray *)list; + (nullable AllNullableTypesWrapper *)nullableFromList:(NSArray *)list; @@ -894,6 +897,7 @@ - (void)echoString:(NSString *)arg_aString }]; } @end + NSObject *HostTrivialApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; sSharedObject = [FlutterStandardMessageCodec sharedInstance]; diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/core_tests.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/core_tests.gen.dart index fe0fe3335ea..1040f61c124 100644 --- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/core_tests.gen.dart +++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/core_tests.gen.dart @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.0), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import + import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart index e92ab29cebd..c3b44b0a10a 100644 --- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart +++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.0), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import + import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart index 1ff71bfa346..3bf1fa57656 100644 --- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart +++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.0), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import + import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart index 88f4611e746..82bfce22591 100644 --- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart +++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.0), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import + import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart index 13fe926bcf1..a4a52e6c3f3 100644 --- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart +++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.0), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import + import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart index be1979c8adf..51af74c2635 100644 --- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart +++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.0), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import + import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart index 36a569e4f46..f9855f6dcc7 100644 --- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart +++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.0), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import + import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart index 3560cc2c37b..1040f61c124 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import + import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt index 17d7e9ffeb9..c2c039086f5 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon package com.example.test_plugin @@ -15,7 +15,17 @@ import io.flutter.plugin.common.StandardMessageCodec import java.io.ByteArrayOutputStream import java.nio.ByteBuffer -/** Generated class from Pigeon. */ +private fun wrapResult(result: Any?): List { + return listOf(result) +} + +private fun wrapError(exception: Throwable): List { + return listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) +} enum class AnEnum(val raw: Int) { ONE(0), @@ -810,15 +820,3 @@ interface HostTrivialApi { } } } - -private fun wrapResult(result: Any?): List { - return listOf(result) -} - -private fun wrapError(exception: Throwable): List { - return listOf( - exception.javaClass.simpleName, - exception.toString(), - "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) - ) -} diff --git a/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift index bcf93bcfcc8..407ee693538 100644 --- a/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift +++ b/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation @@ -15,7 +15,18 @@ import FlutterMacOS #endif -/// Generated class from Pigeon. + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: FlutterError) -> [Any?] { + return [ + error.code, + error.message, + error.details + ] +} enum AnEnum: Int { case one = 0 @@ -666,15 +677,3 @@ class HostTrivialApiSetup { } } } - -private func wrapResult(_ result: Any?) -> [Any?] { - return [result] -} - -private func wrapError(_ error: FlutterError) -> [Any?] { - return [ - error.code, - error.message, - error.details - ] -} diff --git a/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift index bcf93bcfcc8..407ee693538 100644 --- a/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift +++ b/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation @@ -15,7 +15,18 @@ import FlutterMacOS #endif -/// Generated class from Pigeon. + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: FlutterError) -> [Any?] { + return [ + error.code, + error.message, + error.details + ] +} enum AnEnum: Int { case one = 0 @@ -666,15 +677,3 @@ class HostTrivialApiSetup { } } } - -private func wrapResult(_ result: Any?) -> [Any?] { - return [result] -} - -private func wrapError(_ error: FlutterError) -> [Any?] { - return [ - error.code, - error.message, - error.details - ] -} diff --git a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp index 98b9395618e..6c39bceea12 100644 --- a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp +++ b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #undef _HAS_EXCEPTIONS diff --git a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h index aac405e9ce8..c752a5a1f3d 100644 --- a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h +++ b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// Autogenerated from Pigeon (v6.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon -#ifndef PIGEON_CORE_TESTS_PIGEONTEST_H_ -#define PIGEON_CORE_TESTS_PIGEONTEST_H_ +#ifndef PIGEON_CORE_TESTS_GEN_H_H_ +#define PIGEON_CORE_TESTS_GEN_H_H_ #include #include #include @@ -17,12 +17,11 @@ #include namespace core_tests_pigeontest { + class CoreTestsTest; // Generated class from Pigeon. -enum class AnEnum { one = 0, two = 1, three = 2 }; - class FlutterError { public: explicit FlutterError(const std::string& code) : code_(code) {} @@ -64,6 +63,8 @@ class ErrorOr { std::variant v_; }; +enum class AnEnum { one = 0, two = 1, three = 2 }; + // Generated class from Pigeon that represents data sent in messages. class AllTypes { public: @@ -412,4 +413,4 @@ class HostTrivialApi { HostTrivialApi() = default; }; } // namespace core_tests_pigeontest -#endif // PIGEON_CORE_TESTS_PIGEONTEST_H_ +#endif // PIGEON_CORE_TESTS_GEN_H_H_ diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index f2a5a9a6639..caa82a391df 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%3Apigeon -version: 5.0.1 # This must match the version in lib/generator_tools.dart +version: 6.0.0 # This must match the version in lib/generator_tools.dart environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/pigeon/test/cpp_generator_test.dart b/packages/pigeon/test/cpp_generator_test.dart index d8e991700a0..97dc1298600 100644 --- a/packages/pigeon/test/cpp_generator_test.dart +++ b/packages/pigeon/test/cpp_generator_test.dart @@ -4,6 +4,7 @@ import 'package:pigeon/ast.dart'; import 'package:pigeon/cpp_generator.dart'; +import 'package:pigeon/generator_tools.dart'; import 'package:pigeon/pigeon.dart' show Error; import 'package:test/test.dart'; @@ -45,7 +46,13 @@ void main() { ], enums: []); { final StringBuffer sink = StringBuffer(); - generateCppHeader(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('class Input')); expect(code, contains('class Output')); @@ -53,7 +60,13 @@ void main() { } { final StringBuffer sink = StringBuffer(); - generateCppSource(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('Input::Input()')); expect(code, contains('Output::Output')); @@ -101,7 +114,13 @@ void main() { ], enums: []); { final StringBuffer sink = StringBuffer(); - generateCppHeader(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); // Method name and argument names should be adjusted. expect(code, contains(' DoSomething(const Input& some_input)')); @@ -116,7 +135,13 @@ void main() { } { final StringBuffer sink = StringBuffer(); - generateCppSource(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('pointer_input_field')); expect(code, contains('Output::output_field()')); @@ -144,7 +169,17 @@ void main() { ], classes: [], enums: []); { final StringBuffer sink = StringBuffer(); - generateCppHeader(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const CppOptions(), + ); + generator.generate( + generatorOptions, + root, + sink, + ); final String code = sink.toString(); expect( @@ -184,7 +219,13 @@ void main() { ], classes: [], enums: []); { final StringBuffer sink = StringBuffer(); - generateCppHeader(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( @@ -238,14 +279,26 @@ void main() { ], enums: []); { final StringBuffer sink = StringBuffer(); - generateCppHeader(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, isNot(contains('){'))); expect(code, isNot(contains('const{'))); } { final StringBuffer sink = StringBuffer(); - generateCppSource(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, isNot(contains('){'))); expect(code, isNot(contains('const{'))); @@ -271,7 +324,13 @@ void main() { ], classes: [], enums: []); { final StringBuffer sink = StringBuffer(); - generateCppHeader(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains(''' #include @@ -286,8 +345,13 @@ void main() { } { final StringBuffer sink = StringBuffer(); - generateCppSource( - const CppOptions(headerIncludePath: 'a_header.h'), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const CppOptions(headerIncludePath: 'a_header.h'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains(''' #include "a_header.h" @@ -323,14 +387,26 @@ void main() { ], classes: [], enums: []); { final StringBuffer sink = StringBuffer(); - generateCppHeader(const CppOptions(namespace: 'foo'), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const CppOptions(namespace: 'foo'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('namespace foo {')); expect(code, contains('} // namespace foo')); } { final StringBuffer sink = StringBuffer(); - generateCppSource(const CppOptions(namespace: 'foo'), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const CppOptions(namespace: 'foo'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('namespace foo {')); expect(code, contains('} // namespace foo')); @@ -391,7 +467,13 @@ void main() { ], enums: []); { final StringBuffer sink = StringBuffer(); - generateCppHeader(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); // Getters should return const pointers. expect(code, contains('const bool* nullable_bool()')); @@ -423,7 +505,13 @@ void main() { } { final StringBuffer sink = StringBuffer(); - generateCppSource(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); // Getters extract optionals. expect(code, @@ -522,7 +610,13 @@ void main() { ], enums: []); { final StringBuffer sink = StringBuffer(); - generateCppHeader(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); // POD getters should return copies references. expect(code, contains('bool non_nullable_bool()')); @@ -547,7 +641,13 @@ void main() { } { final StringBuffer sink = StringBuffer(); - generateCppSource(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); // Getters just return the value. expect(code, contains('return non_nullable_bool_;')); @@ -645,7 +745,13 @@ void main() { ], enums: []); { final StringBuffer sink = StringBuffer(); - generateCppHeader(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, contains('ErrorOr> ReturnNullableBool()')); @@ -750,7 +856,13 @@ void main() { ], enums: []); { final StringBuffer sink = StringBuffer(); - generateCppHeader(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('ErrorOr ReturnBool()')); expect(code, contains('ErrorOr ReturnInt()')); @@ -832,7 +944,13 @@ void main() { ], enums: []); { final StringBuffer sink = StringBuffer(); - generateCppHeader(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -846,7 +964,13 @@ void main() { } { final StringBuffer sink = StringBuffer(); - generateCppSource(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); // Most types should just use get_if, since the parameter is a pointer, // and get_if will automatically handle null values (since a null @@ -963,7 +1087,13 @@ void main() { ], enums: []); { final StringBuffer sink = StringBuffer(); - generateCppHeader(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -977,7 +1107,13 @@ void main() { } { final StringBuffer sink = StringBuffer(); - generateCppSource(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); // Most types should extract references. Since the type is non-nullable, // there's only one possible type. @@ -1037,7 +1173,13 @@ void main() { ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); - generateCppSource(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); // A bare 'auto' here would create a copy, not a reference, which is // ineffecient. @@ -1149,7 +1291,13 @@ void main() { ], ); final StringBuffer sink = StringBuffer(); - generateCppHeader(const CppOptions(headerIncludePath: 'foo'), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const CppOptions(headerIncludePath: 'foo'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); for (final String comment in comments) { expect(code, contains('//$comment')); @@ -1184,7 +1332,13 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateCppHeader(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, isNot(contains(' : public flutter::StandardCodecSerializer'))); }); @@ -1226,7 +1380,13 @@ void main() { ]) ], enums: []); final StringBuffer sink = StringBuffer(); - generateCppHeader(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains(' : public flutter::StandardCodecSerializer')); }); @@ -1295,7 +1455,13 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateCppSource(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, isNot(contains('reply(wrap'))); expect(code, contains('reply(flutter::EncodableValue(')); @@ -1349,7 +1515,13 @@ void main() { ]) ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); - generateCppSource(const CppOptions(), root, sink); + const CppGenerator generator = CppGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const CppOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); // Nothing should be captured by reference for async handlers, since their // lifetime is unknown (and expected to be longer than the stack's). diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart index c77e5d89e37..d99dde7a71b 100644 --- a/packages/pigeon/test/dart_generator_test.dart +++ b/packages/pigeon/test/dart_generator_test.dart @@ -29,7 +29,8 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('class Foobar')); expect(code, contains(' dataType1? field1;')); @@ -49,7 +50,8 @@ void main() { enums: [anEnum], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('enum Foobar')); expect(code, contains(' one,')); @@ -92,7 +94,8 @@ void main() { ]) ], enums: []); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('class Api')); expect(code, contains('Future doSomething(Input arg_input)')); @@ -118,7 +121,8 @@ void main() { ]) ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('class Api')); expect(code, contains('Future add(int arg_x, int arg_y)')); @@ -145,7 +149,8 @@ void main() { ]) ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('class Api')); expect(code, contains('int add(int x, int y)')); @@ -182,7 +187,8 @@ void main() { ) ], enums: []); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect( code, @@ -224,7 +230,8 @@ void main() { ) ], enums: []); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect( code, @@ -276,7 +283,8 @@ void main() { ]) ], enums: []); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('abstract class Api')); expect(code, contains('static void setup(Api')); @@ -310,7 +318,8 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('Future doSomething')); expect(code, contains('return;')); @@ -343,7 +352,8 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); // The next line verifies that we're not setting a variable to the value of "doSomething", but // ignores the line where we assert the value of the argument isn't null, since on that line @@ -373,7 +383,8 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, matches('output.*=.*doSomething[(][)]')); expect(code, contains('Output doSomething();')); @@ -415,7 +426,8 @@ void main() { ) ]); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('enum1?.index,')); expect(code, contains('? Enum.values[result[0]! as int]')); @@ -442,7 +454,8 @@ void main() { ]) ]); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('enum Foo {')); expect(code, contains('Future bar(Foo? arg_foo) async')); @@ -485,7 +498,8 @@ void main() { ) ]); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('enum1.index,')); expect(code, contains('enum1: Enum.values[result[0]! as int]')); @@ -512,7 +526,8 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, matches('channel.send[(]null[)]')); }); @@ -570,7 +585,8 @@ void main() { ], enums: []); final StringBuffer mainCodeSink = StringBuffer(); final StringBuffer testCodeSink = StringBuffer(); - generateDart(DartOptions(), root, mainCodeSink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, mainCodeSink); final String mainCode = mainCodeSink.toString(); expect(mainCode, isNot(contains(r"import 'fo\'o.dart';"))); expect(mainCode, contains('class Api {')); @@ -578,12 +594,15 @@ void main() { expect(mainCode, isNot(contains('.ApiMock.doSomething'))); expect(mainCode, isNot(contains("'${Keys.result}': output"))); expect(mainCode, isNot(contains('return [];'))); - generateTestDart( - DartOptions(), + + const DartGenerator testGenerator = DartGenerator(); + testGenerator.generateTest( + DartOptions( + sourceOutPath: "fo'o.dart", + testOutPath: 'test.dart', + ), root, testCodeSink, - sourceOutPath: "fo'o.dart", - testOutPath: 'test.dart', ); final String testCode = testCodeSink.toString(); expect(testCode, contains(r"import 'fo\'o.dart';")); @@ -631,7 +650,8 @@ void main() { ]) ], enums: []); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('abstract class Api')); expect(code, contains('Future doSomething(Input arg0);')); @@ -675,7 +695,8 @@ void main() { ]) ], enums: []); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, isNot(matches('=.s*doSomething'))); expect(code, contains('await api.doSomething(')); @@ -719,7 +740,8 @@ void main() { ]) ], enums: []); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('class Api')); expect(code, matches('Output.*doSomething.*Input')); @@ -747,7 +769,8 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, matches('channel.send[(]null[)]')); }); @@ -759,7 +782,9 @@ void main() { test('header', () { final Root root = Root(apis: [], classes: [], enums: []); final StringBuffer sink = StringBuffer(); - generateDart( + + const DartGenerator generator = DartGenerator(); + generator.generate( DartOptions(copyrightHeader: makeIterable('hello world')), root, sink, @@ -788,7 +813,8 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('class Foobar')); expect(code, contains(' List? field1;')); @@ -815,7 +841,8 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('class Foobar')); expect(code, contains(' Map? field1;')); @@ -844,7 +871,8 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('doit(List arg')); }); @@ -872,7 +900,8 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('doit(List arg')); }); @@ -896,7 +925,8 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('Future> doit(')); expect(code, @@ -931,7 +961,8 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('List doit(')); expect( @@ -958,7 +989,8 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('Future doit()')); expect(code, contains('return (replyList[0] as int?);')); @@ -983,7 +1015,8 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('Future?> doit()')); expect(code, @@ -1008,7 +1041,8 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('Future doit()')); expect(code, contains('return (replyList[0] as int?);')); @@ -1031,7 +1065,8 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('int? doit();')); expect(code, contains('final int? output = api.doit();')); @@ -1055,7 +1090,8 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('Future doit();')); expect(code, contains('final int? output = await api.doit();')); @@ -1078,7 +1114,8 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect( code, @@ -1107,7 +1144,8 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('Future doit(int? arg_foo) async {')); }); @@ -1133,7 +1171,8 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('void doit(int? foo);')); }); @@ -1150,12 +1189,14 @@ name: foobar final Root root = Root(classes: [], apis: [], enums: []); final StringBuffer sink = StringBuffer(); - generateTestDart( - DartOptions(), + const DartGenerator testGenerator = DartGenerator(); + testGenerator.generateTest( + DartOptions( + sourceOutPath: path.join(foo.path, 'bar.dart'), + testOutPath: path.join(tempDir.path, 'test', 'bar_test.dart'), + ), root, sink, - sourceOutPath: path.join(foo.path, 'bar.dart'), - testOutPath: path.join(tempDir.path, 'test', 'bar_test.dart'), ); final String code = sink.toString(); expect(code, contains("import 'package:foobar/foo/bar.dart';")); @@ -1238,7 +1279,8 @@ name: foobar ], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); for (final String comment in comments) { expect(code, contains('///$comment')); @@ -1273,7 +1315,8 @@ name: foobar enums: [], ); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, isNot(contains('extends StandardMessageCodec'))); expect(code, contains('StandardMessageCodec')); @@ -1316,7 +1359,8 @@ name: foobar ]) ], enums: []); final StringBuffer sink = StringBuffer(); - generateDart(DartOptions(), root, sink); + const DartGenerator generator = DartGenerator(); + generator.generate(DartOptions(), root, sink); final String code = sink.toString(); expect(code, contains('extends StandardMessageCodec')); }); @@ -1354,13 +1398,17 @@ name: foobar ], ); final StringBuffer sink = StringBuffer(); - generateTestDart( - DartOptions(), + + const DartGenerator testGenerator = DartGenerator(); + testGenerator.generateTest( + DartOptions( + sourceOutPath: 'code.dart', + testOutPath: 'test.dart', + ), root, sink, - sourceOutPath: 'code.dart', - testOutPath: 'test.dart', ); + final String testCode = sink.toString(); expect( testCode, diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart index bfcea98cfe2..40ec00545e7 100644 --- a/packages/pigeon/test/java_generator_test.dart +++ b/packages/pigeon/test/java_generator_test.dart @@ -27,7 +27,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('public class Messages')); expect(code, contains('public static class Foobar')); @@ -55,7 +56,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('public enum Foobar')); expect(code, contains(' ONE(0),')); @@ -86,7 +88,8 @@ void main() { final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages', package: 'com.google.foobar'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('package com.google.foobar;')); expect(code, contains('ArrayList toList()')); @@ -129,7 +132,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('public interface Api')); expect(code, matches('Output.*doSomething.*Input')); @@ -200,7 +204,8 @@ void main() { final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('private @Nullable Boolean aBool;')); expect(code, contains('private @Nullable Long aInt;')); @@ -249,7 +254,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('public static class Api')); expect(code, matches('doSomething.*Input.*Output')); @@ -283,7 +289,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, isNot(matches('=.*doSomething'))); expect(code, contains('doSomething(')); @@ -317,7 +324,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('Reply')); expect(code, contains('callback.reply(null)')); @@ -345,7 +353,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('Output doSomething()')); expect(code, contains('api.doSomething()')); @@ -373,7 +382,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('doSomething(Reply')); expect(code, contains('channel.send(null')); @@ -392,7 +402,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('public static class Foobar')); expect(code, contains('private @Nullable List field1;')); @@ -411,7 +422,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('public static class Foobar')); expect(code, contains('private @Nullable Map field1;')); @@ -447,7 +459,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('public class Messages')); expect(code, contains('public static class Outer')); @@ -498,7 +511,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('public interface Api')); expect(code, contains('public interface Result {')); @@ -549,7 +563,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('public static class Api')); expect(code, matches('doSomething.*Input.*Output')); @@ -582,7 +597,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('public enum Enum1')); expect(code, contains(' ONE(0),')); @@ -621,7 +637,8 @@ void main() { ]); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('public enum Foo')); expect( @@ -641,7 +658,8 @@ void main() { className: 'Messages', copyrightHeader: makeIterable('hello world'), ); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, startsWith('// hello world')); }); @@ -667,7 +685,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('class Foobar')); expect(code, contains('List field1;')); @@ -695,7 +714,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('class Foobar')); expect(code, contains('Map field1;')); @@ -725,7 +745,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('doit(@NonNull List arg')); }); @@ -754,7 +775,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('doit(@NonNull List arg')); }); @@ -779,7 +801,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('List doit(')); expect(code, contains('List output =')); @@ -805,7 +828,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('doit(Reply> callback)')); expect(code, contains('List output =')); @@ -828,7 +852,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('doit(Reply callback)')); expect( @@ -858,7 +883,8 @@ void main() { ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('class Messages')); expect(code, contains('Long add(@NonNull Long x, @NonNull Long y)')); @@ -889,7 +915,8 @@ void main() { ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Api'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('Object xArg = args.get(0)')); }); @@ -915,7 +942,8 @@ void main() { ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('class Messages')); expect(code, contains('BasicMessageChannel channel')); @@ -947,7 +975,8 @@ void main() { ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect( code, @@ -973,7 +1002,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('@Nullable Long doit();')); }); @@ -997,7 +1027,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); // Java doesn't accept nullability annotations in type arguments. expect(code, contains('Result')); @@ -1025,7 +1056,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains(' void doit(@Nullable Long foo);')); }); @@ -1052,7 +1084,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect( code, @@ -1083,7 +1116,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect( code, @@ -1108,7 +1142,8 @@ void main() { final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages', useGeneratedAnnotation: true); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains('@javax.annotation.Generated("dev.flutter.pigeon")')); }); @@ -1125,7 +1160,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, isNot(contains('@javax.annotation.Generated("dev.flutter.pigeon")'))); @@ -1207,7 +1243,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); for (final String comment in comments) { // This regex finds the comment only between the open and close comment block @@ -1247,7 +1284,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, isNot(contains(' extends StandardMessageCodec'))); expect(code, contains('StandardMessageCodec')); @@ -1291,7 +1329,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - generateJava(javaOptions, root, sink); + const JavaGenerator generator = JavaGenerator(); + generator.generate(javaOptions, root, sink); final String code = sink.toString(); expect(code, contains(' extends StandardMessageCodec')); }); diff --git a/packages/pigeon/test/kotlin_generator_test.dart b/packages/pigeon/test/kotlin_generator_test.dart index 4653410837e..aa4ec3efb37 100644 --- a/packages/pigeon/test/kotlin_generator_test.dart +++ b/packages/pigeon/test/kotlin_generator_test.dart @@ -27,7 +27,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('data class Foobar (')); expect(code, contains('val field1: Long? = null')); @@ -50,7 +51,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('enum class Foobar(val raw: Int) {')); expect(code, contains('ONE(0)')); @@ -78,7 +80,8 @@ void main() { ]); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('enum class Foo(val raw: Int) {')); expect(code, contains('val fooArg = Foo.ofRaw(args[0] as Int)')); @@ -124,7 +127,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('interface Api')); expect(code, contains('fun doSomething(input: Input): Output')); @@ -213,7 +217,8 @@ void main() { final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('val aBool: Boolean? = null')); expect(code, contains('val aInt: Long? = null')); @@ -265,7 +270,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('class Api(private val binaryMessenger: BinaryMessenger)')); @@ -302,7 +308,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, isNot(matches('.*doSomething(.*) ->'))); expect(code, matches('doSomething(.*)')); @@ -338,7 +345,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('callback: () -> Unit')); expect(code, contains('callback()')); @@ -367,7 +375,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('fun doSomething(): Output')); expect(code, contains('wrapped = listOf(api.doSomething())')); @@ -398,7 +407,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('fun doSomething(callback: (Output) -> Unit)')); expect(code, contains('channel.send(null)')); @@ -418,7 +428,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('data class Foobar')); expect(code, contains('val field1: List? = null')); @@ -438,7 +449,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('data class Foobar')); expect(code, contains('val field1: Map? = null')); @@ -476,7 +488,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('data class Outer')); expect(code, contains('data class Nested')); @@ -529,7 +542,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('interface Api')); expect(code, contains('api.doSomething(argArg) {')); @@ -577,7 +591,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('class Api')); expect(code, matches('fun doSomething.*Input.*callback.*Output.*Unit')); @@ -610,7 +625,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('enum class Enum1(val raw: Int)')); expect(code, contains('ONE(0)')); @@ -627,7 +643,8 @@ void main() { final KotlinOptions kotlinOptions = KotlinOptions( copyrightHeader: makeIterable('hello world'), ); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, startsWith('// hello world')); }); @@ -654,7 +671,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('data class Foobar')); expect(code, contains('val field1: List')); @@ -683,7 +701,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('data class Foobar')); expect(code, contains('val field1: Map')); @@ -714,7 +733,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('fun doit(arg: List')); }); @@ -744,7 +764,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('fun doit(argArg: List')); }); @@ -769,7 +790,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('fun doit(): List')); expect(code, contains('wrapped = listOf(api.doit())')); @@ -796,7 +818,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('fun doit(callback: (List) -> Unit')); expect(code, contains('val result = it as List')); @@ -824,7 +847,8 @@ void main() { ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('fun add(x: Long, y: Long): Long')); expect(code, contains('val args = message as List')); @@ -861,7 +885,8 @@ void main() { ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('val channel = BasicMessageChannel')); expect(code, contains('val result = it as Long')); @@ -889,7 +914,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('fun doit(): Long?')); }); @@ -913,7 +939,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('fun doit(callback: (Long?) -> Unit')); }); @@ -940,7 +967,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect( code, @@ -970,7 +998,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('fun doit(fooArg: Long?, callback: () -> Unit')); }); @@ -1005,7 +1034,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains('val input: String\n')); }); @@ -1086,7 +1116,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); for (final String comment in comments) { // This regex finds the comment only between the open and close comment block @@ -1126,7 +1157,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, isNot(contains(' : StandardMessageCodec() '))); expect(code, contains('StandardMessageCodec')); @@ -1170,7 +1202,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); - generateKotlin(kotlinOptions, root, sink); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate(kotlinOptions, root, sink); final String code = sink.toString(); expect(code, contains(' : StandardMessageCodec() ')); }); diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart index aeb2db68e26..64f92c857eb 100644 --- a/packages/pigeon/test/objc_generator_test.dart +++ b/packages/pigeon/test/objc_generator_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:pigeon/ast.dart'; +import 'package:pigeon/generator_tools.dart'; import 'package:pigeon/objc_generator.dart'; import 'package:pigeon/pigeon_lib.dart'; import 'package:test/test.dart'; @@ -17,7 +18,13 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('@interface Foobar')); expect(code, matches('@property.*NSString.*field1')); @@ -32,8 +39,13 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h'), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const ObjcOptions(headerIncludePath: 'foo.h'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('#import "foo.h"')); expect(code, contains('@implementation Foobar')); @@ -50,7 +62,13 @@ void main() { ) ]); final StringBuffer sink = StringBuffer(); - generateObjcHeader(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('typedef NS_ENUM(NSUInteger, Enum1) {')); expect(code, contains(' Enum1One = 0,')); @@ -68,7 +86,13 @@ void main() { ) ]); final StringBuffer sink = StringBuffer(); - generateObjcHeader(const ObjcOptions(prefix: 'PREFIX'), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(prefix: 'PREFIX'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('typedef NS_ENUM(NSUInteger, PREFIXEnum1) {')); expect(code, contains(' PREFIXEnum1One = 0,')); @@ -104,8 +128,13 @@ void main() { ], ); final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h'), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const ObjcOptions(headerIncludePath: 'foo.h'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('#import "foo.h"')); expect(code, contains('@implementation Foobar')); @@ -138,13 +167,25 @@ void main() { const ObjcOptions options = ObjcOptions(headerIncludePath: 'foo.h', prefix: 'AC'); { - generateObjcHeader(options, root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: options, + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('typedef NS_ENUM(NSUInteger, ACFoo)')); expect(code, contains(':(ACFoo)foo error:')); } { - generateObjcSource(options, root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: options, + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -207,8 +248,13 @@ void main() { ], ); final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h'), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(headerIncludePath: 'foo.h'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('@property(nonatomic, assign) Enum1 enum1')); }); @@ -240,7 +286,13 @@ void main() { ]) ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('@interface Input')); expect(code, contains('@interface Output')); @@ -279,8 +331,13 @@ void main() { ]) ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h'), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const ObjcOptions(headerIncludePath: 'foo.h'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('#import "foo.h"')); expect(code, contains('@implementation Input')); @@ -327,8 +384,13 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h'), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(headerIncludePath: 'foo.h'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('@interface Foobar')); expect(code, contains('@class FlutterStandardTypedData;')); @@ -356,8 +418,13 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h'), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const ObjcOptions(headerIncludePath: 'foo.h'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('@implementation Foobar')); expect(code, @@ -378,8 +445,13 @@ void main() { ]) ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h'), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(headerIncludePath: 'foo.h'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('@property(nonatomic, strong, nullable) Input * nested;')); @@ -399,8 +471,13 @@ void main() { ]) ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h'), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const ObjcOptions(headerIncludePath: 'foo.h'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -419,7 +496,13 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader(const ObjcOptions(prefix: 'ABC'), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('@interface ABCFoobar')); }); @@ -433,7 +516,13 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource(const ObjcOptions(prefix: 'ABC'), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const ObjcOptions(prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('@implementation ABCFoobar')); }); @@ -467,7 +556,13 @@ void main() { ]) ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader(const ObjcOptions(prefix: 'ABC'), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, matches('property.*ABCInput')); expect(code, matches('ABCNested.*doSomething.*ABCInput')); @@ -503,7 +598,13 @@ void main() { ]) ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource(const ObjcOptions(prefix: 'ABC'), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const ObjcOptions(prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('ABCInput fromList')); expect(code, matches(r'ABCInput.*=.*args.*0.*\;')); @@ -539,8 +640,13 @@ void main() { ]) ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h'), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(headerIncludePath: 'foo.h'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('@interface Api : NSObject')); expect( @@ -579,8 +685,13 @@ void main() { ]) ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h'), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const ObjcOptions(headerIncludePath: 'foo.h'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('@implementation Api')); expect(code, matches('void.*doSomething.*Input.*Output.*{')); @@ -609,10 +720,14 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('(void)doSomething:')); }); @@ -640,10 +755,14 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, isNot(matches('=.*doSomething'))); expect(code, matches('[.*doSomething:.*]')); @@ -673,10 +792,14 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('completion:(void(^)(NSError *_Nullable))')); }); @@ -704,10 +827,14 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('completion:(void(^)(NSError *_Nullable))')); expect(code, contains('completion(nil)')); @@ -730,10 +857,14 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, matches('ABCOutput.*doSomethingWithError:[(]FlutterError')); }); @@ -755,10 +886,14 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, matches('output.*=.*api doSomethingWithError:&error')); }); @@ -780,10 +915,14 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -808,10 +947,14 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -829,7 +972,13 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('@interface Foobar')); expect(code, matches('@property.*NSArray.*field1')); @@ -844,7 +993,13 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('@interface Foobar')); expect(code, matches('@property.*NSDictionary.*field1')); @@ -865,7 +1020,13 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('@interface Foobar')); expect( @@ -894,7 +1055,13 @@ void main() { ]) ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('(NSDictionary *)foo')); }); @@ -928,10 +1095,14 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -969,10 +1140,14 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -998,10 +1173,14 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -1020,10 +1199,14 @@ void main() { ]) ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -1061,10 +1244,14 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -1101,10 +1288,14 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -1123,10 +1314,14 @@ void main() { ]) ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -1152,10 +1347,14 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -1170,14 +1369,16 @@ void main() { test('source copyright', () { final Root root = Root(apis: [], classes: [], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource( - ObjcOptions( + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: ObjcOptions( headerIncludePath: 'foo.h', prefix: 'ABC', copyrightHeader: makeIterable('hello world')), - root, - sink, ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, startsWith('// hello world')); }); @@ -1185,14 +1386,16 @@ void main() { test('header copyright', () { final Root root = Root(apis: [], classes: [], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader( - ObjcOptions( + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: ObjcOptions( headerIncludePath: 'foo.h', prefix: 'ABC', copyrightHeader: makeIterable('hello world')), - root, - sink, ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, startsWith('// hello world')); }); @@ -1217,10 +1420,14 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('NSArray * field1')); }); @@ -1249,19 +1456,27 @@ void main() { ); { final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('doitArg:(NSArray *)arg')); } { final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -1294,19 +1509,27 @@ void main() { ); { final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('doitArg:(NSArray *)arg')); } { final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('doitArg:(NSArray *)arg')); } @@ -1342,10 +1565,14 @@ void main() { ); { final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('doitArg:(NSArray *> *)arg')); } @@ -1371,20 +1598,28 @@ void main() { ); { final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, contains('- (nullable NSArray *)doitWithError:')); } { final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('NSArray *output =')); } @@ -1410,20 +1645,28 @@ void main() { ); { final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, contains('doitWithCompletion:(void(^)(NSArray *')); } { final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, contains('doitWithCompletion:(void(^)(NSArray *')); @@ -1451,10 +1694,14 @@ void main() { ], classes: [], enums: []); { final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -1463,10 +1710,14 @@ void main() { } { final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('NSArray *args = message;')); expect(code, @@ -1500,10 +1751,14 @@ void main() { ], classes: [], enums: []); { final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -1512,10 +1767,14 @@ void main() { } { final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('NSArray *args = message;')); expect(code, @@ -1547,10 +1806,14 @@ void main() { ], classes: [], enums: []); { final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -1559,10 +1822,14 @@ void main() { } { final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - root, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -1605,19 +1872,27 @@ void main() { final Root divideRoot = getDivideRoot(ApiLocation.host); { final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - divideRoot, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, divideRoot, sink); final String code = sink.toString(); expect(code, matches('divideValue:.*by:.*error.*;')); } { final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - divideRoot, - sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), + ); + generator.generate(generatorOptions, divideRoot, sink); final String code = sink.toString(); expect(code, matches('divideValue:.*by:.*error.*;')); } @@ -1627,21 +1902,27 @@ void main() { final Root divideRoot = getDivideRoot(ApiLocation.flutter); { final StringBuffer sink = StringBuffer(); - generateObjcHeader( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - divideRoot, - sink, + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), ); + generator.generate(generatorOptions, divideRoot, sink); final String code = sink.toString(); expect(code, matches('divideValue:.*by:.*completion.*;')); } { final StringBuffer sink = StringBuffer(); - generateObjcSource( - const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), - divideRoot, - sink, + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: + const ObjcOptions(headerIncludePath: 'foo.h', prefix: 'ABC'), ); + generator.generate(generatorOptions, divideRoot, sink); final String code = sink.toString(); expect(code, matches('divideValue:.*by:.*completion.*{')); } @@ -1656,7 +1937,13 @@ void main() { ]), ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcHeader(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('@interface Foobar')); expect(code, contains('@property(nonatomic, copy) NSString * field1')); @@ -1679,7 +1966,13 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateObjcHeader(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -1704,7 +1997,13 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateObjcSource(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, matches(r'doitWithCompletion.*NSNumber \*_Nullable')); }); @@ -1726,7 +2025,13 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateObjcHeader(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, matches(r'nullable NSNumber.*doitWithError')); }); @@ -1753,13 +2058,25 @@ void main() { ); { final StringBuffer sink = StringBuffer(); - generateObjcHeader(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('doitFoo:(nullable NSNumber *)foo')); } { final StringBuffer sink = StringBuffer(); - generateObjcSource(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('NSNumber *arg_foo = GetNullableObjectAtIndex(args, 0);')); @@ -1788,13 +2105,25 @@ void main() { ); { final StringBuffer sink = StringBuffer(); - generateObjcHeader(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('doitFoo:(nullable NSNumber *)foo')); } { final StringBuffer sink = StringBuffer(); - generateObjcSource(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains('- (void)doitFoo:(nullable NSNumber *)arg_foo')); } @@ -1818,7 +2147,13 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateObjcSource(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect( code, @@ -1902,7 +2237,13 @@ void main() { ], ); final StringBuffer sink = StringBuffer(); - generateObjcHeader(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.header, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); for (final String comment in comments) { expect(code, contains('///$comment')); @@ -1937,7 +2278,13 @@ void main() { enums: [], ); final StringBuffer sink = StringBuffer(); - generateObjcSource(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, isNot(contains(' : FlutterStandardReader'))); }); @@ -1979,7 +2326,13 @@ void main() { ]) ], enums: []); final StringBuffer sink = StringBuffer(); - generateObjcSource(const ObjcOptions(), root, sink); + const ObjcGenerator generator = ObjcGenerator(); + final OutputFileOptions generatorOptions = + OutputFileOptions( + fileType: FileType.source, + languageOptions: const ObjcOptions(), + ); + generator.generate(generatorOptions, root, sink); final String code = sink.toString(); expect(code, contains(' : FlutterStandardReader')); }); diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart index 64c4af7b5ab..3d5c706bf41 100644 --- a/packages/pigeon/test/pigeon_lib_test.dart +++ b/packages/pigeon/test/pigeon_lib_test.dart @@ -938,10 +938,10 @@ abstract class Api { dartTestOut: 'stdout', dartOut: 'stdout', ); - final DartTestGeneratorAdapter dartGeneratorAdapter = + final DartTestGeneratorAdapter dartTestGeneratorAdapter = DartTestGeneratorAdapter(); final StringBuffer buffer = StringBuffer(); - dartGeneratorAdapter.generate(buffer, options, root, FileType.source); + dartTestGeneratorAdapter.generate(buffer, options, root, FileType.source); expect(buffer.toString(), startsWith('// Copyright 2013')); }); diff --git a/packages/pigeon/test/swift_generator_test.dart b/packages/pigeon/test/swift_generator_test.dart index 74c8aad15ca..dfd2dfbb487 100644 --- a/packages/pigeon/test/swift_generator_test.dart +++ b/packages/pigeon/test/swift_generator_test.dart @@ -26,7 +26,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('struct Foobar')); expect(code, contains('var field1: Int32? = nil')); @@ -49,7 +50,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('enum Foobar: Int')); expect(code, contains(' case one = 0')); @@ -77,7 +79,8 @@ void main() { ]); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('enum Foo: Int')); expect(code, contains('let fooArg = Foo(rawValue: args[0] as! Int)!')); @@ -120,7 +123,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('protocol Api')); expect(code, matches('func doSomething.*Input.*Output')); @@ -183,7 +187,8 @@ void main() { final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('var aBool: Bool? = nil')); expect(code, contains('var aInt: Int32? = nil')); @@ -232,7 +237,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('class Api')); expect(code, contains('init(binaryMessenger: FlutterBinaryMessenger)')); @@ -267,7 +273,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, isNot(matches('.*doSomething(.*) ->'))); expect(code, matches('doSomething(.*)')); @@ -301,7 +308,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('completion: @escaping () -> Void')); expect(code, contains('completion()')); @@ -329,7 +337,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('func doSomething() -> Output')); expect(code, contains('let result = api.doSomething()')); @@ -358,7 +367,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('func doSomething(completion: @escaping (Output) -> Void)')); @@ -378,7 +388,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('struct Foobar')); expect(code, contains('var field1: [Any?]? = nil')); @@ -397,7 +408,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('struct Foobar')); expect(code, contains('var field1: [AnyHashable: Any?]? = nil')); @@ -433,7 +445,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('struct Outer')); expect(code, contains('struct Nested')); @@ -481,7 +494,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('protocol Api')); expect(code, contains('api.doSomething(arg: argArg) { result in')); @@ -526,7 +540,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('class Api')); expect(code, matches('func doSomething.*Input.*completion.*Output.*Void')); @@ -558,7 +573,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('enum Enum1: Int')); expect(code, contains('case one = 0')); @@ -575,7 +591,8 @@ void main() { final SwiftOptions swiftOptions = SwiftOptions( copyrightHeader: makeIterable('hello world'), ); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, startsWith('// hello world')); }); @@ -601,7 +618,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('struct Foobar')); expect(code, contains('var field1: [Int32?]')); @@ -629,7 +647,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('struct Foobar')); expect(code, contains('var field1: [String?: String?]')); @@ -659,7 +678,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('func doit(arg: [Int32?]')); }); @@ -688,7 +708,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('func doit(arg argArg: [Int32?]')); }); @@ -713,7 +734,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('func doit() -> [Int32?]')); expect(code, contains('let result = api.doit()')); @@ -740,7 +762,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect( code, contains('func doit(completion: @escaping ([Int32?]) -> Void')); @@ -769,7 +792,8 @@ void main() { ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('func add(x: Int32, y: Int32) -> Int32')); expect(code, contains('let args = message as! [Any?]')); @@ -800,7 +824,8 @@ void main() { ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('let channel = FlutterBasicMessageChannel')); expect(code, contains('let result = response as! Int32')); @@ -830,7 +855,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('func doit() -> Int32?')); }); @@ -854,7 +880,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('func doit(completion: @escaping (Int32?) -> Void')); }); @@ -881,7 +908,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('let fooArg = args[0] as? Int32')); }); @@ -908,7 +936,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect( code, @@ -944,7 +973,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains('var input: String\n')); }); @@ -1025,7 +1055,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); for (final String comment in comments) { expect(code, contains('///$comment')); @@ -1061,7 +1092,8 @@ void main() { ); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, isNot(contains(': FlutterStandardReader '))); }); @@ -1104,7 +1136,8 @@ void main() { ], enums: []); final StringBuffer sink = StringBuffer(); const SwiftOptions swiftOptions = SwiftOptions(); - generateSwift(swiftOptions, root, sink); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains(': FlutterStandardReader ')); });