297 lines
9.1 KiB
JavaScript
297 lines
9.1 KiB
JavaScript
// @ts-check
|
|
import eslint from '@eslint/js';
|
|
import prettier from 'eslint-config-prettier/flat';
|
|
import eslintPluginAstro from 'eslint-plugin-astro';
|
|
import importPlugin from 'eslint-plugin-import';
|
|
import jsdoc from 'eslint-plugin-jsdoc';
|
|
import jsxA11y from 'eslint-plugin-jsx-a11y';
|
|
import react from 'eslint-plugin-react';
|
|
import reactHooks from 'eslint-plugin-react-hooks';
|
|
import reactRefresh from 'eslint-plugin-react-refresh';
|
|
import storybook from 'eslint-plugin-storybook';
|
|
import globals from 'globals';
|
|
import tseslint from 'typescript-eslint';
|
|
|
|
export default tseslint.config(
|
|
{
|
|
ignores: [
|
|
'**/dist',
|
|
'**/node_modules',
|
|
'**/.astro',
|
|
'**/.github',
|
|
'**/.changeset',
|
|
'**/coverage',
|
|
'**/storybook-static',
|
|
'.config/**/*',
|
|
'*.config.{js,ts,mjs,cjs}',
|
|
'astro.config.mjs',
|
|
'eslint.config.mjs',
|
|
'jest.config.cjs',
|
|
'release.config.mjs',
|
|
'!.storybook', // Allow linting of .storybook directory
|
|
],
|
|
},
|
|
|
|
// JavaScript
|
|
eslint.configs.recommended,
|
|
|
|
// TypeScript - Strict Configuration
|
|
...tseslint.configs.recommendedTypeChecked,
|
|
...tseslint.configs.stylisticTypeChecked,
|
|
{
|
|
languageOptions: {
|
|
parserOptions: {
|
|
projectService: true,
|
|
tsconfigRootDir: import.meta.dirname,
|
|
},
|
|
},
|
|
rules: {
|
|
// TypeScript strict rules
|
|
'@typescript-eslint/no-explicit-any': 'error',
|
|
'@typescript-eslint/no-unused-vars': 'error',
|
|
'@typescript-eslint/prefer-nullish-coalescing': 'error',
|
|
'@typescript-eslint/prefer-optional-chain': 'error',
|
|
'@typescript-eslint/no-unnecessary-condition': 'error',
|
|
'@typescript-eslint/no-non-null-assertion': 'error',
|
|
'@typescript-eslint/prefer-as-const': 'error',
|
|
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
|
|
'@typescript-eslint/consistent-type-imports': [
|
|
'error',
|
|
{ prefer: 'type-imports', disallowTypeAnnotations: false },
|
|
],
|
|
'@typescript-eslint/explicit-function-return-type': [
|
|
'error',
|
|
{ allowExpressions: true, allowTypedFunctionExpressions: true },
|
|
],
|
|
},
|
|
},
|
|
// Allow triple-slash references in `*.d.ts` files.
|
|
{
|
|
files: ['**/*.d.ts'],
|
|
rules: {
|
|
'@typescript-eslint/triple-slash-reference': 'off',
|
|
},
|
|
},
|
|
|
|
// Import/Export Rules
|
|
{
|
|
plugins: { import: importPlugin },
|
|
rules: {
|
|
'import/order': [
|
|
'error',
|
|
{
|
|
groups: [
|
|
'builtin',
|
|
'external',
|
|
'internal',
|
|
['parent', 'sibling'],
|
|
'index',
|
|
'type',
|
|
],
|
|
'newlines-between': 'always',
|
|
alphabetize: { order: 'asc', caseInsensitive: true },
|
|
},
|
|
],
|
|
'import/no-duplicates': 'error',
|
|
'import/no-unused-modules': 'error',
|
|
'import/no-cycle': 'error',
|
|
'import/no-self-import': 'error',
|
|
},
|
|
},
|
|
|
|
// JSDoc Requirements - Only for main source code
|
|
{
|
|
files: ['src/components/**/*.{ts,tsx}', 'src/shared/**/*.{ts,tsx}', 'src/www/**/*.{ts,tsx}'],
|
|
plugins: { jsdoc },
|
|
rules: {
|
|
'jsdoc/require-jsdoc': [
|
|
'error',
|
|
{
|
|
contexts: [
|
|
'ExportNamedDeclaration > FunctionDeclaration',
|
|
'ExportDefaultDeclaration > FunctionDeclaration',
|
|
// Exclude VariableDeclaration for React components (memo wrapped)
|
|
// React components should document via Props interface instead
|
|
'TSInterfaceDeclaration',
|
|
'TSTypeAliasDeclaration',
|
|
],
|
|
},
|
|
],
|
|
'jsdoc/require-description': 'error',
|
|
'jsdoc/require-param': 'error',
|
|
'jsdoc/require-returns': 'error',
|
|
'jsdoc/require-example': 'warn',
|
|
'jsdoc/check-alignment': 'error',
|
|
'jsdoc/check-indentation': 'error',
|
|
'jsdoc/check-syntax': 'error',
|
|
'jsdoc/check-tag-names': 'error',
|
|
'jsdoc/check-types': 'error',
|
|
'jsdoc/valid-types': 'error',
|
|
},
|
|
},
|
|
|
|
// Astro
|
|
...eslintPluginAstro.configs.recommended,
|
|
|
|
// Storybook - recommended configuration for flat config
|
|
...storybook.configs['flat/recommended'],
|
|
|
|
// Set globals for Node scripts.
|
|
{
|
|
files: ['scripts/**'],
|
|
languageOptions: {
|
|
globals: globals.node,
|
|
},
|
|
},
|
|
|
|
// Test files
|
|
{
|
|
files: ['**/*.{test,spec}.{js,jsx,ts,tsx}', '.config/jest/**/*.{js,jsx,ts,tsx}'],
|
|
languageOptions: {
|
|
globals: {
|
|
...globals.jest,
|
|
},
|
|
},
|
|
rules: {
|
|
// Relax rules for test files and jest configuration
|
|
'@typescript-eslint/no-explicit-any': 'off',
|
|
'@typescript-eslint/no-unsafe-assignment': 'off',
|
|
'@typescript-eslint/no-unsafe-call': 'off',
|
|
'@typescript-eslint/no-unsafe-member-access': 'off',
|
|
'@typescript-eslint/no-empty-function': 'off',
|
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
'jsdoc/require-jsdoc': 'off',
|
|
'react-refresh/only-export-components': 'off', // Allow re-exports in test utils
|
|
},
|
|
},
|
|
|
|
// Config files and tooling
|
|
{
|
|
files: ['*.config.{js,ts,mjs,cjs}', '.storybook/**/*.{js,ts}'],
|
|
rules: {
|
|
'@typescript-eslint/no-explicit-any': 'off',
|
|
'@typescript-eslint/no-unsafe-assignment': 'off',
|
|
'@typescript-eslint/no-unsafe-call': 'off',
|
|
'@typescript-eslint/no-unsafe-member-access': 'off',
|
|
'@typescript-eslint/no-unsafe-argument': 'off',
|
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
'jsdoc/require-jsdoc': 'off',
|
|
'no-undef': 'off', // For CommonJS files
|
|
},
|
|
},
|
|
|
|
// Exclude shadcn directory from strict rules
|
|
{
|
|
files: ['src/shadcn/**/*.{js,jsx,ts,tsx}'],
|
|
rules: {
|
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
'@typescript-eslint/no-unsafe-assignment': 'off',
|
|
'@typescript-eslint/no-unsafe-call': 'off',
|
|
'@typescript-eslint/no-unsafe-member-access': 'off',
|
|
'jsdoc/require-jsdoc': 'off',
|
|
'react-refresh/only-export-components': 'off',
|
|
},
|
|
},
|
|
|
|
// React - Strict Configuration
|
|
{
|
|
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
|
|
...react.configs.flat.recommended,
|
|
languageOptions: {
|
|
...react.configs.flat.recommended.languageOptions,
|
|
globals: {
|
|
...globals.serviceworker,
|
|
...globals.browser,
|
|
},
|
|
},
|
|
settings: {
|
|
react: {
|
|
version: 'detect',
|
|
},
|
|
},
|
|
rules: {
|
|
// React specific rules
|
|
'react/react-in-jsx-scope': 'off', // Not needed with React 17+
|
|
'react/prop-types': 'off', // Using TypeScript instead
|
|
'react/display-name': 'error',
|
|
'react/no-array-index-key': 'warn', // Changed from error to warn
|
|
'react/no-danger': 'error',
|
|
'react/no-deprecated': 'error',
|
|
'react/no-direct-mutation-state': 'error',
|
|
'react/no-find-dom-node': 'error',
|
|
'react/no-is-mounted': 'error',
|
|
'react/no-render-return-value': 'error',
|
|
'react/no-string-refs': 'error',
|
|
'react/no-unescaped-entities': 'error',
|
|
'react/no-unknown-property': 'error',
|
|
'react/no-unsafe': 'error',
|
|
'react/self-closing-comp': 'error',
|
|
'react/void-dom-elements-no-children': 'error',
|
|
// JSX specific rules
|
|
'react/jsx-boolean-value': 'error',
|
|
'react/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }],
|
|
'react/jsx-fragments': ['error', 'syntax'],
|
|
'react/jsx-no-duplicate-props': 'error',
|
|
'react/jsx-no-target-blank': 'error',
|
|
'react/jsx-no-useless-fragment': 'error',
|
|
'react/jsx-pascal-case': 'error',
|
|
'react/jsx-sort-props': [
|
|
'error',
|
|
{
|
|
callbacksLast: true,
|
|
shorthandFirst: true,
|
|
reservedFirst: true,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
|
|
// React Hooks
|
|
{
|
|
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
|
|
plugins: { 'react-hooks': reactHooks },
|
|
rules: {
|
|
'react-hooks/rules-of-hooks': 'error',
|
|
'react-hooks/exhaustive-deps': 'error', // Changed from warn to error
|
|
},
|
|
},
|
|
// React Refresh
|
|
{
|
|
files: ['**/*.{jsx,tsx}'],
|
|
ignores: ['.config/jest/**/*.{jsx,tsx}'], // Ignore jest config files
|
|
plugins: { 'react-refresh': reactRefresh },
|
|
rules: {
|
|
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
|
},
|
|
},
|
|
|
|
// Accessibility (JSX A11Y)
|
|
{
|
|
files: ['**/*.{jsx,tsx}'],
|
|
...jsxA11y.flatConfigs.recommended,
|
|
rules: {
|
|
'jsx-a11y/alt-text': 'error',
|
|
'jsx-a11y/anchor-has-content': 'error',
|
|
'jsx-a11y/anchor-is-valid': 'error',
|
|
'jsx-a11y/aria-props': 'error',
|
|
'jsx-a11y/aria-proptypes': 'error',
|
|
'jsx-a11y/aria-role': 'error',
|
|
'jsx-a11y/aria-unsupported-elements': 'error',
|
|
'jsx-a11y/click-events-have-key-events': 'error',
|
|
'jsx-a11y/heading-has-content': 'error',
|
|
'jsx-a11y/html-has-lang': 'error',
|
|
'jsx-a11y/img-redundant-alt': 'error',
|
|
'jsx-a11y/interactive-supports-focus': 'error',
|
|
'jsx-a11y/label-has-associated-control': 'error',
|
|
'jsx-a11y/no-autofocus': 'error',
|
|
'jsx-a11y/no-distracting-elements': 'error',
|
|
'jsx-a11y/no-redundant-roles': 'error',
|
|
'jsx-a11y/role-has-required-aria-props': 'error',
|
|
'jsx-a11y/role-supports-aria-props': 'error',
|
|
},
|
|
},
|
|
|
|
// Prettier
|
|
prettier
|
|
);
|