From 37608c58df14701af078e8bacaee137ff3b84835 Mon Sep 17 00:00:00 2001 From: Luke Hansford Date: Mon, 2 Sep 2024 15:28:01 +0200 Subject: [PATCH 1/2] Update rules --- packages/base/eslint.config.js | 67 +++++++--------------------------- 1 file changed, 13 insertions(+), 54 deletions(-) diff --git a/packages/base/eslint.config.js b/packages/base/eslint.config.js index 9c96923..8a1c741 100644 --- a/packages/base/eslint.config.js +++ b/packages/base/eslint.config.js @@ -36,27 +36,8 @@ export default tseslint.config( { plugins: { prettier: prettierPlugin }, rules: { - // Core rules replaced by Typescript rules - 'no-use-before-define': 'off', - 'consistent-return': 'off', // TypeScript takes care of checking return - // 'import/no-unresolved': 'off', // Doesn't work properly with TypeScript // TODO: - 'no-extra-parens': 'off', - - // Additional Fishbrain rules - '@typescript-eslint/await-thenable': 'error', - '@typescript-eslint/ban-ts-ignore': 'off', - // This rule required so many exceptions that it was getting difficult to maintain. So - // just name things sensibly :) - '@typescript-eslint/naming-convention': 'off', - '@typescript-eslint/explicit-member-accessibility': 'off', - '@typescript-eslint/interface-name-prefix': 'off', - // Noop functions are a common pattern we use during testing, so we don't want to enable it. - '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-empty-function': 'off', // Noop functions are a common pattern we use during testing, so we don't want to enable it. '@typescript-eslint/no-explicit-any': ['error', { fixToUnknown: true }], - '@typescript-eslint/no-extraneous-class': 'error', - '@typescript-eslint/no-floating-promises': 'error', - '@typescript-eslint/no-for-in-array': 'error', - '@typescript-eslint/no-inferrable-types': 'off', '@typescript-eslint/no-unused-vars': [ 'error', { @@ -65,27 +46,22 @@ export default tseslint.config( argsIgnorePattern: '^_', }, ], - '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/promise-function-async': 'error', - '@typescript-eslint/triple-slash-reference': [ + '@typescript-eslint/restrict-template-expressions': [ 'error', - { types: 'prefer-import' }, + { + allowAny: true, + allowNumber: true, + }, ], - '@typescript-eslint/prefer-readonly': 'error', - - // Warns if a type assertion does not change the type of an expression - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.md - '@typescript-eslint/no-unnecessary-type-assertion': 'error', - - // Enforce includes method over indexOf method - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-includes.md - '@typescript-eslint/prefer-includes': 'error', - - // Enforce the use of String#startsWith and String#endsWith instead of other equivalent methods of checking substrings - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-string-starts-ends-with.md - '@typescript-eslint/prefer-string-starts-ends-with': 'error', - curly: ['error', 'all'], + 'max-lines': ['error', { max: 300, skipComments: true }], + 'no-magic-numbers': [ + 'error', + { ignoreArrayIndexes: true, ignore: ALLOWED_NUMBERS }, + ], + 'prettier/prettier': 'error', + 'require-atomic-updates': 'error', // 'fp/no-delete': 'error', // 'fp/no-let': 'error', @@ -136,23 +112,6 @@ export default tseslint.config( // tsx: 'never', // }, // ], - 'max-lines': ['error', { max: 300, skipComments: true }], - - // Disallow Magic Numbers - // https://eslint.org/docs/rules/no-magic-numbers - 'no-magic-numbers': [ - 'error', - { ignoreArrayIndexes: true, ignore: ALLOWED_NUMBERS }, - ], - - // disallow the use of console - // https://eslint.org/docs/rules/no-console - 'no-console': 'off', - 'prettier/prettier': 'error', - - // Disallow assignments that can lead to race conditions due to usage of await or yield - // https://eslint.org/docs/rules/require-atomic-updates - 'require-atomic-updates': 'error', // no export from test file // https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/no-export.md From 77224d9a95cde292973d6b0070eb5cf3a1a4972f Mon Sep 17 00:00:00 2001 From: Luke Hansford Date: Mon, 2 Sep 2024 15:42:31 +0200 Subject: [PATCH 2/2] Make jest optional --- package.json | 2 +- packages/base/eslint.config.js | 135 +----------------------------- packages/base/index.js | 140 +++++++++++++++++++++++++++++++- packages/base/package.json | 2 +- packages/react/eslint.config.js | 48 +---------- packages/react/index.js | 59 +++++++++++++- packages/react/package.json | 2 +- 7 files changed, 202 insertions(+), 186 deletions(-) diff --git a/package.json b/package.json index 44251b7..a744c03 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@fishbrain/eslint-config-monorepo", "private": true, "description": "ESLint configs for Fishbrain projects", - "version": "6.0.5", + "version": "6.0.6", "workspaces": [ "packages/*" ], diff --git a/packages/base/eslint.config.js b/packages/base/eslint.config.js index 8a1c741..ff096d5 100644 --- a/packages/base/eslint.config.js +++ b/packages/base/eslint.config.js @@ -1,134 +1,3 @@ -// @ts-check +import { config } from './index.js'; -import eslint from '@eslint/js'; -import tseslint from 'typescript-eslint'; -import jestPlugin from 'eslint-plugin-jest'; -import prettierPlugin from 'eslint-plugin-prettier'; - -const HTTP_CODES = [200, 201, 204, 301, 302, 400, 401, 404, 422, 500]; -const HTML_HEADER_LEVELS = [1, 2, 3, 4, 5, 6]; -const COMMON_MATH_VALUES = [24, 60, 100]; -const COMMON_INDEX_VALUES = [-1, 0, 1]; -const ALLOWED_NUMBERS = Array.from( - new Set( - COMMON_INDEX_VALUES.concat( - HTTP_CODES, - HTML_HEADER_LEVELS, - COMMON_MATH_VALUES, - ), - ), -); - -export default tseslint.config( - eslint.configs.recommended, - ...tseslint.configs.strictTypeChecked, - ...tseslint.configs.stylisticTypeChecked, - { - languageOptions: { - parserOptions: { - projectService: true, - tsconfigRootDir: import.meta.dirname, - }, - }, - }, - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access - jestPlugin.configs['flat/recommended'], - { - plugins: { prettier: prettierPlugin }, - rules: { - '@typescript-eslint/no-empty-function': 'off', // Noop functions are a common pattern we use during testing, so we don't want to enable it. - '@typescript-eslint/no-explicit-any': ['error', { fixToUnknown: true }], - '@typescript-eslint/no-unused-vars': [ - 'error', - { - ignoreRestSiblings: true, - varsIgnorePattern: '^_', - argsIgnorePattern: '^_', - }, - ], - '@typescript-eslint/promise-function-async': 'error', - '@typescript-eslint/restrict-template-expressions': [ - 'error', - { - allowAny: true, - allowNumber: true, - }, - ], - curly: ['error', 'all'], - 'max-lines': ['error', { max: 300, skipComments: true }], - 'no-magic-numbers': [ - 'error', - { ignoreArrayIndexes: true, ignore: ALLOWED_NUMBERS }, - ], - 'prettier/prettier': 'error', - 'require-atomic-updates': 'error', - - // 'fp/no-delete': 'error', - // 'fp/no-let': 'error', - // 'fp/no-loops': 'error', - // 'fp/no-mutating-assign': 'error', - // 'fp/no-mutation': [ - // 'error', - // { - // allowThis: true, - // }, - // ], - // TODO: - // 'import/named': 'off', // Redundant when used with Typescript. - // 'import/no-extraneous-dependencies': [ - // 'error', - // { - // devDependencies: [ - // '**/*.test.tsx', - // '**/*.test.ts', - // '**/testing.tsx', - // '**/*.stories.tsx', - // '**/*.stories.ts', - // '**/setupTests.ts', - // '**/webpack.config.{js,ts}', // webpack config - // '**/webpack.config.*.{js,ts}', // webpack config - // ], - // }, - // ], - // 'import/order': [ - // 'error', - // { - // 'newlines-between': 'always-and-inside-groups', - // groups: [ - // ['builtin', 'external'], - // ['internal', 'sibling', 'parent', 'index'], - // ], - // }, - // ], - // 'import/prefer-default-export': 'off', - // // Allow typescript imports, airbnb has disallowed it - // 'import/extensions': [ - // 'error', - // 'ignorePackages', - // { - // js: 'never', - // jsx: 'never', - // ts: 'never', - // tsx: 'never', - // }, - // ], - - // no export from test file - // https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/no-export.md - // 'jest/no-export': 'error', - - // TODO: Below is the "recommended" rules from eslint-plugin-import - // analysis/correctness - // 'import/no-unresolved': 'error', // TODO: dupe - // 'import/named': 'error', // TODO: dupe - // 'import/namespace': 'error', - // 'import/default': 'error', - // 'import/export': 'error', - - // // red flags (thus, warnings) - // 'import/no-named-as-default': 'off', // TODO: Should error, but it doesn't work with eslint9 just yet. - // 'import/no-named-as-default-member': 'off', // TODO: Should error, but it doesn't work with eslint9 just yet. - // 'import/no-duplicates': 'warn', - }, - }, -); +export default config; diff --git a/packages/base/index.js b/packages/base/index.js index 20e8b89..17c9e73 100644 --- a/packages/base/index.js +++ b/packages/base/index.js @@ -1,3 +1,139 @@ -import _config from './eslint.config.js'; +import jestPlugin from 'eslint-plugin-jest'; +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import prettierPlugin from 'eslint-plugin-prettier'; -export const config = _config; +const HTTP_CODES = [200, 201, 204, 301, 302, 400, 401, 404, 422, 500]; +const HTML_HEADER_LEVELS = [1, 2, 3, 4, 5, 6]; +const COMMON_MATH_VALUES = [24, 60, 100]; +const COMMON_INDEX_VALUES = [-1, 0, 1]; +const ALLOWED_NUMBERS = Array.from( + new Set( + COMMON_INDEX_VALUES.concat( + HTTP_CODES, + HTML_HEADER_LEVELS, + COMMON_MATH_VALUES, + ), + ), +); + +const baseConfig = [ + eslint.configs.recommended, + ...tseslint.configs.strictTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + { + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { plugins: { prettier: prettierPlugin } }, +]; + +const customRules = { + rules: { + '@typescript-eslint/no-empty-function': 'off', // Noop functions are a common pattern we use during testing, so we don't want to enable it. + '@typescript-eslint/no-explicit-any': ['error', { fixToUnknown: true }], + '@typescript-eslint/no-unused-vars': [ + 'error', + { + ignoreRestSiblings: true, + varsIgnorePattern: '^_', + argsIgnorePattern: '^_', + }, + ], + '@typescript-eslint/promise-function-async': 'error', + '@typescript-eslint/restrict-template-expressions': [ + 'error', + { + allowAny: true, + allowNumber: true, + }, + ], + curly: ['error', 'all'], + 'max-lines': ['error', { max: 300, skipComments: true }], + 'no-magic-numbers': [ + 'error', + { ignoreArrayIndexes: true, ignore: ALLOWED_NUMBERS }, + ], + 'prettier/prettier': 'error', + 'require-atomic-updates': 'error', + + // 'fp/no-delete': 'error', + // 'fp/no-let': 'error', + // 'fp/no-loops': 'error', + // 'fp/no-mutating-assign': 'error', + // 'fp/no-mutation': [ + // 'error', + // { + // allowThis: true, + // }, + // ], + // TODO: + // 'import/named': 'off', // Redundant when used with Typescript. + // 'import/no-extraneous-dependencies': [ + // 'error', + // { + // devDependencies: [ + // '**/*.test.tsx', + // '**/*.test.ts', + // '**/testing.tsx', + // '**/*.stories.tsx', + // '**/*.stories.ts', + // '**/setupTests.ts', + // '**/webpack.config.{js,ts}', // webpack config + // '**/webpack.config.*.{js,ts}', // webpack config + // ], + // }, + // ], + // 'import/order': [ + // 'error', + // { + // 'newlines-between': 'always-and-inside-groups', + // groups: [ + // ['builtin', 'external'], + // ['internal', 'sibling', 'parent', 'index'], + // ], + // }, + // ], + // 'import/prefer-default-export': 'off', + // // Allow typescript imports, airbnb has disallowed it + // 'import/extensions': [ + // 'error', + // 'ignorePackages', + // { + // js: 'never', + // jsx: 'never', + // ts: 'never', + // tsx: 'never', + // }, + // ], + + // no export from test file + // https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/no-export.md + // 'jest/no-export': 'error', + + // TODO: Below is the "recommended" rules from eslint-plugin-import + // analysis/correctness + // 'import/no-unresolved': 'error', // TODO: dupe + // 'import/named': 'error', // TODO: dupe + // 'import/namespace': 'error', + // 'import/default': 'error', + // 'import/export': 'error', + + // // red flags (thus, warnings) + // 'import/no-named-as-default': 'off', // TODO: Should error, but it doesn't work with eslint9 just yet. + // 'import/no-named-as-default-member': 'off', // TODO: Should error, but it doesn't work with eslint9 just yet. + // 'import/no-duplicates': 'warn', + }, +}; + +export const configWithoutJest = tseslint.config(...baseConfig, customRules); +export const config = tseslint.config( + ...baseConfig, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access + jestPlugin.configs['flat/recommended'], + customRules, +); diff --git a/packages/base/package.json b/packages/base/package.json index 947ab5c..71b2c35 100644 --- a/packages/base/package.json +++ b/packages/base/package.json @@ -1,7 +1,7 @@ { "name": "@fishbrain/eslint-config-base", "packageManager": "yarn@4.4.1", - "version": "6.0.5", + "version": "6.0.6", "type": "module", "exports": "./index.js", "scripts": { diff --git a/packages/react/eslint.config.js b/packages/react/eslint.config.js index 518d34e..ff096d5 100644 --- a/packages/react/eslint.config.js +++ b/packages/react/eslint.config.js @@ -1,47 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument */ -// @ts-check +import { config } from './index.js'; -import tseslint from 'typescript-eslint'; -import reactPlugin from 'eslint-plugin-react'; -import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'; -import compatPlugin from 'eslint-plugin-compat'; -import globals from 'globals'; - -import { config } from '@fishbrain/eslint-config-base'; - -export default tseslint.config( - ...config, - { - ...reactPlugin.configs.flat.recommended, - settings: { react: { version: 'detect' } }, - languageOptions: { - ...reactPlugin.configs.flat.recommended.languageOptions, - globals: { - ...globals.browser, - }, - }, - }, - jsxA11yPlugin.flatConfigs.recommended, - compatPlugin.configs['flat/recommended'], - { - rules: { - // 'jsx-a11y/label-has-for': 'off', // This is deprecated but in the recommended extension for some reason // TODO: Check if needed - 'jsx-a11y/media-has-caption': 'off', - 'jsx-a11y/no-onchange': 'off', - 'no-alert': 'error', - 'no-console': 'warn', - 'react/jsx-filename-extension': [ - 'warn', - { extensions: ['.tsx', '.jsx'] }, - ], - 'react/jsx-max-props-per-line': ['warn', { when: 'multiline' }], - 'react/no-render-return-value': 'off', - 'react/prop-types': 'off', // No need for prop types with Typescript - 'react/react-in-jsx-scope': 'off', - - // TODO: Disabled until https://github.com/facebook/react/issues/28313 is resolved. - // 'react-hooks/exhaustive-deps': 'error', - // 'react-hooks/rules-of-hooks': 'error', - }, - }, -); +export default config; diff --git a/packages/react/index.js b/packages/react/index.js index 20e8b89..c8beb62 100644 --- a/packages/react/index.js +++ b/packages/react/index.js @@ -1,3 +1,58 @@ -import _config from './eslint.config.js'; +/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument */ -export const config = _config; +import tseslint from 'typescript-eslint'; +import reactPlugin from 'eslint-plugin-react'; +import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'; +import compatPlugin from 'eslint-plugin-compat'; +import globals from 'globals'; + +import { + config as baseConfig, + configWithoutJest as baseConfigWithoutJest, +} from '@fishbrain/eslint-config-base'; + +const reactConfig = [ + { + ...reactPlugin.configs.flat.recommended, + settings: { react: { version: 'detect' } }, + languageOptions: { + ...reactPlugin.configs.flat.recommended.languageOptions, + globals: { + ...globals.browser, + }, + }, + }, + jsxA11yPlugin.flatConfigs.recommended, + compatPlugin.configs['flat/recommended'], +]; + +const customRules = { + rules: { + // 'jsx-a11y/label-has-for': 'off', // This is deprecated but in the recommended extension for some reason // TODO: Check if needed + 'jsx-a11y/media-has-caption': 'off', + 'jsx-a11y/no-onchange': 'off', + 'no-alert': 'error', + 'no-console': 'warn', + 'react/jsx-filename-extension': ['warn', { extensions: ['.tsx', '.jsx'] }], + 'react/jsx-max-props-per-line': ['warn', { when: 'multiline' }], + 'react/no-render-return-value': 'off', + 'react/prop-types': 'off', // No need for prop types with Typescript + 'react/react-in-jsx-scope': 'off', + + // TODO: Disabled until https://github.com/facebook/react/issues/28313 is resolved. + // 'react-hooks/exhaustive-deps': 'error', + // 'react-hooks/rules-of-hooks': 'error', + }, +}; + +export const config = tseslint.config( + ...baseConfig, + ...reactConfig, + customRules, +); + +export const configWithoutJest = tseslint.config( + ...baseConfigWithoutJest, + ...reactConfig, + customRules, +); diff --git a/packages/react/package.json b/packages/react/package.json index 2ad46d2..401a352 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,7 +1,7 @@ { "name": "@fishbrain/eslint-config-react", "packageManager": "yarn@4.4.1", - "version": "6.0.5", + "version": "6.0.6", "type": "module", "exports": "./index.js", "scripts": {