From 70ea75d8f73883d9461afbcd30394af09385178e Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 28 Jan 2025 16:40:52 +0100 Subject: [PATCH] [PM-17496] Migrate eslint to flat config (#12806) The legacy config is deprecated and will be removed in eslint 10. The flat config also allows us to write js functions which will assist in handling limitations with multiple identical rules. --- .eslintignore | 29 -- .eslintrc.json | 258 ------------ .github/renovate.json | 9 +- .storybook/main.ts | 5 +- .storybook/manager.js | 8 +- .vscode/settings.json | 3 +- apps/browser/.eslintrc.json | 26 -- .../content/components/.lit-storybook/main.ts | 6 +- apps/browser/tailwind.config.js | 2 +- apps/cli/.eslintrc.json | 5 - apps/cli/src/admin-console/.eslintrc.json | 3 - apps/desktop/.eslintrc.json | 6 - .../.eslintrc.json | 5 - .../src/ipc.service.ts | 1 + .../src/native-message.service.ts | 1 + apps/desktop/tailwind.config.js | 2 +- apps/web/.eslintrc.json | 22 - apps/web/src/app/admin-console/.eslintrc.json | 3 - ...console-cipher-form-config.service.spec.ts | 2 + bitwarden_license/bit-cli/.eslintrc.json | 5 - .../bit-cli/src/admin-console/.eslintrc.json | 3 - .../src/app/admin-console/.eslintrc.json | 3 - eslint.config.mjs | 378 ++++++++++++++++++ libs/admin-console/.eslintrc.json | 22 - .../src/state-migrations/.eslintrc.json | 24 -- package-lock.json | 68 +++- package.json | 7 +- scripts/.eslintrc.json | 5 - tsconfig.eslint.json | 5 + 29 files changed, 469 insertions(+), 447 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.json delete mode 100644 apps/browser/.eslintrc.json delete mode 100644 apps/cli/.eslintrc.json delete mode 100644 apps/cli/src/admin-console/.eslintrc.json delete mode 100644 apps/desktop/.eslintrc.json delete mode 100644 apps/desktop/native-messaging-test-runner/.eslintrc.json delete mode 100644 apps/web/.eslintrc.json delete mode 100644 apps/web/src/app/admin-console/.eslintrc.json delete mode 100644 bitwarden_license/bit-cli/.eslintrc.json delete mode 100644 bitwarden_license/bit-cli/src/admin-console/.eslintrc.json delete mode 100644 bitwarden_license/bit-web/src/app/admin-console/.eslintrc.json create mode 100644 eslint.config.mjs delete mode 100644 libs/admin-console/.eslintrc.json delete mode 100644 libs/common/src/state-migrations/.eslintrc.json delete mode 100644 scripts/.eslintrc.json diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index c9a25670a9..0000000000 --- a/.eslintignore +++ /dev/null @@ -1,29 +0,0 @@ -**/build -**/dist -**/coverage -.angular -storybook-static - -**/node_modules - -**/webpack.*.js -**/jest.config.js - -apps/browser/config/config.js -apps/browser/src/auth/scripts/duo.js -apps/browser/webpack/manifest.js - -apps/desktop/desktop_native -apps/desktop/src/auth/scripts/duo.js - -apps/web/config.js -apps/web/scripts/*.js -apps/web/tailwind.config.js - -apps/cli/config/config.js - -tailwind.config.js -libs/components/tailwind.config.base.js -libs/components/tailwind.config.js - -scripts/*.js diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 3fd6dec3d7..0000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,258 +0,0 @@ -{ - "root": true, - "env": { - "browser": true, - "webextensions": true - }, - "overrides": [ - { - "files": ["*.ts", "*.js"], - "plugins": ["@typescript-eslint", "rxjs", "rxjs-angular", "import"], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": ["./tsconfig.eslint.json"], - "sourceType": "module", - "ecmaVersion": 2020 - }, - "extends": [ - "eslint:recommended", - "plugin:@angular-eslint/recommended", - "plugin:@typescript-eslint/recommended", - "plugin:import/recommended", - "plugin:import/typescript", - "plugin:rxjs/recommended", - "prettier", - "plugin:storybook/recommended" - ], - "settings": { - "import/parsers": { - "@typescript-eslint/parser": [".ts"] - }, - "import/resolver": { - "typescript": { - "alwaysTryTypes": true - } - } - }, - "rules": { - "@angular-eslint/component-class-suffix": 0, - "@angular-eslint/contextual-lifecycle": 0, - "@angular-eslint/directive-class-suffix": 0, - "@angular-eslint/no-empty-lifecycle-method": 0, - "@angular-eslint/no-host-metadata-property": 0, - "@angular-eslint/no-input-rename": 0, - "@angular-eslint/no-inputs-metadata-property": 0, - "@angular-eslint/no-output-native": 0, - "@angular-eslint/no-output-on-prefix": 0, - "@angular-eslint/no-output-rename": 0, - "@angular-eslint/no-outputs-metadata-property": 0, - "@angular-eslint/use-lifecycle-interface": "error", - "@angular-eslint/use-pipe-transform-interface": 0, - "@typescript-eslint/explicit-member-accessibility": [ - "error", - { "accessibility": "no-public" } - ], - "@typescript-eslint/no-explicit-any": "off", // TODO: This should be re-enabled - "@typescript-eslint/no-floating-promises": "error", - "@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }], - "@typescript-eslint/no-this-alias": ["error", { "allowedNames": ["self"] }], - "@typescript-eslint/no-unused-expressions": ["error", { "allowTernary": true }], - "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], - "no-console": "error", - "import/no-unresolved": "off", // TODO: Look into turning off once each package is an actual package. - "import/order": [ - "error", - { - "alphabetize": { - "order": "asc" - }, - "newlines-between": "always", - "pathGroups": [ - { - "pattern": "@bitwarden/**", - "group": "external", - "position": "after" - }, - { - "pattern": "src/**/*", - "group": "parent", - "position": "before" - } - ], - "pathGroupsExcludedImportTypes": ["builtin"] - } - ], - "rxjs-angular/prefer-takeuntil": ["error", { "alias": ["takeUntilDestroyed"] }], - "rxjs/no-exposed-subjects": ["error", { "allowProtected": true }], - "no-restricted-syntax": [ - "error", - { - "message": "Calling `svgIcon` directly is not allowed", - "selector": "CallExpression[callee.name='svgIcon']" - }, - { - "message": "Accessing FormGroup using `get` is not allowed, use `.value` instead", - "selector": "ChainExpression[expression.object.callee.property.name='get'][expression.property.name='value']" - } - ], - "curly": ["error", "all"], - "import/namespace": ["off"], // This doesn't resolve namespace imports correctly, but TS will throw for this anyway - "import/no-restricted-paths": [ - "error", - { - "zones": [ - { - "target": ["libs/**/*"], - "from": ["apps/**/*"], - "message": "Libs should not import app-specific code." - }, - { - // avoid specific frameworks or large dependencies in common - "target": "./libs/common/**/*", - "from": [ - // Angular - "./libs/angular/**/*", - "./node_modules/@angular*/**/*", - - // Node - "./libs/node/**/*", - - //Generator - "./libs/tools/generator/components/**/*", - "./libs/tools/generator/core/**/*", - "./libs/tools/generator/extensions/**/*", - - // Import/export - "./libs/importer/**/*", - "./libs/tools/export/vault-export/vault-export-core/**/*" - ] - }, - { - // avoid import of unexported state objects - "target": [ - "!(libs)/**/*", - "libs/!(common)/**/*", - "libs/common/!(src)/**/*", - "libs/common/src/!(platform)/**/*", - "libs/common/src/platform/!(state)/**/*" - ], - "from": ["./libs/common/src/platform/state/**/*"], - // allow module index import - "except": ["**/state/index.ts"] - } - ] - } - ] - } - }, - { - "files": ["*.html"], - "parser": "@angular-eslint/template-parser", - "plugins": ["@angular-eslint/template", "tailwindcss"], - "rules": { - "@angular-eslint/template/button-has-type": "error", - "tailwindcss/no-custom-classname": [ - "error", - { - // uses negative lookahead to whitelist any class that doesn't start with "tw-" - // in other words: classnames that start with tw- must be valid TailwindCSS classes - "whitelist": ["(?!(tw)\\-).*"] - } - ], - "tailwindcss/enforces-negative-arbitrary-values": "error", - "tailwindcss/enforces-shorthand": "error", - "tailwindcss/no-contradicting-classname": "error" - } - }, - { - "files": ["apps/browser/src/**/*.ts", "libs/**/*.ts"], - "excludedFiles": [ - "apps/browser/src/autofill/{content,notification}/**/*.ts", - "apps/browser/src/**/background/**/*.ts", // It's okay to have long lived listeners in the background - "apps/browser/src/platform/background.ts" - ], - "rules": { - "no-restricted-syntax": [ - "error", - { - "message": "Using addListener in the browser popup produces a memory leak in Safari, use `BrowserApi.addListener` instead", - // This selector covers events like chrome.storage.onChange & chrome.runtime.onMessage - "selector": "CallExpression > [object.object.object.name='chrome'][property.name='addListener']" - }, - { - "message": "Using addListener in the browser popup produces a memory leak in Safari, use `BrowserApi.addListener` instead", - // This selector covers events like chrome.storage.local.onChange - "selector": "CallExpression > [object.object.object.object.name='chrome'][property.name='addListener']" - } - ] - } - }, - { - "files": ["**/*.ts"], - "excludedFiles": ["**/platform/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { - "patterns": [ - "**/platform/**/internal", // General internal pattern - // All features that have been converted to barrel files - "**/platform/messaging/**" - ] - } - ] - } - }, - { - "files": ["**/src/**/*.ts"], - "excludedFiles": ["**/platform/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { - "patterns": [ - "**/platform/**/internal", // General internal pattern - // All features that have been converted to barrel files - "**/platform/messaging/**", - "**/src/**/*" // Prevent relative imports across libs. - ] - } - ] - } - }, - { - "files": ["bitwarden_license/bit-common/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/bit-common/*", "**/src/**/*"] } - ] - } - }, - { - "files": ["apps/**/*.ts"], - "rules": { - // Catches static imports - "no-restricted-imports": [ - "error", - { - "patterns": [ - "biwarden_license/**", - "@bitwarden/bit-common/*", - "@bitwarden/bit-web/*", - "**/src/**/*" - ] - } - ], - // Catches dynamic imports, e.g. in routing modules where modules are lazy-loaded - "no-restricted-syntax": [ - "error", - { - "message": "Don't import Bitwarden licensed code into OSS code.", - "selector": "ImportExpression > Literal.source[value=/.*(bitwarden_license|bit-common|bit-web).*/]" - } - ] - } - } - ] -} diff --git a/.github/renovate.json b/.github/renovate.json index 56b59a2af4..64b81cd9ee 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -71,12 +71,8 @@ }, { "matchPackageNames": [ - "@angular-eslint/eslint-plugin-template", - "@angular-eslint/eslint-plugin", "@angular-eslint/schematics", - "@angular-eslint/template-parser", - "@typescript-eslint/eslint-plugin", - "@typescript-eslint/parser", + "angular-eslint", "eslint-config-prettier", "eslint-import-resolver-typescript", "eslint-plugin-import", @@ -86,7 +82,8 @@ "eslint-plugin-tailwindcss", "eslint", "husky", - "lint-staged" + "lint-staged", + "typescript-eslint" ], "description": "Architecture owned dependencies", "commitMessagePrefix": "[deps] Architecture:", diff --git a/.storybook/main.ts b/.storybook/main.ts index b48a86ba2b..d98ca06ead 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,7 +1,8 @@ import { dirname, join } from "path"; + import { StorybookConfig } from "@storybook/angular"; -import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin"; import remarkGfm from "remark-gfm"; +import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin"; const config: StorybookConfig = { stories: [ @@ -29,6 +30,8 @@ const config: StorybookConfig = { getAbsolutePath("@storybook/addon-designs"), getAbsolutePath("@storybook/addon-interactions"), { + // @storybook/addon-docs is part of @storybook/addon-essentials + // eslint-disable-next-line storybook/no-uninstalled-addons name: "@storybook/addon-docs", options: { mdxPluginOptions: { diff --git a/.storybook/manager.js b/.storybook/manager.js index 409f93ec50..e0ec04fd37 100644 --- a/.storybook/manager.js +++ b/.storybook/manager.js @@ -50,10 +50,14 @@ const darkTheme = create({ }); export const getPreferredColorScheme = () => { - if (!globalThis || !globalThis.matchMedia) return "light"; + if (!globalThis || !globalThis.matchMedia) { + return "light"; + } const isDarkThemePreferred = globalThis.matchMedia("(prefers-color-scheme: dark)").matches; - if (isDarkThemePreferred) return "dark"; + if (isDarkThemePreferred) { + return "dark"; + } return "light"; }; diff --git a/.vscode/settings.json b/.vscode/settings.json index 6b31121e17..295c290a37 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,6 @@ "**/_locales/*[^n]/messages.json": true }, "rust-analyzer.linkedProjects": ["apps/desktop/desktop_native/Cargo.toml"], - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "eslint.useFlatConfig": true } diff --git a/apps/browser/.eslintrc.json b/apps/browser/.eslintrc.json deleted file mode 100644 index ba96051183..0000000000 --- a/apps/browser/.eslintrc.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "env": { - "browser": true, - "webextensions": true - }, - "overrides": [ - { - "files": ["src/**/*.ts"], - "excludedFiles": [ - "src/**/{content,popup,spec}/**/*.ts", - "src/**/autofill/{notification,overlay}/**/*.ts", - "src/**/autofill/**/{autofill-overlay-content,collect-autofill-content,dom-element-visibility,insert-autofill-content}.service.ts", - "src/**/*.spec.ts" - ], - "rules": { - "no-restricted-globals": [ - "error", - { - "name": "window", - "message": "The `window` object is not available in service workers and may not be available within the background script. Consider using `self`, `globalThis`, or another global property instead." - } - ] - } - } - ] -} diff --git a/apps/browser/src/autofill/content/components/.lit-storybook/main.ts b/apps/browser/src/autofill/content/components/.lit-storybook/main.ts index 9e2da59d99..157682160e 100644 --- a/apps/browser/src/autofill/content/components/.lit-storybook/main.ts +++ b/apps/browser/src/autofill/content/components/.lit-storybook/main.ts @@ -1,8 +1,8 @@ -import { dirname, join } from "path"; -import path from "path"; +import path, { dirname, join } from "path"; + import type { StorybookConfig } from "@storybook/web-components-webpack5"; -import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin"; import remarkGfm from "remark-gfm"; +import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin"; const getAbsolutePath = (value: string): string => dirname(require.resolve(join(value, "package.json"))); diff --git a/apps/browser/tailwind.config.js b/apps/browser/tailwind.config.js index d0ec8025c6..5e7c962b95 100644 --- a/apps/browser/tailwind.config.js +++ b/apps/browser/tailwind.config.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef, @typescript-eslint/no-var-requires */ +/* eslint-disable no-undef, @typescript-eslint/no-require-imports */ const config = require("../../libs/components/tailwind.config.base"); config.content = [ diff --git a/apps/cli/.eslintrc.json b/apps/cli/.eslintrc.json deleted file mode 100644 index 10d2238837..0000000000 --- a/apps/cli/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "node": true - } -} diff --git a/apps/cli/src/admin-console/.eslintrc.json b/apps/cli/src/admin-console/.eslintrc.json deleted file mode 100644 index 3846718729..0000000000 --- a/apps/cli/src/admin-console/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../../libs/admin-console/.eslintrc.json" -} diff --git a/apps/desktop/.eslintrc.json b/apps/desktop/.eslintrc.json deleted file mode 100644 index 5d9ea457c3..0000000000 --- a/apps/desktop/.eslintrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "env": { - "browser": true, - "node": true - } -} diff --git a/apps/desktop/native-messaging-test-runner/.eslintrc.json b/apps/desktop/native-messaging-test-runner/.eslintrc.json deleted file mode 100644 index d5ba8f9d9c..0000000000 --- a/apps/desktop/native-messaging-test-runner/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rules": { - "no-console": "off" - } -} diff --git a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts index 8513363956..68c7ac73ab 100644 --- a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { homedir } from "os"; diff --git a/apps/desktop/native-messaging-test-runner/src/native-message.service.ts b/apps/desktop/native-messaging-test-runner/src/native-message.service.ts index 94fdde026b..71c55a17d1 100644 --- a/apps/desktop/native-messaging-test-runner/src/native-message.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/native-message.service.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import "module-alias/register"; import { v4 as uuidv4 } from "uuid"; diff --git a/apps/desktop/tailwind.config.js b/apps/desktop/tailwind.config.js index bf3b67c74a..9e43b21a59 100644 --- a/apps/desktop/tailwind.config.js +++ b/apps/desktop/tailwind.config.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef, @typescript-eslint/no-var-requires */ +/* eslint-disable no-undef, @typescript-eslint/no-require-imports */ const config = require("../../libs/components/tailwind.config.base"); config.content = [ diff --git a/apps/web/.eslintrc.json b/apps/web/.eslintrc.json deleted file mode 100644 index 6f606eb0c3..0000000000 --- a/apps/web/.eslintrc.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "env": { - "browser": true - }, - "rules": { - "no-restricted-imports": [ - "error", - { - "patterns": [ - "**/app/core/*", - "**/reports/*", - "**/app/shared/*", - "**/organizations/settings/*", - "**/organizations/policies/*", - "@bitwarden/web-vault/*", - "src/**/*", - "bitwarden_license" - ] - } - ] - } -} diff --git a/apps/web/src/app/admin-console/.eslintrc.json b/apps/web/src/app/admin-console/.eslintrc.json deleted file mode 100644 index d55df3899e..0000000000 --- a/apps/web/src/app/admin-console/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../../../libs/admin-console/.eslintrc.json" -} diff --git a/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.spec.ts b/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.spec.ts index c318e7389a..d10f83fd42 100644 --- a/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.spec.ts +++ b/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.spec.ts @@ -11,6 +11,8 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { CipherId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { Account } from "../../../../../../../libs/importer/src/importers/lastpass/access/models"; import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service"; diff --git a/bitwarden_license/bit-cli/.eslintrc.json b/bitwarden_license/bit-cli/.eslintrc.json deleted file mode 100644 index 10d2238837..0000000000 --- a/bitwarden_license/bit-cli/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "node": true - } -} diff --git a/bitwarden_license/bit-cli/src/admin-console/.eslintrc.json b/bitwarden_license/bit-cli/src/admin-console/.eslintrc.json deleted file mode 100644 index 3846718729..0000000000 --- a/bitwarden_license/bit-cli/src/admin-console/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../../libs/admin-console/.eslintrc.json" -} diff --git a/bitwarden_license/bit-web/src/app/admin-console/.eslintrc.json b/bitwarden_license/bit-web/src/app/admin-console/.eslintrc.json deleted file mode 100644 index d55df3899e..0000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../../../libs/admin-console/.eslintrc.json" -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000000..514d1ccf0b --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,378 @@ +// @ts-check + +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; +import angular from "angular-eslint"; +// @ts-ignore +import importPlugin from "eslint-plugin-import"; +import eslintConfigPrettier from "eslint-config-prettier"; +import eslintPluginTailwindCSS from "eslint-plugin-tailwindcss"; +import rxjs from "eslint-plugin-rxjs"; +import angularRxjs from "eslint-plugin-rxjs-angular"; +import storybook from "eslint-plugin-storybook"; + +export default tseslint.config( + ...storybook.configs["flat/recommended"], + { + // Everything in this config object targets our TypeScript files (Components, Directives, Pipes etc) + files: ["**/*.ts", "**/*.js"], + extends: [ + eslint.configs.recommended, + ...tseslint.configs.recommended, + //...tseslint.configs.stylistic, + ...angular.configs.tsRecommended, + importPlugin.flatConfigs.recommended, + importPlugin.flatConfigs.typescript, + eslintConfigPrettier, // Disables rules that conflict with Prettier + ], + plugins: { + rxjs: rxjs, + "rxjs-angular": angularRxjs, + }, + languageOptions: { + parserOptions: { + project: ["./tsconfig.eslint.json"], + sourceType: "module", + ecmaVersion: 2020, + }, + }, + settings: { + "import/parsers": { + "@typescript-eslint/parser": [".ts"], + }, + "import/resolver": { + typescript: { + alwaysTryTypes: true, + }, + }, + }, + processor: angular.processInlineTemplates, + rules: { + ...rxjs.configs.recommended.rules, + "rxjs-angular/prefer-takeuntil": ["error", { alias: ["takeUntilDestroyed"] }], + "rxjs/no-exposed-subjects": ["error", { allowProtected: true }], + + // TODO: Enable these. + "@angular-eslint/component-class-suffix": 0, + "@angular-eslint/contextual-lifecycle": 0, + "@angular-eslint/directive-class-suffix": 0, + "@angular-eslint/no-empty-lifecycle-method": 0, + "@angular-eslint/no-host-metadata-property": 0, + "@angular-eslint/no-input-rename": 0, + "@angular-eslint/no-inputs-metadata-property": 0, + "@angular-eslint/no-output-native": 0, + "@angular-eslint/no-output-on-prefix": 0, + "@angular-eslint/no-output-rename": 0, + "@angular-eslint/no-outputs-metadata-property": 0, + "@angular-eslint/use-lifecycle-interface": "error", + "@angular-eslint/use-pipe-transform-interface": 0, + + "@typescript-eslint/explicit-member-accessibility": ["error", { accessibility: "no-public" }], + "@typescript-eslint/no-explicit-any": "off", // TODO: This should be re-enabled + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-misused-promises": ["error", { checksVoidReturn: false }], + "@typescript-eslint/no-this-alias": ["error", { allowedNames: ["self"] }], + "@typescript-eslint/no-unused-expressions": ["error", { allowTernary: true }], + "@typescript-eslint/no-unused-vars": ["error", { args: "none" }], + + curly: ["error", "all"], + "no-console": "error", + + "import/order": [ + "error", + { + alphabetize: { + order: "asc", + }, + "newlines-between": "always", + pathGroups: [ + { + pattern: "@bitwarden/**", + group: "external", + position: "after", + }, + { + pattern: "src/**/*", + group: "parent", + position: "before", + }, + ], + pathGroupsExcludedImportTypes: ["builtin"], + }, + ], + "import/namespace": ["off"], // This doesn't resolve namespace imports correctly, but TS will throw for this anyway + "import/no-restricted-paths": [ + "error", + { + zones: [ + { + target: ["libs/**/*"], + from: ["apps/**/*"], + message: "Libs should not import app-specific code.", + }, + { + // avoid specific frameworks or large dependencies in common + target: "./libs/common/**/*", + from: [ + // Angular + "./libs/angular/**/*", + "./node_modules/@angular*/**/*", + + // Node + "./libs/node/**/*", + + //Generator + "./libs/tools/generator/components/**/*", + "./libs/tools/generator/core/**/*", + "./libs/tools/generator/extensions/**/*", + + // Import/export + "./libs/importer/**/*", + "./libs/tools/export/vault-export/vault-export-core/**/*", + ], + }, + { + // avoid import of unexported state objects + target: [ + "!(libs)/**/*", + "libs/!(common)/**/*", + "libs/common/!(src)/**/*", + "libs/common/src/!(platform)/**/*", + "libs/common/src/platform/!(state)/**/*", + ], + from: ["./libs/common/src/platform/state/**/*"], + // allow module index import + except: ["**/state/index.ts"], + }, + ], + }, + ], + "import/no-unresolved": "off", // TODO: Look into turning off once each package is an actual package., + }, + }, + { + // Everything in this config object targets our HTML files (external templates, + // and inline templates as long as we have the `processor` set on our TypeScript config above) + files: ["**/*.html"], + extends: [ + // Apply the recommended Angular template rules + // ...angular.configs.templateRecommended, + // Apply the Angular template rules which focus on accessibility of our apps + // ...angular.configs.templateAccessibility, + ], + languageOptions: { + parser: angular.templateParser, + }, + plugins: { + "@angular-eslint/template": angular.templatePlugin, + tailwindcss: eslintPluginTailwindCSS, + }, + rules: { + "@angular-eslint/template/button-has-type": "error", + "tailwindcss/no-custom-classname": [ + "error", + { + // uses negative lookahead to whitelist any class that doesn't start with "tw-" + // in other words: classnames that start with tw- must be valid TailwindCSS classes + whitelist: ["(?!(tw)\\-).*"], + }, + ], + "tailwindcss/enforces-negative-arbitrary-values": "error", + "tailwindcss/enforces-shorthand": "error", + "tailwindcss/no-contradicting-classname": "error", + }, + }, + + // Global quirks + { + files: ["apps/browser/src/**/*.ts", "libs/**/*.ts"], + ignores: [ + "apps/browser/src/autofill/{deprecated/content,content,notification}/**/*.ts", + "apps/browser/src/**/background/**/*.ts", // It's okay to have long lived listeners in the background + "apps/browser/src/platform/background.ts", + ], + rules: { + "no-restricted-syntax": [ + "error", + { + message: + "Using addListener in the browser popup produces a memory leak in Safari, use `BrowserApi.addListener` instead", + // This selector covers events like chrome.storage.onChange & chrome.runtime.onMessage + selector: + "CallExpression > [object.object.object.name='chrome'][property.name='addListener']", + }, + { + message: + "Using addListener in the browser popup produces a memory leak in Safari, use `BrowserApi.addListener` instead", + // This selector covers events like chrome.storage.local.onChange + selector: + "CallExpression > [object.object.object.object.name='chrome'][property.name='addListener']", + }, + ], + }, + }, + { + files: ["**/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports(), + }, + }, + + // App overrides. Be considerate if you override these. + { + files: ["apps/browser/src/**/*.ts"], + ignores: [ + "apps/browser/src/**/{content,popup,spec}/**/*.ts", + "apps/browser/src/**/autofill/{notification,overlay}/**/*.ts", + "apps/browser/src/**/autofill/**/{autofill-overlay-content,collect-autofill-content,dom-element-visibility,insert-autofill-content}.service.ts", + "apps/browser/src/**/*.spec.ts", + ], + rules: { + "no-restricted-globals": [ + "error", + { + name: "window", + message: + "The `window` object is not available in service workers and may not be available within the background script. Consider using `self`, `globalThis`, or another global property instead.", + }, + ], + }, + }, + { + files: ["bitwarden_license/bit-common/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports(["@bitwarden/bit-common/*"]), + }, + }, + { + files: ["apps/**/*.ts"], + rules: { + // Catches static imports + "no-restricted-imports": buildNoRestrictedImports([ + "bitwarden_license/**", + "@bitwarden/bit-common/*", + "@bitwarden/bit-web/*", + ]), + }, + }, + { + files: ["apps/web/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + "bitwarden_license/**", + "@bitwarden/bit-common/*", + "@bitwarden/bit-web/*", + + "**/app/core/*", + "**/reports/*", + "**/app/shared/*", + "**/organizations/settings/*", + "**/organizations/policies/*", + ]), + }, + }, + + /// Team overrides + { + files: ["**/src/platform/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([], true), + }, + }, + { + files: [ + "apps/cli/src/admin-console/**/*.ts", + "apps/web/src/app/admin-console/**/*.ts", + "bitwarden_license/bit-cli/src/admin-console/**/*.ts", + "bitwarden_license/bit-web/src/app/admin-console/**/*.ts", + "libs/admin-console/src/**/*.ts", + ], + rules: { + "@angular-eslint/component-class-suffix": "error", + "@angular-eslint/contextual-lifecycle": "error", + "@angular-eslint/directive-class-suffix": "error", + "@angular-eslint/no-empty-lifecycle-method": "error", + "@angular-eslint/no-input-rename": "error", + "@angular-eslint/no-inputs-metadata-property": "error", + "@angular-eslint/no-output-native": "error", + "@angular-eslint/no-output-on-prefix": "error", + "@angular-eslint/no-output-rename": "error", + "@angular-eslint/no-outputs-metadata-property": "error", + "@angular-eslint/use-lifecycle-interface": "error", + "@angular-eslint/use-pipe-transform-interface": "error", + }, + }, + { + files: ["libs/common/src/state-migrations/**/*.ts"], + rules: { + "import/no-restricted-paths": [ + "error", + { + basePath: "libs/common/src/state-migrations", + zones: [ + { + target: "./", + from: "../", + // Relative to from, not basePath + except: ["state-migrations"], + message: + "State migrations should rarely import from the greater codebase. If you need to import from another location, take into account the likelihood of change in that code and consider copying to the migration instead.", + }, + ], + }, + ], + }, + }, + + // Keep ignores at the end + { + ignores: [ + "**/build/", + "**/dist/", + "**/coverage/", + ".angular/", + "storybook-static/", + + "**/node_modules/", + + "**/webpack.*.js", + "**/jest.config.js", + + "apps/browser/config/config.js", + "apps/browser/src/auth/scripts/duo.js", + "apps/browser/webpack/manifest.js", + + "apps/desktop/desktop_native", + "apps/desktop/src/auth/scripts/duo.js", + + "apps/web/config.js", + "apps/web/scripts/*.js", + "apps/web/tailwind.config.js", + + "apps/cli/config/config.js", + + "tailwind.config.js", + "libs/components/tailwind.config.base.js", + "libs/components/tailwind.config.js", + + "scripts/*.js", + ], + }, +); + +/** + * // Helper function for building no-restricted-imports rule + * @param {string[]} additionalForbiddenPatterns + * @returns {any} + */ +function buildNoRestrictedImports(additionalForbiddenPatterns = [], skipPlatform = false) { + return [ + "error", + { + patterns: [ + ...(skipPlatform ? [] : ["**/platform/**/internal", "**/platform/messaging/**"]), + "**/src/**/*", // Prevent relative imports across libs. + ].concat(additionalForbiddenPatterns), + }, + ]; +} diff --git a/libs/admin-console/.eslintrc.json b/libs/admin-console/.eslintrc.json deleted file mode 100644 index d8aa8f64a8..0000000000 --- a/libs/admin-console/.eslintrc.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "overrides": [ - { - "files": ["*.ts"], - "extends": ["plugin:@angular-eslint/recommended"], - "rules": { - "@angular-eslint/component-class-suffix": "error", - "@angular-eslint/contextual-lifecycle": "error", - "@angular-eslint/directive-class-suffix": "error", - "@angular-eslint/no-empty-lifecycle-method": "error", - "@angular-eslint/no-input-rename": "error", - "@angular-eslint/no-inputs-metadata-property": "error", - "@angular-eslint/no-output-native": "error", - "@angular-eslint/no-output-on-prefix": "error", - "@angular-eslint/no-output-rename": "error", - "@angular-eslint/no-outputs-metadata-property": "error", - "@angular-eslint/use-lifecycle-interface": "error", - "@angular-eslint/use-pipe-transform-interface": "error" - } - } - ] -} diff --git a/libs/common/src/state-migrations/.eslintrc.json b/libs/common/src/state-migrations/.eslintrc.json deleted file mode 100644 index 4b66f0a32f..0000000000 --- a/libs/common/src/state-migrations/.eslintrc.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "overrides": [ - { - "files": ["*"], - "rules": { - "import/no-restricted-paths": [ - "error", - { - "basePath": "libs/common/src/state-migrations", - "zones": [ - { - "target": "./", - "from": "../", - // Relative to from, not basePath - "except": ["state-migrations"], - "message": "State migrations should rarely import from the greater codebase. If you need to import from another location, take into account the likelihood of change in that code and consider copying to the migration instead." - } - ] - } - ] - } - } - ] -} diff --git a/package-lock.json b/package-lock.json index e274e82254..005fd25d86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,10 +77,7 @@ }, "devDependencies": { "@angular-devkit/build-angular": "18.2.12", - "@angular-eslint/eslint-plugin": "18.4.3", - "@angular-eslint/eslint-plugin-template": "18.4.3", "@angular-eslint/schematics": "18.4.3", - "@angular-eslint/template-parser": "18.4.3", "@angular/cli": "18.2.12", "@angular/compiler-cli": "18.2.13", "@babel/core": "7.24.9", @@ -121,10 +118,9 @@ "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", - "@typescript-eslint/eslint-plugin": "8.20.0", - "@typescript-eslint/parser": "8.20.0", "@webcomponents/custom-elements": "1.6.0", "@yao-pkg/pkg": "5.16.1", + "angular-eslint": "18.4.3", "autoprefixer": "10.4.20", "babel-loader": "9.2.1", "base64-loader": "1.0.0", @@ -176,6 +172,7 @@ "tsconfig-paths-webpack-plugin": "4.2.0", "type-fest": "2.19.0", "typescript": "5.4.2", + "typescript-eslint": "8.20.0", "typescript-strict-plugin": "^2.4.4", "url": "0.11.4", "util": "0.12.5", @@ -1240,6 +1237,20 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-eslint/builder": { + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-18.4.3.tgz", + "integrity": "sha512-NzmrXlr7GFE+cjwipY/CxBscZXNqnuK0us1mO6Z2T6MeH6m+rRcdlY/rZyKoRniyNNvuzl6vpEsfMIMmnfebrA==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": ">= 0.1800.0 < 0.1900.0", + "@angular-devkit/core": ">= 18.0.0 < 19.0.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, "node_modules/@angular-eslint/bundled-angular-compiler": { "version": "18.4.3", "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.4.3.tgz", @@ -10080,7 +10091,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz", "integrity": "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.20.0", @@ -10259,7 +10269,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz", "integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==", "dev": true, - "license": "MIT", "dependencies": { "@typescript-eslint/scope-manager": "8.20.0", "@typescript-eslint/types": "8.20.0", @@ -10302,7 +10311,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.20.0.tgz", "integrity": "sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==", "dev": true, - "license": "MIT", "dependencies": { "@typescript-eslint/typescript-estree": "8.20.0", "@typescript-eslint/utils": "8.20.0", @@ -11106,6 +11114,28 @@ "ajv": "^8.8.2" } }, + "node_modules/angular-eslint": { + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/angular-eslint/-/angular-eslint-18.4.3.tgz", + "integrity": "sha512-0ZjLzzADGRLUhZC8ZpwSo6CE/m6QhQB/oljMJ0mEfP+lB1sy1v8PBKNsJboIcfEEgGW669Z/efVQ3df88yJLYg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": ">= 18.0.0 < 19.0.0", + "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0", + "@angular-eslint/builder": "18.4.3", + "@angular-eslint/eslint-plugin": "18.4.3", + "@angular-eslint/eslint-plugin-template": "18.4.3", + "@angular-eslint/schematics": "18.4.3", + "@angular-eslint/template-parser": "18.4.3", + "@typescript-eslint/types": "^8.0.0", + "@typescript-eslint/utils": "^8.0.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*", + "typescript-eslint": "^8.0.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -31151,6 +31181,28 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.20.0.tgz", + "integrity": "sha512-Kxz2QRFsgbWj6Xcftlw3Dd154b3cEPFqQC+qMZrMypSijPd4UanKKvoKDrJ4o8AIfZFKAF+7sMaEIR8mTElozA==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.20.0", + "@typescript-eslint/parser": "8.20.0", + "@typescript-eslint/utils": "8.20.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, "node_modules/typescript-strict-plugin": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/typescript-strict-plugin/-/typescript-strict-plugin-2.4.4.tgz", diff --git a/package.json b/package.json index adc02d6e4f..fc4f1a49fb 100644 --- a/package.json +++ b/package.json @@ -37,10 +37,7 @@ ], "devDependencies": { "@angular-devkit/build-angular": "18.2.12", - "@angular-eslint/eslint-plugin": "18.4.3", - "@angular-eslint/eslint-plugin-template": "18.4.3", "@angular-eslint/schematics": "18.4.3", - "@angular-eslint/template-parser": "18.4.3", "@angular/cli": "18.2.12", "@angular/compiler-cli": "18.2.13", "@babel/core": "7.24.9", @@ -81,10 +78,9 @@ "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", - "@typescript-eslint/eslint-plugin": "8.20.0", - "@typescript-eslint/parser": "8.20.0", "@webcomponents/custom-elements": "1.6.0", "@yao-pkg/pkg": "5.16.1", + "angular-eslint": "18.4.3", "autoprefixer": "10.4.20", "babel-loader": "9.2.1", "base64-loader": "1.0.0", @@ -136,6 +132,7 @@ "tsconfig-paths-webpack-plugin": "4.2.0", "type-fest": "2.19.0", "typescript": "5.4.2", + "typescript-eslint": "8.20.0", "typescript-strict-plugin": "^2.4.4", "url": "0.11.4", "util": "0.12.5", diff --git a/scripts/.eslintrc.json b/scripts/.eslintrc.json deleted file mode 100644 index 10d2238837..0000000000 --- a/scripts/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "node": true - } -} diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 980d7832ac..83f62d5f68 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -47,6 +47,11 @@ } ] }, + "files": [ + ".storybook/main.ts", + ".storybook/manager.js", + "apps/browser/src/autofill/content/components/.lit-storybook/main.ts" + ], "include": ["apps/**/*", "libs/**/*", "bitwarden_license/**/*", "scripts/**/*"], "exclude": ["**/build", "**/dist"] }