Add option for Dart protobuf to emit Kythe metdata. (#204)
* Make Dart protobuf emite Kythe metdata.
* Fix Travis tests with proper names Set.
* Fix other Travis tests, update CHANGELOG and pubspec.
* Remove unused _offset from IndentingWriter.
* Fix whitespacing in meta goldens.
* Remove default explicit null value in IndentingWriter.
* Reformat test files to change line wrapping for external paths.
diff --git a/protoc_plugin/CHANGELOG.md b/protoc_plugin/CHANGELOG.md
index b626ac3..4093710 100644
--- a/protoc_plugin/CHANGELOG.md
+++ b/protoc_plugin/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 16.1.0
+
+* Add ability to generate Kythe metadata files via the
+ `generate_kythe_info` option.
+
## 16.0.0
* Breaking change: Remove the '$checkItem' function from generated message classes and use the new method 'pc' on
diff --git a/protoc_plugin/README.md b/protoc_plugin/README.md
index a46ea53..12efc7f 100644
--- a/protoc_plugin/README.md
+++ b/protoc_plugin/README.md
@@ -64,6 +64,15 @@
--dart_out="<option 1>,<option 2>:."
+ ### Generating Code Info
+
+The plugin includes the `generate_kythe_info` option, which, if passed at run
+time, will make the plugin generate metadata files alongside the `.dart` files
+generated for the proto messages and their enums. Pass this along with the other
+dart_out options:
+
+ --dart_out="generate_kythe_info,<other options>:."
+
Using protocol buffer libraries to build new libraries
------------------------------------------------------
diff --git a/protoc_plugin/lib/code_generator.dart b/protoc_plugin/lib/code_generator.dart
index d5080c9..bb7a82d 100644
--- a/protoc_plugin/lib/code_generator.dart
+++ b/protoc_plugin/lib/code_generator.dart
@@ -13,6 +13,14 @@
String get classname;
String get fullName;
+ /// The field path contains the field IDs and indices (for repeated fields)
+ /// that lead to the proto memeber corresponding to a piece of generated code.
+ /// Repeated fields in the descriptor are further identified by the index of
+ /// the message in question.
+ /// For more information see
+ /// https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/descriptor.proto#L728
+ List<int> get fieldPath;
+
/// The fully qualified name with a leading '.'.
///
/// This exists because names from protoc come like this.
@@ -98,4 +106,5 @@
String get classname => null;
String get fullName => '';
get fileGen => null;
+ List<int> get fieldPath => [];
}
diff --git a/protoc_plugin/lib/enum_generator.dart b/protoc_plugin/lib/enum_generator.dart
index f441cd6..06958bd 100644
--- a/protoc_plugin/lib/enum_generator.dart
+++ b/protoc_plugin/lib/enum_generator.dart
@@ -17,15 +17,24 @@
final EnumDescriptorProto _descriptor;
final List<EnumValueDescriptorProto> _canonicalValues =
<EnumValueDescriptorProto>[];
+ final List<int> _originalCanonicalIndices = <int>[];
final List<EnumAlias> _aliases = <EnumAlias>[];
/// Maps the name of an enum value to the Dart name we will use for it.
final Map<String, String> dartNames = <String, String>{};
+ final List<int> _originalAliasIndices = <int>[];
+ List<int> _fieldPath;
+ final List<int> _fieldPathSegment;
- EnumGenerator(EnumDescriptorProto descriptor, ProtobufContainer parent,
- Set<String> usedClassNames)
+ /// See [[ProtobufContainer]
+ List<int> get fieldPath =>
+ _fieldPath ??= List.from(_parent.fieldPath)..addAll(_fieldPathSegment);
+
+ EnumGenerator._(EnumDescriptorProto descriptor, ProtobufContainer parent,
+ Set<String> usedClassNames, int repeatedFieldIndex, int fieldIdTag)
: assert(parent != null),
_parent = parent,
+ _fieldPathSegment = [fieldIdTag, repeatedFieldIndex],
classname = messageOrEnumClassName(descriptor.name, usedClassNames,
parent: parent?.classname ?? ''),
fullName = parent.fullName == ''
@@ -33,19 +42,38 @@
: '${parent.fullName}.${descriptor.name}',
_descriptor = descriptor {
final usedNames = reservedEnumNames;
- for (EnumValueDescriptorProto value in descriptor.value) {
+ for (var i = 0; i < descriptor.value.length; i++) {
+ EnumValueDescriptorProto value = descriptor.value[i];
EnumValueDescriptorProto canonicalValue =
descriptor.value.firstWhere((v) => v.number == value.number);
if (value == canonicalValue) {
_canonicalValues.add(value);
+ _originalCanonicalIndices.add(i);
} else {
_aliases.add(new EnumAlias(value, canonicalValue));
+ _originalAliasIndices.add(i);
}
dartNames[value.name] = disambiguateName(
avoidInitialUnderscore(value.name), usedNames, enumSuffixes());
}
}
+ static const _topLevelFieldTag = 5;
+ static const _nestedFieldTag = 4;
+
+ EnumGenerator.topLevel(
+ EnumDescriptorProto descriptor,
+ ProtobufContainer parent,
+ Set<String> usedClassNames,
+ int repeatedFieldIndex)
+ : this._(descriptor, parent, usedClassNames, repeatedFieldIndex,
+ _topLevelFieldTag);
+
+ EnumGenerator.nested(EnumDescriptorProto descriptor, ProtobufContainer parent,
+ Set<String> usedClassNames, int repeatedFieldIndex)
+ : this._(descriptor, parent, usedClassNames, repeatedFieldIndex,
+ _nestedFieldTag);
+
String get package => _parent.package;
FileGenerator get fileGen => _parent.fileGen;
@@ -64,23 +92,58 @@
return "$fileImportPrefix.$name";
}
+ static const int _enumNameTag = 1;
+ static const int _enumValueTag = 2;
+ static const int _enumValueNameTag = 1;
+
void generate(IndentingWriter out) {
- out.addBlock(
+ out.addAnnotatedBlock(
'class ${classname} extends $_protobufImportPrefix.ProtobufEnum {',
- '}\n', () {
+ '}\n', [
+ new NamedLocation(
+ name: classname,
+ fieldPathSegment: new List.from(fieldPath)..add(_enumNameTag),
+ start: 'class '.length)
+ ], () {
// -----------------------------------------------------------------
// Define enum types.
- for (EnumValueDescriptorProto val in _canonicalValues) {
+ for (var i = 0; i < _canonicalValues.length; i++) {
+ EnumValueDescriptorProto val = _canonicalValues[i];
final name = dartNames[val.name];
- out.println('static const ${classname} $name = '
- "const ${classname}._(${val.number}, ${singleQuote(name)});");
+ out.printlnAnnotated(
+ 'static const ${classname} $name = '
+ "const ${classname}._(${val.number}, ${singleQuote(name)});",
+ [
+ new NamedLocation(
+ name: name,
+ fieldPathSegment: new List.from(fieldPath)
+ ..addAll([
+ _enumValueTag,
+ _originalCanonicalIndices[i],
+ _enumValueNameTag
+ ]),
+ start: 'static const ${classname} '.length)
+ ]);
}
if (_aliases.isNotEmpty) {
out.println();
- for (EnumAlias alias in _aliases) {
+ for (var i = 0; i < _aliases.length; i++) {
+ EnumAlias alias = _aliases[i];
final name = dartNames[alias.value.name];
- out.println('static const ${classname} $name ='
- ' ${dartNames[alias.canonicalValue.name]};');
+ out.printlnAnnotated(
+ 'static const ${classname} $name ='
+ ' ${dartNames[alias.canonicalValue.name]};',
+ [
+ new NamedLocation(
+ name: name,
+ fieldPathSegment: new List.from(fieldPath)
+ ..addAll([
+ _enumValueTag,
+ _originalAliasIndices[i],
+ _enumValueNameTag
+ ]),
+ start: 'static const ${classname} '.length)
+ ]);
}
}
out.println();
diff --git a/protoc_plugin/lib/extension_generator.dart b/protoc_plugin/lib/extension_generator.dart
index e1b6e02..2a53f44 100644
--- a/protoc_plugin/lib/extension_generator.dart
+++ b/protoc_plugin/lib/extension_generator.dart
@@ -12,9 +12,29 @@
ProtobufField _field;
final String _extensionName;
String _extendedFullName = "";
+ List<int> _fieldPath;
+ final List<int> _fieldPathSegment;
- ExtensionGenerator(this._descriptor, this._parent, Set<String> usedNames)
- : _extensionName = extensionName(_descriptor, usedNames);
+ /// See [[ProtobufContainer]
+ List<int> get fieldPath =>
+ _fieldPath ??= List.from(_parent.fieldPath)..addAll(_fieldPathSegment);
+
+ ExtensionGenerator._(this._descriptor, this._parent, Set<String> usedNames,
+ int repeatedFieldIndex, int fieldIdTag)
+ : _extensionName = extensionName(_descriptor, usedNames),
+ _fieldPathSegment = [fieldIdTag, repeatedFieldIndex];
+
+ static const _topLevelFieldTag = 7;
+ static const _nestedFieldTag = 6;
+
+ ExtensionGenerator.topLevel(FieldDescriptorProto descriptor,
+ ProtobufContainer parent, Set<String> usedNames, int repeatedFieldIndex)
+ : this._(descriptor, parent, usedNames, repeatedFieldIndex,
+ _topLevelFieldTag);
+ ExtensionGenerator.nested(FieldDescriptorProto descriptor,
+ ProtobufContainer parent, Set<String> usedNames, int repeatedFieldIndex)
+ : this._(
+ descriptor, parent, usedNames, repeatedFieldIndex, _nestedFieldTag);
void resolve(GenerationContext ctx) {
_field = new ProtobufField.extension(_descriptor, _parent, ctx);
@@ -79,9 +99,16 @@
var dartType = type.getDartType(fileGen);
if (_field.isRepeated) {
- out.print('static final $_protobufImportPrefix.Extension $name = '
+ out.printAnnotated(
+ 'static final $_protobufImportPrefix.Extension $name = '
'new $_protobufImportPrefix.Extension<$dartType>.repeated(\'$_extendedFullName\','
- ' \'$name\', ${_field.number}, ${_field.typeConstant}');
+ ' \'$name\', ${_field.number}, ${_field.typeConstant}',
+ [
+ new NamedLocation(
+ name: name,
+ fieldPathSegment: new List.from(fieldPath)..add(1),
+ start: 'static final $_protobufImportPrefix.Extension '.length)
+ ]);
if (type.isMessage || type.isGroup) {
out.println(
', $_protobufImportPrefix.getCheckFunction(${_field.typeConstant}), $dartType.create);');
@@ -96,9 +123,16 @@
return;
}
- out.print('static final $_protobufImportPrefix.Extension $name = '
+ out.printAnnotated(
+ 'static final $_protobufImportPrefix.Extension $name = '
'new $_protobufImportPrefix.Extension<$dartType>(\'$_extendedFullName\', \'$name\', '
- '${_field.number}, ${_field.typeConstant}');
+ '${_field.number}, ${_field.typeConstant}',
+ [
+ new NamedLocation(
+ name: name,
+ fieldPathSegment: new List.from(fieldPath)..add(1),
+ start: 'static final $_protobufImportPrefix.Extension '.length)
+ ]);
String initializer = _field.generateDefaultFunction(fileGen);
diff --git a/protoc_plugin/lib/file_generator.dart b/protoc_plugin/lib/file_generator.dart
index 845966b..a4225a1 100644
--- a/protoc_plugin/lib/file_generator.dart
+++ b/protoc_plugin/lib/file_generator.dart
@@ -145,16 +145,22 @@
}
// Load and register all enum and message types.
- for (EnumDescriptorProto enumType in descriptor.enumType) {
- enumGenerators.add(new EnumGenerator(enumType, this, usedTopLevelNames));
+ for (var i = 0; i < descriptor.enumType.length; i++) {
+ enumGenerators.add(new EnumGenerator.topLevel(
+ descriptor.enumType[i], this, usedTopLevelNames, i));
}
- for (DescriptorProto messageType in descriptor.messageType) {
- messageGenerators.add(new MessageGenerator(
- messageType, this, declaredMixins, defaultMixin, usedTopLevelNames));
+ for (var i = 0; i < descriptor.messageType.length; i++) {
+ messageGenerators.add(new MessageGenerator.topLevel(
+ descriptor.messageType[i],
+ this,
+ declaredMixins,
+ defaultMixin,
+ usedTopLevelNames,
+ i));
}
- for (FieldDescriptorProto extension in descriptor.extension) {
- extensionGenerators
- .add(new ExtensionGenerator(extension, this, usedExtensionNames));
+ for (var i = 0; i < descriptor.extension.length; i++) {
+ extensionGenerators.add(new ExtensionGenerator.topLevel(
+ descriptor.extension[i], this, usedTopLevelNames, i));
}
for (ServiceDescriptorProto service in descriptor.service) {
if (options.useGrpc) {
@@ -188,6 +194,7 @@
String get classname => '';
String get fullName => descriptor.package;
FileGenerator get fileGen => this;
+ List<int> get fieldPath => [];
/// Generates all the Dart files for this .proto file.
List<CodeGeneratorResponse_File> generateFiles(OutputConfiguration config) {
@@ -201,11 +208,27 @@
..content = content;
}
+ IndentingWriter mainWriter = generateMainFile(config);
+ IndentingWriter enumWriter = generateEnumFile(config);
+
final files = [
- makeFile(".pb.dart", generateMainFile(config)),
- makeFile(".pbenum.dart", generateEnumFile(config)),
+ makeFile(".pb.dart", mainWriter.toString()),
+ makeFile(".pbenum.dart", enumWriter.toString()),
makeFile(".pbjson.dart", generateJsonFile(config)),
];
+
+ if (options.generateMetadata) {
+ files.addAll([
+ makeFile(
+ ".pb.dart.meta",
+ new String.fromCharCodes(
+ mainWriter.sourceLocationInfo.writeToBuffer())),
+ makeFile(
+ ".pbenum.dart.meta",
+ new String.fromCharCodes(
+ enumWriter.sourceLocationInfo.writeToBuffer()))
+ ]);
+ }
if (options.useGrpc) {
if (grpcGenerators.isNotEmpty) {
files.add(makeFile(".pbgrpc.dart", generateGrpcFile(config)));
@@ -216,11 +239,15 @@
return files;
}
+ /// Creates an IndentingWriter with metadata generation enabled or disabled.
+ IndentingWriter makeWriter() => new IndentingWriter(
+ filename: options.generateMetadata ? descriptor.name : null);
+
/// Returns the contents of the .pb.dart file for this .proto file.
- String generateMainFile(
+ IndentingWriter generateMainFile(
[OutputConfiguration config = const DefaultOutputConfiguration()]) {
if (!_linked) throw new StateError("not linked");
- IndentingWriter out = new IndentingWriter();
+ IndentingWriter out = makeWriter();
writeMainHeader(out, config);
@@ -251,7 +278,7 @@
for (ClientApiGenerator c in clientApiGenerators) {
c.generate(out);
}
- return out.toString();
+ return out;
}
/// Writes the header and imports for the .pb.dart file.
@@ -370,11 +397,11 @@
}
/// Returns the contents of the .pbenum.dart file for this .proto file.
- String generateEnumFile(
+ IndentingWriter generateEnumFile(
[OutputConfiguration config = const DefaultOutputConfiguration()]) {
if (!_linked) throw new StateError("not linked");
- var out = new IndentingWriter();
+ var out = makeWriter();
_writeHeading(out);
if (enumCount > 0) {
@@ -394,7 +421,7 @@
m.generateEnums(out);
}
- return out.toString();
+ return out;
}
/// Returns the number of enum types generated in the .pbenum.dart file.
@@ -410,7 +437,7 @@
String generateServerFile(
[OutputConfiguration config = const DefaultOutputConfiguration()]) {
if (!_linked) throw new StateError("not linked");
- var out = new IndentingWriter();
+ var out = makeWriter();
_writeHeading(out);
if (serviceGenerators.isNotEmpty) {
@@ -450,7 +477,7 @@
String generateGrpcFile(
[OutputConfiguration config = const DefaultOutputConfiguration()]) {
if (!_linked) throw new StateError("not linked");
- var out = new IndentingWriter();
+ var out = makeWriter();
_writeHeading(out);
out.println(_asyncImport);
@@ -482,7 +509,7 @@
String generateJsonFile(
[OutputConfiguration config = const DefaultOutputConfiguration()]) {
if (!_linked) throw new StateError("not linked");
- var out = new IndentingWriter();
+ var out = makeWriter();
_writeHeading(out);
// Import the .pbjson.dart files we depend on.
diff --git a/protoc_plugin/lib/indenting_writer.dart b/protoc_plugin/lib/indenting_writer.dart
index c71889a..86ae2e8 100644
--- a/protoc_plugin/lib/indenting_writer.dart
+++ b/protoc_plugin/lib/indenting_writer.dart
@@ -4,15 +4,34 @@
library protoc.indenting_writer;
+import 'src/descriptor.pb.dart';
+
+/// Specifies code locations where metadata annotations should be attached and
+/// where they should point to in the original proto.
+class NamedLocation {
+ final String name;
+ final List<int> fieldPathSegment;
+ final int start;
+ NamedLocation({this.name, this.fieldPathSegment, this.start});
+}
+
/// A buffer for writing indented source code.
class IndentingWriter {
final StringBuffer _buffer = new StringBuffer();
+ final GeneratedCodeInfo sourceLocationInfo = new GeneratedCodeInfo();
String _indent = "";
bool _needIndent = true;
+ // After writing any chunk, _previousOffset is the size of everything that was
+ // written to the buffer before the latest call to print or addBlock.
+ int _previousOffset = 0;
+ final String _sourceFile;
+
+ IndentingWriter({String filename}) : _sourceFile = filename;
/// Appends a string indented to the current level.
/// (Indentation will be added after newline characters where needed.)
void print(String text) {
+ _previousOffset = _buffer.length;
var lastNewline = text.lastIndexOf('\n');
if (lastNewline == -1) {
_writeChunk(text);
@@ -32,22 +51,41 @@
_newline();
}
+ void printAnnotated(String text, List<NamedLocation> namedLocations) {
+ print(text);
+ for (final location in namedLocations) {
+ addAnnotation(location.fieldPathSegment, location.name, location.start);
+ }
+ }
+
+ void printlnAnnotated(String text, List<NamedLocation> namedLocations) {
+ printAnnotated(text, namedLocations);
+ _newline();
+ }
+
/// Prints a block of text with the body indented one more level.
void addBlock(String start, String end, void body(),
{endWithNewline = true}) {
- _addBlock(start, end, body, endWithNewline, _indent + ' ');
+ println(start);
+ _addBlockBodyAndEnd(end, body, endWithNewline, _indent + ' ');
}
/// Prints a block of text with an unindented body.
/// (For example, for triple quotes.)
void addUnindentedBlock(String start, String end, void body(),
{endWithNewline = true}) {
- _addBlock(start, end, body, endWithNewline, '');
+ println(start);
+ _addBlockBodyAndEnd(end, body, endWithNewline, '');
}
- void _addBlock(
- String start, String end, void body(), endWithNewline, newIndent) {
- println(start);
+ void addAnnotatedBlock(
+ String start, String end, List<NamedLocation> namedLocations, void body(),
+ {endWithNewline = true}) {
+ printlnAnnotated(start, namedLocations);
+ _addBlockBodyAndEnd(end, body, endWithNewline, _indent + ' ');
+ }
+
+ void _addBlockBodyAndEnd(String end, void body(), endWithNewline, newIndent) {
var oldIndent = _indent;
_indent = newIndent;
body();
@@ -78,4 +116,20 @@
_buffer.writeln();
_needIndent = true;
}
+
+ /// Creates an annotation, given the starting offset and ending offset.
+ /// [start] should be the location of the identifier as it appears in the
+ /// string that was passed to the previous [print]. Name should be the string
+ /// that was written to file.
+ void addAnnotation(List<int> fieldPath, String name, int start) {
+ if (_sourceFile == null) {
+ return;
+ }
+ var annotation = new GeneratedCodeInfo_Annotation()
+ ..path.addAll(fieldPath)
+ ..sourceFile = _sourceFile
+ ..begin = _previousOffset + start
+ ..end = _previousOffset + start + name.length;
+ sourceLocationInfo.annotation.add(annotation);
+ }
}
diff --git a/protoc_plugin/lib/message_generator.dart b/protoc_plugin/lib/message_generator.dart
index 7e3f55e..d6d51ae 100644
--- a/protoc_plugin/lib/message_generator.dart
+++ b/protoc_plugin/lib/message_generator.dart
@@ -78,19 +78,29 @@
final List<List<ProtobufField>> _oneofFields;
List<OneofNames> _oneofNames;
+ List<int> _fieldPath;
+ final List<int> _fieldPathSegment;
+
+ /// See [[ProtobufContainer]
+ List<int> get fieldPath =>
+ _fieldPath ??= List.from(_parent.fieldPath)..addAll(_fieldPathSegment);
+
// populated by resolve()
List<ProtobufField> _fieldList;
Set<String> _usedTopLevelNames;
- MessageGenerator(
+ MessageGenerator._(
DescriptorProto descriptor,
ProtobufContainer parent,
Map<String, PbMixin> declaredMixins,
PbMixin defaultMixin,
- this._usedTopLevelNames)
+ this._usedTopLevelNames,
+ int repeatedFieldIndex,
+ int fieldIdTag)
: _descriptor = descriptor,
_parent = parent,
+ _fieldPathSegment = [fieldIdTag, repeatedFieldIndex],
classname = messageOrEnumClassName(descriptor.name, _usedTopLevelNames,
parent: parent?.classname ?? ''),
assert(parent != null),
@@ -101,21 +111,51 @@
defaultMixin),
_oneofFields =
List.generate(descriptor.oneofDecl.length, (int index) => []) {
- for (EnumDescriptorProto e in _descriptor.enumType) {
- _enumGenerators.add(new EnumGenerator(e, this, _usedTopLevelNames));
+ for (var i = 0; i < _descriptor.enumType.length; i++) {
+ EnumDescriptorProto e = _descriptor.enumType[i];
+ _enumGenerators
+ .add(new EnumGenerator.nested(e, this, _usedTopLevelNames, i));
}
- for (DescriptorProto n in _descriptor.nestedType) {
- _messageGenerators.add(new MessageGenerator(
- n, this, declaredMixins, defaultMixin, _usedTopLevelNames));
+ for (var i = 0; i < _descriptor.nestedType.length; i++) {
+ DescriptorProto n = _descriptor.nestedType[i];
+ _messageGenerators.add(new MessageGenerator.nested(
+ n, this, declaredMixins, defaultMixin, _usedTopLevelNames, i));
}
- for (FieldDescriptorProto x in _descriptor.extension) {
+ // Extensions within messages won't create top-level classes and don't need
+ // to check against / be added to top-level reserved names.
+ final usedExtensionNames = Set<String>()..addAll(forbiddenExtensionNames);
+ for (var i = 0; i < _descriptor.extension.length; i++) {
+ FieldDescriptorProto x = _descriptor.extension[i];
_extensionGenerators
- .add(new ExtensionGenerator(x, this, _usedTopLevelNames));
+ .add(new ExtensionGenerator.nested(x, this, usedExtensionNames, i));
}
}
+ static const _topLevelFieldTag = 4;
+ static const _nestedFieldTag = 3;
+
+ MessageGenerator.topLevel(
+ DescriptorProto descriptor,
+ ProtobufContainer parent,
+ Map<String, PbMixin> declaredMixins,
+ PbMixin defaultMixin,
+ Set<String> usedNames,
+ int repeatedFieldIndex)
+ : this._(descriptor, parent, declaredMixins, defaultMixin, usedNames,
+ repeatedFieldIndex, _topLevelFieldTag);
+
+ MessageGenerator.nested(
+ DescriptorProto descriptor,
+ ProtobufContainer parent,
+ Map<String, PbMixin> declaredMixins,
+ PbMixin defaultMixin,
+ Set<String> usedNames,
+ int repeatedFieldIndex)
+ : this._(descriptor, parent, declaredMixins, defaultMixin, usedNames,
+ repeatedFieldIndex, _nestedFieldTag);
+
String get package => _parent.package;
/// The generator of the .pb.dart file that will declare this type.
@@ -270,9 +310,14 @@
String packageClause = package == ''
? ''
: ', package: const $_protobufImportPrefix.PackageName(\'$package\')';
- out.addBlock(
+ out.addAnnotatedBlock(
'class ${classname} extends $_protobufImportPrefix.GeneratedMessage${mixinClause} {',
- '}', () {
+ '}', [
+ new NamedLocation(
+ name: classname,
+ fieldPathSegment: new List.from(fieldPath)..addAll([1]),
+ start: 'class '.length)
+ ], () {
for (OneofNames oneof in _oneofNames) {
out.addBlock(
'static const Map<int, ${oneof.oneofEnumName}> ${oneof.byTagMapName} = {',
diff --git a/protoc_plugin/lib/options.dart b/protoc_plugin/lib/options.dart
index c8fc4a6..9b61b7c 100644
--- a/protoc_plugin/lib/options.dart
+++ b/protoc_plugin/lib/options.dart
@@ -47,8 +47,9 @@
/// Options expected by the protoc code generation compiler.
class GenerationOptions {
final bool useGrpc;
+ final bool generateMetadata;
- GenerationOptions({this.useGrpc = false});
+ GenerationOptions({this.useGrpc = false, this.generateMetadata = false});
}
/// A parser for a name-value pair option. Options parsed in
@@ -75,6 +76,19 @@
}
}
+class GenerateMetadataParser implements SingleOptionParser {
+ bool generateKytheInfo = false;
+
+ @override
+ void parse(String name, String value, onError(String details)) {
+ if (value != null) {
+ onError('Invalid metadata option. No Value expected.');
+ return;
+ }
+ generateKytheInfo = true;
+ }
+}
+
/// Parser used by the compiler, which supports the `rpc` option (see
/// [RpcOptionParser]) and any additional option added in [parsers]. If
/// [parsers] has a key for `rpc`, it will be ignored.
@@ -86,9 +100,13 @@
final grpcOptionParser = new GrpcOptionParser();
newParsers['grpc'] = grpcOptionParser;
+ final generateMetadataParser = new GenerateMetadataParser();
+ newParsers['generate_kythe_info'] = generateMetadataParser;
if (genericOptionsParser(request, response, newParsers)) {
- return new GenerationOptions(useGrpc: grpcOptionParser.grpcEnabled);
+ return new GenerationOptions(
+ useGrpc: grpcOptionParser.grpcEnabled,
+ generateMetadata: generateMetadataParser.generateKytheInfo);
}
return null;
}
diff --git a/protoc_plugin/lib/protoc.dart b/protoc_plugin/lib/protoc.dart
index 6a1b390..50e004f 100644
--- a/protoc_plugin/lib/protoc.dart
+++ b/protoc_plugin/lib/protoc.dart
@@ -13,7 +13,7 @@
import 'src/plugin.pb.dart';
import 'const_generator.dart' show writeJsonConst;
-import 'indenting_writer.dart' show IndentingWriter;
+import 'indenting_writer.dart';
import 'names.dart';
part 'base_type.dart';
diff --git a/protoc_plugin/pubspec.yaml b/protoc_plugin/pubspec.yaml
index 9de08be..28a79e9 100644
--- a/protoc_plugin/pubspec.yaml
+++ b/protoc_plugin/pubspec.yaml
@@ -1,5 +1,5 @@
name: protoc_plugin
-version: 16.0.0
+version: 16.1.0
author: Dart Team <misc@dartlang.org>
description: Protoc compiler plugin to generate Dart code
homepage: https://github.com/dart-lang/protobuf
diff --git a/protoc_plugin/test/enum_generator_test.dart b/protoc_plugin/test/enum_generator_test.dart
index d1cd2f1..5a1ded3 100755
--- a/protoc_plugin/test/enum_generator_test.dart
+++ b/protoc_plugin/test/enum_generator_test.dart
@@ -30,11 +30,13 @@
..name = 'BUSINESS'
..number = 2
]);
- IndentingWriter writer = new IndentingWriter();
+ IndentingWriter writer = new IndentingWriter(filename: 'sample.proto');
FileGenerator fg =
new FileGenerator(new FileDescriptorProto(), new GenerationOptions());
- EnumGenerator eg = new EnumGenerator(ed, fg, new Set<String>());
+ EnumGenerator eg = new EnumGenerator.topLevel(ed, fg, new Set<String>(), 0);
eg.generate(writer);
expectMatchesGoldenFile(writer.toString(), 'test/goldens/enum');
+ expectMatchesGoldenFile(
+ writer.sourceLocationInfo.toString(), 'test/goldens/enum.meta');
});
}
diff --git a/protoc_plugin/test/file_generator_test.dart b/protoc_plugin/test/file_generator_test.dart
index f26d828..80814fc 100644
--- a/protoc_plugin/test/file_generator_test.dart
+++ b/protoc_plugin/test/file_generator_test.dart
@@ -79,7 +79,20 @@
FileGenerator fg = new FileGenerator(fd, options);
link(options, [fg]);
expectMatchesGoldenFile(
- fg.generateMainFile(), 'test/goldens/oneMessage.pb');
+ fg.generateMainFile().toString(), 'test/goldens/oneMessage.pb');
+ });
+
+ test(
+ 'FileGenerator outputs a .pb.dart.meta file for a proto with one message',
+ () {
+ FileDescriptorProto fd = buildFileDescriptor();
+ var options = parseGenerationOptions(
+ new CodeGeneratorRequest()..parameter = 'generate_kythe_info',
+ new CodeGeneratorResponse());
+ FileGenerator fg = new FileGenerator(fd, options);
+ link(options, [fg]);
+ expectMatchesGoldenFile(fg.generateMainFile().sourceLocationInfo.toString(),
+ 'test/goldens/oneMessage.pb.meta');
});
test('FileGenerator outputs a pbjson.dart file for a proto with one message',
@@ -102,9 +115,24 @@
FileGenerator fg = new FileGenerator(fd, options);
link(options, [fg]);
expectMatchesGoldenFile(
- fg.generateMainFile(), 'test/goldens/topLevelEnum.pb');
+ fg.generateMainFile().toString(), 'test/goldens/topLevelEnum.pb');
expectMatchesGoldenFile(
- fg.generateEnumFile(), 'test/goldens/topLevelEnum.pbenum');
+ fg.generateEnumFile().toString(), 'test/goldens/topLevelEnum.pbenum');
+ });
+
+ test('FileGenerator generates metadata files for a top-level enum', () {
+ FileDescriptorProto fd =
+ buildFileDescriptor(phoneNumber: false, topLevelEnum: true);
+ var options = parseGenerationOptions(
+ new CodeGeneratorRequest()..parameter = 'generate_kythe_info',
+ new CodeGeneratorResponse());
+ FileGenerator fg = new FileGenerator(fd, options);
+ link(options, [fg]);
+
+ expectMatchesGoldenFile(fg.generateMainFile().sourceLocationInfo.toString(),
+ 'test/goldens/topLevelEnum.pb.meta');
+ expectMatchesGoldenFile(fg.generateEnumFile().sourceLocationInfo.toString(),
+ 'test/goldens/topLevelEnum.pbenum.meta');
});
test('FileGenerator generates a .pbjson.dart file for a top-level enum', () {
@@ -128,7 +156,7 @@
FileGenerator fg = new FileGenerator(fd, options);
link(options, [fg]);
- var writer = new IndentingWriter();
+ var writer = new IndentingWriter(filename: '');
fg.writeMainHeader(writer);
expectMatchesGoldenFile(
writer.toString(), 'test/goldens/header_in_package.pb');
@@ -152,7 +180,7 @@
FileGenerator fg = new FileGenerator(fd, options);
link(options, [fg]);
- var writer = new IndentingWriter();
+ var writer = new IndentingWriter(filename: '');
fg.writeMainHeader(writer);
expectMatchesGoldenFile(
writer.toString(), 'test/goldens/header_with_fixnum.pb');
@@ -179,9 +207,10 @@
FileGenerator fg = new FileGenerator(fd, options);
link(options, [fg]);
- var writer = new IndentingWriter();
+ var writer = new IndentingWriter(filename: '');
fg.writeMainHeader(writer);
- expectMatchesGoldenFile(fg.generateMainFile(), 'test/goldens/service.pb');
+ expectMatchesGoldenFile(
+ fg.generateMainFile().toString(), 'test/goldens/service.pb');
expectMatchesGoldenFile(
fg.generateServerFile(), 'test/goldens/service.pbserver');
});
@@ -207,10 +236,10 @@
FileGenerator fg = new FileGenerator(fd, options);
link(options, [fg]);
- var writer = new IndentingWriter();
+ var writer = new IndentingWriter(filename: '');
fg.writeMainHeader(writer);
expectMatchesGoldenFile(
- fg.generateMainFile(), 'test/goldens/grpc_service.pb');
+ fg.generateMainFile().toString(), 'test/goldens/grpc_service.pb');
});
test('FileGenerator outputs gRPC stubs if gRPC is selected', () {
@@ -256,7 +285,7 @@
FileGenerator fg = new FileGenerator(fd, options);
link(options, [fg]);
- var writer = new IndentingWriter();
+ var writer = new IndentingWriter(filename: '');
fg.writeMainHeader(writer);
expectMatchesGoldenFile(
fg.generateGrpcFile(), 'test/goldens/grpc_service.pbgrpc');
@@ -362,8 +391,9 @@
FileGenerator fg = new FileGenerator(fd, options);
link(options,
[fg, new FileGenerator(fd1, options), new FileGenerator(fd2, options)]);
- expectMatchesGoldenFile(fg.generateMainFile(), 'test/goldens/imports.pb');
expectMatchesGoldenFile(
- fg.generateEnumFile(), 'test/goldens/imports.pbjson');
+ fg.generateMainFile().toString(), 'test/goldens/imports.pb');
+ expectMatchesGoldenFile(
+ fg.generateEnumFile().toString(), 'test/goldens/imports.pbjson');
});
}
diff --git a/protoc_plugin/test/goldens/enum.meta b/protoc_plugin/test/goldens/enum.meta
new file mode 100644
index 0000000..3aa174d
--- /dev/null
+++ b/protoc_plugin/test/goldens/enum.meta
@@ -0,0 +1,48 @@
+annotation: {
+ path: 5
+ path: 0
+ path: 1
+ sourceFile: sample.proto
+ begin: 6
+ end: 15
+}
+annotation: {
+ path: 5
+ path: 0
+ path: 2
+ path: 0
+ path: 1
+ sourceFile: sample.proto
+ begin: 66
+ end: 72
+}
+annotation: {
+ path: 5
+ path: 0
+ path: 2
+ path: 1
+ path: 1
+ sourceFile: sample.proto
+ begin: 132
+ end: 136
+}
+annotation: {
+ path: 5
+ path: 0
+ path: 2
+ path: 2
+ path: 1
+ sourceFile: sample.proto
+ begin: 194
+ end: 198
+}
+annotation: {
+ path: 5
+ path: 0
+ path: 2
+ path: 3
+ path: 1
+ sourceFile: sample.proto
+ begin: 257
+ end: 265
+}
diff --git a/protoc_plugin/test/goldens/messageGenerator.meta b/protoc_plugin/test/goldens/messageGenerator.meta
new file mode 100644
index 0000000..3caf24a
--- /dev/null
+++ b/protoc_plugin/test/goldens/messageGenerator.meta
@@ -0,0 +1,8 @@
+annotation: {
+ path: 4
+ path: 0
+ path: 1
+ sourceFile:
+ begin: 6
+ end: 17
+}
diff --git a/protoc_plugin/test/goldens/messageGeneratorEnums.meta b/protoc_plugin/test/goldens/messageGeneratorEnums.meta
new file mode 100644
index 0000000..789f5b7
--- /dev/null
+++ b/protoc_plugin/test/goldens/messageGeneratorEnums.meta
@@ -0,0 +1,58 @@
+annotation: {
+ path: 4
+ path: 0
+ path: 4
+ path: 0
+ path: 1
+ sourceFile:
+ begin: 6
+ end: 27
+}
+annotation: {
+ path: 4
+ path: 0
+ path: 4
+ path: 0
+ path: 2
+ path: 0
+ path: 1
+ sourceFile:
+ begin: 90
+ end: 96
+}
+annotation: {
+ path: 4
+ path: 0
+ path: 4
+ path: 0
+ path: 2
+ path: 1
+ path: 1
+ sourceFile:
+ begin: 180
+ end: 184
+}
+annotation: {
+ path: 4
+ path: 0
+ path: 4
+ path: 0
+ path: 2
+ path: 2
+ path: 1
+ sourceFile:
+ begin: 266
+ end: 270
+}
+annotation: {
+ path: 4
+ path: 0
+ path: 4
+ path: 0
+ path: 2
+ path: 3
+ path: 1
+ sourceFile:
+ begin: 353
+ end: 361
+}
diff --git a/protoc_plugin/test/goldens/oneMessage.pb.meta b/protoc_plugin/test/goldens/oneMessage.pb.meta
new file mode 100644
index 0000000..b264b07
--- /dev/null
+++ b/protoc_plugin/test/goldens/oneMessage.pb.meta
@@ -0,0 +1,8 @@
+annotation: {
+ path: 4
+ path: 0
+ path: 1
+ sourceFile: test
+ begin: 299
+ end: 310
+}
diff --git a/protoc_plugin/test/goldens/topLevelEnum.pb.meta b/protoc_plugin/test/goldens/topLevelEnum.pb.meta
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/protoc_plugin/test/goldens/topLevelEnum.pb.meta
diff --git a/protoc_plugin/test/goldens/topLevelEnum.pbenum.meta b/protoc_plugin/test/goldens/topLevelEnum.pbenum.meta
new file mode 100644
index 0000000..9fc500e
--- /dev/null
+++ b/protoc_plugin/test/goldens/topLevelEnum.pbenum.meta
@@ -0,0 +1,48 @@
+annotation: {
+ path: 5
+ path: 0
+ path: 1
+ sourceFile: test
+ begin: 313
+ end: 322
+}
+annotation: {
+ path: 5
+ path: 0
+ path: 2
+ path: 0
+ path: 1
+ sourceFile: test
+ begin: 373
+ end: 379
+}
+annotation: {
+ path: 5
+ path: 0
+ path: 2
+ path: 1
+ path: 1
+ sourceFile: test
+ begin: 439
+ end: 443
+}
+annotation: {
+ path: 5
+ path: 0
+ path: 2
+ path: 2
+ path: 1
+ sourceFile: test
+ begin: 501
+ end: 505
+}
+annotation: {
+ path: 5
+ path: 0
+ path: 2
+ path: 3
+ path: 1
+ sourceFile: test
+ begin: 564
+ end: 572
+}
diff --git a/protoc_plugin/test/indenting_writer_test.dart b/protoc_plugin/test/indenting_writer_test.dart
index 61f0427..6c006fc 100755
--- a/protoc_plugin/test/indenting_writer_test.dart
+++ b/protoc_plugin/test/indenting_writer_test.dart
@@ -6,11 +6,12 @@
library indenting_writer_test;
import 'package:protoc_plugin/indenting_writer.dart';
+import 'package:protoc_plugin/src/descriptor.pb.dart';
import 'package:test/test.dart';
void main() {
test('IndentingWriter can indent a block', () {
- var out = new IndentingWriter();
+ var out = new IndentingWriter(filename: '');
out.addBlock('class test {', '}', () {
out.println('first;');
out.println();
@@ -25,4 +26,45 @@
}
''');
});
+
+ test('IndentingWriter annotation tracks previous output', () {
+ var out = new IndentingWriter(filename: 'sample.proto');
+ out.print('13 characters');
+ out.print('sample text');
+ out.addAnnotation([1, 2, 3], 'text', 7);
+ GeneratedCodeInfo_Annotation expected = new GeneratedCodeInfo_Annotation()
+ ..path.addAll([1, 2, 3])
+ ..sourceFile = 'sample.proto'
+ ..begin = 20
+ ..end = 24;
+ GeneratedCodeInfo_Annotation annotation =
+ out.sourceLocationInfo.annotation[0];
+ expect(annotation, equals(expected));
+ });
+
+ test('IndentingWriter annotation counts indents correctly', () {
+ var out = new IndentingWriter(filename: '');
+ out.addBlock('34 characters including newline {', '}', () {
+ out.println('sample text');
+ out.addAnnotation([], 'sample', 0);
+ });
+ GeneratedCodeInfo_Annotation annotation =
+ out.sourceLocationInfo.annotation[0];
+ expect(annotation.begin, equals(34));
+ expect(annotation.end, equals(40));
+ });
+
+ test('IndentingWriter annotations counts multiline output correctly', () {
+ var out = new IndentingWriter(filename: '');
+ out.print('20 characters\ntotal\n');
+ out.println('20 characters before this');
+ out.addAnnotation([], 'ch', 3);
+ GeneratedCodeInfo_Annotation annotation =
+ out.sourceLocationInfo.annotation[0];
+ expect(annotation.begin, equals(23));
+ expect(annotation.end, equals(25));
+ });
+
+ test('IndentingWriter does not break when making annotation for null file',
+ () {});
}
diff --git a/protoc_plugin/test/message_generator_test.dart b/protoc_plugin/test/message_generator_test.dart
index 70b0341..dcc8deb 100755
--- a/protoc_plugin/test/message_generator_test.dart
+++ b/protoc_plugin/test/message_generator_test.dart
@@ -62,19 +62,23 @@
FileGenerator fg = new FileGenerator(fd, options);
MessageGenerator mg =
- new MessageGenerator(md, fg, {}, null, new Set<String>());
+ new MessageGenerator.topLevel(md, fg, {}, null, new Set<String>(), 0);
var ctx = new GenerationContext(options);
mg.register(ctx);
mg.resolve(ctx);
- var writer = new IndentingWriter();
+ var writer = new IndentingWriter(filename: '');
mg.generate(writer);
expectMatchesGoldenFile(writer.toString(), 'test/goldens/messageGenerator');
+ expectMatchesGoldenFile(writer.sourceLocationInfo.toString(),
+ 'test/goldens/messageGenerator.meta');
- writer = new IndentingWriter();
+ writer = new IndentingWriter(filename: '');
mg.generateEnums(writer);
expectMatchesGoldenFile(
writer.toString(), 'test/goldens/messageGeneratorEnums');
+ expectMatchesGoldenFile(writer.sourceLocationInfo.toString(),
+ 'test/goldens/messageGeneratorEnums.meta');
});
}