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

Side by Side Diff: pkg/analyzer_plugin/tool/spec/codegen_dart_protocol.dart

Issue 2664213003: Add the generator and the generated files (Closed)
Patch Set: add missed files Created 3 years, 10 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) 2017, 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 'dart:convert';
6
7 import 'package:analyzer/src/codegen/tools.dart';
8 import 'package:html/dom.dart' as dom;
9
10 import 'api.dart';
11 import 'codegen_dart.dart';
12 import 'from_html.dart';
13 import 'implied_types.dart';
14 import 'to_html.dart';
15
16 /**
17 * Special flags that need to be inserted into the declaration of the Element
18 * class.
19 */
20 const Map<String, String> specialElementFlags = const {
21 'abstract': '0x01',
22 'const': '0x02',
23 'final': '0x04',
24 'static': '0x08',
25 'private': '0x10',
26 'deprecated': '0x20'
27 };
28
29 final GeneratedFile target =
30 new GeneratedFile('lib/protocol/generated_protocol.dart', (String pkgPath) {
31 CodegenProtocolVisitor visitor = new CodegenProtocolVisitor(readApi(pkgPath));
32 return visitor.collectCode(visitor.visitApi);
33 });
34
35 /**
36 * Callback type used to represent arbitrary code generation.
37 */
38 typedef void CodegenCallback();
39
40 typedef String FromJsonSnippetCallback(String jsonPath, String json);
41
42 typedef String ToJsonSnippetCallback(String value);
43
44 /**
45 * Visitor which produces Dart code representing the API.
46 */
47 class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
48 /**
49 * Class members for which the constructor argument should be optional, even
50 * if the member is not an optional part of the protocol. For list types,
51 * the constructor will default the member to the empty list.
52 */
53 static const Map<String, List<String>> _optionalConstructorArguments = const {
54 'AnalysisErrorFixes': const ['fixes'],
55 'SourceChange': const ['edits', 'linkedEditGroups'],
56 'SourceFileEdit': const ['edits'],
57 'TypeHierarchyItem': const ['interfaces', 'mixins', 'subclasses'],
58 };
59
60 /**
61 * The disclaimer added to the documentation comment for each of the classes
62 * that are generated.
63 */
64 static const String disclaimer =
65 'Clients may not extend, implement or mix-in this class.';
66
67 /**
68 * Visitor used to produce doc comments.
69 */
70 final ToHtmlVisitor toHtmlVisitor;
71
72 /**
73 * Types implied by the API. This includes types explicitly named in the
74 * API as well as those implied by the definitions of requests, responses,
75 * notifications, etc.
76 */
77 final Map<String, ImpliedType> impliedTypes;
78
79 CodegenProtocolVisitor(Api api)
80 : toHtmlVisitor = new ToHtmlVisitor(api),
81 impliedTypes = computeImpliedTypes(api),
82 super(api) {
83 codeGeneratorSettings.commentLineLength = 79;
84 codeGeneratorSettings.languageName = 'dart';
85 }
86
87 /**
88 * Compute the code necessary to compare two objects for equality.
89 */
90 String compareEqualsCode(TypeDecl type, String thisVar, String otherVar) {
91 TypeDecl resolvedType = resolveTypeReferenceChain(type);
92 if (resolvedType is TypeReference ||
93 resolvedType is TypeEnum ||
94 resolvedType is TypeObject ||
95 resolvedType is TypeUnion) {
96 return '$thisVar == $otherVar';
97 } else if (resolvedType is TypeList) {
98 String itemTypeName = dartType(resolvedType.itemType);
99 String subComparison = compareEqualsCode(resolvedType.itemType, 'a', 'b');
100 String closure = '($itemTypeName a, $itemTypeName b) => $subComparison';
101 return 'listEqual($thisVar, $otherVar, $closure)';
102 } else if (resolvedType is TypeMap) {
103 String valueTypeName = dartType(resolvedType.valueType);
104 String subComparison =
105 compareEqualsCode(resolvedType.valueType, 'a', 'b');
106 String closure = '($valueTypeName a, $valueTypeName b) => $subComparison';
107 return 'mapEqual($thisVar, $otherVar, $closure)';
108 }
109 throw new Exception(
110 "Don't know how to compare for equality: $resolvedType");
111 }
112
113 /**
114 * Translate each type implied by the API to a class.
115 */
116 void emitClasses() {
117 List<ImpliedType> types = impliedTypes.values.toList();
118 types.sort((first, second) =>
119 capitalize(first.camelName).compareTo(capitalize(second.camelName)));
120 for (ImpliedType impliedType in types) {
121 TypeDecl type = impliedType.type;
122 String dartTypeName = capitalize(impliedType.camelName);
123 if (type == null) {
124 writeln();
125 emitEmptyObjectClass(dartTypeName, impliedType);
126 } else if (type is TypeObject) {
127 writeln();
128 emitObjectClass(dartTypeName, type, impliedType);
129 } else if (type is TypeEnum) {
130 writeln();
131 emitEnumClass(dartTypeName, type, impliedType);
132 }
133 }
134 }
135
136 /**
137 * Emit a convenience constructor for decoding a piece of protocol, if
138 * appropriate. Return true if a constructor was emitted.
139 */
140 bool emitConvenienceConstructor(String className, ImpliedType impliedType) {
141 // The type of object from which this piece of protocol should be decoded.
142 String inputType;
143 // The name of the input object.
144 String inputName;
145 // The field within the input object to decode.
146 String fieldName;
147 // Constructor call to create the JsonDecoder object.
148 String makeDecoder;
149 // Name of the constructor to create.
150 String constructorName;
151 // Extra arguments for the constructor.
152 List<String> extraArgs = <String>[];
153 switch (impliedType.kind) {
154 case 'requestParams':
155 inputType = 'Request';
156 inputName = 'request';
157 fieldName = 'params';
158 makeDecoder = 'new RequestDecoder(request)';
159 constructorName = 'fromRequest';
160 break;
161 case 'requestResult':
162 inputType = 'Response';
163 inputName = 'response';
164 fieldName = 'result';
165 makeDecoder =
166 'new ResponseDecoder(REQUEST_ID_REFACTORING_KINDS.remove(response.id ))';
167 constructorName = 'fromResponse';
168 break;
169 case 'notificationParams':
170 inputType = 'Notification';
171 inputName = 'notification';
172 fieldName = 'params';
173 makeDecoder = 'new ResponseDecoder(null)';
174 constructorName = 'fromNotification';
175 break;
176 case 'refactoringOptions':
177 inputType = 'EditGetRefactoringParams';
178 inputName = 'refactoringParams';
179 fieldName = 'options';
180 makeDecoder = 'new RequestDecoder(request)';
181 constructorName = 'fromRefactoringParams';
182 extraArgs.add('Request request');
183 break;
184 default:
185 return false;
186 }
187 List<String> args = ['$inputType $inputName'];
188 args.addAll(extraArgs);
189 writeln('factory $className.$constructorName(${args.join(', ')}) {');
190 indent(() {
191 String fieldNameString =
192 literalString(fieldName.replaceFirst(new RegExp('^_'), ''));
193 if (className == 'EditGetRefactoringParams') {
194 writeln('var params = new $className.fromJson(');
195 writeln(' $makeDecoder, $fieldNameString, $inputName.$fieldName);');
196 writeln('REQUEST_ID_REFACTORING_KINDS[request.id] = params.kind;');
197 writeln('return params;');
198 } else {
199 writeln('return new $className.fromJson(');
200 writeln(' $makeDecoder, $fieldNameString, $inputName.$fieldName);');
201 }
202 });
203 writeln('}');
204 return true;
205 }
206
207 /**
208 * Emit a class representing an data structure that doesn't exist in the
209 * protocol because it is empty (e.g. the "params" object for a request that
210 * doesn't have any parameters).
211 */
212 void emitEmptyObjectClass(String className, ImpliedType impliedType) {
213 docComment(toHtmlVisitor.collectHtml(() {
214 toHtmlVisitor.p(() {
215 toHtmlVisitor.write(impliedType.humanReadableName);
216 });
217 toHtmlVisitor.p(() {
218 toHtmlVisitor.write(disclaimer);
219 });
220 }));
221 write('class $className');
222 if (impliedType.kind == 'refactoringFeedback') {
223 writeln(' extends RefactoringFeedback implements HasToJson {');
224 } else if (impliedType.kind == 'refactoringOptions') {
225 writeln(' extends RefactoringOptions implements HasToJson {');
226 } else if (impliedType.kind == 'requestResult') {
227 writeln(' implements ResponseResult {');
228 } else {
229 writeln(' {');
230 }
231 indent(() {
232 if (impliedType.kind == 'requestResult') {
233 emitEmptyToJsonMember();
234 writeln();
235 }
236 if (emitToRequestMember(impliedType)) {
237 writeln();
238 }
239 if (emitToResponseMember(impliedType)) {
240 writeln();
241 }
242 if (emitToNotificationMember(impliedType)) {
243 writeln();
244 }
245 emitObjectEqualsMember(null, className);
246 writeln();
247 emitObjectHashCode(null, className);
248 });
249 writeln('}');
250 }
251
252 /**
253 * Emit the toJson() code for an empty class.
254 */
255 void emitEmptyToJsonMember() {
256 writeln('@override');
257 writeln('Map<String, dynamic> toJson() => <String, dynamic>{};');
258 }
259
260 /**
261 * Emit a class to encapsulate an enum.
262 */
263 void emitEnumClass(String className, TypeEnum type, ImpliedType impliedType) {
264 docComment(toHtmlVisitor.collectHtml(() {
265 toHtmlVisitor.p(() {
266 toHtmlVisitor.write(impliedType.humanReadableName);
267 });
268 if (impliedType.type != null) {
269 toHtmlVisitor.showType(null, impliedType.type);
270 }
271 toHtmlVisitor.p(() {
272 toHtmlVisitor.write(disclaimer);
273 });
274 }));
275 writeln('class $className implements Enum {');
276 indent(() {
277 if (emitSpecialStaticMembers(className)) {
278 writeln();
279 }
280 for (TypeEnumValue value in type.values) {
281 docComment(toHtmlVisitor.collectHtml(() {
282 toHtmlVisitor.translateHtml(value.html);
283 }));
284 String valueString = literalString(value.value);
285 writeln(
286 'static const $className ${value.value} = const $className._($valueS tring);');
287 writeln();
288 }
289
290 writeln('/**');
291 writeln(' * A list containing all of the enum values that are defined.');
292 writeln(' */');
293 write('static const List<');
294 write(className);
295 write('> VALUES = const <');
296 write(className);
297 write('>[');
298 bool first = true;
299 for (TypeEnumValue value in type.values) {
300 if (first) {
301 first = false;
302 } else {
303 write(', ');
304 }
305 write(value.value);
306 }
307 writeln('];');
308 writeln();
309
310 writeln('@override');
311 writeln('final String name;');
312 writeln();
313 writeln('const $className._(this.name);');
314 writeln();
315 emitEnumClassConstructor(className, type);
316 writeln();
317 emitEnumFromJsonConstructor(className, type, impliedType);
318 writeln();
319 if (emitSpecialConstructors(className)) {
320 writeln();
321 }
322 if (emitSpecialGetters(className)) {
323 writeln();
324 }
325 if (emitSpecialMethods(className)) {
326 writeln();
327 }
328 writeln('@override');
329 writeln('String toString() => "$className.\$name";');
330 writeln();
331 writeln('String toJson() => name;');
332 });
333 writeln('}');
334 }
335
336 /**
337 * Emit the constructor for an enum class.
338 */
339 void emitEnumClassConstructor(String className, TypeEnum type) {
340 writeln('factory $className(String name) {');
341 indent(() {
342 writeln('switch (name) {');
343 indent(() {
344 for (TypeEnumValue value in type.values) {
345 String valueString = literalString(value.value);
346 writeln('case $valueString:');
347 indent(() {
348 writeln('return ${value.value};');
349 });
350 }
351 });
352 writeln('}');
353 writeln(r"throw new Exception('Illegal enum value: $name');");
354 });
355 writeln('}');
356 }
357
358 /**
359 * Emit the method for decoding an enum from JSON.
360 */
361 void emitEnumFromJsonConstructor(
362 String className, TypeEnum type, ImpliedType impliedType) {
363 writeln(
364 'factory $className.fromJson(JsonDecoder jsonDecoder, String jsonPath, O bject json) {');
365 indent(() {
366 writeln('if (json is String) {');
367 indent(() {
368 writeln('try {');
369 indent(() {
370 writeln('return new $className(json);');
371 });
372 writeln('} catch(_) {');
373 indent(() {
374 writeln('// Fall through');
375 });
376 writeln('}');
377 });
378 writeln('}');
379 String humanReadableNameString =
380 literalString(impliedType.humanReadableName);
381 writeln(
382 'throw jsonDecoder.mismatch(jsonPath, $humanReadableNameString, json); ');
383 });
384 writeln('}');
385 }
386
387 /**
388 * Emit the class to encapsulate an object type.
389 */
390 void emitObjectClass(
391 String className, TypeObject type, ImpliedType impliedType) {
392 docComment(toHtmlVisitor.collectHtml(() {
393 toHtmlVisitor.p(() {
394 toHtmlVisitor.write(impliedType.humanReadableName);
395 });
396 if (impliedType.type != null) {
397 toHtmlVisitor.showType(null, impliedType.type);
398 }
399 toHtmlVisitor.p(() {
400 toHtmlVisitor.write(disclaimer);
401 });
402 }));
403 write('class $className');
404 if (impliedType.kind == 'refactoringFeedback') {
405 writeln(' extends RefactoringFeedback {');
406 } else if (impliedType.kind == 'refactoringOptions') {
407 writeln(' extends RefactoringOptions {');
408 } else if (impliedType.kind == 'requestResult') {
409 writeln(' implements ResponseResult {');
410 } else {
411 writeln(' implements HasToJson {');
412 }
413 indent(() {
414 if (emitSpecialStaticMembers(className)) {
415 writeln();
416 }
417 for (TypeObjectField field in type.fields) {
418 if (field.value != null) {
419 continue;
420 }
421 writeln('${dartType(field.type)} _${field.name};');
422 writeln();
423 }
424 for (TypeObjectField field in type.fields) {
425 if (field.value != null) {
426 continue;
427 }
428 docComment(toHtmlVisitor.collectHtml(() {
429 toHtmlVisitor.translateHtml(field.html);
430 }));
431 writeln('${dartType(field.type)} get ${field.name} => _${field.name};');
432 writeln();
433 docComment(toHtmlVisitor.collectHtml(() {
434 toHtmlVisitor.translateHtml(field.html);
435 }));
436 writeln('void set ${field.name}(${dartType(field.type)} value) {');
437 indent(() {
438 if (!field.optional) {
439 writeln('assert(value != null);');
440 }
441 writeln('this._${field.name} = value;');
442 });
443 writeln('}');
444 writeln();
445 }
446 emitObjectConstructor(type, className);
447 writeln();
448 emitObjectFromJsonConstructor(className, type, impliedType);
449 writeln();
450 if (emitConvenienceConstructor(className, impliedType)) {
451 writeln();
452 }
453 if (emitSpecialConstructors(className)) {
454 writeln();
455 }
456 if (emitSpecialGetters(className)) {
457 writeln();
458 }
459 emitToJsonMember(type);
460 writeln();
461 if (emitToRequestMember(impliedType)) {
462 writeln();
463 }
464 if (emitToResponseMember(impliedType)) {
465 writeln();
466 }
467 if (emitToNotificationMember(impliedType)) {
468 writeln();
469 }
470 if (emitSpecialMethods(className)) {
471 writeln();
472 }
473 writeln('@override');
474 writeln('String toString() => JSON.encode(toJson());');
475 writeln();
476 emitObjectEqualsMember(type, className);
477 writeln();
478 emitObjectHashCode(type, className);
479 });
480 writeln('}');
481 }
482
483 /**
484 * Emit the constructor for an object class.
485 */
486 void emitObjectConstructor(TypeObject type, String className) {
487 List<String> args = <String>[];
488 List<String> optionalArgs = <String>[];
489 List<CodegenCallback> extraInitCode = <CodegenCallback>[];
490 for (TypeObjectField field in type.fields) {
491 if (field.value != null) {
492 continue;
493 }
494 String arg = '${dartType(field.type)} ${field.name}';
495 String setValueFromArg = 'this.${field.name} = ${field.name};';
496 if (isOptionalConstructorArg(className, field)) {
497 optionalArgs.add(arg);
498 if (!field.optional) {
499 // Optional constructor arg, but non-optional field. If no arg is
500 // given, the constructor should populate with the empty list.
501 TypeDecl fieldType = field.type;
502 if (fieldType is TypeList) {
503 extraInitCode.add(() {
504 writeln('if (${field.name} == null) {');
505 indent(() {
506 writeln(
507 'this.${field.name} = <${dartType(fieldType.itemType)}>[];') ;
508 });
509 writeln('} else {');
510 indent(() {
511 writeln(setValueFromArg);
512 });
513 writeln('}');
514 });
515 } else {
516 throw new Exception(
517 "Don't know how to create default field value.");
518 }
519 } else {
520 extraInitCode.add(() {
521 writeln(setValueFromArg);
522 });
523 }
524 } else {
525 args.add(arg);
526 extraInitCode.add(() {
527 writeln(setValueFromArg);
528 });
529 }
530 }
531 if (optionalArgs.isNotEmpty) {
532 args.add('{${optionalArgs.join(', ')}}');
533 }
534 write('$className(${args.join(', ')})');
535 if (extraInitCode.isEmpty) {
536 writeln(';');
537 } else {
538 writeln(' {');
539 indent(() {
540 for (CodegenCallback callback in extraInitCode) {
541 callback();
542 }
543 });
544 writeln('}');
545 }
546 }
547
548 /**
549 * Emit the operator== code for an object class.
550 */
551 void emitObjectEqualsMember(TypeObject type, String className) {
552 writeln('@override');
553 writeln('bool operator==(other) {');
554 indent(() {
555 writeln('if (other is $className) {');
556 indent(() {
557 var comparisons = <String>[];
558 if (type != null) {
559 for (TypeObjectField field in type.fields) {
560 if (field.value != null) {
561 continue;
562 }
563 comparisons.add(compareEqualsCode(
564 field.type, field.name, 'other.${field.name}'));
565 }
566 }
567 if (comparisons.isEmpty) {
568 writeln('return true;');
569 } else {
570 String concatenated = comparisons.join(' &&\n ');
571 writeln('return $concatenated;');
572 }
573 });
574 writeln('}');
575 writeln('return false;');
576 });
577 writeln('}');
578 }
579
580 /**
581 * Emit the method for decoding an object from JSON.
582 */
583 void emitObjectFromJsonConstructor(
584 String className, TypeObject type, ImpliedType impliedType) {
585 String humanReadableNameString =
586 literalString(impliedType.humanReadableName);
587 if (className == 'RefactoringFeedback') {
588 writeln('factory RefactoringFeedback.fromJson(JsonDecoder jsonDecoder, '
589 'String jsonPath, Object json, Map responseJson) {');
590 indent(() {
591 writeln('return refactoringFeedbackFromJson(jsonDecoder, jsonPath, '
592 'json, responseJson);');
593 });
594 writeln('}');
595 return;
596 }
597 if (className == 'RefactoringOptions') {
598 writeln('factory RefactoringOptions.fromJson(JsonDecoder jsonDecoder, '
599 'String jsonPath, Object json, RefactoringKind kind) {');
600 indent(() {
601 writeln('return refactoringOptionsFromJson(jsonDecoder, jsonPath, '
602 'json, kind);');
603 });
604 writeln('}');
605 return;
606 }
607 writeln(
608 'factory $className.fromJson(JsonDecoder jsonDecoder, String jsonPath, O bject json) {');
609 indent(() {
610 writeln('if (json == null) {');
611 indent(() {
612 writeln('json = {};');
613 });
614 writeln('}');
615 writeln('if (json is Map) {');
616 indent(() {
617 List<String> args = <String>[];
618 List<String> optionalArgs = <String>[];
619 for (TypeObjectField field in type.fields) {
620 String fieldNameString = literalString(field.name);
621 String fieldAccessor = 'json[$fieldNameString]';
622 String jsonPath = 'jsonPath + ${literalString('.${field.name}')}';
623 if (field.value != null) {
624 String valueString = literalString(field.value);
625 writeln('if ($fieldAccessor != $valueString) {');
626 indent(() {
627 writeln(
628 'throw jsonDecoder.mismatch(jsonPath, "equal " + $valueString, json);');
629 });
630 writeln('}');
631 continue;
632 }
633 if (isOptionalConstructorArg(className, field)) {
634 optionalArgs.add('${field.name}: ${field.name}');
635 } else {
636 args.add(field.name);
637 }
638 TypeDecl fieldType = field.type;
639 String fieldDartType = dartType(fieldType);
640 writeln('$fieldDartType ${field.name};');
641 writeln('if (json.containsKey($fieldNameString)) {');
642 indent(() {
643 String fromJson =
644 fromJsonCode(fieldType).asSnippet(jsonPath, fieldAccessor);
645 writeln('${field.name} = $fromJson;');
646 });
647 write('}');
648 if (!field.optional) {
649 writeln(' else {');
650 indent(() {
651 writeln(
652 "throw jsonDecoder.mismatch(jsonPath, $fieldNameString);");
653 });
654 writeln('}');
655 } else {
656 writeln();
657 }
658 }
659 args.addAll(optionalArgs);
660 writeln('return new $className(${args.join(', ')});');
661 });
662 writeln('} else {');
663 indent(() {
664 writeln(
665 'throw jsonDecoder.mismatch(jsonPath, $humanReadableNameString, json );');
666 });
667 writeln('}');
668 });
669 writeln('}');
670 }
671
672 /**
673 * Emit the hashCode getter for an object class.
674 */
675 void emitObjectHashCode(TypeObject type, String className) {
676 writeln('@override');
677 writeln('int get hashCode {');
678 indent(() {
679 if (type == null) {
680 writeln('return ${className.hashCode};');
681 } else {
682 writeln('int hash = 0;');
683 for (TypeObjectField field in type.fields) {
684 String valueToCombine;
685 if (field.value != null) {
686 valueToCombine = field.value.hashCode.toString();
687 } else {
688 valueToCombine = '${field.name}.hashCode';
689 }
690 writeln('hash = JenkinsSmiHash.combine(hash, $valueToCombine);');
691 }
692 writeln('return JenkinsSmiHash.finish(hash);');
693 }
694 });
695 writeln('}');
696 }
697
698 /**
699 * If the class named [className] requires special constructors, emit them
700 * and return true.
701 */
702 bool emitSpecialConstructors(String className) {
703 switch (className) {
704 case 'LinkedEditGroup':
705 docComment([new dom.Text('Construct an empty LinkedEditGroup.')]);
706 writeln(
707 'LinkedEditGroup.empty() : this(<Position>[], 0, <LinkedEditSuggesti on>[]);');
708 return true;
709 case 'RefactoringProblemSeverity':
710 docComment([
711 new dom.Text(
712 'Returns the [RefactoringProblemSeverity] with the maximal severit y.')
713 ]);
714 writeln(
715 'static RefactoringProblemSeverity max(RefactoringProblemSeverity a, RefactoringProblemSeverity b) =>');
716 writeln(' maxRefactoringProblemSeverity(a, b);');
717 return true;
718 default:
719 return false;
720 }
721 }
722
723 /**
724 * If the class named [className] requires special getters, emit them and
725 * return true.
726 */
727 bool emitSpecialGetters(String className) {
728 switch (className) {
729 case 'Element':
730 for (String name in specialElementFlags.keys) {
731 String flag = 'FLAG_${name.toUpperCase()}';
732 writeln(
733 'bool get ${camelJoin(['is', name])} => (flags & $flag) != 0;');
734 }
735 return true;
736 case 'SourceEdit':
737 docComment([new dom.Text('The end of the region to be modified.')]);
738 writeln('int get end => offset + length;');
739 return true;
740 default:
741 return false;
742 }
743 }
744
745 /**
746 * If the class named [className] requires special methods, emit them and
747 * return true.
748 */
749 bool emitSpecialMethods(String className) {
750 switch (className) {
751 case 'LinkedEditGroup':
752 docComment([new dom.Text('Add a new position and change the length.')]);
753 writeln('void addPosition(Position position, int length) {');
754 indent(() {
755 writeln('positions.add(position);');
756 writeln('this.length = length;');
757 });
758 writeln('}');
759 writeln();
760 docComment([new dom.Text('Add a new suggestion.')]);
761 writeln('void addSuggestion(LinkedEditSuggestion suggestion) {');
762 indent(() {
763 writeln('suggestions.add(suggestion);');
764 });
765 writeln('}');
766 return true;
767 case 'SourceChange':
768 docComment([
769 new dom.Text('Adds [edit] to the [FileEdit] for the given [file].')
770 ]);
771 writeln('void addEdit(String file, int fileStamp, SourceEdit edit) =>');
772 writeln(' addEditToSourceChange(this, file, fileStamp, edit);');
773 writeln();
774 docComment([new dom.Text('Adds the given [FileEdit].')]);
775 writeln('void addFileEdit(SourceFileEdit edit) {');
776 indent(() {
777 writeln('edits.add(edit);');
778 });
779 writeln('}');
780 writeln();
781 docComment([new dom.Text('Adds the given [LinkedEditGroup].')]);
782 writeln('void addLinkedEditGroup(LinkedEditGroup linkedEditGroup) {');
783 indent(() {
784 writeln('linkedEditGroups.add(linkedEditGroup);');
785 });
786 writeln('}');
787 writeln();
788 docComment([
789 new dom.Text(
790 'Returns the [FileEdit] for the given [file], maybe `null`.')
791 ]);
792 writeln('SourceFileEdit getFileEdit(String file) =>');
793 writeln(' getChangeFileEdit(this, file);');
794 return true;
795 case 'SourceEdit':
796 docComment([
797 new dom.Text(
798 'Get the result of applying the edit to the given [code].')
799 ]);
800 writeln('String apply(String code) => applyEdit(code, this);');
801 return true;
802 case 'SourceFileEdit':
803 docComment([new dom.Text('Adds the given [Edit] to the list.')]);
804 writeln('void add(SourceEdit edit) => addEditForSource(this, edit);');
805 writeln();
806 docComment([new dom.Text('Adds the given [Edit]s.')]);
807 writeln('void addAll(Iterable<SourceEdit> edits) =>');
808 writeln(' addAllEditsForSource(this, edits);');
809 return true;
810 default:
811 return false;
812 }
813 }
814
815 /**
816 * If the class named [className] requires special static members, emit them
817 * and return true.
818 */
819 bool emitSpecialStaticMembers(String className) {
820 switch (className) {
821 case 'Element':
822 List<String> makeFlagsArgs = <String>[];
823 List<String> makeFlagsStatements = <String>[];
824 specialElementFlags.forEach((String name, String value) {
825 String flag = 'FLAG_${name.toUpperCase()}';
826 String camelName = camelJoin(['is', name]);
827 writeln('static const int $flag = $value;');
828 makeFlagsArgs.add('$camelName: false');
829 makeFlagsStatements.add('if ($camelName) flags |= $flag;');
830 });
831 writeln();
832 writeln('static int makeFlags({${makeFlagsArgs.join(', ')}}) {');
833 indent(() {
834 writeln('int flags = 0;');
835 for (String statement in makeFlagsStatements) {
836 writeln(statement);
837 }
838 writeln('return flags;');
839 });
840 writeln('}');
841 return true;
842 case 'SourceEdit':
843 docComment([
844 new dom.Text('Get the result of applying a set of ' +
845 '[edits] to the given [code]. Edits are applied in the order ' +
846 'they appear in [edits].')
847 ]);
848 writeln(
849 'static String applySequence(String code, Iterable<SourceEdit> edits ) =>');
850 writeln(' applySequenceOfEdits(code, edits);');
851 return true;
852 default:
853 return false;
854 }
855 }
856
857 /**
858 * Emit the toJson() code for an object class.
859 */
860 void emitToJsonMember(TypeObject type) {
861 writeln('@override');
862 writeln('Map<String, dynamic> toJson() {');
863 indent(() {
864 writeln('Map<String, dynamic> result = {};');
865 for (TypeObjectField field in type.fields) {
866 String fieldNameString = literalString(field.name);
867 if (field.value != null) {
868 writeln('result[$fieldNameString] = ${literalString(field.value)};');
869 continue;
870 }
871 String fieldToJson = toJsonCode(field.type).asSnippet(field.name);
872 String populateField = 'result[$fieldNameString] = $fieldToJson;';
873 if (field.optional) {
874 writeln('if (${field.name} != null) {');
875 indent(() {
876 writeln(populateField);
877 });
878 writeln('}');
879 } else {
880 writeln(populateField);
881 }
882 }
883 writeln('return result;');
884 });
885 writeln('}');
886 }
887
888 /**
889 * Emit the toNotification() code for a class, if appropriate. Returns true
890 * if code was emitted.
891 */
892 bool emitToNotificationMember(ImpliedType impliedType) {
893 if (impliedType.kind == 'notificationParams') {
894 writeln('Notification toNotification() {');
895 indent(() {
896 String eventString =
897 literalString((impliedType.apiNode as Notification).longEvent);
898 String jsonPart = impliedType.type != null ? 'toJson()' : 'null';
899 writeln('return new Notification($eventString, $jsonPart);');
900 });
901 writeln('}');
902 return true;
903 }
904 return false;
905 }
906
907 /**
908 * Emit the toRequest() code for a class, if appropriate. Returns true if
909 * code was emitted.
910 */
911 bool emitToRequestMember(ImpliedType impliedType) {
912 if (impliedType.kind == 'requestParams') {
913 writeln('Request toRequest(String id) {');
914 indent(() {
915 String methodString =
916 literalString((impliedType.apiNode as Request).longMethod);
917 String jsonPart = impliedType.type != null ? 'toJson()' : 'null';
918 writeln('return new Request(id, $methodString, $jsonPart);');
919 });
920 writeln('}');
921 return true;
922 }
923 return false;
924 }
925
926 /**
927 * Emit the toResponse() code for a class, if appropriate. Returns true if
928 * code was emitted.
929 */
930 bool emitToResponseMember(ImpliedType impliedType) {
931 if (impliedType.kind == 'requestResult') {
932 writeln('@override');
933 writeln('Response toResponse(String id) {');
934 indent(() {
935 String jsonPart = impliedType.type != null ? 'toJson()' : 'null';
936 writeln('return new Response(id, result: $jsonPart);');
937 });
938 writeln('}');
939 return true;
940 }
941 return false;
942 }
943
944 /**
945 * Compute the code necessary to translate [type] from JSON.
946 */
947 FromJsonCode fromJsonCode(TypeDecl type) {
948 if (type is TypeReference) {
949 TypeDefinition referencedDefinition = api.types[type.typeName];
950 if (referencedDefinition != null) {
951 TypeDecl referencedType = referencedDefinition.type;
952 if (referencedType is TypeObject || referencedType is TypeEnum) {
953 return new FromJsonSnippet((String jsonPath, String json) {
954 String typeName = dartType(type);
955 if (typeName == 'RefactoringFeedback') {
956 return 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json, json )';
957 } else if (typeName == 'RefactoringOptions') {
958 return 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json, kind )';
959 } else {
960 return 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json)';
961 }
962 });
963 } else {
964 return fromJsonCode(referencedType);
965 }
966 } else {
967 switch (type.typeName) {
968 case 'String':
969 return new FromJsonFunction('jsonDecoder.decodeString');
970 case 'bool':
971 return new FromJsonFunction('jsonDecoder.decodeBool');
972 case 'int':
973 case 'long':
974 return new FromJsonFunction('jsonDecoder.decodeInt');
975 case 'object':
976 return new FromJsonIdentity();
977 default:
978 throw new Exception('Unexpected type name ${type.typeName}');
979 }
980 }
981 } else if (type is TypeMap) {
982 FromJsonCode keyCode;
983 if (dartType(type.keyType) != 'String') {
984 keyCode = fromJsonCode(type.keyType);
985 } else {
986 keyCode = new FromJsonIdentity();
987 }
988 FromJsonCode valueCode = fromJsonCode(type.valueType);
989 if (keyCode.isIdentity && valueCode.isIdentity) {
990 return new FromJsonFunction('jsonDecoder.decodeMap');
991 } else {
992 return new FromJsonSnippet((String jsonPath, String json) {
993 StringBuffer result = new StringBuffer();
994 result.write('jsonDecoder.decodeMap($jsonPath, $json');
995 if (!keyCode.isIdentity) {
996 result.write(', keyDecoder: ${keyCode.asClosure}');
997 }
998 if (!valueCode.isIdentity) {
999 result.write(', valueDecoder: ${valueCode.asClosure}');
1000 }
1001 result.write(')');
1002 return result.toString();
1003 });
1004 }
1005 } else if (type is TypeList) {
1006 FromJsonCode itemCode = fromJsonCode(type.itemType);
1007 if (itemCode.isIdentity) {
1008 return new FromJsonFunction('jsonDecoder.decodeList');
1009 } else {
1010 return new FromJsonSnippet((String jsonPath, String json) =>
1011 'jsonDecoder.decodeList($jsonPath, $json, ${itemCode.asClosure})');
1012 }
1013 } else if (type is TypeUnion) {
1014 List<String> decoders = <String>[];
1015 for (TypeDecl choice in type.choices) {
1016 TypeDecl resolvedChoice = resolveTypeReferenceChain(choice);
1017 if (resolvedChoice is TypeObject) {
1018 TypeObjectField field = resolvedChoice.getField(type.field);
1019 if (field == null) {
1020 throw new Exception(
1021 'Each choice in the union needs a field named ${type.field}');
1022 }
1023 if (field.value == null) {
1024 throw new Exception(
1025 'Each choice in the union needs a constant value for the field $ {type.field}');
1026 }
1027 String closure = fromJsonCode(choice).asClosure;
1028 decoders.add('${literalString(field.value)}: $closure');
1029 } else {
1030 throw new Exception('Union types must be unions of objects.');
1031 }
1032 }
1033 return new FromJsonSnippet((String jsonPath, String json) =>
1034 'jsonDecoder.decodeUnion($jsonPath, $json, ${literalString(type.field) }, {${decoders.join(', ')}})');
1035 } else {
1036 throw new Exception("Can't convert $type from JSON");
1037 }
1038 }
1039
1040 /**
1041 * True if the constructor argument for the given field should be optional.
1042 */
1043 bool isOptionalConstructorArg(String className, TypeObjectField field) {
1044 if (field.optional) {
1045 return true;
1046 }
1047 List<String> forceOptional = _optionalConstructorArguments[className];
1048 if (forceOptional != null && forceOptional.contains(field.name)) {
1049 return true;
1050 }
1051 return false;
1052 }
1053
1054 /**
1055 * Create a string literal that evaluates to [s].
1056 */
1057 String literalString(String s) {
1058 return JSON.encode(s);
1059 }
1060
1061 /**
1062 * Compute the code necessary to convert [type] to JSON.
1063 */
1064 ToJsonCode toJsonCode(TypeDecl type) {
1065 TypeDecl resolvedType = resolveTypeReferenceChain(type);
1066 if (resolvedType is TypeReference) {
1067 return new ToJsonIdentity(dartType(type));
1068 } else if (resolvedType is TypeList) {
1069 ToJsonCode itemCode = toJsonCode(resolvedType.itemType);
1070 if (itemCode.isIdentity) {
1071 return new ToJsonIdentity(dartType(type));
1072 } else {
1073 return new ToJsonSnippet(dartType(type),
1074 (String value) => '$value.map(${itemCode.asClosure}).toList()');
1075 }
1076 } else if (resolvedType is TypeMap) {
1077 ToJsonCode keyCode;
1078 if (dartType(resolvedType.keyType) != 'String') {
1079 keyCode = toJsonCode(resolvedType.keyType);
1080 } else {
1081 keyCode = new ToJsonIdentity(dartType(resolvedType.keyType));
1082 }
1083 ToJsonCode valueCode = toJsonCode(resolvedType.valueType);
1084 if (keyCode.isIdentity && valueCode.isIdentity) {
1085 return new ToJsonIdentity(dartType(resolvedType));
1086 } else {
1087 return new ToJsonSnippet(dartType(type), (String value) {
1088 StringBuffer result = new StringBuffer();
1089 result.write('mapMap($value');
1090 if (!keyCode.isIdentity) {
1091 result.write(', keyCallback: ${keyCode.asClosure}');
1092 }
1093 if (!valueCode.isIdentity) {
1094 result.write(', valueCallback: ${valueCode.asClosure}');
1095 }
1096 result.write(')');
1097 return result.toString();
1098 });
1099 }
1100 } else if (resolvedType is TypeUnion) {
1101 for (TypeDecl choice in resolvedType.choices) {
1102 if (resolveTypeReferenceChain(choice) is! TypeObject) {
1103 throw new Exception('Union types must be unions of objects');
1104 }
1105 }
1106 return new ToJsonSnippet(
1107 dartType(type), (String value) => '$value.toJson()');
1108 } else if (resolvedType is TypeObject || resolvedType is TypeEnum) {
1109 return new ToJsonSnippet(
1110 dartType(type), (String value) => '$value.toJson()');
1111 } else {
1112 throw new Exception("Can't convert $resolvedType from JSON");
1113 }
1114 }
1115
1116 @override
1117 visitApi() {
1118 outputHeader(year: '2017');
1119 writeln();
1120 writeln("import 'dart:convert' hide JsonDecoder;");
1121 writeln();
1122 writeln("import 'package:analyzer/src/generated/utilities_general.dart';");
1123 writeln("import 'package:analyzer_plugin/protocol/protocol.dart';");
1124 writeln(
1125 "import 'package:analyzer_plugin/src/protocol/protocol_internal.dart';") ;
1126 emitClasses();
1127 }
1128 }
1129
1130 /**
1131 * Container for code that can be used to translate a data type from JSON.
1132 */
1133 abstract class FromJsonCode {
1134 /**
1135 * Get the translation code in the form of a closure.
1136 */
1137 String get asClosure;
1138
1139 /**
1140 * True if the data type is already in JSON form, so the translation is the
1141 * identity function.
1142 */
1143 bool get isIdentity;
1144
1145 /**
1146 * Get the translation code in the form of a code snippet, where [jsonPath]
1147 * is the variable holding the JSON path, and [json] is the variable holding
1148 * the raw JSON.
1149 */
1150 String asSnippet(String jsonPath, String json);
1151 }
1152
1153 /**
1154 * Representation of FromJsonCode for a function defined elsewhere.
1155 */
1156 class FromJsonFunction extends FromJsonCode {
1157 @override
1158 final String asClosure;
1159
1160 FromJsonFunction(this.asClosure);
1161
1162 @override
1163 bool get isIdentity => false;
1164
1165 @override
1166 String asSnippet(String jsonPath, String json) =>
1167 '$asClosure($jsonPath, $json)';
1168 }
1169
1170 /**
1171 * Representation of FromJsonCode for the identity transformation.
1172 */
1173 class FromJsonIdentity extends FromJsonSnippet {
1174 FromJsonIdentity() : super((String jsonPath, String json) => json);
1175
1176 @override
1177 bool get isIdentity => true;
1178 }
1179
1180 /**
1181 * Representation of FromJsonCode for a snippet of inline code.
1182 */
1183 class FromJsonSnippet extends FromJsonCode {
1184 /**
1185 * Callback that can be used to generate the code snippet, once the names
1186 * of the [jsonPath] and [json] variables are known.
1187 */
1188 final FromJsonSnippetCallback callback;
1189
1190 FromJsonSnippet(this.callback);
1191
1192 @override
1193 String get asClosure =>
1194 '(String jsonPath, Object json) => ${callback('jsonPath', 'json')}';
1195
1196 @override
1197 bool get isIdentity => false;
1198
1199 @override
1200 String asSnippet(String jsonPath, String json) => callback(jsonPath, json);
1201 }
1202
1203 /**
1204 * Container for code that can be used to translate a data type to JSON.
1205 */
1206 abstract class ToJsonCode {
1207 /**
1208 * Get the translation code in the form of a closure.
1209 */
1210 String get asClosure;
1211
1212 /**
1213 * True if the data type is already in JSON form, so the translation is the
1214 * identity function.
1215 */
1216 bool get isIdentity;
1217
1218 /**
1219 * Get the translation code in the form of a code snippet, where [value]
1220 * is the variable holding the object to be translated.
1221 */
1222 String asSnippet(String value);
1223 }
1224
1225 /**
1226 * Representation of ToJsonCode for a function defined elsewhere.
1227 */
1228 class ToJsonFunction extends ToJsonCode {
1229 @override
1230 final String asClosure;
1231
1232 ToJsonFunction(this.asClosure);
1233
1234 @override
1235 bool get isIdentity => false;
1236
1237 @override
1238 String asSnippet(String value) => '$asClosure($value)';
1239 }
1240
1241 /**
1242 * Representation of FromJsonCode for the identity transformation.
1243 */
1244 class ToJsonIdentity extends ToJsonSnippet {
1245 ToJsonIdentity(String type) : super(type, (String value) => value);
1246
1247 @override
1248 bool get isIdentity => true;
1249 }
1250
1251 /**
1252 * Representation of ToJsonCode for a snippet of inline code.
1253 */
1254 class ToJsonSnippet extends ToJsonCode {
1255 /**
1256 * Callback that can be used to generate the code snippet, once the name
1257 * of the [value] variable is known.
1258 */
1259 final ToJsonSnippetCallback callback;
1260
1261 /**
1262 * Dart type of the [value] variable.
1263 */
1264 final String type;
1265
1266 ToJsonSnippet(this.type, this.callback);
1267
1268 @override
1269 String get asClosure => '($type value) => ${callback('value')}';
1270
1271 @override
1272 bool get isIdentity => false;
1273
1274 @override
1275 String asSnippet(String value) => callback(value);
1276 }
OLDNEW
« no previous file with comments | « pkg/analyzer_plugin/tool/spec/codegen_dart.dart ('k') | pkg/analyzer_plugin/tool/spec/codegen_inttest_methods.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698