From bf1db0e8e0b92243dfcd0d313b5e30aa6ae0d5fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Vanvelthem?= Date: Sun, 23 Jul 2023 12:49:02 +0200 Subject: [PATCH] feat: improve eslint-config-bases (#4123) --- .prettierignore | 15 ++- apps/nextjs-app/.eslintrc.cjs | 2 +- apps/remix-app/.eslintrc.js | 2 +- apps/vite-app/.eslintrc.js | 2 +- packages/api-gateway/.eslintrc.js | 2 +- packages/api-gateway/jest.config.js | 49 ------- packages/api-gateway/package.json | 4 - packages/common-i18n/.eslintrc.cjs | 2 +- packages/core-lib/.eslintrc.js | 2 +- packages/db-main-prisma/.eslintrc.cjs | 2 +- packages/eslint-config-bases/.eslintrc.js | 2 +- packages/eslint-config-bases/README.md | 126 +++++++++++++----- packages/eslint-config-bases/package.json | 16 ++- .../src/bases/graphql-schema.js | 20 +++ .../eslint-config-bases/src/bases/index.js | 7 +- .../eslint-config-bases/src/bases/jest.js | 25 +++- packages/eslint-config-bases/src/bases/mdx.js | 22 +++ .../src/bases/prettier-config.js | 12 ++ .../bases/{prettier.js => prettier-plugin.js} | 0 .../src/bases/react-query.js | 26 ++++ .../eslint-config-bases/src/bases/react.js | 29 ++-- .../eslint-config-bases/src/bases/sonar.js | 1 + .../src/bases/storybook.js | 21 +-- .../src/bases/typescript.js | 88 +++--------- .../src/helpers/getDefaultIgnorePatterns.js | 17 ++- packages/ts-utils/.eslintrc.cjs | 2 +- packages/ui-lib/.eslintrc.js | 2 +- yarn.lock | 3 - 28 files changed, 268 insertions(+), 233 deletions(-) delete mode 100644 packages/api-gateway/jest.config.js create mode 100644 packages/eslint-config-bases/src/bases/graphql-schema.js create mode 100644 packages/eslint-config-bases/src/bases/mdx.js create mode 100644 packages/eslint-config-bases/src/bases/prettier-config.js rename packages/eslint-config-bases/src/bases/{prettier.js => prettier-plugin.js} (100%) create mode 100644 packages/eslint-config-bases/src/bases/react-query.js diff --git a/.prettierignore b/.prettierignore index 971b94f136b..b278116fb71 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,10 @@ -.yarn -**/.next/** -**/dist/** -**/build/** -**/tmp/** +# prettier 3.0 adds .gitignore by default +**/.yarn +**/.next +**/.out +**/dist +**/build +**/.tmp +**/.cache +**/.turbo + diff --git a/apps/nextjs-app/.eslintrc.cjs b/apps/nextjs-app/.eslintrc.cjs index a5cc725cb70..608b9b370d3 100644 --- a/apps/nextjs-app/.eslintrc.cjs +++ b/apps/nextjs-app/.eslintrc.cjs @@ -28,7 +28,7 @@ module.exports = { // Add specific rules for nextjs 'plugin:@next/next/core-web-vitals', // Apply prettier and disable incompatible rules - '@your-org/eslint-config-bases/prettier', + '@your-org/eslint-config-bases/prettier-plugin', ], rules: { '@typescript-eslint/naming-convention': 'off', diff --git a/apps/remix-app/.eslintrc.js b/apps/remix-app/.eslintrc.js index 17276d853de..4eac6aed790 100644 --- a/apps/remix-app/.eslintrc.js +++ b/apps/remix-app/.eslintrc.js @@ -34,7 +34,7 @@ module.exports = { // Specific rules for remix '@remix-run/eslint-config', // Apply prettier and disable incompatible rules - '@your-org/eslint-config-bases/prettier', + '@your-org/eslint-config-bases/prettier-plugin', ], env: { browser: true, diff --git a/apps/vite-app/.eslintrc.js b/apps/vite-app/.eslintrc.js index d27e40e861a..25b1bd8e478 100644 --- a/apps/vite-app/.eslintrc.js +++ b/apps/vite-app/.eslintrc.js @@ -25,7 +25,7 @@ module.exports = { '@your-org/eslint-config-bases/react', '@your-org/eslint-config-bases/rtl', // Apply prettier and disable incompatible rules - '@your-org/eslint-config-bases/prettier', + '@your-org/eslint-config-bases/prettier-plugin', ], rules: { 'jsx-a11y/anchor-is-valid': 'off', diff --git a/packages/api-gateway/.eslintrc.js b/packages/api-gateway/.eslintrc.js index 001ec5d8a59..d8217355715 100644 --- a/packages/api-gateway/.eslintrc.js +++ b/packages/api-gateway/.eslintrc.js @@ -21,7 +21,7 @@ module.exports = { '@your-org/eslint-config-bases/typescript', '@your-org/eslint-config-bases/sonar', // Apply prettier and disable incompatible rules - '@your-org/eslint-config-bases/prettier', + '@your-org/eslint-config-bases/prettier-plugin', ], rules: { // optional overrides per project diff --git a/packages/api-gateway/jest.config.js b/packages/api-gateway/jest.config.js deleted file mode 100644 index 1d36c5d9e8e..00000000000 --- a/packages/api-gateway/jest.config.js +++ /dev/null @@ -1,49 +0,0 @@ -// @ts-check - -const { pathsToModuleNameMapper } = require('ts-jest'); - -const tsConfigFile = './tsconfig.jest.json'; -const { getJestCachePath } = require('../../cache.config'); - -const packageJson = require('./package.json'); -const { compilerOptions: baseTsConfig } = require('./tsconfig.json'); - -// Take the paths from tsconfig automatically from base tsconfig.json -// @link https://kulshekhar.github.io/ts-jest/docs/paths-mapping -const getTsConfigBasePaths = () => { - return baseTsConfig.paths - ? pathsToModuleNameMapper(baseTsConfig.paths, { - prefix: '/', - }) - : {}; -}; - -/** @type {import('ts-jest').JestConfigWithTsJest} */ -const config = { - displayName: `${packageJson.name}:unit`, - cacheDirectory: getJestCachePath(packageJson.name), - testEnvironment: 'jsdom', - verbose: true, - rootDir: './src', - transform: { - '^.+\\.m?[tj]sx?$': [ - 'ts-jest', - { - tsconfig: tsConfigFile, - }, - ], - }, - setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'], - testMatch: ['/**/*.{spec,test}.{js,jsx,ts,tsx}'], - moduleNameMapper: { - // For @testing-library/react - '^@/test-utils$': '/../config/jest/test-utils', - ...getTsConfigBasePaths(), - }, - // false by default, overrides in cli, ie: yarn test:unit --collect-coverage=true - collectCoverage: false, - coverageDirectory: '/../coverage', - collectCoverageFrom: ['/**/*.{ts,tsx,js,jsx}', '!**/*.test.ts'], -}; - -module.exports = config; diff --git a/packages/api-gateway/package.json b/packages/api-gateway/package.json index 1519f3507f3..5df0fdb878c 100644 --- a/packages/api-gateway/package.json +++ b/packages/api-gateway/package.json @@ -25,7 +25,6 @@ "clean": "rimraf ./dist ./coverage ./tsconfig.tsbuildinfo", "fix-all-files": "eslint . --ext .ts,.tsx,.js,.jsx,.cjs,.mjs --fix", "lint": "eslint . --ext .ts,.tsx,.js,.jsx,.cjs,.mjs --cache --cache-location ../../.cache/eslint/api-gateway.eslintcache", - "test": "jest --config jest.config.js --passWithNoTests", "typecheck": "tsc --project ./tsconfig.json --noEmit" }, "dependencies": { @@ -34,17 +33,14 @@ "@graphql-mesh/runtime": "^0.95.3" }, "devDependencies": { - "@types/jest": "29.5.3", "@types/node": "20.4.4", "@your-org/eslint-config-bases": "workspace:^", "cross-env": "7.0.3", "eslint": "8.45.0", "graphql": "16.7.1", - "jest": "29.6.1", "npm-run-all": "4.1.5", "prettier": "3.0.0", "rimraf": "5.0.1", - "ts-jest": "29.1.1", "tsup": "7.1.0", "typescript": "5.1.6" }, diff --git a/packages/common-i18n/.eslintrc.cjs b/packages/common-i18n/.eslintrc.cjs index 3644d250d99..e588c262ed6 100644 --- a/packages/common-i18n/.eslintrc.cjs +++ b/packages/common-i18n/.eslintrc.cjs @@ -17,7 +17,7 @@ module.exports = { extends: [ '@your-org/eslint-config-bases/typescript', // Apply prettier and disable incompatible rules - '@your-org/eslint-config-bases/prettier', + '@your-org/eslint-config-bases/prettier-plugin', ], rules: { // optional overrides per project diff --git a/packages/core-lib/.eslintrc.js b/packages/core-lib/.eslintrc.js index 84c5d3f3259..3dce6c68225 100644 --- a/packages/core-lib/.eslintrc.js +++ b/packages/core-lib/.eslintrc.js @@ -25,7 +25,7 @@ module.exports = { '@your-org/eslint-config-bases/rtl', '@your-org/eslint-config-bases/react', // Apply prettier and disable incompatible rules - '@your-org/eslint-config-bases/prettier', + '@your-org/eslint-config-bases/prettier-plugin', ], rules: { // optional overrides per project diff --git a/packages/db-main-prisma/.eslintrc.cjs b/packages/db-main-prisma/.eslintrc.cjs index 30580492854..dee7cc1cd8a 100644 --- a/packages/db-main-prisma/.eslintrc.cjs +++ b/packages/db-main-prisma/.eslintrc.cjs @@ -22,7 +22,7 @@ module.exports = { '@your-org/eslint-config-bases/sonar', '@your-org/eslint-config-bases/regexp', // Apply prettier and disable incompatible rules - '@your-org/eslint-config-bases/prettier', + '@your-org/eslint-config-bases/prettier-plugin', ], overrides: [ // optional overrides per project file match diff --git a/packages/eslint-config-bases/.eslintrc.js b/packages/eslint-config-bases/.eslintrc.js index c490dc3e2e7..8ce09b23a97 100644 --- a/packages/eslint-config-bases/.eslintrc.js +++ b/packages/eslint-config-bases/.eslintrc.js @@ -3,5 +3,5 @@ const { getDefaultIgnorePatterns } = require('./src/helpers'); module.exports = { root: true, ignorePatterns: [...getDefaultIgnorePatterns()], - extends: ['./src/bases/typescript', './src/bases/prettier'], + extends: ['./src/bases/typescript', './src/bases/prettier-plugin'], }; diff --git a/packages/eslint-config-bases/README.md b/packages/eslint-config-bases/README.md index ac82416c7b0..593e04b030a 100644 --- a/packages/eslint-config-bases/README.md +++ b/packages/eslint-config-bases/README.md @@ -9,38 +9,41 @@ packages that lives in a [monorepo](https://github.com/belgattitude/nextjs-monor ## Features -## Features - - **Monorepo friendly:** Each workspace can have its own config. - **Composable:** Compose your workspace eslint config from pre-defined bases. - **Peace of mind:** Plugins does not need to be installed per workspaces, thx to [@rushstack/eslint-patch](https://www.npmjs.com/package/@rushstack/eslint-patch). -- **Extensible:** Easily add additional plugins per workspaces (ie: nextjs, remix...) -- **Performance:** Plugins enabled on file conventions patterns to increase perf. +- **Performance!:** Plugins enabled on file conventions patterns to increase perf. ## Install Add the following devDependencies to workspace (apps/packages in monorepo) or main project package.json. ```bash -$ yarn add --dev eslint -$ yarn add --dev @your-org/eslint-config-bases:"workspace:^" +$ yarn add --dev eslint @your-org/eslint-config-bases ``` -> **Tip** the [workspace:^](https://yarnpkg.com/features/workspaces#workspace-ranges-workspace) is supported by yarn and pnpm. +> PS: To keep the size low, if you use the following plugins: +> +> - **graphql**: `yarn add --dev @graphql-eslint/eslint-plugin` +> - **mdx**: `yarn add --dev eslint-plugin-mdx`. +> - **tailwind**: `yarn add --dev eslint-plugin-tailwindcss`. +> In one line +> +> ```bash +> yarn add +> ``` ## Usage -In your app or package, create an `./apps/my-app/.eslintrc.js` file that extends any of the -existing base configs. For example: +Create an `./apps/my-app/.eslintrc.js` or `./apps/my-app/.eslintrc.cjs` +file that extends any of the existing base configs. For example: ```javascript -// Workaround for https://github.com/eslint/eslint/issues/3458 (re-export of @rushstack/eslint-patch) +// next line only required if you're using a monorepo require("@your-org/eslint-config-bases/patch/modern-module-resolution"); module.exports = { - // Be sure to set root to true in monorepo. root: true, - // Will help typescript extended rules. parserOptions: { tsconfigRootDir: __dirname, project: "tsconfig.json", @@ -51,23 +54,34 @@ module.exports = { "@your-org/eslint-config-bases/sonar", "@your-org/eslint-config-bases/regexp", "@your-org/eslint-config-bases/react", + "@your-org/eslint-config-bases/react-query", "@your-org/eslint-config-bases/jest", "@your-org/eslint-config-bases/rtl", - "@your-org/eslint-config-bases/storybook", - "@your-org/eslint-config-bases/playwright", + + // "@your-org/eslint-config-bases/mdx", + + // "@your-org/eslint-config-bases/graphql-schema", + // "@your-org/eslint-config-bases/storybook", + // "@your-org/eslint-config-bases/playwright", // Add specific rules for your framework if needed. // ie: - // - nextjs: 'plugin:@next/next/core-web-vitals', + // - nextjs: 'next/core-web-vitals', // - remix: '@remix-run/eslint-config', // ... - // Post configure the prettier base so there won't be - // any conficts between eslint / prettier - "@your-org/eslint-config-bases/prettier", + // Post configure the prettier base and run prettier + // without conflicts thx to eslint-plugin-prettier + "@your-org/eslint-config-bases/prettier-plugin", + // Alternatively to the above if you're already running prettier + // we can get a speed up by using on eslint-prettier-config + // "@your-org/eslint-config-bases/prettier-config", ], rules: { // Specific global rules for your app or package + // Might help is next eslint plugin does not locate pages + // https://nextjs.org/docs/messages/no-html-link-for-pages#pagesdir + // '@next/next/no-html-link-for-pages': ['error', `${__dirname}/src/pages`], }, overrides: [ // Specific file rules for your app or package @@ -75,24 +89,35 @@ module.exports = { }; ``` -> **Tip:** "@your-org/eslint-config-bases/prettier" must be set at the end to disable any -> conflicting rules. +> **Tip:** +> +> - **Prettier**: `@your-org/eslint-config-bases/prettier-plugin` and `@your-org/eslint-config-bases/prettier-config` are +> mutually exclusives. Choose one. The `prettier-config` suppose that you run prettier independently. The `prettier-plugin` +> will run prettier for you. Easiest the `prettier-plugin`, fastest `prettier-config` (this mostly depends +> if you set up and persist caches as well) +> - **Performance**: Some rules are known to be slow (ie: `import/namespace`...). Slowest identified rules are disabled depending +> on context (ie: `*.test.tsx?` might not need everything). Depending on project +> it's possible to disable entirely some slow rules (ie: `'import/namespace': 'off'`). A good tip +> run eslint with the `TIMING=1` to identify slow rules. ## Bases You can find the bases in [./src/bases](./src/bases). -| Base | Match convention | Scope | -| :-------------------------------------- | :-------------------------------- | :-------------------------------------------------------------- | -| [typescript](./src/bases/typescript.js) | _all_ | Naming conventions, consistent imports, import sorting... | -| [sonar](./src/bases/sonar.js) | `*.{js,jsx,ts,tsx}` | Keep levels of code complexity sane. (excl test and stories) | -| [regexp](./src/bases/regexp.js) | `*.{js,jsx,jsx,tsx}` | Keep regexp consistent and safer. | -| [react](./src/bases/react.js) | `*.{jsx,tsx}` | Recommendations for react, react-hooks and jsx projects. | -| [jest](./src/bases/jest.js) | `**/?(*.)+(test).{js,jsx,ts,tsx}` | Catch inconsistencies or error in jest tests. | -| [rtl](./src/bases/rtl.js) | `**/?(*.)+(test).{js,jsx,ts,tsx}` | Potential errors / deprecations in react-testing-library tests. | -| [storybook](./src/bases/storybook.js) | `*.stories.{ts,tsx,mdx}` | Potential errors / deprecations in stories. | -| [playwright](./src/bases/playwright.js) | `**/e2e/**/*.test.{js,ts}` | Post configure eslint for prettier compatibility. | -| [prettier](./src/bases/prettier.js) | _all_ | Post configure eslint for prettier compatibility. | +| Base | Match convention | Scope | +| :------------------------------------------------ | :-------------------------------- | :-------------------------------------------------------------- | +| [typescript](./src/bases/typescript.js) | _all_ | Naming conventions, consistent imports, import sorting... | +| [sonar](./src/bases/sonar.js) | `*.{js,jsx,ts,tsx}` | Keep levels of code complexity sane. (excl test and stories) | +| [regexp](./src/bases/regexp.js) | `*.{js,jsx,jsx,tsx}` | Keep regexp consistent and safer. | +| [react](./src/bases/react.js) | `*.{jsx,tsx}` | Recommendations for react, react-hooks and jsx projects. | +| [react-query](./src/bases/react-query.js) | `**/?(*.)+(test).{js,jsx,ts,tsx}` | Enforce "recommended" react-query usage. | +| [jest](./src/bases/jest.js) | `**/?(*.)+(test).{js,jsx,ts,tsx}` | Catch inconsistencies or error in jest tests. | +| [rtl](./src/bases/rtl.js) | `**/?(*.)+(test).{js,jsx,ts,tsx}` | Potential errors / deprecations in react-testing-library tests. | +| [graphql-schema](./src/bases/graphql-schema.js) | `*.graphql` | Ensure validity of graphql schema files. | +| [mdx](./src/bases/mdx.js) | _all_ | Mdx validation | +| [storybook](./src/bases/storybook.js) | `*.stories.{ts,tsx,mdx}` | Potential errors / deprecations in stories. | +| [playwright](./src/bases/playwright.js) | `**/e2e/**/*.test.{js,ts}` | Keep "recommended" playwright usage. | +| [prettier-plugin](./src/bases/prettier-plugin.js) | _all_ | Post configure eslint for prettier compatibility. | > **Notes**: > @@ -100,19 +125,37 @@ You can find the bases in [./src/bases](./src/bases). > rules. For example the [react base](./src/bases/react.js) will tune the naming conventions > for function components and increase recommended cognitive complexity. The [typescript base](./src/bases/typescript.js) > will also relax conventions for javascript files. -> > - Based on filename conventions some rules are relaxed or disabled to avoid false positives and > keep a good level of performance. For example the [sonar base](./src/bases/sonar.js) won't run on > test and storybook files. If you work on different conventions the patterns must be updated. +## Cyclic deps + +Due to performance considerations the [import/no-cycle](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-cycle.md) isn't enabled by default. This rule +can prevent subtle and hard to debug bugs. Depending on the project you can enable it either +by setting and env variable `ESLINT_IMPORT_NO_CYCLE=true yarn lint` (will default to `import/no-cycle: 2`) or by adding it +to the extended rules. + ## Prettier integration -To prevent conflicts between prettier and eslint, you must re-export the prettier base from `@your-org/eslint-config-bases`. +Two ways to work with prettier. + +- `@your-org/eslint-config-bases/prettier-plugin` - eslint will run prettier under the hood +- `@your-org/eslint-config-bases/prettier-config` - eslint will just disable some conflicting rules (so you'll need to run prettier after) + +The first method is recommended for simplicity. For best perf use the cache option to run eslint. + +Tune the behaviour by creating a config in ` .prettierrc.js` ```javascript +// @ts-check const { getPrettierConfig } = require("@your-org/eslint-config-bases/helpers"); + +/** + * @type {import('prettier').Config} + */ module.exports = { - ...prettierConfig, + ...getPrettierConfig(), overrides: [ // whatever you need ], @@ -167,6 +210,21 @@ Generic typescript project, mostly based on | :------------------------------------------------------------------------------------ | :------ | | [eslint-plugin-regexp/recommended](https://github.com/ota-meshi/eslint-plugin-regexp) | | +### Mdx + +To tune the behaviour, you can add setting in the top level config + +```js +module.exports = { + settings: { + "mdx/code-blocks": true, + // optional, if you want to disable language mapper, set it to `false` + // if you want to override the default language mapper inside, you can provide your own + "mdx/language-mapper": {}, + }, +}; +``` + ### Etc ... diff --git a/packages/eslint-config-bases/package.json b/packages/eslint-config-bases/package.json index 231a5af88bd..d0e79204aa2 100644 --- a/packages/eslint-config-bases/package.json +++ b/packages/eslint-config-bases/package.json @@ -14,18 +14,30 @@ "./helpers": { "require": "./src/helpers/index.js" }, + "./graphql-schema": { + "require": "./src/bases/graphql-schema.js" + }, + "./mdx": { + "require": "./src/bases/mdx.js" + }, "./jest": { "require": "./src/bases/jest.js" }, "./playwright": { "require": "./src/bases/playwright.js" }, - "./prettier": { - "require": "./src/bases/prettier.js" + "./prettier-config": { + "require": "./src/bases/prettier-config.js" + }, + "./prettier-plugin": { + "require": "./src/bases/prettier-plugin.js" }, "./react": { "require": "./src/bases/react.js" }, + "./react-query": { + "require": "./src/bases/react-query.js" + }, "./rtl": { "require": "./src/bases/rtl.js" }, diff --git a/packages/eslint-config-bases/src/bases/graphql-schema.js b/packages/eslint-config-bases/src/bases/graphql-schema.js new file mode 100644 index 00000000000..5f1a52463f5 --- /dev/null +++ b/packages/eslint-config-bases/src/bases/graphql-schema.js @@ -0,0 +1,20 @@ +/** + * Opinionated config base for projects using graphql schemas (*.graphql) + * @see https://github.com/belgattitude/nextjs-monorepo-example/tree/main/packages/eslint-config-bases + */ +const graphqlSchemaPatterns = { + files: ['*.graphql'], +}; + +module.exports = { + overrides: [ + { + files: graphqlSchemaPatterns.files, + // @see https://github.com/B2o5T/graphql-eslint + extends: 'plugin:@graphql-eslint/schema-recommended', + rules: { + '@graphql-eslint/known-type-names': 'error', + }, + }, + ], +}; diff --git a/packages/eslint-config-bases/src/bases/index.js b/packages/eslint-config-bases/src/bases/index.js index 2c20b418409..f69e3553f68 100644 --- a/packages/eslint-config-bases/src/bases/index.js +++ b/packages/eslint-config-bases/src/bases/index.js @@ -1,11 +1,16 @@ module.exports = { + graphqlSchema: require('./graphql-schema'), jest: require('./jest'), + mdx: require('./mdx'), playwright: require('./playwright'), + 'prettier-plugin': require('./prettier-plugin'), + 'prettier-config': require('./prettier-config'), react: require('./react'), regexp: require('./regexp'), + reactQuery: require('./react-query'), reactTestingLibrary: require('./rtl'), - sonar: require('./sonar'), storybook: require('./storybook'), + sonar: require('./sonar'), tailwind: require('./tailwind'), typescript: require('./typescript'), }; diff --git a/packages/eslint-config-bases/src/bases/jest.js b/packages/eslint-config-bases/src/bases/jest.js index 8e7c8ceabe0..9c536141073 100644 --- a/packages/eslint-config-bases/src/bases/jest.js +++ b/packages/eslint-config-bases/src/bases/jest.js @@ -12,6 +12,12 @@ module.exports = { es6: true, node: true, }, + settings: { + // To prevent autodetection issues in monorepos or via vitest + jest: { + version: 'latest', + }, + }, overrides: [ { // Perf: To ensure best performance enable eslint-plugin-jest for test files only. @@ -19,13 +25,6 @@ module.exports = { // @see https://github.com/jest-community/eslint-plugin-jest extends: ['plugin:jest/recommended'], rules: { - 'jest/prefer-hooks-in-order': 'error', - 'jest/prefer-hooks-on-top': 'error', - 'jest/no-duplicate-hooks': 'error', - 'jest/no-test-return-statement': 'error', - 'jest/prefer-strict-equal': 'error', - 'jest/prefer-to-have-length': 'error', - 'jest/consistent-test-it': ['error', { fn: 'it' }], // Relax rules that are known to be slow and less useful in a test context 'import/namespace': 'off', 'import/default': 'off', @@ -37,6 +36,18 @@ module.exports = { '@typescript-eslint/no-empty-function': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/ban-ts-comment': 'off', + // Enable Jest rules + 'jest/no-focused-tests': 'error', + 'jest/prefer-mock-promise-shorthand': 'error', + 'jest/no-commented-out-tests': 'error', + 'jest/prefer-hooks-in-order': 'error', + 'jest/prefer-hooks-on-top': 'error', + 'jest/no-conditional-in-test': 'error', + 'jest/no-duplicate-hooks': 'error', + 'jest/no-test-return-statement': 'error', + 'jest/prefer-strict-equal': 'error', + 'jest/prefer-to-have-length': 'error', + 'jest/consistent-test-it': ['error', { fn: 'it' }], }, }, ], diff --git a/packages/eslint-config-bases/src/bases/mdx.js b/packages/eslint-config-bases/src/bases/mdx.js new file mode 100644 index 00000000000..01185f188c2 --- /dev/null +++ b/packages/eslint-config-bases/src/bases/mdx.js @@ -0,0 +1,22 @@ +/** + * Opinionated config base for https://github.com/mdx-js/eslint-mdx + * @see https://github.com/belgattitude/nextjs-monorepo-example/tree/main/packages/eslint-config-bases + */ + +const mdxPatterns = { + files: ['*.mdx'], +}; + +module.exports = { + overrides: [ + { + // For performance enable this only on mdx files + files: mdxPatterns.files, + extends: ['plugin:mdx/recommended'], + parser: 'eslint-mdx', + rules: { + '@typescript-eslint/consistent-type-exports': 'off', + }, + }, + ], +}; diff --git a/packages/eslint-config-bases/src/bases/prettier-config.js b/packages/eslint-config-bases/src/bases/prettier-config.js new file mode 100644 index 00000000000..0ce0debd4ba --- /dev/null +++ b/packages/eslint-config-bases/src/bases/prettier-config.js @@ -0,0 +1,12 @@ +/** + * Custom config base for projects using prettier. + * @see https://github.com/belgattitude/nextjs-monorepo-example/tree/main/packages/eslint-config-bases + */ + +module.exports = { + extends: ['prettier'], + rules: { + 'arrow-body-style': 'off', + 'prefer-arrow-callback': 'off', + }, +}; diff --git a/packages/eslint-config-bases/src/bases/prettier.js b/packages/eslint-config-bases/src/bases/prettier-plugin.js similarity index 100% rename from packages/eslint-config-bases/src/bases/prettier.js rename to packages/eslint-config-bases/src/bases/prettier-plugin.js diff --git a/packages/eslint-config-bases/src/bases/react-query.js b/packages/eslint-config-bases/src/bases/react-query.js new file mode 100644 index 00000000000..e74ea7ad9e3 --- /dev/null +++ b/packages/eslint-config-bases/src/bases/react-query.js @@ -0,0 +1,26 @@ +/** + * Opinionated config base for projects using react. + * @see https://github.com/belgattitude/nextjs-monorepo-example/tree/main/packages/eslint-config-bases + */ + +const reactPatterns = { + files: ['*.{jsx,tsx}'], +}; + +/** + * Fine-tune naming convention react typescript jsx (function components) + * @link https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/naming-convention.md + */ + +module.exports = { + overrides: [ + { + files: [...reactPatterns.files], + extends: [ + // @see https://tanstack.com/query/v4/docs/react/eslint/eslint-plugin-query + 'plugin:@tanstack/eslint-plugin-query/recommended', + ], + // rules: { }, + }, + ], +}; diff --git a/packages/eslint-config-bases/src/bases/react.js b/packages/eslint-config-bases/src/bases/react.js index 90dd2116691..f2e70699b3a 100644 --- a/packages/eslint-config-bases/src/bases/react.js +++ b/packages/eslint-config-bases/src/bases/react.js @@ -7,6 +7,15 @@ const reactPatterns = { files: ['*.{jsx,tsx}'], }; +const stylesPatterns = { + files: ['*.styles.{js,ts}', 'styles.{js,ts}'], +}; + +/** + * Fine-tune naming convention react typescript jsx (function components) + * @link https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/naming-convention.md + */ + module.exports = { env: { browser: true, @@ -20,7 +29,7 @@ module.exports = { }, overrides: [ { - files: reactPatterns.files, + files: [...reactPatterns.files, ...stylesPatterns.files], extends: [ // @see https://github.com/yannickcr/eslint-plugin-react 'plugin:react/recommended', @@ -36,24 +45,6 @@ module.exports = { 'react/no-unescaped-entities': ['error', { forbid: ['>'] }], 'react/prop-types': 'off', 'react/react-in-jsx-scope': 'off', - // Fine-tune naming convention react typescript jsx (function components) - // https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/naming-convention.md - '@typescript-eslint/naming-convention': [ - 'warn', - { - selector: 'variable', - format: ['camelCase', 'PascalCase'], - }, - { - selector: ['function'], - format: ['camelCase', 'PascalCase'], - }, - { - selector: 'parameter', - format: ['camelCase', 'PascalCase'], - leadingUnderscore: 'allow', - }, - ], }, }, ], diff --git a/packages/eslint-config-bases/src/bases/sonar.js b/packages/eslint-config-bases/src/bases/sonar.js index 29e5204690d..305744af1d7 100644 --- a/packages/eslint-config-bases/src/bases/sonar.js +++ b/packages/eslint-config-bases/src/bases/sonar.js @@ -24,6 +24,7 @@ module.exports = { extends: ['plugin:sonarjs/recommended'], rules: { 'sonarjs/no-nested-template-literals': 'off', + 'sonarjs/prefer-single-boolean-return': 'off', }, }, { diff --git a/packages/eslint-config-bases/src/bases/storybook.js b/packages/eslint-config-bases/src/bases/storybook.js index 5ab980d5f00..913b3b85055 100644 --- a/packages/eslint-config-bases/src/bases/storybook.js +++ b/packages/eslint-config-bases/src/bases/storybook.js @@ -18,26 +18,7 @@ module.exports = { // For performance run storybook/recommended on test files, not regular code files: storybookPatterns.files, extends: ['plugin:storybook/recommended'], - rules: { - // Fine-tune naming convention for storybook - // https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/naming-convention.md - '@typescript-eslint/naming-convention': [ - 'warn', - { - selector: 'variable', - format: ['camelCase', 'PascalCase'], - }, - { - selector: ['function'], - format: ['camelCase', 'PascalCase'], - }, - { - selector: 'parameter', - format: ['camelCase', 'PascalCase'], - leadingUnderscore: 'allow', - }, - ], - }, + rules: {}, }, ], }; diff --git a/packages/eslint-config-bases/src/bases/typescript.js b/packages/eslint-config-bases/src/bases/typescript.js index fe6a6f87af5..a3be2d17d2b 100644 --- a/packages/eslint-config-bases/src/bases/typescript.js +++ b/packages/eslint-config-bases/src/bases/typescript.js @@ -3,9 +3,15 @@ * @see https://github.com/belgattitude/nextjs-monorepo-example/tree/main/packages/eslint-config-bases */ +// Allow to pass an env to check cycles, defaults to 2 (lint time+++) +// @see https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-cycle.md +// @see https://medium.com/@steven-lemon182/are-typescript-barrel-files-an-anti-pattern-72a713004250 +const checkCycles = process.env?.ESLINT_IMPORT_NO_CYCLE === 'true'; + module.exports = { env: { es6: true, + browser: true, node: true, }, parser: '@typescript-eslint/parser', @@ -19,21 +25,22 @@ module.exports = { sourceType: 'module', }, settings: { + 'import/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx', '.mts'], + }, 'import/resolver': { typescript: {}, }, }, extends: [ 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', 'plugin:import/recommended', 'plugin:import/typescript', - 'plugin:@typescript-eslint/recommended', ], rules: { - // Useful by disabled as it is very slow / add per project - // https://medium.com/@steven-lemon182/are-typescript-barrel-files-an-anti-pattern-72a713004250 - // 'import/no-cycle': 2, - + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-cycle.md + ...(checkCycles ? { 'import/no-cycle': 2 } : {}), // will use 'import/no-duplicates'. 'no-duplicate-imports': 'off', 'spaced-comment': [ @@ -54,8 +61,8 @@ module.exports = { 'linebreak-style': ['error', 'unix'], 'no-empty-function': 'off', 'import/default': ['error'], - // Slow: https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/namespace.md - 'import/namespace': 'off', // ['error'] If you want the extra check (typechecks will spot most issues already) + // Caution this rule is slow https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/namespace.md + 'import/namespace': 'off', // ['error'] If you want the extra check (typechecking will spot most issues already) // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-duplicates.md 'import/no-duplicates': [ 'error', @@ -101,67 +108,7 @@ module.exports = { '@typescript-eslint/consistent-type-exports': 'error', '@typescript-eslint/consistent-type-imports': [ 'error', - { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, - ], - '@typescript-eslint/naming-convention': [ - 'error', - { - selector: 'default', - format: ['camelCase'], - leadingUnderscore: 'forbid', - trailingUnderscore: 'forbid', - }, - { - selector: 'variable', - format: ['camelCase'], - leadingUnderscore: 'allow', - }, - { - selector: ['function'], - format: ['camelCase'], - }, - { - selector: 'parameter', - format: ['camelCase'], - leadingUnderscore: 'allow', - }, - { - selector: 'class', - format: ['PascalCase'], - }, - { - selector: 'classProperty', - format: ['camelCase'], - leadingUnderscore: 'allow', - }, - { - selector: 'objectLiteralProperty', - format: [ - 'camelCase', - // Some external libraries use snake_case for params - 'snake_case', - // Env variables are generally uppercase - 'UPPER_CASE', - // DB / Graphql might use PascalCase for relationships - 'PascalCase', - ], - leadingUnderscore: 'allowSingleOrDouble', - trailingUnderscore: 'allowSingleOrDouble', - }, - { - selector: ['typeAlias', 'interface'], - format: ['PascalCase'], - }, - { - selector: ['typeProperty'], - format: ['camelCase'], - // For graphql __typename - leadingUnderscore: 'allowDouble', - }, - { - selector: ['typeParameter'], - format: ['PascalCase'], - }, + { prefer: 'type-imports' }, ], }, overrides: [ @@ -172,28 +119,25 @@ module.exports = { sourceType: 'module', }, rules: { - '@typescript-eslint/naming-convention': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/consistent-type-exports': 'off', '@typescript-eslint/consistent-type-imports': 'off', }, }, { - // commonjs or assumed + // javascript commonjs files: ['*.js', '*.cjs'], parser: 'espree', parserOptions: { ecmaVersion: 2020, }, rules: { - '@typescript-eslint/naming-convention': 'off', '@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/consistent-type-exports': 'off', '@typescript-eslint/consistent-type-imports': 'off', - 'import/order': 'off', }, }, ], diff --git a/packages/eslint-config-bases/src/helpers/getDefaultIgnorePatterns.js b/packages/eslint-config-bases/src/helpers/getDefaultIgnorePatterns.js index cff9c3b7465..b4fd427e6aa 100644 --- a/packages/eslint-config-bases/src/helpers/getDefaultIgnorePatterns.js +++ b/packages/eslint-config-bases/src/helpers/getDefaultIgnorePatterns.js @@ -1,13 +1,16 @@ const getDefaultIgnorePatterns = () => { + // Hacky way to silence @yarnpkg/doctor about node_modules detection return [ - // Hacky way to silence @yarnpkg/doctor about node_modules detection - `**/${'node'}_modules`, - '.cache', + `${'node'}_modules}`, + `**/${'node'}_modules}`, '**/.cache', - '**/build', - '**/dist', - '**/.storybook', - '**/storybook-static', + 'build', + 'dist', + 'storybook-static', + '.yarn', + '.turbo', + `**/.turbo`, + '.out', ]; }; diff --git a/packages/ts-utils/.eslintrc.cjs b/packages/ts-utils/.eslintrc.cjs index 285437c08c5..5f2571856e5 100644 --- a/packages/ts-utils/.eslintrc.cjs +++ b/packages/ts-utils/.eslintrc.cjs @@ -20,7 +20,7 @@ module.exports = { '@your-org/eslint-config-bases/regexp', '@your-org/eslint-config-bases/jest', // Apply prettier and disable incompatible rules - '@your-org/eslint-config-bases/prettier', + '@your-org/eslint-config-bases/prettier-plugin', ], rules: { // optional overrides per project diff --git a/packages/ui-lib/.eslintrc.js b/packages/ui-lib/.eslintrc.js index 19aeda99eab..871fed39dcb 100644 --- a/packages/ui-lib/.eslintrc.js +++ b/packages/ui-lib/.eslintrc.js @@ -26,7 +26,7 @@ module.exports = { '@your-org/eslint-config-bases/storybook', '@your-org/eslint-config-bases/react', // Apply prettier and disable incompatible rules - '@your-org/eslint-config-bases/prettier', + '@your-org/eslint-config-bases/prettier-plugin', ], rules: { // optional overrides per project diff --git a/yarn.lock b/yarn.lock index 9e1fca5ef06..a19f2563639 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8275,17 +8275,14 @@ __metadata: "@graphql-mesh/cli": "npm:^0.85.4" "@graphql-mesh/openapi": "npm:^0.94.9" "@graphql-mesh/runtime": "npm:^0.95.3" - "@types/jest": "npm:29.5.3" "@types/node": "npm:20.4.4" "@your-org/eslint-config-bases": "workspace:^" cross-env: "npm:7.0.3" eslint: "npm:8.45.0" graphql: "npm:16.7.1" - jest: "npm:29.6.1" npm-run-all: "npm:4.1.5" prettier: "npm:3.0.0" rimraf: "npm:5.0.1" - ts-jest: "npm:29.1.1" tsup: "npm:7.1.0" typescript: "npm:5.1.6" peerDependencies: