1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-01-30 22:41:33 +01:00
bitwarden-browser/eslint.config.mjs
Oscar Hinton 70ea75d8f7
[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.
2025-01-28 16:40:52 +01:00

379 lines
12 KiB
JavaScript

// @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),
},
];
}