diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index 2af203239b6..bda54aef371 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,3 +1,23 @@ +## 5.0.5 + +* Adds writeApi method to Generator classes. + +## 5.0.4 + +* Adds writeEnum method to Generator classes. + +## 5.0.3 + +* Adds writeEnum method to Generator classes. + +## 5.0.2 + +* Adds writeImports method to Generator classes. + +## 5.0.1 + +* Adds writeHeaders method to Generator classes and updates tests to use new Generators. + ## 5.0.0 * Creates new Generator classes for each language. diff --git a/packages/pigeon/lib/cpp_generator.dart b/packages/pigeon/lib/cpp_generator.dart index cddefacfc79..313c993ed64 100644 --- a/packages/pigeon/lib/cpp_generator.dart +++ b/packages/pigeon/lib/cpp_generator.dart @@ -75,102 +75,363 @@ class CppGenerator extends Generator> { /// Instantiates a Cpp Generator. CppGenerator(); - /// Generates Cpp files with specified [OutputFileOptions] + /// Generates Cpp header files with specified [CppOptions] @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); + assert(generatorOptions.fileType == FileType.header || + generatorOptions.fileType == FileType.source); + + final Indent indent = Indent(sink); + writeFilePrologue(generatorOptions, root, sink, indent); + writeFileImports(generatorOptions, root, sink, indent); + + for (final Enum anEnum in root.enums) { + writeEnum(generatorOptions, root, sink, indent, anEnum); + } + + indent.addln(''); + if (generatorOptions.fileType == FileType.header) { + _writeErrorOr(indent, friends: root.apis.map((Api api) => api.name)); + } + for (final Class klass in root.classes) { + writeDataClass(generatorOptions, root, sink, indent, klass); + } + + for (final Api api in root.apis) { + if (getCodecClasses(api, root).isNotEmpty) { + _writeCodec(generatorOptions, root, sink, indent, api); + indent.addln(''); + } + if (api.location == ApiLocation.host) { + writeHostApi(generatorOptions, root, sink, indent, api); + } else if (api.location == ApiLocation.flutter) { + writeFlutterApi(generatorOptions, root, sink, indent, api); + } + } + + if (generatorOptions.fileType == FileType.header) { + if (generatorOptions.languageOptions.namespace != null) { + indent.writeln( + '} // namespace ${generatorOptions.languageOptions.namespace}'); + } + final String guardName = _getGuardName( + generatorOptions.languageOptions.headerIncludePath, + generatorOptions.languageOptions.namespace); + indent.writeln('#endif // $guardName'); + } else { + if (generatorOptions.languageOptions.namespace != null) { + indent.writeln( + '} // namespace ${generatorOptions.languageOptions.namespace}'); + } + } + } + + @override + void writeFilePrologue(OutputFileOptions generatorOptions, + Root root, StringSink sink, Indent indent) { + final FileType fileType = generatorOptions.fileType; if (fileType == FileType.header) { - generateCppHeader(languageOptions.languageOptions, root, sink); + _writeCppHeaderPrologue(generatorOptions, root, sink, indent); } else { - generateCppSource(languageOptions.languageOptions, root, sink); + _writeCppSourcePrologue(generatorOptions, root, sink, indent); } } -} -String _getCodecSerializerName(Api api) => '${api.name}CodecSerializer'; + @override + void writeFileImports(OutputFileOptions generatorOptions, + Root root, StringSink sink, Indent indent) { + final FileType fileType = generatorOptions.fileType; + if (fileType == FileType.header) { + _writeCppHeaderImports(generatorOptions, root, sink, indent); + } else { + _writeCppSourceImports(generatorOptions, root, sink, indent); + } + } -const String _pointerPrefix = 'pointer'; -const String _encodablePrefix = 'encodable'; + @override + void writeEnum(OutputFileOptions generatorOptions, Root root, + StringSink sink, Indent indent, Enum anEnum) { + final FileType fileType = generatorOptions.fileType; + if (fileType == FileType.header) { + _writeCppHeaderEnum(generatorOptions, root, sink, indent, anEnum); + } + } + + @override + void writeDataClass(OutputFileOptions generatorOptions, Root root, + StringSink sink, Indent indent, Class klass) { + if (generatorOptions.fileType == FileType.header) { + // 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.languageOptions.namespace?.endsWith('_pigeontest') ?? + false) { + testFixtureClass = + '${_pascalCaseFromSnakeCase(generatorOptions.languageOptions.namespace!.replaceAll('_pigeontest', ''))}Test'; + } + _writeCppHeaderDataClass(generatorOptions, root, sink, indent, klass, + testFriend: testFixtureClass); + } else { + _writeCppSourceDataClass(generatorOptions, root, sink, indent, klass); + } + } + + @override + void writeClassEncode( + OutputFileOptions generatorOptions, + Root root, + StringSink sink, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) { + assert(generatorOptions.fileType == FileType.source); + _writeCppSourceClassEncode(generatorOptions.languageOptions, root, sink, + indent, klass, customClassNames, customEnumNames); + } + + @override + void writeClassDecode( + OutputFileOptions generatorOptions, + Root root, + StringSink sink, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) { + assert(generatorOptions.fileType == FileType.source); + _writeCppSourceClassDecode(generatorOptions, root, sink, indent, klass, + customClassNames, customEnumNames); + } + + @override + void writeFlutterApi( + OutputFileOptions generatorOptions, + Root root, + StringSink sink, + Indent indent, + Api api, + ) { + if (generatorOptions.fileType == FileType.header) { + _writeFlutterApiHeader(indent, api, root); + } else { + _writeFlutterApiSource(indent, api, root); + } + } + + @override + void writeHostApi( + OutputFileOptions generatorOptions, + Root root, + StringSink sink, + Indent indent, + Api api, + ) { + if (generatorOptions.fileType == FileType.header) { + _writeHostApiHeader(indent, api, root); + } else { + _writeHostApiSource(indent, api, root); + } + } -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(''' + void _writeCodec( + OutputFileOptions generatorOptions, + Root root, + StringSink sink, + Indent indent, + Api api, + ) { + if (generatorOptions.fileType == FileType.header) { + _writeCodecHeader(indent, api, root); + } else { + _writeCodecSource(indent, api, root); + } + } + + // Header methods. + + /// Writes Cpp header file header to sink. + void _writeCppHeaderPrologue(OutputFileOptions generatorOptions, + Root root, StringSink sink, Indent indent) { + if (generatorOptions.languageOptions.copyrightHeader != null) { + addLines(indent, generatorOptions.languageOptions.copyrightHeader!, + linePrefix: '// '); + } + indent.writeln('$_commentPrefix $generatedCodeWarning'); + indent.writeln('$_commentPrefix $seeAlsoWarning'); + indent.addln(''); + } + + /// Writes Cpp header file imports to sink. + void _writeCppHeaderImports(OutputFileOptions generatorOptions, + Root root, StringSink sink, Indent indent) { + final String guardName = _getGuardName( + generatorOptions.languageOptions.headerIncludePath, + generatorOptions.languageOptions.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 (generatorOptions.languageOptions.namespace != null) { + indent + .writeln('namespace ${generatorOptions.languageOptions.namespace} {'); + } + indent.addln(''); + if (generatorOptions.languageOptions.namespace?.endsWith('_pigeontest') ?? + false) { + final String testFixtureClass = + '${_pascalCaseFromSnakeCase(generatorOptions.languageOptions.namespace!.replaceAll('_pigeontest', ''))}Test'; + indent.writeln('class $testFixtureClass;'); + } + indent.addln(''); + indent.writeln('$_commentPrefix Generated class from Pigeon.'); + } + + /// Writes Cpp header enum to sink. + void _writeCppHeaderEnum(OutputFileOptions options, Root root, + StringSink sink, 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 ? '' : ','}'); + }); + }); + } + + /// Writes the declaration for the custom class [klass]. + /// + /// See [_writeCppSourceDataClass] for the corresponding declaration. + /// This is intended to be added to the header. + void _writeCppHeaderDataClass(OutputFileOptions generatorOptions, + Root root, StringSink sink, Indent indent, Class klass, + {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);'); + } + 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 (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)};'); + } + }); + }, nestCount: 0); + indent.writeln(''); + } + + 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(''' 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); + } -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) @@ -211,101 +472,279 @@ $friendLines \tstd::variant v_; }; '''); -} + } -/// 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)); + 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( - '${_getterReturnType(baseDatatype)} ${_makeGetterName(field)}() const;'); + '$_commentPrefix Sets up an instance of `${api.name}` to handle messages through the `binary_messenger`.'); 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);'); + 'static void SetUp(flutter::BinaryMessenger* binary_messenger, ${api.name}* api);'); + indent.writeln( + 'static flutter::EncodableList WrapError(std::string_view error_message);'); + indent.writeln( + 'static flutter::EncodableList WrapError(const FlutterError& error);'); + }); + indent.scoped(' protected:', '', () { + indent.writeln('${api.name}() = default;'); + }); + }, nestCount: 0); + } + + void _writeFlutterApiHeader(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 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);'); + } } - indent.addln(''); - } - }); + }); + }, nestCount: 0); + indent.writeln(''); + } + + // Source methods. + + /// Writes Cpp source file header to sink. + void _writeCppSourcePrologue(OutputFileOptions generatorOptions, + Root root, StringSink sink, Indent indent) { + if (generatorOptions.languageOptions.copyrightHeader != null) { + addLines(indent, generatorOptions.languageOptions.copyrightHeader!, + linePrefix: '// '); + } + indent.writeln('$_commentPrefix $generatedCodeWarning'); + indent.writeln('$_commentPrefix $seeAlsoWarning'); + indent.addln(''); + indent.addln('#undef _HAS_EXCEPTIONS'); + indent.addln(''); + } + + /// Writes Cpp source file imports to sink. + void _writeCppSourceImports(OutputFileOptions generatorOptions, + Root root, StringSink sink, Indent indent) { + indent.writeln( + '#include "${generatorOptions.languageOptions.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 (generatorOptions.languageOptions.namespace != null) { + indent + .writeln('namespace ${generatorOptions.languageOptions.namespace} {'); + } + } + + void _writeCppSourceClassEncode( + CppOptions generatorOptions, + Root root, + StringSink sink, + 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)'; + } - 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};'); + if (field.type.isNullable) { + encodableValue = + '$instanceVariable ? $encodableValue : flutter::EncodableValue()'; + } + + indent.writeln('$encodableValue,'); } - } - 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;'); - } + }); + }); + indent.addln(''); + } - 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)};'); - } + void _writeCppSourceClassDecode( + OutputFileOptions generatorOptions, + Root root, + StringSink sink, + 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;'); + }); + } + } + }); }); - }, nestCount: 0); - indent.writeln(''); -} + indent.addln(''); + } -/// 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(OutputFileOptions generatorOptions, + Root root, StringSink sink, 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 +772,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,539 +780,436 @@ 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)'; - } + /// Writes the implementation for the custom class [klass]. + /// + /// See [_writeCppHeaderDataClass] for the corresponding declaration. + /// This is intended to be added to the implementation file. + void _writeCppSourceDataClass(OutputFileOptions generatorOptions, + Root root, StringSink sink, 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(); - if (field.type.isNullable) { - encodableValue = - '$instanceVariable ? $encodableValue : flutter::EncodableValue()'; - } + indent.addln(''); + indent.writeln('$_commentPrefix ${klass.name}'); + indent.addln(''); - 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('{', '}', () { + // Getters and setters. + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + _writeCppSourceClassField( + generatorOptions, root, sink, indent, klass, field); + } + + // Serialization. + writeClassEncode(generatorOptions, root, sink, indent, klass, + customClassNames, customEnumNames); + + // Default constructor. + indent.writeln('${klass.name}::${klass.name}() {}'); + indent.addln(''); + + // Deserialization. + writeClassDecode(generatorOptions, root, sink, indent, klass, + customClassNames, customEnumNames); + } + + 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( - '$instanceVariableName = ${hostDatatype.datatype}(*$pointerFieldName);'); - }); - } else { - indent.write( - 'if (const ${hostDatatype.datatype}* $pointerFieldName = std::get_if<${hostDatatype.datatype}>(&$encodableFieldName)) '); - indent.scoped('{', '}', () { - indent.writeln('$instanceVariableName = *$pointerFieldName;'); + 'return flutter::CustomEncodableValue(${customClass.name}(std::get(ReadValue(stream))));'); }); } - } + indent.write('default:'); + indent.writeScoped('', '', () { + indent.writeln( + 'return $_defaultCodecSerializer::ReadValueOfType(type, stream);'); + }, addTrailingNewline: false); + }); }); - }); - 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); + 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;'); }); - 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::EncodableList WrapError(std::string_view error_message);'); - indent.writeln( - 'static flutter::EncodableList WrapError(const FlutterError& error);'); - }); - indent.scoped(' protected:', '', () { - indent.writeln('${api.name}() = default;'); + }); + indent.writeln('$_defaultCodecSerializer::WriteValue(value, stream);'); }); - }, nestCount: 0); -} + } -void _writeHostApiSource(Indent indent, Api api, Root root) { - assert(api.location == ApiLocation.host); + 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(''' + 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.writeln('flutter::EncodableList wrapped;'); - 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);'); + 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.writeln('flutter::EncodableList wrapped;'); + 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 { - indent.writeln( - 'const auto& $argName = std::any_cast(std::get($encodableArgName));'); + // 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(flutter::EncodableValue(WrapError("$argName unexpectedly null.")));'); - indent.writeln('return;'); - }); - } - extractEncodedArgument( - argName, encodableArgName, arg, hostType); - methodArgument.add(argName); - }); - } + 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(flutter::EncodableValue(WrapError("$argName unexpectedly null.")));'); + indent.writeln('return;'); + }); + } + extractEncodedArgument( + argName, encodableArgName, arg, hostType); + methodArgument.add(argName); + }); + } - String wrapResponse(String reply, TypeDeclaration returnType) { - String elseBody = ''; - final String ifCondition; - final String errorGetter; - final String prefix = (reply != '') ? '\t' : ''; - - const String nullValue = 'flutter::EncodableValue()'; - if (returnType.isVoid) { - elseBody = - '$prefix\twrapped.push_back($nullValue);${indent.newline}'; - ifCondition = 'output.has_value()'; - errorGetter = 'value'; + final HostDatatype returnType = getHostDatatype( + method.returnType, + root.classes, + root.enums, + (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); + final String returnTypeName = _apiReturnType(returnType); + if (method.isAsynchronous) { + methodArgument.add( + '[&wrapped, &reply]($returnTypeName&& output) {${indent.newline}' + '${_wrapResponse(indent, root, '\treply(flutter::EncodableValue(std::move(wrapped)));${indent.newline}', method.returnType)}' + '}', + ); + } + final String call = + 'api->${_makeMethodName(method)}(${methodArgument.join(', ')})'; + if (method.isAsynchronous) { + indent.format('$call;'); } 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. - elseBody = ''' -$prefix\tauto output_optional = $extractedValue; -$prefix\tif (output_optional) { -$prefix\t\twrapped.push_back($wrapperType(std::move(output_optional).value())); -$prefix\t} else { -$prefix\t\twrapped.push_back($nullValue); -$prefix\t}${indent.newline}'''; - } else { - elseBody = - '$prefix\twrapped.push_back($wrapperType($extractedValue));${indent.newline}'; - } - ifCondition = 'output.has_error()'; - errorGetter = 'error'; + indent.writeln('$returnTypeName output = $call;'); + indent.format( + _wrapResponse(indent, root, '', method.returnType)); } - return '${prefix}if ($ifCondition) {${indent.newline}' - '$prefix\twrapped = WrapError(output.$errorGetter());${indent.newline}' - '$prefix} else {${indent.newline}' - '$elseBody' - '$prefix}' - '$prefix$reply'; - } - - final HostDatatype returnType = getHostDatatype( - method.returnType, - root.classes, - root.enums, - (TypeDeclaration x) => _baseCppTypeForBuiltinDartType(x)); - final String returnTypeName = _apiReturnType(returnType); - if (method.isAsynchronous) { - methodArgument.add( - '[&wrapped, &reply]($returnTypeName&& output) {${indent.newline}' - '${wrapResponse('\treply(flutter::EncodableValue(std::move(wrapped)));${indent.newline}', method.returnType)}' - '}', - ); - } - 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('{', '}', () { - indent.writeln('wrapped = WrapError(exception.what());'); - if (method.isAsynchronous) { + }); + indent.write('catch (const std::exception& exception) '); + indent.scoped('{', '}', () { + indent.writeln('wrapped = WrapError(exception.what());'); + if (method.isAsynchronous) { + indent.writeln( + 'reply(flutter::EncodableValue(std::move(wrapped)));'); + } + }); + if (!method.isAsynchronous) { indent.writeln( 'reply(flutter::EncodableValue(std::move(wrapped)));'); } }); - if (!method.isAsynchronous) { - indent.writeln( - 'reply(flutter::EncodableValue(std::move(wrapped)));'); - } + }); + indent.scoped(null, '}', () { + indent.writeln('channel->SetMessageHandler(nullptr);'); }); }); - indent.scoped(null, '}', () { - indent.writeln('channel->SetMessageHandler(nullptr);'); - }); - }); - } - }); -} - -String _getArgumentName(int count, NamedType argument) => - argument.name.isEmpty ? 'arg$count' : _makeVariableName(argument); - -/// 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'; - -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); + + indent.addln(''); + indent.format(''' +flutter::EncodableList ${api.name}::WrapError(std::string_view error_message) { +\treturn flutter::EncodableList({ +\t\tflutter::EncodableValue(std::string(error_message)), +\t\tflutter::EncodableValue("Error"), +\t\tflutter::EncodableValue() +\t}); } +flutter::EncodableList ${api.name}::WrapError(const FlutterError& error) { +\treturn flutter::EncodableList({ +\t\tflutter::EncodableValue(error.message()), +\t\tflutter::EncodableValue(error.code()), +\t\terror.details() +\t}); +}'''); + indent.addln(''); + } -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(''' + String _wrapResponse( + Indent indent, Root root, String reply, TypeDeclaration returnType) { + String elseBody = ''; + final String ifCondition; + final String errorGetter; + final String prefix = (reply != '') ? '\t' : ''; + + const String nullValue = 'flutter::EncodableValue()'; + if (returnType.isVoid) { + elseBody = '$prefix\twrapped.push_back($nullValue);${indent.newline}'; + ifCondition = '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. + elseBody = ''' +$prefix\tauto output_optional = $extractedValue; +$prefix\tif (output_optional) { +$prefix\t\twrapped.push_back($wrapperType(std::move(output_optional).value())); +$prefix\t} else { +$prefix\t\twrapped.push_back($nullValue); +$prefix\t}${indent.newline}'''; + } else { + elseBody = + '$prefix\twrapped.push_back($wrapperType($extractedValue));${indent.newline}'; + } + ifCondition = 'output.has_error()'; + errorGetter = 'error'; + } + return '${prefix}if ($ifCondition) {${indent.newline}' + '$prefix\twrapped = WrapError(output.$errorGetter());${indent.newline}' + '$prefix} else {${indent.newline}' + '$elseBody' + '$prefix}' + '$prefix$reply'; + } + + 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{};'); + 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('$returnTypeName $output{};'); - } - const String pointerVariable = '${_pointerPrefix}_$output'; - if (func.returnType.baseName == 'int') { - indent.format(''' + 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;'); - }); - } + } 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);'); - } + indent.writeln('callback($output);'); + } + }); }); - }); + } } } +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); + +/// 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'; + /// Returns a non-nullable variant of [type]. HostDatatype _nonNullableType(HostDatatype type) { return HostDatatype( @@ -1049,169 +1385,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::EncodableList ${api.name}::WrapError(std::string_view error_message) { -\treturn flutter::EncodableList({ -\t\tflutter::EncodableValue(std::string(error_message)), -\t\tflutter::EncodableValue("Error"), -\t\tflutter::EncodableValue() -\t}); -} -flutter::EncodableList ${api.name}::WrapError(const FlutterError& error) { -\treturn 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..9de09227aa2 100644 --- a/packages/pigeon/lib/dart_generator.dart +++ b/packages/pigeon/lib/dart_generator.dart @@ -77,23 +77,580 @@ class DartGenerator extends Generator { /// 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 generate(DartOptions generatorOptions, Root root, StringSink sink) { + final Indent indent = Indent(sink); + + writeFilePrologue(generatorOptions, root, sink, indent); + writeFileImports(generatorOptions, root, sink, indent); + for (final Enum anEnum in root.enums) { + writeEnum(generatorOptions, root, sink, indent, anEnum); + } + for (final Class klass in root.classes) { + indent.writeln(''); + writeDataClass(generatorOptions, root, sink, indent, klass); + } + + for (final Api api in root.apis) { + indent.writeln(''); + if (api.location == ApiLocation.host) { + writeHostApi(generatorOptions, root, sink, indent, api); + } else if (api.location == ApiLocation.flutter) { + writeFlutterApi(generatorOptions, root, sink, indent, api); + } + } + } + + @override + void writeFilePrologue( + DartOptions generatorOptions, Root root, StringSink sink, 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, StringSink sink, 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, StringSink sink, + 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, StringSink sink, + 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(); + 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},'); + } + }); + } + + 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(''); + } + writeClassEncode(generatorOptions, root, sink, indent, klass, + customClassNames, customEnumNames); + indent.writeln(''); + writeClassDecode(generatorOptions, root, sink, indent, klass, + customClassNames, customEnumNames); + }); } - /// 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, + @override + void writeClassEncode( + DartOptions generatorOptions, + Root root, + StringSink sink, + 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, + StringSink sink, + 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, + StringSink sink, + 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, StringSink sink, + 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, sink, indent); + _writeTestImports(generatorOptions, root, sink, 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, + sink, + indent, + mockApi, + channelNameFunc: (Method func) => makeChannelName(api, func), + isMockHandler: true, + ); + } + } + } + + /// Writes file header to sink. + void _writeTestPrologue( + DartOptions opt, Root root, StringSink sink, 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'); + } + + /// Writes file imports to sink. + void _writeTestImports( + DartOptions opt, Root root, StringSink sink, 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 +752,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) { @@ -503,193 +787,7 @@ String _addGenericTypesNullable(TypeDeclaration type) { /// 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); - } -} +void generateDart(DartOptions opt, Root root, StringSink sink, Indent indent) {} /// Crawls up the path of [dartFilePath] until it finds a pubspec.yaml in a /// parent directory and returns its path. @@ -739,72 +837,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..981fcc8dfbe 100644 --- a/packages/pigeon/lib/generator.dart +++ b/packages/pigeon/lib/generator.dart @@ -3,11 +3,98 @@ // found in the LICENSE file. import 'ast.dart'; +import 'generator_tools.dart'; /// A superclass of generator classes. /// /// 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); + /// Generates files for specified language with specified [generatorOptions] + /// + /// This method, when overridden, should follow a generic structure that is currently: + /// 1. Create Indent + /// 2. Write File Headers + /// 3. Write Imports + /// 4. Write Enums + /// 5. Write Data Classes + /// 6. Write Apis + void generate( + T generatorOptions, + Root root, + StringSink sink, + ); + + /// Adds specified headers to file. + void writeFilePrologue( + T generatorOptions, + Root root, + StringSink sink, + Indent indent, + ); + + /// Adds specified imports to file. + void writeFileImports( + T generatorOptions, + Root root, + StringSink sink, + Indent indent, + ); + + /// Writes a single Enum to file. + void writeEnum( + T generatorOptions, + Root root, + StringSink sink, + Indent indent, + Enum anEnum, + ); + + /// Writes a single data class to file. + void writeDataClass( + T generatorOptions, + Root root, + StringSink sink, + Indent indent, + Class klass, + ); + + /// Writes a single class encode method to file. + void writeClassEncode( + T generatorOptions, + Root root, + StringSink sink, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ); + + /// Writes a single class decode method to file. + void writeClassDecode( + T generatorOptions, + Root root, + StringSink sink, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ); + + /// Writes a single Flutter Api to file. + void writeFlutterApi( + T generatorOptions, + Root root, + StringSink sink, + Indent indent, + Api api, + ); + + /// Writes a single Host Api to file. + void writeHostApi( + T generatorOptions, + Root root, + StringSink sink, + Indent indent, + Api api, + ); } diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index b1ee06b01e8..07eac70ad9b 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.0'; +const String pigeonVersion = '5.0.5'; /// 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..acbc901ab86 100644 --- a/packages/pigeon/lib/java_generator.dart +++ b/packages/pigeon/lib/java_generator.dart @@ -90,90 +90,448 @@ class JavaGenerator extends Generator { /// Instantiates a Java Generator. JavaGenerator(); - /// Generates Java files with specified [JavaOptions] + /// Generates the ".java" file for the AST represented by [root] to [sink] with the + /// provided [generatorOptions]. @override - void generate(JavaOptions languageOptions, Root root, StringSink sink, - {FileType fileType = FileType.na}) { - assert(fileType == FileType.na); - generateJava(languageOptions, root, sink); + void generate(JavaOptions generatorOptions, Root root, StringSink sink) { + final Indent indent = Indent(sink); + + writeFilePrologue(generatorOptions, root, sink, indent); + writeFileImports(generatorOptions, root, sink, 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.write('public class ${generatorOptions.className!} '); + indent.scoped('{', '}', () { + for (final Enum anEnum in root.enums) { + indent.writeln(''); + writeEnum(generatorOptions, root, sink, indent, anEnum); + } + for (final Class klass in root.classes) { + indent.addln(''); + writeDataClass(generatorOptions, root, sink, indent, klass); + } + if (root.apis.any((Api api) => + api.location == ApiLocation.host && + api.methods.any((Method it) => it.isAsynchronous))) { + indent.addln(''); + _writeResultInterface(indent); + } + for (final Api api in root.apis) { + if (getCodecClasses(api, root).isNotEmpty) { + _writeCodec(indent, api, root); + indent.addln(''); + } + if (api.location == ApiLocation.host) { + writeHostApi(generatorOptions, root, sink, indent, api); + } else if (api.location == ApiLocation.flutter) { + writeFlutterApi(generatorOptions, root, sink, indent, api); + } + } + _writeWrapError(indent); + }); } -} -/// Calculates the name of the codec that will be generated for [api]. -String _getCodecName(Api api) => '${api.name}Codec'; + @override + void writeFilePrologue( + JavaOptions generatorOptions, Root root, StringSink sink, Indent indent) { + if (generatorOptions.copyrightHeader != null) { + addLines(indent, generatorOptions.copyrightHeader!, linePrefix: '// '); + } + indent.writeln('// $generatedCodeWarning'); + indent.writeln('// $seeAlsoWarning'); + 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 writeFileImports( + JavaOptions generatorOptions, Root root, StringSink sink, 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(''); + } -/// 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, StringSink sink, + 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(); + } + + 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, StringSink sink, + 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.' + ]; + 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, sink, 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, sink, indent, klass); + writeClassEncode(generatorOptions, root, sink, indent, klass, + customClassNames, customEnumNames); + writeClassDecode(generatorOptions, root, sink, indent, klass, + customClassNames, customEnumNames); + }); + } + + void _writeClassField(JavaOptions generatorOptions, Root root, + StringSink sink, 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, + StringSink sink, + 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;'); }); }); - }); -} + } + + @override + void writeClassEncode( + JavaOptions generatorOptions, + Root root, + StringSink sink, + 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, + StringSink sink, + 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, + StringSink sink, + 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 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);'); + } + }); + }); + } + }); + } + + /// 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, StringSink sink, + Indent indent, Api api) { + assert(api.location == ApiLocation.host); -/// 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); + 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, sink, 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();'); + } + }); - bool isEnum(TypeDeclaration type) => - root.enums.map((Enum e) => e.name).contains(type.baseName); + 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, sink, 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, + StringSink sink, Indent indent, Api api, final Method method) { final String returnType = method.isAsynchronous ? 'void' : _nullsafeJavaTypeForDartType(method.returnType); @@ -203,7 +561,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, + StringSink sink, Indent indent, Api api, final Method method) { final String channelName = makeChannelName(api, method); indent.write(''); indent.scoped('{', '}', () { @@ -252,7 +611,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 +678,85 @@ 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.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; +}'''); + } } +/// 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 +764,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 +841,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..c0b5ae19a60 100644 --- a/packages/pigeon/lib/kotlin_generator.dart +++ b/packages/pigeon/lib/kotlin_generator.dart @@ -70,305 +70,591 @@ class KotlinGenerator extends Generator { /// Instantiates a Kotlin Generator. KotlinGenerator(); - /// Generates Kotlin files with specified [KotlinOptions] + /// Generates the ".kotlin" file for the AST represented by [root] to [sink] with the + /// provided [KotlinOptions]. @override - void generate(KotlinOptions languageOptions, Root root, StringSink sink, - {FileType fileType = FileType.na}) { - assert(fileType == FileType.na); - generateKotlin(languageOptions, root, sink); + void generate(KotlinOptions generatorOptions, Root root, StringSink sink) { + final Indent indent = Indent(sink); + + writeFilePrologue(generatorOptions, root, sink, indent); + writeFileImports(generatorOptions, root, sink, indent); + indent.writeln('/** Generated class from Pigeon. */'); + for (final Enum anEnum in root.enums) { + indent.writeln(''); + writeEnum(generatorOptions, root, sink, indent, anEnum); + } + for (final Class klass in root.classes) { + indent.addln(''); + writeDataClass(generatorOptions, root, sink, indent, 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(''); + } + if (api.location == ApiLocation.host) { + writeHostApi(generatorOptions, root, sink, indent, api); + } else if (api.location == ApiLocation.flutter) { + writeFlutterApi(generatorOptions, root, sink, indent, api); + } + } + + indent.addln(''); + _writeWrapResult(indent); + indent.addln(''); + _writeWrapError(indent); } -} -/// Calculates the name of the codec that will be generated for [api]. -String _getCodecName(Api api) => '${api.name}Codec'; + @override + void writeFilePrologue(KotlinOptions generatorOptions, Root root, + StringSink sink, Indent indent) { + if (generatorOptions.copyrightHeader != null) { + addLines(indent, generatorOptions.copyrightHeader!, linePrefix: '// '); + } + indent.writeln('// $generatedCodeWarning'); + indent.writeln('// $seeAlsoWarning'); + 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('@Suppress("UNCHECKED_CAST")'); - indent.write('private object $codecName : StandardMessageCodec() '); - indent.scoped('{', '}', () { - indent.write( - 'override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? '); + @override + void writeFileImports(KotlinOptions generatorOptions, Root root, + StringSink sink, Indent indent) { + 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'); + indent.addln(''); + } + + @override + void writeEnum(KotlinOptions generatorOptions, Root root, StringSink sink, + Indent indent, Enum anEnum) { + 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, + StringSink sink, 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.' + ]; + 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, sink, indent, klass, + customClassNames, customEnumNames); + writeClassEncode(generatorOptions, root, sink, indent, klass, + customClassNames, customEnumNames); + }); + } + + @override + void writeClassEncode( + KotlinOptions generatorOptions, + Root root, + StringSink sink, + 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, + StringSink sink, + 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.addln(')'); + 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.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'; + /// 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, + StringSink sink, + Indent indent, + Api api, + ) { + assert(api.location == ApiLocation.flutter); + final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; -/// 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 '); + 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, + StringSink sink, + Indent indent, + Api api, + ) { + 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); - 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) '); + 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)'); + }); + }); + }); + } + + void _writeWrapResult(Indent indent) { + indent.write('private fun wrapResult(result: Any?): List '); + indent.scoped('{', '}', () { + indent.writeln('return listOf(result)'); + }); + } + + void _writeWrapError(Indent indent) { + 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)'); + }); + }); + } +} + +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 +734,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..e09e3dbe2e8 100644 --- a/packages/pigeon/lib/objc_generator.dart +++ b/packages/pigeon/lib/objc_generator.dart @@ -69,19 +69,686 @@ class ObjcGenerator extends Generator> { /// Instantiates a Objc Generator. ObjcGenerator(); - /// Generates Objc files with specified [OutputFileOptions] + /// Generates Objc header files with specified [ObjcOptions] @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); + final Indent indent = Indent(sink); - if (fileType == FileType.header) { - generateObjcHeader(languageOptions.languageOptions, root, sink); + writeFilePrologue(generatorOptions, root, sink, indent); + writeFileImports(generatorOptions, root, sink, indent); + if (generatorOptions.fileType == FileType.header) { + indent.writeln('NS_ASSUME_NONNULL_BEGIN'); + + for (final Enum anEnum in root.enums) { + indent.writeln(''); + writeEnum(generatorOptions, root, sink, indent, anEnum); + } + indent.writeln(''); + for (final Class klass in root.classes) { + indent.writeln( + '@class ${_className(generatorOptions.languageOptions.prefix, klass.name)};'); + } + indent.addln(''); + } else if (generatorOptions.fileType == FileType.source) { + _writeObjcSourceHelperFunctions(indent); + indent.addln(''); + + for (final Class klass in root.classes) { + _writeObjcSourceDataClassExtension( + generatorOptions.languageOptions, indent, klass); + } + indent.writeln(''); + } + + for (final Class klass in root.classes) { + writeDataClass(generatorOptions, root, sink, indent, klass); + } + + indent.writeln(''); + + for (final Api api in root.apis) { + if (generatorOptions.fileType == FileType.source) { + final String codecName = + _getCodecName(generatorOptions.languageOptions.prefix, api.name); + if (getCodecClasses(api, root).isNotEmpty) { + _writeCodec( + indent, codecName, generatorOptions.languageOptions, api, root); + indent.addln(''); + } + _writeCodecGetter( + indent, codecName, generatorOptions.languageOptions, api, root); + indent.addln(''); + } + if (api.location == ApiLocation.host) { + writeHostApi(generatorOptions, root, sink, indent, api); + } else if (api.location == ApiLocation.flutter) { + writeFlutterApi(generatorOptions, root, sink, indent, api); + } + } + if (generatorOptions.fileType == FileType.header) { + indent.writeln('NS_ASSUME_NONNULL_END'); + } + } + + @override + void writeFilePrologue(OutputFileOptions generatorOptions, + Root root, StringSink sink, Indent indent) { + if (generatorOptions.fileType == FileType.header) { + _writeObjcHeaderPrologue( + generatorOptions.languageOptions, root, sink, indent); + } else { + _writeObjcSourcePrologue( + generatorOptions.languageOptions, root, sink, indent); + } + } + + @override + void writeFileImports(OutputFileOptions generatorOptions, + Root root, StringSink sink, Indent indent) { + if (generatorOptions.fileType == FileType.header) { + _writeObjcHeaderImports( + generatorOptions.languageOptions, root, sink, indent); + } else { + _writeObjcSourceImports( + generatorOptions.languageOptions, root, sink, indent); + } + } + + @override + void writeEnum(OutputFileOptions generatorOptions, Root root, + StringSink sink, Indent indent, Enum anEnum) { + if (generatorOptions.fileType == FileType.header) { + _writeObjcHeaderEnum( + generatorOptions.languageOptions, root, sink, indent, anEnum); + } + } + + @override + void writeDataClass(OutputFileOptions generatorOptions, + Root root, StringSink sink, Indent indent, Class klass) { + if (generatorOptions.fileType == FileType.header) { + _writeObjcHeaderDataClass( + generatorOptions.languageOptions, root, sink, indent, klass); + } else { + _writeObjcSourceDataClassImplementation( + generatorOptions, root, sink, indent, klass); + indent.writeln(''); + } + } + + @override + void writeClassEncode( + OutputFileOptions generatorOptions, + Root root, + StringSink sink, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) { + if (generatorOptions.fileType == FileType.source) { + final String className = + _className(generatorOptions.languageOptions.prefix, klass.name); + + _writeObjcSourceClassEncode(generatorOptions.languageOptions, root, sink, + indent, klass, customClassNames, customEnumNames, className); + } + } + + @override + void writeClassDecode( + OutputFileOptions generatorOptions, + Root root, + StringSink sink, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + ) { + if (generatorOptions.fileType == FileType.source) { + final String className = + _className(generatorOptions.languageOptions.prefix, klass.name); + + _writeObjcSourceClassDecode(generatorOptions.languageOptions, root, sink, + indent, klass, customClassNames, customEnumNames, className); + } + } + + @override + void writeFlutterApi( + OutputFileOptions generatorOptions, + Root root, + StringSink sink, + Indent indent, + Api api, + ) { + if (generatorOptions.fileType == FileType.header) { + _writeFlutterApiDeclaration( + indent, api, generatorOptions.languageOptions, root); + } else { + _writeFlutterApiSource( + indent, generatorOptions.languageOptions, api, root); + } + } + + @override + void writeHostApi( + OutputFileOptions generatorOptions, + Root root, + StringSink sink, + Indent indent, + Api api, + ) { + if (generatorOptions.fileType == FileType.header) { + _writeHostApiDeclaration( + indent, api, generatorOptions.languageOptions, root); } else { - generateObjcSource(languageOptions.languageOptions, root, sink); + _writeHostApiSource(indent, generatorOptions.languageOptions, api, root); } } + + // Header File Methods. + + /// Writes Objc header file header to sink. + void _writeObjcHeaderPrologue( + ObjcOptions languageOptions, Root root, StringSink sink, Indent indent) { + if (languageOptions.copyrightHeader != null) { + addLines(indent, languageOptions.copyrightHeader!, linePrefix: '// '); + } + indent.writeln('// $generatedCodeWarning'); + indent.writeln('// $seeAlsoWarning'); + indent.addln(''); + } + + /// Writes Objc header file imports to sink. + void _writeObjcHeaderImports( + ObjcOptions languageOptions, Root root, StringSink sink, Indent indent) { + indent.writeln('#import '); + indent.addln(''); + + indent.writeln('@protocol FlutterBinaryMessenger;'); + indent.writeln('@protocol FlutterMessageCodec;'); + indent.writeln('@class FlutterError;'); + indent.writeln('@class FlutterStandardTypedData;'); + indent.addln(''); + } + + /// Writes single Objc header enum. + void _writeObjcHeaderEnum(ObjcOptions languageOptions, Root root, + StringSink sink, Indent indent, Enum anEnum) { + final String enumName = _className(languageOptions.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,'); + }); + }); + } + + /// Writes the class declaration for a data class. + /// + /// Example: + /// @interface Foo : NSObject + /// @property (nonatomic, copy) NSString *bar; + /// @end + void _writeObjcHeaderDataClass(ObjcOptions generatorOptions, Root root, + StringSink sink, 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(); + + addDocumentationComments( + indent, klass.documentationComments, _docCommentSpec); + + indent.writeln('@interface ${_className(prefix, klass.name)} : NSObject'); + if (getFieldsInSerializationOrder(klass).isNotEmpty) { + if (getFieldsInSerializationOrder(klass) + .map((NamedType e) => !e.type.isNullable) + .any((bool e) => e)) { + indent.writeln( + '$_docCommentPrefix `init` unavailable to enforce nonnull fields, see the `make` class method.'); + indent.writeln('- (instancetype)init NS_UNAVAILABLE;'); + } + _writeObjcSourceClassInitializerDeclaration( + indent, klass, classes, enums, prefix); + indent.addln(';'); + } + for (final NamedType field in getFieldsInSerializationOrder(klass)) { + 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)} *'); + late final String propertyType; + addDocumentationComments( + indent, field.documentationComments, _docCommentSpec); + if (customEnumNames.contains(field.type.baseName)) { + propertyType = 'assign'; + } else { + propertyType = _propertyTypeForDartType(field); + } + final String nullability = + _isNullable(hostDatatype, field.type) ? ', nullable' : ''; + indent.writeln( + '@property(nonatomic, $propertyType$nullability) ${hostDatatype.datatype} ${field.name};'); + } + indent.writeln('@end'); + indent.writeln(''); + } + + /// 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) { + indent.writeln( + '$_docCommentPrefix The codec used by ${_className(options.prefix, api.name)}.'); + indent.writeln( + 'NSObject *${_getCodecGetterName(options.prefix, api.name)}(void);'); + indent.addln(''); + 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) { + indent.writeln( + '$_docCommentPrefix The codec used by ${_className(options.prefix, api.name)}.'); + indent.writeln( + 'NSObject *${_getCodecGetterName(options.prefix, api.name)}(void);'); + indent.addln(''); + 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('@end'); + } + + // Source File Methods. + + /// Writes Objc Source file header to sink. + void _writeObjcSourcePrologue( + ObjcOptions languageOptions, Root root, StringSink sink, Indent indent) { + if (languageOptions.copyrightHeader != null) { + addLines(indent, languageOptions.copyrightHeader!, linePrefix: '// '); + } + indent.writeln('// $generatedCodeWarning'); + indent.writeln('// $seeAlsoWarning'); + indent.addln(''); + } + + /// Writes Objc source file imports to sink. + void _writeObjcSourceImports( + ObjcOptions languageOptions, Root root, StringSink sink, Indent indent) { + indent.writeln('#import "${languageOptions.headerIncludePath}"'); + indent.writeln('#import '); + indent.addln(''); + + indent.writeln('#if !__has_feature(objc_arc)'); + indent.writeln('#error File requires ARC to be enabled.'); + indent.writeln('#endif'); + indent.addln(''); + } + + void _writeObjcSourceHelperFunctions(Indent indent) { + indent.format(''' +static NSArray *wrapResult(id result, FlutterError *error) { +\tif (error) { +\t\treturn @[ error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null] ]; +\t} +\treturn @[ result ?: [NSNull null] ]; +}'''); + indent.format(''' +static id GetNullableObject(NSDictionary* dict, id key) { +\tid result = dict[key]; +\treturn (result == [NSNull null]) ? nil : result; +} +static id GetNullableObjectAtIndex(NSArray* array, NSInteger key) { +\tid result = array[key]; +\treturn (result == [NSNull null]) ? nil : result; +} +'''); + } + + 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'); + } + + void _writeObjcSourceDataClassImplementation( + OutputFileOptions generatorOptions, + Root root, + StringSink sink, + 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.languageOptions.prefix, klass.name); + + indent.writeln('@implementation $className'); + _writeObjcSourceClassInitializer(generatorOptions.languageOptions, root, + sink, indent, klass, customClassNames, customEnumNames, className); + writeClassDecode(generatorOptions, root, sink, indent, klass, + customClassNames, customEnumNames); + writeClassEncode(generatorOptions, root, sink, indent, klass, + customClassNames, customEnumNames); + indent.writeln('@end'); + } + + void _writeObjcSourceClassInitializer( + ObjcOptions languageOptions, + Root root, + StringSink sink, + 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 _writeObjcSourceClassDecode( + ObjcOptions languageOptions, + Root root, + StringSink sink, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + String className, + ) { + 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, languageOptions.prefix)} integerValue];'); + } else { + indent.writeln( + '$resultName.${field.name} = ${_listGetter(customClassNames, 'list', field, index, languageOptions.prefix)};'); + if (!field.type.isNullable) { + indent.writeln('NSAssert($resultName.${field.name} != nil, @"");'); + } + } + }); + indent.writeln('return $resultName;'); + }); + + indent.writeln( + '+ (nullable $className *)nullableFromList:(NSArray *)list { return (list) ? [$className fromList:list] : nil; }'); + } + + void _writeObjcSourceClassEncode( + ObjcOptions languageOptions, + Root root, + StringSink sink, + Indent indent, + Class klass, + Set customClassNames, + Set customEnumNames, + String className, + ) { + indent.write('- (NSArray *)toList '); + indent.scoped('{', '}', () { + indent.write('return'); + indent.scoped(' @[', '];', () { + for (final NamedType field in klass.fields) { + indent.writeln( + '${_arrayValue(customClassNames, customEnumNames, field)},'); + } + }); + }); + } + + /// 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}'); + } + }); + } + + /// 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]];'); + }); + } + 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]];'); + }); + } + 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 +'''); + } + + void _writeCodecGetter( + Indent indent, String name, ObjcOptions options, Api api, Root root) { + final String readerWriterName = '${name}ReaderWriter'; + + 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;'); + }); + } } /// Calculates the ObjC class name, possibly prefixed. @@ -163,108 +830,23 @@ String _propertyTypeForDartType(NamedType field) { '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; - -/// 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}'); - } - }); -} - -/// 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); - - indent.writeln('@interface ${_className(prefix, klass.name)} : NSObject'); - if (getFieldsInSerializationOrder(klass).isNotEmpty) { - if (getFieldsInSerializationOrder(klass) - .map((NamedType e) => !e.type.isNullable) - .any((bool e) => e)) { - indent.writeln( - '$_docCommentPrefix `init` unavailable to enforce nonnull fields, see the `make` class method.'); - indent.writeln('- (instancetype)init NS_UNAVAILABLE;'); - } - _writeInitializerDeclaration(indent, klass, classes, enums, prefix); - indent.addln(';'); - } - for (final NamedType field in getFieldsInSerializationOrder(klass)) { - 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)} *'); - late final String propertyType; - addDocumentationComments( - indent, field.documentationComments, _docCommentSpec); - if (enumNames.contains(field.type.baseName)) { - propertyType = 'assign'; - } else { - propertyType = _propertyTypeForDartType(field); - } - final String nullability = - _isNullable(hostDatatype, field.type) ? ', nullable' : ''; - indent.writeln( - '@property(nonatomic, $propertyType$nullability) ${hostDatatype.datatype} ${field.name};'); - } - indent.writeln('@end'); - indent.writeln(''); + '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'; @@ -274,104 +856,6 @@ String _getCodecName(String? prefix, String className) => String _getCodecGetterName(String? prefix, String className) => '${_className(prefix, className)}GetCodec'; -/// 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]];'); - }); - } - 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]];'); - }); - } - 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 -'''); -} - -void _writeCodecGetter( - Indent indent, String name, ObjcOptions options, Api api, Root root) { - final String readerWriterName = '${name}ReaderWriter'; - - 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;'); - }); -} - String _capitalize(String str) => (str.isEmpty) ? '' : str[0].toUpperCase() + str.substring(1); @@ -451,187 +935,14 @@ String _makeObjcSignature({ 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('@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); - - void writeHeader() { - if (options.copyrightHeader != null) { - addLines(indent, options.copyrightHeader!, linePrefix: '// '); - } - indent.writeln('// $generatedCodeWarning'); - indent.writeln('// $seeAlsoWarning'); - } - - void writeImports() { - indent.writeln('#import '); - } - - void writeForwardDeclarations() { - indent.writeln('@protocol FlutterBinaryMessenger;'); - indent.writeln('@protocol FlutterMessageCodec;'); - indent.writeln('@class FlutterError;'); - indent.writeln('@class FlutterStandardTypedData;'); - } - - 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,'); - }); - }); - } - - 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); +void generateObjcHeader( + ObjcOptions options, Root root, StringSink sink, Indent indent) {} - 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); - } - } - - indent.writeln('NS_ASSUME_NONNULL_END'); -} - -String _listGetter(List classNames, String list, NamedType field, +String _listGetter(Set customClassNames, String list, NamedType field, int index, String? prefix) { - if (classNames.contains(field.type.baseName)) { + if (customClassNames.contains(field.type.baseName)) { String className = field.type.baseName; if (prefix != null) { className = '$prefix$className'; @@ -642,11 +953,11 @@ String _listGetter(List classNames, String list, NamedType field, } } -String _arrayValue( - List classNames, List enumNames, NamedType field) { - if (classNames.contains(field.type.baseName)) { +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 (enumNames.contains(field.type.baseName)) { + } else if (customEnumNames.contains(field.type.baseName)) { return '@(self.${field.name})'; } else { return '(self.${field.name} ?: [NSNull null])'; @@ -815,231 +1126,87 @@ void _writeHostApiSource( /// Writes the definition code for a flutter [Api]. /// See also: [_writeFlutterApiDeclaration] void _writeFlutterApiSource( - Indent indent, ObjcOptions options, Api api, Root root) { + Indent indent, ObjcOptions languageOptions, 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'); - } - - 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);'); - } - }); - }); - } + final String apiName = _className(languageOptions.prefix, api.name); - writeExtension(); + _writeExtension(indent, apiName); indent.addln(''); indent.writeln('@implementation $apiName'); indent.addln(''); - writeInitializer(); - api.methods.forEach(writeMethod); + _writeInitializer(indent); + for (final Method func in api.methods) { + _writeMethod(languageOptions, root, indent, api, func); + } 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() { - indent.format(''' -static NSArray *wrapResult(id result, FlutterError *error) { -\tif (error) { -\t\treturn @[ error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null] ]; -\t} -\treturn @[ result ?: [NSNull null] ]; -}'''); - indent.format(''' -static id GetNullableObject(NSDictionary* dict, id key) { -\tid result = dict[key]; -\treturn (result == [NSNull null]) ? nil : result; -} -static id GetNullableObjectAtIndex(NSArray* array, NSInteger key) { -\tid result = array[key]; -\treturn (result == [NSNull null]) ? nil : result; +void _writeExtension(Indent indent, String apiName) { + indent.writeln('@interface $apiName ()'); + indent.writeln( + '@property (nonatomic, strong) NSObject *binaryMessenger;'); + indent.writeln('@end'); } -'''); - } - - void writeDataClassExtension(Class klass) { - final String className = _className(options.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'); - } - - 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 writeFromList() { - 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 (enumNames.contains(field.type.baseName)) { - indent.writeln( - '$resultName.${field.name} = [${_listGetter(classNames, 'list', field, index, options.prefix)} integerValue];'); - } else { - indent.writeln( - '$resultName.${field.name} = ${_listGetter(classNames, 'list', field, index, options.prefix)};'); - if (!field.type.isNullable) { - indent - .writeln('NSAssert($resultName.${field.name} != nil, @"");'); - } - } - }); - indent.writeln('return $resultName;'); - }); - - indent.writeln( - '+ (nullable $className *)nullableFromList:(NSArray *)list { return (list) ? [$className fromList:list] : nil; }'); - } - - 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.writeln('@implementation $className'); - writeInitializer(); - writeFromList(); - writeToList(); - indent.writeln('@end'); - } - 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(''); - } - _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); - } - } +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;'); + }); +} - 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(''); +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(', ')}]'; } - root.apis.forEach(writeApi); + 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..40f3fee3048 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -456,11 +456,7 @@ class DartTestGeneratorAdapter implements GeneratorAdapter { testOutPath: options.dartTestOut, ); final DartGenerator testGenerator = DartGenerator(); - testGenerator.generateTest( - dartOptionsWithHeader, - root, - sink, - ); + testGenerator.generateTest(dartOptionsWithHeader, root, sink); } @override diff --git a/packages/pigeon/lib/swift_generator.dart b/packages/pigeon/lib/swift_generator.dart index 77fff7e2fac..15c5b51250a 100644 --- a/packages/pigeon/lib/swift_generator.dart +++ b/packages/pigeon/lib/swift_generator.dart @@ -55,224 +55,568 @@ class SwiftGenerator extends Generator { /// 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 generate(SwiftOptions generatorOptions, Root root, StringSink sink) { + final Indent indent = Indent(sink); + writeFilePrologue(generatorOptions, root, sink, indent); + writeFileImports(generatorOptions, root, sink, indent); + indent.writeln('$_docCommentPrefix Generated class from Pigeon.'); + for (final Enum anEnum in root.enums) { + indent.writeln(''); + writeEnum(generatorOptions, root, sink, indent, anEnum); + } + for (final Class klass in root.classes) { + indent.addln(''); + writeDataClass(generatorOptions, root, sink, indent, 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(''); + } + if (api.location == ApiLocation.host) { + writeHostApi(generatorOptions, root, sink, indent, api); + } else if (api.location == ApiLocation.flutter) { + writeFlutterApi(generatorOptions, root, sink, indent, api); + } + } + + indent.addln(''); + _writeWrapResult(indent); + indent.addln(''); + _writeWrapError(indent); } -} -/// Calculates the name of the codec that will be generated for [api]. -String _getCodecName(Api api) => '${api.name}Codec'; + @override + void writeFilePrologue(SwiftOptions generatorOptions, Root root, + StringSink sink, Indent indent) { + if (generatorOptions.copyrightHeader != null) { + addLines(indent, generatorOptions.copyrightHeader!, linePrefix: '// '); + } + indent.writeln('// $generatedCodeWarning'); + indent.writeln('// $seeAlsoWarning'); + indent.addln(''); + } -/// 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 writeFileImports(SwiftOptions generatorOptions, Root root, + StringSink sink, 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(''); + } + + @override + void writeEnum(SwiftOptions generatorOptions, Root root, StringSink sink, + Indent indent, 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'); }); - } - }); + }); + } - // 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, StringSink sink, + 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.' + ]; + 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, sink, indent, klass, + customClassNames, customEnumNames); + writeClassEncode(generatorOptions, root, sink, indent, klass, + customClassNames, customEnumNames); + }); + } + + @override + void writeClassEncode( + SwiftOptions generatorOptions, + Root root, + StringSink sink, + 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, + StringSink sink, + 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); + } + + /// 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, + StringSink sink, + Indent indent, + Api api, + ) { + 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); -/// 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 }'); - } - indent.writeln( - '$_docCommentPrefix Sets up an instance of `$apiName` to handle messages through the `binaryMessenger`.'); - indent.write( - 'static func setUp(binaryMessenger: FlutterBinaryMessenger, api: $apiName?) '); + /// 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, + StringSink sink, + Indent indent, + Api api, + ) { + 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 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())'); + }); + } + + void _writeWrapResult(Indent indent) { + indent.write('private func wrapResult(_ result: Any?) -> [Any?] '); + indent.scoped('{', '}', () { + indent.writeln('return [result]'); + }); + } + + void _writeWrapError(Indent indent) { + 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'); + }); + }); + } +} + +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 +631,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 ? '' : '!'; @@ -449,225 +706,5 @@ String _nullsafeSwiftTypeForDartType(TypeDeclaration type) { /// 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(); -} +void generateSwift( + SwiftOptions options, Root root, StringSink sink, Indent indent) {} diff --git a/packages/pigeon/mock_handler_tester/test/message.dart b/packages/pigeon/mock_handler_tester/test/message.dart index 4f6c9d14457..1780b3c7504 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 (v5.0.5), 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..075fe0e7ca6 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 (v5.0.5), 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 95b0e2a9509..b95b18fb650 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.0), do not edit directly. +// Autogenerated from Pigeon (v5.0.5), do not edit directly. // See also: https://pub.dev/packages/pigeon package com.example.alternate_language_test_plugin; 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 1def5e7af69..74c3c2e68f4 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.0), do not edit directly. +// Autogenerated from Pigeon (v5.0.5), do not edit directly. // See also: https://pub.dev/packages/pigeon + #import + @protocol FlutterBinaryMessenger; @protocol FlutterMessageCodec; @class FlutterError; 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 244b1e236c5..c52ea0822e1 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.0), do not edit directly. +// Autogenerated from Pigeon (v5.0.5), do not edit directly. // See also: https://pub.dev/packages/pigeon + #import "CoreTests.gen.h" #import 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..e681a886aaf 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 (v5.0.5), 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..aa2c80d3824 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 (v5.0.5), 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..d60b52646b1 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 (v5.0.5), 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..6d2d1d41f03 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 (v5.0.5), 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..930fccd57fa 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 (v5.0.5), 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..75e73d74eea 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 (v5.0.5), 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..f2961e54cca 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 (v5.0.5), 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 fe0fe3335ea..e681a886aaf 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.0), do not edit directly. +// Autogenerated from Pigeon (v5.0.5), 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 61a957c8b55..15d31704b6d 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.0), do not edit directly. +// Autogenerated from Pigeon (v5.0.5), do not edit directly. // See also: https://pub.dev/packages/pigeon package com.example.test_plugin 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 844e541a562..ecd65d85009 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.0), do not edit directly. +// Autogenerated from Pigeon (v5.0.5), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation 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 844e541a562..ecd65d85009 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.0), do not edit directly. +// Autogenerated from Pigeon (v5.0.5), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation 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 110c75e358d..7cd0f9cec35 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.0), do not edit directly. +// Autogenerated from Pigeon (v5.0.5), 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 527a0ff45c8..e6e0825cf88 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.0), do not edit directly. +// Autogenerated from Pigeon (v5.0.5), 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_CORE_TESTS_PIGEONTEST_H_ +#define PIGEON_CORE_TESTS_GEN_H_CORE_TESTS_PIGEONTEST_H_ #include #include #include @@ -17,6 +17,7 @@ #include namespace core_tests_pigeontest { + class CoreTestsTest; // Generated class from Pigeon. @@ -412,4 +413,4 @@ class HostTrivialApi { HostTrivialApi() = default; }; } // namespace core_tests_pigeontest -#endif // PIGEON_CORE_TESTS_PIGEONTEST_H_ +#endif // PIGEON_CORE_TESTS_GEN_H_CORE_TESTS_PIGEONTEST_H_ diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index e4ada89edd1..e4feca00cb9 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.0 # This must match the version in lib/generator_tools.dart +version: 5.0.5 # 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 172ce2ea3fe..bbed3a40c88 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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(')); diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart index c77e5d89e37..15ce9e09ac4 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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(), + + final 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); + final 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); + final 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); + final 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); + final 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( + + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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(), + final 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); + final 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); + final 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); + final 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(), + + final 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..f2a25288754 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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..96056c1b1b1 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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..c6b45fd4d43 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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( + final 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( + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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, + final 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, + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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..305ca203fe7 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final 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); + final SwiftGenerator generator = SwiftGenerator(); + generator.generate(swiftOptions, root, sink); final String code = sink.toString(); expect(code, contains(': FlutterStandardReader ')); });