1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00
Files
browser/eslint.config.mjs
Addison Beck e8224fdbe3 feat(nx): add basic-lib generator for streamlined library creation (#14992)
* feat(nx): add basic-lib generator for streamlined library creation

This adds a new nx-plugin library with a generator for creating "common" type
Bitwarden libs. It is set up to accept a lib name, description, team, and
directory. It then
- Creates a folder in the directory (default to libs)
- Sets up complete library scaffolding:
  - README with team ownership
  - Build, lint and test task configuration
  - Test infrastructure
- Configures TypeScript path mapping
- Updates CODEOWNERS with team ownership
- Runs npm i

This will make library creation more consistent and reduce manual boilerplate setup.

The plugin design itself was generated by `npx nx g plugin`. This means we
used a plugin to generate a plugin that exports generators. To create our
generator generator, we first needed a generator.

* fix(dirt/card): correct tsconfig path in jest configuration

Fix the relative path to tsconfig.base in the dirt/card library's Jest config.
The path was incorrectly using four parent directory traversals (../../../../)
when only three (../../../) were needed to reach the project root.

* chore(codeowners): clarify some nx ownership stuff
2025-06-05 14:20:23 -04:00

634 lines
20 KiB
JavaScript

// @ts-check
import { fixupPluginRules } from "@eslint/compat";
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";
import platformPlugins from "./libs/eslint/platform/index.mjs";
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
],
linterOptions: {
reportUnusedDisableDirectives: "error",
},
plugins: {
rxjs: fixupPluginRules(rxjs),
"rxjs-angular": fixupPluginRules(angularRxjs),
"@bitwarden/platform": platformPlugins,
},
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/prefer-standalone": 0,
"@angular-eslint/use-lifecycle-interface": "error",
"@angular-eslint/use-pipe-transform-interface": 0,
"@bitwarden/platform/required-using": "error",
"@bitwarden/platform/no-enums": "error",
"@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/*",
]),
},
},
{
files: ["libs/nx-plugin/**/*.ts", "libs/nx-plugin/**/*.js"],
rules: {
"no-console": "off",
},
},
/// Bandaids for keeping existing circular dependencies from getting worse and new ones from being created
/// Will be removed after Nx is implemented and existing circular dependencies are removed.
{
files: ["libs/common/src/**/*.ts"],
rules: {
"no-restricted-imports": buildNoRestrictedImports([
// Common is at the base level - should not import from other libs except shared
"@bitwarden/admin-console",
"@bitwarden/angular",
"@bitwarden/auth",
"@bitwarden/billing",
"@bitwarden/components",
"@bitwarden/importer",
"@bitwarden/key-management",
"@bitwarden/key-management-ui",
"@bitwarden/node",
"@bitwarden/platform",
"@bitwarden/tools",
"@bitwarden/ui",
"@bitwarden/vault",
]),
},
},
{
files: ["libs/shared/src/**/*.ts"],
rules: {
"no-restricted-imports": buildNoRestrictedImports([
// Shared shouldnt have deps
"@bitwarden/admin-console",
"@bitwarden/angular",
"@bitwarden/auth",
"@bitwarden/billing",
"@bitwarden/common",
"@bitwarden/components",
"@bitwarden/importer",
"@bitwarden/key-management",
"@bitwarden/key-management-ui",
"@bitwarden/node",
"@bitwarden/platform",
"@bitwarden/tools",
"@bitwarden/ui",
"@bitwarden/vault",
]),
},
},
{
files: ["libs/auth/src/**/*.ts"],
rules: {
"no-restricted-imports": buildNoRestrictedImports([
// Auth can only depend on common, shared, angular, node, platform, eslint
"@bitwarden/admin-console",
"@bitwarden/billing",
"@bitwarden/components",
"@bitwarden/importer",
"@bitwarden/key-management-ui",
"@bitwarden/tools",
"@bitwarden/ui",
"@bitwarden/vault",
]),
},
},
{
files: ["libs/key-management/src/**/*.ts"],
rules: {
"no-restricted-imports": buildNoRestrictedImports([
// Key management can depend on common, node, angular, components, eslint, platform, ui
"@bitwarden/auth",
"@bitwarden/admin-console",
"@bitwarden/billing",
"@bitwarden/importer",
"@bitwarden/key-management-ui",
"@bitwarden/tools",
"@bitwarden/vault",
]),
},
},
{
files: ["libs/billing/src/**/*.ts"],
rules: {
"no-restricted-imports": buildNoRestrictedImports([
// Billing can depend on auth, common, angular, components, eslint, node, platform, ui
"@bitwarden/admin-console",
"@bitwarden/importer",
"@bitwarden/key-management-ui",
"@bitwarden/tools",
"@bitwarden/vault",
]),
},
},
{
files: ["libs/components/src/**/*.ts"],
rules: {
"no-restricted-imports": buildNoRestrictedImports([
// Components can depend on common, shared
"@bitwarden/admin-console",
"@bitwarden/auth",
"@bitwarden/billing",
"@bitwarden/eslint",
"@bitwarden/importer",
"@bitwarden/key-management-ui",
"@bitwarden/node",
"@bitwarden/tools",
"@bitwarden/vault",
]),
},
},
{
files: ["libs/ui/src/**/*.ts"],
rules: {
"no-restricted-imports": buildNoRestrictedImports([
// UI can depend on common, shared, auth
"@bitwarden/admin-console",
"@bitwarden/billing",
"@bitwarden/importer",
"@bitwarden/key-management-ui",
"@bitwarden/node",
"@bitwarden/platform",
"@bitwarden/tools",
"@bitwarden/vault",
]),
},
},
{
files: ["libs/key-management-ui/src/**/*.ts"],
rules: {
"no-restricted-imports": buildNoRestrictedImports([
// Key-management-ui can depend on key-management, common, angular, shared, auth, components, ui, eslint
"@bitwarden/admin-console",
"@bitwarden/billing",
"@bitwarden/importer",
"@bitwarden/node",
"@bitwarden/platform",
"@bitwarden/tools",
"@bitwarden/vault",
]),
},
},
{
files: ["libs/angular/src/**/*.ts"],
rules: {
"no-restricted-imports": buildNoRestrictedImports([
// Angular can depend on common, shared, components, ui
"@bitwarden/admin-console",
"@bitwarden/auth",
"@bitwarden/billing",
"@bitwarden/importer",
"@bitwarden/key-management-ui",
"@bitwarden/node",
"@bitwarden/platform",
"@bitwarden/tools",
"@bitwarden/vault",
]),
},
},
{
files: ["libs/vault/src/**/*.ts"],
rules: {
"no-restricted-imports": buildNoRestrictedImports([
// Vault can depend on most libs
"@bitwarden/admin-console",
"@bitwarden/importer",
"@bitwarden/tools",
]),
},
},
{
files: ["libs/admin-console/src/**/*.ts"],
rules: {
"no-restricted-imports": buildNoRestrictedImports([
// Admin console can depend on all libs
]),
},
},
{
files: ["libs/tools/src/**/*.ts"],
rules: {
"no-restricted-imports": buildNoRestrictedImports([
// Tools can depend on most libs
"@bitwarden/admin-console",
]),
},
},
{
files: ["libs/platform/src/**/*.ts"],
rules: {
"no-restricted-imports": buildNoRestrictedImports([
// Platform cant depend on most libs
"@bitwarden/admin-console",
"@bitwarden/auth",
"@bitwarden/billing",
"@bitwarden/importer",
"@bitwarden/key-management",
"@bitwarden/key-management-ui",
"@bitwarden/tools",
"@bitwarden/vault",
]),
},
},
{
files: ["libs/importer/src/**/*.ts"],
rules: {
"no-restricted-imports": buildNoRestrictedImports([
// Importer can depend on most libs but not other domain libs
"@bitwarden/admin-console",
"@bitwarden/tools",
]),
},
},
{
files: ["libs/eslint/src/**/*.ts"],
rules: {
"no-restricted-imports": buildNoRestrictedImports([
// ESLint should not depend on app code
"@bitwarden/admin-console",
"@bitwarden/angular",
"@bitwarden/auth",
"@bitwarden/billing",
"@bitwarden/components",
"@bitwarden/importer",
"@bitwarden/key-management",
"@bitwarden/key-management-ui",
"@bitwarden/node",
"@bitwarden/platform",
"@bitwarden/tools",
"@bitwarden/ui",
"@bitwarden/vault",
]),
},
},
{
files: ["libs/node/src/**/*.ts"],
rules: {
"no-restricted-imports": buildNoRestrictedImports([
// Node can depend on common, shared, auth
"@bitwarden/admin-console",
"@bitwarden/angular",
"@bitwarden/components",
"@bitwarden/importer",
"@bitwarden/key-management-ui",
"@bitwarden/platform",
"@bitwarden/tools",
"@bitwarden/ui",
"@bitwarden/vault",
]),
},
},
/// Team overrides
{
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",
"jest.preset.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),
},
];
}