From 94077e0e74d21a56ade9471cbd952576762d70a6 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 28 Dec 2020 10:54:47 -0800 Subject: [PATCH] chore: remove JS types checker, rely on typescript (#4831) chore: remove JS types checker, rely on typescript We keep checking that all methods are documented, and no extra methods are documented, but rely on typescript for everything else. --- .eslintignore | 1 - docs-src/api-body.md | 3 - docs/api.md | 4 - package.json | 2 +- types/types.d.ts | 2 - utils/doclint/check_public_api/JSBuilder.js | 314 ----------------- utils/doclint/check_public_api/index.js | 325 ------------------ utils/doclint/check_public_api/missingDocs.js | 175 ++++++++++ .../doclint/check_public_api/test/.gitignore | 2 - .../test/check-duplicates/api.ts | 13 - .../test/check-duplicates/doc.md | 17 - .../test/check-duplicates/result.txt | 3 - .../test/check-nullish/api.ts | 19 - .../test/check-nullish/doc.md | 33 -- .../test/check-nullish/result.txt | 0 .../test/check-returns/api.ts | 21 -- .../test/check-returns/doc.md | 11 - .../test/check-returns/result.txt | 2 - .../test/check-sorting/api.ts | 11 - .../test/check-sorting/doc.md | 15 - .../test/check-sorting/events.js | 8 - .../test/check-sorting/result.txt | 4 - .../test/diff-arguments/api.ts | 11 - .../test/diff-arguments/doc.md | 19 - .../test/diff-arguments/result.txt | 3 - .../check_public_api/test/diff-classes/api.ts | 2 - .../check_public_api/test/diff-classes/doc.md | 5 - .../check_public_api/test/diff-classes/foo.ts | 2 - .../test/diff-classes/other.ts | 2 - .../test/diff-classes/result.txt | 3 - .../check_public_api/test/diff-events/api.ts | 4 - .../check_public_api/test/diff-events/doc.md | 5 - .../test/diff-events/events.js | 8 - .../test/diff-events/result.txt | 2 - .../check_public_api/test/diff-methods/api.ts | 18 - .../check_public_api/test/diff-methods/doc.md | 10 - .../test/diff-methods/result.txt | 3 - .../test/diff-properties/api.ts | 5 - .../test/diff-properties/doc.md | 5 - .../test/diff-properties/result.txt | 2 - .../test/js-builder-common/api.ts | 15 - .../test/js-builder-common/events.js | 6 - .../test/js-builder-common/result.txt | 50 --- .../test/js-builder-inheritance/api.ts | 18 - .../test/js-builder-inheritance/events.js | 8 - .../test/js-builder-inheritance/result.txt | 61 ---- .../test/md-builder-comments/doc.md | 18 - .../test/md-builder-comments/result.txt | 35 -- .../test/md-builder-common/doc.md | 23 -- .../test/md-builder-common/result.txt | 44 --- .../check_public_api/test/test-api-class.ts | 35 ++ .../doclint/check_public_api/test/test-api.md | 20 ++ .../doclint/check_public_api/test/test-api.ts | 18 + utils/doclint/check_public_api/test/test.js | 111 ------ .../check_public_api/test/testMissingDocs.js | 45 +++ utils/doclint/cli.js | 7 +- utils/generate_types/index.js | 38 +- utils/watch.js | 3 +- 58 files changed, 307 insertions(+), 1342 deletions(-) delete mode 100644 utils/doclint/check_public_api/JSBuilder.js delete mode 100644 utils/doclint/check_public_api/index.js create mode 100644 utils/doclint/check_public_api/missingDocs.js delete mode 100644 utils/doclint/check_public_api/test/.gitignore delete mode 100644 utils/doclint/check_public_api/test/check-duplicates/api.ts delete mode 100644 utils/doclint/check_public_api/test/check-duplicates/doc.md delete mode 100644 utils/doclint/check_public_api/test/check-duplicates/result.txt delete mode 100644 utils/doclint/check_public_api/test/check-nullish/api.ts delete mode 100644 utils/doclint/check_public_api/test/check-nullish/doc.md delete mode 100644 utils/doclint/check_public_api/test/check-nullish/result.txt delete mode 100644 utils/doclint/check_public_api/test/check-returns/api.ts delete mode 100644 utils/doclint/check_public_api/test/check-returns/doc.md delete mode 100644 utils/doclint/check_public_api/test/check-returns/result.txt delete mode 100644 utils/doclint/check_public_api/test/check-sorting/api.ts delete mode 100644 utils/doclint/check_public_api/test/check-sorting/doc.md delete mode 100644 utils/doclint/check_public_api/test/check-sorting/events.js delete mode 100644 utils/doclint/check_public_api/test/check-sorting/result.txt delete mode 100644 utils/doclint/check_public_api/test/diff-arguments/api.ts delete mode 100644 utils/doclint/check_public_api/test/diff-arguments/doc.md delete mode 100644 utils/doclint/check_public_api/test/diff-arguments/result.txt delete mode 100644 utils/doclint/check_public_api/test/diff-classes/api.ts delete mode 100644 utils/doclint/check_public_api/test/diff-classes/doc.md delete mode 100644 utils/doclint/check_public_api/test/diff-classes/foo.ts delete mode 100644 utils/doclint/check_public_api/test/diff-classes/other.ts delete mode 100644 utils/doclint/check_public_api/test/diff-classes/result.txt delete mode 100644 utils/doclint/check_public_api/test/diff-events/api.ts delete mode 100644 utils/doclint/check_public_api/test/diff-events/doc.md delete mode 100644 utils/doclint/check_public_api/test/diff-events/events.js delete mode 100644 utils/doclint/check_public_api/test/diff-events/result.txt delete mode 100644 utils/doclint/check_public_api/test/diff-methods/api.ts delete mode 100644 utils/doclint/check_public_api/test/diff-methods/doc.md delete mode 100644 utils/doclint/check_public_api/test/diff-methods/result.txt delete mode 100644 utils/doclint/check_public_api/test/diff-properties/api.ts delete mode 100644 utils/doclint/check_public_api/test/diff-properties/doc.md delete mode 100644 utils/doclint/check_public_api/test/diff-properties/result.txt delete mode 100644 utils/doclint/check_public_api/test/js-builder-common/api.ts delete mode 100644 utils/doclint/check_public_api/test/js-builder-common/events.js delete mode 100644 utils/doclint/check_public_api/test/js-builder-common/result.txt delete mode 100644 utils/doclint/check_public_api/test/js-builder-inheritance/api.ts delete mode 100644 utils/doclint/check_public_api/test/js-builder-inheritance/events.js delete mode 100644 utils/doclint/check_public_api/test/js-builder-inheritance/result.txt delete mode 100644 utils/doclint/check_public_api/test/md-builder-comments/doc.md delete mode 100644 utils/doclint/check_public_api/test/md-builder-comments/result.txt delete mode 100644 utils/doclint/check_public_api/test/md-builder-common/doc.md delete mode 100644 utils/doclint/check_public_api/test/md-builder-common/result.txt create mode 100644 utils/doclint/check_public_api/test/test-api-class.ts create mode 100644 utils/doclint/check_public_api/test/test-api.md create mode 100644 utils/doclint/check_public_api/test/test-api.ts delete mode 100644 utils/doclint/check_public_api/test/test.js create mode 100644 utils/doclint/check_public_api/test/testMissingDocs.js diff --git a/.eslintignore b/.eslintignore index bdd07eeb3d..e454775f7c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,4 @@ test/assets/modernizr.js -utils/doclint/check_public_api/test/ lib/ *.js src/generated/* diff --git a/docs-src/api-body.md b/docs-src/api-body.md index eae73d15ee..68b514db89 100644 --- a/docs-src/api-body.md +++ b/docs-src/api-body.md @@ -3811,9 +3811,6 @@ When all steps combined have not finished during the specified [`option: timeout Returns the `node.textContent`. -## method: ElementHandle.toString -- returns: <[string]> - ## async method: ElementHandle.type Focuses the element, and then sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text. diff --git a/docs/api.md b/docs/api.md index 48c435e875..456b04cc5b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -3001,7 +3001,6 @@ ElementHandle instances can be used as an argument in [`page.$eval(selector, pag - [elementHandle.setInputFiles(files[, options])](#elementhandlesetinputfilesfiles-options) - [elementHandle.tap([options])](#elementhandletapoptions) - [elementHandle.textContent()](#elementhandletextcontent) -- [elementHandle.toString()](#elementhandletostring) - [elementHandle.type(text[, options])](#elementhandletypetext-options) - [elementHandle.uncheck([options])](#elementhandleuncheckoptions) - [elementHandle.waitForElementState(state[, options])](#elementhandlewaitforelementstatestate-options) @@ -3375,9 +3374,6 @@ When all steps combined have not finished during the specified `timeout`, this m Returns the `node.textContent`. -#### elementHandle.toString() -- returns: <[string]> - #### elementHandle.type(text[, options]) - `text` <[string]> A text to type into a focused element. - `options` <[Object]> diff --git a/package.json b/package.json index 5d218d8c06..4ef0e6880e 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "tsc": "tsc -p .", "tsc-installer": "tsc -p ./src/install/tsconfig.json", "doc": "node utils/doclint/cli.js", - "test-infra": "folio utils/doclint/check_public_api/test/test.js && folio utils/doclint/preprocessor/test.js", + "test-infra": "folio utils/doclint/check_public_api/test/testMissingDocs.js && folio utils/doclint/preprocessor/test.js", "lint": "npm run eslint && npm run tsc && npm run doc && npm run check-deps && npm run generate-channels && node utils/generate_types/ --check-clean && npm run test-types && npm run test-infra", "clean": "rimraf lib && rimraf types", "prepare": "node install-from-github.js", diff --git a/types/types.d.ts b/types/types.d.ts index 290aa7bea1..00c779e335 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -5120,8 +5120,6 @@ export interface ElementHandle extends JSHandle { */ textContent(): Promise; - toString(): string; - /** * Focuses the element, and then sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text. * diff --git a/utils/doclint/check_public_api/JSBuilder.js b/utils/doclint/check_public_api/JSBuilder.js deleted file mode 100644 index d83b65c979..0000000000 --- a/utils/doclint/check_public_api/JSBuilder.js +++ /dev/null @@ -1,314 +0,0 @@ -/** - * Copyright 2019 Google Inc. All rights reserved. - * Modifications copyright (c) Microsoft Corporation. - * - * 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 ts = require('typescript'); -const path = require('path'); -const Documentation = require('./Documentation'); -const EventEmitter = require('events'); -module.exports = { checkSources }; - -/** - * @param {!Array} sources - */ -function checkSources(sources) { - // special treatment for Events.js - const classEvents = new Map(); - const eventsSources = sources.filter(source => source.name().startsWith('events.')); - for (const eventsSource of eventsSources) { - const {Events} = require(eventsSource.filePath().endsWith('.js') ? eventsSource.filePath() : eventsSource.filePath().replace(/\bsrc\b/, 'lib').replace('.ts', '.js')); - 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.ESNext, - strict: true - }, - rootNames: sources.map(source => source.filePath()) - }); - const checker = program.getTypeChecker(); - const sourceFiles = program.getSourceFiles(); - const errors = []; - const apiClassNames = new Set(); - /** @type {!Array} */ - const classes = []; - /** @type {!Map} */ - const inheritance = new Map(); - sourceFiles.filter(x => !x.fileName.includes('node_modules')).map(x => visit(x)); - const documentation = new Documentation(recreateClassesWithInheritance(classes, inheritance).filter(cls => apiClassNames.has(cls.name))); - - return {errors, documentation}; - - /** - * @param {!Array} classes - * @param {!Map} inheritance - * @return {!Array} - */ - function recreateClassesWithInheritance(classes, inheritance) { - const classesByName = new Map(classes.map(cls => [cls.name, cls])); - return classes.map(cls => { - const membersMap = new Map(); - const visit = cls => { - if (!cls) - return; - for (const member of cls.membersArray) { - // Member was overridden. - const memberId = member.kind + ':' + member.name; - if (membersMap.has(memberId)) - continue; - membersMap.set(memberId, member); - } - const parents = inheritance.get(cls.name) || []; - for (const parent of parents) - visit(classesByName.get(parent)); - }; - visit(cls); - return new Documentation.Class(cls.name, Array.from(membersMap.values()), undefined, undefined, cls.templates); - }); - } - - /** - * @param {!ts.Node} node - */ - function visit(node) { - const fileName = node.getSourceFile().fileName; - if (ts.isClassDeclaration(node) || ts.isClassExpression(node) || ts.isInterfaceDeclaration(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) && !fileName.endsWith('/protocol.ts') && !fileName.endsWith('/protocol.d.ts') && !fileName.endsWith('/types.d.ts') && !fileName.endsWith('node_modules/electron/electron.d.ts')) { - excludeClasses.add(className); - classes.push(serializeClass(className, symbol, node)); - inheritance.set(className, parentClasses(node)); - } - } - if (fileName.endsWith('/api.ts') && ts.isExportSpecifier(node)) - apiClassNames.add((node.propertyName || node.name).text); - ts.forEachChild(node, visit); - } - - function parentClasses(classNode) { - const parents = []; - for (const herigateClause of classNode.heritageClauses || []) { - for (const heritageType of herigateClause.types) { - let expression = heritageType.expression; - if (expression.kind === ts.SyntaxKind.PropertyAccessExpression) - expression = expression.name; - if (classNode.name.escapedText !== expression.escapedText) - parents.push(expression.escapedText); - } - } - return parents; - } - - /** - * @param {ts.Symbol} symbol - * @param {string[]=} circular - * @param {boolean=} parentRequired - */ - function serializeSymbol(symbol, circular = [], parentRequired = true) { - const type = checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration); - const name = symbol.getName(); - if (symbol.valueDeclaration && symbol.valueDeclaration.dotDotDotToken) { - const innerType = serializeType(type.typeArguments ? type.typeArguments[0] : type, circular); - innerType.name = '...' + innerType.name; - const required = false; - return Documentation.Member.createProperty('...' + name, innerType, undefined, required); - } - - const required = parentRequired && !typeHasUndefined(type); - return Documentation.Member.createProperty(name, serializeType(type, circular), undefined, required); - } - - /** - * @param {!ts.Type} type - */ - function typeHasUndefined(type) { - if (!type.isUnion()) - return type.flags & ts.TypeFlags.Undefined; - return type.types.some(typeHasUndefined); - } - - /** - * @param {!ts.Type} type - */ - function isNotUndefined(type) { - return !(type.flags & ts.TypeFlags.Undefined); - } - - /** - * @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.isUnion()) - return false; - return true; - } - - /** - * @param {!ts.Type} type - * @return {!Documentation.Type} - */ - function serializeType(type, circular = []) { - let typeName = checker.typeToString(type).replace(/SmartHandle/g, 'Handle'); - if (typeName === 'any') - typeName = 'Object'; - const nextCircular = [typeName].concat(circular); - const stringIndexType = type.getStringIndexType(); - if (stringIndexType) { - return new Documentation.Type(`Object`); - } else if (isRegularObject(type)) { - let properties = undefined; - if (!circular.includes(typeName)) - properties = getTypeProperties(type).map(property => serializeSymbol(property, nextCircular)); - return new Documentation.Type('Object', properties); - } - if (type.isUnion() && (typeName.includes('|') || type.types.every(type => type.isStringLiteral() || type.intrinsicName === 'number'))) { - const types = type.types.filter(isNotUndefined).map((type, index) => { - return { isLiteral: type.isStringLiteral(), serialized: serializeType(type, circular), index }; - }); - types.sort((a, b) => { - if (!a.isLiteral || !b.isLiteral) - return a.index - b.index; - return a.serialized.name.localeCompare(b.serialized.name); - }); - const name = types.map(type => type.serialized.name).join('|'); - const properties = [].concat(...types.map(type => type.serialized.properties)); - return new Documentation.Type(name.replace(/false\|true/g, 'boolean'), properties); - } - if (type.typeArguments && type.symbol) { - 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 === 'Promise' ? 'Promise' : type.symbol.name); - return new Documentation.Type(`${type.symbol.name}<${innerTypeNames.join(', ')}>`, properties); - } - return new Documentation.Type(typeName, []); - } - - /** - * @param {string} className - * @param {!ts.Symbol} symbol - * @return {} - */ - function serializeClass(className, symbol, node) { - /** @type {!Array} */ - const members = classEvents.get(className) || []; - const templates = []; - for (const [name, member] of symbol.members || []) { - if (className === 'Error') - continue; - if (name.startsWith('_')) - continue; - if (member.valueDeclaration && ts.getCombinedModifierFlags(member.valueDeclaration) & ts.ModifierFlags.Private) - continue; - if (EventEmitter.prototype.hasOwnProperty(name)) - continue; - const memberType = checker.getTypeOfSymbolAtLocation(member, member.valueDeclaration); - const signature = signatureForType(memberType); - if (member.flags & ts.SymbolFlags.TypeParameter) - templates.push(name); - else if (signature) - members.push(serializeSignature(name, signature)); - else - members.push(serializeProperty(name, memberType)); - } - - return new Documentation.Class(className, members, undefined, undefined, templates); - } - - /** - * @param {ts.Type} type - */ - function signatureForType(type) { - const signatures = type.getCallSignatures(); - if (signatures.length) - return signatures[signatures.length - 1]; - if (type.isUnion()) { - const innerTypes = type.types.filter(isNotUndefined); - if (innerTypes.length === 1) - return signatureForType(innerTypes[0]); - } - return null; - } - - /** - * @param {string} name - * @param {!ts.Signature} signature - */ - function serializeSignature(name, signature) { - const minArgumentCount = signature.minArgumentCount || 0; - const parameters = signature.parameters.map((s, index) => serializeSymbol(s, [], index < minArgumentCount)); - const templates = signature.typeParameters ? signature.typeParameters.map(t => t.symbol.name) : []; - const returnType = serializeType(signature.getReturnType()); - return Documentation.Member.createMethod(name, parameters, returnType.name !== 'void' ? returnType : null, undefined, templates); - } - - /** - * @param {string} name - * @param {!ts.Type} type - */ - function serializeProperty(name, type) { - return Documentation.Member.createProperty(name, serializeType(type)); - } - - /** - * @param {!ts.Type} type - */ - function getTypeProperties(type) { - if (type.aliasSymbol && type.aliasSymbol.escapedName === 'Pick') { - const props = getTypeProperties(type.aliasTypeArguments[0]); - const pickNames = type.aliasTypeArguments[1].types.map(t => t.value); - return props.filter(p => pickNames.includes(p.getName())); - } - if (!type.isIntersection()) - return type.getProperties(); - let props = []; - for (const innerType of type.types) { - let innerProps = getTypeProperties(innerType); - props = props.filter(p => !innerProps.find(e => e.getName() === p.getName())); - props = props.filter(p => p.getName() !== '_tracePath' && p.getName() !== '_traceResourcesPath'); - props.push(...innerProps); - } - return props; - } -} diff --git a/utils/doclint/check_public_api/index.js b/utils/doclint/check_public_api/index.js deleted file mode 100644 index bdcff2f3d2..0000000000 --- a/utils/doclint/check_public_api/index.js +++ /dev/null @@ -1,325 +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'); -const mdBuilder = require('./MDBuilder'); -const Documentation = require('./Documentation'); -const Message = require('../Message'); - -const EXCLUDE_PROPERTIES = new Set([ - 'JSHandle.toString', -]); - -/** - * @return {!Array} - */ -module.exports = function lint(api, jsSources) { - const mdResult = mdBuilder(api, true); - const jsResult = jsBuilder.checkSources(jsSources); - const jsDocumentation = filterJSDocumentation(jsSources, 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)); - - // 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 {!Array} jsSources - * @param {!Documentation} jsDocumentation - * @return {!Documentation} - */ -function filterJSDocumentation(jsSources, jsDocumentation) { - // Filter private classes and methods. - const classes = []; - for (const cls of jsDocumentation.classesArray) { - 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 - * @return {!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; -} - -/** - * @param {!Documentation} actual - * @param {!Documentation} expected - * @return {!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); - for (const className of classesDiff.extra) - errors.push(`Documented but not implemented class: ${className}`); - for (const className of classesDiff.missing) - errors.push(`Implemented but not documented class: ${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) - errors.push(`Documented but not implemented method: ${className}.${methodName}()`); - for (const methodName of methodDiff.missing) - errors.push(`Implemented but not documented method: ${className}.${methodName}()`); - - for (const methodName of methodDiff.equal) { - const actualMethod = actualClass.methods.get(methodName); - const expectedMethod = expectedClass.methods.get(methodName); - const hasActualType = actualMethod.type && actualMethod.type.name !== 'void'; - const hasExpectedType = expectedMethod.type && expectedMethod.type.name !== 'void'; - if (hasActualType !== hasExpectedType) { - if (hasActualType) - errors.push(`Method ${className}.${methodName} has unneeded description of return type: `+ actualMethod.type.name); - else if (hasExpectedType) - errors.push(`Method ${className}.${methodName} is missing return type description: ` + expectedMethod.type.name); - } else if (hasActualType) { - 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) { - const text = [`Method ${className}.${methodName}() fails to describe its parameters:`]; - for (const arg of argsDiff.missing) - text.push(`- Implemented but not documented argument: ${arg}`); - for (const arg of argsDiff.extra) - text.push(`- Documented but not implemented argument: ${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) - errors.push(`Documented but not implemented property: ${className}.${propertyName}`); - for (const propertyName of propertyDiff.missing) { - if (propertyName === 'T') - continue; - errors.push(`Implemented but not documented property: ${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(`Documented but not implemented event ${className}: '${eventName}'`); - for (const eventName of eventsDiff.missing) - errors.push(`Implemented but not documented event ${className}: '${eventName}'`); - } - - - /** - * @param {string} source - * @param {!Documentation.Member} actual - * @param {!Documentation.Member} expected - */ - function checkProperty(source, actual, expected) { - if (actual.required !== expected.required) - errors.push(`${source}: ${actual.name} should be ${expected.required ? 'required' : 'optional'}`); - checkType(source + '.' + actual.name, actual.type, expected.type); - } - - /** - * @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; - if (expected.name === 'T' || expected.name.includes('[T]')) - return; - /** @type {Parameters[]} */ - const mdReplacers = [ - [/\ /g, ''], - // We shortcut ? to null| - [/\?/g, 'null|'], - [/EvaluationArgument/g, 'Object'], - ]; - const tsReplacers = [ - [/\ /g, ''], - [/Arg/g, 'Object'], - [/ChromiumBrowserContext/g, 'BrowserContext'], - [/ElementHandle<[^>]+>/g, 'ElementHandle'], - [/Handle/g, 'JSHandle'], - [/JSHandle/g, 'JSHandle'], - [/object/g, 'Object'], - [/Promise/, 'Promise'], - [/TextendsNode\?ElementHandle:null/, 'null|ElementHandle'] - ] - let actualName = actual.name; - for (const replacer of mdReplacers) - actualName = actualName.replace(...replacer); - let expectedName = expected.name; - for (const replacer of tsReplacers) - expectedName = expectedName.replace(...replacer); - if (normalizeType(expectedName) !== normalizeType(actualName)) - errors.push(`${source} ${actualName} != ${expectedName}`); - if (actual.name === 'boolean' || actual.name === 'string') - return; - const actualPropertiesMap = new Map(actual.properties.map(property => [property.name, property])); - const expectedPropertiesMap = new Map(expected.properties.map(property => [property.name, property])); - 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) - checkProperty(source, actualPropertiesMap.get(propertyName), expectedPropertiesMap.get(propertyName)); - } - - return errors; -} - -/** - * @param {!Array} actual - * @param {!Array} expected - * @return {{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]; - } -} - -function normalizeType(type) { - let nesting = 0; - const result = []; - let word = ''; - for (const c of type) { - if (c === '<') { - ++nesting; - } else if (c === '>') { - --nesting; - } - if (c === '|' && !nesting) { - result.push(word); - word = ''; - } else { - word += c; - } - } - if (word) - result.push(word); - result.sort(); - return result.join('|'); -} diff --git a/utils/doclint/check_public_api/missingDocs.js b/utils/doclint/check_public_api/missingDocs.js new file mode 100644 index 0000000000..df338989a0 --- /dev/null +++ b/utils/doclint/check_public_api/missingDocs.js @@ -0,0 +1,175 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * 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 mdBuilder = require('./MDBuilder'); +const Message = require('../Message'); +const ts = require('typescript'); +const EventEmitter = require('events'); +const Documentation = require('./Documentation'); + +/** + * @return {!Array} + */ +module.exports = function lint(api, jsSources, apiFileName) { + const documentation = mdBuilder(api, true).documentation; + const apiMethods = listMethods(jsSources, apiFileName); + const errors = []; + for (const [className, methods] of apiMethods) { + const docClass = documentation.classes.get(className); + if (!docClass) { + errors.push(Message.error(`Missing documentation for "${className}"`)); + continue; + } + for (const [methodName, params] of methods) { + const member = docClass.members.get(methodName); + if (!member) { + errors.push(Message.error(`Missing documentation for "${className}.${methodName}"`)); + continue; + } + const memberParams = paramsForMember(member); + for (const paramName of params) { + if (!memberParams.has(paramName)) + errors.push(Message.error(`Missing documentation for "${className}.${methodName}.${paramName}"`)); + } + } + } + for (const cls of documentation.classesArray) { + const methods = apiMethods.get(cls.name); + if (!methods) { + errors.push(Message.error(`Documented "${cls.name}" not found in sources`)); + continue; + } + for (const member of cls.membersArray) { + if (member.kind === 'event') + continue; + const params = methods.get(member.name); + if (!params) { + errors.push(Message.error(`Documented "${cls.name}.${member.name}" not found is sources`)); + continue; + } + const memberParams = paramsForMember(member); + for (const paramName of memberParams) { + if (!params.has(paramName)) + errors.push(Message.error(`Documented "${cls.name}.${member.name}.${paramName}" not found is sources`)); + } + } + } + return errors; +}; + +/** + * @param {!Documentation.Member} member + */ +function paramsForMember(member) { + if (member.kind !== 'method') + return []; + const paramNames = new Set(member.argsArray.map(a => a.name)); + if (member.options) + paramNames.add('options'); + return paramNames; +} + +/** + * @param {!Array} sources + */ +function listMethods(sources, apiFileName) { + const program = ts.createProgram({ + options: { + allowJs: true, + target: ts.ScriptTarget.ESNext, + strict: true + }, + rootNames: sources.map(source => source.filePath()) + }); + const checker = program.getTypeChecker(); + const apiClassNames = new Set(); + const apiMethods = new Map(); + const apiSource = program.getSourceFiles().find(f => f.fileName === apiFileName); + + /** + * @param {ts.Type} type + */ + function signatureForType(type) { + const signatures = type.getCallSignatures(); + if (signatures.length) + return signatures[signatures.length - 1]; + if (type.isUnion()) { + const innerTypes = type.types.filter(t => !(t.flags & ts.TypeFlags.Undefined)); + if (innerTypes.length === 1) + return signatureForType(innerTypes[0]); + } + return null; + } + + /** + * @param {string} className + * @param {!ts.Type} classType + */ + function visitClass(className, classType) { + let methods = apiMethods.get(className); + if (!methods) { + methods = new Map(); + apiMethods.set(className, methods); + } + for (const [name, member] of classType.symbol.members || []) { + if (name.startsWith('_') || name === 'T' || name === 'toString') + continue; + if (EventEmitter.prototype.hasOwnProperty(name)) + continue; + const memberType = checker.getTypeOfSymbolAtLocation(member, member.valueDeclaration); + const signature = signatureForType(memberType); + if (signature) + methods.set(name, new Set(signature.parameters.map(p => p.escapedName))); + else + methods.set(name, new Set()); + } + for (const baseType of classType.getBaseTypes() || []) { + const baseTypeName = baseType.symbol ? baseType.symbol.name : ''; + if (apiClassNames.has(baseTypeName)) + visitClass(className, baseType); + } + } + + /** + * @param {!ts.Node} node + */ + function visitMethods(node) { + if (ts.isExportSpecifier(node)) { + const className = node.name.text; + const exportSymbol = node.name ? checker.getSymbolAtLocation(node.name) : node.symbol; + const classType = checker.getDeclaredTypeOfSymbol(exportSymbol); + if (!classType) + throw new Error(`Cannot parse class "${className}"`); + visitClass(className, classType); + } + ts.forEachChild(node, visitMethods); + } + + /** + * @param {!ts.Node} node + */ + function visitNames(node) { + if (ts.isExportSpecifier(node)) + apiClassNames.add(node.name.text); + ts.forEachChild(node, visitNames); + } + + visitNames(apiSource); + visitMethods(apiSource); + + return apiMethods; +} diff --git a/utils/doclint/check_public_api/test/.gitignore b/utils/doclint/check_public_api/test/.gitignore deleted file mode 100644 index 17526b1162..0000000000 --- a/utils/doclint/check_public_api/test/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -result-actual.txt -result-diff.html diff --git a/utils/doclint/check_public_api/test/check-duplicates/api.ts b/utils/doclint/check_public_api/test/check-duplicates/api.ts deleted file mode 100644 index 66b2b7d80f..0000000000 --- a/utils/doclint/check_public_api/test/check-duplicates/api.ts +++ /dev/null @@ -1,13 +0,0 @@ -class Foo { - test() { - } - - title(arg: number) { - } -} - -class Bar { -} - -export {Bar}; -export {Foo}; \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/check-duplicates/doc.md b/utils/doclint/check_public_api/test/check-duplicates/doc.md deleted file mode 100644 index dc182a3553..0000000000 --- a/utils/doclint/check_public_api/test/check-duplicates/doc.md +++ /dev/null @@ -1,17 +0,0 @@ -# class: Bar - -# class: Foo - -## method: Foo.test - -## method: Foo.test - -## method: Foo.title - -### param: Foo.title.arg -- `arg` <[number]> - -### param: Foo.title.arg -- `arg` <[number]> - -# class: Bar diff --git a/utils/doclint/check_public_api/test/check-duplicates/result.txt b/utils/doclint/check_public_api/test/check-duplicates/result.txt deleted file mode 100644 index d9994416e9..0000000000 --- a/utils/doclint/check_public_api/test/check-duplicates/result.txt +++ /dev/null @@ -1,3 +0,0 @@ -[MarkDown] Duplicate declaration of method Foo.test() -[MarkDown] Duplicate declaration of argument Foo.title "arg" -[MarkDown] Duplicate declaration of class Bar \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/check-nullish/api.ts b/utils/doclint/check_public_api/test/check-nullish/api.ts deleted file mode 100644 index c771dccfea..0000000000 --- a/utils/doclint/check_public_api/test/check-nullish/api.ts +++ /dev/null @@ -1,19 +0,0 @@ -class Foo { - bar(options?: {x?: number, y?: number, maybe?: number, nullable?: string|null, object?: {one: number, two?: number}}) { - - } - - async goBack() : Promise { - return null; - } - - response(): Response | null { - return null; - } - - baz(): {abc: number, def?: number, ghi: string} | null { - return null; - } -} - -export {Foo}; \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/check-nullish/doc.md b/utils/doclint/check_public_api/test/check-nullish/doc.md deleted file mode 100644 index 6b1c3c4a56..0000000000 --- a/utils/doclint/check_public_api/test/check-nullish/doc.md +++ /dev/null @@ -1,33 +0,0 @@ -# class: Foo - -## method: Foo.bar - -### option: Foo.bar.x -- `x` <[number]> - -### option: Foo.bar.y -- `y` <[number]> - -### option: Foo.bar.nullable -- `nullable` - -### option: Foo.bar.maybe -- `maybe` <[number]> - -### option: Foo.bar.object -- `object` <[Object]> - - `one` <[number]> - - `two` <[number]> defaults to `2`. - -## method: Foo.baz -- returns: - - `abc` <[number]> - - `def` <[number]> if applicable. - - `ghi` <[string]> - -## method: Foo.goBack -- returns: <[Promise]> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. If -can not go back, resolves to `null`. - -## method: Foo.response -- returns: A matching [Response] object, or `null` if the response has not been received yet. diff --git a/utils/doclint/check_public_api/test/check-nullish/result.txt b/utils/doclint/check_public_api/test/check-nullish/result.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/utils/doclint/check_public_api/test/check-returns/api.ts b/utils/doclint/check_public_api/test/check-returns/api.ts deleted file mode 100644 index 502d2a1c9c..0000000000 --- a/utils/doclint/check_public_api/test/check-returns/api.ts +++ /dev/null @@ -1,21 +0,0 @@ -class Foo { - return42() { - return 42; - } - - returnNothing() { - let e = () => { - return 10; - } - e(); - } - - www() : string { - return 'df'; - } - - async asyncFunction() { - } -} - -export {Foo}; \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/check-returns/doc.md b/utils/doclint/check_public_api/test/check-returns/doc.md deleted file mode 100644 index 74cf3c5b84..0000000000 --- a/utils/doclint/check_public_api/test/check-returns/doc.md +++ /dev/null @@ -1,11 +0,0 @@ -# class: Foo - -## async method: Foo.asyncFunction - -## method: Foo.return42 - -## method: Foo.returnNothing -- returns: <[number]> - -## method: Foo.www -- returns: <[string]> diff --git a/utils/doclint/check_public_api/test/check-returns/result.txt b/utils/doclint/check_public_api/test/check-returns/result.txt deleted file mode 100644 index 2af6e82b0a..0000000000 --- a/utils/doclint/check_public_api/test/check-returns/result.txt +++ /dev/null @@ -1,2 +0,0 @@ -[MarkDown] Method Foo.return42 is missing return type description: number -[MarkDown] Method Foo.returnNothing has unneeded description of return type: number \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/check-sorting/api.ts b/utils/doclint/check_public_api/test/check-sorting/api.ts deleted file mode 100644 index 8bd0913171..0000000000 --- a/utils/doclint/check_public_api/test/check-sorting/api.ts +++ /dev/null @@ -1,11 +0,0 @@ -class Foo { - ddd = 10; - - aaa() {} - - bbb() {} - - ccc() {} -} - -export {Foo}; \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/check-sorting/doc.md b/utils/doclint/check_public_api/test/check-sorting/doc.md deleted file mode 100644 index 0f07761206..0000000000 --- a/utils/doclint/check_public_api/test/check-sorting/doc.md +++ /dev/null @@ -1,15 +0,0 @@ -# class: Foo - -## event: Foo.c - -## event: Foo.a - -## method: Foo.aaa - -## event: Foo.b - -## property: Foo.ddd - -## method: Foo.ccc - -## method: Foo.bbb diff --git a/utils/doclint/check_public_api/test/check-sorting/events.js b/utils/doclint/check_public_api/test/check-sorting/events.js deleted file mode 100644 index 55f20be064..0000000000 --- a/utils/doclint/check_public_api/test/check-sorting/events.js +++ /dev/null @@ -1,8 +0,0 @@ -const Events = { - Foo: { - a: 'a', - b: 'b', - c: 'c', - }, -}; -module.exports = {Events}; diff --git a/utils/doclint/check_public_api/test/check-sorting/result.txt b/utils/doclint/check_public_api/test/check-sorting/result.txt deleted file mode 100644 index fee39688e6..0000000000 --- a/utils/doclint/check_public_api/test/check-sorting/result.txt +++ /dev/null @@ -1,4 +0,0 @@ -[MarkDown] Events should go first. Event 'b' in class Foo breaks order -[MarkDown] Event 'c' in class Foo breaks alphabetic ordering of events -[MarkDown] Bad alphabetic ordering of Foo members: Foo.ddd should go after Foo.ccc() -[MarkDown] Bad alphabetic ordering of Foo members: Foo.ccc() should go after Foo.bbb() \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/diff-arguments/api.ts b/utils/doclint/check_public_api/test/diff-arguments/api.ts deleted file mode 100644 index 1474a6a9d9..0000000000 --- a/utils/doclint/check_public_api/test/diff-arguments/api.ts +++ /dev/null @@ -1,11 +0,0 @@ -class Foo { - foo(arg1: string, arg3 = {}) { - } - - test(filePaths : string[]) { - } - - bar(options?: {visibility?: boolean}) { - } -} -export {Foo}; \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/diff-arguments/doc.md b/utils/doclint/check_public_api/test/diff-arguments/doc.md deleted file mode 100644 index 8176ef07e2..0000000000 --- a/utils/doclint/check_public_api/test/diff-arguments/doc.md +++ /dev/null @@ -1,19 +0,0 @@ -# class: Foo - -## method: Foo.bar - -### option: Foo.bar.visibility -- `visibility` <[boolean]> - -## method: Foo.foo - -### param: Foo.foo.arg1 -- `arg1` <[string]> - -### param: Foo.foo.arg2 -- `arg2` <[string]> - -## method: Foo.test - -### param: Foo.test.filePaths -- `filePaths` <[Array]<[string]>> diff --git a/utils/doclint/check_public_api/test/diff-arguments/result.txt b/utils/doclint/check_public_api/test/diff-arguments/result.txt deleted file mode 100644 index 2d789b55f7..0000000000 --- a/utils/doclint/check_public_api/test/diff-arguments/result.txt +++ /dev/null @@ -1,3 +0,0 @@ -[MarkDown] Method Foo.foo() fails to describe its parameters: -- Implemented but not documented argument: arg3 -- Documented but not implemented argument: arg2 \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/diff-classes/api.ts b/utils/doclint/check_public_api/test/diff-classes/api.ts deleted file mode 100644 index 89af0e7961..0000000000 --- a/utils/doclint/check_public_api/test/diff-classes/api.ts +++ /dev/null @@ -1,2 +0,0 @@ -export {Foo} from './foo'; -export {Other} from './other'; diff --git a/utils/doclint/check_public_api/test/diff-classes/doc.md b/utils/doclint/check_public_api/test/diff-classes/doc.md deleted file mode 100644 index 98fdcac4cd..0000000000 --- a/utils/doclint/check_public_api/test/diff-classes/doc.md +++ /dev/null @@ -1,5 +0,0 @@ -# class: Foo - -# class: Bar - -# class: Baz diff --git a/utils/doclint/check_public_api/test/diff-classes/foo.ts b/utils/doclint/check_public_api/test/diff-classes/foo.ts deleted file mode 100644 index 08c51dd90d..0000000000 --- a/utils/doclint/check_public_api/test/diff-classes/foo.ts +++ /dev/null @@ -1,2 +0,0 @@ -export class Foo { -} diff --git a/utils/doclint/check_public_api/test/diff-classes/other.ts b/utils/doclint/check_public_api/test/diff-classes/other.ts deleted file mode 100644 index 57e436cf88..0000000000 --- a/utils/doclint/check_public_api/test/diff-classes/other.ts +++ /dev/null @@ -1,2 +0,0 @@ -export class Other { -} diff --git a/utils/doclint/check_public_api/test/diff-classes/result.txt b/utils/doclint/check_public_api/test/diff-classes/result.txt deleted file mode 100644 index 37e0cbf023..0000000000 --- a/utils/doclint/check_public_api/test/diff-classes/result.txt +++ /dev/null @@ -1,3 +0,0 @@ -[MarkDown] Documented but not implemented class: Bar -[MarkDown] Documented but not implemented class: Baz -[MarkDown] Implemented but not documented class: Other \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/diff-events/api.ts b/utils/doclint/check_public_api/test/diff-events/api.ts deleted file mode 100644 index 8a15f23400..0000000000 --- a/utils/doclint/check_public_api/test/diff-events/api.ts +++ /dev/null @@ -1,4 +0,0 @@ -class Foo { -} - -export {Foo}; \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/diff-events/doc.md b/utils/doclint/check_public_api/test/diff-events/doc.md deleted file mode 100644 index f461f99f8a..0000000000 --- a/utils/doclint/check_public_api/test/diff-events/doc.md +++ /dev/null @@ -1,5 +0,0 @@ -# class: Foo - -## event: Foo.start - -## event: Foo.stop diff --git a/utils/doclint/check_public_api/test/diff-events/events.js b/utils/doclint/check_public_api/test/diff-events/events.js deleted file mode 100644 index 7ef8bcdfdb..0000000000 --- a/utils/doclint/check_public_api/test/diff-events/events.js +++ /dev/null @@ -1,8 +0,0 @@ -const Events = { - Foo: { - Start: 'start', - Finish: 'finish', - }, -}; - -module.exports = {Events}; diff --git a/utils/doclint/check_public_api/test/diff-events/result.txt b/utils/doclint/check_public_api/test/diff-events/result.txt deleted file mode 100644 index b132fcd333..0000000000 --- a/utils/doclint/check_public_api/test/diff-events/result.txt +++ /dev/null @@ -1,2 +0,0 @@ -[MarkDown] Documented but not implemented event Foo: 'stop' -[MarkDown] Implemented but not documented event Foo: 'finish' \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/diff-methods/api.ts b/utils/doclint/check_public_api/test/diff-methods/api.ts deleted file mode 100644 index 3142fc70d2..0000000000 --- a/utils/doclint/check_public_api/test/diff-methods/api.ts +++ /dev/null @@ -1,18 +0,0 @@ -class Foo { - start() { - } - - stop() { - } - - get zzz() { - } - - $() { - } - - money$$money() { - } -} - -export {Foo}; \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/diff-methods/doc.md b/utils/doclint/check_public_api/test/diff-methods/doc.md deleted file mode 100644 index a41355af9d..0000000000 --- a/utils/doclint/check_public_api/test/diff-methods/doc.md +++ /dev/null @@ -1,10 +0,0 @@ -# class: Foo - -## method: Foo.$ - -## method: Foo.money$$money - -## method: Foo.proceed - -## method: Foo.start - diff --git a/utils/doclint/check_public_api/test/diff-methods/result.txt b/utils/doclint/check_public_api/test/diff-methods/result.txt deleted file mode 100644 index 3f1ef2fea6..0000000000 --- a/utils/doclint/check_public_api/test/diff-methods/result.txt +++ /dev/null @@ -1,3 +0,0 @@ -[MarkDown] Documented but not implemented method: Foo.proceed() -[MarkDown] Implemented but not documented method: Foo.stop() -[MarkDown] Implemented but not documented property: Foo.zzz \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/diff-properties/api.ts b/utils/doclint/check_public_api/test/diff-properties/api.ts deleted file mode 100644 index 40b7cecf89..0000000000 --- a/utils/doclint/check_public_api/test/diff-properties/api.ts +++ /dev/null @@ -1,5 +0,0 @@ -class Foo { - a = 42; - b = 'hello'; -} -export {Foo}; \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/diff-properties/doc.md b/utils/doclint/check_public_api/test/diff-properties/doc.md deleted file mode 100644 index e355b26f5a..0000000000 --- a/utils/doclint/check_public_api/test/diff-properties/doc.md +++ /dev/null @@ -1,5 +0,0 @@ -# class: Foo - -## property: Foo.a - -## property: Foo.c diff --git a/utils/doclint/check_public_api/test/diff-properties/result.txt b/utils/doclint/check_public_api/test/diff-properties/result.txt deleted file mode 100644 index 1c3039dd53..0000000000 --- a/utils/doclint/check_public_api/test/diff-properties/result.txt +++ /dev/null @@ -1,2 +0,0 @@ -[MarkDown] Documented but not implemented property: Foo.c -[MarkDown] Implemented but not documented property: Foo.b \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/js-builder-common/api.ts b/utils/doclint/check_public_api/test/js-builder-common/api.ts deleted file mode 100644 index 366471820f..0000000000 --- a/utils/doclint/check_public_api/test/js-builder-common/api.ts +++ /dev/null @@ -1,15 +0,0 @@ -class A { - property1 = 1; - _property2 = 2; - constructor(delegate) { - } - - get getter() : any { - return null; - } - - async method(foo, bar) { - } -} - -export {A}; \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/js-builder-common/events.js b/utils/doclint/check_public_api/test/js-builder-common/events.js deleted file mode 100644 index da44ad81a1..0000000000 --- a/utils/doclint/check_public_api/test/js-builder-common/events.js +++ /dev/null @@ -1,6 +0,0 @@ -const Events = { - A: { - AnEvent: 'anevent' - }, -}; -module.exports = { Events }; diff --git a/utils/doclint/check_public_api/test/js-builder-common/result.txt b/utils/doclint/check_public_api/test/js-builder-common/result.txt deleted file mode 100644 index 09856293db..0000000000 --- a/utils/doclint/check_public_api/test/js-builder-common/result.txt +++ /dev/null @@ -1,50 +0,0 @@ -{ - "classes": [ - { - "name": "A", - "members": [ - { - "name": "anevent", - "kind": "event" - }, - { - "name": "property1", - "type": { - "name": "number" - }, - "kind": "property" - }, - { - "name": "getter", - "type": { - "name": "Object" - }, - "kind": "property" - }, - { - "name": "method", - "type": { - "name": "Promise" - }, - "kind": "method", - "args": [ - { - "name": "foo", - "type": { - "name": "Object" - }, - "kind": "property" - }, - { - "name": "bar", - "type": { - "name": "Object" - }, - "kind": "property" - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/js-builder-inheritance/api.ts b/utils/doclint/check_public_api/test/js-builder-inheritance/api.ts deleted file mode 100644 index 914588a0f7..0000000000 --- a/utils/doclint/check_public_api/test/js-builder-inheritance/api.ts +++ /dev/null @@ -1,18 +0,0 @@ -class A { - constructor() { - } - - foo(a) { - } - - bar() { - } -} - -class B extends A { - bar(override) { - } -} - -export {A}; -export {B}; \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/js-builder-inheritance/events.js b/utils/doclint/check_public_api/test/js-builder-inheritance/events.js deleted file mode 100644 index f22e6aadc4..0000000000 --- a/utils/doclint/check_public_api/test/js-builder-inheritance/events.js +++ /dev/null @@ -1,8 +0,0 @@ -const Events = { - B: { - // Event with the same name as a super class method. - foo: 'foo', - }, -}; - -module.exports = {Events}; diff --git a/utils/doclint/check_public_api/test/js-builder-inheritance/result.txt b/utils/doclint/check_public_api/test/js-builder-inheritance/result.txt deleted file mode 100644 index 0975d03ab3..0000000000 --- a/utils/doclint/check_public_api/test/js-builder-inheritance/result.txt +++ /dev/null @@ -1,61 +0,0 @@ -{ - "classes": [ - { - "name": "A", - "members": [ - { - "name": "foo", - "kind": "method", - "args": [ - { - "name": "a", - "type": { - "name": "Object" - }, - "kind": "property" - } - ] - }, - { - "name": "bar", - "kind": "method" - } - ] - }, - { - "name": "B", - "members": [ - { - "name": "foo", - "kind": "event" - }, - { - "name": "bar", - "kind": "method", - "args": [ - { - "name": "override", - "type": { - "name": "Object" - }, - "kind": "property" - } - ] - }, - { - "name": "foo", - "kind": "method", - "args": [ - { - "name": "a", - "type": { - "name": "Object" - }, - "kind": "property" - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/md-builder-comments/doc.md b/utils/doclint/check_public_api/test/md-builder-comments/doc.md deleted file mode 100644 index f8f3d0381e..0000000000 --- a/utils/doclint/check_public_api/test/md-builder-comments/doc.md +++ /dev/null @@ -1,18 +0,0 @@ -# class: Foo - -## method: Foo.method -- returns: <[Promise]<[ElementHandle]>> - -The method does something. - -### param: Foo.method.arg1 -- `arg1` <[string]> - -A single line argument comment - -### param: Foo.method.arg2 -- `arg2` <[string]> - -A multiline argument comment: -* it could be this -* or it could be that diff --git a/utils/doclint/check_public_api/test/md-builder-comments/result.txt b/utils/doclint/check_public_api/test/md-builder-comments/result.txt deleted file mode 100644 index 4a4a546efc..0000000000 --- a/utils/doclint/check_public_api/test/md-builder-comments/result.txt +++ /dev/null @@ -1,35 +0,0 @@ -{ - "classes": [ - { - "name": "Foo", - "members": [ - { - "name": "method", - "type": { - "name": "Promise" - }, - "kind": "method", - "comment": "The method does something.", - "args": [ - { - "name": "arg1", - "type": { - "name": "string" - }, - "kind": "property", - "comment": "A single line argument comment" - }, - { - "name": "arg2", - "type": { - "name": "string" - }, - "kind": "property", - "comment": "A multiline argument comment:\n- it could be this\n- or it could be that" - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/md-builder-common/doc.md b/utils/doclint/check_public_api/test/md-builder-common/doc.md deleted file mode 100644 index 2ab67c7329..0000000000 --- a/utils/doclint/check_public_api/test/md-builder-common/doc.md +++ /dev/null @@ -1,23 +0,0 @@ -# class: Foo - -This is a class. - -## event: Foo.frame -- type: <[Frame]> - -This event is dispatched. - -## method: Foo.$ -- returns: <[Promise]<[ElementHandle]>> - -The method runs document.querySelector. - -### param: Foo.$.selector -- `selector` <[string]> - -A selector to query page for - -## property: Foo.url -- type: <[string]> - -Contains the URL of the request. diff --git a/utils/doclint/check_public_api/test/md-builder-common/result.txt b/utils/doclint/check_public_api/test/md-builder-common/result.txt deleted file mode 100644 index d1e50f6185..0000000000 --- a/utils/doclint/check_public_api/test/md-builder-common/result.txt +++ /dev/null @@ -1,44 +0,0 @@ -{ - "classes": [ - { - "name": "Foo", - "comment": "This is a class.", - "members": [ - { - "name": "frame", - "type": { - "name": "Frame" - }, - "kind": "event", - "comment": "This event is dispatched." - }, - { - "name": "$", - "type": { - "name": "Promise" - }, - "kind": "method", - "comment": "The method runs document.querySelector.", - "args": [ - { - "name": "selector", - "type": { - "name": "string" - }, - "kind": "property", - "comment": "A selector to query page for" - } - ] - }, - { - "name": "url", - "type": { - "name": "string" - }, - "kind": "property", - "comment": "Contains the URL of the request." - } - ] - } - ] -} \ No newline at end of file diff --git a/utils/doclint/check_public_api/test/test-api-class.ts b/utils/doclint/check_public_api/test/test-api-class.ts new file mode 100644 index 0000000000..65295e2bb5 --- /dev/null +++ b/utils/doclint/check_public_api/test/test-api-class.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * 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. + */ + +export class Exists { + exists(exists: boolean) { + return true; + } + + exists2(extra: boolean, options: {}) { + return true; + } + + extra() { + return false; + } +} + +export class Extra { + exists() { + return true; + } +} diff --git a/utils/doclint/check_public_api/test/test-api.md b/utils/doclint/check_public_api/test/test-api.md new file mode 100644 index 0000000000..4b607f88af --- /dev/null +++ b/utils/doclint/check_public_api/test/test-api.md @@ -0,0 +1,20 @@ +# class: Exists + +## method: Exists.exists + +### param: Exists.exists.exists +- `exists` <[boolean]> + +### param: Exists.exists.doesNotExist +- `doesNotExist` <[boolean]> + +### option: Exists.exists.option +- `option` <[number]> + +## method: Exists.exists2 + +## method: Exists.doesNotExist + +# class: DoesNotExist + +## method: DoesNotExist.doesNotExist diff --git a/utils/doclint/check_public_api/test/test-api.ts b/utils/doclint/check_public_api/test/test-api.ts new file mode 100644 index 0000000000..ff0fc4ca07 --- /dev/null +++ b/utils/doclint/check_public_api/test/test-api.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * 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. + */ + +export { Exists } from './test-api-class'; +export { Extra } from './test-api-class'; diff --git a/utils/doclint/check_public_api/test/test.js b/utils/doclint/check_public_api/test/test.js deleted file mode 100644 index 5040bb2305..0000000000 --- a/utils/doclint/check_public_api/test/test.js +++ /dev/null @@ -1,111 +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 fs = require('fs'); -const path = require('path'); -const checkPublicAPI = require('..'); -const Source = require('../../Source'); -const mdBuilder = require('../MDBuilder'); -const jsBuilder = require('../JSBuilder'); -const { folio } = require('folio'); -const { parseMd } = require('../../../parse_md'); - -const fixtures = folio.extend(); -const { describe, it, expect } = fixtures.build(); - -describe('checkPublicAPI', function() { - testLint('diff-classes'); - testLint('diff-methods'); - testLint('diff-properties'); - testLint('diff-arguments'); - testLint('diff-events'); - testLint('check-duplicates'); - testLint('check-sorting'); - testLint('check-returns'); - testLint('check-nullish'); - testJSBuilder('js-builder-common'); - testJSBuilder('js-builder-inheritance'); - testMDBuilder('md-builder-common'); - testMDBuilder('md-builder-comments'); -}); - -async function testLint(name) { - it(name, async({}) => { - const dirPath = path.join(__dirname, name); - const api = parseMd(fs.readFileSync(path.join(dirPath, 'doc.md')).toString()); - const tsSources = await Source.readdir(dirPath, '.ts'); - const jsSources = await Source.readdir(dirPath, '.js'); - const messages = await checkPublicAPI(api, jsSources.concat(tsSources)); - const errors = messages.map(message => message.text); - expect(errors.join('\n')).toBe(fs.readFileSync(path.join(dirPath, 'result.txt')).toString()); - }); -} - -async function testMDBuilder(name) { - it(name, ({}) => { - const dirPath = path.join(__dirname, name); - const api = parseMd(fs.readFileSync(path.join(dirPath, 'doc.md')).toString()); - const {documentation} = mdBuilder(api, true); - expect(serialize(documentation)).toBe(fs.readFileSync(path.join(dirPath, 'result.txt')).toString()); - }); -} - -async function testJSBuilder(name) { - it(name, async() => { - const dirPath = path.join(__dirname, name); - const jsSources = await Source.readdir(dirPath, '.js'); - const tsSources = await Source.readdir(dirPath, '.ts'); - const {documentation} = await jsBuilder.checkSources(jsSources.concat(tsSources)); - expect(serialize(documentation)).toBe(fs.readFileSync(path.join(dirPath, 'result.txt')).toString()); - }); -} - -/** - * @param {import('../Documentation')} doc - */ -function serialize(doc) { - const result = { - classes: doc.classesArray.map(cls => ({ - name: cls.name, - comment: cls.comment || undefined, - members: cls.membersArray.map(serializeMember) - })) - }; - return JSON.stringify(result, null, 2); -} -/** - * @param {import('../Documentation').Member} member - */ -function serializeMember(member) { - return { - name: member.name, - type: serializeType(member.type), - kind: member.kind, - comment: member.comment || undefined, - args: member.argsArray.length ? member.argsArray.map(serializeMember) : undefined - } -} -/** - * @param {import('../Documentation').Type} type - */ -function serializeType(type) { - if (!type) - return undefined; - return { - name: type.name, - properties: type.properties.length ? type.properties.map(serializeMember) : undefined - } -} diff --git a/utils/doclint/check_public_api/test/testMissingDocs.js b/utils/doclint/check_public_api/test/testMissingDocs.js new file mode 100644 index 0000000000..e4c8ac90b7 --- /dev/null +++ b/utils/doclint/check_public_api/test/testMissingDocs.js @@ -0,0 +1,45 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * 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 fs = require('fs'); +const path = require('path'); +const missingDocs = require('../missingDocs'); +const Source = require('../../Source'); +const { folio } = require('folio'); +const { parseMd } = require('../../../parse_md'); + +const { test, expect } = folio; + +test('missing docs', async ({}) => { + const api = parseMd(fs.readFileSync(path.join(__dirname, 'test-api.md')).toString()); + const tsSources = [ + await Source.readFile(path.join(__dirname, 'test-api.ts')), + await Source.readFile(path.join(__dirname, 'test-api-class.ts')), + ]; + const messages = missingDocs(api, tsSources, path.join(__dirname, 'test-api.ts')); + const errors = messages.map(message => message.text); + expect(errors).toEqual([ + 'Missing documentation for "Exists.exists2.extra"', + 'Missing documentation for "Exists.exists2.options"', + 'Missing documentation for "Exists.extra"', + 'Missing documentation for "Extra"', + 'Documented "Exists.exists.doesNotExist" not found is sources', + 'Documented "Exists.exists.options" not found is sources', + 'Documented "Exists.doesNotExist" not found is sources', + 'Documented "DoesNotExist" not found in sources', + ]); +}); diff --git a/utils/doclint/cli.js b/utils/doclint/cli.js index f6f52f1c77..cb136b10e6 100755 --- a/utils/doclint/cli.js +++ b/utils/doclint/cli.js @@ -120,7 +120,7 @@ async function run() { result.push({ type: 'text', text: links - }); + }); api.setText([comment, header, renderMd(result, 10000), footer].join('\n')); } } @@ -139,10 +139,9 @@ async function run() { for (const source of mdSources.filter(source => source.hasUpdatedText())) messages.push(Message.warning(`WARN: updated ${source.projectPath()}`)); - const checkPublicAPI = require('./check_public_api'); - const jsSources = await Source.readdir(path.join(PROJECT_DIR, 'src', 'client'), '', []); - messages.push(...checkPublicAPI(apiSpec, jsSources)); + const missingDocs = require('./check_public_api/missingDocs.js'); + messages.push(...missingDocs(apiSpec, jsSources, path.join(PROJECT_DIR, 'src', 'client', 'api.ts'))); for (const source of mdSources) { if (!source.hasUpdatedText()) diff --git a/utils/generate_types/index.js b/utils/generate_types/index.js index f618d95b86..2e71c06841 100644 --- a/utils/generate_types/index.js +++ b/utils/generate_types/index.js @@ -16,7 +16,6 @@ //@ts-check const path = require('path'); -const Source = require('../doclint/Source'); const {devices} = require('../..'); const Documentation = require('../doclint/check_public_api/Documentation'); const PROJECT_DIR = path.join(__dirname, '..', '..'); @@ -40,10 +39,14 @@ let hadChanges = false; const apiBody = parseMd(fs.readFileSync(path.join(PROJECT_DIR, 'docs-src', 'api-body.md')).toString()); const apiParams = parseMd(fs.readFileSync(path.join(PROJECT_DIR, 'docs-src', 'api-params.md')).toString()); const api = applyTemplates(apiBody, apiParams); - const {documentation: mdDocumentation} = require('../doclint/check_public_api/MDBuilder')(api, true); - const sources = await Source.readdir(path.join(PROJECT_DIR, 'src', 'client'), '', []); - const {documentation: jsDocumentation} = await require('../doclint/check_public_api/JSBuilder').checkSources(sources); - documentation = mergeDocumentation(mdDocumentation, jsDocumentation); + const mdResult = require('../doclint/check_public_api/MDBuilder')(api, true); + documentation = mdResult.documentation; + + // Root module types are overridden. + const playwrightClass = documentation.classes.get('Playwright'); + documentation.classes.delete('Playwright'); + documentation.classesArray.splice(documentation.classesArray.indexOf(playwrightClass), 1); + const handledClasses = new Set(); function docClassForName(name) { @@ -124,8 +127,6 @@ function classToString(classDesc) { if (classDesc.comment) { parts.push(writeComment(classDesc.comment)) } - if (classDesc.templates.length) - console.error(`expected an override for "${classDesc.name}" becasue it is templated`); parts.push(`export interface ${classDesc.name} ${classDesc.extends ? `extends ${classDesc.extends} ` : ''}{`); parts.push(classBody(classDesc)); parts.push('}\n'); @@ -210,8 +211,6 @@ function classBody(classDesc) { // do this late, because we still want object definitions for overridden types if (!hasOwnMethod(classDesc, member.name)) return ''; - if (member.templates.length) - console.error(`expected an override for "${classDesc.name}.${member.name}" because it is templated`); return `${jsdoc}${member.name}${args}: ${type};` }).filter(x => x).join('\n\n')); return parts.join('\n'); @@ -414,27 +413,6 @@ function memberJSDOC(member, indent) { return writeComment(lines.join('\n'), indent) + '\n' + indent; } -/** - * @param {Documentation} mdDoc - * @param {Documentation} jsDoc - * @return {Documentation} - */ -function mergeDocumentation(mdDoc, jsDoc) { - const classes = []; - for (const mdClass of mdDoc.classesArray) { - const jsClass = jsDoc.classes.get(mdClass.name); - if (!jsClass) - classes.push(mdClass); - else - classes.push(mergeClasses(mdClass, jsClass)); - } - // Root module types are overridden. - const c = mdDoc.classes.get('Playwright'); - mdDoc.classes.delete('Playwright'); - mdDoc.classesArray.splice(mdDoc.classesArray.indexOf(c), 1); - return mdDoc; -} - /** * @param {Documentation.Class} mdClass * @param {Documentation.Class} jsClass diff --git a/utils/watch.js b/utils/watch.js index f6ad51b6a8..fdff62caf7 100644 --- a/utils/watch.js +++ b/utils/watch.js @@ -26,7 +26,8 @@ process.on('exit', () => spawns.forEach(s => s.kill())); runOnChanges(['src/protocol/protocol.yml'], 'utils/generate_channels.js'); runOnChanges([ - 'docs/api.md', + 'docs-src/api-body.md', + 'docs-src/api-params.md', 'utils/generate_types/overrides.d.ts', 'utils/generate_types/exported.json', 'src/server/chromium/protocol.ts',