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

Side by Side Diff: pkg/analyzer_plugin/tool/spec/to_html.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
« no previous file with comments | « pkg/analyzer_plugin/tool/spec/plugin_spec.html ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 /**
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 }
OLDNEW
« no previous file with comments | « pkg/analyzer_plugin/tool/spec/plugin_spec.html ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698