mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore: remove doclint type checking (#8549)
This commit is contained in:
parent
347101883f
commit
93d6c6deae
@ -1,161 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2017 Google Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class Documentation {
|
|
||||||
/**
|
|
||||||
* @param {!Array<!Documentation.Class>} classesArray
|
|
||||||
*/
|
|
||||||
constructor(classesArray) {
|
|
||||||
this.classesArray = classesArray;
|
|
||||||
/** @type {!Map<string, !Documentation.Class>} */
|
|
||||||
this.classes = new Map();
|
|
||||||
for (const cls of classesArray) {
|
|
||||||
this.classes.set(cls.name, cls);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Documentation.Class = class {
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {!Array<!Documentation.Member>} membersArray
|
|
||||||
* @param {?string=} extendsName
|
|
||||||
* @param {string=} comment
|
|
||||||
*/
|
|
||||||
constructor(name, membersArray, extendsName = null, comment = '') {
|
|
||||||
this.name = name;
|
|
||||||
this.membersArray = membersArray;
|
|
||||||
/** @type {!Map<string, !Documentation.Member>} */
|
|
||||||
this.members = new Map();
|
|
||||||
/** @type {!Map<string, !Documentation.Member>} */
|
|
||||||
this.properties = new Map();
|
|
||||||
/** @type {!Array<!Documentation.Member>} */
|
|
||||||
this.propertiesArray = [];
|
|
||||||
/** @type {!Map<string, !Documentation.Member>} */
|
|
||||||
this.methods = new Map();
|
|
||||||
/** @type {!Array<!Documentation.Member>} */
|
|
||||||
this.methodsArray = [];
|
|
||||||
/** @type {!Map<string, !Documentation.Member>} */
|
|
||||||
this.events = new Map();
|
|
||||||
/** @type {!Array<!Documentation.Member>} */
|
|
||||||
this.eventsArray = [];
|
|
||||||
this.comment = comment;
|
|
||||||
this.extends = extendsName;
|
|
||||||
for (const member of membersArray) {
|
|
||||||
this.members.set(member.name, member);
|
|
||||||
if (member.kind === 'method') {
|
|
||||||
this.methods.set(member.name, member);
|
|
||||||
this.methodsArray.push(member);
|
|
||||||
} else if (member.kind === 'property') {
|
|
||||||
this.properties.set(member.name, member);
|
|
||||||
this.propertiesArray.push(member);
|
|
||||||
} else if (member.kind === 'event') {
|
|
||||||
this.events.set(member.name, member);
|
|
||||||
this.eventsArray.push(member);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Documentation.Member = class {
|
|
||||||
/**
|
|
||||||
* @param {string} kind
|
|
||||||
* @param {string} name
|
|
||||||
* @param {?Documentation.Type} type
|
|
||||||
* @param {!Array<!Documentation.Member>} argsArray
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
kind,
|
|
||||||
name,
|
|
||||||
type,
|
|
||||||
argsArray,
|
|
||||||
comment = '',
|
|
||||||
returnComment = '',
|
|
||||||
required = true
|
|
||||||
) {
|
|
||||||
this.kind = kind;
|
|
||||||
this.name = name;
|
|
||||||
this.type = type;
|
|
||||||
this.comment = comment;
|
|
||||||
this.returnComment = returnComment;
|
|
||||||
this.argsArray = argsArray;
|
|
||||||
this.required = required;
|
|
||||||
/** @type {!Map<string, !Documentation.Member>} */
|
|
||||||
this.args = new Map();
|
|
||||||
for (const arg of argsArray) {
|
|
||||||
this.args.set(arg.name, arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {!Array<!Documentation.Member>} argsArray
|
|
||||||
* @param {?Documentation.Type} returnType
|
|
||||||
* @returns {!Documentation.Member}
|
|
||||||
*/
|
|
||||||
static createMethod(name, argsArray, returnType, returnComment, comment) {
|
|
||||||
return new Documentation.Member(
|
|
||||||
'method',
|
|
||||||
name,
|
|
||||||
returnType,
|
|
||||||
argsArray,
|
|
||||||
comment,
|
|
||||||
returnComment
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {!Documentation.Type} type
|
|
||||||
* @param {string=} comment
|
|
||||||
* @param {boolean=} required
|
|
||||||
* @returns {!Documentation.Member}
|
|
||||||
*/
|
|
||||||
static createProperty(name, type, comment, required) {
|
|
||||||
return new Documentation.Member(
|
|
||||||
'property',
|
|
||||||
name,
|
|
||||||
type,
|
|
||||||
[],
|
|
||||||
comment,
|
|
||||||
undefined,
|
|
||||||
required
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {?Documentation.Type=} type
|
|
||||||
* @param {string=} comment
|
|
||||||
* @returns {!Documentation.Member}
|
|
||||||
*/
|
|
||||||
static createEvent(name, type = null, comment) {
|
|
||||||
return new Documentation.Member('event', name, type, [], comment);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Documentation.Type = class {
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {!Array<!Documentation.Member>=} properties
|
|
||||||
*/
|
|
||||||
constructor(name, properties = []) {
|
|
||||||
this.name = name;
|
|
||||||
this.properties = properties;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Documentation;
|
|
@ -1,312 +0,0 @@
|
|||||||
const ts = require('typescript');
|
|
||||||
const path = require('path');
|
|
||||||
const Documentation = require('./Documentation.js');
|
|
||||||
module.exports = checkSources;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {!Array<!import('../Source')>} sources
|
|
||||||
*/
|
|
||||||
function checkSources(sources) {
|
|
||||||
// special treatment for Events.js
|
|
||||||
const classEvents = new Map();
|
|
||||||
const eventsSource = sources.find((source) => source.name() === 'Events.js');
|
|
||||||
if (eventsSource) {
|
|
||||||
const { Events } = require(eventsSource.filePath());
|
|
||||||
for (const [className, events] of Object.entries(Events)) {
|
|
||||||
classEvents.set(
|
|
||||||
className,
|
|
||||||
Array.from(Object.values(events))
|
|
||||||
.filter((e) => typeof e === 'string')
|
|
||||||
.map((e) => Documentation.Member.createEvent(e))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const excludeClasses = new Set([]);
|
|
||||||
const program = ts.createProgram({
|
|
||||||
options: {
|
|
||||||
allowJs: true,
|
|
||||||
target: ts.ScriptTarget.ES2017,
|
|
||||||
},
|
|
||||||
rootNames: sources.map((source) => source.filePath()),
|
|
||||||
});
|
|
||||||
const checker = program.getTypeChecker();
|
|
||||||
const sourceFiles = program.getSourceFiles();
|
|
||||||
/** @type {!Array<!Documentation.Class>} */
|
|
||||||
const classes = [];
|
|
||||||
/** @type {!Map<string, string>} */
|
|
||||||
const inheritance = new Map();
|
|
||||||
|
|
||||||
const sourceFilesNoNodeModules = sourceFiles.filter(
|
|
||||||
(x) => !x.fileName.includes('node_modules')
|
|
||||||
);
|
|
||||||
const sourceFileNamesSet = new Set(
|
|
||||||
sourceFilesNoNodeModules.map((x) => x.fileName)
|
|
||||||
);
|
|
||||||
sourceFilesNoNodeModules.map((x) => {
|
|
||||||
if (x.fileName.includes('/lib/')) {
|
|
||||||
const potentialTSSource = x.fileName
|
|
||||||
.replace('lib', 'src')
|
|
||||||
.replace('.js', '.ts');
|
|
||||||
if (sourceFileNamesSet.has(potentialTSSource)) {
|
|
||||||
/* Not going to visit this file because we have the TypeScript src code
|
|
||||||
* which we'll use instead.
|
|
||||||
*/
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
visit(x);
|
|
||||||
});
|
|
||||||
|
|
||||||
const errors = [];
|
|
||||||
const documentation = new Documentation(
|
|
||||||
recreateClassesWithInheritance(classes, inheritance)
|
|
||||||
);
|
|
||||||
|
|
||||||
return { errors, documentation };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {!Array<!Documentation.Class>} classes
|
|
||||||
* @param {!Map<string, string>} inheritance
|
|
||||||
* @returns {!Array<!Documentation.Class>}
|
|
||||||
*/
|
|
||||||
function recreateClassesWithInheritance(classes, inheritance) {
|
|
||||||
const classesByName = new Map(classes.map((cls) => [cls.name, cls]));
|
|
||||||
return classes.map((cls) => {
|
|
||||||
const membersMap = new Map();
|
|
||||||
for (let wp = cls; wp; wp = classesByName.get(inheritance.get(wp.name))) {
|
|
||||||
for (const member of wp.membersArray) {
|
|
||||||
// Member was overridden.
|
|
||||||
const memberId = member.kind + ':' + member.name;
|
|
||||||
if (membersMap.has(memberId)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
membersMap.set(memberId, member);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Documentation.Class(cls.name, Array.from(membersMap.values()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {!ts.Node} node
|
|
||||||
*/
|
|
||||||
function visit(node) {
|
|
||||||
if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
|
|
||||||
const symbol = node.name
|
|
||||||
? checker.getSymbolAtLocation(node.name)
|
|
||||||
: node.symbol;
|
|
||||||
let className = symbol.getName();
|
|
||||||
|
|
||||||
if (className === '__class') {
|
|
||||||
let parent = node;
|
|
||||||
while (parent.parent) {
|
|
||||||
parent = parent.parent;
|
|
||||||
}
|
|
||||||
className = path.basename(parent.fileName, '.js');
|
|
||||||
}
|
|
||||||
if (className && !excludeClasses.has(className)) {
|
|
||||||
classes.push(serializeClass(className, symbol, node));
|
|
||||||
const parentClassName = parentClass(node);
|
|
||||||
if (parentClassName) {
|
|
||||||
inheritance.set(className, parentClassName);
|
|
||||||
}
|
|
||||||
excludeClasses.add(className);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ts.forEachChild(node, visit);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parentClass(classNode) {
|
|
||||||
for (const herigateClause of classNode.heritageClauses || []) {
|
|
||||||
for (const heritageType of herigateClause.types) {
|
|
||||||
const parentClassName = heritageType.expression.escapedText;
|
|
||||||
return parentClassName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function serializeSymbol(symbol, circular = []) {
|
|
||||||
const type = checker.getTypeOfSymbolAtLocation(
|
|
||||||
symbol,
|
|
||||||
symbol.valueDeclaration
|
|
||||||
);
|
|
||||||
const name = symbol.getName();
|
|
||||||
if (symbol.valueDeclaration && symbol.valueDeclaration.dotDotDotToken) {
|
|
||||||
try {
|
|
||||||
const innerType = serializeType(type.typeArguments[0], circular);
|
|
||||||
innerType.name = '...' + innerType.name;
|
|
||||||
return Documentation.Member.createProperty('...' + name, innerType);
|
|
||||||
} catch (error) {
|
|
||||||
/**
|
|
||||||
* DocLint struggles with the paramArgs type on CDPSession.send because
|
|
||||||
* it uses a complex type from the devtools-protocol method. Doclint
|
|
||||||
* isn't going to be here for much longer so we'll just silence this
|
|
||||||
* warning than try to add support which would warrant a huge rewrite.
|
|
||||||
*/
|
|
||||||
if (name !== 'paramArgs') {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Documentation.Member.createProperty(
|
|
||||||
name,
|
|
||||||
serializeType(type, circular)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {!ts.ObjectType} type
|
|
||||||
*/
|
|
||||||
function isRegularObject(type) {
|
|
||||||
if (type.isIntersection()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!type.objectFlags) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!('aliasSymbol' in type)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (type.getConstructSignatures().length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (type.getCallSignatures().length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (type.isLiteral()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (type.isUnion()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {!ts.Type} type
|
|
||||||
* @returns {!Documentation.Type}
|
|
||||||
*/
|
|
||||||
function serializeType(type, circular = []) {
|
|
||||||
let typeName = checker.typeToString(type);
|
|
||||||
if (
|
|
||||||
typeName === 'any' ||
|
|
||||||
typeName === '{ [x: string]: string; }' ||
|
|
||||||
typeName === '{}'
|
|
||||||
) {
|
|
||||||
typeName = 'Object';
|
|
||||||
}
|
|
||||||
const nextCircular = [typeName].concat(circular);
|
|
||||||
|
|
||||||
if (isRegularObject(type)) {
|
|
||||||
let properties = undefined;
|
|
||||||
if (!circular.includes(typeName)) {
|
|
||||||
properties = type
|
|
||||||
.getProperties()
|
|
||||||
.map((property) => serializeSymbol(property, nextCircular));
|
|
||||||
}
|
|
||||||
return new Documentation.Type('Object', properties);
|
|
||||||
}
|
|
||||||
if (type.isUnion() && typeName.includes('|')) {
|
|
||||||
const types = type.types.map((type) => serializeType(type, circular));
|
|
||||||
const name = types.map((type) => type.name).join('|');
|
|
||||||
const properties = [].concat(...types.map((type) => type.properties));
|
|
||||||
return new Documentation.Type(
|
|
||||||
name.replace(/false\|true/g, 'boolean'),
|
|
||||||
properties
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (type.typeArguments) {
|
|
||||||
const properties = [];
|
|
||||||
const innerTypeNames = [];
|
|
||||||
for (const typeArgument of type.typeArguments) {
|
|
||||||
const innerType = serializeType(typeArgument, nextCircular);
|
|
||||||
if (innerType.properties) {
|
|
||||||
properties.push(...innerType.properties);
|
|
||||||
}
|
|
||||||
innerTypeNames.push(innerType.name);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
innerTypeNames.length === 0 ||
|
|
||||||
(innerTypeNames.length === 1 && innerTypeNames[0] === 'void')
|
|
||||||
) {
|
|
||||||
return new Documentation.Type(type.symbol.name);
|
|
||||||
}
|
|
||||||
return new Documentation.Type(
|
|
||||||
`${type.symbol.name}<${innerTypeNames.join(', ')}>`,
|
|
||||||
properties
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return new Documentation.Type(typeName, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {!ts.Symbol} symbol
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
function symbolHasPrivateModifier(symbol) {
|
|
||||||
const modifiers =
|
|
||||||
(symbol.valueDeclaration && symbol.valueDeclaration.modifiers) || [];
|
|
||||||
return modifiers.some(
|
|
||||||
(modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} className
|
|
||||||
* @param {!ts.Symbol} symbol
|
|
||||||
* @returns {}
|
|
||||||
*/
|
|
||||||
function serializeClass(className, symbol, node) {
|
|
||||||
/** @type {!Array<!Documentation.Member>} */
|
|
||||||
const members = classEvents.get(className) || [];
|
|
||||||
|
|
||||||
for (const [name, member] of symbol.members || []) {
|
|
||||||
/* Before TypeScript we denoted private methods with an underscore
|
|
||||||
* but in TypeScript we use the private keyword
|
|
||||||
* hence we check for either here.
|
|
||||||
*/
|
|
||||||
if (name.startsWith('_') || symbolHasPrivateModifier(member)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const memberType = checker.getTypeOfSymbolAtLocation(
|
|
||||||
member,
|
|
||||||
member.valueDeclaration
|
|
||||||
);
|
|
||||||
const signature = memberType.getCallSignatures()[0];
|
|
||||||
if (signature) {
|
|
||||||
members.push(serializeSignature(name, signature));
|
|
||||||
} else {
|
|
||||||
members.push(serializeProperty(name, memberType));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Documentation.Class(className, members);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {!ts.Signature} signature
|
|
||||||
*/
|
|
||||||
function serializeSignature(name, signature) {
|
|
||||||
const parameters = signature.parameters.map((s) => serializeSymbol(s));
|
|
||||||
const returnType = serializeType(signature.getReturnType());
|
|
||||||
return Documentation.Member.createMethod(
|
|
||||||
name,
|
|
||||||
parameters,
|
|
||||||
returnType.name !== 'void' ? returnType : null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {!ts.Type} type
|
|
||||||
*/
|
|
||||||
function serializeProperty(name, type) {
|
|
||||||
return Documentation.Member.createProperty(name, serializeType(type));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,417 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2017 Google Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const Documentation = require('./Documentation.js');
|
|
||||||
const { Parser, HtmlRenderer } = require('commonmark');
|
|
||||||
|
|
||||||
class MDOutline {
|
|
||||||
/**
|
|
||||||
* @param {!Page} page
|
|
||||||
* @param {string} text
|
|
||||||
* @returns {!MDOutline}
|
|
||||||
*/
|
|
||||||
static async create(page, text) {
|
|
||||||
// Render markdown as HTML.
|
|
||||||
const reader = new Parser();
|
|
||||||
const parsed = reader.parse(text);
|
|
||||||
const writer = new HtmlRenderer();
|
|
||||||
const html = writer.render(parsed);
|
|
||||||
|
|
||||||
page.on('console', (msg) => {
|
|
||||||
console.log(msg.text());
|
|
||||||
});
|
|
||||||
// Extract headings.
|
|
||||||
await page.setContent(html);
|
|
||||||
const { classes, errors } = await page.evaluate(() => {
|
|
||||||
const classes = [];
|
|
||||||
const errors = [];
|
|
||||||
const headers = document.body.querySelectorAll('h3');
|
|
||||||
for (let i = 0; i < headers.length; i++) {
|
|
||||||
const fragment = extractSiblingsIntoFragment(
|
|
||||||
headers[i],
|
|
||||||
headers[i + 1]
|
|
||||||
);
|
|
||||||
classes.push(parseClass(fragment));
|
|
||||||
}
|
|
||||||
return { classes, errors };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {HTMLLIElement} element
|
|
||||||
*/
|
|
||||||
function parseProperty(element) {
|
|
||||||
const clone = element.cloneNode(true);
|
|
||||||
const ul = clone.querySelector(':scope > ul');
|
|
||||||
const str = parseComment(
|
|
||||||
extractSiblingsIntoFragment(clone.firstChild, ul)
|
|
||||||
);
|
|
||||||
const name = str
|
|
||||||
.substring(0, str.indexOf('<'))
|
|
||||||
.replace(/\`/g, '')
|
|
||||||
.trim();
|
|
||||||
const type = findType(str);
|
|
||||||
const properties = [];
|
|
||||||
const comment = str
|
|
||||||
.substring(str.indexOf('<') + type.length + 2)
|
|
||||||
.trim();
|
|
||||||
// Strings have enum values instead of properties
|
|
||||||
if (!type.includes('string')) {
|
|
||||||
for (const childElement of element.querySelectorAll(
|
|
||||||
':scope > ul > li'
|
|
||||||
)) {
|
|
||||||
const property = parseProperty(childElement);
|
|
||||||
property.required = property.comment.includes('***required***');
|
|
||||||
properties.push(property);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
type,
|
|
||||||
comment,
|
|
||||||
properties,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} str
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function findType(str) {
|
|
||||||
const start = str.indexOf('<') + 1;
|
|
||||||
let count = 1;
|
|
||||||
for (let i = start; i < str.length; i++) {
|
|
||||||
if (str[i] === '<') {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
if (str[i] === '>') {
|
|
||||||
count--;
|
|
||||||
}
|
|
||||||
if (!count) {
|
|
||||||
return str.substring(start, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 'unknown';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {DocumentFragment} content
|
|
||||||
*/
|
|
||||||
function parseClass(content) {
|
|
||||||
const members = [];
|
|
||||||
const headers = content.querySelectorAll('h4');
|
|
||||||
const name = content.firstChild.textContent;
|
|
||||||
let extendsName = null;
|
|
||||||
let commentStart = content.firstChild.nextSibling;
|
|
||||||
const extendsElement = content.querySelector('ul');
|
|
||||||
if (
|
|
||||||
extendsElement &&
|
|
||||||
extendsElement.textContent.trim().startsWith('extends:')
|
|
||||||
) {
|
|
||||||
commentStart = extendsElement.nextSibling;
|
|
||||||
extendsName = extendsElement.querySelector('a').textContent;
|
|
||||||
}
|
|
||||||
const comment = parseComment(
|
|
||||||
extractSiblingsIntoFragment(commentStart, headers[0])
|
|
||||||
);
|
|
||||||
for (let i = 0; i < headers.length; i++) {
|
|
||||||
const fragment = extractSiblingsIntoFragment(
|
|
||||||
headers[i],
|
|
||||||
headers[i + 1]
|
|
||||||
);
|
|
||||||
members.push(parseMember(fragment));
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
comment,
|
|
||||||
extendsName,
|
|
||||||
members,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Node} content
|
|
||||||
*/
|
|
||||||
function parseComment(content) {
|
|
||||||
for (const code of content.querySelectorAll('pre > code')) {
|
|
||||||
code.replaceWith(
|
|
||||||
'```' +
|
|
||||||
code.className.substring('language-'.length) +
|
|
||||||
'\n' +
|
|
||||||
code.textContent +
|
|
||||||
'```'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (const code of content.querySelectorAll('code')) {
|
|
||||||
code.replaceWith('`' + code.textContent + '`');
|
|
||||||
}
|
|
||||||
for (const strong of content.querySelectorAll('strong')) {
|
|
||||||
strong.replaceWith('**' + parseComment(strong) + '**');
|
|
||||||
}
|
|
||||||
return content.textContent.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {DocumentFragment} content
|
|
||||||
*/
|
|
||||||
function parseMember(content) {
|
|
||||||
const name = content.firstChild.textContent;
|
|
||||||
const args = [];
|
|
||||||
let returnType = null;
|
|
||||||
|
|
||||||
const paramRegex = /^\w+\.[\w$]+\((.*)\)$/;
|
|
||||||
const matches = paramRegex.exec(name) || ['', ''];
|
|
||||||
const parameters = matches[1];
|
|
||||||
const optionalStartIndex = parameters.indexOf('[');
|
|
||||||
const optinalParamsStr =
|
|
||||||
optionalStartIndex !== -1
|
|
||||||
? parameters.substring(optionalStartIndex).replace(/[\[\]]/g, '')
|
|
||||||
: '';
|
|
||||||
const optionalparams = new Set(
|
|
||||||
optinalParamsStr
|
|
||||||
.split(',')
|
|
||||||
.filter((x) => x)
|
|
||||||
.map((x) => x.trim())
|
|
||||||
);
|
|
||||||
const ul = content.querySelector('ul');
|
|
||||||
for (const element of content.querySelectorAll('h4 + ul > li')) {
|
|
||||||
if (
|
|
||||||
element.matches('li') &&
|
|
||||||
element.textContent.trim().startsWith('<')
|
|
||||||
) {
|
|
||||||
returnType = parseProperty(element);
|
|
||||||
} else if (
|
|
||||||
element.matches('li') &&
|
|
||||||
element.firstChild.matches &&
|
|
||||||
element.firstChild.matches('code')
|
|
||||||
) {
|
|
||||||
const property = parseProperty(element);
|
|
||||||
property.required = !optionalparams.has(property.name);
|
|
||||||
args.push(property);
|
|
||||||
} else if (
|
|
||||||
element.matches('li') &&
|
|
||||||
element.firstChild.nodeType === Element.TEXT_NODE &&
|
|
||||||
element.firstChild.textContent.toLowerCase().startsWith('return')
|
|
||||||
) {
|
|
||||||
returnType = parseProperty(element);
|
|
||||||
const expectedText = 'returns: ';
|
|
||||||
let actualText = element.firstChild.textContent;
|
|
||||||
let angleIndex = actualText.indexOf('<');
|
|
||||||
let spaceIndex = actualText.indexOf(' ');
|
|
||||||
angleIndex = angleIndex === -1 ? actualText.length : angleIndex;
|
|
||||||
spaceIndex = spaceIndex === -1 ? actualText.length : spaceIndex + 1;
|
|
||||||
actualText = actualText.substring(
|
|
||||||
0,
|
|
||||||
Math.min(angleIndex, spaceIndex)
|
|
||||||
);
|
|
||||||
if (actualText !== expectedText) {
|
|
||||||
errors.push(
|
|
||||||
`${name} has mistyped 'return' type declaration: expected exactly '${expectedText}', found '${actualText}'.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const comment = parseComment(
|
|
||||||
extractSiblingsIntoFragment(ul ? ul.nextSibling : content)
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
args,
|
|
||||||
returnType,
|
|
||||||
comment,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {!Node} fromInclusive
|
|
||||||
* @param {!Node} toExclusive
|
|
||||||
* @returns {!DocumentFragment}
|
|
||||||
*/
|
|
||||||
function extractSiblingsIntoFragment(fromInclusive, toExclusive) {
|
|
||||||
const fragment = document.createDocumentFragment();
|
|
||||||
let node = fromInclusive;
|
|
||||||
while (node && node !== toExclusive) {
|
|
||||||
const next = node.nextSibling;
|
|
||||||
fragment.appendChild(node);
|
|
||||||
node = next;
|
|
||||||
}
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return new MDOutline(classes, errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(classes, errors) {
|
|
||||||
this.classes = [];
|
|
||||||
this.errors = errors;
|
|
||||||
const classHeading = /^class: (\w+)$/;
|
|
||||||
const constructorRegex = /^new (\w+)\((.*)\)$/;
|
|
||||||
const methodRegex = /^(\w+)\.([\w$]+)\((.*)\)$/;
|
|
||||||
const propertyRegex = /^(\w+)\.(\w+)$/;
|
|
||||||
const eventRegex = /^event: '(\w+)'$/;
|
|
||||||
let currentClassName = null;
|
|
||||||
let currentClassMembers = [];
|
|
||||||
let currentClassComment = '';
|
|
||||||
let currentClassExtends = null;
|
|
||||||
for (const cls of classes) {
|
|
||||||
const match = cls.name.match(classHeading);
|
|
||||||
if (!match) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
currentClassName = match[1];
|
|
||||||
currentClassComment = cls.comment;
|
|
||||||
currentClassExtends = cls.extendsName;
|
|
||||||
for (const member of cls.members) {
|
|
||||||
if (constructorRegex.test(member.name)) {
|
|
||||||
const match = member.name.match(constructorRegex);
|
|
||||||
handleMethod.call(this, member, match[1], 'constructor', match[2]);
|
|
||||||
} else if (methodRegex.test(member.name)) {
|
|
||||||
const match = member.name.match(methodRegex);
|
|
||||||
handleMethod.call(this, member, match[1], match[2], match[3]);
|
|
||||||
} else if (propertyRegex.test(member.name)) {
|
|
||||||
const match = member.name.match(propertyRegex);
|
|
||||||
handleProperty.call(this, member, match[1], match[2]);
|
|
||||||
} else if (eventRegex.test(member.name)) {
|
|
||||||
const match = member.name.match(eventRegex);
|
|
||||||
handleEvent.call(this, member, match[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
flushClassIfNeeded.call(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMethod(member, className, methodName, parameters) {
|
|
||||||
if (
|
|
||||||
!currentClassName ||
|
|
||||||
!className ||
|
|
||||||
!methodName ||
|
|
||||||
className.toLowerCase() !== currentClassName.toLowerCase()
|
|
||||||
) {
|
|
||||||
this.errors.push(`Failed to process header as method: ${member.name}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
parameters = parameters.trim().replace(/[\[\]]/g, '');
|
|
||||||
if (parameters !== member.args.map((arg) => arg.name).join(', ')) {
|
|
||||||
this.errors.push(
|
|
||||||
`Heading arguments for "${
|
|
||||||
member.name
|
|
||||||
}" do not match described ones, i.e. "${parameters}" != "${member.args
|
|
||||||
.map((a) => a.name)
|
|
||||||
.join(', ')}"`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const args = member.args.map(createPropertyFromJSON);
|
|
||||||
let returnType = null;
|
|
||||||
let returnComment = '';
|
|
||||||
if (member.returnType) {
|
|
||||||
const returnProperty = createPropertyFromJSON(member.returnType);
|
|
||||||
returnType = returnProperty.type;
|
|
||||||
returnComment = returnProperty.comment;
|
|
||||||
}
|
|
||||||
const method = Documentation.Member.createMethod(
|
|
||||||
methodName,
|
|
||||||
args,
|
|
||||||
returnType,
|
|
||||||
returnComment,
|
|
||||||
member.comment
|
|
||||||
);
|
|
||||||
currentClassMembers.push(method);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createPropertyFromJSON(payload) {
|
|
||||||
const type = new Documentation.Type(
|
|
||||||
payload.type,
|
|
||||||
payload.properties.map(createPropertyFromJSON)
|
|
||||||
);
|
|
||||||
const required = payload.required;
|
|
||||||
return Documentation.Member.createProperty(
|
|
||||||
payload.name,
|
|
||||||
type,
|
|
||||||
payload.comment,
|
|
||||||
required
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleProperty(member, className, propertyName) {
|
|
||||||
if (
|
|
||||||
!currentClassName ||
|
|
||||||
!className ||
|
|
||||||
!propertyName ||
|
|
||||||
className.toLowerCase() !== currentClassName.toLowerCase()
|
|
||||||
) {
|
|
||||||
this.errors.push(
|
|
||||||
`Failed to process header as property: ${member.name}`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const type = member.returnType ? member.returnType.type : null;
|
|
||||||
const properties = member.returnType ? member.returnType.properties : [];
|
|
||||||
currentClassMembers.push(
|
|
||||||
createPropertyFromJSON({
|
|
||||||
type,
|
|
||||||
name: propertyName,
|
|
||||||
properties,
|
|
||||||
comment: member.comment,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleEvent(member, eventName) {
|
|
||||||
if (!currentClassName || !eventName) {
|
|
||||||
this.errors.push(`Failed to process header as event: ${member.name}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentClassMembers.push(
|
|
||||||
Documentation.Member.createEvent(
|
|
||||||
eventName,
|
|
||||||
member.returnType && createPropertyFromJSON(member.returnType).type,
|
|
||||||
member.comment
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function flushClassIfNeeded() {
|
|
||||||
if (currentClassName === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.classes.push(
|
|
||||||
new Documentation.Class(
|
|
||||||
currentClassName,
|
|
||||||
currentClassMembers,
|
|
||||||
currentClassExtends,
|
|
||||||
currentClassComment
|
|
||||||
)
|
|
||||||
);
|
|
||||||
currentClassName = null;
|
|
||||||
currentClassMembers = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {!Page} page
|
|
||||||
* @param {!Array<!Source>} sources
|
|
||||||
* @returns {!Promise<{documentation: !Documentation, errors: !Array<string>}>}
|
|
||||||
*/
|
|
||||||
module.exports = async function (page, sources) {
|
|
||||||
const classes = [];
|
|
||||||
const errors = [];
|
|
||||||
for (const source of sources) {
|
|
||||||
const outline = await MDOutline.create(page, source.text());
|
|
||||||
classes.push(...outline.classes);
|
|
||||||
errors.push(...outline.errors);
|
|
||||||
}
|
|
||||||
const documentation = new Documentation(classes);
|
|
||||||
return { documentation, errors };
|
|
||||||
};
|
|
File diff suppressed because it is too large
Load Diff
@ -15,8 +15,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// eslint-disable-next-line import/extensions
|
|
||||||
const puppeteer = require('../..');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const Source = require('./Source.js');
|
const Source = require('./Source.js');
|
||||||
|
|
||||||
@ -72,38 +70,6 @@ async function run() {
|
|||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
const browser = await puppeteer.launch();
|
|
||||||
const page = await browser.newPage();
|
|
||||||
const checkPublicAPI = require('./check_public_api/index.js');
|
|
||||||
const tsSources = [
|
|
||||||
/* Source.readdir doesn't deal with nested directories well.
|
|
||||||
* Rather than invest time here when we're going to remove this Doc tooling soon
|
|
||||||
* we'll just list the directories manually.
|
|
||||||
*/
|
|
||||||
...(await Source.readdir(path.join(PROJECT_DIR, 'src'), 'ts')),
|
|
||||||
...(await Source.readdir(path.join(PROJECT_DIR, 'src', 'common'), 'ts')),
|
|
||||||
...(await Source.readdir(path.join(PROJECT_DIR, 'src', 'node'), 'ts')),
|
|
||||||
];
|
|
||||||
|
|
||||||
const tsSourcesNoDefinitions = tsSources.filter(
|
|
||||||
(source) => !source.filePath().endsWith('.d.ts')
|
|
||||||
);
|
|
||||||
|
|
||||||
const jsSources = [
|
|
||||||
...(await Source.readdir(path.join(PROJECT_DIR, 'lib'))),
|
|
||||||
...(await Source.readdir(path.join(PROJECT_DIR, 'lib', 'cjs'))),
|
|
||||||
...(await Source.readdir(
|
|
||||||
path.join(PROJECT_DIR, 'lib', 'cjs', 'puppeteer', 'common')
|
|
||||||
)),
|
|
||||||
...(await Source.readdir(
|
|
||||||
path.join(PROJECT_DIR, 'lib', 'cjs', 'puppeteer', 'node')
|
|
||||||
)),
|
|
||||||
];
|
|
||||||
const allSrcCode = [...jsSources, ...tsSourcesNoDefinitions];
|
|
||||||
messages.push(...(await checkPublicAPI(page, mdSources, allSrcCode)));
|
|
||||||
|
|
||||||
await browser.close();
|
|
||||||
|
|
||||||
for (const source of mdSources) {
|
for (const source of mdSources) {
|
||||||
if (!source.hasUpdatedText()) {
|
if (!source.hasUpdatedText()) {
|
||||||
continue;
|
continue;
|
||||||
|
Loading…
Reference in New Issue
Block a user