Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 27.2.0

* Adds support for sharing constants across platforms.

## 27.1.0

* [swift] Adds `CaseIterable` conformance to generated enums.
Expand Down
15 changes: 15 additions & 0 deletions packages/pigeon/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<?code-excerpt path-base="example/app"?>
# Pigeon

Pigeon is a code generator tool to make communication between Flutter and the
Expand Down Expand Up @@ -82,6 +83,20 @@ the threading model for handling HostApi methods can be selected with the
Host and Flutter APIs now support the ability to provide a unique message channel suffix string
to the api to allow for multiple instances to be created and operate in parallel.

### Constants

Pigeon supports generating top-level constants in the generated files. Constants can be defined at the top level of the Pigeon file:

<?code-excerpt "pigeons/messages.dart (constants)"?>
```dart
const String aStringConstant = 'stringConstantValue';
const int anIntConstant = 42;
const double aDoubleConstant = 3.14;
const bool aBoolConstant = true;
```

These constants will be translated into static constants or final variables in the target languages (e.g., `public static final` in Java, `let` in Swift, `const` in Dart, etc.). Only `String`, `int`, `double`, and `bool` constant types are supported.

## Usage

1) Add pigeon as a `dev_dependency`.
Expand Down
7 changes: 7 additions & 0 deletions packages/pigeon/example/app/pigeons/messages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,10 @@ abstract class MessageFlutterApi {
}

// #enddocregion flutter-definitions

// #docregion constants
const String aStringConstant = 'stringConstantValue';
const int anIntConstant = 42;
const double aDoubleConstant = 3.14;
const bool aBoolConstant = true;
// #enddocregion constants
40 changes: 38 additions & 2 deletions packages/pigeon/lib/src/ast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -785,13 +785,46 @@ class EnumMember extends Node {
}
}

/// Represents a constant.
class Constant extends Node {
/// Parametric constructor for [Constant].
Constant({
required this.name,
required this.type,
required this.value,
this.offset,
this.documentationComments = const <String>[],
});

/// The name of the constant.
final String name;

/// The type of the constant.
final TypeDeclaration type;

/// The value of the constant.
final Object value;

/// The offset in the source file where the constant appears.
final int? offset;

/// List of documentation comments, separated by line.
final List<String> documentationComments;

@override
String toString() {
return '(Constant name:$name type:$type value:$value documentationComments:$documentationComments)';
}
}

