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

Side by Side 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 unified diff | Download patch
OLDNEW
(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 ];
OLDNEW
« 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