{ "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-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." }, { "target": ["libs/**/*"], "from": ["bitwarden_license/**/*"], "message": "Libs should not import bitwarden licensed 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"] } ] } ], "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/generator/components/src/**/*.ts"], "rules": { "no-restricted-imports": [ "error", { "patterns": ["@bitwarden/generator-components/*", "src/**/*"] } ] } }, { "files": ["libs/tools/generator/core/src/**/*.ts"], "rules": { "no-restricted-imports": [ "error", { "patterns": ["@bitwarden/generator-core/*", "src/**/*"] } ] } }, { "files": ["libs/tools/generator/extensions/history/src/**/*.ts"], "rules": { "no-restricted-imports": [ "error", { "patterns": ["@bitwarden/generator-history/*", "src/**/*"] } ] } }, { "files": ["libs/tools/generator/extensions/legacy/src/**/*.ts"], "rules": { "no-restricted-imports": [ "error", { "patterns": ["@bitwarden/generator-legacy/*", "src/**/*"] } ] } }, { "files": ["libs/tools/generator/extensions/navigation/src/**/*.ts"], "rules": { "no-restricted-imports": [ "error", { "patterns": ["@bitwarden/generator-navigation/*", "src/**/*"] } ] } }, { "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/tools/send/send-ui/src/**/*.ts"], "rules": { "no-restricted-imports": ["error", { "patterns": ["@bitwarden/send-ui/*", "src/**/*"] }] } }, { "files": ["libs/tools/card/src/**/*.ts"], "rules": { "no-restricted-imports": ["error", { "patterns": ["@bitwarden/tools-card/*", "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/**" ] } ] } }, { "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/*"] } ], // 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).*/]" } ] } } ] }