chore: move doc-specific code into documentation (#18933)

This commit is contained in:
Pavel Feldman 2022-11-19 11:26:11 -08:00 committed by GitHub
parent 941090f0c4
commit 03d2b2ecbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 198 additions and 194 deletions

View File

@ -19,7 +19,7 @@
const fs = require('fs');
const path = require('path');
const md = require('../markdown');
const Documentation = require('./documentation');
const docs = require('./documentation');
/** @typedef {import('../markdown').MarkdownNode} MarkdownNode */
/** @typedef {import('../markdown').MarkdownHeaderNode} MarkdownHeaderNode */
@ -45,7 +45,7 @@ class ApiParser {
const params = paramsPath ? md.parse(fs.readFileSync(paramsPath).toString()) : undefined;
checkNoDuplicateParamEntries(params);
const api = params ? applyTemplates(body, params) : body;
/** @type {Map<string, Documentation.Class>} */
/** @type {Map<string, documentation.Class>} */
this.classes = new Map();
md.visitAll(api, node => {
if (node.type === 'h1')
@ -59,7 +59,7 @@ class ApiParser {
if (node.type === 'h3')
this.parseArgument(node);
});
this.documentation = new Documentation([...this.classes.values()]);
this.documentation = new docs.Documentation([...this.classes.values()]);
this.documentation.index();
}
@ -77,7 +77,7 @@ class ApiParser {
continue;
}
}
const clazz = new Documentation.Class(extractMetainfo(node), name, [], extendsName, extractComments(node));
const clazz = new docs.Class(extractMetainfo(node), name, [], extendsName, extractComments(node));
this.classes.set(clazz.name, clazz);
}
@ -100,16 +100,16 @@ class ApiParser {
}
}
if (!returnType)
returnType = new Documentation.Type('void');
returnType = new docs.Type('void');
const comments = extractComments(spec);
let member;
if (match[1] === 'event')
member = Documentation.Member.createEvent(extractMetainfo(spec), name, returnType, comments);
member = docs.Member.createEvent(extractMetainfo(spec), name, returnType, comments);
if (match[1] === 'property')
member = Documentation.Member.createProperty(extractMetainfo(spec), name, returnType, comments, !optional);
member = docs.Member.createProperty(extractMetainfo(spec), name, returnType, comments, !optional);
if (['method', 'async method', 'optional method', 'optional async method'].includes(match[1])) {
member = Documentation.Member.createMethod(extractMetainfo(spec), name, [], returnType, comments);
member = docs.Member.createMethod(extractMetainfo(spec), name, [], returnType, comments);
if (match[1].includes('async'))
member.async = true;
if (match[1].includes('optional'))
@ -118,7 +118,7 @@ class ApiParser {
if (!member)
throw new Error('Unknown member: ' + spec.text);
const clazz = /** @type {Documentation.Class} */(this.classes.get(match[2]));
const clazz = /** @type {documentation.Class} */(this.classes.get(match[2]));
const existingMember = clazz.membersArray.find(m => m.name === name && m.kind === member.kind);
if (existingMember && isTypeOverride(existingMember, member)) {
for (const lang of member?.langs?.only || []) {
@ -173,8 +173,8 @@ class ApiParser {
// match[1] === 'option'
let options = method.argsArray.find(o => o.name === 'options');
if (!options) {
const type = new Documentation.Type('Object', []);
options = Documentation.Member.createProperty({ langs: {}, experimental: false, since: 'v1.0' }, 'options', type, undefined, false);
const type = new docs.Type('Object', []);
options = docs.Member.createProperty({ langs: {}, experimental: false, since: 'v1.0' }, 'options', type, undefined, false);
method.argsArray.push(options);
}
const p = this.parseProperty(spec);
@ -196,12 +196,12 @@ class ApiParser {
const name = text.substring(0, typeStart).replace(/\`/g, '').trim();
const comments = extractComments(spec);
const { type, optional } = this.parseType(/** @type {MarkdownLiNode} */(param));
return Documentation.Member.createProperty(extractMetainfo(spec), name, type, comments, !optional);
return docs.Member.createProperty(extractMetainfo(spec), name, type, comments, !optional);
}
/**
* @param {MarkdownLiNode} spec
* @return {{ type: Documentation.Type, optional: boolean, experimental: boolean }}
* @return {{ type: documentation.Type, optional: boolean, experimental: boolean }}
*/
parseType(spec) {
const arg = parseVariable(spec.text);
@ -210,9 +210,9 @@ class ApiParser {
const { name, text } = parseVariable(/** @type {string} */(child.text));
const comments = /** @type {MarkdownNode[]} */ ([{ type: 'text', text }]);
const childType = this.parseType(child);
properties.push(Documentation.Member.createProperty({ langs: {}, experimental: childType.experimental, since: 'v1.0' }, name, childType.type, comments, !childType.optional));
properties.push(docs.Member.createProperty({ langs: {}, experimental: childType.experimental, since: 'v1.0' }, name, childType.type, comments, !childType.optional));
}
const type = Documentation.Type.parse(arg.type, properties);
const type = docs.Type.parse(arg.type, properties);
return { type, optional: arg.optional, experimental: arg.experimental };
}
}
@ -415,8 +415,8 @@ function childrenWithoutProperties(spec) {
}
/**
* @param {Documentation.Member} existingMember
* @param {Documentation.Member} member
* @param {documentation.Member} existingMember
* @param {documentation.Member} member
* @returns {boolean}
*/
function isTypeOverride(existingMember, member) {

View File

@ -23,6 +23,7 @@ const path = require('path');
const { parseApi } = require('./api_parser');
const missingDocs = require('./missingDocs');
const md = require('../markdown');
const docs = require('./documentation');
/** @typedef {import('./documentation').Type} Type */
/** @typedef {import('../markdown').MarkdownNode} MarkdownNode */
@ -189,7 +190,7 @@ async function run() {
const data = fs.readFileSync(filePath, 'utf-8');
let rootNode = md.filterNodesForLanguage(md.parse(data), lang);
// Validates code snippet groups.
rootNode = md.processCodeGroups(rootNode, lang, tabs => tabs.map(tab => tab.spec));
rootNode = docs.processCodeGroups(rootNode, lang, tabs => tabs.map(tab => tab.spec));
// Renders links.
documentation.renderLinksInText(rootNode);
// Validate links.

View File

@ -36,15 +36,15 @@ const md = require('../markdown');
* @typedef {{
* only?: string[],
* aliases?: Object<string, string>,
* types?: Object<string, Documentation.Type>,
* overrides?: Object<string, Documentation.Member>,
* types?: Object<string, Type>,
* overrides?: Object<string, Member>,
* }} Langs
*/
/**
* @typedef {function({
* clazz?: Documentation.Class,
* member?: Documentation.Member,
* clazz?: Class,
* member?: Member,
* param?: string,
* option?: string,
* href?: string,
@ -65,13 +65,19 @@ const md = require('../markdown');
* }} LanguageOptions
*/
/** @typedef {{
* value: string, groupId: string, spec: MarkdownNode
* }} CodeGroup */
/** @typedef {function(CodeGroup[]): MarkdownNode[]} CodeGroupTransformer */
class Documentation {
/**
* @param {!Array<!Documentation.Class>} classesArray
* @param {!Array<!Class>} classesArray
*/
constructor(classesArray) {
this.classesArray = classesArray;
/** @type {!Map<string, !Documentation.Class>} */
/** @type {!Map<string, !Class>} */
this.classes = new Map();
this.index();
}
@ -147,7 +153,7 @@ class Documentation {
* @param {Renderer} linkRenderer
*/
setLinkRenderer(linkRenderer) {
// @type {Map<string, Documentation.Class>}
// @type {Map<string, Class>}
const classesMap = new Map();
const membersMap = new Map();
for (const clazz of this.classesArray) {
@ -156,7 +162,7 @@ class Documentation {
membersMap.set(`${member.kind}: ${clazz.name}.${member.name}`, member);
}
/**
* @param {Documentation.Class|Documentation.Member|null} classOrMember
* @param {Class|Member|null} classOrMember
* @param {MarkdownNode[] | undefined} nodes
*/
this._patchLinks = (classOrMember, nodes) => patchLinks(classOrMember, nodes, classesMap, membersMap, linkRenderer);
@ -174,7 +180,7 @@ class Documentation {
/**
* @param {string} lang
* @param {import('../markdown').CodeGroupTransformer} transformer
* @param {CodeGroupTransformer} transformer
*/
setCodeGroupsTransformer(lang, transformer) {
this._codeGroupsTransformer = { lang, transformer };
@ -185,7 +191,7 @@ class Documentation {
clazz.visit(item => {
let spec = item.spec;
if (spec && this._codeGroupsTransformer)
spec = md.processCodeGroups(spec, this._codeGroupsTransformer.lang, this._codeGroupsTransformer.transformer);
spec = processCodeGroups(spec, this._codeGroupsTransformer.lang, this._codeGroupsTransformer.transformer);
item.comment = generateSourceCodeComment(spec);
});
}
@ -196,11 +202,11 @@ class Documentation {
}
}
Documentation.Class = class {
class Class {
/**
* @param {Metainfo} metainfo
* @param {string} name
* @param {!Array<!Documentation.Member>} membersArray
* @param {!Array<!Member>} membersArray
* @param {?string=} extendsName
* @param {MarkdownNode[]=} spec
*/
@ -216,19 +222,19 @@ Documentation.Class = class {
this.index();
const match = /** @type {string[]} */(name.match(/(API|JS|CDP|[A-Z])(.*)/));
this.varName = match[1].toLowerCase() + match[2];
/** @type {!Map<string, !Documentation.Member>} */
/** @type {!Map<string, !Member>} */
this.members = new Map();
/** @type {!Map<string, !Documentation.Member>} */
/** @type {!Map<string, !Member>} */
this.properties = new Map();
/** @type {!Array<!Documentation.Member>} */
/** @type {!Array<!Member>} */
this.propertiesArray = [];
/** @type {!Map<string, !Documentation.Member>} */
/** @type {!Map<string, !Member>} */
this.methods = new Map();
/** @type {!Array<!Documentation.Member>} */
/** @type {!Array<!Member>} */
this.methodsArray = [];
/** @type {!Map<string, !Documentation.Member>} */
/** @type {!Map<string, !Member>} */
this.events = new Map();
/** @type {!Array<!Documentation.Member>} */
/** @type {!Array<!Member>} */
this.eventsArray = [];
}
@ -259,7 +265,7 @@ Documentation.Class = class {
}
clone() {
const cls = new Documentation.Class({ langs: this.langs, experimental: this.experimental, since: this.since }, this.name, this.membersArray.map(m => m.clone()), this.extends, this.spec);
const cls = new Class({ langs: this.langs, experimental: this.experimental, since: this.since }, this.name, this.membersArray.map(m => m.clone()), this.extends, this.spec);
cls.comment = this.comment;
return cls;
}
@ -335,7 +341,7 @@ Documentation.Class = class {
}
/**
* @param {function(Documentation.Member|Documentation.Class): void} visitor
* @param {function(Member|Class): void} visitor
*/
visit(visitor) {
visitor(this);
@ -348,13 +354,13 @@ Documentation.Class = class {
}
};
Documentation.Member = class {
class Member {
/**
* @param {string} kind
* @param {Metainfo} metainfo
* @param {string} name
* @param {?Documentation.Type} type
* @param {!Array<!Documentation.Member>} argsArray
* @param {?Type} type
* @param {!Array<!Member>} argsArray
* @param {MarkdownNode[]=} spec
* @param {boolean=} required
*/
@ -369,12 +375,12 @@ Documentation.Member = class {
this.argsArray = argsArray;
this.required = required;
this.comment = '';
/** @type {!Map<string, !Documentation.Member>} */
/** @type {!Map<string, !Member>} */
this.args = new Map();
this.index();
/** @type {!Documentation.Class | null} */
/** @type {!Class | null} */
this.clazz = null;
/** @type {Documentation.Member=} */
/** @type {Member=} */
this.enclosingMethod = undefined;
this.deprecated = false;
if (spec) {
@ -466,7 +472,7 @@ Documentation.Member = class {
}
clone() {
const result = new Documentation.Member(this.kind, { langs: this.langs, experimental: this.experimental, since: this.since }, this.name, this.type?.clone(), this.argsArray.map(arg => arg.clone()), this.spec, this.required);
const result = new Member(this.kind, { langs: this.langs, experimental: this.experimental, since: this.since }, this.name, this.type?.clone(), this.argsArray.map(arg => arg.clone()), this.spec, this.required);
result.alias = this.alias;
result.async = this.async;
result.paramOrOption = this.paramOrOption;
@ -476,40 +482,40 @@ Documentation.Member = class {
/**
* @param {Metainfo} metainfo
* @param {string} name
* @param {!Array<!Documentation.Member>} argsArray
* @param {?Documentation.Type} returnType
* @param {!Array<!Member>} argsArray
* @param {?Type} returnType
* @param {MarkdownNode[]=} spec
* @return {!Documentation.Member}
* @return {!Member}
*/
static createMethod(metainfo, name, argsArray, returnType, spec) {
return new Documentation.Member('method', metainfo, name, returnType, argsArray, spec);
return new Member('method', metainfo, name, returnType, argsArray, spec);
}
/**
* @param {Metainfo} metainfo
* @param {!string} name
* @param {!Documentation.Type} type
* @param {!Type} type
* @param {!MarkdownNode[]=} spec
* @param {boolean=} required
* @return {!Documentation.Member}
* @return {!Member}
*/
static createProperty(metainfo, name, type, spec, required) {
return new Documentation.Member('property', metainfo, name, type, [], spec, required);
return new Member('property', metainfo, name, type, [], spec, required);
}
/**
* @param {Metainfo} metainfo
* @param {string} name
* @param {?Documentation.Type=} type
* @param {?Type=} type
* @param {MarkdownNode[]=} spec
* @return {!Documentation.Member}
* @return {!Member}
*/
static createEvent(metainfo, name, type = null, spec) {
return new Documentation.Member('event', metainfo, name, type, [], spec);
return new Member('event', metainfo, name, type, [], spec);
}
/**
* @param {function(Documentation.Member|Documentation.Class): void} visitor
* @param {function(Member|Class): void} visitor
*/
visit(visitor) {
visitor(this);
@ -520,15 +526,15 @@ Documentation.Member = class {
}
};
Documentation.Type = class {
class Type {
/**
* @param {string} expression
* @param {!Array<!Documentation.Member>=} properties
* @return {Documentation.Type}
* @param {!Array<!Member>=} properties
* @return {Type}
*/
static parse(expression, properties = []) {
expression = expression.replace(/\\\(/g, '(').replace(/\\\)/g, ')');
const type = Documentation.Type.fromParsedType(parseTypeExpression(expression));
const type = Type.fromParsedType(parseTypeExpression(expression));
type.expression = expression;
if (type.name === 'number')
throw new Error('Number types should be either int or float, not number in: ' + expression);
@ -550,7 +556,7 @@ Documentation.Type = class {
/**
* @param {ParsedType} parsedType
* @return {Documentation.Type}
* @return {Type}
*/
static fromParsedType(parsedType, inUnion = false) {
if (!inUnion && !parsedType.unionName && isStringUnion(parsedType) ) {
@ -558,12 +564,12 @@ Documentation.Type = class {
}
if (!inUnion && (parsedType.union || parsedType.unionName)) {
const type = new Documentation.Type(parsedType.unionName || '');
const type = new Type(parsedType.unionName || '');
type.union = [];
// @ts-ignore
for (let t = parsedType; t; t = t.union) {
const nestedUnion = !!t.unionName && t !== parsedType;
type.union.push(Documentation.Type.fromParsedType(t, !nestedUnion));
type.union.push(Type.fromParsedType(t, !nestedUnion));
if (nestedUnion)
break;
}
@ -571,41 +577,41 @@ Documentation.Type = class {
}
if (parsedType.args) {
const type = new Documentation.Type('function');
const type = new Type('function');
type.args = [];
// @ts-ignore
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) : undefined;
type.args.push(Type.fromParsedType(t));
type.returnType = parsedType.retType ? Type.fromParsedType(parsedType.retType) : undefined;
return type;
}
if (parsedType.template) {
const type = new Documentation.Type(parsedType.name);
const type = new Type(parsedType.name);
type.templates = [];
// @ts-ignore
for (let t = parsedType.template; t; t = t.next)
type.templates.push(Documentation.Type.fromParsedType(t));
type.templates.push(Type.fromParsedType(t));
return type;
}
return new Documentation.Type(parsedType.name);
return new Type(parsedType.name);
}
/**
* @param {string} name
* @param {!Array<!Documentation.Member>=} properties
* @param {!Array<!Member>=} properties
*/
constructor(name, properties) {
this.name = name.replace(/^\[/, '').replace(/\]$/, '');
/** @type {Documentation.Member[] | undefined} */
/** @type {Member[] | undefined} */
this.properties = this.name === 'Object' ? properties : undefined;
/** @type {Documentation.Type[] | undefined} */
/** @type {Type[] | undefined} */
this.union;
/** @type {Documentation.Type[] | undefined} */
/** @type {Type[] | undefined} */
this.args;
/** @type {Documentation.Type | undefined} */
/** @type {Type | undefined} */
this.returnType;
/** @type {Documentation.Type[] | undefined} */
/** @type {Type[] | undefined} */
this.templates;
/** @type {string | undefined} */
this.expression;
@ -621,7 +627,7 @@ Documentation.Type = class {
}
clone() {
const type = new Documentation.Type(this.name, this.properties ? this.properties.map(prop => prop.clone()) : undefined);
const type = new Type(this.name, this.properties ? this.properties.map(prop => prop.clone()) : undefined);
if (this.union)
type.union = this.union.map(type => type.clone());
if (this.args)
@ -635,7 +641,7 @@ Documentation.Type = class {
}
/**
* @returns {Documentation.Member[]}
* @returns {Member[]}
*/
deepProperties() {
const types = [];
@ -648,7 +654,7 @@ Documentation.Type = class {
}
/**
* @returns {Documentation.Member[] | undefined}
* @returns {Member[] | undefined}
*/
sortedProperties() {
if (!this.properties)
@ -689,7 +695,7 @@ Documentation.Type = class {
}
/**
* @param {Documentation.Type[]} result
* @param {Type[]} result
*/
_collectAllTypes(result) {
result.push(this);
@ -795,10 +801,10 @@ function matchingBracket(str, open, close) {
}
/**
* @param {Documentation.Class|Documentation.Member|null} classOrMember
* @param {Class|Member|null} classOrMember
* @param {MarkdownNode[]|undefined} spec
* @param {Map<string, Documentation.Class>} classesMap
* @param {Map<string, Documentation.Member>} membersMap
* @param {Map<string, Class>} classesMap
* @param {Map<string, Member>} membersMap
* @param {Renderer} linkRenderer
*/
function patchLinks(classOrMember, spec, classesMap, membersMap, linkRenderer) {
@ -849,6 +855,8 @@ function generateSourceCodeComment(spec) {
md.visitAll(comments, node => {
if (node.type === 'li' && node.liType === 'bullet')
node.liType = 'default';
if (node.type === 'code' && node.codeLang)
node.codeLang = parseCodeLang(node.codeLang).highlighter;
if (node.type === 'note') {
// @ts-ignore
node.type = 'text';
@ -859,7 +867,7 @@ function generateSourceCodeComment(spec) {
}
/**
* @param {Documentation.Member} optionsArg
* @param {Member} optionsArg
* @param {LanguageOptions=} options
*/
function patchCSharpOptionOverloads(optionsArg, options = {}) {
@ -927,4 +935,88 @@ function csharpOptionOverloadSuffix(option, type) {
throw new Error(`CSharp option "${option}" has unsupported type overload "${type}"`);
}
module.exports = Documentation;
/**
* @param {MarkdownNode[]} spec
* @param {string} language
* @param {CodeGroupTransformer} transformer
* @returns {MarkdownNode[]}
*/
function processCodeGroups(spec, language, transformer) {
/** @type {MarkdownNode[]} */
const newSpec = [];
for (let i = 0; i < spec.length; ++i) {
/** @type {{value: string, groupId: string, spec: MarkdownNode}[]} */
const tabs = [];
for (;i < spec.length; i++) {
const codeLang = spec[i].codeLang;
if (!codeLang)
break;
let parsed;
try {
parsed = parseCodeLang(codeLang);
} catch (e) {
throw new Error(e.message + '\n while processing:\n' + md.render([spec[i]]));
}
if (!parsed.codeGroup)
break;
if (parsed.language && parsed.language !== language)
continue;
const [groupId, value] = parsed.codeGroup.split('-');
const clone = md.clone(spec[i]);
clone.codeLang = parsed.highlighter;
tabs.push({ groupId, value, spec: clone });
}
if (tabs.length) {
if (tabs.length === 1)
throw new Error(`Lonely tab "${tabs[0].spec.codeLang}". Make sure there are at least two tabs in the group.\n` + md.render([tabs[0].spec]));
// Validate group consistency.
const groupId = tabs[0].groupId;
const values = new Set();
for (const tab of tabs) {
if (tab.groupId !== groupId)
throw new Error('Mixed group ids: ' + md.render(spec));
if (values.has(tab.value))
throw new Error(`Duplicated tab "${tab.value}"\n` + md.render(tabs.map(tab => tab.spec)));
values.add(tab.value);
}
// Append transformed nodes.
newSpec.push(...transformer(tabs));
}
if (i < spec.length)
newSpec.push(spec[i]);
}
return newSpec;
}
/**
* @param {string} codeLang
* @return {{ highlighter: string, language: string|undefined, codeGroup: string|undefined}}
*/
function parseCodeLang(codeLang) {
if (codeLang === 'python async')
return { highlighter: 'py', codeGroup: 'python-async', language: 'python' };
if (codeLang === 'python sync')
return { highlighter: 'py', codeGroup: 'python-sync', language: 'python' };
const [highlighter] = codeLang.split(' ');
if (!highlighter)
throw new Error(`Cannot parse code block lang: "${codeLang}"`);
const languageMatch = codeLang.match(/ lang=([\w\d]+)/);
let language = languageMatch ? languageMatch[1] : undefined;
if (!language) {
if (highlighter === 'ts')
language = 'js';
else if (highlighter === 'py')
language = 'python';
else if (['js', 'python', 'csharp', 'java'].includes(highlighter))
language = highlighter;
}
const tabMatch = codeLang.match(/ tab=([\w\d-]+)/);
return { highlighter, language, codeGroup: tabMatch ? tabMatch[1] : '' };
}
module.exports = { Documentation, Class, Member, Type, processCodeGroups, parseCodeLang };

View File

@ -18,7 +18,7 @@
const path = require('path');
const toKebabCase = require('lodash/kebabCase')
const devices = require('../../packages/playwright-core/lib/server/deviceDescriptors');
const Documentation = require('../doclint/documentation');
const docs = require('../doclint/documentation');
const PROJECT_DIR = path.join(__dirname, '..', '..');
const fs = require('fs');
const { parseOverrides } = require('./parseOverrides');
@ -30,7 +30,7 @@ Error.stackTraceLimit = 50;
class TypesGenerator {
/**
* @param {{
* documentation: Documentation,
* documentation: docs.Documentation,
* overridesToDocsClassMapping?: Map<string, string>,
* ignoreMissing?: Set<string>,
* doNotExportClassNames?: Set<string>,
@ -39,7 +39,7 @@ class TypesGenerator {
* }} options
*/
constructor(options) {
/** @type {Array<{name: string, properties: Documentation.Member[]}>} */
/** @type {Array<{name: string, properties: docs.Member[]}>} */
this.objectDefinitions = [];
/** @type {Set<string>} */
this.handledMethods = new Set();
@ -77,6 +77,8 @@ class TypesGenerator {
return `\`${option}\``;
if (clazz)
return `[${clazz.name}]`;
if (!member || !member.clazz)
throw new Error('Internal error');
const className = member.clazz.varName === 'playwrightAssertions' ? '' : member.clazz.varName + '.';
if (member.kind === 'method')
return createMarkdownLink(member, `${className}${member.alias}(${this.renderJSSignature(member.argsArray)})`);
@ -203,7 +205,7 @@ class TypesGenerator {
}
/**
* @param {Documentation.Class} classDesc
* @param {docs.Class} classDesc
*/
classToString(classDesc) {
const parts = [];
@ -229,7 +231,7 @@ class TypesGenerator {
}
/**
* @param {Documentation.Class} classDesc
* @param {docs.Class} classDesc
*/
hasUniqueEvents(classDesc) {
if (!classDesc.events.size)
@ -241,7 +243,7 @@ class TypesGenerator {
}
/**
* @param {Documentation.Class} classDesc
* @param {docs.Class} classDesc
*/
createEventDescriptions(classDesc) {
if (!this.hasUniqueEvents(classDesc))
@ -263,7 +265,7 @@ class TypesGenerator {
}
/**
* @param {Documentation.Class} classDesc
* @param {docs.Class} classDesc
* @param {boolean=} exportMembersAsGlobals
*/
classBody(classDesc, exportMembersAsGlobals) {
@ -319,8 +321,8 @@ class TypesGenerator {
}
/**
* @param {Documentation.Class} classDesc
* @param {Documentation.Member} member
* @param {docs.Class} classDesc
* @param {docs.Member} member
*/
hasOwnMethod(classDesc, member) {
if (this.handledMethods.has(`${classDesc.name}.${member.alias}#${member.overloadIndex}`))
@ -333,7 +335,7 @@ class TypesGenerator {
}
/**
* @param {Documentation.Class} classDesc
* @param {docs.Class} classDesc
*/
parentClass(classDesc) {
if (!classDesc.extends)
@ -378,7 +380,7 @@ class TypesGenerator {
}
/**
* @param {Documentation.Type} type
* @param {docs.Type} type
*/
stringifyComplexType(type, indent, ...namespace) {
if (!type)
@ -387,7 +389,7 @@ class TypesGenerator {
}
/**
* @param {Documentation.Member[]} properties
* @param {docs.Member[]} properties
* @param {string} name
* @param {string=} indent
* @returns {string}
@ -406,7 +408,7 @@ class TypesGenerator {
}
/**
* @param {Documentation.Type=} type
* @param {docs.Type=} type
* @returns{string}
*/
stringifySimpleType(type, indent = '', ...namespace) {
@ -455,7 +457,7 @@ class TypesGenerator {
}
/**
* @param {Documentation.Member} member
* @param {docs.Member} member
*/
argsFromMember(member, indent, ...namespace) {
if (member.kind === 'property')
@ -464,7 +466,7 @@ class TypesGenerator {
}
/**
* @param {Documentation.Member} member
* @param {docs.Member} member
* @param {string} indent
*/
memberJSDOC(member, indent) {
@ -480,7 +482,7 @@ class TypesGenerator {
}
/**
* @param {Documentation.Member[]} args
* @param {docs.Member[]} args
*/
renderJSSignature(args) {
const tokens = [];

View File

@ -62,12 +62,6 @@
* lines: string[],
* }} MarkdownPropsNode */
/** @typedef {{
* value: string, groupId: string, spec: MarkdownNode
* }} CodeGroup */
/** @typedef {function(CodeGroup[]): MarkdownNode[]} CodeGroupTransformer */
/** @typedef {MarkdownTextNode | MarkdownLiNode | MarkdownCodeNode | MarkdownNoteNode | MarkdownHeaderNode | MarkdownNullNode | MarkdownPropsNode } MarkdownNode */
function flattenWrappedLines(content) {
@ -310,10 +304,7 @@ function innerRenderMdNode(indent, node, lastNode, result, maxColumns) {
if (node.type === 'code') {
newLine();
if (process.env.API_JSON_MODE)
result.push(`${indent}\`\`\`${node.codeLang}`);
else
result.push(`${indent}\`\`\`${node.codeLang ? parseCodeLang(node.codeLang).highlighter : ''}`);
result.push(`${indent}\`\`\`${node.codeLang}`);
for (const line of node.lines)
result.push(indent + line);
result.push(`${indent}\`\`\``);
@ -473,86 +464,4 @@ function filterNodesForLanguage(nodes, language) {
return result;
}
/**
* @param {string} codeLang
* @return {{ highlighter: string, language: string|undefined, codeGroup: string|undefined}}
*/
function parseCodeLang(codeLang) {
if (codeLang === 'python async')
return { highlighter: 'py', codeGroup: 'python-async', language: 'python' };
if (codeLang === 'python sync')
return { highlighter: 'py', codeGroup: 'python-sync', language: 'python' };
const [highlighter] = codeLang.split(' ');
if (!highlighter)
throw new Error(`Cannot parse code block lang: "${codeLang}"`);
const languageMatch = codeLang.match(/ lang=([\w\d]+)/);
let language = languageMatch ? languageMatch[1] : undefined;
if (!language) {
if (highlighter === 'ts')
language = 'js';
else if (highlighter === 'py')
language = 'python';
else if (['js', 'python', 'csharp', 'java'].includes(highlighter))
language = highlighter;
}
const tabMatch = codeLang.match(/ tab=([\w\d-]+)/);
return { highlighter, language, codeGroup: tabMatch ? tabMatch[1] : '' };
}
/**
* @param {MarkdownNode[]} spec
* @param {string} language
* @param {CodeGroupTransformer} transformer
* @returns {MarkdownNode[]}
*/
function processCodeGroups(spec, language, transformer) {
/** @type {MarkdownNode[]} */
const newSpec = [];
for (let i = 0; i < spec.length; ++i) {
/** @type {{value: string, groupId: string, spec: MarkdownNode}[]} */
const tabs = [];
for (;i < spec.length; i++) {
const codeLang = spec[i].codeLang;
if (!codeLang)
break;
let parsed;
try {
parsed = parseCodeLang(codeLang);
} catch (e) {
throw new Error(e.message + '\n while processing:\n' + render([spec[i]]));
}
if (!parsed.codeGroup)
break;
if (parsed.language && parsed.language !== language)
continue;
const [groupId, value] = parsed.codeGroup.split('-');
tabs.push({ groupId, value, spec: spec[i] });
}
if (tabs.length) {
if (tabs.length === 1)
throw new Error(`Lonely tab "${tabs[0].spec.codeLang}". Make sure there are at least two tabs in the group.\n` + render([tabs[0].spec]));
// Validate group consistency.
const groupId = tabs[0].groupId;
const values = new Set();
for (const tab of tabs) {
if (tab.groupId !== groupId)
throw new Error('Mixed group ids: ' + render(spec));
if (values.has(tab.value))
throw new Error(`Duplicated tab "${tab.value}"\n` + render(tabs.map(tab => tab.spec)));
values.add(tab.value);
}
// Append transformed nodes.
newSpec.push(...transformer(tabs));
}
if (i < spec.length)
newSpec.push(spec[i]);
}
return newSpec;
}
module.exports = { parse, render, clone, visitAll, visit, generateToc, filterNodesForLanguage, parseCodeLang, processCodeGroups };
module.exports = { parse, render, clone, visitAll, visit, generateToc, filterNodesForLanguage };