diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 3abbe7f59..000000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -src/assets/* -src/stories/assets/* \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 4e27bd4b2..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,105 +0,0 @@ -module.exports = { - env: { - browser: true, - es2021: true, - node: true, - 'vitest-globals/env': true - }, - extends: [ - 'eslint:recommended', - 'plugin:prettier/recommended', - 'plugin:react/recommended', - 'plugin:storybook/recommended', - 'plugin:vitest-globals/recommended' - ], - parserOptions: { - ecmaFeatures: { - jsx: true - }, - ecmaVersion: 2021, - sourceType: 'module' - }, - plugins: ['react', 'testing-library', 'react-hooks'], - settings: { - react: { - version: 'detect' - } - }, - rules: { - 'prefer-arrow-callback': ['error'], - 'react/jsx-boolean-value': 'error', - 'react/jsx-closing-bracket-location': 'error', - 'react/jsx-equals-spacing': 'error', - 'react/jsx-first-prop-new-line': 'error', - 'react/jsx-handler-names': 'error', - 'react/jsx-key': 'error', - 'react/react-in-jsx-scope': 'off', - 'react/jsx-pascal-case': 'error', - 'react/jsx-sort-props': 'error', - 'react/jsx-no-useless-fragment': [ - 'error', - { - allowExpressions: true - } - ], - 'react/jsx-max-depth': [ - 'error', - { - max: 6 - } - ], - 'react/jsx-wrap-multilines': [ - 'error', - { - declaration: 'parens', - assignment: 'parens', - return: 'parens-new-line', - arrow: 'parens', - condition: 'parens', - logical: 'parens', - prop: 'ignore' - } - ], - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', - 'react/prop-types': 'off' - }, - overrides: [ - { - files: ['**/*.ts', '**/*.tsx'], - parser: '@typescript-eslint/parser', - parserOptions: { - tsconfigRootDir: __dirname, - project: './tsconfig.eslint.json' - }, - extends: [ - 'plugin:@typescript-eslint/recommended', - 'plugin:@typescript-eslint/recommended-requiring-type-checking' - ], - plugins: ['@typescript-eslint'], - rules: { - '@typescript-eslint/no-restricted-imports': [ - 'warn', - { - name: 'react-redux', - importNames: ['useSelector', 'useDispatch'], - message: - 'Use typed hooks `useAppDispatch` and `useAppSelector` instead.' - } - ] - } - }, - { - files: ['**/tests/**/*.*.js', '**/tests/**/*.js'], - env: { - jest: true - } - }, - { - files: ['**/*.stories.*'], - rules: { - 'import/no-anonymous-default-export': 'off' - } - } - ] -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..650484a24 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,147 @@ +import { fixupPluginRules } from '@eslint/compat' +import { FlatCompat } from '@eslint/eslintrc' +import js from '@eslint/js' +import typescriptEslint from '@typescript-eslint/eslint-plugin' +import tsParser from '@typescript-eslint/parser' +import react from 'eslint-plugin-react' +import reactHooks from 'eslint-plugin-react-hooks' +import testingLibrary from 'eslint-plugin-testing-library' +import globals from 'globals' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all +}) + +export default [ + { + ignores: ['src/assets/*', 'src/stories/assets/*'] + }, + ...compat.extends( + 'eslint:recommended', + 'plugin:prettier/recommended', + 'plugin:react/recommended', + 'plugin:storybook/recommended', + 'plugin:vitest-globals/recommended' + ), + { + plugins: { + react, + 'testing-library': testingLibrary, + 'react-hooks': fixupPluginRules(reactHooks) + }, + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + ...globals.es2021, + 'vitest-globals/env': true + }, + ecmaVersion: 2021, + sourceType: 'module', + parserOptions: { + ecmaFeatures: { + jsx: true + } + } + }, + settings: { + react: { + version: 'detect' + } + }, + rules: { + 'prefer-arrow-callback': ['error'], + 'react/jsx-boolean-value': 'error', + 'react/jsx-closing-bracket-location': 'error', + 'react/jsx-equals-spacing': 'error', + 'react/jsx-first-prop-new-line': 'error', + 'react/jsx-handler-names': 'error', + 'react/jsx-key': 'error', + 'react/react-in-jsx-scope': 'off', + 'react/jsx-pascal-case': 'error', + 'react/jsx-sort-props': 'error', + 'react/jsx-no-useless-fragment': [ + 'error', + { + allowExpressions: true + } + ], + 'react/jsx-max-depth': [ + 'error', + { + max: 6 + } + ], + 'react/jsx-wrap-multilines': [ + 'error', + { + declaration: 'parens', + assignment: 'parens', + return: 'parens-new-line', + arrow: 'parens', + condition: 'parens', + logical: 'parens', + prop: 'ignore' + } + ], + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + 'react/prop-types': 'off' + } + }, + ...compat + .extends( + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking' + ) + .map((config) => ({ + ...config, + files: ['**/*.ts', '**/*.tsx'] + })), + { + files: ['**/*.ts', '**/*.tsx'], + plugins: { + '@typescript-eslint': typescriptEslint + }, + languageOptions: { + parser: tsParser, + ecmaVersion: 5, + sourceType: 'script', + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.eslint.json' + } + }, + rules: { + '@typescript-eslint/no-restricted-imports': [ + 'warn', + { + name: 'react-redux', + importNames: ['useSelector', 'useDispatch'], + message: + 'Use typed hooks `useAppDispatch` and `useAppSelector` instead.' + } + ] + } + }, + { + files: ['**/tests/**/*.*.js', '**/tests/**/*.js'], + languageOptions: { + globals: { + ...globals.jest + } + } + }, + { + files: ['**/*.stories.*'], + rules: { + 'import/no-anonymous-default-export': 'off' + } + } +] diff --git a/package.json b/package.json index ab0c3c367..12a68e0cc 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,9 @@ }, "devDependencies": { "@babel/core": "^7.23.2", + "@eslint/compat": "^1.1.1", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "^9.7.0", "@storybook/addon-actions": "^6.5.16", "@storybook/addon-essentials": "^6.5.16", "@storybook/addon-interactions": "^6.5.16", @@ -69,15 +72,16 @@ "c8": "^10.1.2", "cross-env": "^7.0.3", "css-mediaquery": "^0.1.2", - "eslint": "^8.52.0", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-prettier": "^5.0.1", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-storybook": "^0.6.1", - "eslint-plugin-testing-library": "^6.1.0", + "eslint": "^9.7.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-react": "^7.35.0", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-storybook": "^0.8.0", + "eslint-plugin-testing-library": "^6.2.2", "eslint-plugin-typescript": "^0.14.0", - "eslint-plugin-vitest-globals": "^1.4.0", + "eslint-plugin-vitest-globals": "^1.5.0", + "globals": "^15.8.0", "husky": "^8.0.3", "jsdom": "^22.1.0", "lint-staged": "^15.0.2", diff --git a/sonar-project.properties b/sonar-project.properties index 07c086235..d18209441 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -6,7 +6,7 @@ sonar.projectName=SpaceToStudy-Client # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. sonar.sources=. sonar.exclusions=**/tests/**/*, **/src/stories/*, **/build-story/* -sonar.coverage.exclusions=**/src/constants/*, **/tests/**/*, **/*.styles.ts +sonar.coverage.exclusions=**/src/constants/*, **/tests/**/*, **/*.styles.ts, eslint.config.mjs sonar.tests=./tests diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index d51900612..3b1a42127 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -3,6 +3,6 @@ "compilerOptions": { "esModuleInterop": true }, - "include": ["src", "vite.config.ts", "vitest.config.ts", ".eslintrc.js"], + "include": ["src", "vite.config.ts", "vitest.config.ts", "eslint.config.mjs"], "exclude": ["node_modules", "dist", "coverage", "tests"] }