diff --git a/utils/doclint/api_parser.js b/utils/doclint/api_parser.js index b0b4778f38..9373ce77bc 100644 --- a/utils/doclint/api_parser.js +++ b/utils/doclint/api_parser.js @@ -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} */ + /** @type {Map} */ 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) { diff --git a/utils/doclint/cli.js b/utils/doclint/cli.js index b0c9ddba81..2e48aa095e 100755 --- a/utils/doclint/cli.js +++ b/utils/doclint/cli.js @@ -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. diff --git a/utils/doclint/documentation.js b/utils/doclint/documentation.js index fa0f867c11..742e6fcc5f 100644 --- a/utils/doclint/documentation.js +++ b/utils/doclint/documentation.js @@ -36,15 +36,15 @@ const md = require('../markdown'); * @typedef {{ * only?: string[], * aliases?: Object, - * types?: Object, - * overrides?: Object, + * types?: Object, + * overrides?: Object, * }} 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} classesArray + * @param {!Array} classesArray */ constructor(classesArray) { this.classesArray = classesArray; - /** @type {!Map} */ + /** @type {!Map} */ this.classes = new Map(); this.index(); } @@ -147,7 +153,7 @@ class Documentation { * @param {Renderer} linkRenderer */ setLinkRenderer(linkRenderer) { - // @type {Map} + // @type {Map} 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} membersArray + * @param {!Array} 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} */ + /** @type {!Map} */ this.members = new Map(); - /** @type {!Map} */ + /** @type {!Map} */ this.properties = new Map(); - /** @type {!Array} */ + /** @type {!Array} */ this.propertiesArray = []; - /** @type {!Map} */ + /** @type {!Map} */ this.methods = new Map(); - /** @type {!Array} */ + /** @type {!Array} */ this.methodsArray = []; - /** @type {!Map} */ + /** @type {!Map} */ this.events = new Map(); - /** @type {!Array} */ + /** @type {!Array} */ 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} argsArray + * @param {?Type} type + * @param {!Array} argsArray * @param {MarkdownNode[]=} spec * @param {boolean=} required */ @@ -369,12 +375,12 @@ Documentation.Member = class { this.argsArray = argsArray; this.required = required; this.comment = ''; - /** @type {!Map} */ + /** @type {!Map} */ 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} argsArray - * @param {?Documentation.Type} returnType + * @param {!Array} 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=} properties - * @return {Documentation.Type} + * @param {!Array=} 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=} properties + * @param {!Array=} 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} classesMap - * @param {Map} membersMap + * @param {Map} classesMap + * @param {Map} 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 }; diff --git a/utils/generate_types/index.js b/utils/generate_types/index.js index 9dec97387d..a4ed499b41 100644 --- a/utils/generate_types/index.js +++ b/utils/generate_types/index.js @@ -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, * ignoreMissing?: Set, * doNotExportClassNames?: Set, @@ -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} */ 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 = []; diff --git a/utils/markdown.js b/utils/markdown.js index 9c3379388c..9a6cc696e5 100644 --- a/utils/markdown.js +++ b/utils/markdown.js @@ -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 };