diff --git a/.eslintrc.js b/.eslintrc.js index ac342c269b11d..dd36f69a926cb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -124,7 +124,10 @@ module.exports = { 'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended', ], + plugins: ['eslint-plugin-tsdoc'], rules: { + // Error if comments do not adhere to `tsdoc`. + 'tsdoc/syntax': 2, 'no-unused-vars': 0, '@typescript-eslint/no-unused-vars': [ 'error', diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c845a8d801fc..00531cba7ffcd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: matrix: # Include all major maintenance + active LTS + current Node.js versions. # https://github.com/nodejs/Release#release-schedule - node: [14, 16] + node: [14, 16, 18] steps: - name: Checkout uses: actions/checkout@v3 diff --git a/package.json b/package.json index 99fffcda461cc..a34ac49ba817d 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "eslint-plugin-import": "2.26.0", "eslint-plugin-mocha": "10.0.5", "eslint-plugin-prettier": "4.0.0", + "eslint-plugin-tsdoc": "0.2.16", "eslint-plugin-unicorn": "42.0.0", "esprima": "4.0.1", "expect": "25.2.7", diff --git a/scripts/ensure-correct-devtools-protocol-package.ts b/scripts/ensure-correct-devtools-protocol-package.ts index 1c94dbccbaaae..a3c072c284e11 100644 --- a/scripts/ensure-correct-devtools-protocol-package.ts +++ b/scripts/ensure-correct-devtools-protocol-package.ts @@ -27,7 +27,7 @@ * not true that each Chromium revision will have an exact matching revision * version of devtools-protocol. To ensure we're using a devtools-protocol that * is aligned with our revision, we want to find the largest package number - * that's <= the revision that Puppeteer is using. + * that's \<= the revision that Puppeteer is using. * * This script uses npm's `view` function to list all versions in a range and * find the one closest to our Chromium revision. diff --git a/src/.eslintrc.js b/src/.eslintrc.js index 4ebb9bb1ec572..05bd5e99c707a 100644 --- a/src/.eslintrc.js +++ b/src/.eslintrc.js @@ -6,7 +6,7 @@ module.exports = { * All available rules: http://eslint.org/docs/rules/ * * Rules take the following form: - * "rule-name", [severity, { opts }] + * "rule-name", [severity, \{ opts \}] * Severity: 2 == error, 1 == warning, 0 == off. */ rules: { diff --git a/src/common/Connection.ts b/src/common/Connection.ts index 6fc9d41dc086a..10b122540e162 100644 --- a/src/common/Connection.ts +++ b/src/common/Connection.ts @@ -363,12 +363,6 @@ export class CDPSession extends EventEmitter { } } -/** - * @param {!Error} error - * @param {string} method - * @param {{error: {message: string, data: any}}} object - * @returns {!Error} - */ function createProtocolError( error: ProtocolError, method: string, @@ -379,11 +373,6 @@ function createProtocolError( return rewriteError(error, message, object.error.message); } -/** - * @param {!Error} error - * @param {string} message - * @returns {!Error} - */ function rewriteError( error: ProtocolError, message: string, diff --git a/src/common/DOMWorld.ts b/src/common/DOMWorld.ts index 2d1069176d685..51de8de2c5ec4 100644 --- a/src/common/DOMWorld.ts +++ b/src/common/DOMWorld.ts @@ -335,7 +335,7 @@ export class DOMWorld { 'Cannot pass a filepath to addScriptTag in the browser environment.' ); } - const fs = await helper.importFSModule(); + const fs = await import('fs'); let contents = await fs.promises.readFile(path, 'utf8'); contents += '//# sourceURL=' + path.replace(/\n/g, ''); const context = await this.executionContext(); @@ -442,7 +442,7 @@ export class DOMWorld { 'Cannot pass a filepath to addStyleTag in the browser environment.' ); } - const fs = await helper.importFSModule(); + const fs = await import('fs'); let contents = await fs.promises.readFile(path, 'utf8'); contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/'; const context = await this.executionContext(); @@ -985,9 +985,6 @@ async function waitForPredicatePageFunction( return await pollInterval(polling); } - /** - * @returns {!Promise<*>} - */ async function pollMutation(): Promise { const success = predicateAcceptsContextElement ? await predicate(root, ...args) diff --git a/src/common/Events.ts b/src/common/Events.ts index 90d8bae785933..76bca06701986 100644 --- a/src/common/Events.ts +++ b/src/common/Events.ts @@ -24,7 +24,7 @@ * add a new Page event, you should update the PageEmittedEvents enum in * src/common/Page.ts. * - * Chat to @jackfranklin if you're unsure. + * Chat to \@jackfranklin if you're unsure. */ export const Events = { diff --git a/src/common/JSHandle.ts b/src/common/JSHandle.ts index 19dbf7b7c735d..59c9a17ef409d 100644 --- a/src/common/JSHandle.ts +++ b/src/common/JSHandle.ts @@ -829,7 +829,7 @@ export class ElementHandle< avoid paying the cost unnecessarily. */ const path = await import('path'); - const fs = await helper.importFSModule(); + const fs = await import('fs'); // Locate all files and confirm that they exist. const files = await Promise.all( filePaths.map(async (filePath) => { diff --git a/src/common/LifecycleWatcher.ts b/src/common/LifecycleWatcher.ts index a9731410a2a26..c7898c91e940f 100644 --- a/src/common/LifecycleWatcher.ts +++ b/src/common/LifecycleWatcher.ts @@ -236,11 +236,6 @@ export class LifecycleWatcher { if (this._swapped || this._newDocumentNavigation) this._newDocumentNavigationCompleteCallback(); - /** - * @param {!Frame} frame - * @param {!Array} expectedLifecycle - * @returns {boolean} - */ function checkLifecycle( frame: Frame, expectedLifecycle: ProtocolLifeCycleEvent[] diff --git a/src/common/Page.ts b/src/common/Page.ts index bb47112a244d1..c47066a5d9c8b 100644 --- a/src/common/Page.ts +++ b/src/common/Page.ts @@ -2830,8 +2830,8 @@ export class Page extends EventEmitter { 'Screenshots can only be written to a file path in a Node environment.' ); } - const fs = await helper.importFSModule(); - await fs.promises.writeFile(options.path, buffer); + const fs = (await import('fs')).promises; + await fs.writeFile(options.path, buffer); } return buffer; @@ -3416,9 +3416,9 @@ function convertPrintParameterToInches( let pixels; if (helper.isNumber(parameter)) { // Treat numbers as pixel values to be aligned with phantom's paperSize. - pixels = /** @type {number} */ parameter; + pixels = parameter; } else if (helper.isString(parameter)) { - const text = /** @type {string} */ parameter; + const text = parameter; let unit = text.substring(text.length - 2).toLowerCase(); let valueText = ''; if (unit in unitToPixels) { diff --git a/src/common/assert.ts b/src/common/assert.ts index 6a2f94f37e0e1..e1ba43f93a0c0 100644 --- a/src/common/assert.ts +++ b/src/common/assert.ts @@ -16,7 +16,7 @@ /** * Asserts that the given value is truthy. - * @param value + * @param value - some conditional statement * @param message - the error message to throw if the value is not truthy. */ export const assert: (value: unknown, message?: string) => asserts value = ( diff --git a/src/common/helper.ts b/src/common/helper.ts index ebcefbff164f5..bc57e582c87a0 100644 --- a/src/common/helper.ts +++ b/src/common/helper.ts @@ -322,18 +322,18 @@ async function getReadableAsBuffer( throw new Error('Cannot write to a path outside of Node.js environment.'); } - const fs = isNode ? await importFSModule() : null; + const fs = isNode ? (await import('fs')).promises : null; let fileHandle: import('fs').promises.FileHandle | undefined; if (path && fs) { - fileHandle = await fs.promises.open(path, 'w'); + fileHandle = await fs.open(path, 'w'); } const buffers = []; for await (const chunk of readable) { buffers.push(chunk); if (fileHandle && fs) { - await fs.promises.writeFile(fileHandle, chunk); + await fs.writeFile(fileHandle, chunk); } } @@ -376,30 +376,6 @@ async function getReadableFromProtocolStream( }); } -/** - * Loads the Node fs promises API. Needed because on Node 10.17 and below, - * fs.promises is experimental, and therefore not marked as enumerable. That - * means when TypeScript compiles an `import('fs')`, its helper doesn't spot the - * promises declaration and therefore on Node <10.17 you get an error as - * fs.promises is undefined in compiled TypeScript land. - * - * See https://github.com/puppeteer/puppeteer/issues/6548 for more details. - * - * Once Node 10 is no longer supported (April 2021) we can remove this and use - * `(await import('fs')).promises`. - */ -async function importFSModule(): Promise { - if (!isNode) { - throw new Error('Cannot load the fs module API outside of Node.'); - } - - const fs = await import('fs'); - if (fs.promises) { - return fs; - } - return fs.default; -} - export const helper = { evaluationString, pageBindingInitString, @@ -413,7 +389,6 @@ export const helper = { waitForEvent, isString, isNumber, - importFSModule, addEventListener, removeEventListeners, valueFromRemoteObject, diff --git a/src/node/BrowserRunner.ts b/src/node/BrowserRunner.ts index 2047953a9feb3..2f62068c4b104 100644 --- a/src/node/BrowserRunner.ts +++ b/src/node/BrowserRunner.ts @@ -277,9 +277,6 @@ function waitForWSEndpoint( ]; const timeoutId = timeout ? setTimeout(onTimeout, timeout) : 0; - /** - * @param {!Error=} error - */ function onClose(error?: Error): void { cleanup(); reject( diff --git a/src/node/Launcher.ts b/src/node/Launcher.ts index 971bd8a531307..fad95bdc45fe6 100644 --- a/src/node/Launcher.ts +++ b/src/node/Launcher.ts @@ -687,8 +687,8 @@ class FirefoxLauncher implements ProductLauncher { * able to restore the original values of preferences a backup of prefs.js * will be created. * - * @param prefs List of preferences to add. - * @param profilePath Firefox profile to write the preferences to. + * @param prefs - List of preferences to add. + * @param profilePath - Firefox profile to write the preferences to. */ async writePreferences( prefs: { [x: string]: unknown }, diff --git a/src/node/NodeWebSocketTransport.ts b/src/node/NodeWebSocketTransport.ts index 78a60781e6a8b..03b4a925e4ef6 100644 --- a/src/node/NodeWebSocketTransport.ts +++ b/src/node/NodeWebSocketTransport.ts @@ -16,9 +16,21 @@ import NodeWebSocket from 'ws'; import { ConnectionTransport } from '../common/ConnectionTransport.js'; import { packageVersion } from '../generated/version.js'; +import { promises as dns } from 'dns'; export class NodeWebSocketTransport implements ConnectionTransport { - static create(url: string): Promise { + static async create(urlString: string): Promise { + // TODO(jrandolf): Starting in Node 17, IPv6 is favoured over IPv4 due to a change + // in a default option: + // - https://github.com/nodejs/node/issues/40537, + // Due to this, we parse and resolve the hostname manually with the previous + // behavior according to: + // - https://nodejs.org/api/dns.html#dnslookuphostname-options-callback + // because of https://bugzilla.mozilla.org/show_bug.cgi?id=1769994. + const url = new URL(urlString); + const { address } = await dns.lookup(url.hostname, { verbatim: false }); + url.hostname = address; + return new Promise((resolve, reject) => { const ws = new NodeWebSocket(url, [], { followRedirects: true, diff --git a/test/NetworkManager.spec.ts b/test/NetworkManager.spec.ts index a065ee5cbc81a..c48e945d8559f 100644 --- a/test/NetworkManager.spec.ts +++ b/test/NetworkManager.spec.ts @@ -480,14 +480,14 @@ describeChromeOnly('NetworkManager', () => { * This sequence was taken from an actual CDP session produced by the following * test script: * - * const browser = await puppeteer.launch({ headless: false }); + * const browser = await puppeteer.launch(\{ headless: false \}); * const page = await browser.newPage(); * await page.setCacheEnabled(false); * * await page.setRequestInterception(true) - * page.on('request', (interceptedRequest) => { + * page.on('request', (interceptedRequest) =\> \{ * interceptedRequest.continue(); - * }); + * \}); * * await page.goto('https://www.google.com'); * await browser.close(); diff --git a/test/oopif.spec.ts b/test/oopif.spec.ts index 2c7d9baca92e3..fadc7f72aeb08 100644 --- a/test/oopif.spec.ts +++ b/test/oopif.spec.ts @@ -419,9 +419,6 @@ describeChromeOnly('OOPIF', function () { }); }); -/** - * @param {!BrowserContext} context - */ function oopifs(context) { return context .targets() diff --git a/test/requestinterception.spec.ts b/test/requestinterception.spec.ts index 0602e2f915c94..450937a2c47f8 100644 --- a/test/requestinterception.spec.ts +++ b/test/requestinterception.spec.ts @@ -806,11 +806,7 @@ describe('request interception', function () { }); }); -/** - * @param {string} path - * @returns {string} - */ -function pathToFileURL(path) { +function pathToFileURL(path: string): string { let pathName = path.replace(/\\/g, '/'); // Windows drive letter must be prefixed with a slash. if (!pathName.startsWith('/')) pathName = '/' + pathName;