{ "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:@typescript-eslint/recommended", "plugin:import/recommended", "plugin:import/typescript", "prettier", "plugin:rxjs/recommended", "plugin:storybook/recommended" ], "settings": { "import/parsers": { "@typescript-eslint/parser": [".ts"] }, "import/resolver": { "typescript": { "alwaysTryTypes": true } } }, "rules": { "@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-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": [ { // avoid specific frameworks or large dependencies in common "target": "./libs/common/**/*", "from": [ // Angular "./libs/angular/**/*", "./node_modules/@angular*/**/*", // Node "./libs/node/**/*", // 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"] } ] } ], "no-restricted-imports": ["error", { "patterns": ["src/**/*"] }] } }, { "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": ["libs/admin-console/src/**/*.ts"], "rules": { "no-restricted-imports": [ "error", { "patterns": ["@bitwarden/admin-console/*", "src/**/*"] } ] } }, { "files": ["libs/angular/src/**/*.ts"], "rules": { "no-restricted-imports": ["error", { "patterns": ["@bitwarden/angular/*", "src/**/*"] }] } }, { "files": ["libs/auth/src/**/*.ts"], "rules": { "no-restricted-imports": ["error", { "patterns": ["@bitwarden/auth/*", "src/**/*"] }] } }, { "files": ["libs/billing/src/**/*.ts"], "rules": { "no-restricted-imports": ["error", { "patterns": ["@bitwarden/billing/*", "src/**/*"] }] } }, { "files": ["libs/common/src/**/*.ts"], "rules": { "no-restricted-imports": ["error", { "patterns": ["@bitwarden/common/*", "src/**/*"] }] } }, { "files": ["libs/components/src/**/*.ts"], "rules": { "no-restricted-imports": [ "error", { "patterns": ["@bitwarden/components/*", "src/**/*", "@bitwarden/angular/*"] } ] } }, { "files": ["libs/tools/export/vault-export/vault-export-core/src/**/*.ts"], "rules": { "no-restricted-imports": [ "error", { "patterns": ["@bitwarden/vault-export-core/*", "src/**/*"] } ] } }, { "files": ["libs/tools/export/vault-export/vault-export-ui/src/**/*.ts"], "rules": { "no-restricted-imports": [ "error", { "patterns": ["@bitwarden/vault-export-ui/*", "src/**/*"] } ] } }, { "files": ["libs/importer/src/**/*.ts"], "rules": { "no-restricted-imports": ["error", { "patterns": ["@bitwarden/importer/*", "src/**/*"] }] } }, { "files": ["libs/node/src/**/*.ts"], "rules": { "no-restricted-imports": ["error", { "patterns": ["@bitwarden/node/*", "src/**/*"] }] } }, { "files": ["libs/platform/src/**/*.ts"], "rules": { "no-restricted-imports": ["error", { "patterns": ["@bitwarden/platform/*", "src/**/*"] }] } }, { "files": ["libs/vault/src/**/*.ts"], "rules": { "no-restricted-imports": ["error", { "patterns": ["@bitwarden/vault/*", "src/**/*"] }] } }, { "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/**" ] } ] } } ] }