/// Top-level node for the AST.
class Root extends Node {
/// Parametric constructor for [Root].
Root({
required this.classes,
required this.apis,
required this.enums,
this.constants = const <Constant>[],
this.containsHostApi = false,
this.containsFlutterApi = false,
this.containsProxyApi = false,
Expand All @@ -800,7 +833,7 @@ class Root extends Node {

/// Factory function for generating an empty root, usually used when early errors are encountered.
factory Root.makeEmpty() {
return Root(apis: <Api>[], classes: <Class>[], enums: <Enum>[]);
return Root(apis: <Api>[], classes: <Class>[], enums: <Enum>[], constants: <Constant>[]);
}

/// All the classes contained in the AST.
Expand All @@ -812,6 +845,9 @@ class Root extends Node {
/// All of the enums contained in the AST.
List<Enum> enums;

/// All of the constants contained in the AST.
List<Constant> constants;

/// Whether the root has any Host API definitions.
bool containsHostApi;

Expand All @@ -833,6 +869,6 @@ class Root extends Node {

@override
String toString() {
return '(Root classes:$classes apis:$apis enums:$enums)';
return '(Root classes:$classes apis:$apis enums:$enums constants:$constants)';
}
}
24 changes: 24 additions & 0 deletions packages/pigeon/lib/src/cpp/cpp_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,30 @@ $friendLines
''');
}

@override
void writeConstants(
InternalCppOptions generatorOptions,
Root root,
Indent indent, {
required String dartPackageName,
}) {
if (root.constants.isEmpty) {
return;
}
indent.newln();
for (final Constant constant in root.constants) {
addDocumentationComments(indent, constant.documentationComments, _docCommentSpec);
final String type = constant.type.baseName;
if (type == 'String') {
final String escaped = escapeStringDoubleQuotes(constant.value.toString());
indent.writeln('inline constexpr const char* ${constant.name} = "$escaped";');
} else {
final String cppType = _baseCppTypeForBuiltinDartType(constant.type) ?? 'auto';
indent.writeln('inline constexpr $cppType ${constant.name} = ${constant.value};');
}
}
}

@override
void writeCloseNamespace(
InternalCppOptions generatorOptions,
Expand Down
27 changes: 27 additions & 0 deletions packages/pigeon/lib/src/dart/dart_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,33 @@ class DartGenerator extends StructuredGenerator<InternalDartOptions> {
indent.writeln("import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;");
}

@override
void writeConstants(
InternalDartOptions generatorOptions,
Root root,
Indent indent, {
required String dartPackageName,
}) {
if (root.constants.isEmpty) {
return;
}
indent.newln();
for (final Constant constant in root.constants) {
addDocumentationComments(indent, constant.documentationComments, docCommentSpec);
final String formattedValue = _formatValue(constant.type.baseName, constant.value);
indent.writeln('const ${constant.type.baseName} ${constant.name} = $formattedValue;');
}
}

String _formatValue(String type, Object value) {
if (type == 'String') {
final String escaped = escapeStringSingleQuotes(value.toString());
return "'$escaped'";
} else {
return value.toString();
}
Comment thread
tarrinneal marked this conversation as resolved.
Resolved
}

@override
void writeEnum(
InternalDartOptions generatorOptions,
Expand Down
12 changes: 12 additions & 0 deletions packages/pigeon/lib/src/generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ abstract class StructuredGenerator<T extends InternalOptions> extends Generator<

writeOpenNamespace(generatorOptions, root, indent, dartPackageName: dartPackageName);

writeConstants(generatorOptions, root, indent, dartPackageName: dartPackageName);

writeGeneralUtilities(generatorOptions, root, indent, dartPackageName: dartPackageName);

if (root.apis.any((Api api) => api is AstProxyApi)) {
Expand Down Expand Up @@ -106,6 +108,16 @@ abstract class StructuredGenerator<T extends InternalOptions> extends Generator<
required String dartPackageName,
}) {}

/// Writes all constants to [indent].
///
/// This method is not required, and does not need to be overridden.
void writeConstants(
T generatorOptions,
Root root,
Indent indent, {
required String dartPackageName,
}) {}

/// Writes all enums to [indent].
///
/// Can be overridden to add extra code before/after enums.
Expand Down
22 changes: 21 additions & 1 deletion packages/pigeon/lib/src/generator_tools.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import 'generator.dart';
/// The current version of pigeon.
///
/// This must match the version in pubspec.yaml.
const String pigeonVersion = '27.1.0';
const String pigeonVersion = '27.2.0';

/// Default plugin package name.
const String defaultPluginPackageName = 'dev.flutter.pigeon';
Expand Down Expand Up @@ -871,3 +871,23 @@ bool isCollectionType(TypeDeclaration type) {
!type.isProxyApi &&
(type.baseName.contains('List') || type.baseName == 'Map');
}

/// Escapes special characters in a string for use in double-quoted string literals.
String escapeStringDoubleQuotes(String value) {
return value
.replaceAll('\\', r'\\') // ignore: use_raw_strings
Comment thread
tarrinneal marked this conversation as resolved.
Resolved
.replaceAll('"', r'\"')
.replaceAll('\n', r'\n')
.replaceAll('\r', r'\r')
.replaceAll(r'$', r'\$');
}
Comment thread
tarrinneal marked this conversation as resolved.
Resolved

/// Escapes special characters in a string for use in single-quoted string literals.
String escapeStringSingleQuotes(String value) {
return value
.replaceAll('\\', r'\\') // ignore: use_raw_strings
.replaceAll("'", r"\'")
.replaceAll('\n', r'\n')
.replaceAll('\r', r'\r')
.replaceAll(r'$', r'\$');
}
30 changes: 30 additions & 0 deletions packages/pigeon/lib/src/gobject/gobject_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,36 @@ class GObjectHeaderGenerator extends StructuredGenerator<InternalGObjectOptions>
indent.writeln('G_BEGIN_DECLS');
}

@override
void writeConstants(
InternalGObjectOptions generatorOptions,
Root root,
Indent indent, {
required String dartPackageName,
}) {
if (root.constants.isEmpty) {
return;
}
indent.newln();
final String module = _getModule(generatorOptions, dartPackageName);
final String moduleScreaming = toScreamingSnakeCase(module);
for (final Constant constant in root.constants) {
addDocumentationComments(indent, constant.documentationComments, _docCommentSpec);
final String constantName = toScreamingSnakeCase(constant.name);
final String type = constant.type.baseName;
final String valueStr;
if (type == 'String') {
final String escaped = escapeStringDoubleQuotes(constant.value.toString());
valueStr = '"$escaped"';
} else if (type == 'bool') {
valueStr = (constant.value as bool) ? 'TRUE' : 'FALSE';
} else {
valueStr = constant.value.toString();
}
indent.writeln('#define ${moduleScreaming}_$constantName $valueStr');
}
}

@override
void writeEnum(
InternalGObjectOptions generatorOptions,
Expand Down
37 changes: 34 additions & 3 deletions packages/pigeon/lib/src/java/java_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,36 @@ class JavaGenerator extends StructuredGenerator<InternalJavaOptions> {
}
indent.writeln('public class ${generatorOptions.className!} {');
indent.inc();
_writeNumberHelpers(indent);
_writeDeepEquals(indent);
_writeDeepHashCode(indent);
}

@override
void writeConstants(
InternalJavaOptions generatorOptions,
Root root,
Indent indent, {
required String dartPackageName,
}) {
if (root.constants.isEmpty) {
return;
}
indent.newln();
for (final Constant constant in root.constants) {
addDocumentationComments(indent, constant.documentationComments, _docCommentSpec);
final String javaType = _javaTypeForBuiltinDartType(constant.type) ?? 'Object';
final String formattedValue = _formatJavaValue(constant.type.baseName, constant.value);
indent.writeln('public static final $javaType ${constant.name} = $formattedValue;');
}
Comment thread
tarrinneal marked this conversation as resolved.
Resolved
}

String _formatJavaValue(String type, Object value) {
if (type == 'String') {
final String escaped = escapeStringDoubleQuotes(value.toString());
return '"$escaped"';
} else if (type == 'int') {
return '${value}L';
} else {
return value.toString();
}
Comment thread
tarrinneal marked this conversation as resolved.
Resolved
}

@override
Expand Down Expand Up @@ -1322,6 +1349,10 @@ protected static ArrayList<Object> wrapError(@NonNull Throwable exception) {
Indent indent, {
required String dartPackageName,
}) {
indent.newln();
_writeNumberHelpers(indent);
_writeDeepEquals(indent);
_writeDeepHashCode(indent);
indent.newln();
_writeErrorClass(indent);
if (root.containsHostApi) {
Expand Down
30 changes: 30 additions & 0 deletions packages/pigeon/lib/src/kotlin/kotlin_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,36 @@ class KotlinGenerator extends StructuredGenerator<InternalKotlinOptions> {
}
}

@override
void writeConstants(
InternalKotlinOptions generatorOptions,
Root root,
Indent indent, {
required String dartPackageName,
}) {
if (root.constants.isEmpty) {
return;
}
indent.newln();
for (final Constant constant in root.constants) {
addDocumentationComments(indent, constant.documentationComments, _docCommentSpec);
final String kotlinType = _kotlinTypeForBuiltinDartType(constant.type) ?? 'Any';
final String formattedValue = _formatKotlinValue(constant.type.baseName, constant.value);
indent.writeln('const val ${constant.name}: $kotlinType = $formattedValue');
}
}

String _formatKotlinValue(String type, Object value) {
if (type == 'String') {
final String escaped = escapeStringDoubleQuotes(value.toString());
return '"$escaped"';
} else if (type == 'int') {
return '${value}L';
} else {
return value.toString();
}
Comment thread
tarrinneal marked this conversation as resolved.
Resolved
}
Comment thread
tarrinneal marked this conversation as resolved.
Resolved

@override
void writeEnum(
InternalKotlinOptions generatorOptions,
Expand Down
Loading
Loading