Preserve the original order of proto fields when generating metadata. (#241)
* Preserve the original order of proto fields.
This corrects metadata edges pointing to the wrong field if the fields were not originally sorted by tag number.
* Fix dartanalyzer lints.
diff --git a/protoc_plugin/CHANGELOG.md b/protoc_plugin/CHANGELOG.md
index cc8adb2..d434e8a 100644
--- a/protoc_plugin/CHANGELOG.md
+++ b/protoc_plugin/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 16.0.6
+
+* Track the original order of proto fields and include it in metadata
+ to ensure the correct fields are being referenced.
+
## 16.0.5
* Fix generation of invalid Dart code for oneof enums
diff --git a/protoc_plugin/lib/message_generator.dart b/protoc_plugin/lib/message_generator.dart
index 41746a3..514b190 100644
--- a/protoc_plugin/lib/message_generator.dart
+++ b/protoc_plugin/lib/message_generator.dart
@@ -439,11 +439,11 @@
_oneofNames
.forEach((OneofNames oneof) => generateoneOfAccessors(out, oneof));
- for (var i = 0; i < _fieldList.length; i++) {
+ for (var field in _fieldList) {
out.println();
List<int> memberFieldPath = List.from(fieldPath)
- ..addAll([_messageFieldTag, i]);
- generateFieldAccessorsMutators(_fieldList[i], out, memberFieldPath);
+ ..addAll([_messageFieldTag, field.sourcePosition]);
+ generateFieldAccessorsMutators(field, out, memberFieldPath);
}
}
@@ -524,7 +524,7 @@
NamedLocation(
name: names.hasMethodName,
fieldPathSegment: memberFieldPath,
- start: 'bool '.length)
+ start: '$_coreImportPrefix.bool '.length)
]);
_emitDeprecatedIf(field.isDeprecated, out);
_emitOverrideIf(field.overridesClearMethod, out);
diff --git a/protoc_plugin/lib/names.dart b/protoc_plugin/lib/names.dart
index cac4e54..bac5055 100644
--- a/protoc_plugin/lib/names.dart
+++ b/protoc_plugin/lib/names.dart
@@ -21,6 +21,10 @@
/// The same index will be stored in FieldInfo.index.
final int index;
+ /// The position of this field as it appeared in the original DescriptorProto.
+ /// Used to construct metadata.
+ final int sourcePosition;
+
/// Identifier for generated getters/setters.
final String fieldName;
@@ -34,7 +38,7 @@
/// `null` for repeated fields.
final String clearMethodName;
- FieldNames(this.descriptor, this.index, this.fieldName,
+ FieldNames(this.descriptor, this.index, this.sourcePosition, this.fieldName,
{this.hasMethodName, this.clearMethodName});
}
@@ -81,7 +85,7 @@
/// Chooses the Dart name of an extension.
String extensionName(FieldDescriptorProto descriptor, Set<String> usedNames) {
- return _unusedMemberNames(descriptor, null, usedNames).fieldName;
+ return _unusedMemberNames(descriptor, null, null, usedNames).fieldName;
}
Iterable<String> extensionSuffixes() sync* {
@@ -218,7 +222,10 @@
MemberNames messageMemberNames(DescriptorProto descriptor,
String parentClassName, Set<String> usedTopLevelNames,
{Iterable<String> reserved = const []}) {
- var sorted = List<FieldDescriptorProto>.from(descriptor.field)
+ var fieldList = List<FieldDescriptorProto>.from(descriptor.field);
+ var sourcePositions =
+ fieldList.asMap().map((index, field) => MapEntry(field.name, index));
+ var sorted = fieldList
..sort((FieldDescriptorProto a, FieldDescriptorProto b) {
if (a.number < b.number) return -1;
if (a.number > b.number) return 1;
@@ -255,8 +262,8 @@
// Explicitly setting a name that's already taken is a build error.
for (var field in sorted) {
if (_nameOption(field).isNotEmpty) {
- takeFieldNames(_memberNamesFromOption(
- descriptor, field, indexes[field.name], existingNames));
+ takeFieldNames(_memberNamesFromOption(descriptor, field,
+ indexes[field.name], sourcePositions[field.name], existingNames));
}
}
@@ -265,7 +272,9 @@
for (var field in sorted) {
if (_nameOption(field).isEmpty) {
var index = indexes[field.name];
- takeFieldNames(_unusedMemberNames(field, index, existingNames));
+ var sourcePosition = sourcePositions[field.name];
+ takeFieldNames(
+ _unusedMemberNames(field, index, sourcePosition, existingNames));
}
}
@@ -313,8 +322,12 @@
///
/// If the explicitly-set Dart name is already taken, throw an exception.
/// (Fails the build.)
-FieldNames _memberNamesFromOption(DescriptorProto message,
- FieldDescriptorProto field, int index, Set<String> existingNames) {
+FieldNames _memberNamesFromOption(
+ DescriptorProto message,
+ FieldDescriptorProto field,
+ int index,
+ int sourcePosition,
+ Set<String> existingNames) {
// TODO(skybrian): provide more context in errors (filename).
var where = "${message.name}.${field.name}";
@@ -336,7 +349,7 @@
checkAvailable(name);
if (_isRepeated(field)) {
- return FieldNames(field, index, name);
+ return FieldNames(field, index, sourcePosition, name);
}
String hasMethod = "has${_capitalize(name)}";
@@ -345,7 +358,7 @@
String clearMethod = "clear${_capitalize(name)}";
checkAvailable(clearMethod);
- return FieldNames(field, index, name,
+ return FieldNames(field, index, sourcePosition, name,
hasMethodName: hasMethod, clearMethodName: clearMethod);
}
@@ -357,12 +370,13 @@
}
}
-FieldNames _unusedMemberNames(
- FieldDescriptorProto field, int index, Set<String> existingNames) {
+FieldNames _unusedMemberNames(FieldDescriptorProto field, int index,
+ int sourcePosition, Set<String> existingNames) {
if (_isRepeated(field)) {
return FieldNames(
field,
index,
+ sourcePosition,
disambiguateName(_defaultFieldName(_fieldMethodSuffix(field)),
existingNames, _memberNamesSuffix(field.number)));
}
@@ -378,7 +392,7 @@
String name = disambiguateName(_fieldMethodSuffix(field), existingNames,
_memberNamesSuffix(field.number),
generateVariants: generateNameVariants);
- return FieldNames(field, index, _defaultFieldName(name),
+ return FieldNames(field, index, sourcePosition, _defaultFieldName(name),
hasMethodName: _defaultHasMethodName(name),
clearMethodName: _defaultClearMethodName(name));
}
diff --git a/protoc_plugin/lib/protobuf_field.dart b/protoc_plugin/lib/protobuf_field.dart
index f058f26..ed04415 100644
--- a/protoc_plugin/lib/protobuf_field.dart
+++ b/protoc_plugin/lib/protobuf_field.dart
@@ -45,6 +45,9 @@
/// `null` for an extension.
int get index => memberNames?.index;
+ /// The position of this field as it appeared in the original DescriptorProto.
+ int get sourcePosition => memberNames.sourcePosition;
+
/// True if the field is to be encoded with [deprecated = true] encoding.
bool get isDeprecated => descriptor.options?.deprecated;
diff --git a/protoc_plugin/pubspec.yaml b/protoc_plugin/pubspec.yaml
index 35528ff..14f1bd1 100644
--- a/protoc_plugin/pubspec.yaml
+++ b/protoc_plugin/pubspec.yaml
@@ -1,5 +1,5 @@
name: protoc_plugin
-version: 16.0.5-dev-1
+version: 16.0.6
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/goldens/messageGenerator.meta b/protoc_plugin/test/goldens/messageGenerator.meta
index e91d72a..e573df9 100644
--- a/protoc_plugin/test/goldens/messageGenerator.meta
+++ b/protoc_plugin/test/goldens/messageGenerator.meta
@@ -16,7 +16,7 @@
path: 4
path: 0
path: 2
- path: 0
+ path: 1
sourceFile:
begin: 1205
end: 1211
@@ -25,7 +25,7 @@
path: 4
path: 0
path: 2
- path: 0
+ path: 1
sourceFile:
begin: 1236
end: 1242
@@ -34,16 +34,16 @@
path: 4
path: 0
path: 2
- path: 0
+ path: 1
sourceFile:
- begin: 1289
- end: 1298
+ begin: 1295
+ end: 1304
}
annotation: {
path: 4
path: 0
path: 2
- path: 0
+ path: 1
sourceFile:
begin: 1327
end: 1338
@@ -52,7 +52,7 @@
path: 4
path: 0
path: 2
- path: 1
+ path: 0
sourceFile:
begin: 1388
end: 1392
@@ -61,7 +61,7 @@
path: 4
path: 0
path: 2
- path: 1
+ path: 0
sourceFile:
begin: 1413
end: 1417
@@ -70,16 +70,16 @@
path: 4
path: 0
path: 2
- path: 1
+ path: 0
sourceFile:
- begin: 1470
- end: 1477
+ begin: 1476
+ end: 1483
}
annotation: {
path: 4
path: 0
path: 2
- path: 1
+ path: 0
sourceFile:
begin: 1506
end: 1515
@@ -108,8 +108,8 @@
path: 2
path: 2
sourceFile:
- begin: 1638
- end: 1645
+ begin: 1644
+ end: 1651
}
annotation: {
path: 4
@@ -144,8 +144,8 @@
path: 2
path: 3
sourceFile:
- begin: 1973
- end: 1991
+ begin: 1979
+ end: 1997
}
annotation: {
path: 4
diff --git a/protoc_plugin/test/goldens/oneMessage.pb.meta b/protoc_plugin/test/goldens/oneMessage.pb.meta
index 74ed759..fb67861 100644
--- a/protoc_plugin/test/goldens/oneMessage.pb.meta
+++ b/protoc_plugin/test/goldens/oneMessage.pb.meta
@@ -36,8 +36,8 @@
path: 2
path: 0
sourceFile: test
- begin: 1474
- end: 1483
+ begin: 1480
+ end: 1489
}
annotation: {
path: 4
@@ -72,8 +72,8 @@
path: 2
path: 1
sourceFile: test
- begin: 1641
- end: 1648
+ begin: 1647
+ end: 1654
}
annotation: {
path: 4
@@ -108,8 +108,8 @@
path: 2
path: 2
sourceFile: test
- begin: 1809
- end: 1816
+ begin: 1815
+ end: 1822
}
annotation: {
path: 4
diff --git a/protoc_plugin/test/message_generator_test.dart b/protoc_plugin/test/message_generator_test.dart
index 30fcb10..bc67df3 100755
--- a/protoc_plugin/test/message_generator_test.dart
+++ b/protoc_plugin/test/message_generator_test.dart
@@ -5,6 +5,8 @@
library message_generator_test;
+import 'dart:collection';
+import 'package:collection/collection.dart';
import 'package:protoc_plugin/indenting_writer.dart';
import 'package:protoc_plugin/protoc.dart';
import 'package:test/test.dart';
@@ -15,9 +17,12 @@
import 'golden_file.dart';
void main() {
- test('testMessageGenerator', () {
- FileDescriptorProto fd = FileDescriptorProto();
- EnumDescriptorProto ed = EnumDescriptorProto()
+ FileDescriptorProto fd;
+ EnumDescriptorProto ed;
+ DescriptorProto md;
+ setUp(() async {
+ fd = FileDescriptorProto();
+ ed = EnumDescriptorProto()
..name = 'PhoneType'
..value.addAll([
EnumValueDescriptorProto()
@@ -33,7 +38,7 @@
..name = 'BUSINESS'
..number = 2
]);
- DescriptorProto md = DescriptorProto()
+ md = DescriptorProto()
..name = 'PhoneNumber'
..field.addAll([
// optional PhoneType type = 2 [default = HOME];
@@ -63,6 +68,8 @@
..options = (FieldOptions()..deprecated = true),
])
..enumType.add(ed);
+ });
+ test('testMessageGenerator', () {
var options =
parseGenerationOptions(CodeGeneratorRequest(), CodeGeneratorResponse());
@@ -87,4 +94,45 @@
expectMatchesGoldenFile(writer.sourceLocationInfo.toString(),
'test/goldens/messageGeneratorEnums.meta');
});
+
+ test('testMetadataIndices', () {
+ var options =
+ parseGenerationOptions(CodeGeneratorRequest(), CodeGeneratorResponse());
+ FileGenerator fg = FileGenerator(fd, options);
+ MessageGenerator mg =
+ MessageGenerator.topLevel(md, fg, {}, null, Set<String>(), 0);
+
+ var ctx = GenerationContext(options);
+ mg.register(ctx);
+ mg.resolve(ctx);
+
+ var writer = IndentingWriter(filename: '');
+ mg.generate(writer);
+
+ var eq = ListEquality();
+ var fieldStringsMap = HashMap(
+ equals: eq.equals, hashCode: eq.hash, isValidKey: eq.isValidKey);
+ fieldStringsMap[[4, 0]] = ['PhoneNumber'];
+ fieldStringsMap[[4, 0, 2, 0]] = ['type', 'hasType', 'clearType'];
+ fieldStringsMap[[4, 0, 2, 1]] = ['number', 'hasNumber', 'clearNumber'];
+ fieldStringsMap[[4, 0, 2, 2]] = ['name', 'hasName', 'clearName'];
+ fieldStringsMap[[4, 0, 2, 3]] = [
+ 'deprecatedField',
+ 'hasDeprecatedField',
+ 'clearDeprecatedField'
+ ];
+
+ String generatedContents = writer.toString();
+ GeneratedCodeInfo metadata = writer.sourceLocationInfo;
+ for (var annotation in metadata.annotation) {
+ String annotatedName =
+ generatedContents.substring(annotation.begin, annotation.end);
+ var expectedStrings = fieldStringsMap[annotation.path];
+ if (expectedStrings == null) {
+ fail('The field path ${annotation.path} '
+ 'did not match any expected field path.');
+ }
+ expect(annotatedName, isIn(expectedStrings));
+ }
+ });
}