doc: further align docs w/ playwright.dev (3) (#4884)

This commit is contained in:
Pavel Feldman 2021-01-04 17:59:23 -08:00 committed by GitHub
parent 5215add60d
commit 80f8a0fdf5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 270 additions and 195 deletions

View file

@ -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` <boolean|"mixed"> Whether the checkbox is checked, or "mixed", if applicable.
- `pressed` <boolean|"mixed"> 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.

View file

@ -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<!Documentation.Class>} classesArray
@ -242,19 +253,186 @@ Documentation.Member = class {
};
Documentation.Type = class {
/**
* @param {string} expression
* @param {!Array<!Documentation.Member>=} 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<!Documentation.Member>=} 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;

View file

@ -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);
}
/**

View file

@ -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}]`;
}

View file

@ -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;
}

View file

@ -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
*/