| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Copyright 2017 Google Inc. All rights reserved. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  |  * you may not use this file except in compliance with the License. | 
					
						
							|  |  |  |  * You may obtain a copy of the License at | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *     http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  |  * distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  |  * See the License for the specific language governing permissions and | 
					
						
							|  |  |  |  * limitations under the License. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-27 06:31:41 +08:00
										 |  |  | // @ts-check
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-08 03:11:40 +08:00
										 |  |  | const md = require('../markdown'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-29 09:38:00 +08:00
										 |  |  | /** @typedef {import('../markdown').MarkdownNode} MarkdownNode */ | 
					
						
							| 
									
										
										
										
											2020-12-27 06:31:41 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-05 09:59:23 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @typedef {{ | 
					
						
							|  |  |  |   *   name: string, | 
					
						
							|  |  |  |   *   args: ParsedType | null, | 
					
						
							|  |  |  |   *   retType: ParsedType | null, | 
					
						
							|  |  |  |   *   template: ParsedType | null, | 
					
						
							|  |  |  |   *   union: ParsedType | null, | 
					
						
							|  |  |  |   *   next: ParsedType | null, | 
					
						
							|  |  |  |   * }} ParsedType | 
					
						
							|  |  |  |   */ | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-09 07:00:14 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @typedef {{ | 
					
						
							|  |  |  |  *   only?: string[], | 
					
						
							| 
									
										
										
										
											2021-01-16 08:01:41 +08:00
										 |  |  |  *   aliases?: Object<string, string>, | 
					
						
							|  |  |  |   *  types?: Object<string, Documentation.Type>, | 
					
						
							| 
									
										
										
										
											2021-01-09 07:00:14 +08:00
										 |  |  |  * }} Langs | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @typedef {function({ | 
					
						
							|  |  |  |   *   clazz?: Documentation.Class, | 
					
						
							|  |  |  |   *   member?: Documentation.Member, | 
					
						
							|  |  |  |   *   param?: string, | 
					
						
							|  |  |  |   *   option?: string | 
					
						
							|  |  |  |   * }): string} Renderer | 
					
						
							|  |  |  |   */ | 
					
						
							| 
									
										
										
										
											2021-01-05 09:59:23 +08:00
										 |  |  |   | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  | class Documentation { | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * @param {!Array<!Documentation.Class>} classesArray | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   constructor(classesArray) { | 
					
						
							|  |  |  |     this.classesArray = classesArray; | 
					
						
							|  |  |  |     /** @type {!Map<string, !Documentation.Class>} */ | 
					
						
							|  |  |  |     this.classes = new Map(); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     this.index(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * @param {string[]} errors | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   copyDocsFromSuperclasses(errors) { | 
					
						
							|  |  |  |     for (const [name, clazz] of this.classes.entries()) { | 
					
						
							|  |  |  |       clazz.validateOrder(errors, clazz); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (!clazz.extends || clazz.extends === 'EventEmitter' || clazz.extends === 'Error') | 
					
						
							|  |  |  |         continue; | 
					
						
							|  |  |  |       const superClass = this.classes.get(clazz.extends); | 
					
						
							|  |  |  |       if (!superClass) { | 
					
						
							|  |  |  |         errors.push(`Undefined superclass: ${superClass} in ${name}`); | 
					
						
							|  |  |  |         continue; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       for (const memberName of clazz.members.keys()) { | 
					
						
							|  |  |  |         if (superClass.members.has(memberName)) | 
					
						
							|  |  |  |           errors.push(`Member documentation overrides base: ${name}.${memberName} over ${clazz.extends}.${memberName}`); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       clazz.membersArray = [...clazz.membersArray, ...superClass.membersArray.map(c => c.clone())]; | 
					
						
							|  |  |  |       clazz.index(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * @param {string} lang | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   filterForLanguage(lang) { | 
					
						
							|  |  |  |     const classesArray = []; | 
					
						
							|  |  |  |     for (const clazz of this.classesArray) { | 
					
						
							| 
									
										
										
										
											2021-01-09 07:00:14 +08:00
										 |  |  |       if (clazz.langs.only && !clazz.langs.only.includes(lang)) | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |         continue; | 
					
						
							|  |  |  |       clazz.filterForLanguage(lang); | 
					
						
							|  |  |  |       classesArray.push(clazz); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.classesArray = classesArray; | 
					
						
							|  |  |  |     this.index(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   index() { | 
					
						
							|  |  |  |     for (const cls of this.classesArray) { | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |       this.classes.set(cls.name, cls); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |       cls.index(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * @param {Renderer} linkRenderer | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   setLinkRenderer(linkRenderer) { | 
					
						
							|  |  |  |     // @type {Map<string, Documentation.Class>}
 | 
					
						
							|  |  |  |     const classesMap = new Map(); | 
					
						
							|  |  |  |     const membersMap = new Map(); | 
					
						
							|  |  |  |     for (const clazz of this.classesArray) { | 
					
						
							|  |  |  |       classesMap.set(clazz.name, clazz); | 
					
						
							|  |  |  |       for (const member of clazz.membersArray) | 
					
						
							|  |  |  |         membersMap.set(`${member.kind}: ${clazz.name}.${member.name}`, member); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this._patchLinks = nodes => patchLinks(nodes, classesMap, membersMap, linkRenderer); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (const clazz of this.classesArray) | 
					
						
							|  |  |  |       clazz.visit(item => this._patchLinks(item.spec)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * @param {MarkdownNode[]} nodes | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   renderLinksInText(nodes) { | 
					
						
							|  |  |  |     this._patchLinks(nodes); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   generateSourceCodeComments() { | 
					
						
							|  |  |  |     for (const clazz of this.classesArray) | 
					
						
							|  |  |  |       clazz.visit(item => item.comment = generateSourceCodeComment(item.spec)); | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Documentation.Class = class { | 
					
						
							|  |  |  |   /** | 
					
						
							| 
									
										
										
										
											2021-01-09 07:00:14 +08:00
										 |  |  |    * @param {Langs} langs | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |    * @param {string} name | 
					
						
							|  |  |  |    * @param {!Array<!Documentation.Member>} membersArray | 
					
						
							|  |  |  |    * @param {?string=} extendsName | 
					
						
							| 
									
										
										
										
											2020-12-27 06:31:41 +08:00
										 |  |  |    * @param {MarkdownNode[]=} spec | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |    */ | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |   constructor(langs, name, membersArray, extendsName = null, spec = undefined) { | 
					
						
							|  |  |  |     this.langs = langs; | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |     this.name = name; | 
					
						
							|  |  |  |     this.membersArray = membersArray; | 
					
						
							| 
									
										
										
										
											2020-12-27 06:31:41 +08:00
										 |  |  |     this.spec = spec; | 
					
						
							| 
									
										
										
										
											2019-12-21 05:07:14 +08:00
										 |  |  |     this.extends = extendsName; | 
					
						
							| 
									
										
										
										
											2020-12-27 06:31:41 +08:00
										 |  |  |     this.comment =  ''; | 
					
						
							| 
									
										
										
										
											2019-12-21 05:07:14 +08:00
										 |  |  |     this.index(); | 
					
						
							| 
									
										
										
										
											2020-12-29 15:42:51 +08:00
										 |  |  |     const match = name.match(/(JS|CDP|[A-Z])(.*)/); | 
					
						
							|  |  |  |     this.varName = match[1].toLowerCase() + match[2]; | 
					
						
							| 
									
										
										
										
											2019-12-21 05:07:14 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   index() { | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |     /** @type {!Map<string, !Documentation.Member>} */ | 
					
						
							|  |  |  |     this.members = new Map(); | 
					
						
							|  |  |  |     /** @type {!Map<string, !Documentation.Member>} */ | 
					
						
							|  |  |  |     this.properties = new Map(); | 
					
						
							|  |  |  |     /** @type {!Array<!Documentation.Member>} */ | 
					
						
							|  |  |  |     this.propertiesArray = []; | 
					
						
							|  |  |  |     /** @type {!Map<string, !Documentation.Member>} */ | 
					
						
							|  |  |  |     this.methods = new Map(); | 
					
						
							|  |  |  |     /** @type {!Array<!Documentation.Member>} */ | 
					
						
							|  |  |  |     this.methodsArray = []; | 
					
						
							|  |  |  |     /** @type {!Map<string, !Documentation.Member>} */ | 
					
						
							|  |  |  |     this.events = new Map(); | 
					
						
							|  |  |  |     /** @type {!Array<!Documentation.Member>} */ | 
					
						
							|  |  |  |     this.eventsArray = []; | 
					
						
							| 
									
										
										
										
											2019-12-21 05:07:14 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     for (const member of this.membersArray) { | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |       this.members.set(member.name, member); | 
					
						
							|  |  |  |       if (member.kind === 'method') { | 
					
						
							|  |  |  |         this.methods.set(member.name, member); | 
					
						
							|  |  |  |         this.methodsArray.push(member); | 
					
						
							|  |  |  |       } else if (member.kind === 'property') { | 
					
						
							|  |  |  |         this.properties.set(member.name, member); | 
					
						
							|  |  |  |         this.propertiesArray.push(member); | 
					
						
							|  |  |  |       } else if (member.kind === 'event') { | 
					
						
							|  |  |  |         this.events.set(member.name, member); | 
					
						
							|  |  |  |         this.eventsArray.push(member); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2020-12-29 15:42:51 +08:00
										 |  |  |       member.clazz = this; | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |       member.index(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * @param {string} lang | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   filterForLanguage(lang) { | 
					
						
							|  |  |  |     const membersArray = []; | 
					
						
							|  |  |  |     for (const member of this.membersArray) { | 
					
						
							| 
									
										
										
										
											2021-01-09 07:00:14 +08:00
										 |  |  |       if (member.langs.only && !member.langs.only.includes(lang)) | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |         continue; | 
					
						
							|  |  |  |       member.filterForLanguage(lang); | 
					
						
							|  |  |  |       membersArray.push(member); | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     this.membersArray = membersArray; | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-12-21 08:57:21 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-25 10:24:02 +08:00
										 |  |  |   validateOrder(errors, cls) { | 
					
						
							| 
									
										
										
										
											2019-12-21 08:57:21 +08:00
										 |  |  |     const members = this.membersArray; | 
					
						
							|  |  |  |     // Events should go first.
 | 
					
						
							|  |  |  |     let eventIndex = 0; | 
					
						
							|  |  |  |     for (; eventIndex < members.length && members[eventIndex].kind === 'event'; ++eventIndex); | 
					
						
							|  |  |  |     for (; eventIndex < members.length && members[eventIndex].kind !== 'event'; ++eventIndex); | 
					
						
							|  |  |  |     if (eventIndex < members.length) | 
					
						
							|  |  |  |       errors.push(`Events should go first. Event '${members[eventIndex].name}' in class ${cls.name} breaks order`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Constructor should be right after events and before all other members.
 | 
					
						
							|  |  |  |     const constructorIndex = members.findIndex(member => member.kind === 'method' && member.name === 'constructor'); | 
					
						
							|  |  |  |     if (constructorIndex > 0 && members[constructorIndex - 1].kind !== 'event') | 
					
						
							|  |  |  |       errors.push(`Constructor of ${cls.name} should go before other methods`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Events should be sorted alphabetically.
 | 
					
						
							|  |  |  |     for (let i = 0; i < members.length - 1; ++i) { | 
					
						
							|  |  |  |       const member1 = this.membersArray[i]; | 
					
						
							|  |  |  |       const member2 = this.membersArray[i + 1]; | 
					
						
							|  |  |  |       if (member1.kind !== 'event' || member2.kind !== 'event') | 
					
						
							|  |  |  |         continue; | 
					
						
							|  |  |  |       if (member1.name > member2.name) | 
					
						
							|  |  |  |         errors.push(`Event '${member1.name}' in class ${this.name} breaks alphabetic ordering of events`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // All other members should be sorted alphabetically.
 | 
					
						
							|  |  |  |     for (let i = 0; i < members.length - 1; ++i) { | 
					
						
							|  |  |  |       const member1 = this.membersArray[i]; | 
					
						
							|  |  |  |       const member2 = this.membersArray[i + 1]; | 
					
						
							|  |  |  |       if (member1.kind === 'event' || member2.kind === 'event') | 
					
						
							|  |  |  |         continue; | 
					
						
							|  |  |  |       if (member1.kind === 'method' && member1.name === 'constructor') | 
					
						
							|  |  |  |         continue; | 
					
						
							| 
									
										
										
										
											2020-04-02 04:09:24 +08:00
										 |  |  |       if (member1.name.replace(/^\$+/, '$') > member2.name.replace(/^\$+/, '$')) { | 
					
						
							| 
									
										
										
										
											2019-12-21 08:57:21 +08:00
										 |  |  |         let memberName1 = `${this.name}.${member1.name}`; | 
					
						
							|  |  |  |         if (member1.kind === 'method') | 
					
						
							|  |  |  |           memberName1 += '()'; | 
					
						
							|  |  |  |         let memberName2 = `${this.name}.${member2.name}`; | 
					
						
							|  |  |  |         if (member2.kind === 'method') | 
					
						
							|  |  |  |           memberName2 += '()'; | 
					
						
							|  |  |  |         errors.push(`Bad alphabetic ordering of ${this.name} members: ${memberName1} should go after ${memberName2}`); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-12-27 06:31:41 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-29 09:38:00 +08:00
										 |  |  |   /**  | 
					
						
							|  |  |  |    * @param {function(Documentation.Member|Documentation.Class): void} visitor | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2020-12-27 06:31:41 +08:00
										 |  |  |   visit(visitor) { | 
					
						
							|  |  |  |     visitor(this); | 
					
						
							|  |  |  |     for (const p of this.propertiesArray) | 
					
						
							|  |  |  |       p.visit(visitor); | 
					
						
							|  |  |  |     for (const m of this.methodsArray) | 
					
						
							|  |  |  |       m.visit(visitor); | 
					
						
							|  |  |  |     for (const e of this.eventsArray) | 
					
						
							|  |  |  |       e.visit(visitor); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Documentation.Member = class { | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * @param {string} kind | 
					
						
							| 
									
										
										
										
											2021-01-09 07:00:14 +08:00
										 |  |  |    * @param {Langs} langs | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |    * @param {string} name | 
					
						
							|  |  |  |    * @param {?Documentation.Type} type | 
					
						
							|  |  |  |    * @param {!Array<!Documentation.Member>} argsArray | 
					
						
							| 
									
										
										
										
											2020-12-27 06:31:41 +08:00
										 |  |  |    * @param {MarkdownNode[]=} spec | 
					
						
							| 
									
										
										
										
											2020-03-20 16:30:35 +08:00
										 |  |  |    * @param {boolean=} required | 
					
						
							|  |  |  |    * @param {string[]=} templates | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |    */ | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |   constructor(kind, langs, name, type, argsArray, spec = undefined, required = true, templates = []) { | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |     this.kind = kind; | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     this.langs = langs; | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |     this.name = name; | 
					
						
							|  |  |  |     this.type = type; | 
					
						
							| 
									
										
										
										
											2020-12-27 06:31:41 +08:00
										 |  |  |     this.spec = spec; | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |     this.argsArray = argsArray; | 
					
						
							|  |  |  |     this.required = required; | 
					
						
							| 
									
										
										
										
											2020-12-27 06:31:41 +08:00
										 |  |  |     this.comment =  ''; | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |     /** @type {!Map<string, !Documentation.Member>} */ | 
					
						
							|  |  |  |     this.args = new Map(); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     this.index(); | 
					
						
							| 
									
										
										
										
											2020-12-29 15:42:51 +08:00
										 |  |  |     /** @type {!Documentation.Class} */ | 
					
						
							|  |  |  |     this.clazz = null; | 
					
						
							| 
									
										
										
										
											2021-01-08 03:11:40 +08:00
										 |  |  |     this.deprecated = false; | 
					
						
							|  |  |  |     if (spec) { | 
					
						
							|  |  |  |       md.visitAll(spec, node => { | 
					
						
							|  |  |  |         if (node.text && node.text.includes('**DEPRECATED**')) | 
					
						
							|  |  |  |           this.deprecated = true; | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2021-01-08 15:37:53 +08:00
										 |  |  |     }; | 
					
						
							|  |  |  |     this.async = false; | 
					
						
							| 
									
										
										
										
											2021-01-09 07:00:14 +08:00
										 |  |  |     this.alias = name; | 
					
						
							| 
									
										
										
										
											2020-12-29 15:42:51 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |   index() { | 
					
						
							|  |  |  |     this.args = new Map(); | 
					
						
							|  |  |  |     for (const arg of this.argsArray) { | 
					
						
							|  |  |  |       this.args.set(arg.name, arg); | 
					
						
							|  |  |  |       if (arg.name === 'options') | 
					
						
							|  |  |  |         arg.type.properties.sort((p1, p2) => p1.name.localeCompare(p2.name)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |    * @param {string} lang | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   filterForLanguage(lang) { | 
					
						
							| 
									
										
										
										
											2021-01-16 08:01:41 +08:00
										 |  |  |     if (this.langs.aliases && this.langs.aliases[lang]) | 
					
						
							|  |  |  |       this.alias = this.langs.aliases[lang]; | 
					
						
							|  |  |  |     if (this.langs.types && this.langs.types[lang]) | 
					
						
							|  |  |  |       this.type = this.langs.types[lang]; | 
					
						
							|  |  |  |     this.type.filterForLanguage(lang); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     const argsArray = []; | 
					
						
							|  |  |  |     for (const arg of this.argsArray) { | 
					
						
							| 
									
										
										
										
											2021-01-09 07:00:14 +08:00
										 |  |  |       if (arg.langs.only && !arg.langs.only.includes(lang)) | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |         continue; | 
					
						
							| 
									
										
										
										
											2021-01-09 07:00:14 +08:00
										 |  |  |       if (arg.langs.aliases && arg.langs.aliases[lang]) | 
					
						
							|  |  |  |         arg.alias = arg.langs.aliases[lang]; | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |       arg.filterForLanguage(lang); | 
					
						
							| 
									
										
										
										
											2021-01-11 10:18:35 +08:00
										 |  |  |       arg.type.filterForLanguage(lang); | 
					
						
							|  |  |  |       if (arg.name === 'options' && !arg.type.properties.length) | 
					
						
							|  |  |  |         continue; | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |       argsArray.push(arg); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.argsArray = argsArray; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-29 15:42:51 +08:00
										 |  |  |   clone() { | 
					
						
							| 
									
										
										
										
											2021-01-08 15:37:53 +08:00
										 |  |  |     const result = new Documentation.Member(this.kind, this.langs, this.name, this.type, this.argsArray, this.spec, this.required); | 
					
						
							|  |  |  |     result.async = this.async; | 
					
						
							|  |  |  |     return result; | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							| 
									
										
										
										
											2021-01-09 07:00:14 +08:00
										 |  |  |    * @param {Langs} langs | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |    * @param {string} name | 
					
						
							|  |  |  |    * @param {!Array<!Documentation.Member>} argsArray | 
					
						
							|  |  |  |    * @param {?Documentation.Type} returnType | 
					
						
							| 
									
										
										
										
											2020-12-27 06:31:41 +08:00
										 |  |  |    * @param {MarkdownNode[]=} spec | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |    * @return {!Documentation.Member} | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |   static createMethod(langs, name, argsArray, returnType, spec) { | 
					
						
							|  |  |  |     return new Documentation.Member('method', langs, name, returnType, argsArray, spec); | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							| 
									
										
										
										
											2021-01-09 07:00:14 +08:00
										 |  |  |    * @param {!Langs} langs | 
					
						
							|  |  |  |    * @param {!string} name | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |    * @param {!Documentation.Type} type | 
					
						
							| 
									
										
										
										
											2021-01-09 07:00:14 +08:00
										 |  |  |    * @param {!MarkdownNode[]=} spec | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |    * @param {boolean=} required | 
					
						
							|  |  |  |    * @return {!Documentation.Member} | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |   static createProperty(langs, name, type, spec, required) { | 
					
						
							|  |  |  |     return new Documentation.Member('property', langs, name, type, [], spec, required); | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							| 
									
										
										
										
											2021-01-09 07:00:14 +08:00
										 |  |  |    * @param {Langs} langs | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |    * @param {string} name | 
					
						
							|  |  |  |    * @param {?Documentation.Type=} type | 
					
						
							| 
									
										
										
										
											2020-12-27 06:31:41 +08:00
										 |  |  |    * @param {MarkdownNode[]=} spec | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |    * @return {!Documentation.Member} | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |   static createEvent(langs, name, type = null, spec) { | 
					
						
							|  |  |  |     return new Documentation.Member('event', langs, name, type, [], spec); | 
					
						
							| 
									
										
										
										
											2020-12-27 06:31:41 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-29 09:38:00 +08:00
										 |  |  |   /**  | 
					
						
							|  |  |  |    * @param {function(Documentation.Member|Documentation.Class): void} visitor | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2020-12-27 06:31:41 +08:00
										 |  |  |   visit(visitor) { | 
					
						
							|  |  |  |     visitor(this); | 
					
						
							|  |  |  |     if (this.type) | 
					
						
							|  |  |  |       this.type.visit(visitor); | 
					
						
							|  |  |  |     for (const arg of this.argsArray) | 
					
						
							|  |  |  |       arg.visit(visitor); | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Documentation.Type = class { | 
					
						
							| 
									
										
										
										
											2021-01-05 09:59:23 +08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * @param {string} expression | 
					
						
							|  |  |  |    * @param {!Array<!Documentation.Member>=} properties | 
					
						
							|  |  |  |    * @return {Documentation.Type} | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   static parse(expression, properties = []) { | 
					
						
							|  |  |  |     expression = expression.replace(/\\\(/g, '(').replace(/\\\)/g, ')'); | 
					
						
							|  |  |  |     const type = Documentation.Type.fromParsedType(parseTypeExpression(expression)); | 
					
						
							| 
									
										
										
										
											2021-01-06 01:42:49 +08:00
										 |  |  |     type.expression = expression; | 
					
						
							|  |  |  |     if (type.name === 'number') | 
					
						
							|  |  |  |       throw new Error('Number types should be either int or float, not number in: ' + expression); | 
					
						
							| 
									
										
										
										
											2021-01-05 09:59:23 +08:00
										 |  |  |     if (!properties.length) | 
					
						
							|  |  |  |       return type; | 
					
						
							|  |  |  |     const types = []; | 
					
						
							|  |  |  |     type._collectAllTypes(types); | 
					
						
							|  |  |  |     let success = false; | 
					
						
							|  |  |  |     for (const t of types) { | 
					
						
							|  |  |  |       if (t.name === 'Object') { | 
					
						
							|  |  |  |         t.properties = properties; | 
					
						
							|  |  |  |         success = true; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (!success) | 
					
						
							|  |  |  |       throw new Error('Nested properties given, but there are no objects in type expression: ' + expression); | 
					
						
							|  |  |  |     return type; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * @param {ParsedType} parsedType | 
					
						
							|  |  |  |    * @return {Documentation.Type} | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   static fromParsedType(parsedType, inUnion = false) { | 
					
						
							|  |  |  |     if (!inUnion && parsedType.union) { | 
					
						
							|  |  |  |       const type = new Documentation.Type('union'); | 
					
						
							|  |  |  |       type.union = []; | 
					
						
							|  |  |  |       for (let t = parsedType; t; t = t.union) | 
					
						
							|  |  |  |         type.union.push(Documentation.Type.fromParsedType(t, true)); | 
					
						
							|  |  |  |       return type; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (parsedType.args) { | 
					
						
							|  |  |  |       const type = new Documentation.Type('function'); | 
					
						
							|  |  |  |       type.args = []; | 
					
						
							|  |  |  |       for (let t = parsedType.args; t; t = t.next) | 
					
						
							|  |  |  |         type.args.push(Documentation.Type.fromParsedType(t)); | 
					
						
							|  |  |  |       type.returnType = parsedType.retType ? Documentation.Type.fromParsedType(parsedType.retType) : null; | 
					
						
							|  |  |  |       return type; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (parsedType.template) { | 
					
						
							|  |  |  |       const type = new Documentation.Type(parsedType.name); | 
					
						
							|  |  |  |       type.templates = []; | 
					
						
							|  |  |  |       for (let t = parsedType.template; t; t = t.next) | 
					
						
							|  |  |  |         type.templates.push(Documentation.Type.fromParsedType(t)); | 
					
						
							|  |  |  |       return type; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return new Documentation.Type(parsedType.name); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * @param {string} name | 
					
						
							|  |  |  |    * @param {!Array<!Documentation.Member>=} properties | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2021-01-05 09:59:23 +08:00
										 |  |  |   constructor(name, properties) { | 
					
						
							|  |  |  |     this.name = name.replace(/^\[/, '').replace(/\]$/, ''); | 
					
						
							|  |  |  |     this.properties = this.name === 'Object' ? properties : undefined; | 
					
						
							|  |  |  |     /** @type {Documentation.Type[]} | undefined */ | 
					
						
							|  |  |  |     this.union; | 
					
						
							|  |  |  |     /** @type {Documentation.Type[]} | undefined */ | 
					
						
							|  |  |  |     this.args; | 
					
						
							|  |  |  |     /** @type {Documentation.Type} | undefined */ | 
					
						
							|  |  |  |     this.returnType; | 
					
						
							|  |  |  |     /** @type {Documentation.Type[]} | undefined */ | 
					
						
							|  |  |  |     this.templates; | 
					
						
							| 
									
										
										
										
											2021-01-06 01:42:49 +08:00
										 |  |  |     /** @type {string | undefined } */ | 
					
						
							|  |  |  |     this.expression; | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-12-27 06:31:41 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   visit(visitor) { | 
					
						
							| 
									
										
										
										
											2021-01-05 09:59:23 +08:00
										 |  |  |     const types = []; | 
					
						
							|  |  |  |     this._collectAllTypes(types); | 
					
						
							|  |  |  |     for (const type of types) { | 
					
						
							|  |  |  |       for (const p of type.properties || []) | 
					
						
							|  |  |  |         p.visit(visitor); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * @returns {Documentation.Member[]} | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   deepProperties() { | 
					
						
							|  |  |  |     const types = []; | 
					
						
							|  |  |  |     this._collectAllTypes(types); | 
					
						
							|  |  |  |     for (const type of types) { | 
					
						
							|  |  |  |       if (type.properties && type.properties.length) | 
					
						
							|  |  |  |         return type.properties; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return []; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-11 10:18:35 +08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * @param {string} lang | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   filterForLanguage(lang) { | 
					
						
							|  |  |  |     if (!this.properties) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     const properties = []; | 
					
						
							|  |  |  |     for (const prop of this.properties) { | 
					
						
							|  |  |  |       if (prop.langs.only && !prop.langs.only.includes(lang)) | 
					
						
							|  |  |  |         continue; | 
					
						
							|  |  |  |       prop.filterForLanguage(lang); | 
					
						
							|  |  |  |       properties.push(prop); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.properties = properties; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-05 09:59:23 +08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * @param {Documentation.Type[]} result | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   _collectAllTypes(result) { | 
					
						
							|  |  |  |     result.push(this); | 
					
						
							|  |  |  |     for (const t of this.union || []) | 
					
						
							|  |  |  |       t._collectAllTypes(result); | 
					
						
							|  |  |  |     for (const t of this.args || []) | 
					
						
							|  |  |  |       t._collectAllTypes(result); | 
					
						
							|  |  |  |     for (const t of this.templates || []) | 
					
						
							|  |  |  |       t._collectAllTypes(result); | 
					
						
							|  |  |  |     if (this.returnType) | 
					
						
							|  |  |  |       this.returnType._collectAllTypes(result); | 
					
						
							| 
									
										
										
										
											2020-12-27 06:31:41 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-05 09:59:23 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {string} type | 
					
						
							|  |  |  |  * @returns {ParsedType} | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function parseTypeExpression(type) { | 
					
						
							|  |  |  |   type = type.trim(); | 
					
						
							|  |  |  |   let name = type; | 
					
						
							|  |  |  |   let next = null; | 
					
						
							|  |  |  |   let template = null; | 
					
						
							|  |  |  |   let args = null; | 
					
						
							|  |  |  |   let retType = null; | 
					
						
							|  |  |  |   let firstTypeLength = type.length; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (let i = 0; i < type.length; i++) { | 
					
						
							|  |  |  |     if (type[i] === '<') { | 
					
						
							|  |  |  |       name = type.substring(0, i); | 
					
						
							|  |  |  |       const matching = matchingBracket(type.substring(i), '<', '>'); | 
					
						
							|  |  |  |       template = parseTypeExpression(type.substring(i + 1, i + matching - 1)); | 
					
						
							|  |  |  |       firstTypeLength = i + matching; | 
					
						
							|  |  |  |       break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (type[i] === '(') { | 
					
						
							|  |  |  |       name = type.substring(0, i); | 
					
						
							|  |  |  |       const matching = matchingBracket(type.substring(i), '(', ')'); | 
					
						
							|  |  |  |       args = parseTypeExpression(type.substring(i + 1, i + matching - 1)); | 
					
						
							|  |  |  |       i = i + matching; | 
					
						
							|  |  |  |       if (type[i] === ':') { | 
					
						
							|  |  |  |         retType = parseTypeExpression(type.substring(i + 1)); | 
					
						
							|  |  |  |         next = retType.next; | 
					
						
							|  |  |  |         retType.next = null; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (type[i] === '|' || type[i] === ',') { | 
					
						
							|  |  |  |       name = type.substring(0, i); | 
					
						
							|  |  |  |       firstTypeLength = i; | 
					
						
							|  |  |  |       break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   let union = null; | 
					
						
							|  |  |  |   if (type[firstTypeLength] === '|') | 
					
						
							|  |  |  |     union = parseTypeExpression(type.substring(firstTypeLength + 1)); | 
					
						
							|  |  |  |   else if (type[firstTypeLength] === ',') | 
					
						
							|  |  |  |     next = parseTypeExpression(type.substring(firstTypeLength + 1)); | 
					
						
							|  |  |  |   return { | 
					
						
							|  |  |  |     name, | 
					
						
							|  |  |  |     args, | 
					
						
							|  |  |  |     retType, | 
					
						
							|  |  |  |     template, | 
					
						
							|  |  |  |     union, | 
					
						
							|  |  |  |     next | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @param {string} str | 
					
						
							|  |  |  |  * @param {any} open | 
					
						
							|  |  |  |  * @param {any} close | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function matchingBracket(str, open, close) { | 
					
						
							|  |  |  |   let count = 1; | 
					
						
							|  |  |  |   let i = 1; | 
					
						
							|  |  |  |   for (; i < str.length && count; i++) { | 
					
						
							|  |  |  |     if (str[i] === open) | 
					
						
							|  |  |  |       count++; | 
					
						
							|  |  |  |     else if (str[i] === close) | 
					
						
							|  |  |  |       count--; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return i; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {MarkdownNode[]} spec | 
					
						
							|  |  |  |  * @param {Map<string, Documentation.Class>} classesMap | 
					
						
							|  |  |  |  * @param {Map<string, Documentation.Member>} membersMap | 
					
						
							|  |  |  |  * @param {Renderer} linkRenderer | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function patchLinks(spec, classesMap, membersMap, linkRenderer) { | 
					
						
							|  |  |  |   if (!spec) | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   md.visitAll(spec, node => { | 
					
						
							|  |  |  |     if (!node.text) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     node.text = node.text.replace(/\[`((?:event|method|property): [^\]]+)`\]/g, (match, p1) => { | 
					
						
							|  |  |  |       const member = membersMap.get(p1); | 
					
						
							|  |  |  |       if (!member) | 
					
						
							|  |  |  |         throw new Error('Undefined member references: ' + match); | 
					
						
							|  |  |  |       return linkRenderer({ member }) || match; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     node.text = node.text.replace(/\[`(param|option): ([^\]]+)`\]/g, (match, p1, p2) => { | 
					
						
							|  |  |  |       if (p1 === 'param') | 
					
						
							|  |  |  |         return linkRenderer({ param: p2 }) || match; | 
					
						
							|  |  |  |       if (p1 === 'option') | 
					
						
							|  |  |  |         return linkRenderer({ option: p2 }) || match; | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2021-01-28 08:21:27 +08:00
										 |  |  |     node.text = node.text.replace(/\[([\w]+)\]/g, (match, p1) => { | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |       const clazz = classesMap.get(p1); | 
					
						
							|  |  |  |       if (clazz) | 
					
						
							|  |  |  |         return linkRenderer({ clazz }) || match; | 
					
						
							|  |  |  |       return match; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @param {MarkdownNode[]} spec | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function generateSourceCodeComment(spec) { | 
					
						
							|  |  |  |   const comments = (spec || []).filter(n => !n.type.startsWith('h') && (n.type !== 'li' ||  n.liType !== 'default')).map(c => md.clone(c)); | 
					
						
							|  |  |  |   md.visitAll(comments, node => { | 
					
						
							|  |  |  |     if (node.liType === 'bullet') | 
					
						
							|  |  |  |       node.liType = 'default'; | 
					
						
							| 
									
										
										
										
											2021-01-13 04:14:27 +08:00
										 |  |  |     if (node.type === 'note') { | 
					
						
							|  |  |  |       node.type = 'text'; | 
					
						
							|  |  |  |       node.text = '> NOTE: ' + node.text; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |   }); | 
					
						
							|  |  |  |   return md.render(comments, 120); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-19 10:18:28 +08:00
										 |  |  | module.exports = Documentation; |