// @ts-check import { baseConfig } from '@ermnvldmr/eslint-config'; import eslintPluginAstro from 'eslint-plugin-astro'; 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 jsxA11y from 'eslint-plugin-jsx-a11y'; import globals from 'globals'; import tseslint from 'typescript-eslint'; export default tseslint.config( ...baseConfig, // Additional ignores for monorepo { ignores: [ 'services/docs/**/*', // Hugo content, no linting needed ], }, // Astro ...eslintPluginAstro.configs.recommended, { files: ['services/www/**/*.astro', 'services/static/**/*.astro'], rules: { // Astro specific rules can go here }, }, // Storybook - recommended configuration for flat config ...storybook.configs['flat/recommended'], // 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', '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', }, }, // React Refresh { files: ['**/*.{jsx,tsx}'], ignores: ['.config/jest/**/*.{jsx,tsx}'], 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', }, }, // Set globals for Node scripts. { files: ['scripts/**'], languageOptions: { globals: globals.node, }, } );