Resolving names with underscores (#139)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 650143a..3f25578 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,27 @@
+## 0.12.0
+
+* Breaking change: Handle identifiers starting with a leading underscore.
+ This covers message names, enum names, enum value identifiers and file names.
+
+ Before, these would appear in the generated Dart code as private identifiers.
+ Now the underscore is moved to the end.
+
+ Field names and extension field names already had all underscores removed, and these are not
+ affected by this change.
+
+ If there is a conflicting name with a trailing underscore defined later in the same scope, a
+ disambiguation will happen that can potentially lead to existing identifiers getting a new name in
+ the generated Dart.
+
+ For example:
+
+ ```
+ message _Foo {}
+ message Foo_ {}
+ ```
+
+ `_Foo` will get the name `Foo_` and `Foo_` will now end up being called `Foo__`.
+
## 0.11.0
* Breaking change: Support for [map fields](https://developers.google.com/protocol-buffers/docs/proto3#maps)
diff --git a/Makefile b/Makefile
index e75116c..6cc1bf2 100644
--- a/Makefile
+++ b/Makefile
@@ -12,6 +12,7 @@
BENCHMARK_PROTOS = $(wildcard benchmark/protos/*.proto)
TEST_PROTO_LIST = \
+ _leading_underscores \
google/protobuf/any \
google/protobuf/unittest_import \
google/protobuf/unittest_optimize_for \
diff --git a/lib/client_generator.dart b/lib/client_generator.dart
index 63376de..309b121 100644
--- a/lib/client_generator.dart
+++ b/lib/client_generator.dart
@@ -7,14 +7,20 @@
class ClientApiGenerator {
// The service that this Client API connects to.
final ServiceGenerator service;
+ final String className;
+ final Set<String> usedMethodNames = new Set<String>()
+ ..addAll(reservedMemberNames);
- ClientApiGenerator(this.service);
+ ClientApiGenerator(this.service, Set<String> usedNames)
+ : className = disambiguateName(
+ avoidInitialUnderscore(service._descriptor.name),
+ usedNames,
+ defaultSuffixes());
// Subclasses can override this.
String get _clientType => '$_protobufImportPrefix.RpcClient';
void generate(IndentingWriter out) {
- var className = service._descriptor.name;
out.addBlock('class ${className}Api {', '}', () {
out.println('$_clientType _client;');
out.println('${className}Api(this._client);');
@@ -29,7 +35,10 @@
// Subclasses can override this.
void generateMethod(IndentingWriter out, MethodDescriptorProto m) {
- var methodName = service._methodName(m.name);
+ var methodName = disambiguateName(
+ avoidInitialUnderscore(service._methodName(m.name)),
+ usedMethodNames,
+ defaultSuffixes());
var inputType = service._getDartClassName(m.inputType);
var outputType = service._getDartClassName(m.outputType);
out.addBlock(
@@ -37,8 +46,7 @@
'$_protobufImportPrefix.ClientContext ctx, $inputType request) {',
'}', () {
out.println('var emptyResponse = new $outputType();');
- out.println(
- 'return _client.invoke<$outputType>(ctx, \'${service._descriptor.name}\', '
+ out.println('return _client.invoke<$outputType>(ctx, \'${className}\', '
'\'${m.name}\', request, emptyResponse);');
});
}
diff --git a/lib/enum_generator.dart b/lib/enum_generator.dart
index 225cae6..2ff355c 100644
--- a/lib/enum_generator.dart
+++ b/lib/enum_generator.dart
@@ -19,15 +19,20 @@
<EnumValueDescriptorProto>[];
final List<EnumAlias> _aliases = <EnumAlias>[];
- EnumGenerator(EnumDescriptorProto descriptor, ProtobufContainer parent)
+ /// Maps the name of an enum value to the Dart name we will use for it.
+ final Map<String, String> dartNames = <String, String>{};
+
+ EnumGenerator(EnumDescriptorProto descriptor, ProtobufContainer parent,
+ Set<String> usedClassNames)
: assert(parent != null),
_parent = parent,
- classname = messageOrEnumClassName(descriptor.name,
+ classname = messageOrEnumClassName(descriptor.name, usedClassNames,
parent: parent?.classname ?? ''),
fullName = parent.fullName == ''
? descriptor.name
: '${parent.fullName}.${descriptor.name}',
_descriptor = descriptor {
+ final usedNames = reservedEnumNames;
for (EnumValueDescriptorProto value in descriptor.value) {
EnumValueDescriptorProto canonicalValue =
descriptor.value.firstWhere((v) => v.number == value.number);
@@ -36,6 +41,8 @@
} else {
_aliases.add(new EnumAlias(value, canonicalValue));
}
+ dartNames[value.name] = disambiguateName(
+ avoidInitialUnderscore(value.name), usedNames, enumSuffixes());
}
}
@@ -63,27 +70,25 @@
'}\n', () {
// -----------------------------------------------------------------
// Define enum types.
- var reservedNames = reservedEnumNames;
for (EnumValueDescriptorProto val in _canonicalValues) {
- final name = unusedEnumNames(val.name, reservedNames);
+ final name = dartNames[val.name];
out.println('static const ${classname} $name = '
- "const ${classname}._(${val.number}, '$name');");
+ "const ${classname}._(${val.number}, ${singleQuote(name)});");
}
if (_aliases.isNotEmpty) {
out.println();
for (EnumAlias alias in _aliases) {
- final name = unusedEnumNames(alias.value.name, reservedNames);
+ final name = dartNames[alias.value.name];
out.println('static const ${classname} $name ='
- ' ${alias.canonicalValue.name};');
+ ' ${dartNames[alias.canonicalValue.name]};');
}
}
out.println();
out.println('static const List<${classname}> values ='
' const <${classname}> [');
- reservedNames = reservedEnumNames;
for (EnumValueDescriptorProto val in _canonicalValues) {
- final name = unusedEnumNames(val.name, reservedNames);
+ final name = dartNames[val.name];
out.println(' $name,');
}
out.println('];');
@@ -95,7 +100,7 @@
' _byValue[value];');
out.addBlock('static void $checkItem($classname v) {', '}', () {
out.println('if (v is! $classname)'
- " $_protobufImportPrefix.checkItemFailed(v, '$classname');");
+ " $_protobufImportPrefix.checkItemFailed(v, ${singleQuote(classname)});");
});
out.println();
diff --git a/lib/extension_generator.dart b/lib/extension_generator.dart
index 352e0f4..aca8255 100644
--- a/lib/extension_generator.dart
+++ b/lib/extension_generator.dart
@@ -10,13 +10,13 @@
// populated by resolve()
ProtobufField _field;
- String _extensionName;
+ final String _extensionName;
String _extendedFullName = "";
- ExtensionGenerator(this._descriptor, this._parent);
+ ExtensionGenerator(this._descriptor, this._parent, Set<String> usedNames)
+ : _extensionName = extensionName(_descriptor, usedNames);
void resolve(GenerationContext ctx) {
- _extensionName = extensionName(_descriptor);
_field = new ProtobufField.extension(_descriptor, _parent, ctx);
ProtobufContainer extendedType = ctx.getFieldType(_descriptor.extendee);
diff --git a/lib/file_generator.dart b/lib/file_generator.dart
index 0b9d2a3..c9940c6 100644
--- a/lib/file_generator.dart
+++ b/lib/file_generator.dart
@@ -104,6 +104,19 @@
final serviceGenerators = <ServiceGenerator>[];
final grpcGenerators = <GrpcServiceGenerator>[];
+ /// Used to avoid collisions after names have been mangled to match the Dart
+ /// style.
+ final Set<String> usedTopLevelNames = Set<String>()
+ ..addAll(toplevelReservedCapitalizedNames);
+
+ /// Used to avoid collisions in the service file after names have been mangled
+ /// to match the dart style.
+ final Set<String> usedTopLevelServiceNames = Set<String>()
+ ..addAll(toplevelReservedCapitalizedNames);
+
+ final Set<String> usedExtensionNames = Set<String>()
+ ..addAll(forbiddenExtensionNames);
+
/// True if cross-references have been resolved.
bool _linked = false;
@@ -126,22 +139,25 @@
// Load and register all enum and message types.
for (EnumDescriptorProto enumType in descriptor.enumType) {
- enumGenerators.add(new EnumGenerator(enumType, this));
+ enumGenerators.add(new EnumGenerator(enumType, this, usedTopLevelNames));
}
for (DescriptorProto messageType in descriptor.messageType) {
messageGenerators.add(new MessageGenerator(
- messageType, this, declaredMixins, defaultMixin));
+ messageType, this, declaredMixins, defaultMixin, usedTopLevelNames));
}
for (FieldDescriptorProto extension in descriptor.extension) {
- extensionGenerators.add(new ExtensionGenerator(extension, this));
+ extensionGenerators
+ .add(new ExtensionGenerator(extension, this, usedExtensionNames));
}
for (ServiceDescriptorProto service in descriptor.service) {
if (options.useGrpc) {
grpcGenerators.add(new GrpcServiceGenerator(service, this));
} else {
- var serviceGen = new ServiceGenerator(service, this);
+ var serviceGen =
+ new ServiceGenerator(service, this, usedTopLevelServiceNames);
serviceGenerators.add(serviceGen);
- clientApiGenerators.add(new ClientApiGenerator(serviceGen));
+ clientApiGenerators
+ .add(new ClientApiGenerator(serviceGen, usedTopLevelNames));
}
}
}
@@ -210,7 +226,7 @@
// name derived from the file name.
if (extensionGenerators.isNotEmpty) {
// TODO(antonm): do not generate a class.
- String className = extensionClassName(descriptor);
+ String className = extensionClassName(descriptor, usedTopLevelNames);
out.addBlock('class $className {', '}\n', () {
for (ExtensionGenerator x in extensionGenerators) {
x.generate(out);
diff --git a/lib/message_generator.dart b/lib/message_generator.dart
index 365953a..5c9c072 100644
--- a/lib/message_generator.dart
+++ b/lib/message_generator.dart
@@ -60,11 +60,15 @@
// populated by resolve()
List<ProtobufField> _fieldList;
- MessageGenerator(DescriptorProto descriptor, ProtobufContainer parent,
- Map<String, PbMixin> declaredMixins, PbMixin defaultMixin)
+ MessageGenerator(
+ DescriptorProto descriptor,
+ ProtobufContainer parent,
+ Map<String, PbMixin> declaredMixins,
+ PbMixin defaultMixin,
+ Set<String> usedNames)
: _descriptor = descriptor,
_parent = parent,
- classname = messageOrEnumClassName(descriptor.name,
+ classname = messageOrEnumClassName(descriptor.name, usedNames,
parent: parent?.classname ?? ''),
assert(parent != null),
fullName = parent.fullName == ''
@@ -73,16 +77,16 @@
mixin = _getMixin(descriptor, parent.fileGen.descriptor, declaredMixins,
defaultMixin) {
for (EnumDescriptorProto e in _descriptor.enumType) {
- _enumGenerators.add(new EnumGenerator(e, this));
+ _enumGenerators.add(new EnumGenerator(e, this, usedNames));
}
for (DescriptorProto n in _descriptor.nestedType) {
- _messageGenerators
- .add(new MessageGenerator(n, this, declaredMixins, defaultMixin));
+ _messageGenerators.add(new MessageGenerator(
+ n, this, declaredMixins, defaultMixin, usedNames));
}
for (FieldDescriptorProto x in _descriptor.extension) {
- _extensionGenerators.add(new ExtensionGenerator(x, this));
+ _extensionGenerators.add(new ExtensionGenerator(x, this, usedNames));
}
}
diff --git a/lib/names.dart b/lib/names.dart
index e5dab99..a974bec 100644
--- a/lib/names.dart
+++ b/lib/names.dart
@@ -32,52 +32,57 @@
/// `null` for repeated fields.
final String clearMethodName;
- MemberNames(this.descriptor, this.index, this.fieldName, this.hasMethodName,
- this.clearMethodName);
+ MemberNames(this.descriptor, this.index, this.fieldName,
+ {this.hasMethodName, this.clearMethodName});
+}
- MemberNames.forRepeatedField(this.descriptor, this.index, this.fieldName)
- : hasMethodName = null,
- clearMethodName = null;
+/// Move any initial underscores in [input] to the end.
+///
+/// According to the spec identifiers cannot start with _, but it seems to be
+/// accepted by protoc.
+///
+/// These identifiers are private in Dart, so they have to be transformed.
+String avoidInitialUnderscore(String input) {
+ while (input.startsWith('_')) {
+ input = '${input.substring(1)}_';
+ }
+ return input;
+}
+
+/// Returns [input] surrounded by single quotes and with all '$'s escaped.
+String singleQuote(String input) {
+ return "'${input.replaceAll(r'$', r'\$')}'";
}
/// Chooses the Dart name of an extension.
-String extensionName(FieldDescriptorProto descriptor) {
- var existingNames = new Set<String>()
- ..addAll(_dartReservedWords)
- ..addAll(GeneratedMessage_reservedNames)
- ..addAll(_generatedMessageNames);
- return _unusedMemberNames(descriptor, null, existingNames).fieldName;
+String extensionName(FieldDescriptorProto descriptor, Set<String> usedNames) {
+ return _unusedMemberNames(descriptor, null, usedNames).fieldName;
+}
+
+Iterable<String> extensionSuffixes() sync* {
+ yield "Ext";
+ int i = 2;
+ while (true) {
+ yield '$i';
+ i++;
+ }
+}
+
+/// Replaces all characters in [imput] that are not valid in a dart identifier
+/// with _.
+///
+/// This function does not take care of leading underscores.
+String legalDartIdentifier(String imput) {
+ return imput.replaceAll(new RegExp(r'[^a-zA-Z0-9$_]'), '_');
}
/// Chooses the name of the Dart class holding top-level extensions.
-String extensionClassName(FileDescriptorProto descriptor) {
- var taken = new Set<String>();
- for (var messageType in descriptor.messageType) {
- taken.add(messageOrEnumClassName(messageType.name));
- }
- for (var enumType in descriptor.enumType) {
- taken.add(enumType.name);
- }
-
- String s = _fileNameWithoutExtension(descriptor).replaceAll('-', '_');
+String extensionClassName(
+ FileDescriptorProto descriptor, Set<String> usedNames) {
+ String s = avoidInitialUnderscore(
+ legalDartIdentifier(_fileNameWithoutExtension(descriptor)));
String candidate = '${s[0].toUpperCase()}${s.substring(1)}';
-
- if (!taken.contains(candidate)) {
- return candidate;
- }
-
- // Found a conflict; try again.
- candidate = "${candidate}Ext";
- if (!taken.contains(candidate)) {
- return candidate;
- }
-
- // Next, try numbers.
- int suffix = 2;
- while (taken.contains("$candidate$suffix")) {
- suffix++;
- }
- return "$candidate$suffix";
+ return disambiguateName(candidate, usedNames, extensionSuffixes());
}
String _fileNameWithoutExtension(FileDescriptorProto descriptor) {
@@ -94,24 +99,61 @@
String toString() => "$message";
}
+/// Returns a [name] that is not contained in [usedNames] by suffixing it with
+/// the first possible suffix from [suffixes].
+///
+/// The chosen name is added to [usedNames].
+///
+/// If [variants] is given, all the variants of a name must be available before
+/// that name is chosen, and all the chosen variants will be added to
+/// [usedNames].
+/// The returned name is that, which will generate the accepted variants.
+String disambiguateName(
+ String name, Set<String> usedNames, Iterable<String> suffixes,
+ {List<String> Function(String candidate) generateVariants}) {
+ generateVariants ??= (String name) => <String>[name];
+
+ bool allVariantsAvailable(List<String> variants) {
+ return variants.every((String variant) => !usedNames.contains(variant));
+ }
+
+ String usedSuffix = '';
+ List<String> candidateVariants = generateVariants(name);
+
+ if (!allVariantsAvailable(candidateVariants)) {
+ for (String suffix in suffixes) {
+ candidateVariants = generateVariants('$name$suffix');
+ if (allVariantsAvailable(candidateVariants)) {
+ usedSuffix = suffix;
+ break;
+ }
+ }
+ }
+
+ usedNames.addAll(candidateVariants);
+ return '$name$usedSuffix';
+}
+
+Iterable<String> defaultSuffixes() sync* {
+ yield '_';
+ int i = 0;
+ while (true) {
+ yield ('_$i');
+ i++;
+ }
+}
+
/// Chooses the name of the Dart class to generate for a proto message or enum.
///
/// For a nested message or enum, [parent] should be provided
/// with the name of the Dart class for the immediate parent.
-String messageOrEnumClassName(String descriptorName, {String parent = ''}) {
- var name = descriptorName;
+String messageOrEnumClassName(String descriptorName, Set<String> usedNames,
+ {String parent = ''}) {
if (parent != '') {
- name = '${parent}_${descriptorName}';
+ descriptorName = '${parent}_${descriptorName}';
}
- if (name == 'Function') {
- name = 'Function_'; // Avoid reserved word.
- } else if (name == 'List') {
- name = 'List_';
- } else if (name.startsWith('Function_')) {
- // Avoid any further name conflicts due to 'Function' rename (unlikely).
- name = name + '_';
- }
- return name;
+ return disambiguateName(
+ avoidInitialUnderscore(descriptorName), usedNames, defaultSuffixes());
}
/// Returns the set of names reserved by the ProtobufEnum class and its
@@ -120,18 +162,12 @@
..addAll(ProtobufEnum_reservedNames)
..addAll(_protobufEnumNames);
-/// Chooses the ProtobufEnum names for each value.
-///
-/// Since the values all have the same type as the containing enum class, it
-/// does not require all the same checks as for message member names. Still,
-/// it needs to be checked against a list of reserved names to avoid collisions.
-String unusedEnumNames(String name, Set<String> existingNames) {
- final suffix = '_';
- while (existingNames.contains(name)) {
- name += suffix;
+Iterable<String> enumSuffixes() sync* {
+ String s = '_';
+ while (true) {
+ yield s;
+ s += '_';
}
- existingNames.add(name);
- return name;
}
/// Chooses the GeneratedMessage member names for each field.
@@ -161,9 +197,7 @@
}
var existingNames = new Set<String>()
- ..addAll(_dartReservedWords)
- ..addAll(GeneratedMessage_reservedNames)
- ..addAll(_generatedMessageNames)
+ ..addAll(reservedMemberNames)
..addAll(reserved);
var memberNames = <String, MemberNames>{};
@@ -234,7 +268,7 @@
checkAvailable(name);
if (_isRepeated(field)) {
- return new MemberNames.forRepeatedField(field, index, name);
+ return new MemberNames(field, index, name);
}
String hasMethod = "has${_capitalize(name)}";
@@ -243,47 +277,55 @@
String clearMethod = "clear${_capitalize(name)}";
checkAvailable(clearMethod);
- return new MemberNames(field, index, name, hasMethod, clearMethod);
+ return new MemberNames(field, index, name,
+ hasMethodName: hasMethod, clearMethodName: clearMethod);
+}
+
+Iterable<String> _memberNamesSuffix(int number) sync* {
+ String suffix = '_$number';
+ while (true) {
+ yield suffix;
+ suffix = '${suffix}_$number';
+ }
}
MemberNames _unusedMemberNames(
FieldDescriptorProto field, int index, Set<String> existingNames) {
- var suffix = '_' + field.number.toString();
-
if (_isRepeated(field)) {
- var name = _defaultFieldName(field);
- while (existingNames.contains(name)) {
- name += suffix;
- }
- return new MemberNames.forRepeatedField(field, index, name);
+ return new MemberNames(
+ field,
+ index,
+ disambiguateName(_defaultFieldName(_fieldMethodSuffix(field)),
+ existingNames, _memberNamesSuffix(field.number)));
}
- String name = _defaultFieldName(field);
- String hasMethod = _defaultHasMethodName(field);
- String clearMethod = _defaultClearMethodName(field);
-
- while (existingNames.contains(name) ||
- existingNames.contains(hasMethod) ||
- existingNames.contains(clearMethod)) {
- name += suffix;
- hasMethod += suffix;
- clearMethod += suffix;
+ List<String> generateNameVariants(String name) {
+ return [
+ _defaultFieldName(name),
+ _defaultHasMethodName(name),
+ _defaultClearMethodName(name)
+ ];
}
- return new MemberNames(field, index, name, hasMethod, clearMethod);
+
+ String name = disambiguateName(_fieldMethodSuffix(field), existingNames,
+ _memberNamesSuffix(field.number),
+ generateVariants: generateNameVariants);
+ return new MemberNames(field, index, _defaultFieldName(name),
+ hasMethodName: _defaultHasMethodName(name),
+ clearMethodName: _defaultClearMethodName(name));
}
/// The name to use by default for the Dart getter and setter.
/// (A suffix will be added if there is a conflict.)
-String _defaultFieldName(FieldDescriptorProto field) {
- String name = _fieldMethodSuffix(field);
- return '${name[0].toLowerCase()}${name.substring(1)}';
+String _defaultFieldName(String fieldMethodSuffix) {
+ return '${fieldMethodSuffix[0].toLowerCase()}${fieldMethodSuffix.substring(1)}';
}
-String _defaultHasMethodName(FieldDescriptorProto field) =>
- 'has${_fieldMethodSuffix(field)}';
+String _defaultHasMethodName(String fieldMethodSuffix) =>
+ 'has$fieldMethodSuffix';
-String _defaultClearMethodName(FieldDescriptorProto field) =>
- 'clear${_fieldMethodSuffix(field)}';
+String _defaultClearMethodName(String fieldMethodSuffix) =>
+ 'clear$fieldMethodSuffix';
/// The suffix to use for this field in Dart method names.
/// (It should be camelcase and begin with an uppercase letter.)
@@ -319,6 +361,24 @@
final _dartFieldNameExpr = new RegExp(r'^[a-z]\w+$');
+/// Names that would collide with capitalized core Dart names as top-level
+/// identifiers.
+final List<String> toplevelReservedCapitalizedNames = const <String>[
+ 'List',
+ 'Function',
+ 'Map',
+];
+
+final List<String> reservedMemberNames = <String>[]
+ ..addAll(_dartReservedWords)
+ ..addAll(GeneratedMessage_reservedNames)
+ ..addAll(_generatedMessageNames);
+
+final List<String> forbiddenExtensionNames = <String>[]
+ ..addAll(_dartReservedWords)
+ ..addAll(GeneratedMessage_reservedNames)
+ ..addAll(_generatedMessageNames);
+
// List of Dart language reserved words in names which cannot be used in a
// subclass of GeneratedMessage.
const List<String> _dartReservedWords = const [
diff --git a/lib/protobuf_field.dart b/lib/protobuf_field.dart
index ce53c7d..83eca16 100644
--- a/lib/protobuf_field.dart
+++ b/lib/protobuf_field.dart
@@ -291,7 +291,7 @@
descriptor.defaultValue.isNotEmpty) {
return '$className.${descriptor.defaultValue}';
} else if (gen._canonicalValues.isNotEmpty) {
- return '$className.${gen._canonicalValues[0].name}';
+ return '$className.${gen.dartNames[gen._canonicalValues[0].name]}';
}
return null;
default:
diff --git a/lib/service_generator.dart b/lib/service_generator.dart
index 4f9f183..e06d5fc 100644
--- a/lib/service_generator.dart
+++ b/lib/service_generator.dart
@@ -27,16 +27,22 @@
/// Populated by [resolve].
final _undefinedDeps = <String, String>{};
- ServiceGenerator(this._descriptor, this.fileGen);
+ final String classname;
- String get classname {
- if (_descriptor.name.endsWith("Service")) {
- return _descriptor.name + "Base"; // avoid: ServiceServiceBase
+ static String serviceBaseName(String originalName) {
+ if (originalName.endsWith("Service")) {
+ return originalName + "Base"; // avoid: ServiceServiceBase
} else {
- return _descriptor.name + "ServiceBase";
+ return originalName + "ServiceBase";
}
}
+ ServiceGenerator(this._descriptor, this.fileGen, Set<String> usedNames)
+ : classname = disambiguateName(
+ serviceBaseName(avoidInitialUnderscore(_descriptor.name)),
+ usedNames,
+ defaultSuffixes());
+
/// Finds all message types used by this service.
///
/// Puts the types found in [_deps] and [_transitiveDeps].
@@ -194,8 +200,8 @@
out.println();
}
- String get jsonConstant => "${_descriptor.name}\$json";
- String get messageJsonConstant => "${_descriptor.name}\$messageJson";
+ String get jsonConstant => "$classname\$json";
+ String get messageJsonConstant => "$classname\$messageJson";
/// Writes Dart constants for the service and message descriptors.
///
diff --git a/pubspec.yaml b/pubspec.yaml
index dac6d95..7d7f545 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: protoc_plugin
-version: 11.0.0
+version: 12.0.0
author: Dart Team <misc@dartlang.org>
description: Protoc compiler plugin to generate Dart code
homepage: https://github.com/dart-lang/dart-protoc-plugin
diff --git a/test/all_tests.dart b/test/all_tests.dart
index e9e159b..fec7447 100755
--- a/test/all_tests.dart
+++ b/test/all_tests.dart
@@ -17,6 +17,7 @@
import 'indenting_writer_test.dart' as indenting_writer;
import 'import_test.dart' as import_prefix;
import 'json_test.dart' as json;
+import 'leading_underscores_test.dart' as leading_underscores;
import 'map_test.dart' as map;
import 'message_generator_test.dart' as message_generator;
import 'message_test.dart' as message;
@@ -43,6 +44,7 @@
indenting_writer.main();
import_prefix.main();
json.main();
+ leading_underscores.main();
map.main();
message_generator.main();
message.main();
diff --git a/test/enum_generator_test.dart b/test/enum_generator_test.dart
index a5f8fc5..d1cd2f1 100755
--- a/test/enum_generator_test.dart
+++ b/test/enum_generator_test.dart
@@ -33,7 +33,7 @@
IndentingWriter writer = new IndentingWriter();
FileGenerator fg =
new FileGenerator(new FileDescriptorProto(), new GenerationOptions());
- EnumGenerator eg = new EnumGenerator(ed, fg);
+ EnumGenerator eg = new EnumGenerator(ed, fg, new Set<String>());
eg.generate(writer);
expectMatchesGoldenFile(writer.toString(), 'test/goldens/enum');
});
diff --git a/test/goldens/service.pbserver b/test/goldens/service.pbserver
index f50d95b..cf1be0c 100644
--- a/test/goldens/service.pbserver
+++ b/test/goldens/service.pbserver
@@ -30,7 +30,7 @@
}
}
- Map<String, dynamic> get $json => Test$json;
- Map<String, Map<String, dynamic>> get $messageJson => Test$messageJson;
+ Map<String, dynamic> get $json => TestServiceBase$json;
+ Map<String, Map<String, dynamic>> get $messageJson => TestServiceBase$messageJson;
}
diff --git a/test/goldens/serviceGenerator b/test/goldens/serviceGenerator
index 00f9075..f5eb2e9 100644
--- a/test/goldens/serviceGenerator
+++ b/test/goldens/serviceGenerator
@@ -18,7 +18,7 @@
}
}
- Map<String, dynamic> get $json => Test$json;
- Map<String, Map<String, dynamic>> get $messageJson => Test$messageJson;
+ Map<String, dynamic> get $json => TestServiceBase$json;
+ Map<String, Map<String, dynamic>> get $messageJson => TestServiceBase$messageJson;
}
diff --git a/test/goldens/serviceGenerator.pb.json b/test/goldens/serviceGenerator.pb.json
new file mode 100644
index 0000000..96cdc7a
--- /dev/null
+++ b/test/goldens/serviceGenerator.pb.json
@@ -0,0 +1,31 @@
+///
+// Generated code. Do not modify.
+// source: testpkg.proto
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes,unused_import
+
+import 'foobar.pbjson.dart' as $0;
+
+const SomeRequest$json = const {
+ '1': 'SomeRequest',
+};
+
+const SomeReply$json = const {
+ '1': 'SomeReply',
+};
+
+const TestServiceBase$json = const {
+ '1': 'Test',
+ '2': const [
+ const {'1': 'AMethod', '2': '.testpkg.SomeRequest', '3': '.testpkg.SomeReply'},
+ const {'1': 'AnotherMethod', '2': '.foo.bar.EmptyMessage', '3': '.foo.bar.AnotherReply'},
+ ],
+};
+
+const TestServiceBase$messageJson = const {
+ '.testpkg.SomeRequest': SomeRequest$json,
+ '.testpkg.SomeReply': SomeReply$json,
+ '.foo.bar.EmptyMessage': $0.EmptyMessage$json,
+ '.foo.bar.AnotherReply': $0.AnotherReply$json,
+};
+
diff --git a/test/leading_underscores_test.dart b/test/leading_underscores_test.dart
new file mode 100644
index 0000000..8227542
--- /dev/null
+++ b/test/leading_underscores_test.dart
@@ -0,0 +1,45 @@
+#!/usr/bin/env dart
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test/test.dart';
+import 'package:fixnum/fixnum.dart';
+
+import '../out/protos/_leading_underscores.pb.dart';
+
+void main() {
+ test('can set, read and clear all fields and refer to types', () {
+ A_ message = new A_();
+ message.setExtension(Leading_underscores_.p, Int64(99));
+ expect(message.getExtension(Leading_underscores_.p), Int64(99));
+ message.f = 'foo';
+ message.f_2 = 'foo2';
+ expect(message.f, 'foo');
+ expect(message.f_2, 'foo2');
+ message.clearF();
+ message.clearF_2();
+ expect(message.hasF(), false);
+ expect(message.hasF_2(), false);
+ expect(message.f, '');
+ expect(message.f_2, '');
+ A messageA = new A();
+ messageA.b = message;
+ messageA.b_6 = message;
+ expect(messageA.b_6, message);
+ messageA.amap['foo'] = message;
+ expect(messageA.amap['foo'], message);
+
+ messageA.e = Enum_.constant;
+ expect(messageA.e, Enum_.constant);
+ messageA.clearE();
+ expect(messageA.e, Enum_.constant_);
+ messageA.r.add(message);
+ expect(messageA.r, [message]);
+ messageA.setExtension(Leading_underscores_.q, Int64(100));
+ expect(messageA.getExtension(Leading_underscores_.q), Int64(100));
+
+ A__ a = A__()..foo = 'hi';
+ expect(a.foo, 'hi');
+ });
+}
diff --git a/test/message_generator_test.dart b/test/message_generator_test.dart
index 1187cd1..70b0341 100755
--- a/test/message_generator_test.dart
+++ b/test/message_generator_test.dart
@@ -61,7 +61,8 @@
new CodeGeneratorRequest(), new CodeGeneratorResponse());
FileGenerator fg = new FileGenerator(fd, options);
- MessageGenerator mg = new MessageGenerator(md, fg, {}, null);
+ MessageGenerator mg =
+ new MessageGenerator(md, fg, {}, null, new Set<String>());
var ctx = new GenerationContext(options);
mg.register(ctx);
diff --git a/test/names_test.dart b/test/names_test.dart
index 95906c0..667d11d 100644
--- a/test/names_test.dart
+++ b/test/names_test.dart
@@ -86,6 +86,61 @@
new pb.Function_()..fun = 'renamed';
new pb.Function__()..fun1 = 'also renamed';
});
+
+ test('disambiguateName', () {
+ Iterable<String> oneTwoThree() sync* {
+ yield* ['_one', '_two', '_three'];
+ }
+
+ {
+ final used = Set<String>.from(['moo']);
+ expect(names.disambiguateName('foo', used, oneTwoThree()), 'foo');
+ expect(used, Set<String>.from(['moo', 'foo']));
+ }
+ {
+ final used = Set<String>.from(['foo']);
+ expect(names.disambiguateName('foo', used, oneTwoThree()), 'foo_one');
+ expect(used, Set<String>.from(['foo', 'foo_one']));
+ }
+ {
+ final used = Set<String>.from(['foo', 'foo_one']);
+ expect(names.disambiguateName('foo', used, oneTwoThree()), 'foo_two');
+ expect(used, Set<String>.from(['foo', 'foo_one', 'foo_two']));
+ }
+
+ {
+ List<String> variants(String s) {
+ return ['a_' + s, 'b_' + s];
+ }
+
+ final used = Set<String>.from(['a_foo', 'b_foo_one']);
+ expect(
+ names.disambiguateName('foo', used, oneTwoThree(),
+ generateVariants: variants),
+ 'foo_two');
+ expect(used,
+ Set<String>.from(['a_foo', 'b_foo_one', 'a_foo_two', 'b_foo_two']));
+ }
+ });
+
+ test('avoidInitialUnderscore', () {
+ expect(names.avoidInitialUnderscore('foo'), 'foo');
+ expect(names.avoidInitialUnderscore('foo_'), 'foo_');
+ expect(names.avoidInitialUnderscore('_foo'), 'foo_');
+ expect(names.avoidInitialUnderscore('__foo'), 'foo__');
+ });
+
+ test('legalDartIdentifier', () {
+ expect(names.legalDartIdentifier("foo"), "foo");
+ expect(names.legalDartIdentifier("_foo"), "_foo");
+ expect(names.legalDartIdentifier("-foo"), "_foo");
+ expect(names.legalDartIdentifier("foo.\$a{b}c(d)e_"), "foo_\$a_b_c_d_e_");
+ });
+
+ test('defaultSuffixes', () {
+ expect(names.defaultSuffixes().take(5).toList(),
+ ['_', '_0', '_1', '_2', '_3']);
+ });
}
FieldDescriptorProto stringField(String name, int number, String dartName) {
diff --git a/test/protos/_leading_underscores.proto b/test/protos/_leading_underscores.proto
new file mode 100644
index 0000000..35d7688
--- /dev/null
+++ b/test/protos/_leading_underscores.proto
@@ -0,0 +1,51 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+syntax = "proto2";
+
+package _leading_underscores;
+
+message _A {
+ optional string _f = 1;
+ optional string f = 2;
+ extensions 3 to 4;
+ extend A {
+ optional int64 _q = 3;
+ }
+}
+
+message A_ {
+ optional string foo = 1;
+}
+
+message A {
+ optional string _f = 1;
+ optional string f = 2;
+ extensions 3 to 4;
+ optional _A b = 5;
+ optional _A _b = 6;
+ optional _A _c = 7;
+ optional _Enum _e = 8;
+ map<string, _A> _amap = 9;
+ repeated _A _r = 10;
+}
+
+extend _A {
+ optional int64 _p = 3;
+ optional int64 p = 4;
+}
+
+extend A {
+ optional int64 _q = 4;
+}
+
+enum _Enum {
+ _constant = 0;
+ constant = 1;
+}
+
+service _service {
+ rpc _search (_A) returns (_A);
+ rpc search (_A) returns (_A);
+}
diff --git a/test/service_generator_test.dart b/test/service_generator_test.dart
index 3c4ef2a..ca15923 100644
--- a/test/service_generator_test.dart
+++ b/test/service_generator_test.dart
@@ -26,8 +26,11 @@
link(new GenerationOptions(), [fg, fg2]);
- var writer = new IndentingWriter();
- fg.serviceGenerators[0].generate(writer);
- expectMatchesGoldenFile(writer.toString(), 'test/goldens/serviceGenerator');
+ var serviceWriter = new IndentingWriter();
+ fg.serviceGenerators[0].generate(serviceWriter);
+ expectMatchesGoldenFile(
+ serviceWriter.toString(), 'test/goldens/serviceGenerator');
+ expectMatchesGoldenFile(
+ fg.generateJsonFile(), 'test/goldens/serviceGenerator.pb.json');
});
}