|  | 
|  | 1 | +/* | 
|  | 2 | + * This Source Code Form is subject to the terms of the Mozilla Public | 
|  | 3 | + * License, v. 2.0. If a copy of the MPL was not distributed with this | 
|  | 4 | + * file, you can obtain one at https://mozilla.org/MPL/2.0/. | 
|  | 5 | + * | 
|  | 6 | + * Copyright Oxide Computer Company | 
|  | 7 | + */ | 
|  | 8 | +import { fixupConfigRules } from '@eslint/compat' | 
|  | 9 | +import { FlatCompat } from '@eslint/eslintrc' | 
|  | 10 | +import js from '@eslint/js' | 
|  | 11 | +import prettierConfig from 'eslint-config-prettier' | 
|  | 12 | +import playwrightPlugin from 'eslint-plugin-playwright' | 
|  | 13 | +import prettierPlugin from 'eslint-plugin-prettier' | 
|  | 14 | +import reactPlugin from 'eslint-plugin-react' | 
|  | 15 | +import reactHooksPlugin from 'eslint-plugin-react-hooks' | 
|  | 16 | +import { defineConfig } from 'eslint/config' | 
|  | 17 | +import globals from 'globals' | 
|  | 18 | +import tseslint from 'typescript-eslint' | 
|  | 19 | + | 
|  | 20 | +// FlatCompat needed for plugins that don't fully support flat config yet | 
|  | 21 | +const compat = new FlatCompat({ | 
|  | 22 | +  baseDirectory: import.meta.dirname, | 
|  | 23 | +  recommendedConfig: js.configs.recommended, | 
|  | 24 | +}) | 
|  | 25 | + | 
|  | 26 | +export default defineConfig( | 
|  | 27 | +  { ignores: ['**/dist/', '**/node_modules/', 'tools/deno/'] }, | 
|  | 28 | +  js.configs.recommended, | 
|  | 29 | +  tseslint.configs.recommendedTypeChecked, | 
|  | 30 | +  tseslint.configs.strict, | 
|  | 31 | +  tseslint.configs.stylistic, | 
|  | 32 | +  reactPlugin.configs.flat.recommended, | 
|  | 33 | +  reactHooksPlugin.configs.flat.recommended, | 
|  | 34 | +  // Plugins without flat config support - use compat | 
|  | 35 | +  ...fixupConfigRules( | 
|  | 36 | +    compat.extends( | 
|  | 37 | +      'plugin:jsx-a11y/recommended', | 
|  | 38 | +      'plugin:react-hook-form/recommended', | 
|  | 39 | +      'plugin:import/recommended' | 
|  | 40 | +    ) | 
|  | 41 | +  ), | 
|  | 42 | +  prettierConfig, | 
|  | 43 | +  // Main config | 
|  | 44 | +  { | 
|  | 45 | +    languageOptions: { | 
|  | 46 | +      parserOptions: { | 
|  | 47 | +        warnOnUnsupportedTypeScriptVersion: false, | 
|  | 48 | +        // this config is needed for type aware lint rules | 
|  | 49 | +        project: true, | 
|  | 50 | +        tsconfigRootDir: import.meta.dirname, | 
|  | 51 | +      }, | 
|  | 52 | +      globals: { ...globals.node }, | 
|  | 53 | +    }, | 
|  | 54 | +    plugins: { | 
|  | 55 | +      prettier: prettierPlugin, | 
|  | 56 | +    }, | 
|  | 57 | +    settings: { | 
|  | 58 | +      react: { version: 'detect' }, | 
|  | 59 | +    }, | 
|  | 60 | + | 
|  | 61 | +    rules: { | 
|  | 62 | +      '@typescript-eslint/array-type': 'off', | 
|  | 63 | +      '@typescript-eslint/consistent-type-definitions': 'off', | 
|  | 64 | +      '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }], | 
|  | 65 | +      '@typescript-eslint/no-empty-function': 'off', | 
|  | 66 | +      '@typescript-eslint/ban-ts-comment': 'off', | 
|  | 67 | +      '@typescript-eslint/no-empty-object-type': [ | 
|  | 68 | +        'error', | 
|  | 69 | +        { allowInterfaces: 'with-single-extends' }, | 
|  | 70 | +      ], | 
|  | 71 | +      '@typescript-eslint/no-non-null-assertion': 'off', | 
|  | 72 | +      '@typescript-eslint/no-duplicate-type-constituents': 'off', | 
|  | 73 | +      '@typescript-eslint/no-unused-vars': [ | 
|  | 74 | +        'error', | 
|  | 75 | +        { | 
|  | 76 | +          argsIgnorePattern: '^_', | 
|  | 77 | +          varsIgnorePattern: '^_', | 
|  | 78 | +          caughtErrorsIgnorePattern: '^_', | 
|  | 79 | +        }, | 
|  | 80 | +      ], | 
|  | 81 | + | 
|  | 82 | +      // disabling the type-aware rules we don't like | 
|  | 83 | +      // https://typescript-eslint.io/getting-started/typed-linting/ | 
|  | 84 | +      '@typescript-eslint/no-floating-promises': 'off', | 
|  | 85 | +      '@typescript-eslint/no-misused-promises': 'off', | 
|  | 86 | +      '@typescript-eslint/no-unsafe-argument': 'off', | 
|  | 87 | +      '@typescript-eslint/no-unsafe-assignment': 'off', | 
|  | 88 | +      '@typescript-eslint/no-unsafe-call': 'off', | 
|  | 89 | +      '@typescript-eslint/no-unsafe-member-access': 'off', | 
|  | 90 | +      '@typescript-eslint/no-unsafe-return': 'off', | 
|  | 91 | +      '@typescript-eslint/only-throw-error': 'off', | 
|  | 92 | +      '@typescript-eslint/unbound-method': 'off', | 
|  | 93 | + | 
|  | 94 | +      eqeqeq: ['error', 'always', { null: 'ignore' }], | 
|  | 95 | +      'import/no-default-export': 'error', | 
|  | 96 | +      'import/no-unresolved': 'off', // plugin doesn't know anything | 
|  | 97 | +      'jsx-a11y/label-has-associated-control': [2, { controlComponents: ['button'] }], | 
|  | 98 | +      // only worry about console.log | 
|  | 99 | +      'no-console': ['error', { allow: ['warn', 'error', 'info', 'table'] }], | 
|  | 100 | +      'no-param-reassign': 'error', | 
|  | 101 | +      'no-restricted-imports': [ | 
|  | 102 | +        'error', | 
|  | 103 | +        { | 
|  | 104 | +          paths: [ | 
|  | 105 | +            '.', // preventing confusion due to auto-imports and barrel files | 
|  | 106 | +          ], | 
|  | 107 | +          patterns: [ | 
|  | 108 | +            // import all CSS except index.css at top level through CSS @import statements | 
|  | 109 | +            // to avoid bad ordering situations. See https://github.com/oxidecomputer/console/pull/2035 | 
|  | 110 | +            '*.css', | 
|  | 111 | +          ], | 
|  | 112 | +        }, | 
|  | 113 | +      ], | 
|  | 114 | +      'no-return-assign': 'error', | 
|  | 115 | +      'no-unused-vars': 'off', | 
|  | 116 | +      'prefer-arrow-callback': 'off', | 
|  | 117 | +      'prettier/prettier': 'error', | 
|  | 118 | +      radix: 'error', | 
|  | 119 | + | 
|  | 120 | +      // https://react.dev/reference/eslint-plugin-react-hooks#recommended | 
|  | 121 | +      'react-hooks/exhaustive-deps': 'error', | 
|  | 122 | +      // recommended rules that go nuts in our codebase. we should fix them eventually | 
|  | 123 | +      'react-hooks/incompatible-library': 'off', | 
|  | 124 | +      'react-hooks/purity': 'off', | 
|  | 125 | +      'react-hooks/refs': 'off', | 
|  | 126 | +      'react-hooks/set-state-in-effect': 'off', | 
|  | 127 | + | 
|  | 128 | +      'react/button-has-type': 'error', | 
|  | 129 | +      'react/jsx-boolean-value': 'error', | 
|  | 130 | +      'react/display-name': 'off', | 
|  | 131 | +      'react/react-in-jsx-scope': 'off', | 
|  | 132 | +      'react/prop-types': 'off', | 
|  | 133 | +    }, | 
|  | 134 | +  }, | 
|  | 135 | + | 
|  | 136 | +  // Default exports are needed in the route modules and the config files, | 
|  | 137 | +  // but we want to avoid them anywhere else | 
|  | 138 | +  { | 
|  | 139 | +    files: ['app/pages/**/*', 'app/layouts/**/*', 'app/forms/**/*', '*.config.ts'], | 
|  | 140 | +    rules: { | 
|  | 141 | +      'import/no-default-export': 'off', | 
|  | 142 | +    }, | 
|  | 143 | +  }, | 
|  | 144 | + | 
|  | 145 | +  // Playwright config | 
|  | 146 | +  { | 
|  | 147 | +    files: ['**/*.e2e.ts'], | 
|  | 148 | +    ...playwrightPlugin.configs['flat/recommended'], | 
|  | 149 | +    rules: { | 
|  | 150 | +      ...playwrightPlugin.configs['flat/recommended'].rules, | 
|  | 151 | +      'playwright/expect-expect': [ | 
|  | 152 | +        'warn', | 
|  | 153 | +        { | 
|  | 154 | +          assertFunctionNames: [ | 
|  | 155 | +            'expectVisible', | 
|  | 156 | +            'expectRowVisible', | 
|  | 157 | +            'expectOptions', | 
|  | 158 | +            'expectRowMenuStaysOpen', | 
|  | 159 | +          ], | 
|  | 160 | +        }, | 
|  | 161 | +      ], | 
|  | 162 | +      'playwright/no-force-option': 'off', | 
|  | 163 | +    }, | 
|  | 164 | +  } | 
|  | 165 | +) | 
0 commit comments