OLD | NEW |
(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 } |
OLD | NEW |