mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-07 11:46:42 +03:00
chore: move doc-specific code into documentation (#18933)
This commit is contained in:
parent
941090f0c4
commit
03d2b2ecbf
@ -19,7 +19,7 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const md = require('../markdown');
|
const md = require('../markdown');
|
||||||
const Documentation = require('./documentation');
|
const docs = require('./documentation');
|
||||||
|
|
||||||
/** @typedef {import('../markdown').MarkdownNode} MarkdownNode */
|
/** @typedef {import('../markdown').MarkdownNode} MarkdownNode */
|
||||||
/** @typedef {import('../markdown').MarkdownHeaderNode} MarkdownHeaderNode */
|
/** @typedef {import('../markdown').MarkdownHeaderNode} MarkdownHeaderNode */
|
||||||
@ -45,7 +45,7 @@ class ApiParser {
|
|||||||
const params = paramsPath ? md.parse(fs.readFileSync(paramsPath).toString()) : undefined;
|
const params = paramsPath ? md.parse(fs.readFileSync(paramsPath).toString()) : undefined;
|
||||||
checkNoDuplicateParamEntries(params);
|
checkNoDuplicateParamEntries(params);
|
||||||
const api = params ? applyTemplates(body, params) : body;
|
const api = params ? applyTemplates(body, params) : body;
|
||||||
/** @type {Map<string, Documentation.Class>} */
|
/** @type {Map<string, documentation.Class>} */
|
||||||
this.classes = new Map();
|
this.classes = new Map();
|
||||||
md.visitAll(api, node => {
|
md.visitAll(api, node => {
|
||||||
if (node.type === 'h1')
|
if (node.type === 'h1')
|
||||||
@ -59,7 +59,7 @@ class ApiParser {
|
|||||||
if (node.type === 'h3')
|
if (node.type === 'h3')
|
||||||
this.parseArgument(node);
|
this.parseArgument(node);
|
||||||
});
|
});
|
||||||
this.documentation = new Documentation([...this.classes.values()]);
|
this.documentation = new docs.Documentation([...this.classes.values()]);
|
||||||
this.documentation.index();
|
this.documentation.index();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ class ApiParser {
|
|||||||
continue;
|
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);
|
this.classes.set(clazz.name, clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,16 +100,16 @@ class ApiParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!returnType)
|
if (!returnType)
|
||||||
returnType = new Documentation.Type('void');
|
returnType = new docs.Type('void');
|
||||||
|
|
||||||
const comments = extractComments(spec);
|
const comments = extractComments(spec);
|
||||||
let member;
|
let member;
|
||||||
if (match[1] === 'event')
|
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')
|
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])) {
|
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'))
|
if (match[1].includes('async'))
|
||||||
member.async = true;
|
member.async = true;
|
||||||
if (match[1].includes('optional'))
|
if (match[1].includes('optional'))
|
||||||
@ -118,7 +118,7 @@ class ApiParser {
|
|||||||
if (!member)
|
if (!member)
|
||||||
throw new Error('Unknown member: ' + spec.text);
|
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);
|
const existingMember = clazz.membersArray.find(m => m.name === name && m.kind === member.kind);
|
||||||
if (existingMember && isTypeOverride(existingMember, member)) {
|
if (existingMember && isTypeOverride(existingMember, member)) {
|
||||||
for (const lang of member?.langs?.only || []) {
|
for (const lang of member?.langs?.only || []) {
|
||||||
@ -173,8 +173,8 @@ class ApiParser {
|
|||||||
// match[1] === 'option'
|
// match[1] === 'option'
|
||||||
let options = method.argsArray.find(o => o.name === 'options');
|
let options = method.argsArray.find(o => o.name === 'options');
|
||||||
if (!options) {
|
if (!options) {
|
||||||
const type = new Documentation.Type('Object', []);
|
const type = new docs.Type('Object', []);
|
||||||
options = Documentation.Member.createProperty({ langs: {}, experimental: false, since: 'v1.0' }, 'options', type, undefined, false);
|
options = docs.Member.createProperty({ langs: {}, experimental: false, since: 'v1.0' }, 'options', type, undefined, false);
|
||||||
method.argsArray.push(options);
|
method.argsArray.push(options);
|
||||||
}
|
}
|
||||||
const p = this.parseProperty(spec);
|
const p = this.parseProperty(spec);
|
||||||
@ -196,12 +196,12 @@ class ApiParser {
|
|||||||
const name = text.substring(0, typeStart).replace(/\`/g, '').trim();
|
const name = text.substring(0, typeStart).replace(/\`/g, '').trim();
|
||||||
const comments = extractComments(spec);
|
const comments = extractComments(spec);
|
||||||
const { type, optional } = this.parseType(/** @type {MarkdownLiNode} */(param));
|
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
|
* @param {MarkdownLiNode} spec
|
||||||
* @return {{ type: Documentation.Type, optional: boolean, experimental: boolean }}
|
* @return {{ type: documentation.Type, optional: boolean, experimental: boolean }}
|
||||||
*/
|
*/
|
||||||
parseType(spec) {
|
parseType(spec) {
|
||||||
const arg = parseVariable(spec.text);
|
const arg = parseVariable(spec.text);
|
||||||
@ -210,9 +210,9 @@ class ApiParser {
|
|||||||
const { name, text } = parseVariable(/** @type {string} */(child.text));
|
const { name, text } = parseVariable(/** @type {string} */(child.text));
|
||||||
const comments = /** @type {MarkdownNode[]} */ ([{ type: 'text', text }]);
|
const comments = /** @type {MarkdownNode[]} */ ([{ type: 'text', text }]);
|
||||||
const childType = this.parseType(child);
|
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 };
|
return { type, optional: arg.optional, experimental: arg.experimental };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -415,8 +415,8 @@ function childrenWithoutProperties(spec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Documentation.Member} existingMember
|
* @param {documentation.Member} existingMember
|
||||||
* @param {Documentation.Member} member
|
* @param {documentation.Member} member
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
function isTypeOverride(existingMember, member) {
|
function isTypeOverride(existingMember, member) {
|
||||||
|
@ -23,6 +23,7 @@ const path = require('path');
|
|||||||
const { parseApi } = require('./api_parser');
|
const { parseApi } = require('./api_parser');
|
||||||
const missingDocs = require('./missingDocs');
|
const missingDocs = require('./missingDocs');
|
||||||
const md = require('../markdown');
|
const md = require('../markdown');
|
||||||
|
const docs = require('./documentation');
|
||||||
|
|
||||||
/** @typedef {import('./documentation').Type} Type */
|
/** @typedef {import('./documentation').Type} Type */
|
||||||
/** @typedef {import('../markdown').MarkdownNode} MarkdownNode */
|
/** @typedef {import('../markdown').MarkdownNode} MarkdownNode */
|
||||||
@ -189,7 +190,7 @@ async function run() {
|
|||||||
const data = fs.readFileSync(filePath, 'utf-8');
|
const data = fs.readFileSync(filePath, 'utf-8');
|
||||||
let rootNode = md.filterNodesForLanguage(md.parse(data), lang);
|
let rootNode = md.filterNodesForLanguage(md.parse(data), lang);
|
||||||
// Validates code snippet groups.
|
// 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.
|
// Renders links.
|
||||||
documentation.renderLinksInText(rootNode);
|
documentation.renderLinksInText(rootNode);
|
||||||
// Validate links.
|
// Validate links.
|
||||||
|
@ -36,15 +36,15 @@ const md = require('../markdown');
|
|||||||
* @typedef {{
|
* @typedef {{
|
||||||
* only?: string[],
|
* only?: string[],
|
||||||
* aliases?: Object<string, string>,
|
* aliases?: Object<string, string>,
|
||||||
* types?: Object<string, Documentation.Type>,
|
* types?: Object<string, Type>,
|
||||||
* overrides?: Object<string, Documentation.Member>,
|
* overrides?: Object<string, Member>,
|
||||||
* }} Langs
|
* }} Langs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {function({
|
* @typedef {function({
|
||||||
* clazz?: Documentation.Class,
|
* clazz?: Class,
|
||||||
* member?: Documentation.Member,
|
* member?: Member,
|
||||||
* param?: string,
|
* param?: string,
|
||||||
* option?: string,
|
* option?: string,
|
||||||
* href?: string,
|
* href?: string,
|
||||||
@ -65,13 +65,19 @@ const md = require('../markdown');
|
|||||||
* }} LanguageOptions
|
* }} LanguageOptions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/** @typedef {{
|
||||||
|
* value: string, groupId: string, spec: MarkdownNode
|
||||||
|
* }} CodeGroup */
|
||||||
|
|
||||||
|
/** @typedef {function(CodeGroup[]): MarkdownNode[]} CodeGroupTransformer */
|
||||||
|
|
||||||
class Documentation {
|
class Documentation {
|
||||||
/**
|
/**
|
||||||
* @param {!Array<!Documentation.Class>} classesArray
|
* @param {!Array<!Class>} classesArray
|
||||||
*/
|
*/
|
||||||
constructor(classesArray) {
|
constructor(classesArray) {
|
||||||
this.classesArray = classesArray;
|
this.classesArray = classesArray;
|
||||||
/** @type {!Map<string, !Documentation.Class>} */
|
/** @type {!Map<string, !Class>} */
|
||||||
this.classes = new Map();
|
this.classes = new Map();
|
||||||
this.index();
|
this.index();
|
||||||
}
|
}
|
||||||
@ -147,7 +153,7 @@ class Documentation {
|
|||||||
* @param {Renderer} linkRenderer
|
* @param {Renderer} linkRenderer
|
||||||
*/
|
*/
|
||||||
setLinkRenderer(linkRenderer) {
|
setLinkRenderer(linkRenderer) {
|
||||||
// @type {Map<string, Documentation.Class>}
|
// @type {Map<string, Class>}
|
||||||
const classesMap = new Map();
|
const classesMap = new Map();
|
||||||
const membersMap = new Map();
|
const membersMap = new Map();
|
||||||
for (const clazz of this.classesArray) {
|
for (const clazz of this.classesArray) {
|
||||||
@ -156,7 +162,7 @@ class Documentation {
|
|||||||
membersMap.set(`${member.kind}: ${clazz.name}.${member.name}`, member);
|
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
|
* @param {MarkdownNode[] | undefined} nodes
|
||||||
*/
|
*/
|
||||||
this._patchLinks = (classOrMember, nodes) => patchLinks(classOrMember, nodes, classesMap, membersMap, linkRenderer);
|
this._patchLinks = (classOrMember, nodes) => patchLinks(classOrMember, nodes, classesMap, membersMap, linkRenderer);
|
||||||
@ -174,7 +180,7 @@ class Documentation {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} lang
|
* @param {string} lang
|
||||||
* @param {import('../markdown').CodeGroupTransformer} transformer
|
* @param {CodeGroupTransformer} transformer
|
||||||
*/
|
*/
|
||||||
setCodeGroupsTransformer(lang, transformer) {
|
setCodeGroupsTransformer(lang, transformer) {
|
||||||
this._codeGroupsTransformer = { lang, transformer };
|
this._codeGroupsTransformer = { lang, transformer };
|
||||||
@ -185,7 +191,7 @@ class Documentation {
|
|||||||
clazz.visit(item => {
|
clazz.visit(item => {
|
||||||
let spec = item.spec;
|
let spec = item.spec;
|
||||||
if (spec && this._codeGroupsTransformer)
|
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);
|
item.comment = generateSourceCodeComment(spec);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -196,11 +202,11 @@ class Documentation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Documentation.Class = class {
|
class Class {
|
||||||
/**
|
/**
|
||||||
* @param {Metainfo} metainfo
|
* @param {Metainfo} metainfo
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {!Array<!Documentation.Member>} membersArray
|
* @param {!Array<!Member>} membersArray
|
||||||
* @param {?string=} extendsName
|
* @param {?string=} extendsName
|
||||||
* @param {MarkdownNode[]=} spec
|
* @param {MarkdownNode[]=} spec
|
||||||
*/
|
*/
|
||||||
@ -216,19 +222,19 @@ Documentation.Class = class {
|
|||||||
this.index();
|
this.index();
|
||||||
const match = /** @type {string[]} */(name.match(/(API|JS|CDP|[A-Z])(.*)/));
|
const match = /** @type {string[]} */(name.match(/(API|JS|CDP|[A-Z])(.*)/));
|
||||||
this.varName = match[1].toLowerCase() + match[2];
|
this.varName = match[1].toLowerCase() + match[2];
|
||||||
/** @type {!Map<string, !Documentation.Member>} */
|
/** @type {!Map<string, !Member>} */
|
||||||
this.members = new Map();
|
this.members = new Map();
|
||||||
/** @type {!Map<string, !Documentation.Member>} */
|
/** @type {!Map<string, !Member>} */
|
||||||
this.properties = new Map();
|
this.properties = new Map();
|
||||||
/** @type {!Array<!Documentation.Member>} */
|
/** @type {!Array<!Member>} */
|
||||||
this.propertiesArray = [];
|
this.propertiesArray = [];
|
||||||
/** @type {!Map<string, !Documentation.Member>} */
|
/** @type {!Map<string, !Member>} */
|
||||||
this.methods = new Map();
|
this.methods = new Map();
|
||||||
/** @type {!Array<!Documentation.Member>} */
|
/** @type {!Array<!Member>} */
|
||||||
this.methodsArray = [];
|
this.methodsArray = [];
|
||||||
/** @type {!Map<string, !Documentation.Member>} */
|
/** @type {!Map<string, !Member>} */
|
||||||
this.events = new Map();
|
this.events = new Map();
|
||||||
/** @type {!Array<!Documentation.Member>} */
|
/** @type {!Array<!Member>} */
|
||||||
this.eventsArray = [];
|
this.eventsArray = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,7 +265,7 @@ Documentation.Class = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clone() {
|
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;
|
cls.comment = this.comment;
|
||||||
return cls;
|
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) {
|
visit(visitor) {
|
||||||
visitor(this);
|
visitor(this);
|
||||||
@ -348,13 +354,13 @@ Documentation.Class = class {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Documentation.Member = class {
|
class Member {
|
||||||
/**
|
/**
|
||||||
* @param {string} kind
|
* @param {string} kind
|
||||||
* @param {Metainfo} metainfo
|
* @param {Metainfo} metainfo
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {?Documentation.Type} type
|
* @param {?Type} type
|
||||||
* @param {!Array<!Documentation.Member>} argsArray
|
* @param {!Array<!Member>} argsArray
|
||||||
* @param {MarkdownNode[]=} spec
|
* @param {MarkdownNode[]=} spec
|
||||||
* @param {boolean=} required
|
* @param {boolean=} required
|
||||||
*/
|
*/
|
||||||
@ -369,12 +375,12 @@ Documentation.Member = class {
|
|||||||
this.argsArray = argsArray;
|
this.argsArray = argsArray;
|
||||||
this.required = required;
|
this.required = required;
|
||||||
this.comment = '';
|
this.comment = '';
|
||||||
/** @type {!Map<string, !Documentation.Member>} */
|
/** @type {!Map<string, !Member>} */
|
||||||
this.args = new Map();
|
this.args = new Map();
|
||||||
this.index();
|
this.index();
|
||||||
/** @type {!Documentation.Class | null} */
|
/** @type {!Class | null} */
|
||||||
this.clazz = null;
|
this.clazz = null;
|
||||||
/** @type {Documentation.Member=} */
|
/** @type {Member=} */
|
||||||
this.enclosingMethod = undefined;
|
this.enclosingMethod = undefined;
|
||||||
this.deprecated = false;
|
this.deprecated = false;
|
||||||
if (spec) {
|
if (spec) {
|
||||||
@ -466,7 +472,7 @@ Documentation.Member = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clone() {
|
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.alias = this.alias;
|
||||||
result.async = this.async;
|
result.async = this.async;
|
||||||
result.paramOrOption = this.paramOrOption;
|
result.paramOrOption = this.paramOrOption;
|
||||||
@ -476,40 +482,40 @@ Documentation.Member = class {
|
|||||||
/**
|
/**
|
||||||
* @param {Metainfo} metainfo
|
* @param {Metainfo} metainfo
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {!Array<!Documentation.Member>} argsArray
|
* @param {!Array<!Member>} argsArray
|
||||||
* @param {?Documentation.Type} returnType
|
* @param {?Type} returnType
|
||||||
* @param {MarkdownNode[]=} spec
|
* @param {MarkdownNode[]=} spec
|
||||||
* @return {!Documentation.Member}
|
* @return {!Member}
|
||||||
*/
|
*/
|
||||||
static createMethod(metainfo, name, argsArray, returnType, spec) {
|
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 {Metainfo} metainfo
|
||||||
* @param {!string} name
|
* @param {!string} name
|
||||||
* @param {!Documentation.Type} type
|
* @param {!Type} type
|
||||||
* @param {!MarkdownNode[]=} spec
|
* @param {!MarkdownNode[]=} spec
|
||||||
* @param {boolean=} required
|
* @param {boolean=} required
|
||||||
* @return {!Documentation.Member}
|
* @return {!Member}
|
||||||
*/
|
*/
|
||||||
static createProperty(metainfo, name, type, spec, required) {
|
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 {Metainfo} metainfo
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {?Documentation.Type=} type
|
* @param {?Type=} type
|
||||||
* @param {MarkdownNode[]=} spec
|
* @param {MarkdownNode[]=} spec
|
||||||
* @return {!Documentation.Member}
|
* @return {!Member}
|
||||||
*/
|
*/
|
||||||
static createEvent(metainfo, name, type = null, spec) {
|
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) {
|
visit(visitor) {
|
||||||
visitor(this);
|
visitor(this);
|
||||||
@ -520,15 +526,15 @@ Documentation.Member = class {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Documentation.Type = class {
|
class Type {
|
||||||
/**
|
/**
|
||||||
* @param {string} expression
|
* @param {string} expression
|
||||||
* @param {!Array<!Documentation.Member>=} properties
|
* @param {!Array<!Member>=} properties
|
||||||
* @return {Documentation.Type}
|
* @return {Type}
|
||||||
*/
|
*/
|
||||||
static parse(expression, properties = []) {
|
static parse(expression, properties = []) {
|
||||||
expression = expression.replace(/\\\(/g, '(').replace(/\\\)/g, ')');
|
expression = expression.replace(/\\\(/g, '(').replace(/\\\)/g, ')');
|
||||||
const type = Documentation.Type.fromParsedType(parseTypeExpression(expression));
|
const type = Type.fromParsedType(parseTypeExpression(expression));
|
||||||
type.expression = expression;
|
type.expression = expression;
|
||||||
if (type.name === 'number')
|
if (type.name === 'number')
|
||||||
throw new Error('Number types should be either int or float, not number in: ' + expression);
|
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
|
* @param {ParsedType} parsedType
|
||||||
* @return {Documentation.Type}
|
* @return {Type}
|
||||||
*/
|
*/
|
||||||
static fromParsedType(parsedType, inUnion = false) {
|
static fromParsedType(parsedType, inUnion = false) {
|
||||||
if (!inUnion && !parsedType.unionName && isStringUnion(parsedType) ) {
|
if (!inUnion && !parsedType.unionName && isStringUnion(parsedType) ) {
|
||||||
@ -558,12 +564,12 @@ Documentation.Type = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!inUnion && (parsedType.union || parsedType.unionName)) {
|
if (!inUnion && (parsedType.union || parsedType.unionName)) {
|
||||||
const type = new Documentation.Type(parsedType.unionName || '');
|
const type = new Type(parsedType.unionName || '');
|
||||||
type.union = [];
|
type.union = [];
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
for (let t = parsedType; t; t = t.union) {
|
for (let t = parsedType; t; t = t.union) {
|
||||||
const nestedUnion = !!t.unionName && t !== parsedType;
|
const nestedUnion = !!t.unionName && t !== parsedType;
|
||||||
type.union.push(Documentation.Type.fromParsedType(t, !nestedUnion));
|
type.union.push(Type.fromParsedType(t, !nestedUnion));
|
||||||
if (nestedUnion)
|
if (nestedUnion)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -571,41 +577,41 @@ Documentation.Type = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (parsedType.args) {
|
if (parsedType.args) {
|
||||||
const type = new Documentation.Type('function');
|
const type = new Type('function');
|
||||||
type.args = [];
|
type.args = [];
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
for (let t = parsedType.args; t; t = t.next)
|
for (let t = parsedType.args; t; t = t.next)
|
||||||
type.args.push(Documentation.Type.fromParsedType(t));
|
type.args.push(Type.fromParsedType(t));
|
||||||
type.returnType = parsedType.retType ? Documentation.Type.fromParsedType(parsedType.retType) : undefined;
|
type.returnType = parsedType.retType ? Type.fromParsedType(parsedType.retType) : undefined;
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedType.template) {
|
if (parsedType.template) {
|
||||||
const type = new Documentation.Type(parsedType.name);
|
const type = new Type(parsedType.name);
|
||||||
type.templates = [];
|
type.templates = [];
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
for (let t = parsedType.template; t; t = t.next)
|
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 type;
|
||||||
}
|
}
|
||||||
return new Documentation.Type(parsedType.name);
|
return new Type(parsedType.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {!Array<!Documentation.Member>=} properties
|
* @param {!Array<!Member>=} properties
|
||||||
*/
|
*/
|
||||||
constructor(name, properties) {
|
constructor(name, properties) {
|
||||||
this.name = name.replace(/^\[/, '').replace(/\]$/, '');
|
this.name = name.replace(/^\[/, '').replace(/\]$/, '');
|
||||||
/** @type {Documentation.Member[] | undefined} */
|
/** @type {Member[] | undefined} */
|
||||||
this.properties = this.name === 'Object' ? properties : undefined;
|
this.properties = this.name === 'Object' ? properties : undefined;
|
||||||
/** @type {Documentation.Type[] | undefined} */
|
/** @type {Type[] | undefined} */
|
||||||
this.union;
|
this.union;
|
||||||
/** @type {Documentation.Type[] | undefined} */
|
/** @type {Type[] | undefined} */
|
||||||
this.args;
|
this.args;
|
||||||
/** @type {Documentation.Type | undefined} */
|
/** @type {Type | undefined} */
|
||||||
this.returnType;
|
this.returnType;
|
||||||
/** @type {Documentation.Type[] | undefined} */
|
/** @type {Type[] | undefined} */
|
||||||
this.templates;
|
this.templates;
|
||||||
/** @type {string | undefined} */
|
/** @type {string | undefined} */
|
||||||
this.expression;
|
this.expression;
|
||||||
@ -621,7 +627,7 @@ Documentation.Type = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clone() {
|
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)
|
if (this.union)
|
||||||
type.union = this.union.map(type => type.clone());
|
type.union = this.union.map(type => type.clone());
|
||||||
if (this.args)
|
if (this.args)
|
||||||
@ -635,7 +641,7 @@ Documentation.Type = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Documentation.Member[]}
|
* @returns {Member[]}
|
||||||
*/
|
*/
|
||||||
deepProperties() {
|
deepProperties() {
|
||||||
const types = [];
|
const types = [];
|
||||||
@ -648,7 +654,7 @@ Documentation.Type = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Documentation.Member[] | undefined}
|
* @returns {Member[] | undefined}
|
||||||
*/
|
*/
|
||||||
sortedProperties() {
|
sortedProperties() {
|
||||||
if (!this.properties)
|
if (!this.properties)
|
||||||
@ -689,7 +695,7 @@ Documentation.Type = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Documentation.Type[]} result
|
* @param {Type[]} result
|
||||||
*/
|
*/
|
||||||
_collectAllTypes(result) {
|
_collectAllTypes(result) {
|
||||||
result.push(this);
|
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 {MarkdownNode[]|undefined} spec
|
||||||
* @param {Map<string, Documentation.Class>} classesMap
|
* @param {Map<string, Class>} classesMap
|
||||||
* @param {Map<string, Documentation.Member>} membersMap
|
* @param {Map<string, Member>} membersMap
|
||||||
* @param {Renderer} linkRenderer
|
* @param {Renderer} linkRenderer
|
||||||
*/
|
*/
|
||||||
function patchLinks(classOrMember, spec, classesMap, membersMap, linkRenderer) {
|
function patchLinks(classOrMember, spec, classesMap, membersMap, linkRenderer) {
|
||||||
@ -849,6 +855,8 @@ function generateSourceCodeComment(spec) {
|
|||||||
md.visitAll(comments, node => {
|
md.visitAll(comments, node => {
|
||||||
if (node.type === 'li' && node.liType === 'bullet')
|
if (node.type === 'li' && node.liType === 'bullet')
|
||||||
node.liType = 'default';
|
node.liType = 'default';
|
||||||
|
if (node.type === 'code' && node.codeLang)
|
||||||
|
node.codeLang = parseCodeLang(node.codeLang).highlighter;
|
||||||
if (node.type === 'note') {
|
if (node.type === 'note') {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
node.type = 'text';
|
node.type = 'text';
|
||||||
@ -859,7 +867,7 @@ function generateSourceCodeComment(spec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Documentation.Member} optionsArg
|
* @param {Member} optionsArg
|
||||||
* @param {LanguageOptions=} options
|
* @param {LanguageOptions=} options
|
||||||
*/
|
*/
|
||||||
function patchCSharpOptionOverloads(optionsArg, options = {}) {
|
function patchCSharpOptionOverloads(optionsArg, options = {}) {
|
||||||
@ -927,4 +935,88 @@ function csharpOptionOverloadSuffix(option, type) {
|
|||||||
throw new Error(`CSharp option "${option}" has unsupported type overload "${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 };
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const toKebabCase = require('lodash/kebabCase')
|
const toKebabCase = require('lodash/kebabCase')
|
||||||
const devices = require('../../packages/playwright-core/lib/server/deviceDescriptors');
|
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 PROJECT_DIR = path.join(__dirname, '..', '..');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const { parseOverrides } = require('./parseOverrides');
|
const { parseOverrides } = require('./parseOverrides');
|
||||||
@ -30,7 +30,7 @@ Error.stackTraceLimit = 50;
|
|||||||
class TypesGenerator {
|
class TypesGenerator {
|
||||||
/**
|
/**
|
||||||
* @param {{
|
* @param {{
|
||||||
* documentation: Documentation,
|
* documentation: docs.Documentation,
|
||||||
* overridesToDocsClassMapping?: Map<string, string>,
|
* overridesToDocsClassMapping?: Map<string, string>,
|
||||||
* ignoreMissing?: Set<string>,
|
* ignoreMissing?: Set<string>,
|
||||||
* doNotExportClassNames?: Set<string>,
|
* doNotExportClassNames?: Set<string>,
|
||||||
@ -39,7 +39,7 @@ class TypesGenerator {
|
|||||||
* }} options
|
* }} options
|
||||||
*/
|
*/
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
/** @type {Array<{name: string, properties: Documentation.Member[]}>} */
|
/** @type {Array<{name: string, properties: docs.Member[]}>} */
|
||||||
this.objectDefinitions = [];
|
this.objectDefinitions = [];
|
||||||
/** @type {Set<string>} */
|
/** @type {Set<string>} */
|
||||||
this.handledMethods = new Set();
|
this.handledMethods = new Set();
|
||||||
@ -77,6 +77,8 @@ class TypesGenerator {
|
|||||||
return `\`${option}\``;
|
return `\`${option}\``;
|
||||||
if (clazz)
|
if (clazz)
|
||||||
return `[${clazz.name}]`;
|
return `[${clazz.name}]`;
|
||||||
|
if (!member || !member.clazz)
|
||||||
|
throw new Error('Internal error');
|
||||||
const className = member.clazz.varName === 'playwrightAssertions' ? '' : member.clazz.varName + '.';
|
const className = member.clazz.varName === 'playwrightAssertions' ? '' : member.clazz.varName + '.';
|
||||||
if (member.kind === 'method')
|
if (member.kind === 'method')
|
||||||
return createMarkdownLink(member, `${className}${member.alias}(${this.renderJSSignature(member.argsArray)})`);
|
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) {
|
classToString(classDesc) {
|
||||||
const parts = [];
|
const parts = [];
|
||||||
@ -229,7 +231,7 @@ class TypesGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Documentation.Class} classDesc
|
* @param {docs.Class} classDesc
|
||||||
*/
|
*/
|
||||||
hasUniqueEvents(classDesc) {
|
hasUniqueEvents(classDesc) {
|
||||||
if (!classDesc.events.size)
|
if (!classDesc.events.size)
|
||||||
@ -241,7 +243,7 @@ class TypesGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Documentation.Class} classDesc
|
* @param {docs.Class} classDesc
|
||||||
*/
|
*/
|
||||||
createEventDescriptions(classDesc) {
|
createEventDescriptions(classDesc) {
|
||||||
if (!this.hasUniqueEvents(classDesc))
|
if (!this.hasUniqueEvents(classDesc))
|
||||||
@ -263,7 +265,7 @@ class TypesGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Documentation.Class} classDesc
|
* @param {docs.Class} classDesc
|
||||||
* @param {boolean=} exportMembersAsGlobals
|
* @param {boolean=} exportMembersAsGlobals
|
||||||
*/
|
*/
|
||||||
classBody(classDesc, exportMembersAsGlobals) {
|
classBody(classDesc, exportMembersAsGlobals) {
|
||||||
@ -319,8 +321,8 @@ class TypesGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Documentation.Class} classDesc
|
* @param {docs.Class} classDesc
|
||||||
* @param {Documentation.Member} member
|
* @param {docs.Member} member
|
||||||
*/
|
*/
|
||||||
hasOwnMethod(classDesc, member) {
|
hasOwnMethod(classDesc, member) {
|
||||||
if (this.handledMethods.has(`${classDesc.name}.${member.alias}#${member.overloadIndex}`))
|
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) {
|
parentClass(classDesc) {
|
||||||
if (!classDesc.extends)
|
if (!classDesc.extends)
|
||||||
@ -378,7 +380,7 @@ class TypesGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Documentation.Type} type
|
* @param {docs.Type} type
|
||||||
*/
|
*/
|
||||||
stringifyComplexType(type, indent, ...namespace) {
|
stringifyComplexType(type, indent, ...namespace) {
|
||||||
if (!type)
|
if (!type)
|
||||||
@ -387,7 +389,7 @@ class TypesGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Documentation.Member[]} properties
|
* @param {docs.Member[]} properties
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {string=} indent
|
* @param {string=} indent
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
@ -406,7 +408,7 @@ class TypesGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Documentation.Type=} type
|
* @param {docs.Type=} type
|
||||||
* @returns{string}
|
* @returns{string}
|
||||||
*/
|
*/
|
||||||
stringifySimpleType(type, indent = '', ...namespace) {
|
stringifySimpleType(type, indent = '', ...namespace) {
|
||||||
@ -455,7 +457,7 @@ class TypesGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Documentation.Member} member
|
* @param {docs.Member} member
|
||||||
*/
|
*/
|
||||||
argsFromMember(member, indent, ...namespace) {
|
argsFromMember(member, indent, ...namespace) {
|
||||||
if (member.kind === 'property')
|
if (member.kind === 'property')
|
||||||
@ -464,7 +466,7 @@ class TypesGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Documentation.Member} member
|
* @param {docs.Member} member
|
||||||
* @param {string} indent
|
* @param {string} indent
|
||||||
*/
|
*/
|
||||||
memberJSDOC(member, indent) {
|
memberJSDOC(member, indent) {
|
||||||
@ -480,7 +482,7 @@ class TypesGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Documentation.Member[]} args
|
* @param {docs.Member[]} args
|
||||||
*/
|
*/
|
||||||
renderJSSignature(args) {
|
renderJSSignature(args) {
|
||||||
const tokens = [];
|
const tokens = [];
|
||||||
|
@ -62,12 +62,6 @@
|
|||||||
* lines: string[],
|
* lines: string[],
|
||||||
* }} MarkdownPropsNode */
|
* }} MarkdownPropsNode */
|
||||||
|
|
||||||
/** @typedef {{
|
|
||||||
* value: string, groupId: string, spec: MarkdownNode
|
|
||||||
* }} CodeGroup */
|
|
||||||
|
|
||||||
/** @typedef {function(CodeGroup[]): MarkdownNode[]} CodeGroupTransformer */
|
|
||||||
|
|
||||||
/** @typedef {MarkdownTextNode | MarkdownLiNode | MarkdownCodeNode | MarkdownNoteNode | MarkdownHeaderNode | MarkdownNullNode | MarkdownPropsNode } MarkdownNode */
|
/** @typedef {MarkdownTextNode | MarkdownLiNode | MarkdownCodeNode | MarkdownNoteNode | MarkdownHeaderNode | MarkdownNullNode | MarkdownPropsNode } MarkdownNode */
|
||||||
|
|
||||||
function flattenWrappedLines(content) {
|
function flattenWrappedLines(content) {
|
||||||
@ -310,10 +304,7 @@ function innerRenderMdNode(indent, node, lastNode, result, maxColumns) {
|
|||||||
|
|
||||||
if (node.type === 'code') {
|
if (node.type === 'code') {
|
||||||
newLine();
|
newLine();
|
||||||
if (process.env.API_JSON_MODE)
|
result.push(`${indent}\`\`\`${node.codeLang}`);
|
||||||
result.push(`${indent}\`\`\`${node.codeLang}`);
|
|
||||||
else
|
|
||||||
result.push(`${indent}\`\`\`${node.codeLang ? parseCodeLang(node.codeLang).highlighter : ''}`);
|
|
||||||
for (const line of node.lines)
|
for (const line of node.lines)
|
||||||
result.push(indent + line);
|
result.push(indent + line);
|
||||||
result.push(`${indent}\`\`\``);
|
result.push(`${indent}\`\`\``);
|
||||||
@ -473,86 +464,4 @@ function filterNodesForLanguage(nodes, language) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
module.exports = { parse, render, clone, visitAll, visit, generateToc, filterNodesForLanguage };
|
||||||
* @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 };
|
|
||||||
|
Loading…
Reference in New Issue
Block a user