Files
payloadcms/packages/eslint-config/index.mjs
Alessio Gravili 1038e1c228 chore: move to eslint v9 (#7041)
- Upgrades eslint from v8 to v9
- Upgrades all other eslint packages. We will have to do a new
full-project lint, as new rules have been added
- Upgrades husky from v8 to v9
- Upgrades lint-staged from v14 to v15
- Moves the old .eslintrc.cjs file format to the new eslint.config.js
flat file format.

Previously, we were very specific regarding which rules are applied to
which files. Now that `extends` is no longer a thing, I have to use
deepMerge & imports instead.

This is rather uncommon and is not a documented pattern - e.g.
typescript-eslint docs want us to add the default typescript-eslint
rules to the top-level & then disable it in files using the
disable-typechecked config.

However, I hate this opt-out approach. The way I did it here adds a lot
of clarity as to which rules are applied to which files, and is pretty
easy to read. Much less black magic

## .eslintignore

These files are no longer supported (see
https://eslint.org/docs/latest/use/configure/migration-guide#ignoring-files).
I moved the entries to the ignores property in the eslint config. => one
less file in each package folder!
2024-07-09 09:50:37 -04:00

210 lines
5.8 KiB
JavaScript

import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import perfectionistNatural from 'eslint-plugin-perfectionist/configs/recommended-natural'
import { configs as regexpPluginConfigs } from 'eslint-plugin-regexp'
import eslintConfigPrettier from 'eslint-config-prettier';
import payloadPlugin from '@payloadcms/eslint-plugin'
import reactExtends from './configs/react/index.mjs'
import jestExtends from './configs/jest/index.mjs'
import globals from 'globals';
import importX from 'eslint-plugin-import-x'
import typescriptParser from '@typescript-eslint/parser'
import { deepMerge } from './deepMerge.js'
const baseRules = {
// This rule makes no sense when overriding class methods. This is used a lot in richtext-lexical.
'class-methods-use-this': 'off',
'arrow-body-style': 0,
'import-x/prefer-default-export': 'off',
'no-restricted-exports': ['warn', { restrictDefaultExports: { direct: true } }],
'no-console': 'warn',
'no-sparse-arrays': 'off',
'no-underscore-dangle': 'off',
'no-use-before-define': 'off',
'object-shorthand': 'warn',
'no-useless-escape': 'warn',
'import-x/no-duplicates': 'warn',
'perfectionist/sort-objects': [
'error',
{
type: 'natural',
order: 'asc',
'partition-by-comment': true,
'partition-by-new-line': true,
groups: ['top', 'unknown'],
'custom-groups': {
top: ['_id', 'id', 'name', 'slug', 'type'],
},
},
],
/*'perfectionist/sort-object-types': [
'error',
{
'partition-by-new-line': true,
},
],
'perfectionist/sort-interfaces': [
'error',
{
'partition-by-new-line': true,
},
],*/
'payload/no-jsx-import-statements': 'error',
}
const reactRules = {
'react/no-unused-prop-types': 'off',
'react/prop-types': 'off',
'react/require-default-props': 'off',
'react/destructuring-assignment': 'warn',
'react/no-unescaped-entities': 'warn',
'jsx-a11y/anchor-is-valid': 'warn',
'jsx-a11y/control-has-associated-label': 'warn',
'jsx-a11y/no-static-element-interactions': 'warn',
'jsx-a11y/label-has-associated-control': 'warn',
}
const typescriptRules = {
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
// Type-aware any rules:
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/unbound-method': 'warn',
// This rule doesn't work well in .tsx files
'@typescript-eslint/no-misused-promises': 'off',
'@typescript-eslint/consistent-type-imports': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
// Type-aware any rules end
// ts-expect preferred over ts-ignore. It will error if the expected error is no longer present.
'@typescript-eslint/prefer-ts-expect-error': 'error',
// By default, it errors for unused variables. This is annoying, warnings are enough.
'@typescript-eslint/no-unused-vars': [
'warn',
{
vars: 'all',
args: 'after-used',
ignoreRestSiblings: false,
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^ignore',
},
],
'@typescript-eslint/no-base-to-string': 'warn',
'@typescript-eslint/restrict-template-expressions': 'warn',
'@typescript-eslint/no-redundant-type-constituents': 'warn',
'@typescript-eslint/no-unnecessary-type-constraint': 'warn',
'@typescript-eslint/ban-types': 'warn',
}
/** @typedef {import('eslint').Linter.FlatConfig} */
let FlatConfig
/** @type {FlatConfig} */
const baseExtends = deepMerge(js.configs.recommended, perfectionistNatural , regexpPluginConfigs['flat/recommended'])
/** @type {FlatConfig[]} */
export const rootEslintConfig = [
{
name: 'Settings',
languageOptions: {
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
...globals.node,
},
parser: typescriptParser,
},
plugins: {
'import-x': importX,
},
},
{
name: 'TypeScript',
// has 3 entries: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/src/configs/recommended-type-checked.ts
...deepMerge(
baseExtends,
tseslint.configs.recommendedTypeChecked[0],
tseslint.configs.recommendedTypeChecked[1],
tseslint.configs.recommendedTypeChecked[2],
eslintConfigPrettier,
{
plugins: {
payload: payloadPlugin,
},
rules: {
...baseRules,
...typescriptRules,
},
}
),
files: ['**/*.ts'],
},
{
name: 'TypeScript-React',
...deepMerge(
baseExtends,
tseslint.configs.recommendedTypeChecked[0],
tseslint.configs.recommendedTypeChecked[1],
tseslint.configs.recommendedTypeChecked[2],
reactExtends,
eslintConfigPrettier,
{
plugins: {
payload: payloadPlugin,
},
rules: {
...baseRules,
...typescriptRules,
...reactRules,
},
}
),
files: ['**/*.tsx'],
},
{
name: 'Unit Tests',
...deepMerge(
jestExtends,
{
plugins: {
payload: payloadPlugin
},
rules: {
...baseRules,
...typescriptRules,
'@typescript-eslint/unbound-method': 'off',
},
}
),
files: ['**/*.spec.ts'],
},
{
name: 'Payload Config',
plugins: {
payload: payloadPlugin
},
rules: {
...baseRules,
...typescriptRules,
'no-restricted-exports': 'off',
},
files: ['*.config.ts', 'config.ts'],
},
]
export default rootEslintConfig