diff --git a/utils/doclint/check_public_api/Documentation.js b/utils/doclint/check_public_api/Documentation.js deleted file mode 100644 index c1a923ce385..00000000000 --- a/utils/doclint/check_public_api/Documentation.js +++ /dev/null @@ -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} classesArray - */ - constructor(classesArray) { - this.classesArray = classesArray; - /** @type {!Map} */ - this.classes = new Map(); - for (const cls of classesArray) { - this.classes.set(cls.name, cls); - } - } -} - -Documentation.Class = class { - /** - * @param {string} name - * @param {!Array} membersArray - * @param {?string=} extendsName - * @param {string=} comment - */ - constructor(name, membersArray, extendsName = null, comment = '') { - this.name = name; - this.membersArray = membersArray; - /** @type {!Map} */ - this.members = new Map(); - /** @type {!Map} */ - this.properties = new Map(); - /** @type {!Array} */ - this.propertiesArray = []; - /** @type {!Map} */ - this.methods = new Map(); - /** @type {!Array} */ - this.methodsArray = []; - /** @type {!Map} */ - this.events = new Map(); - /** @type {!Array} */ - 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} 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} */ - this.args = new Map(); - for (const arg of argsArray) { - this.args.set(arg.name, arg); - } - } - - /** - * @param {string} name - * @param {!Array} 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=} properties - */ - constructor(name, properties = []) { - this.name = name; - this.properties = properties; - } -}; - -module.exports = Documentation; diff --git a/utils/doclint/check_public_api/JSBuilder.js b/utils/doclint/check_public_api/JSBuilder.js deleted file mode 100644 index 60175c404d1..00000000000 --- a/utils/doclint/check_public_api/JSBuilder.js +++ /dev/null @@ -1,312 +0,0 @@ -const ts = require('typescript'); -const path = require('path'); -const Documentation = require('./Documentation.js'); -module.exports = checkSources; - -/** - * @param {!Array} 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} */ - const classes = []; - /** @type {!Map} */ - 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} classes - * @param {!Map} inheritance - * @returns {!Array} - */ - 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} */ - 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)); - } -} diff --git a/utils/doclint/check_public_api/MDBuilder.js b/utils/doclint/check_public_api/MDBuilder.js deleted file mode 100644 index 3281dd7e8be..00000000000 --- a/utils/doclint/check_public_api/MDBuilder.js +++ /dev/null @@ -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} sources - * @returns {!Promise<{documentation: !Documentation, errors: !Array}>} - */ -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 }; -}; diff --git a/utils/doclint/check_public_api/index.js b/utils/doclint/check_public_api/index.js deleted file mode 100644 index fea3ad4c3a5..00000000000 --- a/utils/doclint/check_public_api/index.js +++ /dev/null @@ -1,1273 +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 jsBuilder = require('./JSBuilder.js'); -const mdBuilder = require('./MDBuilder.js'); -const Documentation = require('./Documentation.js'); -const Message = require('../Message.js'); - -const MODULES_TO_CHECK_FOR_COVERAGE = { - Accessibility: '../../lib/cjs/puppeteer/common/Accessibility', - Browser: '../../lib/cjs/puppeteer/common/Browser', - BrowserContext: '../../lib/cjs/puppeteer/common/Browser', - BrowserFetcher: '../../lib/cjs/puppeteer/node/BrowserFetcher', - CDPSession: '../../lib/cjs/puppeteer/common/Connection', - ConsoleMessage: '../../lib/cjs/puppeteer/common/ConsoleMessage', - Coverage: '../../lib/cjs/puppeteer/common/Coverage', - Dialog: '../../lib/cjs/puppeteer/common/Dialog', - ElementHandle: '../../lib/cjs/puppeteer/common/JSHandle', - ExecutionContext: '../../lib/cjs/puppeteer/common/ExecutionContext', - EventEmitter: '../../lib/cjs/puppeteer/common/EventEmitter', - FileChooser: '../../lib/cjs/puppeteer/common/FileChooser', - Frame: '../../lib/cjs/puppeteer/common/FrameManager', - JSHandle: '../../lib/cjs/puppeteer/common/JSHandle', - Keyboard: '../../lib/cjs/puppeteer/common/Input', - Mouse: '../../lib/cjs/puppeteer/common/Input', - Page: '../../lib/cjs/puppeteer/common/Page', - Puppeteer: '../../lib/cjs/puppeteer/common/Puppeteer', - PuppeteerNode: '../../lib/cjs/puppeteer/node/Puppeteer', - HTTPRequest: '../../lib/cjs/puppeteer/common/HTTPRequest', - HTTPResponse: '../../lib/cjs/puppeteer/common/HTTPResponse', - SecurityDetails: '../../lib/cjs/puppeteer/common/SecurityDetails', - Target: '../../lib/cjs/puppeteer/common/Target', - TimeoutError: '../../lib/cjs/puppeteer/common/Errors', - Touchscreen: '../../lib/cjs/puppeteer/common/Input', - Tracing: '../../lib/cjs/puppeteer/common/Tracing', - WebWorker: '../../lib/cjs/puppeteer/common/WebWorker', -}; - -const EXCLUDE_PROPERTIES = new Set([ - 'Browser.create', - 'Frame.client', - 'Headers.fromPayload', - 'Page.client', - 'Page.create', - 'JSHandle.toString', - 'TimeoutError.name', - /* These are not actual properties, but a TypeScript generic. - * DocLint incorrectly parses it as a property. - */ - 'ElementHandle.ElementType', - 'ElementHandle.HandleObjectType', - 'JSHandle.HandleObjectType', -]); - -/** - * @param {!Page} page - * @param {!Array} mdSources - * @returns {!Promise>} - */ -module.exports = async function lint(page, mdSources, jsSources) { - const mdResult = await mdBuilder(page, mdSources); - const jsResult = await jsBuilder(jsSources); - const jsDocumentation = filterJSDocumentation(jsResult.documentation); - const mdDocumentation = mdResult.documentation; - - const jsErrors = jsResult.errors; - jsErrors.push(...checkDuplicates(jsDocumentation)); - - const mdErrors = mdResult.errors; - mdErrors.push(...compareDocumentations(mdDocumentation, jsDocumentation)); - mdErrors.push(...checkDuplicates(mdDocumentation)); - mdErrors.push(...checkSorting(mdDocumentation)); - - // Push all errors with proper prefixes - const errors = jsErrors.map((error) => '[JavaScript] ' + error); - errors.push(...mdErrors.map((error) => '[MarkDown] ' + error)); - return errors.map((error) => Message.error(error)); -}; - -/** - * @param {!Documentation} doc - * @returns {!Array} - */ -function checkSorting(doc) { - const errors = []; - for (const cls of doc.classesArray) { - const members = cls.membersArray; - - // Events should go first. - let eventIndex = 0; - for ( - ; - eventIndex < members.length && members[eventIndex].kind === 'event'; - ++eventIndex - ) {} - for ( - ; - eventIndex < members.length && members[eventIndex].kind !== 'event'; - ++eventIndex - ) {} - if (eventIndex < members.length) { - errors.push( - `Events should go first. Event '${members[eventIndex].name}' in class ${cls.name} breaks order` - ); - } - - // Constructor should be right after events and before all other members. - const constructorIndex = members.findIndex( - (member) => member.kind === 'method' && member.name === 'constructor' - ); - if ( - constructorIndex > 0 && - members[constructorIndex - 1].kind !== 'event' - ) { - errors.push(`Constructor of ${cls.name} should go before other methods`); - } - - // Events should be sorted alphabetically. - for (let i = 0; i < members.length - 1; ++i) { - const member1 = cls.membersArray[i]; - const member2 = cls.membersArray[i + 1]; - if (member1.kind !== 'event' || member2.kind !== 'event') { - continue; - } - if (member1.name > member2.name) { - errors.push( - `Event '${member1.name}' in class ${cls.name} breaks alphabetic ordering of events` - ); - } - } - - // All other members should be sorted alphabetically. - for (let i = 0; i < members.length - 1; ++i) { - const member1 = cls.membersArray[i]; - const member2 = cls.membersArray[i + 1]; - if (member1.kind === 'event' || member2.kind === 'event') { - continue; - } - if (member1.kind === 'method' && member1.name === 'constructor') { - continue; - } - if (member1.name > member2.name) { - let memberName1 = `${cls.name}.${member1.name}`; - if (member1.kind === 'method') { - memberName1 += '()'; - } - let memberName2 = `${cls.name}.${member2.name}`; - if (member2.kind === 'method') { - memberName2 += '()'; - } - errors.push( - `Bad alphabetic ordering of ${cls.name} members: ${memberName1} should go after ${memberName2}` - ); - } - } - } - return errors; -} - -/** - * @param {!Documentation} jsDocumentation - * @returns {!Documentation} - */ -function filterJSDocumentation(jsDocumentation) { - const includedClasses = new Set(Object.keys(MODULES_TO_CHECK_FOR_COVERAGE)); - // Filter private classes and methods. - const classes = []; - for (const cls of jsDocumentation.classesArray) { - if (includedClasses && !includedClasses.has(cls.name)) { - continue; - } - const members = cls.membersArray.filter( - (member) => !EXCLUDE_PROPERTIES.has(`${cls.name}.${member.name}`) - ); - classes.push(new Documentation.Class(cls.name, members)); - } - return new Documentation(classes); -} - -/** - * @param {!Documentation} doc - * @returns {!Array} - */ -function checkDuplicates(doc) { - const errors = []; - const classes = new Set(); - // Report duplicates. - for (const cls of doc.classesArray) { - if (classes.has(cls.name)) { - errors.push(`Duplicate declaration of class ${cls.name}`); - } - classes.add(cls.name); - const members = new Set(); - for (const member of cls.membersArray) { - if (members.has(member.kind + ' ' + member.name)) { - errors.push( - `Duplicate declaration of ${member.kind} ${cls.name}.${member.name}()` - ); - } - members.add(member.kind + ' ' + member.name); - const args = new Set(); - for (const arg of member.argsArray) { - if (args.has(arg.name)) { - errors.push( - `Duplicate declaration of argument ${cls.name}.${member.name} "${arg.name}"` - ); - } - args.add(arg.name); - } - } - } - return errors; -} - -// All the methods from our EventEmitter that we don't document for each subclass. -const EVENT_LISTENER_METHODS = new Set([ - 'emit', - 'listenerCount', - 'off', - 'on', - 'once', - 'removeListener', - 'addListener', - 'removeAllListeners', -]); - -/* Methods that are defined in code but are not documented */ -const expectedNotFoundMethods = new Map([ - ['Browser', EVENT_LISTENER_METHODS], - ['BrowserContext', EVENT_LISTENER_METHODS], - ['CDPSession', EVENT_LISTENER_METHODS], - ['Page', EVENT_LISTENER_METHODS], - ['WebWorker', EVENT_LISTENER_METHODS], -]); - -/** - * @param {!Documentation} actual - * @param {!Documentation} expected - * @returns {!Array} - */ -function compareDocumentations(actual, expected) { - const errors = []; - - const actualClasses = Array.from(actual.classes.keys()).sort(); - const expectedClasses = Array.from(expected.classes.keys()).sort(); - const classesDiff = diff(actualClasses, expectedClasses); - - /* These have been moved onto PuppeteerNode but we want to document them under - * Puppeteer. See https://github.com/puppeteer/puppeteer/pull/6504 for details. - */ - const expectedPuppeteerClassMissingMethods = new Set([ - 'createBrowserFetcher', - 'defaultArgs', - 'executablePath', - 'launch', - ]); - - for (const className of classesDiff.extra) { - errors.push(`Non-existing class found: ${className}`); - } - - for (const className of classesDiff.missing) { - if (className === 'PuppeteerNode') { - continue; - } - errors.push(`Class not found: ${className}`); - } - - for (const className of classesDiff.equal) { - const actualClass = actual.classes.get(className); - const expectedClass = expected.classes.get(className); - const actualMethods = Array.from(actualClass.methods.keys()).sort(); - const expectedMethods = Array.from(expectedClass.methods.keys()).sort(); - const methodDiff = diff(actualMethods, expectedMethods); - - for (const methodName of methodDiff.extra) { - if ( - expectedPuppeteerClassMissingMethods.has(methodName) && - actualClass.name === 'Puppeteer' - ) { - continue; - } - errors.push(`Non-existing method found: ${className}.${methodName}()`); - } - - for (const methodName of methodDiff.missing) { - const missingMethodsForClass = expectedNotFoundMethods.get(className); - if (missingMethodsForClass && missingMethodsForClass.has(methodName)) { - continue; - } - errors.push(`Method not found: ${className}.${methodName}()`); - } - - for (const methodName of methodDiff.equal) { - const actualMethod = actualClass.methods.get(methodName); - const expectedMethod = expectedClass.methods.get(methodName); - if (!actualMethod.type !== !expectedMethod.type) { - if (actualMethod.type) { - errors.push( - `Method ${className}.${methodName} has unneeded description of return type` - ); - } else { - errors.push( - `Method ${className}.${methodName} is missing return type description` - ); - } - } else if (actualMethod.hasReturn) { - checkType( - `Method ${className}.${methodName} has the wrong return type: `, - actualMethod.type, - expectedMethod.type - ); - } - const actualArgs = Array.from(actualMethod.args.keys()); - const expectedArgs = Array.from(expectedMethod.args.keys()); - const argsDiff = diff(actualArgs, expectedArgs); - - if (argsDiff.extra.length || argsDiff.missing.length) { - /* Doclint cannot handle the parameter type of the CDPSession send method. - * so we just ignore it. - */ - const isCdpSessionSend = - className === 'CDPSession' && methodName === 'send'; - if (!isCdpSessionSend) { - const text = [ - `Method ${className}.${methodName}() fails to describe its parameters:`, - ]; - for (const arg of argsDiff.missing) { - text.push(`- Argument not found: ${arg}`); - } - for (const arg of argsDiff.extra) { - text.push(`- Non-existing argument found: ${arg}`); - } - errors.push(text.join('\n')); - } - } - - for (const arg of argsDiff.equal) { - checkProperty( - `Method ${className}.${methodName}()`, - actualMethod.args.get(arg), - expectedMethod.args.get(arg) - ); - } - } - const actualProperties = Array.from(actualClass.properties.keys()).sort(); - const expectedProperties = Array.from( - expectedClass.properties.keys() - ).sort(); - const propertyDiff = diff(actualProperties, expectedProperties); - for (const propertyName of propertyDiff.extra) { - if (className === 'Puppeteer' && propertyName === 'product') { - continue; - } - errors.push(`Non-existing property found: ${className}.${propertyName}`); - } - for (const propertyName of propertyDiff.missing) { - errors.push(`Property not found: ${className}.${propertyName}`); - } - - const actualEvents = Array.from(actualClass.events.keys()).sort(); - const expectedEvents = Array.from(expectedClass.events.keys()).sort(); - const eventsDiff = diff(actualEvents, expectedEvents); - for (const eventName of eventsDiff.extra) { - errors.push( - `Non-existing event found in class ${className}: '${eventName}'` - ); - } - for (const eventName of eventsDiff.missing) { - errors.push(`Event not found in class ${className}: '${eventName}'`); - } - } - - /** - * @param {string} source - * @param {!Documentation.Member} actual - * @param {!Documentation.Member} expected - */ - function checkProperty(source, actual, expected) { - checkType(source + ' ' + actual.name, actual.type, expected.type); - } - - /** - * @param {string} source - * @param {!string} actualName - * @param {!string} expectedName - */ - function namingMisMatchInTypeIsExpected(source, actualName, expectedName) { - /* The DocLint tooling doesn't deal well with generics in TypeScript - * source files. We could fix this but the longterm plan is to - * auto-generate documentation from TS. So instead we document here - * the methods that use generics that DocLint trips up on and if it - * finds a mismatch that matches one of the cases below it doesn't - * error. This still means we're protected from accidental changes, as - * if the mismatch doesn't exactly match what's described below - * DocLint will fail. - */ - const expectedNamingMismatches = new Map([ - [ - 'Method CDPSession.send() method', - { - actualName: 'string', - expectedName: 'T', - }, - ], - [ - 'Method CDPSession.send() params', - { - actualName: 'Object', - expectedName: 'CommandParameters[T]', - }, - ], - [ - 'Method ElementHandle.click() options', - { - actualName: 'Object', - expectedName: 'ClickOptions', - }, - ], - [ - 'Method ElementHandle.clickablePoint() offset', - { - actualName: 'Object', - expectedName: 'Offset', - }, - ], - [ - 'Method ElementHandle.press() options', - { - actualName: 'Object', - expectedName: 'PressOptions', - }, - ], - [ - 'Method ElementHandle.press() key', - { - actualName: 'string', - expectedName: 'KeyInput', - }, - ], - [ - 'Method ElementHandle.drag() target', - { - actualName: 'Object', - expectedName: 'Point', - }, - ], - [ - 'Method ElementHandle.dragAndDrop() target', - { - actualName: 'ElementHandle', - expectedName: 'ElementHandle', - }, - ], - [ - 'Method ElementHandle.dragEnter() data', - { - actualName: 'Object', - expectedName: 'Protocol.Input.DragData', - }, - ], - [ - 'Method ElementHandle.dragOver() data', - { - actualName: 'Object', - expectedName: 'Protocol.Input.DragData', - }, - ], - [ - 'Method ElementHandle.drop() data', - { - actualName: 'Object', - expectedName: 'Protocol.Input.DragData', - }, - ], - [ - 'Method ElementHandle.screenshot() options', - { - actualName: 'Object', - expectedName: 'ScreenshotOptions', - }, - ], - [ - 'Method Keyboard.down() key', - { - actualName: 'string', - expectedName: 'KeyInput', - }, - ], - [ - 'Method Keyboard.press() key', - { - actualName: 'string', - expectedName: 'KeyInput', - }, - ], - [ - 'Method Keyboard.up() key', - { - actualName: 'string', - expectedName: 'KeyInput', - }, - ], - [ - 'Method Mouse.down() options', - { - actualName: 'Object', - expectedName: 'MouseOptions', - }, - ], - [ - 'Method Mouse.drag() start', - { - actualName: 'Object', - expectedName: 'Point', - }, - ], - [ - 'Method Mouse.drag() target', - { - actualName: 'Object', - expectedName: 'Point', - }, - ], - [ - 'Method Mouse.dragAndDrop() start', - { - actualName: 'Object', - expectedName: 'Point', - }, - ], - [ - 'Method Mouse.dragAndDrop() target', - { - actualName: 'Object', - expectedName: 'Point', - }, - ], - [ - 'Method Mouse.dragAndDrop() target', - { - actualName: 'Object', - expectedName: 'Point', - }, - ], - [ - 'Method Mouse.dragEnter() target', - { - actualName: 'Object', - expectedName: 'Point', - }, - ], - [ - 'Method Mouse.dragEnter() data', - { - actualName: 'Object', - expectedName: 'Protocol.Input.DragData', - }, - ], - [ - 'Method Mouse.dragOver() target', - { - actualName: 'Object', - expectedName: 'Point', - }, - ], - [ - 'Method Mouse.dragOver() data', - { - actualName: 'Object', - expectedName: 'Protocol.Input.DragData', - }, - ], - [ - 'Method Mouse.drop() target', - { - actualName: 'Object', - expectedName: 'Point', - }, - ], - [ - 'Method Mouse.drop() data', - { - actualName: 'Object', - expectedName: 'Protocol.Input.DragData', - }, - ], - [ - 'Method Mouse.up() options', - { - actualName: 'Object', - expectedName: 'MouseOptions', - }, - ], - [ - 'Method Mouse.wheel() options', - { - actualName: 'Object', - expectedName: 'MouseWheelOptions', - }, - ], - [ - 'Method Tracing.start() options', - { - actualName: 'Object', - expectedName: 'TracingOptions', - }, - ], - [ - 'Method Frame.waitForSelector() options', - { - actualName: 'Object', - expectedName: 'WaitForSelectorOptions', - }, - ], - [ - 'Method Frame.waitForXPath() options', - { - actualName: 'Object', - expectedName: 'WaitForSelectorOptions', - }, - ], - [ - 'Method HTTPRequest.abort() errorCode', - { - actualName: 'string', - expectedName: 'ErrorCode', - }, - ], - [ - 'Method Frame.goto() options.waitUntil', - { - actualName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - expectedName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - }, - ], - [ - 'Method Frame.waitForNavigation() options.waitUntil', - { - actualName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - expectedName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - }, - ], - [ - 'Method Frame.setContent() options.waitUntil', - { - actualName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - expectedName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - }, - ], - [ - 'Method Puppeteer.defaultArgs() options', - { - actualName: 'Object', - expectedName: 'ChromeArgOptions', - }, - ], - [ - 'Method Page.goBack() options.waitUntil', - { - actualName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - expectedName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - }, - ], - [ - 'Method Page.goForward() options.waitUntil', - { - actualName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - expectedName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - }, - ], - [ - 'Method Page.goto() options.waitUntil', - { - actualName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - expectedName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - }, - ], - [ - 'Method Page.reload() options.waitUntil', - { - actualName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - expectedName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - }, - ], - [ - 'Method Page.setContent() options.waitUntil', - { - actualName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - expectedName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - }, - ], - [ - 'Method Page.waitForNavigation() options.waitUntil', - { - actualName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - expectedName: - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - }, - ], - [ - 'Method Browser.createIncognitoBrowserContext() options', - { - actualName: 'Object', - expectedName: 'BrowserContextOptions', - }, - ], - [ - 'Method BrowserContext.overridePermissions() permissions', - { - actualName: 'Array', - expectedName: 'Array', - }, - ], - [ - 'Method Puppeteer.createBrowserFetcher() options', - { - actualName: 'Object', - expectedName: 'BrowserFetcherOptions', - }, - ], - [ - 'Method Page.authenticate() credentials', - { - actualName: 'Object', - expectedName: 'Credentials', - }, - ], - [ - 'Method Page.emulateMediaFeatures() features', - { - actualName: 'Array', - expectedName: 'Array', - }, - ], - [ - 'Method Page.emulate() options.viewport', - { - actualName: 'Object', - expectedName: 'Viewport', - }, - ], - [ - 'Method Page.emulateNetworkConditions() networkConditions', - { - actualName: 'Object', - expectedName: 'NetworkConditions', - }, - ], - [ - 'Method Page.setUserAgent() userAgentMetadata', - { - actualName: 'Object', - expectedName: 'Protocol.Emulation.UserAgentMetadata', - }, - ], - [ - 'Method Page.setViewport() options.viewport', - { - actualName: 'Object', - expectedName: 'Viewport', - }, - ], - [ - 'Method Page.setViewport() viewport', - { - actualName: 'Object', - expectedName: 'Viewport', - }, - ], - [ - 'Method Page.connect() options.defaultViewport', - { - actualName: 'Object', - expectedName: 'Viewport', - }, - ], - [ - 'Method Puppeteer.connect() options.defaultViewport', - { - actualName: 'Object', - expectedName: 'Viewport', - }, - ], - [ - 'Method Puppeteer.launch() options.defaultViewport', - { - actualName: 'Object', - expectedName: 'Viewport', - }, - ], - [ - 'Method Page.launch() options.defaultViewport', - { - actualName: 'Object', - expectedName: 'Viewport', - }, - ], - [ - 'Method Page.goBack() options', - { - actualName: 'Object', - expectedName: 'WaitForOptions', - }, - ], - [ - 'Method Page.goForward() options', - { - actualName: 'Object', - expectedName: 'WaitForOptions', - }, - ], - [ - 'Method Page.reload() options', - { - actualName: 'Object', - expectedName: 'WaitForOptions', - }, - ], - [ - 'Method Page.waitForNavigation() options', - { - actualName: 'Object', - expectedName: 'WaitForOptions', - }, - ], - [ - 'Method Page.pdf() options', - { - actualName: 'Object', - expectedName: 'PDFOptions', - }, - ], - [ - 'Method Page.createPDFStream() options', - { - actualName: 'Object', - expectedName: 'PDFOptions', - }, - ], - [ - 'Method Page.screenshot() options', - { - actualName: 'Object', - expectedName: 'ScreenshotOptions', - }, - ], - [ - 'Method Page.setContent() options', - { - actualName: 'Object', - expectedName: 'WaitForOptions', - }, - ], - [ - 'Method Page.setCookie() ...cookies', - { - actualName: '...Object', - expectedName: '...Protocol.Network.CookieParam', - }, - ], - [ - 'Method Page.emulateVisionDeficiency() type', - { - actualName: 'string', - expectedName: 'Protocol.Emulation.SetEmulatedVisionDeficiencyRequest', - }, - ], - [ - 'Method Accessibility.snapshot() options', - { - actualName: 'Object', - expectedName: 'SnapshotOptions', - }, - ], - [ - 'Method Browser.waitForTarget() options', - { - actualName: 'Object', - expectedName: 'WaitForTargetOptions', - }, - ], - [ - 'Method EventEmitter.emit() event', - { - actualName: 'string|symbol', - expectedName: 'EventType', - }, - ], - [ - 'Method EventEmitter.listenerCount() event', - { - actualName: 'string|symbol', - expectedName: 'EventType', - }, - ], - [ - 'Method EventEmitter.off() event', - { - actualName: 'string|symbol', - expectedName: 'EventType', - }, - ], - [ - 'Method EventEmitter.on() event', - { - actualName: 'string|symbol', - expectedName: 'EventType', - }, - ], - [ - 'Method EventEmitter.once() event', - { - actualName: 'string|symbol', - expectedName: 'EventType', - }, - ], - [ - 'Method EventEmitter.removeListener() event', - { - actualName: 'string|symbol', - expectedName: 'EventType', - }, - ], - [ - 'Method EventEmitter.addListener() event', - { - actualName: 'string|symbol', - expectedName: 'EventType', - }, - ], - [ - 'Method EventEmitter.removeAllListeners() event', - { - actualName: 'string|symbol', - expectedName: 'EventType', - }, - ], - [ - 'Method Coverage.startCSSCoverage() options', - { - actualName: 'Object', - expectedName: 'CSSCoverageOptions', - }, - ], - [ - 'Method Coverage.startJSCoverage() options', - { - actualName: 'Object', - expectedName: 'JSCoverageOptions', - }, - ], - [ - 'Method Mouse.click() options.button', - { - actualName: '"left"|"right"|"middle"|"back"|"forward"', - expectedName: 'MouseButton', - }, - ], - [ - 'Method Frame.click() options.button', - { - actualName: '"left"|"right"|"middle"|"back"|"forward"', - expectedName: 'MouseButton', - }, - ], - [ - 'Method Page.click() options.button', - { - actualName: '"left"|"right"|"middle"|"back"|"forward"', - expectedName: 'MouseButton', - }, - ], - [ - 'Method HTTPRequest.continue() overrides', - { - actualName: 'Object', - expectedName: 'ContinueRequestOverrides', - }, - ], - [ - 'Method HTTPRequest.respond() response', - { - actualName: 'Object', - expectedName: 'ResponseForRequest', - }, - ], - [ - 'Method Frame.addScriptTag() options', - { - actualName: 'Object', - expectedName: 'FrameAddScriptTagOptions', - }, - ], - [ - 'Method Frame.addStyleTag() options', - { - actualName: 'Object', - expectedName: 'FrameAddStyleTagOptions', - }, - ], - [ - 'Method Frame.waitForFunction() options', - { - actualName: 'Object', - expectedName: 'FrameWaitForFunctionOptions', - }, - ], - [ - 'Method BrowserContext.overridePermissions() permissions', - { - actualName: 'Array', - expectedName: 'Array', - }, - ], - [ - 'Method Puppeteer.connect() options', - { - actualName: 'Object', - expectedName: 'ConnectOptions', - }, - ], - [ - 'Method Page.deleteCookie() ...cookies', - { - actualName: '...Object', - expectedName: '...Protocol.Network.DeleteCookiesRequest', - }, - ], - [ - 'Method BrowserContext.overridePermissions() permissions', - { - actualName: 'Array', - expectedName: 'Array', - }, - ], - [ - 'Method HTTPRequest.respond() response.body', - { - actualName: 'string|Buffer', - expectedName: 'Object', - }, - ], - [ - 'Method HTTPRequest.respond() response.contentType', - { - actualName: 'string', - expectedName: 'Object', - }, - ], - [ - 'Method HTTPRequest.respond() response.status', - { - actualName: 'number', - expectedName: 'Object', - }, - ], - [ - 'Method EventEmitter.emit() eventData', - { - actualName: 'Object', - expectedName: 'unknown', - }, - ], - [ - 'Method Page.queryObjects() prototypeHandle', - { - actualName: 'JSHandle', - expectedName: 'JSHandle', - }, - ], - [ - 'Method ExecutionContext.queryObjects() prototypeHandle', - { - actualName: 'JSHandle', - expectedName: 'JSHandle', - }, - ], - ]); - - const expectedForSource = expectedNamingMismatches.get(source); - if (!expectedForSource) { - return false; - } - - const namingMismatchIsExpected = - expectedForSource.actualName === actualName && - expectedForSource.expectedName === expectedName; - - return namingMismatchIsExpected; - } - - /** - * @param {string} source - * @param {!Documentation.Type} actual - * @param {!Documentation.Type} expected - */ - function checkType(source, actual, expected) { - // TODO(@JoelEinbinder): check functions and Serializable - if ( - actual.name.includes('unction') || - actual.name.includes('Serializable') - ) { - return; - } - // We don't have nullchecks on for TypeScript - const actualName = actual.name.replace(/[\? ]/g, ''); - // TypeScript likes to add some spaces - const expectedName = expected.name.replace(/\ /g, ''); - const namingMismatchIsExpected = namingMisMatchInTypeIsExpected( - source, - actualName, - expectedName - ); - if (expectedName !== actualName && !namingMismatchIsExpected) { - errors.push(`${source} ${actualName} != ${expectedName}`); - } - - /* If we got a naming mismatch and it was expected, don't check the properties - * as they will likely be considered "wrong" by DocLint too. - */ - if (namingMismatchIsExpected) { - return; - } - - /* Some methods cause errors in the property checks for an unknown reason - * so we support a list of methods whose parameters are not checked. - */ - const skipPropertyChecksOnMethods = new Set([ - 'Method Page.deleteCookie() ...cookies', - 'Method Page.setCookie() ...cookies', - 'Method Puppeteer.connect() options', - 'Method Page.setUserAgent() userAgentMetadata', - ]); - if (skipPropertyChecksOnMethods.has(source)) { - return; - } - - const actualPropertiesMap = new Map( - actual.properties.map((property) => [property.name, property.type]) - ); - const expectedPropertiesMap = new Map( - expected.properties.map((property) => [property.name, property.type]) - ); - const propertiesDiff = diff( - Array.from(actualPropertiesMap.keys()).sort(), - Array.from(expectedPropertiesMap.keys()).sort() - ); - for (const propertyName of propertiesDiff.extra) { - errors.push(`${source} has unexpected property ${propertyName}`); - } - for (const propertyName of propertiesDiff.missing) { - errors.push(`${source} is missing property ${propertyName}`); - } - for (const propertyName of propertiesDiff.equal) { - checkType( - source + '.' + propertyName, - actualPropertiesMap.get(propertyName), - expectedPropertiesMap.get(propertyName) - ); - } - } - - return errors; -} - -/** - * @param {!Array} actual - * @param {!Array} expected - * @returns {{extra: !Array, missing: !Array, equal: !Array}} - */ -function diff(actual, expected) { - const N = actual.length; - const M = expected.length; - if (N === 0 && M === 0) { - return { extra: [], missing: [], equal: [] }; - } - if (N === 0) { - return { extra: [], missing: expected.slice(), equal: [] }; - } - if (M === 0) { - return { extra: actual.slice(), missing: [], equal: [] }; - } - const d = new Array(N); - const bt = new Array(N); - for (let i = 0; i < N; ++i) { - d[i] = new Array(M); - bt[i] = new Array(M); - for (let j = 0; j < M; ++j) { - const top = val(i - 1, j); - const left = val(i, j - 1); - if (top > left) { - d[i][j] = top; - bt[i][j] = 'extra'; - } else { - d[i][j] = left; - bt[i][j] = 'missing'; - } - const diag = val(i - 1, j - 1); - if (actual[i] === expected[j] && d[i][j] < diag + 1) { - d[i][j] = diag + 1; - bt[i][j] = 'eq'; - } - } - } - // Backtrack results. - let i = N - 1; - let j = M - 1; - const missing = []; - const extra = []; - const equal = []; - while (i >= 0 && j >= 0) { - switch (bt[i][j]) { - case 'extra': - extra.push(actual[i]); - i -= 1; - break; - case 'missing': - missing.push(expected[j]); - j -= 1; - break; - case 'eq': - equal.push(actual[i]); - i -= 1; - j -= 1; - break; - } - } - while (i >= 0) { - extra.push(actual[i--]); - } - while (j >= 0) { - missing.push(expected[j--]); - } - extra.reverse(); - missing.reverse(); - equal.reverse(); - return { extra, missing, equal }; - - function val(i, j) { - return i < 0 || j < 0 ? 0 : d[i][j]; - } -} diff --git a/utils/doclint/cli.js b/utils/doclint/cli.js index 42ad08c0df8..4bbda872d49 100755 --- a/utils/doclint/cli.js +++ b/utils/doclint/cli.js @@ -15,8 +15,6 @@ * limitations under the License. */ -// eslint-disable-next-line import/extensions -const puppeteer = require('../..'); const path = require('path'); 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) { if (!source.hasUpdatedText()) { continue;