From d3b2f515139f17ac6c3be9f361348d464a0d6a48 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 11 May 2026 13:49:49 +0100 Subject: [PATCH] chore: remove missingDocs This is a remnant of the times when Playwright lacked some test coverage. --- .claude/skills/playwright-dev/library.md | 4 - packages/playwright-core/src/client/api.ts | 48 ------ utils/doclint/cli.js | 19 --- utils/doclint/missingDocs.js | 185 --------------------- 4 files changed, 256 deletions(-) delete mode 100644 packages/playwright-core/src/client/api.ts delete mode 100644 utils/doclint/missingDocs.js diff --git a/.claude/skills/playwright-dev/library.md b/.claude/skills/playwright-dev/library.md index 2b3846847e289..1a6fa28946cef 100644 --- a/.claude/skills/playwright-dev/library.md +++ b/.claude/skills/playwright-dev/library.md @@ -145,10 +145,6 @@ Manages the client-server transport: | `Locator` | `locator.ts` | Delegates to `Frame` methods with selector + `strict: true` | | `ElementHandle` | `elementHandle.ts` | DOM element reference | -### Public API Exports - -`packages/playwright-core/src/client/api.ts` exports all public classes. - ## Server Layer ### SdkObject — Base Class diff --git a/packages/playwright-core/src/client/api.ts b/packages/playwright-core/src/client/api.ts deleted file mode 100644 index 3a36d03285cef..0000000000000 --- a/packages/playwright-core/src/client/api.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * 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 { Android, AndroidDevice, AndroidInput, AndroidSocket, AndroidWebView } from './android'; -export { Browser } from './browser'; -export { BrowserContext } from './browserContext'; -export type { BrowserServer } from './browserType'; -export { BrowserType } from './browserType'; -export { Clock } from './clock'; -export { ConsoleMessage } from './consoleMessage'; -export { Coverage } from './coverage'; -export { Debugger } from './debugger'; -export { Dialog } from './dialog'; -export type { Disposable } from './disposable'; -export { Download } from './download'; -export { Electron, ElectronApplication } from './electron'; -export { FrameLocator, Locator } from './locator'; -export { ElementHandle } from './elementHandle'; -export { FileChooser } from './fileChooser'; -export type { Screencast } from './screencast'; -export type { Logger } from './types'; -export { TimeoutError } from './errors'; -export { Frame } from './frame'; -export { Keyboard, Mouse, Touchscreen } from './input'; -export { JSHandle } from './jsHandle'; -export { Request, Response, Route, WebSocket, WebSocketRoute } from './network'; -export { APIRequest, APIRequestContext, APIResponse } from './fetch'; -export { Page } from './page'; -export { Selectors } from './selectors'; -export { Tracing } from './tracing'; -export { Video } from './video'; -export { Worker } from './worker'; -export { CDPSession } from './cdpSession'; -export { Playwright } from './playwright'; -export { WebError } from './webError'; diff --git a/utils/doclint/cli.js b/utils/doclint/cli.js index b6d346817de21..79c4ff73630c3 100755 --- a/utils/doclint/cli.js +++ b/utils/doclint/cli.js @@ -21,7 +21,6 @@ const playwright = require('playwright-core'); const fs = require('fs'); const path = require('path'); const { parseApi } = require('./api_parser'); -const missingDocs = require('./missingDocs'); const md = require('../markdown'); const docs = require('./documentation'); const toKebabCase = require('lodash/kebabCase') @@ -269,24 +268,6 @@ async function run() { } } - // Check for missing docs - { - const apiDocumentation = parseApi(path.join(PROJECT_DIR, 'docs', 'src', 'api')) - .mergeWith(parseApi(path.join(PROJECT_DIR, 'docs', 'src', 'electron-api'), path.join(PROJECT_DIR, 'docs', 'src', 'api', 'params.md'))) - .mergeWith(parseApi(path.join(PROJECT_DIR, 'docs', 'src', 'mobile-api'), path.join(PROJECT_DIR, 'docs', 'src', 'api', 'params.md'))); - apiDocumentation.filterForLanguage('js'); - const srcClient = path.join(PROJECT_DIR, 'packages', 'playwright-core', 'src', 'client'); - const sources = fs.readdirSync(srcClient).map(n => path.join(srcClient, n)); - const errors = missingDocs(apiDocumentation, sources, path.join(srcClient, 'api.ts')); - if (errors.length) { - console.log('============================'); - console.log('ERROR: missing documentation:'); - errors.forEach(e => console.log(e)); - console.log('============================') - process.exit(1); - } - } - if (dirtyFiles.size) { console.log('============================') console.log('ERROR: generated files have changed, this is only error if happens in CI:'); diff --git a/utils/doclint/missingDocs.js b/utils/doclint/missingDocs.js deleted file mode 100644 index 856c4f02101b6..0000000000000 --- a/utils/doclint/missingDocs.js +++ /dev/null @@ -1,185 +0,0 @@ -/** - * 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 ts = require('typescript'); -const EventEmitter = require('events'); -const Documentation = require('./documentation'); -const path = require('path'); - -/** @typedef {import('../../markdown').MarkdownNode} MarkdownNode */ - -const IGNORE_CLASSES = ['PlaywrightAssertions', 'GenericAssertions', 'LocatorAssertions', 'PageAssertions', 'APIResponseAssertions', 'SnapshotAssertions']; - -module.exports = function lint(documentation, jsSources, apiFileName) { - const errors = []; - documentation.copyDocsFromSuperclasses(errors); - const apiMethods = listMethods(jsSources, apiFileName); - for (const [className, methods] of apiMethods) { - const docClass = documentation.classes.get(className); - if (!docClass) { - errors.push(`Missing documentation for "${className}"`); - continue; - } - for (const [methodName, params] of methods) { - const members = docClass.membersArray.filter(m => m.alias === methodName && m.kind !== 'event'); - if (!members.length) { - errors.push(`Missing documentation for "${className}.${methodName}"`); - continue; - } - for (const paramName of params) { - const found = members.some(member => paramsForMember(member).has(paramName)); - if (!found && !paramName.startsWith('_')) - errors.push(`Missing documentation for "${className}.${methodName}.${paramName}"`); - } - } - } - for (const cls of documentation.classesArray) { - if (IGNORE_CLASSES.includes(cls.name)) - continue; - const methods = apiMethods.get(cls.name); - if (!methods) { - errors.push(`Documented "${cls.name}" not found in sources`); - continue; - } - for (const member of cls.membersArray) { - if (member.kind === 'event' || member.alias === 'removeAllListeners') - continue; - const params = methods.get(member.alias); - if (!params) { - errors.push(`Documented "${cls.name}.${member.alias}" not found in sources`); - continue; - } - const memberParams = paramsForMember(member); - for (const paramName of memberParams) { - if (!params.has(paramName) && paramName !== 'options') - errors.push(`Documented "${cls.name}.${member.alias}.${paramName}" not found in sources`); - } - } - } - return errors; -}; - -/** - * @param {!Documentation.Member} member - */ -function paramsForMember(member) { - if (member.kind !== 'method') - return new Set(); - return new Set(member.argsArray.map(a => a.alias)); -} - -/** - * @param {string[]} rootNames - */ -function listMethods(rootNames, apiFileName) { - const program = ts.createProgram({ - options: { - allowJs: true, - target: ts.ScriptTarget.ESNext, - strict: true - }, - rootNames - }); - const checker = program.getTypeChecker(); - const apiClassNames = new Set(); - const apiMethods = new Map(); - const apiSource = program.getSourceFiles().find(f => f.fileName === apiFileName.split(path.sep).join(path.posix.sep)); - /** - * @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 {string} methodName - */ - function shouldSkipMethodByName(className, methodName) { - if (methodName.startsWith('_') || methodName === 'T') - return true; - if (methodName === 'toString' && className !== 'Locator') - return true; - if (EventEmitter.prototype.hasOwnProperty(methodName)) - return true; - return false; - } - - /** - * @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 /** @type {any[]} */(classType.symbol.members || [])) { - if (shouldSkipMethodByName(className, name)) - continue; - const memberType = checker.getTypeOfSymbolAtLocation(member, member.valueDeclaration); - const signature = signatureForType(memberType); - if (signature) - methods.set(name, new Set(signature.parameters.filter(p => !p.escapedName.startsWith('_')).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) : /** @type {any} */ (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; -}