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 |