Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(366)

Unified Diff: lib/names.dart

Issue 2043913005: Change how to set the Dart name of a field (Closed) Base URL: git@github.com:dart-lang/dart-protoc-plugin.git@master
Patch Set: better error checking, clean up pubspec Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: lib/names.dart
diff --git a/lib/names.dart b/lib/names.dart
new file mode 100644
index 0000000000000000000000000000000000000000..24a9e66bb1d9432e4cbc53ded2872fe89c6d5e91
--- /dev/null
+++ b/lib/names.dart
@@ -0,0 +1,288 @@
+// Copyright (c) 2016, 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:protobuf/meta.dart';
+import 'package:protoc_plugin/src/dart_options.pb.dart';
+import 'package:protoc_plugin/src/descriptor.pb.dart';
+
+/// A Dart function called on each item added to a repeated list
+/// to check its type and range.
+const checkItem = '\$checkItem';
+
+/// The Dart member names in a GeneratedMessage subclass for one protobuf field.
+class MemberNames {
+ /// The descriptor of the field these member names apply to.
+ final FieldDescriptorProto descriptor;
+
+ /// The index of this field in MessageGenerator.fieldList.
+ /// The same index will be stored in FieldInfo.index.
+ final int index;
+
+ /// Identifier for generated getters/setters.
+ final String fieldName;
+
+ /// Identifier for the generated hasX() method, without braces.
+ ///
+ /// `null` for repeated fields.
+ final String hasMethodName;
+
+ /// Identifier for the generated clearX() method, without braces.
+ ///
+ /// `null` for repeated fields.
+ final String clearMethodName;
+
+ MemberNames(this.descriptor, this.index, this.fieldName, this.hasMethodName,
+ this.clearMethodName);
+
+ MemberNames.forRepeatedField(this.descriptor, this.index, this.fieldName)
+ : hasMethodName = null,
+ clearMethodName = null;
+}
+
+/// Chooses the Dart name of an extension.
+String extensionName(FieldDescriptorProto descriptor) {
+ var existingNames = new Set<String>()
+ ..addAll(_dartReservedWords)
+ ..addAll(GeneratedMessage_reservedNames)
+ ..addAll(_generatedNames);
+ return _unusedMemberNames(descriptor, null, existingNames).fieldName;
+}
+
+// Exception thrown when a field has an invalid 'dart_name' option.
+class DartNameOptionException implements Exception {
+ final String message;
+ DartNameOptionException(this.message);
+ String toString() => "$message";
+}
+
+/// Chooses the GeneratedMessage member names for each field.
+///
+/// Additional names to avoid can be supplied using [reserved].
+/// (This should only be used for mixins.)
+///
+/// Returns a map from the field name in the .proto file to its
+/// corresponding MemberNames.
+///
+/// Throws [DartNameOptionException] if a field has this option and
+/// it's set to an invalid name.
+Map<String, MemberNames> messageFieldNames(DescriptorProto descriptor,
+ {Iterable<String> reserved: const []}) {
+ var sorted = new List<FieldDescriptorProto>.from(descriptor.field)
+ ..sort((FieldDescriptorProto a, FieldDescriptorProto b) {
+ if (a.number < b.number) return -1;
+ if (a.number > b.number) return 1;
+ throw "multiple fields defined for tag ${a.number} in ${descriptor.name}";
+ });
+
+ // Choose indexes first, based on their position in the sorted list.
+ var indexes = <String, int>{};
+ for (var field in sorted) {
+ var index = indexes.length;
+ indexes[field.name] = index;
+ }
+
+ var existingNames = new Set<String>()
+ ..addAll(_dartReservedWords)
+ ..addAll(GeneratedMessage_reservedNames)
+ ..addAll(_generatedNames)
+ ..addAll(reserved);
+
+ var memberNames = <String, MemberNames>{};
+
+ void takeNames(MemberNames chosen) {
+ memberNames[chosen.descriptor.name] = chosen;
+
+ existingNames.add(chosen.fieldName);
+ if (chosen.hasMethodName != null) {
+ existingNames.add(chosen.hasMethodName);
+ }
+ if (chosen.clearMethodName != null) {
+ existingNames.add(chosen.clearMethodName);
+ }
+ }
+
+ // Handle fields with a dart_name option.
+ // They have higher priority than automatically chosen names.
+ // Explicitly setting a name that's already taken is a build error.
+ for (var field in sorted) {
+ if (_nameOption(field).isNotEmpty) {
+ takeNames(_memberNamesFromOption(
+ descriptor, field, indexes[field.name], existingNames));
+ }
+ }
+
+ // Then do other fields.
+ // They are automatically renamed until we find something unused.
+ for (var field in sorted) {
+ if (_nameOption(field).isEmpty) {
+ var index = indexes[field.name];
+ takeNames(_unusedMemberNames(field, index, existingNames));
+ }
+ }
+
+ // Return a map with entries in sorted order.
+ var result = <String, MemberNames>{};
+ for (var field in sorted) {
+ result[field.name] = memberNames[field.name];
+ }
+ return result;
+}
+
+/// Chooses the member names for a field that has the 'dart_name' option.
+///
+/// If the explicitly-set Dart name is already taken, throw an exception.
+/// (Fails the build.)
+MemberNames _memberNamesFromOption(DescriptorProto message,
+ FieldDescriptorProto field, int index, Set<String> existingNames) {
+ // TODO(skybrian): provide more context in errors (filename).
+ var where = "${message.name}.${field.name}";
+
+ void checkAvailable(String name) {
+ if (existingNames.contains(name)) {
+ throw throw new DartNameOptionException(
frederikmutzel 2016/06/09 07:50:28 double throw
skybrian 2016/06/09 19:10:47 Done.
+ "$where: dart_name option is invalid: '$name' is already used");
+ }
+ }
+
+ var name = _nameOption(field);
+ if (name.isEmpty) {
+ throw new ArgumentError("field doesn't have dart_name option");
+ }
+ if (!_isDartFieldName(name)) {
+ throw new DartNameOptionException("$where: dart_name option is invalid: "
+ "'$name' is not a valid Dart field name");
+ }
+ checkAvailable(name);
+
+ if (_isRepeated(field)) {
+ return new MemberNames.forRepeatedField(field, index, name);
+ }
+
+ String hasMethod = "has${_capitalize(name)}";
+ checkAvailable(hasMethod);
+
+ String clearMethod = "clear${_capitalize(name)}";
+ checkAvailable(clearMethod);
+
+ return new MemberNames(field, index, name, hasMethod, clearMethod);
+}
+
+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);
+ }
+
+ 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;
+ }
+ return new MemberNames(field, index, name, hasMethod, clearMethod);
+}
+
+/// 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 _defaultHasMethodName(FieldDescriptorProto field) =>
+ 'has${_fieldMethodSuffix(field)}';
+
+String _defaultClearMethodName(FieldDescriptorProto field) =>
+ 'clear${_fieldMethodSuffix(field)}';
+
+/// The suffix to use for this field in Dart method names.
+/// (It should be camelcase and begin with an uppercase letter.)
+String _fieldMethodSuffix(FieldDescriptorProto field) {
+ var name = _nameOption(field);
+ if (name.isNotEmpty) return _capitalize(name);
+
+ if (field.type != FieldDescriptorProto_Type.TYPE_GROUP) {
+ return _underscoresToCamelCase(field.name);
+ }
+
+ // For groups, use capitalization of 'typeName' rather than 'name'.
+ name = field.typeName;
+ int index = name.lastIndexOf('.');
+ if (index != -1) {
+ name = name.substring(index + 1);
+ }
+ return _underscoresToCamelCase(name);
+}
+
+String _underscoresToCamelCase(s) => s.split('_').map(_capitalize).join('');
+
+String _capitalize(s) =>
+ s.isEmpty ? s : '${s[0].toUpperCase()}${s.substring(1)}';
+
+bool _isRepeated(FieldDescriptorProto field) =>
+ field.label == FieldDescriptorProto_Label.LABEL_REPEATED;
+
+String _nameOption(FieldDescriptorProto field) =>
+ field.options.getExtension(Dart_options.dartName);
+
+bool _isDartFieldName(name) => name.startsWith(_dartFieldNameExpr);
+
+final _dartFieldNameExpr = new RegExp(r'^[a-z]\w+$');
+
+// List of Dart language reserved words in names which cannot be used in a
+// subclass of GeneratedMessage.
+const List<String> _dartReservedWords = const [
+ 'assert',
+ 'break',
+ 'case',
+ 'catch',
+ 'class',
+ 'const',
+ 'continue',
+ 'default',
+ 'do',
+ 'else',
+ 'enum',
+ 'extends',
+ 'false',
+ 'final',
+ 'finally',
+ 'for',
+ 'if',
+ 'in',
+ 'is',
+ 'new',
+ 'null',
+ 'rethrow',
+ 'return',
+ 'super',
+ 'switch',
+ 'this',
+ 'throw',
+ 'true',
+ 'try',
+ 'var',
+ 'void',
+ 'while',
+ 'with'
+];
+
+// List of names used in the generated class itself.
+const List<String> _generatedNames = const [
+ 'create',
+ 'createRepeated',
+ 'getDefault',
+ checkItem
+];
« no previous file with comments | « lib/message_generator.dart ('k') | lib/options.dart » ('j') | test/names_test.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698