UNPKG

chrome-devtools-frontend

Version:
892 lines (818 loc) • 25.5 kB
// Copyright 2025 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import stylisticPlugin from '@stylistic/eslint-plugin'; import { defineConfig, globalIgnores } from 'eslint/config'; import eslintPlugin from 'eslint-plugin-eslint-plugin'; import importPlugin from 'eslint-plugin-import'; import jsdocPlugin from 'eslint-plugin-jsdoc'; import mochaPlugin from 'eslint-plugin-mocha'; import globals from 'globals'; import { join } from 'path'; import typescriptEslint from 'typescript-eslint'; import devToolsPlugin from './scripts/eslint_rules/plugin.mjs'; export default defineConfig([ globalIgnores([ // Git submodules that are not in third_party 'build/', 'buildtools/', // Don't include the common build directory 'out/', // Don't include third party code 'third_party/', 'front_end/diff/diff_match_patch.js', 'front_end/models/javascript_metadata/NativeFunctions.js', // All of these scripts are auto-generated so don't lint them. 'front_end/generated/ARIAProperties.js', 'front_end/generated/Deprecation.ts', 'front_end/generated/InspectorBackendCommands.ts', 'front_end/generated/protocol-mapping.d.ts', 'front_end/generated/protocol-proxy-api.d.ts', 'front_end/generated/protocol.ts', // Any third_party addition has its source code checked out into // third_party/X/package, so we ignore that code as it's not code we author or // own. 'front_end/third_party/*/package/', // Any JS files are also not authored by devtools-frontend, so we ignore those. 'front_end/third_party/**/*', // Lighthouse doesn't have a package/ folder but has other nested folders, so // we ignore any folders within the lighthouse directory. 'front_end/third_party/lighthouse/*/', // The CodeMirror bundle file is auto-generated and rolled-up as part of the', // install script, so we don't need to lint it. 'front_end/third_party/codemirror.next/bundle.ts', // Lit lib files are auto-generated and rolled up as part of the install script. 'front_end/third_party/lit/src/*.ts', // @puppeteer/replay is auto-generated. 'front_end/third_party/puppeteer-replay/**/*.ts', // Third party code we did not author for extensions 'extensions/cxx_debugging/third_party/**/*', '**/node_modules', 'scripts/build/typescript/tests', 'scripts/migration/**/*.js', 'scripts/protocol_typescript/*.js', 'scripts/deps/tests/fixtures', 'test/**/fixtures/', 'test/e2e/**/*.js', 'test/shared/**/*.js' ]), { name: 'JavaScript files', plugins: { '@typescript-eslint': typescriptEslint.plugin, '@stylistic': stylisticPlugin, '@eslint-plugin': eslintPlugin, mocha: mochaPlugin, '@devtools': devToolsPlugin, import: importPlugin, jsdoc: jsdocPlugin }, languageOptions: { ecmaVersion: 'latest', sourceType: 'module', globals: { ...globals.browser } }, linterOptions: { reportUnusedDisableDirectives: 'error', reportUnusedInlineConfigs: 'error' }, rules: { // syntax preferences '@stylistic/quotes': [ 'error', 'single', { avoidEscape: true, allowTemplateLiterals: 'always' } ], '@stylistic/semi': 'error', '@stylistic/no-extra-semi': 'error', '@stylistic/comma-style': ['error', 'last'], '@stylistic/wrap-iife': ['error', 'inside'], '@stylistic/spaced-comment': [ 'error', 'always', { markers: ['*'] } ], eqeqeq: 'error', 'accessor-pairs': [ 'error', { getWithoutSet: false, setWithoutGet: false } ], curly: 'error', '@stylistic/new-parens': 'error', '@stylistic/function-call-spacing': 'error', '@stylistic/arrow-parens': ['error', 'as-needed'], '@stylistic/eol-last': 'error', 'object-shorthand': ['error', 'properties'], 'no-useless-rename': 'error', // anti-patterns 'no-caller': 'error', 'no-case-declarations': 'error', 'no-cond-assign': 'error', 'no-console': [ 'error', { allow: [ 'assert', 'context', 'error', 'timeStamp', 'time', 'timeEnd', 'warn' ] } ], 'no-debugger': 'error', 'no-dupe-keys': 'error', 'no-duplicate-case': 'error', 'no-else-return': [ 'error', { allowElseIf: false } ], 'no-empty': [ 'error', { allowEmptyCatch: true } ], 'no-lonely-if': 'error', 'no-empty-character-class': 'error', 'no-global-assign': 'error', 'no-implied-eval': 'error', 'no-labels': 'error', 'no-multi-str': 'error', 'no-object-constructor': 'error', 'no-octal-escape': 'error', 'no-self-compare': 'error', 'no-shadow-restricted-names': 'error', 'no-unreachable': 'error', 'no-unsafe-negation': 'error', 'no-unused-vars': [ 'error', { args: 'none', vars: 'local' } ], 'no-var': 'error', 'no-with': 'error', 'prefer-const': 'error', radix: 'error', 'valid-typeof': 'error', 'no-return-assign': ['error', 'always'], 'no-implicit-coercion': ['error', { allow: ['!!'] }], 'no-array-constructor': 'error', // es2015 features 'require-yield': 'error', '@stylistic/template-curly-spacing': ['error', 'never'], // file whitespace '@stylistic/no-multiple-empty-lines': [ 'error', { max: 1 } ], '@stylistic/no-mixed-spaces-and-tabs': 'error', '@stylistic/no-trailing-spaces': 'error', '@stylistic/linebreak-style': ['error', 'unix'], /** * Disabled, aspirational rules */ '@stylistic/indent': [ 'off', 2, { SwitchCase: 1, CallExpression: { arguments: 2 }, MemberExpression: 2 } ], // brace-style is disabled, as eslint cannot enforce 1tbs as default, but allman for functions '@stylistic/brace-style': [ 'off', 'allman', { allowSingleLine: true } ], // key-spacing is disabled, as some objects use value-aligned spacing, some not. '@stylistic/key-spacing': [ 'off', { beforeColon: false, afterColon: true, align: 'value' } ], '@stylistic/quote-props': ['error', 'as-needed'], // no-implicit-globals will prevent accidental globals 'no-implicit-globals': 'off', 'no-unused-private-class-members': 'error', 'no-useless-constructor': 'error', // Sort imports first 'import/first': 'error', // Closure does not properly typecheck default exports 'import/no-default-export': 'error', /** * Catch duplicate import paths. For example this would catch the following example: * import {Foo} from './foo.js' * import * as FooModule from './foo.js' **/ 'import/no-duplicates': 'error', /** * Provides more consistency in the imports. */ 'import/order': [ 'error', { // We need to group the builtin and external as clang-format // can't differentiate the two groups: [['builtin', 'external'], 'parent', 'sibling', 'index'], 'newlines-between': 'always', // clang-format has it's own logic overriding this named: false, alphabetize: { order: 'asc', caseInsensitive: true } } ], // Try to spot '// console.log()' left over from debugging '@devtools/no-commented-out-console': 'error', // Prevent imports being commented out rather than deleted. '@devtools/no-commented-out-import': 'error', /** * Enforce some consistency and usefulness of JSDoc comments, to make sure * we actually benefit from them. */ 'jsdoc/check-alignment': 'error', 'jsdoc/check-tag-names': [ 'error', { definedTags: [ 'attribute', // @attribute is used by lit-analyzer (through web-component-analyzer) 'meaning' // @meaning is used by localization ] } ], 'jsdoc/empty-tags': 'error', 'jsdoc/multiline-blocks': 'error', 'jsdoc/no-bad-blocks': 'error', 'jsdoc/no-blank-blocks': [ 'error', { enableFixer: true } ], 'jsdoc/require-asterisk-prefix': 'error', 'jsdoc/require-param-name': 'error', 'jsdoc/require-hyphen-before-param-description': ['error', 'never'], 'jsdoc/sort-tags': 'error' } }, { name: 'TypeScript files', files: ['**/*.ts'], languageOptions: { ecmaVersion: 'latest', sourceType: 'module', parser: typescriptEslint.parser, parserOptions: { allowAutomaticSingleRunInference: true, project: join( import.meta.dirname, 'config', 'typescript', 'tsconfig.eslint.json' ) } }, rules: { '@typescript-eslint/array-type': [ 'error', { default: 'array-simple' } ], '@typescript-eslint/no-explicit-any': [ 'error', { ignoreRestArgs: true } ], '@typescript-eslint/explicit-member-accessibility': [ 'error', { accessibility: 'no-public' } ], // run just the TypeScript unused-vars rule, else we get duplicate errors 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_' } ], '@stylistic/member-delimiter-style': [ 'error', { multiline: { delimiter: 'semi', requireLast: true }, singleline: { delimiter: 'comma', requireLast: false }, overrides: { interface: { singleline: { delimiter: 'semi', requireLast: false }, multiline: { delimiter: 'semi', requireLast: true } }, typeLiteral: { singleline: { delimiter: 'comma', requireLast: false }, multiline: { delimiter: 'comma', requireLast: true } } } } ], '@typescript-eslint/no-floating-promises': [ 'error', { ignoreVoid: true } ], /** * Enforce that enum members are explicitly defined: * const enum Foo { A = 'a' } rather than const enum Foo { A } */ '@typescript-eslint/prefer-enum-initializers': 'error', /** * Ban non-null assertion operator, e.g.: * this.foo!.toLowerCase() */ '@typescript-eslint/no-non-null-assertion': 'error', '@typescript-eslint/consistent-type-imports': 'error', '@typescript-eslint/naming-convention': [ 'error', // Forbids interfaces starting with an I prefix. { selector: 'interface', format: ['PascalCase'], custom: { regex: '^I[A-Z]', match: false } }, { selector: [ 'function', 'accessor', 'method', 'property', 'parameterProperty' ], format: ['camelCase'] }, { selector: 'variable', filter: { // Ignore localization variables. regex: '^(UIStrings|str_)$', match: false }, format: ['camelCase'] }, { // We are using camelCase, PascalCase and UPPER_CASE for top-level constants, allow the for now. selector: 'variable', modifiers: ['const'], filter: { // Ignore localization variables. regex: '^(UIStrings|str_)$', match: false }, format: ['camelCase', 'UPPER_CASE', 'PascalCase'] }, { selector: 'classProperty', modifiers: ['static', 'readonly'], format: ['UPPER_CASE', 'camelCase'] }, { selector: 'enumMember', format: ['UPPER_CASE'] }, { selector: ['typeLike'], format: ['PascalCase'] }, { selector: 'parameter', format: ['camelCase'], leadingUnderscore: 'allow' }, { // Public methods are currently in transition and may still have leading underscores. selector: 'method', modifiers: ['public'], format: ['camelCase'], leadingUnderscore: 'allow' }, { selector: 'property', modifiers: ['public'], format: ['camelCase'], leadingUnderscore: 'allow' }, { // Object literals may be constructed as arguments to external libraries which follow different styles. selector: ['objectLiteralMethod', 'objectLiteralProperty'], modifiers: ['public'], format: null }, { // Ignore type properties that require quotes selector: 'typeProperty', format: null, modifiers: ['requiresQuotes'] } ], '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], // Disable eslint base rule 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', // Disabled this rule while investigating why it creates // certain TypeScript compilation errors after fixes '@typescript-eslint/no-unnecessary-type-assertion': 'off', '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/consistent-generic-constructors': [ 'error', 'constructor' ], // This is more performant // And should provide better stack trace when debugging // see https://v8.dev/blog/fast-async. '@typescript-eslint/return-await': ['error', 'always'], '@typescript-eslint/ban-ts-comment': [ 'error', { // Change after we add some placeholder for old errors minimumDescriptionLength: 0, 'ts-check': false, 'ts-expect-error': 'allow-with-description', 'ts-ignore': true, 'ts-nocheck': true } ], '@typescript-eslint/prefer-optional-chain': 'error', '@typescript-eslint/no-unsafe-function-type': 'error', '@typescript-eslint/no-empty-object-type': [ 'error', { allowInterfaces: 'with-single-extends' } ], 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/consistent-indexed-object-style': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@devtools/no-underscored-properties': 'error', '@devtools/inline-type-imports': 'error', '@devtools/enforce-default-import-name': [ 'error', { // Enforce that any import of models/trace/trace.js names the import Trace. modulePath: join( import.meta.dirname, 'front_end', 'models', 'trace', 'trace.js' ), importName: 'Trace' } ], '@devtools/validate-timing-types': 'error', // Disallow redundant (and potentially conflicting) type information // within JSDoc comments. 'jsdoc/no-types': 'error', 'jsdoc/require-returns-description': 'error' } }, { name: 'Scripts files', files: ['scripts/**/*'], rules: { 'no-console': 'off', '@devtools/es-modules-import': 'off', 'import/no-default-export': 'off' } }, { name: 'Front-end files', files: ['front_end/**/*'], rules: { // L10n rules are only relevant in 'front_end'. '@devtools/l10n-filename-matches': [ 'error', { rootFrontendDirectory: join(import.meta.dirname, 'front_end') } ], '@devtools/l10n-i18nString-call-only-with-uistrings': 'error', '@devtools/l10n-no-i18nString-calls-module-instantiation': 'error', '@devtools/l10n-no-locked-or-placeholder-only-phrase': 'error', '@devtools/l10n-no-uistrings-export': 'error', '@devtools/l10n-no-unused-message': 'error' } }, { name: 'Front-end TypeScript files', files: ['front_end/**/*.ts'], rules: { '@typescript-eslint/explicit-function-return-type': [ 'error', { allowExpressions: true, allowConciseArrowFunctionExpressionsStartingWithVoid: true, allowIIFEs: true } ], '@devtools/no-imperative-dom-api': 'error', '@devtools/no-lit-render-outside-of-view': 'error', '@devtools/no-importing-images-from-src': 'error', '@devtools/enforce-custom-event-names': 'error', '@devtools/set-data-type-reference': 'error', '@devtools/no-bound-component-methods': 'error', '@devtools/no-adopted-style-sheets': 'error', '@devtools/no-customized-builtin-elements': 'error', '@devtools/no-deprecated-component-usages': 'error', '@devtools/no-self-closing-custom-element-tagnames': 'error', '@devtools/no-a-tags-in-lit': 'error', '@devtools/check-css-import': 'error', '@devtools/enforce-optional-properties-last': 'error', '@devtools/check-enumerated-histograms': 'error', '@devtools/require-super-calls-in-overridden-methods': [ 'error', { methodNames: ['wasShown', 'willHide'] } ], '@devtools/static-custom-event-names': 'error', '@devtools/lit-no-attribute-quotes': 'error', '@devtools/lit-template-result-or-nothing': 'error', '@devtools/inject-checkbox-styles': 'error', '@devtools/jslog-context-list': 'error', '@devtools/es-modules-import': 'error', '@devtools/html-tagged-template': 'error', '@devtools/enforce-custom-element-definitions-location': [ 'error', { rootFrontendDirectory: join(import.meta.dirname, 'front_end') } ], '@devtools/enforce-ui-strings-as-const': 'error', '@devtools/no-new-lit-element-components': 'error', '@devtools/enforce-custom-element-prefix': 'error' } }, { name: 'Front-end meta files', files: ['front_end/**/*-meta.ts'], rules: { '@typescript-eslint/naming-convention': [ 'error', { selector: 'parameter', format: ['camelCase', 'PascalCase'], leadingUnderscore: 'allow' } ] } }, { name: 'TypeScript test files', files: [ '*.test.ts', // This makes the specificity greater than the front-end ts files 'front_end/**/*.test.ts', 'test/**/*.ts', '**/testing/*.ts', 'scripts/eslint_rules/test/**/*', 'extensions/cxx_debugging/e2e/**' ], rules: { // errors on {describe, it}.only 'mocha/no-exclusive-tests': 'error', 'mocha/no-async-describe': 'error', 'mocha/no-global-tests': 'error', 'mocha/no-nested-tests': 'error', '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/only-throw-error': [ 'error', { allow: [ { // Chai AssertionError does not extend Error from: 'package', package: 'chai', name: ['AssertionError'] } ] } ], '@devtools/check-test-definitions': 'error', '@devtools/no-assert-strict-equal-for-arrays-and-objects': 'error', '@devtools/no-assert-deep-strict-equal': 'error', '@devtools/no-assert-equal': 'error', '@devtools/no-assert-equal-boolean-null-undefined': 'error', '@devtools/no-capture-screenshot': 'error', '@devtools/no-imperative-dom-api': 'off', '@devtools/no-lit-render-outside-of-view': 'off', '@devtools/prefer-assert-instance-of': 'error', '@devtools/prefer-assert-is-ok': 'error', '@devtools/prefer-assert-length-of': 'error', '@devtools/prefer-assert-strict-equal': 'error', '@devtools/prefer-sinon-assert': 'error', '@devtools/prefer-url-string': 'error', '@devtools/trace-engine-test-timeouts': 'error', '@devtools/no-document-body-mutation': 'error', '@devtools/enforce-custom-element-definitions-location': 'off', '@devtools/enforce-custom-element-prefix': 'off' }, settings: { 'mocha/additionalCustomNames': [ { name: 'describeWithDevtoolsExtension', type: 'suite', interfaces: ['BDD', 'TDD'] }, { name: 'describeWithEnvironment', type: 'suite', interfaces: ['BDD', 'TDD'] }, { name: 'describeWithLocale', type: 'suite', interfaces: ['BDD', 'TDD'] }, { name: 'describeWithMockConnection', type: 'suite', interfaces: ['BDD', 'TDD'] } ] } }, { name: 'Use private class members rule', files: [ 'front_end/panels/**/components/*.ts', 'front_end/ui/components/**/*.ts', 'front_end/entrypoints/**/*.ts' ], rules: { '@devtools/prefer-private-class-members': 'error' } }, { name: 'Ignore private class members rule', files: [ 'front_end/panels/recorder/**/*.ts', 'front_end/ui/components/suggestion_input/*.ts' ], rules: { // TODO(crbug/1402569): Reenable once https://github.com/microsoft/TypeScript/issues/48885 is closed. '@devtools/prefer-private-class-members': 'off' } }, { name: 'Supported CSS properties rules', files: ['front_end/generated/SupportedCSSProperties.js'], rules: { '@devtools/jslog-context-list': 'error' } }, { name: 'EsLint rules test', files: ['scripts/eslint_rules/tests/**/*'], rules: { '@eslint-plugin/no-only-tests': 'error' } }, { name: 'Legacy test runner', files: ['front_end/legacy_test_runner/**/*'], rules: { '@devtools/es-modules-import': 'off' } }, { name: 'Front-end component docs', files: ['front_end/ui/components/docs/**/*.ts', '**/*.docs.ts'], rules: { // This makes the component doc examples very verbose and doesn't add // anything, so we leave return types to the developer within the // component_docs folder. '@typescript-eslint/explicit-function-return-type': 'off', // We use Lit to help render examples sometimes and we don't use // {host: this} as often the `this` is the window. '@devtools/lit-host-this': 'off', '@devtools/no-imperative-dom-api': 'off', '@devtools/no-lit-render-outside-of-view': 'off' } }, { name: 'Keep models/trace isolated', files: ['front_end/models/trace/**/*.ts'], ignores: ['front_end/models/trace/**/*.test.ts'], rules: { '@devtools/no-imports-in-directory': [ 'error', { bannedImportPaths: [ { bannedPath: join( import.meta.dirname, 'front_end', 'core', 'sdk', 'sdk.js' ), allowTypeImports: true } ] } ] } }, { name: 'Recorder injected code', files: ['front_end/panels/recorder/injected/**/*.ts'], rules: { // The code is rolled up and tree-shaken independently from the regular entrypoints. '@devtools/es-modules-import': 'off' } }, { name: 'Performance panel file', files: ['front_end/ui/legacy/components/perf_ui/**/*.ts'], rules: { // Enable tracking of canvas save() and // restore() calls to try and catch bugs. Only // enabled in this folder because it is an // expensive rule to run and we do not need it // for any code that doesn't use Canvas. '@devtools/canvas-context-tracking': 'error' } }, { name: 'TypeScript type-definitions', files: ['**/*.d.ts'], rules: { // Not a useful rule for .d.ts files where we are // representing an existing module. 'import/no-default-export': 'off' } }, { name: 'Config files', files: ['eslint.config.mjs', '**/*/rollup.config.mjs'], rules: { // The config operate on the default export // So allow it for them 'import/no-default-export': 'off' } } ]);