[pigeon] added AST validation phase for generators (#1548)
* [pigeon] added AST validation phase for generators
* added negative test
* updated docstring
diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index d3d130a..0035248 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 3.0.3
+
+* Adds ability for generators to do AST validation. This can help generators
+ without complete implementations to report gaps in coverage.
+
## 3.0.2
* Fixes non-nullable classes and enums as fields.
diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart
index a22c7e4..5fbb29b 100644
--- a/packages/pigeon/lib/generator_tools.dart
+++ b/packages/pigeon/lib/generator_tools.dart
@@ -7,8 +7,8 @@
import 'dart:mirrors';
import 'ast.dart';
-/// The current version of pigeon. This must match the version in pubspec.yaml.\
-const String pigeonVersion = '3.0.2';
+/// The current version of pigeon. This must match the version in pubspec.yaml.
+const String pigeonVersion = '3.0.3';
/// Read all the content from [stdin] to a String.
String readStdin() {
diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart
index 0b5e75e..aea6ef7 100644
--- a/packages/pigeon/lib/pigeon_lib.dart
+++ b/packages/pigeon/lib/pigeon_lib.dart
@@ -314,6 +314,10 @@
/// Write the generated code described in [root] to [sink] using the
/// [options].
void generate(StringSink sink, PigeonOptions options, Root root);
+
+ /// Generates errors that would only be appropriate for this [Generator]. For
+ /// example, maybe a certain feature isn't implemented in a [Generator] yet.
+ List<Error> validate(PigeonOptions options, Root root);
}
DartOptions _dartOptionsWithCopyrightHeader(
@@ -336,6 +340,9 @@
@override
IOSink? shouldGenerate(PigeonOptions options) => _openSink(options.astOut);
+
+ @override
+ List<Error> validate(PigeonOptions options, Root root) => <Error>[];
}
/// A [Generator] that generates Dart source code.
@@ -352,6 +359,9 @@
@override
IOSink? shouldGenerate(PigeonOptions options) => _openSink(options.dartOut);
+
+ @override
+ List<Error> validate(PigeonOptions options, Root root) => <Error>[];
}
/// A [Generator] that generates Dart test source code.
@@ -383,6 +393,9 @@
return null;
}
}
+
+ @override
+ List<Error> validate(PigeonOptions options, Root root) => <Error>[];
}
/// A [Generator] that generates Objective-C header code.
@@ -403,6 +416,9 @@
@override
IOSink? shouldGenerate(PigeonOptions options) =>
_openSink(options.objcHeaderOut);
+
+ @override
+ List<Error> validate(PigeonOptions options, Root root) => <Error>[];
}
/// A [Generator] that generates Objective-C source code.
@@ -423,6 +439,9 @@
@override
IOSink? shouldGenerate(PigeonOptions options) =>
_openSink(options.objcSourceOut);
+
+ @override
+ List<Error> validate(PigeonOptions options, Root root) => <Error>[];
}
/// A [Generator] that generates Java source code.
@@ -444,6 +463,9 @@
@override
IOSink? shouldGenerate(PigeonOptions options) => _openSink(options.javaOut);
+
+ @override
+ List<Error> validate(PigeonOptions options, Root root) => <Error>[];
}
dart_ast.Annotation? _findMetadata(
@@ -1136,16 +1158,25 @@
final ParseResults parseResults =
pigeon.parseFile(options.input!, sdkPath: sdkPath);
- if (parseResults.errors.isNotEmpty) {
- final List<Error> errors = <Error>[];
- for (final Error err in parseResults.errors) {
- errors.add(Error(
- message: err.message,
- filename: options.input,
- lineNumber: err.lineNumber));
- }
+ final List<Error> errors = <Error>[];
+ errors.addAll(parseResults.errors);
- printErrors(errors);
+ for (final Generator generator in safeGenerators) {
+ final IOSink? sink = generator.shouldGenerate(options);
+ if (sink != null) {
+ final List<Error> generatorErrors =
+ generator.validate(options, parseResults.root);
+ errors.addAll(generatorErrors);
+ }
+ }
+
+ if (errors.isNotEmpty) {
+ printErrors(errors
+ .map((Error err) => Error(
+ message: err.message,
+ filename: options.input,
+ lineNumber: err.lineNumber))
+ .toList());
return 1;
}
diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml
index a08046a..6b884e2 100644
--- a/packages/pigeon/pubspec.yaml
+++ b/packages/pigeon/pubspec.yaml
@@ -2,7 +2,7 @@
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: 3.0.2 # This must match the version in lib/generator_tools.dart
+version: 3.0.3 # This must match the version in lib/generator_tools.dart
environment:
sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart
index d40c015..9fa597a 100644
--- a/packages/pigeon/test/pigeon_lib_test.dart
+++ b/packages/pigeon/test/pigeon_lib_test.dart
@@ -2,12 +2,34 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:async';
import 'dart:io';
import 'package:pigeon/ast.dart';
import 'package:pigeon/pigeon_lib.dart';
import 'package:test/test.dart';
+class _ValidatorGenerator implements Generator {
+ _ValidatorGenerator(this.sink);
+ bool didCallValidate = false;
+
+ final IOSink? sink;
+
+ @override
+ void generate(StringSink sink, PigeonOptions options, Root root) {}
+
+ @override
+ IOSink? shouldGenerate(PigeonOptions options) => sink;
+
+ @override
+ List<Error> validate(PigeonOptions options, Root root) {
+ didCallValidate = true;
+ return <Error>[
+ Error(message: '_ValidatorGenerator'),
+ ];
+ }
+}
+
void main() {
/// Creates a temporary file named [filename] then calls [callback] with a
/// [File] representing that temporary directory. The file will be deleted
@@ -1079,4 +1101,31 @@
expect(results.errors[0].message,
contains('Unsupported TaskQueue specification'));
});
+
+ test('generator validation', () async {
+ final Completer<void> completer = Completer<void>();
+ _withTempFile('foo.dart', (File input) async {
+ final _ValidatorGenerator generator = _ValidatorGenerator(stdout);
+ final int result = await Pigeon.run(<String>['--input', input.path],
+ generators: <Generator>[generator]);
+ expect(generator.didCallValidate, isTrue);
+ expect(result, isNot(0));
+ completer.complete();
+ });
+ await completer.future;
+ });
+
+ test('generator validation skipped', () async {
+ final Completer<void> completer = Completer<void>();
+ _withTempFile('foo.dart', (File input) async {
+ final _ValidatorGenerator generator = _ValidatorGenerator(null);
+ final int result = await Pigeon.run(
+ <String>['--input', input.path, '--dart_out', 'foo.dart'],
+ generators: <Generator>[generator]);
+ expect(generator.didCallValidate, isFalse);
+ expect(result, equals(0));
+ completer.complete();
+ });
+ await completer.future;
+ });
}