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 /** |
| 6 * Code for displaying the API as HTML. This is used both for generating a |
| 7 * full description of the API as a web page, and for generating doc comments |
| 8 * in generated code. |
| 9 */ |
| 10 import 'dart:convert'; |
| 11 |
| 12 import 'package:analyzer/src/codegen/html.dart'; |
| 13 import 'package:analyzer/src/codegen/tools.dart'; |
| 14 import 'package:html/dom.dart' as dom; |
| 15 |
| 16 import 'api.dart'; |
| 17 import 'from_html.dart'; |
| 18 |
| 19 /** |
| 20 * Embedded stylesheet |
| 21 */ |
| 22 final String stylesheet = ''' |
| 23 body { |
| 24 font-family: 'Roboto', sans-serif; |
| 25 max-width: 800px; |
| 26 margin: 0 auto; |
| 27 padding: 0 16px; |
| 28 font-size: 16px; |
| 29 line-height: 1.5; |
| 30 color: #111; |
| 31 background-color: #fdfdfd; |
| 32 font-weight: 300; |
| 33 -webkit-font-smoothing: auto; |
| 34 } |
| 35 |
| 36 h1 { |
| 37 text-align: center; |
| 38 } |
| 39 |
| 40 h2, h3, h4, h5 { |
| 41 margin-bottom: 0; |
| 42 } |
| 43 |
| 44 h2.domain { |
| 45 border-bottom: 1px solid rgb(200, 200, 200); |
| 46 margin-bottom: 0.5em; |
| 47 } |
| 48 |
| 49 h4 { |
| 50 font-size: 18px; |
| 51 } |
| 52 |
| 53 h5 { |
| 54 font-size: 16px; |
| 55 } |
| 56 |
| 57 p { |
| 58 margin-top: 0; |
| 59 } |
| 60 |
| 61 pre { |
| 62 margin: 0; |
| 63 font-family: 'Source Code Pro', monospace; |
| 64 font-size: 15px; |
| 65 } |
| 66 |
| 67 div.box { |
| 68 background-color: rgb(240, 245, 240); |
| 69 border-radius: 4px; |
| 70 padding: 4px 12px; |
| 71 margin: 16px 0; |
| 72 } |
| 73 |
| 74 div.hangingIndent { |
| 75 padding-left: 3em; |
| 76 text-indent: -3em; |
| 77 } |
| 78 |
| 79 dl dt { |
| 80 font-weight: bold; |
| 81 } |
| 82 |
| 83 dl dd { |
| 84 margin-left: 16px; |
| 85 } |
| 86 |
| 87 dt { |
| 88 margin-top: 1em; |
| 89 } |
| 90 |
| 91 dt.notification { |
| 92 font-weight: bold; |
| 93 } |
| 94 |
| 95 dt.refactoring { |
| 96 font-weight: bold; |
| 97 } |
| 98 |
| 99 dt.request { |
| 100 font-weight: bold; |
| 101 } |
| 102 |
| 103 dt.typeDefinition { |
| 104 font-weight: bold; |
| 105 } |
| 106 |
| 107 a { |
| 108 text-decoration: none; |
| 109 } |
| 110 |
| 111 a:focus, a:hover { |
| 112 text-decoration: underline; |
| 113 } |
| 114 |
| 115 /* Styles for index */ |
| 116 |
| 117 .subindex { |
| 118 } |
| 119 |
| 120 .subindex ul { |
| 121 padding-left: 0; |
| 122 margin-left: 0; |
| 123 |
| 124 -webkit-margin-before: 0; |
| 125 -webkit-margin-start: 0; |
| 126 -webkit-padding-start: 0; |
| 127 |
| 128 list-style-type: none; |
| 129 } |
| 130 ''' |
| 131 .trim(); |
| 132 |
| 133 final GeneratedFile target = |
| 134 new GeneratedFile('doc/api.html', (String pkgPath) { |
| 135 ToHtmlVisitor visitor = new ToHtmlVisitor(readApi(pkgPath)); |
| 136 dom.Document document = new dom.Document(); |
| 137 document.append(new dom.DocumentType('html', null, null)); |
| 138 for (dom.Node node in visitor.collectHtml(visitor.visitApi)) { |
| 139 document.append(node); |
| 140 } |
| 141 return document.outerHtml; |
| 142 }); |
| 143 |
| 144 /** |
| 145 * Visitor that records the mapping from HTML elements to various kinds of API |
| 146 * nodes. |
| 147 */ |
| 148 class ApiMappings extends HierarchicalApiVisitor { |
| 149 Map<dom.Element, Domain> domains = <dom.Element, Domain>{}; |
| 150 |
| 151 ApiMappings(Api api) : super(api); |
| 152 |
| 153 @override |
| 154 void visitDomain(Domain domain) { |
| 155 domains[domain.html] = domain; |
| 156 } |
| 157 } |
| 158 |
| 159 /** |
| 160 * Helper methods for creating HTML elements. |
| 161 */ |
| 162 abstract class HtmlMixin { |
| 163 void anchor(String id, void callback()) { |
| 164 element('a', {'name': id}, callback); |
| 165 } |
| 166 |
| 167 void b(void callback()) => element('b', {}, callback); |
| 168 void body(void callback()) => element('body', {}, callback); |
| 169 void box(void callback()) { |
| 170 element('div', {'class': 'box'}, callback); |
| 171 } |
| 172 |
| 173 void br() => element('br', {}); |
| 174 void dd(void callback()) => element('dd', {}, callback); |
| 175 void dl(void callback()) => element('dl', {}, callback); |
| 176 void dt(String cls, void callback()) => |
| 177 element('dt', {'class': cls}, callback); |
| 178 void element(String name, Map<dynamic, String> attributes, [void callback()]); |
| 179 void gray(void callback()) => |
| 180 element('span', {'style': 'color:#999999'}, callback); |
| 181 void h1(void callback()) => element('h1', {}, callback); |
| 182 void h2(String cls, void callback()) { |
| 183 if (cls == null) { |
| 184 return element('h2', {}, callback); |
| 185 } |
| 186 return element('h2', {'class': cls}, callback); |
| 187 } |
| 188 |
| 189 void h3(void callback()) => element('h3', {}, callback); |
| 190 void h4(void callback()) => element('h4', {}, callback); |
| 191 void h5(void callback()) => element('h5', {}, callback); |
| 192 void hangingIndent(void callback()) => |
| 193 element('div', {'class': 'hangingIndent'}, callback); |
| 194 void head(void callback()) => element('head', {}, callback); |
| 195 void html(void callback()) => element('html', {}, callback); |
| 196 void i(void callback()) => element('i', {}, callback); |
| 197 void link(String id, void callback()) { |
| 198 element('a', {'href': '#$id'}, callback); |
| 199 } |
| 200 |
| 201 void p(void callback()) => element('p', {}, callback); |
| 202 void pre(void callback()) => element('pre', {}, callback); |
| 203 void title(void callback()) => element('title', {}, callback); |
| 204 void tt(void callback()) => element('tt', {}, callback); |
| 205 } |
| 206 |
| 207 /** |
| 208 * Visitor that generates HTML documentation of the API. |
| 209 */ |
| 210 class ToHtmlVisitor extends HierarchicalApiVisitor |
| 211 with HtmlMixin, HtmlGenerator { |
| 212 /** |
| 213 * Set of types defined in the API. |
| 214 */ |
| 215 Set<String> definedTypes = new Set<String>(); |
| 216 |
| 217 /** |
| 218 * Mappings from HTML elements to API nodes. |
| 219 */ |
| 220 ApiMappings apiMappings; |
| 221 |
| 222 ToHtmlVisitor(Api api) |
| 223 : apiMappings = new ApiMappings(api), |
| 224 super(api) { |
| 225 apiMappings.visitApi(); |
| 226 } |
| 227 |
| 228 /** |
| 229 * Describe the payload of request, response, notification, refactoring |
| 230 * feedback, or refactoring options. |
| 231 * |
| 232 * If [force] is true, then a section is inserted even if the payload is |
| 233 * null. |
| 234 */ |
| 235 void describePayload(TypeObject subType, String name, {bool force: false}) { |
| 236 if (force || subType != null) { |
| 237 h4(() { |
| 238 write(name); |
| 239 }); |
| 240 if (subType == null) { |
| 241 p(() { |
| 242 write('none'); |
| 243 }); |
| 244 } else { |
| 245 visitTypeDecl(subType); |
| 246 } |
| 247 } |
| 248 } |
| 249 |
| 250 void generateDomainIndex(Domain domain) { |
| 251 h4(() { |
| 252 write(domain.name); |
| 253 write(' ('); |
| 254 link('domain_${domain.name}', () => write('\u2191')); |
| 255 write(')'); |
| 256 }); |
| 257 if (domain.requests.length > 0) { |
| 258 element('div', {'class': 'subindex'}, () { |
| 259 generateRequestsIndex(domain.requests); |
| 260 if (domain.notifications.length > 0) { |
| 261 generateNotificationsIndex(domain.notifications); |
| 262 } |
| 263 }); |
| 264 } else if (domain.notifications.length > 0) { |
| 265 element('div', {'class': 'subindex'}, () { |
| 266 generateNotificationsIndex(domain.notifications); |
| 267 }); |
| 268 } |
| 269 } |
| 270 |
| 271 void generateIndex() { |
| 272 h3(() => write('Domains')); |
| 273 for (var domain in api.domains) { |
| 274 if (domain.experimental || |
| 275 (domain.requests.length == 0 && domain.notifications == 0)) { |
| 276 continue; |
| 277 } |
| 278 generateDomainIndex(domain); |
| 279 } |
| 280 |
| 281 generateTypesIndex(definedTypes); |
| 282 generateRefactoringsIndex(api.refactorings); |
| 283 } |
| 284 |
| 285 void generateNotificationsIndex(Iterable<Notification> notifications) { |
| 286 h5(() => write("Notifications")); |
| 287 element('div', {'class': 'subindex'}, () { |
| 288 element('ul', {}, () { |
| 289 for (var notification in notifications) { |
| 290 element( |
| 291 'li', |
| 292 {}, |
| 293 () => link('notification_${notification.longEvent}', |
| 294 () => write(notification.event))); |
| 295 } |
| 296 }); |
| 297 }); |
| 298 } |
| 299 |
| 300 void generateRefactoringsIndex(Iterable<Refactoring> refactorings) { |
| 301 if (refactorings == null) { |
| 302 return; |
| 303 } |
| 304 h3(() { |
| 305 write("Refactorings"); |
| 306 write(' ('); |
| 307 link('refactorings', () => write('\u2191')); |
| 308 write(')'); |
| 309 }); |
| 310 // TODO: Individual refactorings are not yet hyperlinked. |
| 311 element('div', {'class': 'subindex'}, () { |
| 312 element('ul', {}, () { |
| 313 for (var refactoring in refactorings) { |
| 314 element( |
| 315 'li', |
| 316 {}, |
| 317 () => link('refactoring_${refactoring.kind}', |
| 318 () => write(refactoring.kind))); |
| 319 } |
| 320 }); |
| 321 }); |
| 322 } |
| 323 |
| 324 void generateRequestsIndex(Iterable<Request> requests) { |
| 325 h5(() => write("Requests")); |
| 326 element('ul', {}, () { |
| 327 for (var request in requests) { |
| 328 element( |
| 329 'li', |
| 330 {}, |
| 331 () => link( |
| 332 'request_${request.longMethod}', () => write(request.method))); |
| 333 } |
| 334 }); |
| 335 } |
| 336 |
| 337 void generateTypesIndex(Set<String> types) { |
| 338 h3(() { |
| 339 write("Types"); |
| 340 write(' ('); |
| 341 link('types', () => write('\u2191')); |
| 342 write(')'); |
| 343 }); |
| 344 element('div', {'class': 'subindex'}, () { |
| 345 element('ul', {}, () { |
| 346 for (var type in types) { |
| 347 element('li', {}, () => link('type_$type', () => write(type))); |
| 348 } |
| 349 }); |
| 350 }); |
| 351 } |
| 352 |
| 353 void javadocParams(TypeObject typeObject) { |
| 354 if (typeObject != null) { |
| 355 for (TypeObjectField field in typeObject.fields) { |
| 356 hangingIndent(() { |
| 357 write('@param ${field.name} '); |
| 358 translateHtml(field.html, squashParagraphs: true); |
| 359 }); |
| 360 } |
| 361 } |
| 362 } |
| 363 |
| 364 /** |
| 365 * Generate a description of [type] using [TypeVisitor]. |
| 366 * |
| 367 * If [shortDesc] is non-null, the output is prefixed with this string |
| 368 * and a colon. |
| 369 * |
| 370 * If [typeForBolding] is supplied, then fields in this type are shown in |
| 371 * boldface. |
| 372 */ |
| 373 void showType(String shortDesc, TypeDecl type, [TypeObject typeForBolding]) { |
| 374 Set<String> fieldsToBold = new Set<String>(); |
| 375 if (typeForBolding != null) { |
| 376 for (TypeObjectField field in typeForBolding.fields) { |
| 377 fieldsToBold.add(field.name); |
| 378 } |
| 379 } |
| 380 pre(() { |
| 381 if (shortDesc != null) { |
| 382 write('$shortDesc: '); |
| 383 } |
| 384 TypeVisitor typeVisitor = |
| 385 new TypeVisitor(api, fieldsToBold: fieldsToBold); |
| 386 addAll(typeVisitor.collectHtml(() { |
| 387 typeVisitor.visitTypeDecl(type); |
| 388 })); |
| 389 }); |
| 390 } |
| 391 |
| 392 /** |
| 393 * Copy the contents of the given HTML element, translating the special |
| 394 * elements that define the API appropriately. |
| 395 */ |
| 396 void translateHtml(dom.Element html, {bool squashParagraphs: false}) { |
| 397 for (dom.Node node in html.nodes) { |
| 398 if (node is dom.Element) { |
| 399 if (squashParagraphs && node.localName == 'p') { |
| 400 translateHtml(node, squashParagraphs: squashParagraphs); |
| 401 continue; |
| 402 } |
| 403 switch (node.localName) { |
| 404 case 'domain': |
| 405 visitDomain(apiMappings.domains[node]); |
| 406 break; |
| 407 case 'head': |
| 408 head(() { |
| 409 translateHtml(node, squashParagraphs: squashParagraphs); |
| 410 element('link', { |
| 411 'rel': 'stylesheet', |
| 412 'href': |
| 413 'https://fonts.googleapis.com/css?family=Source+Code+Pro|Rob
oto:500,400italic,300,400', |
| 414 'type': 'text/css' |
| 415 }); |
| 416 element('style', {}, () { |
| 417 writeln(stylesheet); |
| 418 }); |
| 419 }); |
| 420 break; |
| 421 case 'refactorings': |
| 422 visitRefactorings(api.refactorings); |
| 423 break; |
| 424 case 'types': |
| 425 visitTypes(api.types); |
| 426 break; |
| 427 case 'version': |
| 428 translateHtml(node, squashParagraphs: squashParagraphs); |
| 429 break; |
| 430 case 'index': |
| 431 generateIndex(); |
| 432 break; |
| 433 default: |
| 434 if (!specialElements.contains(node.localName)) { |
| 435 element(node.localName, node.attributes, () { |
| 436 translateHtml(node, squashParagraphs: squashParagraphs); |
| 437 }); |
| 438 } |
| 439 } |
| 440 } else if (node is dom.Text) { |
| 441 String text = node.text; |
| 442 write(text); |
| 443 } |
| 444 } |
| 445 } |
| 446 |
| 447 @override |
| 448 void visitApi() { |
| 449 Iterable<TypeDefinition> apiTypes = |
| 450 api.types.where((TypeDefinition td) => !td.experimental); |
| 451 definedTypes = apiTypes.map((TypeDefinition td) => td.name).toSet(); |
| 452 |
| 453 html(() { |
| 454 translateHtml(api.html); |
| 455 }); |
| 456 } |
| 457 |
| 458 @override |
| 459 void visitDomain(Domain domain) { |
| 460 if (domain.experimental) { |
| 461 return; |
| 462 } |
| 463 h2('domain', () { |
| 464 anchor('domain_${domain.name}', () { |
| 465 write('${domain.name} domain'); |
| 466 }); |
| 467 }); |
| 468 translateHtml(domain.html); |
| 469 if (domain.requests.isNotEmpty) { |
| 470 h3(() { |
| 471 write('Requests'); |
| 472 }); |
| 473 dl(() { |
| 474 domain.requests.forEach(visitRequest); |
| 475 }); |
| 476 } |
| 477 if (domain.notifications.isNotEmpty) { |
| 478 h3(() { |
| 479 write('Notifications'); |
| 480 }); |
| 481 dl(() { |
| 482 domain.notifications.forEach(visitNotification); |
| 483 }); |
| 484 } |
| 485 } |
| 486 |
| 487 @override |
| 488 void visitNotification(Notification notification) { |
| 489 dt('notification', () { |
| 490 anchor('notification_${notification.longEvent}', () { |
| 491 write(notification.longEvent); |
| 492 }); |
| 493 write(' ('); |
| 494 link('notification_${notification.longEvent}', () { |
| 495 write('#'); |
| 496 }); |
| 497 write(')'); |
| 498 }); |
| 499 dd(() { |
| 500 box(() { |
| 501 showType( |
| 502 'notification', notification.notificationType, notification.params); |
| 503 }); |
| 504 translateHtml(notification.html); |
| 505 describePayload(notification.params, 'parameters:'); |
| 506 }); |
| 507 } |
| 508 |
| 509 @override |
| 510 visitRefactoring(Refactoring refactoring) { |
| 511 dt('refactoring', () { |
| 512 write(refactoring.kind); |
| 513 }); |
| 514 dd(() { |
| 515 translateHtml(refactoring.html); |
| 516 describePayload(refactoring.feedback, 'Feedback:', force: true); |
| 517 describePayload(refactoring.options, 'Options:', force: true); |
| 518 }); |
| 519 } |
| 520 |
| 521 @override |
| 522 void visitRefactorings(Refactorings refactorings) { |
| 523 translateHtml(refactorings.html); |
| 524 dl(() { |
| 525 super.visitRefactorings(refactorings); |
| 526 }); |
| 527 } |
| 528 |
| 529 @override |
| 530 void visitRequest(Request request) { |
| 531 dt('request', () { |
| 532 anchor('request_${request.longMethod}', () { |
| 533 write(request.longMethod); |
| 534 }); |
| 535 write(' ('); |
| 536 link('request_${request.longMethod}', () { |
| 537 write('#'); |
| 538 }); |
| 539 write(')'); |
| 540 }); |
| 541 dd(() { |
| 542 box(() { |
| 543 showType('request', request.requestType, request.params); |
| 544 br(); |
| 545 showType('response', request.responseType, request.result); |
| 546 }); |
| 547 translateHtml(request.html); |
| 548 describePayload(request.params, 'parameters:'); |
| 549 describePayload(request.result, 'returns:'); |
| 550 }); |
| 551 } |
| 552 |
| 553 @override |
| 554 void visitTypeDefinition(TypeDefinition typeDefinition) { |
| 555 if (typeDefinition.experimental) { |
| 556 return; |
| 557 } |
| 558 dt('typeDefinition', () { |
| 559 anchor('type_${typeDefinition.name}', () { |
| 560 write('${typeDefinition.name}: '); |
| 561 TypeVisitor typeVisitor = new TypeVisitor(api, short: true); |
| 562 addAll(typeVisitor.collectHtml(() { |
| 563 typeVisitor.visitTypeDecl(typeDefinition.type); |
| 564 })); |
| 565 }); |
| 566 }); |
| 567 dd(() { |
| 568 translateHtml(typeDefinition.html); |
| 569 visitTypeDecl(typeDefinition.type); |
| 570 }); |
| 571 } |
| 572 |
| 573 @override |
| 574 void visitTypeEnum(TypeEnum typeEnum) { |
| 575 dl(() { |
| 576 super.visitTypeEnum(typeEnum); |
| 577 }); |
| 578 } |
| 579 |
| 580 @override |
| 581 void visitTypeEnumValue(TypeEnumValue typeEnumValue) { |
| 582 bool isDocumented = false; |
| 583 for (dom.Node node in typeEnumValue.html.nodes) { |
| 584 if ((node is dom.Element && node.localName != 'code') || |
| 585 (node is dom.Text && node.text.trim().isNotEmpty)) { |
| 586 isDocumented = true; |
| 587 break; |
| 588 } |
| 589 } |
| 590 dt('value', () { |
| 591 write(typeEnumValue.value); |
| 592 }); |
| 593 if (isDocumented) { |
| 594 dd(() { |
| 595 translateHtml(typeEnumValue.html); |
| 596 }); |
| 597 } |
| 598 } |
| 599 |
| 600 @override |
| 601 void visitTypeList(TypeList typeList) { |
| 602 visitTypeDecl(typeList.itemType); |
| 603 } |
| 604 |
| 605 @override |
| 606 void visitTypeMap(TypeMap typeMap) { |
| 607 visitTypeDecl(typeMap.valueType); |
| 608 } |
| 609 |
| 610 @override |
| 611 void visitTypeObject(TypeObject typeObject) { |
| 612 dl(() { |
| 613 super.visitTypeObject(typeObject); |
| 614 }); |
| 615 } |
| 616 |
| 617 @override |
| 618 void visitTypeObjectField(TypeObjectField typeObjectField) { |
| 619 dt('field', () { |
| 620 b(() { |
| 621 write(typeObjectField.name); |
| 622 if (typeObjectField.value != null) { |
| 623 write(' = ${JSON.encode(typeObjectField.value)}'); |
| 624 } else { |
| 625 write(' ('); |
| 626 if (typeObjectField.optional) { |
| 627 gray(() { |
| 628 write('optional'); |
| 629 }); |
| 630 write(' '); |
| 631 } |
| 632 TypeVisitor typeVisitor = new TypeVisitor(api, short: true); |
| 633 addAll(typeVisitor.collectHtml(() { |
| 634 typeVisitor.visitTypeDecl(typeObjectField.type); |
| 635 })); |
| 636 write(')'); |
| 637 } |
| 638 }); |
| 639 }); |
| 640 dd(() { |
| 641 translateHtml(typeObjectField.html); |
| 642 }); |
| 643 } |
| 644 |
| 645 @override |
| 646 void visitTypeReference(TypeReference typeReference) {} |
| 647 |
| 648 @override |
| 649 void visitTypes(Types types) { |
| 650 translateHtml(types.html); |
| 651 dl(() { |
| 652 super.visitTypes(types); |
| 653 }); |
| 654 } |
| 655 } |
| 656 |
| 657 /** |
| 658 * Visitor that generates a compact representation of a type, such as: |
| 659 * |
| 660 * { |
| 661 * "id": String |
| 662 * "error": optional Error |
| 663 * "result": { |
| 664 * "version": String |
| 665 * } |
| 666 * } |
| 667 */ |
| 668 class TypeVisitor extends HierarchicalApiVisitor |
| 669 with HtmlMixin, HtmlCodeGenerator { |
| 670 /** |
| 671 * Set of fields which should be shown in boldface, or null if no field |
| 672 * should be shown in boldface. |
| 673 */ |
| 674 final Set<String> fieldsToBold; |
| 675 |
| 676 /** |
| 677 * True if a short description should be generated. In a short description, |
| 678 * objects are shown as simply "object", and enums are shown as "String". |
| 679 */ |
| 680 final bool short; |
| 681 |
| 682 TypeVisitor(Api api, {this.fieldsToBold, this.short: false}) : super(api); |
| 683 |
| 684 @override |
| 685 void visitTypeEnum(TypeEnum typeEnum) { |
| 686 if (short) { |
| 687 write('String'); |
| 688 return; |
| 689 } |
| 690 writeln('enum {'); |
| 691 indent(() { |
| 692 for (TypeEnumValue value in typeEnum.values) { |
| 693 writeln(value.value); |
| 694 } |
| 695 }); |
| 696 write('}'); |
| 697 } |
| 698 |
| 699 @override |
| 700 void visitTypeList(TypeList typeList) { |
| 701 write('List<'); |
| 702 visitTypeDecl(typeList.itemType); |
| 703 write('>'); |
| 704 } |
| 705 |
| 706 @override |
| 707 void visitTypeMap(TypeMap typeMap) { |
| 708 write('Map<'); |
| 709 visitTypeDecl(typeMap.keyType); |
| 710 write(', '); |
| 711 visitTypeDecl(typeMap.valueType); |
| 712 write('>'); |
| 713 } |
| 714 |
| 715 @override |
| 716 void visitTypeObject(TypeObject typeObject) { |
| 717 if (short) { |
| 718 write('object'); |
| 719 return; |
| 720 } |
| 721 writeln('{'); |
| 722 indent(() { |
| 723 for (TypeObjectField field in typeObject.fields) { |
| 724 write('"'); |
| 725 if (fieldsToBold != null && fieldsToBold.contains(field.name)) { |
| 726 b(() { |
| 727 write(field.name); |
| 728 }); |
| 729 } else { |
| 730 write(field.name); |
| 731 } |
| 732 write('": '); |
| 733 if (field.value != null) { |
| 734 write(JSON.encode(field.value)); |
| 735 } else { |
| 736 if (field.optional) { |
| 737 gray(() { |
| 738 write('optional'); |
| 739 }); |
| 740 write(' '); |
| 741 } |
| 742 visitTypeDecl(field.type); |
| 743 } |
| 744 writeln(); |
| 745 } |
| 746 }); |
| 747 write('}'); |
| 748 } |
| 749 |
| 750 @override |
| 751 void visitTypeReference(TypeReference typeReference) { |
| 752 String displayName = typeReference.typeName; |
| 753 if (api.types.containsKey(typeReference.typeName)) { |
| 754 link('type_${typeReference.typeName}', () { |
| 755 write(displayName); |
| 756 }); |
| 757 } else { |
| 758 write(displayName); |
| 759 } |
| 760 } |
| 761 |
| 762 @override |
| 763 void visitTypeUnion(TypeUnion typeUnion) { |
| 764 bool verticalBarNeeded = false; |
| 765 for (TypeDecl choice in typeUnion.choices) { |
| 766 if (verticalBarNeeded) { |
| 767 write(' | '); |
| 768 } |
| 769 visitTypeDecl(choice); |
| 770 verticalBarNeeded = true; |
| 771 } |
| 772 } |
| 773 } |
OLD | NEW |