blob: 0662261b9eeea005bb2eadd0669ea1bb80f9bb2a [file] [log] [blame]
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:pigeon/src/ast.dart';
import 'package:pigeon/src/cpp/cpp_generator.dart';
import 'package:pigeon/src/generator_tools.dart';
import 'package:pigeon/src/pigeon_lib.dart' show Error;
import 'package:test/test.dart';
const String DEFAULT_PACKAGE_NAME = 'test_package';
final Class emptyClass = Class(
name: 'className',
fields: <NamedType>[
NamedType(
name: 'namedTypeName',
type: const TypeDeclaration(baseName: 'baseName', isNullable: false),
),
],
);
final Enum emptyEnum = Enum(
name: 'enumName',
members: <EnumMember>[EnumMember(name: 'enumMemberName')],
);
void main() {
test('gen one api', () {
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'Api',
methods: <Method>[
Method(
name: 'doSomething',
parameters: <Parameter>[
Parameter(
type: TypeDeclaration(
baseName: 'Input',
isNullable: false,
associatedClass: emptyClass,
),
name: 'input',
),
],
location: ApiLocation.host,
returnType: TypeDeclaration(
baseName: 'Output',
isNullable: false,
associatedClass: emptyClass,
),
),
],
),
],
classes: <Class>[
Class(
name: 'Input',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'String', isNullable: true),
name: 'input',
),
],
),
Class(
name: 'Output',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'String', isNullable: true),
name: 'output',
),
],
),
],
enums: <Enum>[],
);
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(code, contains('class Input'));
expect(code, contains('class Output'));
expect(code, contains('class Api'));
expect(code, contains('virtual ~Api() {}\n'));
}
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.source,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(code, contains('Input::Input()'));
expect(code, contains('Output::Output'));
expect(
code,
contains(
RegExp(
r'void Api::SetUp\(\s*'
r'flutter::BinaryMessenger\* binary_messenger,\s*'
r'Api\* api\s*\)',
),
),
);
}
});
test('naming follows style', () {
final Enum anEnum = Enum(
name: 'AnEnum',
members: <EnumMember>[
EnumMember(name: 'one'),
EnumMember(name: 'fortyTwo'),
],
);
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'Api',
methods: <Method>[
Method(
name: 'doSomething',
parameters: <Parameter>[
Parameter(
type: TypeDeclaration(
baseName: 'Input',
isNullable: false,
associatedClass: emptyClass,
),
name: 'someInput',
),
],
location: ApiLocation.host,
returnType: TypeDeclaration(
baseName: 'Output',
isNullable: false,
associatedClass: emptyClass,
),
),
],
),
],
classes: <Class>[
Class(
name: 'Input',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'bool', isNullable: false),
name: 'inputField',
),
],
),
Class(
name: 'Output',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'bool', isNullable: false),
name: 'outputField',
),
NamedType(
type: TypeDeclaration(
baseName: anEnum.name,
isNullable: false,
associatedEnum: anEnum,
),
name: 'code',
),
],
),
],
enums: <Enum>[anEnum],
);
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
// Method name and argument names should be adjusted.
expect(code, contains(' DoSomething(const Input& some_input)'));
// Getters and setters should use optional getter/setter style.
expect(code, contains('bool input_field()'));
expect(code, contains('void set_input_field(bool value_arg)'));
expect(code, contains('bool output_field()'));
expect(code, contains('void set_output_field(bool value_arg)'));
// Instance variables should be adjusted.
expect(code, contains('bool input_field_'));
expect(code, contains('bool output_field_'));
// Enum values should be adjusted.
expect(code, contains('kOne'));
expect(code, contains('kFortyTwo'));
}
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.source,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(code, contains('encodable_some_input'));
expect(code, contains('Output::output_field()'));
expect(code, contains('Output::set_output_field(bool value_arg)'));
}
});
test('FlutterError fields are private with public accessors', () {
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'Api',
methods: <Method>[
Method(
name: 'doSomething',
location: ApiLocation.host,
parameters: <Parameter>[
Parameter(
type: const TypeDeclaration(
baseName: 'int',
isNullable: false,
),
name: 'someInput',
),
],
returnType: const TypeDeclaration(
baseName: 'int',
isNullable: false,
),
),
],
),
],
classes: <Class>[],
enums: <Enum>[],
);
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(
code.split('\n'),
containsAllInOrder(<Matcher>[
contains('class FlutterError {'),
contains(' public:'),
contains(' const std::string& code() const { return code_; }'),
contains(' const std::string& message() const { return message_; }'),
contains(
' const flutter::EncodableValue& details() const { return details_; }',
),
contains(' private:'),
contains(' std::string code_;'),
contains(' std::string message_;'),
contains(' flutter::EncodableValue details_;'),
]),
);
}
});
test('Error field is private with public accessors', () {
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'Api',
methods: <Method>[
Method(
name: 'doSomething',
location: ApiLocation.host,
parameters: <Parameter>[
Parameter(
type: const TypeDeclaration(
baseName: 'int',
isNullable: false,
),
name: 'someInput',
),
],
returnType: const TypeDeclaration(
baseName: 'int',
isNullable: false,
),
),
],
),
],
classes: <Class>[],
enums: <Enum>[],
containsHostApi: true,
);
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(
code.split('\n'),
containsAllInOrder(<Matcher>[
contains('class ErrorOr {'),
contains(' public:'),
contains(' bool has_error() const {'),
contains(' const T& value() const {'),
contains(' const FlutterError& error() const {'),
contains(' private:'),
contains(' std::variant<T, FlutterError> v_;'),
]),
);
}
});
test('Spaces before {', () {
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'Api',
methods: <Method>[
Method(
name: 'doSomething',
location: ApiLocation.host,
parameters: <Parameter>[
Parameter(
type: TypeDeclaration(
baseName: 'Input',
isNullable: false,
associatedClass: emptyClass,
),
name: 'input',
),
],
returnType: TypeDeclaration(
baseName: 'Output',
isNullable: false,
associatedClass: emptyClass,
),
),
],
),
],
classes: <Class>[
Class(
name: 'Input',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'String', isNullable: true),
name: 'input',
),
],
),
Class(
name: 'Output',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'String', isNullable: true),
name: 'output',
),
],
),
],
enums: <Enum>[],
);
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(code, isNot(contains('){')));
expect(code, isNot(contains('const{')));
}
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.source,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(code, isNot(contains('){')));
expect(code, isNot(contains('const{')));
}
});
test('include blocks follow style', () {
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'Api',
methods: <Method>[
Method(
name: 'doSomething',
location: ApiLocation.host,
parameters: <Parameter>[
Parameter(
type: const TypeDeclaration(
baseName: 'String',
isNullable: true,
),
name: 'input',
),
],
returnType: const TypeDeclaration(
baseName: 'int',
isNullable: false,
),
),
],
),
],
classes: <Class>[],
enums: <Enum>[],
);
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(
code,
contains('''
#include <flutter/basic_message_channel.h>
#include <flutter/binary_messenger.h>
#include <flutter/encodable_value.h>
#include <flutter/standard_message_codec.h>
#include <map>
#include <optional>
#include <string>
'''),
);
}
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.source,
languageOptions: const InternalCppOptions(
headerIncludePath: 'a_header.h',
cppHeaderOut: '',
cppSourceOut: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(
code,
contains('''
#include "a_header.h"
#include <flutter/basic_message_channel.h>
#include <flutter/binary_messenger.h>
#include <flutter/encodable_value.h>
#include <flutter/standard_message_codec.h>
#include <map>
#include <optional>
#include <string>
'''),
);
}
});
test('namespaces follows style', () {
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'Api',
methods: <Method>[
Method(
name: 'doSomething',
location: ApiLocation.host,
parameters: <Parameter>[
Parameter(
type: const TypeDeclaration(
baseName: 'String',
isNullable: true,
),
name: 'input',
),
],
returnType: const TypeDeclaration(
baseName: 'int',
isNullable: false,
),
),
],
),
],
classes: <Class>[],
enums: <Enum>[],
);
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
namespace: 'foo',
headerIncludePath: '',
cppHeaderOut: '',
cppSourceOut: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(code, contains('namespace foo {'));
expect(code, contains('} // namespace foo'));
}
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.source,
languageOptions: const InternalCppOptions(
namespace: 'foo',
headerIncludePath: '',
cppHeaderOut: '',
cppSourceOut: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(code, contains('namespace foo {'));
expect(code, contains('} // namespace foo'));
}
});
test('data classes handle nullable fields', () {
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'Api',
methods: <Method>[
Method(
name: 'doSomething',
location: ApiLocation.host,
parameters: <Parameter>[
Parameter(
type: TypeDeclaration(
baseName: 'Input',
isNullable: false,
associatedClass: emptyClass,
),
name: 'someInput',
),
],
returnType: const TypeDeclaration.voidDeclaration(),
),
],
),
],
classes: <Class>[
Class(
name: 'Nested',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'bool', isNullable: true),
name: 'nestedValue',
),
],
),
Class(
name: 'Input',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'bool', isNullable: true),
name: 'nullableBool',
),
NamedType(
type: const TypeDeclaration(baseName: 'int', isNullable: true),
name: 'nullableInt',
),
NamedType(
type: const TypeDeclaration(baseName: 'String', isNullable: true),
name: 'nullableString',
),
NamedType(
type: TypeDeclaration(
baseName: 'Nested',
isNullable: true,
associatedClass: emptyClass,
),
name: 'nullableNested',
),
],
),
],
enums: <Enum>[],
);
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
// There should be a default constructor.
expect(code, contains('Nested();'));
// There should be a convenience constructor.
expect(
code,
contains(RegExp(r'explicit Nested\(\s*const bool\* nested_value\s*\)')),
);
// Getters should return const pointers.
expect(code, contains('const bool* nullable_bool()'));
expect(code, contains('const int64_t* nullable_int()'));
expect(code, contains('const std::string* nullable_string()'));
expect(code, contains('const Nested* nullable_nested()'));
// Setters should take const pointers.
expect(code, contains('void set_nullable_bool(const bool* value_arg)'));
expect(code, contains('void set_nullable_int(const int64_t* value_arg)'));
// Strings should be string_view rather than string as an argument.
expect(
code,
contains('void set_nullable_string(const std::string_view* value_arg)'),
);
expect(
code,
contains('void set_nullable_nested(const Nested* value_arg)'),
);
// Setters should have non-null-style variants.
expect(code, contains('void set_nullable_bool(bool value_arg)'));
expect(code, contains('void set_nullable_int(int64_t value_arg)'));
expect(
code,
contains('void set_nullable_string(std::string_view value_arg)'),
);
expect(
code,
contains('void set_nullable_nested(const Nested& value_arg)'),
);
// Most instance variables should be std::optionals.
expect(code, contains('std::optional<bool> nullable_bool_'));
expect(code, contains('std::optional<int64_t> nullable_int_'));
expect(code, contains('std::optional<std::string> nullable_string_'));
// Custom classes are the exception, to avoid inline storage.
expect(code, contains('std::unique_ptr<Nested> nullable_nested_'));
}
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.source,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
// There should be a default constructor.
expect(code, contains('Nested::Nested() {}'));
// There should be a convenience constructor.
expect(
code,
contains(
RegExp(
r'Nested::Nested\(\s*const bool\* nested_value\s*\)'
r'\s*:\s*nested_value_\(nested_value \? '
r'std::optional<bool>\(\*nested_value\) : std::nullopt\)\s*{}',
),
),
);
// Getters extract optionals.
expect(
code,
contains('return nullable_bool_ ? &(*nullable_bool_) : nullptr;'),
);
expect(
code,
contains('return nullable_int_ ? &(*nullable_int_) : nullptr;'),
);
expect(
code,
contains('return nullable_string_ ? &(*nullable_string_) : nullptr;'),
);
expect(code, contains('return nullable_nested_.get();'));
// Setters convert to optionals.
expect(
code,
contains(
'nullable_bool_ = value_arg ? '
'std::optional<bool>(*value_arg) : std::nullopt;',
),
);
expect(
code,
contains(
'nullable_int_ = value_arg ? '
'std::optional<int64_t>(*value_arg) : std::nullopt;',
),
);
expect(
code,
contains(
'nullable_string_ = value_arg ? '
'std::optional<std::string>(*value_arg) : std::nullopt;',
),
);
expect(
code,
contains(
'nullable_nested_ = value_arg ? std::make_unique<Nested>(*value_arg) : nullptr;',
),
);
// Serialization handles optionals.
expect(
code,
contains(
'nullable_bool_ ? EncodableValue(*nullable_bool_) '
': EncodableValue()',
),
);
expect(
code,
contains(
'nullable_nested_ ? CustomEncodableValue(*nullable_nested_) : EncodableValue())',
),
);
// Serialization should use push_back, not initializer lists, to avoid
// copies.
expect(code, contains('list.reserve(4)'));
expect(
code,
contains(
'list.push_back(nullable_bool_ ? '
'EncodableValue(*nullable_bool_) : '
'EncodableValue())',
),
);
}
});
test('data classes handle non-nullable fields', () {
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'Api',
methods: <Method>[
Method(
name: 'doSomething',
location: ApiLocation.host,
parameters: <Parameter>[
Parameter(
type: TypeDeclaration(
baseName: 'Input',
isNullable: false,
associatedClass: emptyClass,
),
name: 'someInput',
),
],
returnType: const TypeDeclaration.voidDeclaration(),
),
],
),
],
classes: <Class>[
Class(
name: 'Nested',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'bool', isNullable: false),
name: 'nestedValue',
),
],
),
Class(
name: 'Input',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'bool', isNullable: false),
name: 'nonNullableBool',
),
NamedType(
type: const TypeDeclaration(baseName: 'int', isNullable: false),
name: 'nonNullableInt',
),
NamedType(
type: const TypeDeclaration(
baseName: 'String',
isNullable: false,
),
name: 'nonNullableString',
),
NamedType(
type: TypeDeclaration(
baseName: 'Nested',
isNullable: false,
associatedClass: emptyClass,
),
name: 'nonNullableNested',
),
],
),
],
enums: <Enum>[],
);
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
// There should not be a default constructor.
expect(code, isNot(contains('Nested();')));
// There should be a convenience constructor.
expect(
code,
contains(RegExp(r'explicit Nested\(\s*bool nested_value\s*\)')),
);
// POD getters should return copies references.
expect(code, contains('bool non_nullable_bool()'));
expect(code, contains('int64_t non_nullable_int()'));
// Non-POD getters should return const references.
expect(code, contains('const std::string& non_nullable_string()'));
expect(code, contains('const Nested& non_nullable_nested()'));
// POD setters should take values.
expect(code, contains('void set_non_nullable_bool(bool value_arg)'));
expect(code, contains('void set_non_nullable_int(int64_t value_arg)'));
// Strings should be string_view as an argument.
expect(
code,
contains('void set_non_nullable_string(std::string_view value_arg)'),
);
// Other non-POD setters should take const references.
expect(
code,
contains('void set_non_nullable_nested(const Nested& value_arg)'),
);
// Instance variables should be plain types.
expect(code, contains('bool non_nullable_bool_;'));
expect(code, contains('int64_t non_nullable_int_;'));
expect(code, contains('std::string non_nullable_string_;'));
// Except for custom classes.
expect(code, contains('std::unique_ptr<Nested> non_nullable_nested_;'));
}
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.source,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
// There should not be a default constructor.
expect(code, isNot(contains('Nested::Nested() {}')));
// There should be a convenience constructor.
expect(
code,
contains(
RegExp(
r'Nested::Nested\(\s*bool nested_value\s*\)'
r'\s*:\s*nested_value_\(nested_value\)\s*{}',
),
),
);
// Getters just return the value.
expect(code, contains('return non_nullable_bool_;'));
expect(code, contains('return non_nullable_int_;'));
expect(code, contains('return non_nullable_string_;'));
// Unless it's a custom class.
expect(code, contains('return *non_nullable_nested_;'));
// Setters just assign the value.
expect(code, contains('non_nullable_bool_ = value_arg;'));
expect(code, contains('non_nullable_int_ = value_arg;'));
expect(code, contains('non_nullable_string_ = value_arg;'));
// Unless it's a custom class.
expect(
code,
contains('non_nullable_nested_ = std::make_unique<Nested>(value_arg);'),
);
// Serialization uses the value directly.
expect(code, contains('EncodableValue(non_nullable_bool_)'));
expect(code, contains('CustomEncodableValue(*non_nullable_nested_)'));
// Serialization should use push_back, not initializer lists, to avoid
// copies.
expect(code, contains('list.reserve(4)'));
expect(
code,
contains('list.push_back(CustomEncodableValue(*non_nullable_nested_))'),
);
}
});
test('host nullable return types map correctly', () {
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'Api',
methods: <Method>[
Method(
name: 'returnNullableBool',
location: ApiLocation.host,
parameters: <Parameter>[],
returnType: const TypeDeclaration(
baseName: 'bool',
isNullable: true,
),
),
Method(
name: 'returnNullableInt',
location: ApiLocation.host,
parameters: <Parameter>[],
returnType: const TypeDeclaration(
baseName: 'int',
isNullable: true,
),
),
Method(
name: 'returnNullableString',
location: ApiLocation.host,
parameters: <Parameter>[],
returnType: const TypeDeclaration(
baseName: 'String',
isNullable: true,
),
),
Method(
name: 'returnNullableList',
location: ApiLocation.host,
parameters: <Parameter>[],
returnType: const TypeDeclaration(
baseName: 'List',
typeArguments: <TypeDeclaration>[
TypeDeclaration(baseName: 'String', isNullable: true),
],
isNullable: true,
),
),
Method(
name: 'returnNullableMap',
location: ApiLocation.host,
parameters: <Parameter>[],
returnType: const TypeDeclaration(
baseName: 'Map',
typeArguments: <TypeDeclaration>[
TypeDeclaration(baseName: 'String', isNullable: true),
TypeDeclaration(baseName: 'String', isNullable: true),
],
isNullable: true,
),
),
Method(
name: 'returnNullableDataClass',
location: ApiLocation.host,
parameters: <Parameter>[],
returnType: TypeDeclaration(
baseName: 'ReturnData',
isNullable: true,
associatedClass: emptyClass,
),
),
],
),
],
classes: <Class>[
Class(
name: 'ReturnData',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'bool', isNullable: false),
name: 'aValue',
),
],
),
],
enums: <Enum>[],
);
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(
code,
contains('ErrorOr<std::optional<bool>> ReturnNullableBool()'),
);
expect(
code,
contains('ErrorOr<std::optional<int64_t>> ReturnNullableInt()'),
);
expect(
code,
contains('ErrorOr<std::optional<std::string>> ReturnNullableString()'),
);
expect(
code,
contains(
'ErrorOr<std::optional<flutter::EncodableList>> ReturnNullableList()',
),
);
expect(
code,
contains(
'ErrorOr<std::optional<flutter::EncodableMap>> ReturnNullableMap()',
),
);
expect(
code,
contains(
'ErrorOr<std::optional<ReturnData>> ReturnNullableDataClass()',
),
);
}
});
test('host non-nullable return types map correctly', () {
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'Api',
methods: <Method>[
Method(
name: 'returnBool',
location: ApiLocation.host,
parameters: <Parameter>[],
returnType: const TypeDeclaration(
baseName: 'bool',
isNullable: false,
),
),
Method(
name: 'returnInt',
location: ApiLocation.host,
parameters: <Parameter>[],
returnType: const TypeDeclaration(
baseName: 'int',
isNullable: false,
),
),
Method(
name: 'returnString',
location: ApiLocation.host,
parameters: <Parameter>[],
returnType: const TypeDeclaration(
baseName: 'String',
isNullable: false,
),
),
Method(
name: 'returnList',
location: ApiLocation.host,
parameters: <Parameter>[],
returnType: const TypeDeclaration(
baseName: 'List',
typeArguments: <TypeDeclaration>[
TypeDeclaration(baseName: 'String', isNullable: true),
],
isNullable: false,
),
),
Method(
name: 'returnMap',
location: ApiLocation.host,
parameters: <Parameter>[],
returnType: const TypeDeclaration(
baseName: 'Map',
typeArguments: <TypeDeclaration>[
TypeDeclaration(baseName: 'String', isNullable: true),
TypeDeclaration(baseName: 'String', isNullable: true),
],
isNullable: false,
),
),
Method(
name: 'returnDataClass',
location: ApiLocation.host,
parameters: <Parameter>[],
returnType: TypeDeclaration(
baseName: 'ReturnData',
isNullable: false,
associatedClass: emptyClass,
),
),
],
),
],
classes: <Class>[
Class(
name: 'ReturnData',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'bool', isNullable: false),
name: 'aValue',
),
],
),
],
enums: <Enum>[],
);
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(code, contains('ErrorOr<bool> ReturnBool()'));
expect(code, contains('ErrorOr<int64_t> ReturnInt()'));
expect(code, contains('ErrorOr<std::string> ReturnString()'));
expect(code, contains('ErrorOr<flutter::EncodableList> ReturnList()'));
expect(code, contains('ErrorOr<flutter::EncodableMap> ReturnMap()'));
expect(code, contains('ErrorOr<ReturnData> ReturnDataClass()'));
}
});
test('host nullable arguments map correctly', () {
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'Api',
methods: <Method>[
Method(
name: 'doSomething',
location: ApiLocation.host,
parameters: <Parameter>[
Parameter(
name: 'aBool',
type: const TypeDeclaration(
baseName: 'bool',
isNullable: true,
),
),
Parameter(
name: 'anInt',
type: const TypeDeclaration(
baseName: 'int',
isNullable: true,
),
),
Parameter(
name: 'aString',
type: const TypeDeclaration(
baseName: 'String',
isNullable: true,
),
),
Parameter(
name: 'aList',
type: const TypeDeclaration(
baseName: 'List',
typeArguments: <TypeDeclaration>[
TypeDeclaration(baseName: 'Object', isNullable: true),
],
isNullable: true,
),
),
Parameter(
name: 'aMap',
type: const TypeDeclaration(
baseName: 'Map',
typeArguments: <TypeDeclaration>[
TypeDeclaration(baseName: 'String', isNullable: true),
TypeDeclaration(baseName: 'Object', isNullable: true),
],
isNullable: true,
),
),
Parameter(
name: 'anObject',
type: TypeDeclaration(
baseName: 'ParameterObject',
isNullable: true,
associatedClass: emptyClass,
),
),
Parameter(
name: 'aGenericObject',
type: const TypeDeclaration(
baseName: 'Object',
isNullable: true,
),
),
],
returnType: const TypeDeclaration.voidDeclaration(),
),
],
),
],
classes: <Class>[
Class(
name: 'ParameterObject',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'bool', isNullable: false),
name: 'aValue',
),
],
),
],
enums: <Enum>[],
);
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(
code,
contains(
RegExp(
r'DoSomething\(\s*'
r'const bool\* a_bool,\s*'
r'const int64_t\* an_int,\s*'
r'const std::string\* a_string,\s*'
r'const flutter::EncodableList\* a_list,\s*'
r'const flutter::EncodableMap\* a_map,\s*'
r'const ParameterObject\* an_object,\s*'
r'const flutter::EncodableValue\* a_generic_object\s*\)',
),
),
);
}
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.source,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
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
// EncodableValue will not match the queried type, so get_if will return
// nullptr).
expect(
code,
contains(
'const auto* a_bool_arg = std::get_if<bool>(&encodable_a_bool_arg);',
),
);
expect(
code,
contains(
'const auto* a_string_arg = std::get_if<std::string>(&encodable_a_string_arg);',
),
);
expect(
code,
contains(
'const auto* a_list_arg = std::get_if<EncodableList>(&encodable_a_list_arg);',
),
);
expect(
code,
contains(
'const auto* a_map_arg = std::get_if<EncodableMap>(&encodable_a_map_arg);',
),
);
expect(
code,
contains(
'const auto* a_bool_arg = std::get_if<bool>(&encodable_a_bool_arg);',
),
);
expect(
code,
contains(
'const auto* an_int_arg = std::get_if<int64_t>(&encodable_an_int_arg);',
),
);
// Custom class types require an extra layer of extraction.
expect(
code,
contains(
'const auto* an_object_arg = encodable_an_object_arg.IsNull() ? nullptr : &(std::any_cast<const ParameterObject&>(std::get<CustomEncodableValue>(encodable_an_object_arg)));',
),
);
// "Object" requires no extraction at all since it has to use
// EncodableValue directly.
expect(
code,
contains(
'const auto* a_generic_object_arg = &encodable_a_generic_object_arg;',
),
);
}
});
test('host non-nullable arguments map correctly', () {
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'Api',
methods: <Method>[
Method(
name: 'doSomething',
location: ApiLocation.host,
parameters: <Parameter>[
Parameter(
name: 'aBool',
type: const TypeDeclaration(
baseName: 'bool',
isNullable: false,
),
),
Parameter(
name: 'anInt',
type: const TypeDeclaration(
baseName: 'int',
isNullable: false,
),
),
Parameter(
name: 'aString',
type: const TypeDeclaration(
baseName: 'String',
isNullable: false,
),
),
Parameter(
name: 'aList',
type: const TypeDeclaration(
baseName: 'List',
typeArguments: <TypeDeclaration>[
TypeDeclaration(baseName: 'Object', isNullable: true),
],
isNullable: false,
),
),
Parameter(
name: 'aMap',
type: const TypeDeclaration(
baseName: 'Map',
typeArguments: <TypeDeclaration>[
TypeDeclaration(baseName: 'String', isNullable: true),
TypeDeclaration(baseName: 'Object', isNullable: true),
],
isNullable: false,
),
),
Parameter(
name: 'anObject',
type: TypeDeclaration(
baseName: 'ParameterObject',
isNullable: false,
associatedClass: emptyClass,
),
),
Parameter(
name: 'aGenericObject',
type: const TypeDeclaration(
baseName: 'Object',
isNullable: false,
),
),
],
returnType: const TypeDeclaration.voidDeclaration(),
),
],
),
],
classes: <Class>[
Class(
name: 'ParameterObject',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'bool', isNullable: false),
name: 'aValue',
),
],
),
],
enums: <Enum>[],
);
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(
code,
contains(
RegExp(
r'DoSomething\(\s*'
r'bool a_bool,\s*'
r'int64_t an_int,\s*'
r'const std::string& a_string,\s*'
r'const flutter::EncodableList& a_list,\s*'
r'const flutter::EncodableMap& a_map,\s*'
r'const ParameterObject& an_object,\s*'
r'const flutter::EncodableValue& a_generic_object\s*\)',
),
),
);
}
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.source,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
// Most types should extract references. Since the type is non-nullable,
// there's only one possible type.
expect(
code,
contains(
'const auto& a_bool_arg = std::get<bool>(encodable_a_bool_arg);',
),
);
expect(
code,
contains(
'const auto& a_string_arg = std::get<std::string>(encodable_a_string_arg);',
),
);
expect(
code,
contains(
'const auto& a_list_arg = std::get<EncodableList>(encodable_a_list_arg);',
),
);
expect(
code,
contains(
'const auto& a_map_arg = std::get<EncodableMap>(encodable_a_map_arg);',
),
);
// Ints use a copy since there are two possible reference types, but
// the parameter always needs an int64_t.
expect(
code,
contains(
'const int64_t an_int_arg = encodable_an_int_arg.LongValue();',
),
);
// Custom class types require an extra layer of extraction.
expect(
code,
contains(
'const auto& an_object_arg = std::any_cast<const ParameterObject&>(std::get<CustomEncodableValue>(encodable_an_object_arg));',
),
);
// "Object" requires no extraction at all since it has to use
// EncodableValue directly.
expect(
code,
contains(
'const auto& a_generic_object_arg = encodable_a_generic_object_arg;',
),
);
}
});
test('flutter nullable arguments map correctly', () {
final Root root = Root(
apis: <Api>[
AstFlutterApi(
name: 'Api',
methods: <Method>[
Method(
name: 'doSomething',
location: ApiLocation.flutter,
parameters: <Parameter>[
Parameter(
name: 'aBool',
type: const TypeDeclaration(
baseName: 'bool',
isNullable: true,
),
),
Parameter(
name: 'anInt',
type: const TypeDeclaration(
baseName: 'int',
isNullable: true,
),
),
Parameter(
name: 'aString',
type: const TypeDeclaration(
baseName: 'String',
isNullable: true,
),
),
Parameter(
name: 'aList',
type: const TypeDeclaration(
baseName: 'List',
typeArguments: <TypeDeclaration>[
TypeDeclaration(baseName: 'Object', isNullable: true),
],
isNullable: true,
),
),
Parameter(
name: 'aMap',
type: const TypeDeclaration(
baseName: 'Map',
typeArguments: <TypeDeclaration>[
TypeDeclaration(baseName: 'String', isNullable: true),
TypeDeclaration(baseName: 'Object', isNullable: true),
],
isNullable: true,
),
),
Parameter(
name: 'anObject',
type: TypeDeclaration(
baseName: 'ParameterObject',
isNullable: true,
associatedClass: emptyClass,
),
),
Parameter(
name: 'aGenericObject',
type: const TypeDeclaration(
baseName: 'Object',
isNullable: true,
),
),
],
returnType: const TypeDeclaration(
baseName: 'bool',
isNullable: true,
),
),
],
),
],
classes: <Class>[
Class(
name: 'ParameterObject',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'bool', isNullable: false),
name: 'aValue',
),
],
),
],
enums: <Enum>[],
);
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
// Nullable arguments should all be pointers. This will make them somewhat
// awkward for some uses (literals, values that could be inlined) but
// unlike setters there's no way to provide reference-based alternatives
// since it's not always just one argument.
// TODO(stuartmorgan): Consider generating a second variant using
// `std::optional`s; that may be more ergonomic, but the perf implications
// would need to be considered.
expect(
code,
contains(
RegExp(
r'DoSomething\(\s*'
r'const bool\* a_bool,\s*'
r'const int64_t\* an_int,\s*'
// Nullable strings use std::string* rather than std::string_view*
// since there's no implicit conversion for pointer types.
r'const std::string\* a_string,\s*'
r'const flutter::EncodableList\* a_list,\s*'
r'const flutter::EncodableMap\* a_map,\s*'
r'const ParameterObject\* an_object,\s*'
r'const flutter::EncodableValue\* a_generic_object,',
),
),
);
// The callback should pass a pointer as well.
expect(
code,
contains(
RegExp(
r'std::function<void\(const bool\*\)>&& on_success,\s*'
r'std::function<void\(const FlutterError&\)>&& on_error\)',
),
),
);
}
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.source,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
// All types pass nulls values when the pointer is null.
// Standard types are wrapped an EncodableValues.
expect(
code,
contains('a_bool_arg ? EncodableValue(*a_bool_arg) : EncodableValue()'),
);
expect(
code,
contains('an_int_arg ? EncodableValue(*an_int_arg) : EncodableValue()'),
);
expect(
code,
contains(
'a_string_arg ? EncodableValue(*a_string_arg) : EncodableValue()',
),
);
expect(
code,
contains('a_list_arg ? EncodableValue(*a_list_arg) : EncodableValue()'),
);
expect(
code,
contains('a_map_arg ? EncodableValue(*a_map_arg) : EncodableValue()'),
);
// Class types use ToEncodableList.
expect(
code,
contains(
'an_object_arg ? CustomEncodableValue(*an_object_arg) : EncodableValue()',
),
);
}
});
test('flutter non-nullable arguments map correctly', () {
final Root root = Root(
apis: <Api>[
AstFlutterApi(
name: 'Api',
methods: <Method>[
Method(
name: 'doSomething',
location: ApiLocation.flutter,
parameters: <Parameter>[
Parameter(
name: 'aBool',
type: const TypeDeclaration(
baseName: 'bool',
isNullable: false,
),
),
Parameter(
name: 'anInt',
type: const TypeDeclaration(
baseName: 'int',
isNullable: false,
),
),
Parameter(
name: 'aString',
type: const TypeDeclaration(
baseName: 'String',
isNullable: false,
),
),
Parameter(
name: 'aList',
type: const TypeDeclaration(
baseName: 'List',
typeArguments: <TypeDeclaration>[
TypeDeclaration(baseName: 'Object', isNullable: true),
],
isNullable: false,
),
),
Parameter(
name: 'aMap',
type: const TypeDeclaration(
baseName: 'Map',
typeArguments: <TypeDeclaration>[
TypeDeclaration(baseName: 'String', isNullable: true),
TypeDeclaration(baseName: 'Object', isNullable: true),
],
isNullable: false,
),
),
Parameter(
name: 'anObject',
type: TypeDeclaration(
baseName: 'ParameterObject',
isNullable: false,
associatedClass: emptyClass,
),
),
Parameter(
name: 'aGenericObject',
type: const TypeDeclaration(
baseName: 'Object',
isNullable: false,
),
),
],
returnType: const TypeDeclaration(
baseName: 'bool',
isNullable: false,
),
),
],
),
],
classes: <Class>[
Class(
name: 'ParameterObject',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'bool', isNullable: false),
name: 'aValue',
),
],
),
],
enums: <Enum>[],
);
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(
code,
contains(
RegExp(
r'DoSomething\(\s*'
r'bool a_bool,\s*'
r'int64_t an_int,\s*'
// Non-nullable strings use std::string for consistency with
// nullable strings.
r'const std::string& a_string,\s*'
// Non-POD types use const references.
r'const flutter::EncodableList& a_list,\s*'
r'const flutter::EncodableMap& a_map,\s*'
r'const ParameterObject& an_object,\s*'
r'const flutter::EncodableValue& a_generic_object,\s*',
),
),
);
// The callback should pass a value.
expect(
code,
contains(
RegExp(
r'std::function<void\(bool\)>&& on_success,\s*'
r'std::function<void\(const FlutterError&\)>&& on_error\s*\)',
),
),
);
}
{
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.source,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
// Standard types are wrapped in EncodableValues.
expect(code, contains('EncodableValue(a_bool_arg)'));
expect(code, contains('EncodableValue(an_int_arg)'));
expect(code, contains('EncodableValue(a_string_arg)'));
expect(code, contains('EncodableValue(a_list_arg)'));
expect(code, contains('EncodableValue(a_map_arg)'));
// Class types are wrapped in CustomEncodableValues.
expect(code, contains('CustomEncodableValue(an_object_arg)'));
}
});
test('host API argument extraction uses references', () {
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'Api',
methods: <Method>[
Method(
name: 'doSomething',
location: ApiLocation.host,
parameters: <Parameter>[
Parameter(
name: 'anArg',
type: const TypeDeclaration(
baseName: 'int',
isNullable: false,
),
),
],
returnType: const TypeDeclaration.voidDeclaration(),
),
],
),
],
classes: <Class>[],
enums: <Enum>[],
);
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.source,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
// A bare 'auto' here would create a copy, not a reference, which is
// inefficient.
expect(
code,
contains('const auto& args = std::get<EncodableList>(message);'),
);
expect(code, contains('const auto& encodable_an_arg_arg = args.at(0);'));
});
test('enum argument', () {
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'Bar',
methods: <Method>[
Method(
name: 'bar',
location: ApiLocation.host,
returnType: const TypeDeclaration.voidDeclaration(),
parameters: <Parameter>[
Parameter(
name: 'foo',
type: TypeDeclaration(
baseName: 'Foo',
isNullable: false,
associatedEnum: emptyEnum,
),
),
],
),
],
),
],
classes: <Class>[],
enums: <Enum>[
Enum(
name: 'Foo',
members: <EnumMember>[
EnumMember(name: 'one'),
EnumMember(name: 'two'),
],
),
],
);
final List<Error> errors = validateCpp(
const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
root,
);
expect(errors.length, 1);
});
test('transfers documentation comments', () {
final List<String> comments = <String>[
' api comment',
' api method comment',
' class comment',
' class field comment',
' enum comment',
' enum member comment',
];
int count = 0;
final List<String> unspacedComments = <String>['////////'];
int unspacedCount = 0;
final Root root = Root(
apis: <Api>[
AstFlutterApi(
name: 'Api',
documentationComments: <String>[comments[count++]],
methods: <Method>[
Method(
name: 'method',
location: ApiLocation.flutter,
returnType: const TypeDeclaration.voidDeclaration(),
documentationComments: <String>[comments[count++]],
parameters: <Parameter>[
Parameter(
name: 'field',
type: const TypeDeclaration(
baseName: 'int',
isNullable: true,
),
),
],
),
],
),
],
classes: <Class>[
Class(
name: 'class',
documentationComments: <String>[comments[count++]],
fields: <NamedType>[
NamedType(
documentationComments: <String>[comments[count++]],
type: const TypeDeclaration(
baseName: 'Map',
isNullable: true,
typeArguments: <TypeDeclaration>[
TypeDeclaration(baseName: 'String', isNullable: true),
TypeDeclaration(baseName: 'int', isNullable: true),
],
),
name: 'field1',
),
],
),
],
enums: <Enum>[
Enum(
name: 'enum',
documentationComments: <String>[
comments[count++],
unspacedComments[unspacedCount++],
],
members: <EnumMember>[
EnumMember(
name: 'one',
documentationComments: <String>[comments[count++]],
),
EnumMember(name: 'two'),
],
),
],
);
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
headerIncludePath: 'foo',
cppHeaderOut: '',
cppSourceOut: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
for (final String comment in comments) {
expect(code, contains('//$comment'));
}
expect(code, contains('// ///'));
});
test('creates custom codecs', () {
final Root root = Root(
apis: <Api>[
AstFlutterApi(
name: 'Api',
methods: <Method>[
Method(
name: 'doSomething',
location: ApiLocation.flutter,
parameters: <Parameter>[
Parameter(
type: TypeDeclaration(
baseName: 'Input',
isNullable: false,
associatedClass: emptyClass,
),
name: '',
),
],
returnType: TypeDeclaration(
baseName: 'Output',
isNullable: false,
associatedClass: emptyClass,
),
isAsynchronous: true,
),
],
),
],
classes: <Class>[
Class(
name: 'Input',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'String', isNullable: true),
name: 'input',
),
],
),
Class(
name: 'Output',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'String', isNullable: true),
name: 'output',
),
],
),
],
enums: <Enum>[],
);
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.header,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(code, contains(' : public flutter::StandardCodecSerializer'));
});
test('Does not send unwrapped EncodableLists', () {
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'Api',
methods: <Method>[
Method(
name: 'doSomething',
location: ApiLocation.host,
parameters: <Parameter>[
Parameter(
name: 'aBool',
type: const TypeDeclaration(
baseName: 'bool',
isNullable: false,
),
),
Parameter(
name: 'anInt',
type: const TypeDeclaration(
baseName: 'int',
isNullable: false,
),
),
Parameter(
name: 'aString',
type: const TypeDeclaration(
baseName: 'String',
isNullable: false,
),
),
Parameter(
name: 'aList',
type: const TypeDeclaration(
baseName: 'List',
typeArguments: <TypeDeclaration>[
TypeDeclaration(baseName: 'Object', isNullable: true),
],
isNullable: false,
),
),
Parameter(
name: 'aMap',
type: const TypeDeclaration(
baseName: 'Map',
typeArguments: <TypeDeclaration>[
TypeDeclaration(baseName: 'String', isNullable: true),
TypeDeclaration(baseName: 'Object', isNullable: true),
],
isNullable: false,
),
),
Parameter(
name: 'anObject',
type: TypeDeclaration(
baseName: 'ParameterObject',
isNullable: false,
associatedClass: emptyClass,
),
),
],
returnType: const TypeDeclaration.voidDeclaration(),
),
],
),
],
classes: <Class>[
Class(
name: 'ParameterObject',
fields: <NamedType>[
NamedType(
type: const TypeDeclaration(baseName: 'bool', isNullable: false),
name: 'aValue',
),
],
),
],
enums: <Enum>[],
);
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.source,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(code, isNot(contains('reply(wrap')));
expect(code, contains('reply(EncodableValue('));
});
test('does not keep unowned references in async handlers', () {
final Root root = Root(
apis: <Api>[
AstHostApi(
name: 'HostApi',
methods: <Method>[
Method(
name: 'noop',
location: ApiLocation.host,
parameters: <Parameter>[],
returnType: const TypeDeclaration.voidDeclaration(),
isAsynchronous: true,
),
Method(
name: 'doSomething',
location: ApiLocation.host,
parameters: <Parameter>[
Parameter(
type: const TypeDeclaration(
baseName: 'int',
isNullable: false,
),
name: '',
),
],
returnType: const TypeDeclaration(
baseName: 'double',
isNullable: false,
),
isAsynchronous: true,
),
],
),
AstFlutterApi(
name: 'FlutterApi',
methods: <Method>[
Method(
name: 'noop',
location: ApiLocation.flutter,
parameters: <Parameter>[],
returnType: const TypeDeclaration.voidDeclaration(),
isAsynchronous: true,
),
Method(
name: 'doSomething',
location: ApiLocation.flutter,
parameters: <Parameter>[
Parameter(
type: const TypeDeclaration(
baseName: 'String',
isNullable: false,
),
name: '',
),
],
returnType: const TypeDeclaration(
baseName: 'bool',
isNullable: false,
),
isAsynchronous: true,
),
],
),
],
classes: <Class>[],
enums: <Enum>[],
);
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.source,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
// Nothing should be captured by reference for async handlers, since their
// lifetime is unknown (and expected to be longer than the stack's).
expect(code, isNot(contains('&reply')));
expect(code, isNot(contains('&wrapped')));
// Check for the exact capture format that is currently being used, to
// ensure that the negative tests above get updated if there are any
// changes to lambda capture.
expect(code, contains('[reply]('));
});
test('connection error contains channel name', () {
final Root root = Root(
apis: <Api>[
AstFlutterApi(
name: 'Api',
methods: <Method>[
Method(
name: 'method',
location: ApiLocation.flutter,
returnType: const TypeDeclaration.voidDeclaration(),
parameters: <Parameter>[
Parameter(
name: 'field',
type: const TypeDeclaration(
baseName: 'int',
isNullable: true,
),
),
],
),
],
),
],
classes: <Class>[],
enums: <Enum>[],
containsFlutterApi: true,
);
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.source,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(
code,
contains(
'"Unable to establish connection on channel: \'" + channel_name + "\'."',
),
);
expect(code, contains('on_error(CreateConnectionError(channel_name));'));
});
test('stack allocates the message channel.', () {
final Root root = Root(
apis: <Api>[
AstFlutterApi(
name: 'Api',
methods: <Method>[
Method(
name: 'method',
location: ApiLocation.flutter,
returnType: const TypeDeclaration.voidDeclaration(),
parameters: <Parameter>[
Parameter(
name: 'field',
type: const TypeDeclaration(
baseName: 'int',
isNullable: true,
),
),
],
),
],
),
],
classes: <Class>[],
enums: <Enum>[],
);
final StringBuffer sink = StringBuffer();
const CppGenerator generator = CppGenerator();
final OutputFileOptions<InternalCppOptions> generatorOptions =
OutputFileOptions<InternalCppOptions>(
fileType: FileType.source,
languageOptions: const InternalCppOptions(
cppHeaderOut: '',
cppSourceOut: '',
headerIncludePath: '',
),
);
generator.generate(
generatorOptions,
root,
sink,
dartPackageName: DEFAULT_PACKAGE_NAME,
);
final String code = sink.toString();
expect(
code,
contains(
'BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec());',
),
);
expect(code, contains('channel.Send'));
});
}