Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 import 'package:protobuf/meta.dart'; | |
| 6 import 'package:protoc_plugin/src/dart_options.pb.dart'; | |
| 7 import 'package:protoc_plugin/src/descriptor.pb.dart'; | |
| 8 | |
| 9 /// A Dart function called on each item added to a repeated list | |
| 10 /// to check its type and range. | |
| 11 const checkItem = '\$checkItem'; | |
| 12 | |
| 13 /// The Dart member names in a GeneratedMessage subclass for one protobuf field. | |
| 14 class MemberNames { | |
| 15 /// The descriptor of the field these member names apply to. | |
| 16 final FieldDescriptorProto descriptor; | |
| 17 | |
| 18 /// The index of this field in MessageGenerator.fieldList. | |
| 19 /// The same index will be stored in FieldInfo.index. | |
| 20 final int index; | |
| 21 | |
| 22 /// Identifier for generated getters/setters. | |
| 23 final String fieldName; | |
| 24 | |
| 25 /// Identifier for the generated hasX() method, without braces. | |
| 26 /// | |
| 27 /// `null` for repeated fields. | |
| 28 final String hasMethodName; | |
| 29 | |
| 30 /// Identifier for the generated clearX() method, without braces. | |
| 31 /// | |
| 32 /// `null` for repeated fields. | |
| 33 final String clearMethodName; | |
| 34 | |
| 35 MemberNames(this.descriptor, this.index, this.fieldName, this.hasMethodName, | |
| 36 this.clearMethodName); | |
| 37 | |
| 38 MemberNames.forRepeatedField(this.descriptor, this.index, this.fieldName) | |
| 39 : hasMethodName = null, | |
| 40 clearMethodName = null; | |
| 41 } | |
| 42 | |
| 43 /// Chooses the Dart name of an extension. | |
| 44 String extensionName(FieldDescriptorProto descriptor) { | |
| 45 var existingNames = new Set<String>() | |
| 46 ..addAll(_dartReservedWords) | |
| 47 ..addAll(GeneratedMessage_reservedNames) | |
| 48 ..addAll(_generatedNames); | |
| 49 return _unusedMemberNames(descriptor, null, existingNames).fieldName; | |
| 50 } | |
| 51 | |
| 52 // Exception thrown when a field has an invalid 'dart_name' option. | |
| 53 class DartNameOptionException implements Exception { | |
| 54 final String message; | |
| 55 DartNameOptionException(this.message); | |
| 56 String toString() => "$message"; | |
| 57 } | |
| 58 | |
| 59 /// Chooses the GeneratedMessage member names for each field. | |
| 60 /// | |
| 61 /// Additional names to avoid can be supplied using [reserved]. | |
| 62 /// (This should only be used for mixins.) | |
| 63 /// | |
| 64 /// Returns a map from the field name in the .proto file to its | |
| 65 /// corresponding MemberNames. | |
| 66 /// | |
| 67 /// Throws [DartNameOptionException] if a field has this option and | |
| 68 /// it's set to an invalid name. | |
| 69 Map<String, MemberNames> messageFieldNames(DescriptorProto descriptor, | |
| 70 {Iterable<String> reserved: const []}) { | |
| 71 var sorted = new List<FieldDescriptorProto>.from(descriptor.field) | |
| 72 ..sort((FieldDescriptorProto a, FieldDescriptorProto b) { | |
| 73 if (a.number < b.number) return -1; | |
| 74 if (a.number > b.number) return 1; | |
| 75 throw "multiple fields defined for tag ${a.number} in ${descriptor.name}"; | |
| 76 }); | |
| 77 | |
| 78 // Choose indexes first, based on their position in the sorted list. | |
| 79 var indexes = <String, int>{}; | |
| 80 for (var field in sorted) { | |
| 81 var index = indexes.length; | |
| 82 indexes[field.name] = index; | |
| 83 } | |
| 84 | |
| 85 var existingNames = new Set<String>() | |
| 86 ..addAll(_dartReservedWords) | |
| 87 ..addAll(GeneratedMessage_reservedNames) | |
| 88 ..addAll(_generatedNames) | |
| 89 ..addAll(reserved); | |
| 90 | |
| 91 var memberNames = <String, MemberNames>{}; | |
| 92 | |
| 93 void takeNames(MemberNames chosen) { | |
| 94 memberNames[chosen.descriptor.name] = chosen; | |
| 95 | |
| 96 existingNames.add(chosen.fieldName); | |
| 97 if (chosen.hasMethodName != null) { | |
| 98 existingNames.add(chosen.hasMethodName); | |
| 99 } | |
| 100 if (chosen.clearMethodName != null) { | |
| 101 existingNames.add(chosen.clearMethodName); | |
| 102 } | |
| 103 } | |
| 104 | |
| 105 // Handle fields with a dart_name option. | |
| 106 // They have higher priority than automatically chosen names. | |
| 107 // Explicitly setting a name that's already taken is a build error. | |
| 108 for (var field in sorted) { | |
| 109 if (_nameOption(field).isNotEmpty) { | |
| 110 takeNames(_memberNamesFromOption( | |
| 111 descriptor, field, indexes[field.name], existingNames)); | |
| 112 } | |
| 113 } | |
| 114 | |
| 115 // Then do other fields. | |
| 116 // They are automatically renamed until we find something unused. | |
| 117 for (var field in sorted) { | |
| 118 if (_nameOption(field).isEmpty) { | |
| 119 var index = indexes[field.name]; | |
| 120 takeNames(_unusedMemberNames(field, index, existingNames)); | |
| 121 } | |
| 122 } | |
| 123 | |
| 124 // Return a map with entries in sorted order. | |
| 125 var result = <String, MemberNames>{}; | |
| 126 for (var field in sorted) { | |
| 127 result[field.name] = memberNames[field.name]; | |
| 128 } | |
| 129 return result; | |
| 130 } | |
| 131 | |
| 132 /// Chooses the member names for a field that has the 'dart_name' option. | |
| 133 /// | |
| 134 /// If the explicitly-set Dart name is already taken, throw an exception. | |
| 135 /// (Fails the build.) | |
| 136 MemberNames _memberNamesFromOption(DescriptorProto message, | |
| 137 FieldDescriptorProto field, int index, Set<String> existingNames) { | |
| 138 // TODO(skybrian): provide more context in errors (filename). | |
| 139 var where = "${message.name}.${field.name}"; | |
| 140 | |
| 141 void checkAvailable(String name) { | |
| 142 if (existingNames.contains(name)) { | |
| 143 throw throw new DartNameOptionException( | |
|
frederikmutzel
2016/06/09 07:50:28
double throw
skybrian
2016/06/09 19:10:47
Done.
| |
| 144 "$where: dart_name option is invalid: '$name' is already used"); | |
| 145 } | |
| 146 } | |
| 147 | |
| 148 var name = _nameOption(field); | |
| 149 if (name.isEmpty) { | |
| 150 throw new ArgumentError("field doesn't have dart_name option"); | |
| 151 } | |
| 152 if (!_isDartFieldName(name)) { | |
| 153 throw new DartNameOptionException("$where: dart_name option is invalid: " | |
| 154 "'$name' is not a valid Dart field name"); | |
| 155 } | |
| 156 checkAvailable(name); | |
| 157 | |
| 158 if (_isRepeated(field)) { | |
| 159 return new MemberNames.forRepeatedField(field, index, name); | |
| 160 } | |
| 161 | |
| 162 String hasMethod = "has${_capitalize(name)}"; | |
| 163 checkAvailable(hasMethod); | |
| 164 | |
| 165 String clearMethod = "clear${_capitalize(name)}"; | |
| 166 checkAvailable(clearMethod); | |
| 167 | |
| 168 return new MemberNames(field, index, name, hasMethod, clearMethod); | |
| 169 } | |
| 170 | |
| 171 MemberNames _unusedMemberNames( | |
| 172 FieldDescriptorProto field, int index, Set<String> existingNames) { | |
| 173 var suffix = '_' + field.number.toString(); | |
| 174 | |
| 175 if (_isRepeated(field)) { | |
| 176 var name = _defaultFieldName(field); | |
| 177 while (existingNames.contains(name)) { | |
| 178 name += suffix; | |
| 179 } | |
| 180 return new MemberNames.forRepeatedField(field, index, name); | |
| 181 } | |
| 182 | |
| 183 String name = _defaultFieldName(field); | |
| 184 String hasMethod = _defaultHasMethodName(field); | |
| 185 String clearMethod = _defaultClearMethodName(field); | |
| 186 | |
| 187 while (existingNames.contains(name) || | |
| 188 existingNames.contains(hasMethod) || | |
| 189 existingNames.contains(clearMethod)) { | |
| 190 name += suffix; | |
| 191 hasMethod += suffix; | |
| 192 clearMethod += suffix; | |
| 193 } | |
| 194 return new MemberNames(field, index, name, hasMethod, clearMethod); | |
| 195 } | |
| 196 | |
| 197 /// The name to use by default for the Dart getter and setter. | |
| 198 /// (A suffix will be added if there is a conflict.) | |
| 199 String _defaultFieldName(FieldDescriptorProto field) { | |
| 200 String name = _fieldMethodSuffix(field); | |
| 201 return '${name[0].toLowerCase()}${name.substring(1)}'; | |
| 202 } | |
| 203 | |
| 204 String _defaultHasMethodName(FieldDescriptorProto field) => | |
| 205 'has${_fieldMethodSuffix(field)}'; | |
| 206 | |
| 207 String _defaultClearMethodName(FieldDescriptorProto field) => | |
| 208 'clear${_fieldMethodSuffix(field)}'; | |
| 209 | |
| 210 /// The suffix to use for this field in Dart method names. | |
| 211 /// (It should be camelcase and begin with an uppercase letter.) | |
| 212 String _fieldMethodSuffix(FieldDescriptorProto field) { | |
| 213 var name = _nameOption(field); | |
| 214 if (name.isNotEmpty) return _capitalize(name); | |
| 215 | |
| 216 if (field.type != FieldDescriptorProto_Type.TYPE_GROUP) { | |
| 217 return _underscoresToCamelCase(field.name); | |
| 218 } | |
| 219 | |
| 220 // For groups, use capitalization of 'typeName' rather than 'name'. | |
| 221 name = field.typeName; | |
| 222 int index = name.lastIndexOf('.'); | |
| 223 if (index != -1) { | |
| 224 name = name.substring(index + 1); | |
| 225 } | |
| 226 return _underscoresToCamelCase(name); | |
| 227 } | |
| 228 | |
| 229 String _underscoresToCamelCase(s) => s.split('_').map(_capitalize).join(''); | |
| 230 | |
| 231 String _capitalize(s) => | |
| 232 s.isEmpty ? s : '${s[0].toUpperCase()}${s.substring(1)}'; | |
| 233 | |
| 234 bool _isRepeated(FieldDescriptorProto field) => | |
| 235 field.label == FieldDescriptorProto_Label.LABEL_REPEATED; | |
| 236 | |
| 237 String _nameOption(FieldDescriptorProto field) => | |
| 238 field.options.getExtension(Dart_options.dartName); | |
| 239 | |
| 240 bool _isDartFieldName(name) => name.startsWith(_dartFieldNameExpr); | |
| 241 | |
| 242 final _dartFieldNameExpr = new RegExp(r'^[a-z]\w+$'); | |
| 243 | |
| 244 // List of Dart language reserved words in names which cannot be used in a | |
| 245 // subclass of GeneratedMessage. | |
| 246 const List<String> _dartReservedWords = const [ | |
| 247 'assert', | |
| 248 'break', | |
| 249 'case', | |
| 250 'catch', | |
| 251 'class', | |
| 252 'const', | |
| 253 'continue', | |
| 254 'default', | |
| 255 'do', | |
| 256 'else', | |
| 257 'enum', | |
| 258 'extends', | |
| 259 'false', | |
| 260 'final', | |
| 261 'finally', | |
| 262 'for', | |
| 263 'if', | |
| 264 'in', | |
| 265 'is', | |
| 266 'new', | |
| 267 'null', | |
| 268 'rethrow', | |
| 269 'return', | |
| 270 'super', | |
| 271 'switch', | |
| 272 'this', | |
| 273 'throw', | |
| 274 'true', | |
| 275 'try', | |
| 276 'var', | |
| 277 'void', | |
| 278 'while', | |
| 279 'with' | |
| 280 ]; | |
| 281 | |
| 282 // List of names used in the generated class itself. | |
| 283 const List<String> _generatedNames = const [ | |
| 284 'create', | |
| 285 'createRepeated', | |
| 286 'getDefault', | |
| 287 checkItem | |
| 288 ]; | |
| OLD | NEW |