| 
									
										
										
										
											2021-01-08 07:00:04 +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. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // @ts-check
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const fs = require('fs'); | 
					
						
							|  |  |  | const path = require('path'); | 
					
						
							|  |  |  | const md = require('../markdown'); | 
					
						
							|  |  |  | const Documentation = require('./documentation'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** @typedef {import('../markdown').MarkdownNode} MarkdownNode */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ApiParser { | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * @param {string} apiDir | 
					
						
							| 
									
										
										
										
											2021-07-23 05:47:12 +08:00
										 |  |  |    * @param {string=} paramsPath | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |    */ | 
					
						
							| 
									
										
										
										
											2021-07-23 05:47:12 +08:00
										 |  |  |   constructor(apiDir, paramsPath) { | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     let bodyParts = []; | 
					
						
							|  |  |  |     for (const name of fs.readdirSync(apiDir)) { | 
					
						
							| 
									
										
										
										
											2021-07-23 02:01:18 +08:00
										 |  |  |       if (!name.endsWith('.md')) | 
					
						
							|  |  |  |         continue; | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |       if (name === 'params.md') | 
					
						
							|  |  |  |         paramsPath = path.join(apiDir, name); | 
					
						
							| 
									
										
										
										
											2021-01-08 08:12:25 +08:00
										 |  |  |       else | 
					
						
							|  |  |  |         bodyParts.push(fs.readFileSync(path.join(apiDir, name)).toString()); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     } | 
					
						
							|  |  |  |     const body = md.parse(bodyParts.join('\n')); | 
					
						
							|  |  |  |     const params = paramsPath ? md.parse(fs.readFileSync(paramsPath).toString()) : null; | 
					
						
							| 
									
										
										
										
											2022-03-03 04:43:16 +08:00
										 |  |  |     checkNoDuplicateParamEntries(params); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     const api = params ? applyTemplates(body, params) : body; | 
					
						
							|  |  |  |     /** @type {Map<string, Documentation.Class>} */ | 
					
						
							|  |  |  |     this.classes = new Map(); | 
					
						
							|  |  |  |     md.visitAll(api, node => { | 
					
						
							|  |  |  |       if (node.type === 'h1') | 
					
						
							|  |  |  |         this.parseClass(node); | 
					
						
							| 
									
										
										
										
											2021-02-02 03:43:26 +08:00
										 |  |  |     }); | 
					
						
							|  |  |  |     md.visitAll(api, node => { | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |       if (node.type === 'h2') | 
					
						
							|  |  |  |         this.parseMember(node); | 
					
						
							| 
									
										
										
										
											2021-02-02 03:43:26 +08:00
										 |  |  |     }); | 
					
						
							|  |  |  |     md.visitAll(api, node => { | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |       if (node.type === 'h3') | 
					
						
							|  |  |  |         this.parseArgument(node); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     this.documentation = new Documentation([...this.classes.values()]); | 
					
						
							|  |  |  |     this.documentation.index(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * @param {MarkdownNode} node | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   parseClass(node) { | 
					
						
							|  |  |  |     let extendsName = null; | 
					
						
							|  |  |  |     const name = node.text.substring('class: '.length); | 
					
						
							|  |  |  |     for (const member of node.children) { | 
					
						
							|  |  |  |       if (member.type.startsWith('h')) | 
					
						
							|  |  |  |         continue; | 
					
						
							| 
									
										
										
										
											2021-01-08 08:12:25 +08:00
										 |  |  |       if (member.type === 'li' && member.liType === 'bullet' && member.text.startsWith('extends: [')) { | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |         extendsName = member.text.substring('extends: ['.length, member.text.indexOf(']')); | 
					
						
							|  |  |  |         continue; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-07-06 08:24:50 +08:00
										 |  |  |     const clazz = new Documentation.Class(extractMetainfo(node), name, [], extendsName, extractComments(node)); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     this.classes.set(clazz.name, clazz); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * @param {MarkdownNode} spec | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   parseMember(spec) { | 
					
						
							| 
									
										
										
										
											2022-04-08 09:51:05 +08:00
										 |  |  |     const match = spec.text.match(/(event|method|property|async method|optional method|optional async method): ([^.]+)\.(.*)/); | 
					
						
							| 
									
										
										
										
											2021-01-11 13:00:52 +08:00
										 |  |  |     if (!match) | 
					
						
							|  |  |  |       throw new Error('Invalid member: ' + spec.text); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     const name = match[3]; | 
					
						
							|  |  |  |     let returnType = null; | 
					
						
							| 
									
										
										
										
											2022-04-07 10:02:10 +08:00
										 |  |  |     let optional = false; | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     for (const item of spec.children || []) { | 
					
						
							| 
									
										
										
										
											2022-04-07 10:02:10 +08:00
										 |  |  |       if (item.type === 'li' && item.liType === 'default') { | 
					
						
							| 
									
										
										
										
											2022-04-15 04:22:42 +08:00
										 |  |  |         const parsed = this.parseType(item); | 
					
						
							| 
									
										
										
										
											2022-04-07 10:02:10 +08:00
										 |  |  |         returnType = parsed.type; | 
					
						
							|  |  |  |         optional = parsed.optional; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     } | 
					
						
							|  |  |  |     if (!returnType) | 
					
						
							|  |  |  |       returnType = new Documentation.Type('void'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 04:36:20 +08:00
										 |  |  |     const comments = extractComments(spec); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     let member; | 
					
						
							|  |  |  |     if (match[1] === 'event') | 
					
						
							| 
									
										
										
										
											2022-07-06 08:24:50 +08:00
										 |  |  |       member = Documentation.Member.createEvent(extractMetainfo(spec), name, returnType, comments); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     if (match[1] === 'property') | 
					
						
							| 
									
										
										
										
											2022-07-06 08:24:50 +08:00
										 |  |  |       member = Documentation.Member.createProperty(extractMetainfo(spec), name, returnType, comments, !optional); | 
					
						
							| 
									
										
										
										
											2022-04-08 09:51:05 +08:00
										 |  |  |     if (['method', 'async method', 'optional method', 'optional async method'].includes(match[1])) { | 
					
						
							| 
									
										
										
										
											2022-07-06 08:24:50 +08:00
										 |  |  |       member = Documentation.Member.createMethod(extractMetainfo(spec), name, [], returnType, comments); | 
					
						
							| 
									
										
										
										
											2022-04-08 09:51:05 +08:00
										 |  |  |       if (match[1].includes('async')) | 
					
						
							| 
									
										
										
										
											2021-01-08 15:37:53 +08:00
										 |  |  |         member.async = true; | 
					
						
							| 
									
										
										
										
											2022-04-08 09:51:05 +08:00
										 |  |  |       if (match[1].includes('optional')) | 
					
						
							|  |  |  |         member.required = false; | 
					
						
							| 
									
										
										
										
											2021-01-08 15:37:53 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     const clazz = this.classes.get(match[2]); | 
					
						
							| 
									
										
										
										
											2021-02-10 04:11:48 +08:00
										 |  |  |     const existingMember = clazz.membersArray.find(m => m.name === name && m.kind === member.kind); | 
					
						
							| 
									
										
										
										
											2021-02-12 15:43:59 +08:00
										 |  |  |     if (existingMember && isTypeOverride(existingMember, member)) { | 
					
						
							| 
									
										
										
										
											2021-01-16 08:01:41 +08:00
										 |  |  |       for (const lang of member.langs.only) { | 
					
						
							|  |  |  |         existingMember.langs.types = existingMember.langs.types || {}; | 
					
						
							|  |  |  |         existingMember.langs.types[lang] = returnType; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       clazz.membersArray.push(member); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * @param {MarkdownNode} spec | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   parseArgument(spec) { | 
					
						
							| 
									
										
										
										
											2021-07-23 02:01:18 +08:00
										 |  |  |     const match = spec.text.match(/(param|option): (.*)/); | 
					
						
							|  |  |  |     if (!match) | 
					
						
							| 
									
										
										
										
											2021-01-30 03:08:22 +08:00
										 |  |  |       throw `Something went wrong with matching ${spec.text}`; | 
					
						
							| 
									
										
										
										
											2021-07-23 02:01:18 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // For "test.describe.only.title":
 | 
					
						
							|  |  |  |     // - className is "test"
 | 
					
						
							|  |  |  |     // - methodName is "describe.only"
 | 
					
						
							|  |  |  |     // - argument name is "title"
 | 
					
						
							|  |  |  |     const parts = match[2].split('.'); | 
					
						
							|  |  |  |     const className = parts[0]; | 
					
						
							|  |  |  |     const name = parts[parts.length - 1]; | 
					
						
							|  |  |  |     const methodName = parts.slice(1, parts.length - 1).join('.'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const clazz = this.classes.get(className); | 
					
						
							| 
									
										
										
										
											2021-01-11 13:00:52 +08:00
										 |  |  |     if (!clazz) | 
					
						
							| 
									
										
										
										
											2021-07-23 02:01:18 +08:00
										 |  |  |       throw new Error('Invalid class ' + className); | 
					
						
							| 
									
										
										
										
											2021-08-28 12:57:40 +08:00
										 |  |  |     const method = clazz.membersArray.find(m => m.kind === 'method' && m.name === methodName); | 
					
						
							| 
									
										
										
										
											2021-01-11 13:00:52 +08:00
										 |  |  |     if (!method) | 
					
						
							| 
									
										
										
										
											2021-07-23 02:01:18 +08:00
										 |  |  |       throw new Error(`Invalid method ${className}.${methodName} when parsing: ${match[0]}`); | 
					
						
							| 
									
										
										
										
											2021-01-29 09:51:41 +08:00
										 |  |  |     if (!name) | 
					
						
							|  |  |  |       throw new Error('Invalid member name ' + spec.text); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     if (match[1] === 'param') { | 
					
						
							| 
									
										
										
										
											2021-01-29 09:51:41 +08:00
										 |  |  |       const arg = this.parseProperty(spec); | 
					
						
							|  |  |  |       arg.name = name; | 
					
						
							|  |  |  |       const existingArg = method.argsArray.find(m => m.name === arg.name); | 
					
						
							| 
									
										
										
										
											2021-02-12 15:43:59 +08:00
										 |  |  |       if (existingArg && isTypeOverride(existingArg, arg)) { | 
					
						
							| 
									
										
										
										
											2021-02-02 03:43:26 +08:00
										 |  |  |         if (!arg.langs || !arg.langs.only) | 
					
						
							|  |  |  |           throw new Error('Override does not have lang: ' + spec.text); | 
					
						
							| 
									
										
										
										
											2021-01-29 09:51:41 +08:00
										 |  |  |         for (const lang of arg.langs.only) { | 
					
						
							|  |  |  |           existingArg.langs.overrides = existingArg.langs.overrides || {}; | 
					
						
							|  |  |  |           existingArg.langs.overrides[lang] = arg; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         method.argsArray.push(arg); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2022-04-15 04:22:42 +08:00
										 |  |  |       // match[1] === 'option'
 | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |       let options = method.argsArray.find(o => o.name === 'options'); | 
					
						
							|  |  |  |       if (!options) { | 
					
						
							|  |  |  |         const type = new Documentation.Type('Object', []); | 
					
						
							| 
									
										
										
										
											2022-07-06 08:24:50 +08:00
										 |  |  |         options = Documentation.Member.createProperty({ langs: {}, experimental: false, since: 'v1.0' }, 'options', type, undefined, false); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |         method.argsArray.push(options); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       const p = this.parseProperty(spec); | 
					
						
							|  |  |  |       p.required = false; | 
					
						
							|  |  |  |       options.type.properties.push(p); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * @param {MarkdownNode} spec | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   parseProperty(spec) { | 
					
						
							| 
									
										
										
										
											2021-01-08 08:12:25 +08:00
										 |  |  |     const param = childrenWithoutProperties(spec)[0]; | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     const text = param.text; | 
					
						
							| 
									
										
										
										
											2022-04-07 10:02:10 +08:00
										 |  |  |     let typeStart = text.indexOf('<'); | 
					
						
							| 
									
										
										
										
											2022-04-15 04:22:42 +08:00
										 |  |  |     while ('?e'.includes(text[typeStart - 1])) | 
					
						
							| 
									
										
										
										
											2022-04-07 10:02:10 +08:00
										 |  |  |       typeStart--; | 
					
						
							|  |  |  |     const name = text.substring(0, typeStart).replace(/\`/g, '').trim(); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     const comments = extractComments(spec); | 
					
						
							| 
									
										
										
										
											2022-04-07 10:02:10 +08:00
										 |  |  |     const { type, optional } = this.parseType(param); | 
					
						
							| 
									
										
										
										
											2022-07-06 08:24:50 +08:00
										 |  |  |     return Documentation.Member.createProperty(extractMetainfo(spec), name, type, comments, !optional); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * @param {MarkdownNode=} spec | 
					
						
							| 
									
										
										
										
											2022-04-15 04:22:42 +08:00
										 |  |  |    * @return {{ type: Documentation.Type, optional: boolean, experimental: boolean }} | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |    */ | 
					
						
							|  |  |  |   parseType(spec) { | 
					
						
							| 
									
										
										
										
											2021-01-08 08:12:25 +08:00
										 |  |  |     const arg = parseVariable(spec.text); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     const properties = []; | 
					
						
							|  |  |  |     for (const child of spec.children || []) { | 
					
						
							| 
									
										
										
										
											2021-01-08 08:12:25 +08:00
										 |  |  |       const { name, text } = parseVariable(child.text); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |       const comments = /** @type {MarkdownNode[]} */ ([{ type: 'text', text }]); | 
					
						
							| 
									
										
										
										
											2022-04-07 10:02:10 +08:00
										 |  |  |       const childType = this.parseType(child); | 
					
						
							| 
									
										
										
										
											2022-07-06 08:24:50 +08:00
										 |  |  |       properties.push(Documentation.Member.createProperty({ langs: {}, experimental: childType.experimental, since: 'v1.0' }, name, childType.type, comments, !childType.optional)); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-04-07 10:02:10 +08:00
										 |  |  |     const type = Documentation.Type.parse(arg.type, properties); | 
					
						
							| 
									
										
										
										
											2022-04-15 04:22:42 +08:00
										 |  |  |     return { type, optional: arg.optional, experimental: arg.experimental }; | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2021-02-26 14:22:47 +08:00
										 |  |  |  * @param {string} line | 
					
						
							| 
									
										
										
										
											2022-04-15 04:22:42 +08:00
										 |  |  |  * @returns {{ name: string, type: string, text: string, optional: boolean, experimental: boolean }} | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2021-01-08 08:12:25 +08:00
										 |  |  | function parseVariable(line) { | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |   let match = line.match(/^`([^`]+)` (.*)/); | 
					
						
							|  |  |  |   if (!match) | 
					
						
							|  |  |  |     match = line.match(/^(returns): (.*)/); | 
					
						
							|  |  |  |   if (!match) | 
					
						
							|  |  |  |     match = line.match(/^(type): (.*)/); | 
					
						
							| 
									
										
										
										
											2021-02-26 14:22:47 +08:00
										 |  |  |   if (!match) | 
					
						
							|  |  |  |     match = line.match(/^(argument): (.*)/); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |   if (!match) | 
					
						
							|  |  |  |     throw new Error('Invalid argument: ' + line); | 
					
						
							|  |  |  |   const name = match[1]; | 
					
						
							| 
									
										
										
										
											2022-04-07 10:02:10 +08:00
										 |  |  |   let remainder = match[2]; | 
					
						
							|  |  |  |   let optional = false; | 
					
						
							| 
									
										
										
										
											2022-04-15 04:22:42 +08:00
										 |  |  |   let experimental = false; | 
					
						
							|  |  |  |   while ('?e'.includes(remainder[0])) { | 
					
						
							|  |  |  |     if (remainder[0] === '?') | 
					
						
							|  |  |  |       optional = true; | 
					
						
							|  |  |  |     else if (remainder[0] === 'e') | 
					
						
							|  |  |  |       experimental = true; | 
					
						
							| 
									
										
										
										
											2022-04-07 10:02:10 +08:00
										 |  |  |     remainder = remainder.substring(1); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |   if (!remainder.startsWith('<')) | 
					
						
							| 
									
										
										
										
											2021-07-23 02:01:18 +08:00
										 |  |  |     throw new Error(`Bad argument: "${name}" in "${line}"`); | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |   let depth = 0; | 
					
						
							|  |  |  |   for (let i = 0; i < remainder.length; ++i) { | 
					
						
							|  |  |  |     const c = remainder.charAt(i); | 
					
						
							|  |  |  |     if (c === '<') | 
					
						
							|  |  |  |       ++depth; | 
					
						
							|  |  |  |     if (c === '>') | 
					
						
							|  |  |  |       --depth; | 
					
						
							|  |  |  |     if (depth === 0) | 
					
						
							| 
									
										
										
										
											2022-04-15 04:22:42 +08:00
										 |  |  |       return { name, type: remainder.substring(1, i), text: remainder.substring(i + 2), optional, experimental }; | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |   } | 
					
						
							|  |  |  |   throw new Error('Should not be reached'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @param {MarkdownNode[]} body | 
					
						
							|  |  |  |  * @param {MarkdownNode[]} params | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function applyTemplates(body, params) { | 
					
						
							|  |  |  |   const paramsMap = new Map(); | 
					
						
							|  |  |  |   for (const node of params) | 
					
						
							|  |  |  |     paramsMap.set('%%-' + node.text + '-%%', node); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const visit = (node, parent) => { | 
					
						
							|  |  |  |     if (node.text && node.text.includes('-inline- = %%')) { | 
					
						
							|  |  |  |       const [name, key] = node.text.split('-inline- = '); | 
					
						
							|  |  |  |       const list = paramsMap.get(key); | 
					
						
							| 
									
										
										
										
											2021-04-30 12:16:09 +08:00
										 |  |  |       const newChildren = []; | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |       if (!list) | 
					
						
							|  |  |  |         throw new Error('Bad template: ' + key); | 
					
						
							|  |  |  |       for (const prop of list.children) { | 
					
						
							|  |  |  |         const template = paramsMap.get(prop.text); | 
					
						
							|  |  |  |         if (!template) | 
					
						
							|  |  |  |           throw new Error('Bad template: ' + prop.text); | 
					
						
							| 
									
										
										
										
											2021-01-08 08:12:25 +08:00
										 |  |  |         const children = childrenWithoutProperties(template); | 
					
						
							|  |  |  |         const { name: argName } = parseVariable(children[0].text); | 
					
						
							| 
									
										
										
										
											2021-04-30 12:16:09 +08:00
										 |  |  |         newChildren.push({ | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |           type: node.type, | 
					
						
							|  |  |  |           text: name + argName, | 
					
						
							| 
									
										
										
										
											2022-07-06 08:24:50 +08:00
										 |  |  |           children: [...node.children, ...template.children.map(c => md.clone(c))] | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2021-04-30 12:16:09 +08:00
										 |  |  |       const nodeIndex = parent.children.indexOf(node); | 
					
						
							|  |  |  |       parent.children = [...parent.children.slice(0, nodeIndex), ...newChildren, ...parent.children.slice(nodeIndex + 1)]; | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     } else if (node.text && node.text.includes(' = %%')) { | 
					
						
							|  |  |  |       const [name, key] = node.text.split(' = '); | 
					
						
							|  |  |  |       node.text = name; | 
					
						
							|  |  |  |       const template = paramsMap.get(key); | 
					
						
							|  |  |  |       if (!template) | 
					
						
							|  |  |  |         throw new Error('Bad template: ' + key); | 
					
						
							|  |  |  |       node.children.push(...template.children.map(c => md.clone(c))); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     for (const child of node.children || []) | 
					
						
							|  |  |  |       visit(child, node); | 
					
						
							|  |  |  |     if (node.children) | 
					
						
							|  |  |  |       node.children = node.children.filter(child => !child.text || !child.text.includes('-inline- = %%')); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (const node of body) | 
					
						
							|  |  |  |     visit(node, null); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return body; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @param {MarkdownNode} item | 
					
						
							|  |  |  |  * @returns {MarkdownNode[]} | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function extractComments(item) { | 
					
						
							| 
									
										
										
										
											2022-04-14 07:13:30 +08:00
										 |  |  |   return childrenWithoutProperties(item).filter(c => { | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |     if (c.type.startsWith('h')) | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     if (c.type === 'li' && c.liType === 'default') | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @param {string} apiDir | 
					
						
							| 
									
										
										
										
											2021-07-23 05:47:12 +08:00
										 |  |  |  * @param {string=} paramsPath | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2021-07-23 05:47:12 +08:00
										 |  |  | function parseApi(apiDir, paramsPath) { | 
					
						
							|  |  |  |   return new ApiParser(apiDir, paramsPath).documentation; | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-06 08:24:50 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {MarkdownNode} spec | 
					
						
							|  |  |  |  * @returns {import('./documentation').Metainfo} | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function extractMetainfo(spec) { | 
					
						
							|  |  |  |   return { | 
					
						
							|  |  |  |     langs: extractLangs(spec), | 
					
						
							|  |  |  |     since: extractSince(spec), | 
					
						
							|  |  |  |     experimental: extractExperimental(spec) | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-08 08:12:25 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {MarkdownNode} spec | 
					
						
							| 
									
										
										
										
											2021-01-09 07:00:14 +08:00
										 |  |  |  * @returns {import('./documentation').Langs} | 
					
						
							| 
									
										
										
										
											2021-01-08 08:12:25 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | function extractLangs(spec) { | 
					
						
							| 
									
										
										
										
											2021-01-09 07:00:14 +08:00
										 |  |  |   for (const child of spec.children) { | 
					
						
							|  |  |  |     if (child.type !== 'li' || child.liType !== 'bullet' || !child.text.startsWith('langs:')) | 
					
						
							|  |  |  |       continue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const only = child.text.substring('langs:'.length).trim(); | 
					
						
							|  |  |  |     /** @type {Object<string, string>} */ | 
					
						
							|  |  |  |     const aliases = {}; | 
					
						
							|  |  |  |     for (const p of child.children || []) { | 
					
						
							|  |  |  |       const match = p.text.match(/alias-(\w+)[\s]*:(.*)/); | 
					
						
							|  |  |  |       if (match) | 
					
						
							|  |  |  |         aliases[match[1].trim()] = match[2].trim(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return { | 
					
						
							| 
									
										
										
										
											2021-02-02 03:13:13 +08:00
										 |  |  |       only: only ? only.split(',').map(l => l.trim()) : undefined, | 
					
						
							| 
									
										
										
										
											2021-01-16 08:01:41 +08:00
										 |  |  |       aliases, | 
					
						
							| 
									
										
										
										
											2021-01-29 09:51:41 +08:00
										 |  |  |       types: {}, | 
					
						
							|  |  |  |       overrides: {} | 
					
						
							| 
									
										
										
										
											2021-01-09 07:00:14 +08:00
										 |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return {}; | 
					
						
							| 
									
										
										
										
											2021-01-08 08:12:25 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-06 08:24:50 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {MarkdownNode} spec | 
					
						
							|  |  |  |  * @returns {string} | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function extractSince(spec) { | 
					
						
							|  |  |  |   for (const child of spec.children) { | 
					
						
							|  |  |  |     if (child.type !== 'li' || child.liType !== 'bullet' || !child.text.startsWith('since:')) | 
					
						
							|  |  |  |       continue; | 
					
						
							|  |  |  |     return child.text.substring(child.text.indexOf(':') + 1).trim(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   console.error('Missing since: v1.** declaration in node:'); | 
					
						
							|  |  |  |   console.error(spec); | 
					
						
							|  |  |  |   process.exit(1); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 07:13:30 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {MarkdownNode} spec | 
					
						
							|  |  |  |  * @returns {boolean} | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  |  function extractExperimental(spec) { | 
					
						
							|  |  |  |   for (const child of spec.children) { | 
					
						
							|  |  |  |     if (child.type === 'li' && child.liType === 'bullet' && child.text === 'experimental') | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-08 08:12:25 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {MarkdownNode} spec | 
					
						
							|  |  |  |  * @returns {MarkdownNode[]} | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function childrenWithoutProperties(spec) { | 
					
						
							| 
									
										
										
										
											2022-04-14 07:13:30 +08:00
										 |  |  |   return (spec.children || []).filter(c => { | 
					
						
							| 
									
										
										
										
											2022-07-06 08:24:50 +08:00
										 |  |  |     const isProperty = c.liType === 'bullet' && (c.text.startsWith('langs:') || c.text.startsWith('since:') || c.text === 'experimental'); | 
					
						
							| 
									
										
										
										
											2022-04-14 07:13:30 +08:00
										 |  |  |     return !isProperty; | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2021-01-08 08:12:25 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-12 15:43:59 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {Documentation.Member} existingMember | 
					
						
							|  |  |  |  * @param {Documentation.Member} member | 
					
						
							|  |  |  |  * @returns {boolean} | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function isTypeOverride(existingMember, member) { | 
					
						
							|  |  |  |   if (!existingMember.langs.only) | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   if (member.langs.only.every(l => existingMember.langs.only.includes(l))) { | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } else if (member.langs.only.some(l => existingMember.langs.only.includes(l))) { | 
					
						
							|  |  |  |     throw new Error(`Ambiguous language override for: ${member.name}`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-03 04:43:16 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {MarkdownNode[]=} params | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function checkNoDuplicateParamEntries(params) { | 
					
						
							|  |  |  |   if (!params) | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   const entries = new Set(); | 
					
						
							|  |  |  |   for (const node of params) { | 
					
						
							|  |  |  |     if (entries.has(node.text)) | 
					
						
							|  |  |  |       throw new Error('Duplicate param entry, for language-specific params use prefix (e.g. js-...): ' + node.text); | 
					
						
							|  |  |  |     entries.add(node.text); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-08 07:00:04 +08:00
										 |  |  | module.exports = { parseApi }; |