From 80f8a0fdf5803b77de266defb90b50b32130b725 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 4 Jan 2021 17:59:23 -0800 Subject: [PATCH] doc: further align docs w/ playwright.dev (3) (#4884) --- docs/out/api/class-accessibility.md | 4 +- utils/doclint/Documentation.js | 188 +++++++++++++++++++++++++++- utils/doclint/MDBuilder.js | 47 +++---- utils/doclint/cli.js | 28 +++-- utils/doclint/generateApiJson.js | 51 +++----- utils/generate_types/index.js | 147 +++++----------------- 6 files changed, 270 insertions(+), 195 deletions(-) diff --git a/docs/out/api/class-accessibility.md b/docs/out/api/class-accessibility.md index f790159c4f53e..c44349a29aa1e 100644 --- a/docs/out/api/class-accessibility.md +++ b/docs/out/api/class-accessibility.md @@ -36,8 +36,8 @@ Most of the accessibility tree gets filtered out when converting from Blink AX T - `readonly` <[boolean]> Whether the node is read only, if applicable. - `required` <[boolean]> Whether the node is required, if applicable. - `selected` <[boolean]> Whether the node is selected in its parent node, if applicable. - - `checked` Whether the checkbox is checked, or "mixed", if applicable. - - `pressed` Whether the toggle button is checked, or "mixed", if applicable. + - `checked` <[boolean]|"mixed"> Whether the checkbox is checked, or "mixed", if applicable. + - `pressed` <[boolean]|"mixed"> Whether the toggle button is checked, or "mixed", if applicable. - `level` <[number]> The level of a heading, if applicable. - `valuemin` <[number]> The minimum value in a node, if applicable. - `valuemax` <[number]> The maximum value in a node, if applicable. diff --git a/utils/doclint/Documentation.js b/utils/doclint/Documentation.js index 2238beaf430f9..4458b29aa7540 100644 --- a/utils/doclint/Documentation.js +++ b/utils/doclint/Documentation.js @@ -18,6 +18,17 @@ /** @typedef {import('../markdown').MarkdownNode} MarkdownNode */ +/** + * @typedef {{ + * name: string, + * args: ParsedType | null, + * retType: ParsedType | null, + * template: ParsedType | null, + * union: ParsedType | null, + * next: ParsedType | null, + * }} ParsedType + */ + class Documentation { /** * @param {!Array} classesArray @@ -242,19 +253,186 @@ Documentation.Member = class { }; Documentation.Type = class { + /** + * @param {string} expression + * @param {!Array=} properties + * @return {Documentation.Type} + */ + static parse(expression, properties = []) { + expression = expression.replace(/\\\(/g, '(').replace(/\\\)/g, ')'); + const type = Documentation.Type.fromParsedType(parseTypeExpression(expression)); + if (!properties.length) + return type; + const types = []; + type._collectAllTypes(types); + let success = false; + for (const t of types) { + if (t.name === 'Object') { + t.properties = properties; + success = true; + } + } + if (!success) + throw new Error('Nested properties given, but there are no objects in type expression: ' + expression); + return type; + } + + /** + * @param {ParsedType} parsedType + * @return {Documentation.Type} + */ + static fromParsedType(parsedType, inUnion = false) { + if (!inUnion && parsedType.union) { + const type = new Documentation.Type('union'); + type.union = []; + for (let t = parsedType; t; t = t.union) + type.union.push(Documentation.Type.fromParsedType(t, true)); + return type; + } + + if (parsedType.args) { + const type = new Documentation.Type('function'); + type.args = []; + for (let t = parsedType.args; t; t = t.next) + type.args.push(Documentation.Type.fromParsedType(t)); + type.returnType = parsedType.retType ? Documentation.Type.fromParsedType(parsedType.retType) : null; + return type; + } + + if (parsedType.template) { + const type = new Documentation.Type(parsedType.name); + type.templates = []; + for (let t = parsedType.template; t; t = t.next) + type.templates.push(Documentation.Type.fromParsedType(t)); + return type; + } + return new Documentation.Type(parsedType.name); + } + /** * @param {string} name * @param {!Array=} properties */ - constructor(name, properties = []) { - this.name = name; - this.properties = properties; + constructor(name, properties) { + this.name = name.replace(/^\[/, '').replace(/\]$/, ''); + this.properties = this.name === 'Object' ? properties : undefined; + /** @type {Documentation.Type[]} | undefined */ + this.union; + /** @type {Documentation.Type[]} | undefined */ + this.args; + /** @type {Documentation.Type} | undefined */ + this.returnType; + /** @type {Documentation.Type[]} | undefined */ + this.templates; } visit(visitor) { - for (const p of this.properties || []) - p.visit(visitor); + const types = []; + this._collectAllTypes(types); + for (const type of types) { + for (const p of type.properties || []) + p.visit(visitor); + } + } + + /** + * @returns {Documentation.Member[]} + */ + deepProperties() { + const types = []; + this._collectAllTypes(types); + for (const type of types) { + if (type.properties && type.properties.length) + return type.properties; + } + return []; + } + + /** + * @param {Documentation.Type[]} result + */ + _collectAllTypes(result) { + result.push(this); + for (const t of this.union || []) + t._collectAllTypes(result); + for (const t of this.args || []) + t._collectAllTypes(result); + for (const t of this.templates || []) + t._collectAllTypes(result); + if (this.returnType) + this.returnType._collectAllTypes(result); } }; +/** + * @param {string} type + * @returns {ParsedType} + */ +function parseTypeExpression(type) { + type = type.trim(); + let name = type; + let next = null; + let template = null; + let args = null; + let retType = null; + let firstTypeLength = type.length; + + for (let i = 0; i < type.length; i++) { + if (type[i] === '<') { + name = type.substring(0, i); + const matching = matchingBracket(type.substring(i), '<', '>'); + template = parseTypeExpression(type.substring(i + 1, i + matching - 1)); + firstTypeLength = i + matching; + break; + } + if (type[i] === '(') { + name = type.substring(0, i); + const matching = matchingBracket(type.substring(i), '(', ')'); + args = parseTypeExpression(type.substring(i + 1, i + matching - 1)); + i = i + matching; + if (type[i] === ':') { + retType = parseTypeExpression(type.substring(i + 1)); + next = retType.next; + retType.next = null; + break; + } + } + if (type[i] === '|' || type[i] === ',') { + name = type.substring(0, i); + firstTypeLength = i; + break; + } + } + let union = null; + if (type[firstTypeLength] === '|') + union = parseTypeExpression(type.substring(firstTypeLength + 1)); + else if (type[firstTypeLength] === ',') + next = parseTypeExpression(type.substring(firstTypeLength + 1)); + return { + name, + args, + retType, + template, + union, + next + }; +} + +/** + * @param {string} str + * @param {any} open + * @param {any} close + */ +function matchingBracket(str, open, close) { + let count = 1; + let i = 1; + for (; i < str.length && count; i++) { + if (str[i] === open) + count++; + else if (str[i] === close) + count--; + } + return i; +} + module.exports = Documentation; diff --git a/utils/doclint/MDBuilder.js b/utils/doclint/MDBuilder.js index 0332a4dcda6d2..ba863d966b53f 100644 --- a/utils/doclint/MDBuilder.js +++ b/utils/doclint/MDBuilder.js @@ -22,12 +22,14 @@ const Documentation = require('./Documentation'); /** @typedef {import('../markdown').MarkdownNode} MarkdownNode */ -/** @typedef {function({ - * clazz?: Documentation.Class, - * member?: Documentation.Member, - * param?: string, - * option?: string - * }): string} Renderer */ +/** + * @typedef {function({ + * clazz?: Documentation.Class, + * member?: Documentation.Member, + * param?: string, + * option?: string + * }): string} Renderer + */ class MDOutline { /** @@ -192,8 +194,12 @@ function parseMember(member) { } if (!returnType) returnType = new Documentation.Type('void'); - if (match[1] === 'async method') - returnType.name = `Promise<${returnType.name}>`; + + if (match[1] === 'async method') { + const templates = [ returnType ]; + returnType = new Documentation.Type('Promise'); + returnType.templates = templates; + } if (match[1] === 'event') return Documentation.Member.createEvent(name, returnType, extractComments(member)); @@ -234,27 +240,14 @@ function parseProperty(spec) { * @return {Documentation.Type} */ function parseType(spec) { - const { type } = parseArgument(spec.text); - let typeName = type.replace(/[\[\]\\]/g, ''); - const literals = typeName.match(/("[^"]+"(\|"[^"]+")*)/); - if (literals) { - const assorted = literals[1]; - typeName = typeName.substring(0, literals.index) + assorted + typeName.substring(literals.index + literals[0].length); - } + const arg = parseArgument(spec.text); const properties = []; - const hasNonEnumProperties = typeName.split('|').some(part => { - const basicTypes = new Set(['string', 'number', 'boolean']); - const arrayTypes = new Set([...basicTypes].map(type => `Array<${type}>`)); - return !basicTypes.has(part) && !arrayTypes.has(part) && !(part.startsWith('"') && part.endsWith('"')); - }); - if (hasNonEnumProperties && spec) { - for (const child of spec.children || []) { - const { name, text } = parseArgument(child.text); - const comments = /** @type {MarkdownNode[]} */ ([{ type: 'text', text }]); - properties.push(Documentation.Member.createProperty(name, parseType(child), comments, guessRequired(text))); - } + for (const child of spec.children || []) { + const { name, text } = parseArgument(child.text); + const comments = /** @type {MarkdownNode[]} */ ([{ type: 'text', text }]); + properties.push(Documentation.Member.createProperty(name, parseType(child), comments, guessRequired(text))); } - return new Documentation.Type(typeName, properties); + return Documentation.Type.parse(arg.type, properties); } /** diff --git a/utils/doclint/cli.js b/utils/doclint/cli.js index a52cf1296b83a..86131696cb367 100755 --- a/utils/doclint/cli.js +++ b/utils/doclint/cli.js @@ -268,29 +268,37 @@ function renderProperty(name, type, spec) { if (spec && spec.length) comment = spec[0].text; let children; - if (type.properties && type.properties.length) - children = type.properties.map(p => renderProperty(`\`${p.name}\``, p.type, p.spec)) + const properties = type.deepProperties(); + if (properties && properties.length) + children = properties.map(p => renderProperty(`\`${p.name}\``, p.type, p.spec)) else if (spec && spec.length > 1) children = spec.slice(1).map(s => md.clone(s)); + let typeText = renderType(type); + if (typeText === '[Promise]<[void]>') + typeText = '[Promise]'; + /** @type {MarkdownNode} */ const result = { type: 'li', liType: 'default', - text: `${name} <${renderType(type.name)}>${comment ? ' ' + comment : ''}`, + text: `${name} <${typeText}>${comment ? ' ' + comment : ''}`, children }; return result; } /** - * @param {string} type + * @param {Documentation.Type} type */ function renderType(type) { - if (type.includes('"')) - return type.replace(/,/g, '|').replace(/Array/, "[Array]").replace(/null/, "[null]").replace(/number/, "[number]"); - const result = type.replace(/([\w]+)/g, '[$1]'); - if (result === '[Promise]<[void]>') - return '[Promise]'; - return result.replace(/[(]/g, '\\(').replace(/[)]/g, '\\)'); + if (type.union) + return type.union.map(l => renderType(l)).join('|'); + if (type.templates) + return `[${type.name}]<${type.templates.map(l => renderType(l)).join(', ')}>`; + if (type.args) + return `[function]\\(${type.args.map(l => renderType(l)).join(', ')}\\)${type.returnType ? ':' + renderType(type.returnType) : ''}`; + if (type.name.startsWith('"')) + return type.name; + return `[${type.name}]`; } diff --git a/utils/doclint/generateApiJson.js b/utils/doclint/generateApiJson.js index 3bb20068e7fc3..de55450e4a51f 100644 --- a/utils/doclint/generateApiJson.js +++ b/utils/doclint/generateApiJson.js @@ -57,22 +57,7 @@ function serializeClass(clazz) { result.extends = clazz.extends; if (clazz.comment) result.comment = clazz.comment; - result.methods = {}; - result.events = {}; - result.properties = {}; - for (const member of clazz.membersArray) { - let map; - if (member.kind === 'event') { - map = result.events; - } else if (member.kind === 'method') { - map = result.methods; - } else if (member.kind === 'property') { - map = result.properties; - } else { - throw new Error('Unexpected member kind: ' + member.kind + ' ' + member.name + ' ' + member.type); - } - map[member.name] = serializeMember(member); - } + result.members = clazz.membersArray.map(serializeMember); return result; } @@ -82,9 +67,7 @@ function serializeClass(clazz) { function serializeMember(member) { const result = /** @type {any} */ ({ ...member }); sanitize(result); - result.args = {}; - for (const arg of member.argsArray) - result.args[arg.name] = serializeProperty(arg); + result.args = member.argsArray.map(serializeProperty); if (member.type) result.type = serializeType(member.type) return result; @@ -99,27 +82,27 @@ function serializeProperty(arg) { } function sanitize(result) { - delete result.kind; delete result.args; delete result.argsArray; - delete result.templates; delete result.clazz; - if (result.properties && !Object.keys(result.properties).length) - delete result.properties; - if (result.comment === '') - delete result.comment; - if (result.returnComment === '') - delete result.returnComment; + delete result.spec; } +/** + * @param {Documentation.Type} type + */ function serializeType(type) { + /** @type {any} */ const result = { ...type }; - if (type.properties && type.properties.length) { - result.properties = {}; - for (const prop of type.properties) - result.properties[prop.name] = serializeProperty(prop); - } else { - delete result.properties; - } + if (type.properties) + result.properties = type.properties.map(serializeProperty); + if (type.union) + result.union = type.union.map(serializeType); + if (type.templates) + result.templates = type.templates.map(serializeType); + if (type.args) + result.args = type.args.map(serializeType); + if (type.returnType) + result.returnType = serializeType(type.returnType); return result; } diff --git a/utils/generate_types/index.js b/utils/generate_types/index.js index 888b9f2e28b27..d99a219b0863a 100644 --- a/utils/generate_types/index.js +++ b/utils/generate_types/index.js @@ -278,19 +278,7 @@ function writeComment(comment, indent = '') { function stringifyComplexType(type, indent, ...namespace) { if (!type) return 'void'; - let typeString = stringifySimpleType(parseType(type.name)); - if (type.properties.length && typeString.indexOf('Object') !== -1) { - const name = namespace.map(n => n[0].toUpperCase() + n.substring(1)).join(''); - const shouldExport = exported[name]; - objectDefinitions.push({name, properties: type.properties}); - if (shouldExport) { - typeString = typeString.replace(/Object/g, name); - } else { - const objType = stringifyObjectType(type.properties, name, indent); - typeString = typeString.replace(/Object/g, objType); - } - } - return typeString; + return stringifySimpleType(type, indent, ...namespace); } function stringifyObjectType(properties, name, indent = '') { @@ -302,122 +290,47 @@ function stringifyObjectType(properties, name, indent = '') { } /** - * @param {string} type + * @param {Documentation.Type=} type + * @returns{string} */ -function parseType(type) { - type = type.trim(); - if (type.startsWith('?')) { - const parsed = parseType(type.substring(1)); - parsed.nullable = true; - return parsed; - } - if (type.startsWith('...')) - return parseType('Array<' + type.substring(3) + '>'); - let name = type; - let next = null; - let template = null; - let args = null; - let retType = null; - let firstTypeLength = type.length; - for (let i = 0; i < type.length; i++) { - if (type[i] === '<') { - name = type.substring(0, i); - const matching = matchingBracket(type.substring(i), '<', '>'); - template = parseType(type.substring(i + 1, i + matching - 1)); - firstTypeLength = i + matching; - break; - } - if (type[i] === '(') { - name = type.substring(0, i); - const matching = matchingBracket(type.substring(i), '(', ')'); - args = parseType(type.substring(i + 1, i + matching - 1)); - i = i + matching; - if (type[i] === ':') { - retType = parseType(type.substring(i + 1)); - next = retType.next; - retType.next = null; - break; - } - } - if (type[i] === '|' || type[i] === ',') { - name = type.substring(0, i); - firstTypeLength = i; - break; - } - } - let pipe = null; - if (type[firstTypeLength] === '|') - pipe = parseType(type.substring(firstTypeLength + 1)); - else if (type[firstTypeLength] === ',') - next = parseType(type.substring(firstTypeLength + 1)); - if (name === 'Promise' && !template) - template = parseType('void'); - return { - name, - args, - retType, - template, - pipe, - next - }; -} - -/** - * @return {string} - */ -function stringifySimpleType(parsedType) { - if (!parsedType) +function stringifySimpleType(type, indent = '', ...namespace) { + if (!type) return 'void'; - if (parsedType.name === 'Object' && parsedType.template) { - const keyType = stringifySimpleType({ - ...parsedType.template, - next: null - }); - const valueType = stringifySimpleType(parsedType.template.next); + if (type.name === 'Object' && type.templates) { + const keyType = stringifySimpleType(type.templates[0], indent, ...namespace); + const valueType = stringifySimpleType(type.templates[1], indent, ...namespace); return `{ [key: ${keyType}]: ${valueType}; }`; } - let out = parsedType.name; - if (parsedType.args) { - let args = parsedType.args; - const stringArgs = []; - while (args) { - const arg = args; - args = args.next; - arg.next = null; - stringArgs.push({ - type: stringifySimpleType(arg), - name: arg.name.toLowerCase() - }); + let out = type.name; + if (type.name === 'Object' && type.properties && type.properties.length) { + const name = namespace.map(n => n[0].toUpperCase() + n.substring(1)).join(''); + const shouldExport = exported[name]; + objectDefinitions.push({name, properties: type.properties}); + if (shouldExport) { + out = name; + } else { + out = stringifyObjectType(type.properties, name, indent); } - out = `((${stringArgs.map(({name, type}) => `${name}: ${type}`).join(', ')}) => ${stringifySimpleType(parsedType.retType)})`; - } else if (parsedType.name === 'function') { + } + + if (type.args) { + const stringArgs = type.args.map(a => ({ + type: stringifySimpleType(a, indent, ...namespace), + name: a.name.toLowerCase() + })); + out = `((${stringArgs.map(({name, type}) => `${name}: ${type}`).join(', ')}) => ${stringifySimpleType(type.returnType, indent, ...namespace)})`; + } else if (type.name === 'function') { out = 'Function'; } if (out === 'path') return 'string'; - if (parsedType.nullable) - out = 'null|' + out; - if (parsedType.template) - out += '<' + stringifySimpleType(parsedType.template) + '>'; - if (parsedType.pipe) - out += '|' + stringifySimpleType(parsedType.pipe); - if (parsedType.next) - out += ', ' + stringifySimpleType(parsedType.next); + if (type.templates) + out += '<' + type.templates.map(t => stringifySimpleType(t, indent, ...namespace)).join(', ') + '>'; + if (type.union) + out = type.union.map(t => stringifySimpleType(t, indent, ...namespace)).join('|'); return out.trim(); } -function matchingBracket(str, open, close) { - let count = 1; - let i = 1; - for (; i < str.length && count; i++) { - if (str[i] === open) - count++; - else if (str[i] === close) - count--; - } - return i; -} - /** * @param {Documentation.Member} member */