eslint.config.mjs (11582B)
1 /** 2 * @license 3 * Copyright 2024 Google Inc. 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 import fs from 'node:fs'; 7 import path from 'node:path'; 8 9 import stylisticPlugin from '@stylistic/eslint-plugin'; 10 import {defineConfig, globalIgnores} from 'eslint/config'; 11 import importPlugin from 'eslint-plugin-import'; 12 import mocha from 'eslint-plugin-mocha'; 13 import eslintPrettierPluginRecommended from 'eslint-plugin-prettier/recommended'; 14 import rulesdir from 'eslint-plugin-rulesdir'; 15 import tsdoc from 'eslint-plugin-tsdoc'; 16 import globals from 'globals'; 17 import typescriptEslint from 'typescript-eslint'; 18 rulesdir.RULES_DIR = 'tools/eslint/lib'; 19 20 function getThirdPartyPackages() { 21 return fs 22 .readdirSync( 23 path.join(import.meta.dirname, 'packages/puppeteer-core/third_party'), 24 { 25 withFileTypes: true, 26 }, 27 ) 28 .filter(dirent => { 29 return dirent.isDirectory(); 30 }) 31 .map(({name}) => { 32 return { 33 name, 34 message: `Import \`${name}\` from the vendored location: third_party/${name}/index.js`, 35 }; 36 }); 37 } 38 39 export default defineConfig([ 40 globalIgnores([ 41 '**/node_modules', 42 '**/build/', 43 '**/lib/', 44 '**/bin/', 45 '**/*.tsbuildinfo', 46 '**/*.api.json', 47 '**/*.tgz', 48 '**/yarn.lock', 49 '**/.docusaurus/', 50 '**/.cache-loader', 51 'test/output-*/', 52 '**/.dev_profile*', 53 '**/coverage/', 54 '**/generated/', 55 '**/.eslintcache', 56 '**/.cache/', 57 '**/.vscode', 58 '!.vscode/extensions.json', 59 '!.vscode/*.template.json', 60 '**/.devcontainer', 61 '**/.DS_Store', 62 '**/.env.local', 63 '**/.env.development.local', 64 '**/.env.test.local', 65 '**/.env.production.local', 66 '**/npm-debug.log*', 67 '**/yarn-debug.log*', 68 '**/yarn-error.log*', 69 '**/.wireit', 70 '**/assets/', 71 '**/third_party/', 72 'packages/ng-schematics/sandbox/**/*', 73 'packages/ng-schematics/multi/**/*', 74 'packages/ng-schematics/src/**/files/', 75 'examples/puppeteer-in-browser/out/**/*', 76 'examples/puppeteer-in-browser/node_modules/**/*', 77 'examples/puppeteer-in-extension/out/**/*', 78 'examples/puppeteer-in-extension/node_modules/**/*', 79 ]), 80 eslintPrettierPluginRecommended, 81 importPlugin.flatConfigs.typescript, 82 { 83 name: 'JavaScript rules', 84 plugins: { 85 mocha, 86 '@typescript-eslint': typescriptEslint.plugin, 87 import: importPlugin, 88 rulesdir, 89 '@stylistic': stylisticPlugin, 90 }, 91 92 languageOptions: { 93 ecmaVersion: 'latest', 94 sourceType: 'module', 95 96 globals: { 97 ...globals.node, 98 }, 99 100 parser: typescriptEslint.parser, 101 }, 102 103 settings: { 104 'import/resolver': { 105 typescript: true, 106 }, 107 }, 108 109 rules: { 110 curly: ['error', 'all'], 111 'arrow-body-style': ['error', 'always'], 112 'prettier/prettier': 'error', 113 114 'spaced-comment': [ 115 'error', 116 'always', 117 { 118 markers: ['*', '/'], 119 }, 120 ], 121 122 eqeqeq: ['error'], 123 124 'accessor-pairs': [ 125 'error', 126 { 127 getWithoutSet: false, 128 setWithoutGet: false, 129 }, 130 ], 131 132 'new-parens': 'error', 133 134 'prefer-const': 'error', 135 136 'max-len': [ 137 'error', 138 { 139 /* this setting doesn't impact things as we use Prettier to format 140 * our code and hence dictate the line length. 141 * Prettier aims for 80 but sometimes makes the decision to go just 142 * over 80 chars as it decides that's better than wrapping. ESLint's 143 * rule defaults to 80 but therefore conflicts with Prettier. So we 144 * set it to something far higher than Prettier would allow to avoid 145 * it causing issues and conflicting with Prettier. 146 */ 147 code: 200, 148 comments: 90, 149 ignoreTemplateLiterals: true, 150 ignoreUrls: true, 151 ignoreStrings: true, 152 ignoreRegExpLiterals: true, 153 }, 154 ], 155 156 'no-var': 'error', 157 'no-with': 'error', 158 'no-multi-str': 'error', 159 'no-caller': 'error', 160 'no-implied-eval': 'error', 161 'no-labels': 'error', 162 'no-new-object': 'error', 163 'no-octal-escape': 'error', 164 'no-self-compare': 'error', 165 'no-shadow-restricted-names': 'error', 166 'no-cond-assign': 'error', 167 'no-debugger': 'error', 168 'no-dupe-keys': 'error', 169 'no-duplicate-case': 'error', 170 'no-empty-character-class': 'error', 171 'no-unreachable': 'error', 172 'no-unsafe-negation': 'error', 173 radix: 'error', 174 'valid-typeof': 'error', 175 176 'no-unused-vars': [ 177 'error', 178 { 179 args: 'none', 180 vars: 'local', 181 varsIgnorePattern: 182 '([fx]?describe|[fx]?it|beforeAll|beforeEach|afterAll|afterEach)', 183 }, 184 ], 185 186 // Disabled as it now reports issues - https://eslint.org/docs/latest/rules/no-implicit-globals 187 // 'no-implicit-globals': ['error'], 188 'require-yield': 'error', 189 'template-curly-spacing': ['error', 'never'], 190 'mocha/no-exclusive-tests': 'error', 191 192 'import/order': [ 193 'error', 194 { 195 'newlines-between': 'always', 196 197 alphabetize: { 198 order: 'asc', 199 caseInsensitive: true, 200 }, 201 }, 202 ], 203 204 'import/no-cycle': [ 205 'error', 206 { 207 maxDepth: Infinity, 208 }, 209 ], 210 211 // TODO: enable with next version 212 // 'import/enforce-node-protocol-usage': 'error', 213 214 '@stylistic/func-call-spacing': 'error', 215 '@stylistic/semi': 'error', 216 217 // Keeps comments formatted. 218 'rulesdir/prettier-comments': 'error', 219 // Enforces consistent file extension 220 'rulesdir/extensions': 'error', 221 // Enforces license headers on files 222 'rulesdir/check-license': 'error', 223 }, 224 }, 225 ...[ 226 typescriptEslint.configs.eslintRecommended, 227 typescriptEslint.configs.recommended, 228 typescriptEslint.configs.stylistic, 229 ] 230 .flat() 231 .map(config => { 232 return { 233 ...config, 234 files: ['**/*.ts'], 235 }; 236 }), 237 { 238 name: 'TypeScript rules', 239 files: ['**/*.ts'], 240 241 plugins: { 242 tsdoc, 243 }, 244 245 languageOptions: { 246 ecmaVersion: 'latest', 247 sourceType: 'module', 248 249 parserOptions: { 250 allowAutomaticSingleRunInference: true, 251 project: './tsconfig.base.json', 252 }, 253 }, 254 255 rules: { 256 // Enforces clean up of used resources. 257 'rulesdir/use-using': 'error', 258 259 '@typescript-eslint/array-type': [ 260 'error', 261 { 262 default: 'array-simple', 263 }, 264 ], 265 266 'no-unused-vars': 'off', 267 268 '@typescript-eslint/no-unused-vars': [ 269 'error', 270 { 271 argsIgnorePattern: '^_', 272 varsIgnorePattern: '^_', 273 }, 274 ], 275 276 '@typescript-eslint/no-empty-function': 'off', 277 '@typescript-eslint/no-use-before-define': 'off', 278 // We have to use any on some types so the warning isn't valuable. 279 '@typescript-eslint/no-explicit-any': 'off', 280 // We don't require explicit return types on basic functions or 281 // dummy functions in tests, for example 282 '@typescript-eslint/explicit-function-return-type': 'off', 283 // We allow non-null assertions if the value was asserted using `assert` API. 284 '@typescript-eslint/no-non-null-assertion': 'off', 285 '@typescript-eslint/no-unnecessary-template-expression': 'error', 286 '@typescript-eslint/no-unsafe-function-type': 'error', 287 '@typescript-eslint/no-wrapper-object-types': 'error', 288 289 // By default this is a warning but we want it to error. 290 '@typescript-eslint/explicit-module-boundary-types': 'error', 291 292 'no-restricted-syntax': [ 293 'error', 294 { 295 selector: "CallExpression[callee.name='require']", 296 message: '`require` statements are not allowed. Use `import`.', 297 }, 298 ], 299 300 '@typescript-eslint/no-floating-promises': [ 301 'error', 302 { 303 ignoreVoid: true, 304 ignoreIIFE: true, 305 }, 306 ], 307 308 '@typescript-eslint/prefer-ts-expect-error': 'error', 309 // This is more performant; see https://v8.dev/blog/fast-async. 310 '@typescript-eslint/return-await': ['error', 'always'], 311 // This optimizes the dependency tracking for type-only files. 312 '@typescript-eslint/consistent-type-imports': 'error', 313 // So type-only exports get elided. 314 '@typescript-eslint/consistent-type-exports': 'error', 315 // Don't want to trigger unintended side-effects. 316 '@typescript-eslint/no-import-type-side-effects': 'error', 317 // Prefer interfaces over types for shape like. 318 '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], 319 }, 320 }, 321 { 322 name: 'Puppeteer Core syntax', 323 files: ['packages/puppeteer-core/src/**/*.ts'], 324 rules: { 325 'no-restricted-syntax': [ 326 'error', 327 { 328 message: 'Use method `Deferred.race()` instead.', 329 selector: 330 'MemberExpression[object.name="Promise"][property.name="race"]', 331 }, 332 { 333 message: 334 'Deferred `valueOrThrow` should not be called in `Deferred.race()` pass deferred directly', 335 selector: 336 'CallExpression[callee.object.name="Deferred"][callee.property.name="race"] > ArrayExpression > CallExpression[callee.property.name="valueOrThrow"]', 337 }, 338 ], 339 'no-restricted-imports': [ 340 'error', 341 { 342 patterns: ['*Events', '*.test.js'], 343 paths: [...getThirdPartyPackages()], 344 }, 345 ], 346 }, 347 }, 348 { 349 name: 'Packages', 350 files: [ 351 'packages/puppeteer-core/src/**/*.test.ts', 352 'tools/mocha-runner/src/test.ts', 353 ], 354 rules: { 355 // With the Node.js test runner, `describe` and `it` are technically 356 // promises, but we don't need to await them. 357 '@typescript-eslint/no-floating-promises': 'off', 358 }, 359 }, 360 { 361 files: ['packages/**/*.ts'], 362 363 rules: { 364 'tsdoc/syntax': 'error', 365 }, 366 }, 367 { 368 name: 'Mocha', 369 files: ['test/**/*.ts'], 370 rules: { 371 'no-restricted-imports': [ 372 'error', 373 { 374 /** 375 * The mocha tests run on the compiled output in the /lib directory 376 * so we should avoid importing from src. 377 */ 378 patterns: ['*src*'], 379 }, 380 ], 381 }, 382 }, 383 { 384 name: 'Mocha Tests', 385 files: ['test/**/*.spec.ts'], 386 387 rules: { 388 '@typescript-eslint/no-unused-vars': [ 389 'error', 390 { 391 argsIgnorePattern: '^_', 392 varsIgnorePattern: '^_', 393 }, 394 ], 395 396 'no-restricted-syntax': [ 397 'error', 398 { 399 message: 400 'Use helper command `launch` to make sure the browsers get cleaned', 401 selector: 402 'MemberExpression[object.name="puppeteer"][property.name="launch"]', 403 }, 404 { 405 message: 'Unexpected debugging mocha test.', 406 selector: 407 'CallExpression[callee.object.name="it"] > MemberExpression > Identifier[name="deflake"], CallExpression[callee.object.name="it"] > MemberExpression > Identifier[name="deflakeOnly"]', 408 }, 409 { 410 message: 'No `expect` in EventHandler. They will never throw errors', 411 selector: 412 'CallExpression[callee.property.name="on"] BlockStatement > :not(TryStatement) > ExpressionStatement > CallExpression[callee.object.callee.name="expect"]', 413 }, 414 ], 415 416 'mocha/no-pending-tests': 'error', 417 'mocha/no-identical-title': 'error', 418 }, 419 }, 420 ]);