From 82e0d48516a4b2884cdc362c7071081ce51deb62 Mon Sep 17 00:00:00 2001 From: Moreno Feltscher Date: Wed, 4 Dec 2024 16:32:56 +0100 Subject: [PATCH] feat: update ESLint to v9 (#30) BREAKING CHANGE: due to the upgrade of various ESLint plugins, the minimum supported version TypeScript and Node.js has been increased. This should not affect most users, but if you are using an older version of TypeScript or Node.js, you may need to upgrade. Furthermore some of the dependencies have been updated to their latest versions, which may cause some checks to fail. --- .eslintrc.js | 56 ------------ .eslintrc.react.js | 30 ------- .github/workflows/main.yml | 7 ++ .gitignore | 46 +--------- .npmignore | 8 -- README.md | 30 +++---- index.js | 3 - package.json | 56 +++++++++--- react.js | 3 - src/index.ts | 122 ++++++++++++++++++++++++++ src/react-legacy.ts | 3 + src/typescript-legacy.ts | 3 + src/typings/eslint-plugin-import.d.ts | 35 ++++++++ tsconfig.json | 15 ++++ 14 files changed, 246 insertions(+), 171 deletions(-) delete mode 100644 .eslintrc.js delete mode 100644 .eslintrc.react.js delete mode 100644 .npmignore delete mode 100644 index.js delete mode 100644 react.js create mode 100644 src/index.ts create mode 100644 src/react-legacy.ts create mode 100644 src/typescript-legacy.ts create mode 100644 src/typings/eslint-plugin-import.d.ts create mode 100644 tsconfig.json diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 8f46c26..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,56 +0,0 @@ -/** @type {import('eslint').Linter.Config} */ -module.exports = { - rules: { - '@typescript-eslint/no-unsafe-enum-comparison': 'off', - '@typescript-eslint/consistent-type-definitions': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-var-requires': 'warn', - '@typescript-eslint/no-unused-vars': ['error'], - '@typescript-eslint/no-floating-promises': ['error'], - '@typescript-eslint/no-explicit-any': ['error', { fixToUnknown: true }], - 'no-constant-binary-expression': 'error', - 'array-callback-return': 'error', - 'no-debugger': 'error', - 'no-alert': 'error', - 'no-console': ['error', { allow: ['debug', 'info', 'warn', 'error', 'trace', 'time', 'timeEnd'] }], - 'newline-before-return': 'error', - 'prefer-const': 'error', - 'no-else-return': 'error', - 'no-extra-semi': 'error', - curly: 'error', - eqeqeq: 'error', - 'default-case-last': 'error', - 'prettier/prettier': [ - 'error', - { - endOfLine: 'auto', - }, - ], - }, - env: { - es6: true, - browser: true, - node: true, - }, - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module', - project: true, - }, - extends: [ - 'eslint:recommended', - 'plugin:prettier/recommended', - 'plugin:import/errors', - 'plugin:import/warnings', - 'plugin:import/typescript', - 'plugin:@typescript-eslint/recommended-type-checked', - 'plugin:@typescript-eslint/stylistic-type-checked', - ], - globals: { - Atomics: 'readonly', - SharedArrayBuffer: 'readonly', - }, - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint', 'prettier'], -}; diff --git a/.eslintrc.react.js b/.eslintrc.react.js deleted file mode 100644 index 8a981f5..0000000 --- a/.eslintrc.react.js +++ /dev/null @@ -1,30 +0,0 @@ -/** @type {import('eslint').Linter.Config} */ -module.exports = { - rules: { - 'react/react-in-jsx-scope': 'off', - 'react/prop-types': 'off', - 'react/display-name': 'off', - 'react/forbid-component-props': ['warn', { forbid: ['style', 'className'] }], - '@typescript-eslint/no-misused-promises': [ - 'error', - { - checksVoidReturn: { - attributes: false, - }, - }, - ], - }, - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - extends: ['./index', 'plugin:react/recommended'], - settings: { - react: { - pragma: 'React', - version: 'detect', - }, - }, - plugins: ['react', 'react-hooks'], -}; diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 542390a..b6838c2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,6 +13,13 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: '22.x' + - run: npm install + - run: npm run build - name: Semantic Release uses: cycjimmy/semantic-release-action@v4 with: diff --git a/.gitignore b/.gitignore index 49d2b85..6682568 100644 --- a/.gitignore +++ b/.gitignore @@ -5,39 +5,8 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - # Dependency directories node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ # Optional npm cache directory .npm @@ -45,20 +14,11 @@ typings/ # Optional eslint cache .eslintcache -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - # next.js build output .next # NPM lock file package-lock.json + +# Distribution directory +dist diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 8d5b334..0000000 --- a/.npmignore +++ /dev/null @@ -1,8 +0,0 @@ -package-lock.json -npm-debug.log* -node_modules/ -.npm -.eslintcache -.editorconfig -.gitignore -.github diff --git a/README.md b/README.md index 44ec663..109ec13 100644 --- a/README.md +++ b/README.md @@ -10,24 +10,28 @@ $ npm install eslint @smartive/eslint-config -D ## Usage -Create a `.eslintrc` file in the root of your project's directory (it should live where `package.json` does). This package offers two different rulesets, on for plain TypeScript applications (`@smartive/eslint-config`) and a separate one for React applications (`@smartive/eslint-config`). Your `.eslintrc` file should look like this: +This package offers two different rule sets, one for plain TypeScript applications and a separate one for React applications. -### Plain TypeScript applications +### Flat Config (`eslint.config.mjs`) -``` -{ - "extends": [ - "@smartive/eslint-config" - ] -} -``` +```javascript +import { configs } from '@smartive/eslint-config' -### React applications +// For plain TS applications .. +export default configs.typescript; +// .. or React applications +export default configs.react; ``` + +### Legacy Config (`.eslintrc`) + +```json { "extends": [ - "@smartive/eslint-config/react" + "@smartive/eslint-config/typescript-legacy" // Plain TS applications + // or + "@smartive/eslint-config/react-legacy" // React applications ] } ``` @@ -42,7 +46,3 @@ To use eslint add the following to your package.json: "lint:fix": "eslint . --fix" } ``` - -### TypeScript configuration - -Since there are some rules which require type information please make sure to set up a `tsconfig.json` configuration file in the root directory of your project. If your TypeScript configuration file is placed in another location you have to configure it using `parserOptions.project` in your ESLint configuration file. For more information have a look at the [typescript-eslint documentation](https://typescript-eslint.io/packages/parser/#project). diff --git a/index.js b/index.js deleted file mode 100644 index bd66436..0000000 --- a/index.js +++ /dev/null @@ -1,3 +0,0 @@ -const eslintrc = require('./.eslintrc'); - -module.exports = eslintrc; diff --git a/package.json b/package.json index f5d41c4..ff4c5f6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,26 @@ "name": "@smartive/eslint-config", "version": "0.0.0-development", "description": "ESLint configuration by smartive", - "main": "index.js", + "files": [ + "README.md", + "LICENSE", + "dist" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js" + }, + "./react-legacy": { + "import": "./dist/esm/react-legacy.js", + "require": "./dist/cjs/react-legacy.js" + }, + "./typescript-legacy": { + "import": "./dist/esm/typescript-legacy.js", + "require": "./dist/cjs/typescript-legacy.js" + } + }, "repository": { "type": "git", "url": "git+https://github.com/smartive/eslint-config.git" @@ -20,26 +39,37 @@ }, "homepage": "https://github.com/smartive/eslint-config#readme", "dependencies": { - "@typescript-eslint/eslint-plugin": "^6.7.4", - "@typescript-eslint/parser": "^6.7.4", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-prettier": ">=4.0.0 <6", - "eslint-plugin-react": "^7.27.0", - "eslint-plugin-react-hooks": "^4.3.0" + "@typescript-eslint/eslint-plugin": "^8.15.0", + "@typescript-eslint/parser": "^8.15.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.0.0", + "globals": "^15.13.0", + "typescript-eslint": "^8.15.0" }, "peerDependencies": { - "eslint": "^8.0.0" + "eslint": "^8.57.0 || ^9.0.0" }, "devDependencies": { - "@commitlint/cli": "^19.0.0", - "@commitlint/config-conventional": "^19.0.0", + "@commitlint/cli": "^19.6.0", + "@commitlint/config-conventional": "^19.6.0", + "@eslint/js": "^9.15.0", "@smartive/prettier-config": "^3.0.0", + "@types/node": "^22.9.1", "cz-conventional-changelog": "^3.3.0", - "husky": "^9.0.0", - "prettier": "^3.0.0" + "esbuild": "0.24.0", + "eslint": "^9.15.0", + "husky": "^9.1.7", + "prettier": "^3.3.3", + "typescript": "^5.6.3" }, "scripts": { + "build:types": "tsc --declaration --emitDeclarationOnly", + "build:cjs": "esbuild --platform=browser --format=cjs --outdir=dist/cjs/ ./src/*.ts", + "build:mjs": "esbuild --platform=neutral --format=esm --outdir=dist/esm/ ./src/*.ts", + "build": "rm -rf dist && npm run build:cjs && npm run build:mjs && npm run build:types", "commitlint": "commitlint --edit", "prepare": "[ ! -f node_modules/.bin/husky ] || husky" }, diff --git a/react.js b/react.js deleted file mode 100644 index a96a084..0000000 --- a/react.js +++ /dev/null @@ -1,3 +0,0 @@ -const eslintrc = require('./.eslintrc.react'); - -module.exports = eslintrc; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..dc3cb8e --- /dev/null +++ b/src/index.ts @@ -0,0 +1,122 @@ +import js from '@eslint/js'; +import type { Linter } from 'eslint'; +import { flatConfigs as eslintPluginImportConfigs } from 'eslint-plugin-import'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import reactPlugin from 'eslint-plugin-react'; +import globals from 'globals'; +import tsEslint from 'typescript-eslint'; + +const reactRules: Linter.RulesRecord = { + 'react/prop-types': 'off', + 'react/display-name': 'off', + 'react/forbid-component-props': ['warn', { forbid: ['style', 'className'] }], + '@typescript-eslint/no-misused-promises': [ + 'error', + { + checksVoidReturn: { + attributes: false, + }, + }, + ], +}; + +const rules = (react: boolean): Linter.RulesRecord => ({ + '@typescript-eslint/no-unsafe-enum-comparison': 'off', + '@typescript-eslint/consistent-type-definitions': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-var-requires': 'warn', + '@typescript-eslint/no-unused-vars': ['error'], + '@typescript-eslint/no-floating-promises': ['error'], + '@typescript-eslint/no-explicit-any': ['error', { fixToUnknown: true }], + 'no-constant-binary-expression': 'error', + 'array-callback-return': 'error', + 'no-debugger': 'error', + 'no-alert': 'error', + 'no-console': ['error', { allow: ['debug', 'info', 'warn', 'error', 'trace', 'time', 'timeEnd'] }], + 'newline-before-return': 'error', + 'prefer-const': 'error', + 'no-else-return': 'error', + 'no-extra-semi': 'error', + curly: 'error', + eqeqeq: 'error', + 'default-case-last': 'error', + 'prettier/prettier': [ + 'error', + { + endOfLine: 'auto', + }, + ], + ...(react ? reactRules : {}), +}); + +export const generateLegacyConfig = (react: boolean): Linter.LegacyConfig => ({ + rules: rules(react), + env: { + es2020: true, + browser: true, + node: true, + }, + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + projectService: true, + }, + extends: [ + 'eslint:recommended', + 'plugin:prettier/recommended', + 'plugin:import/errors', + 'plugin:import/warnings', + 'plugin:import/typescript', + 'plugin:@typescript-eslint/recommended-type-checked', + 'plugin:@typescript-eslint/stylistic-type-checked', + ...(react ? ['plugin:react/recommended', 'plugin:react/jsx-runtime'] : []), + ], + globals: { + Atomics: 'readonly', + SharedArrayBuffer: 'readonly', + }, + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint', 'prettier'], +}); + +const flatConfigTypescript = tsEslint.config( + js.configs.recommended, + eslintPluginPrettierRecommended, + eslintPluginImportConfigs.errors, + eslintPluginImportConfigs.warnings, + eslintPluginImportConfigs.typescript, + tsEslint.configs.recommendedTypeChecked, + tsEslint.configs.stylisticTypeChecked, + { + rules: rules(false), + languageOptions: { + ecmaVersion: 2020, + sourceType: 'module', + globals: { + ...globals.browser, + ...globals.node, + ...globals.es2020, + Atomics: 'readonly', + SharedArrayBuffer: 'readonly', + }, + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + projectService: true, + }, + }, + }, +); + +const flatConfigReact = tsEslint.config( + flatConfigTypescript, + reactPlugin.configs.flat!.recommended as unknown as Linter.Config, + reactPlugin.configs.flat!['jsx-runtime'] as unknown as Linter.Config, + { rules: reactRules }, +); + +export const configs = { + typescript: flatConfigTypescript, + react: flatConfigReact, +}; diff --git a/src/react-legacy.ts b/src/react-legacy.ts new file mode 100644 index 0000000..c110ead --- /dev/null +++ b/src/react-legacy.ts @@ -0,0 +1,3 @@ +import { generateLegacyConfig } from './'; + +module.exports = generateLegacyConfig(true); diff --git a/src/typescript-legacy.ts b/src/typescript-legacy.ts new file mode 100644 index 0000000..83deb11 --- /dev/null +++ b/src/typescript-legacy.ts @@ -0,0 +1,3 @@ +import { generateLegacyConfig } from './'; + +module.exports = generateLegacyConfig(false); diff --git a/src/typings/eslint-plugin-import.d.ts b/src/typings/eslint-plugin-import.d.ts new file mode 100644 index 0000000..ab5073f --- /dev/null +++ b/src/typings/eslint-plugin-import.d.ts @@ -0,0 +1,35 @@ +import type { ESLint, Linter, Rule } from 'eslint'; + +declare module 'eslint-plugin-import' { + export = plugin; +} + +const plugin: ESLint.Plugin & { + meta: { + name: string; + version: string; + }; + configs: { + recommended: Linter.LegacyConfig; + errors: Linter.LegacyConfig; + warnings: Linter.LegacyConfig; + 'stage-0': Linter.LegacyConfig; + react: Linter.LegacyConfig; + 'react-native': Linter.LegacyConfig; + electron: Linter.LegacyConfig; + typescript: Linter.LegacyConfig; + }; + flatConfigs: { + recommended: Linter.FlatConfig; + errors: Linter.FlatConfig; + warnings: Linter.FlatConfig; + 'stage-0': Linter.FlatConfig; + react: Linter.FlatConfig; + 'react-native': Linter.FlatConfig; + electron: Linter.FlatConfig; + typescript: Linter.FlatConfig; + }; + rules: { + [key: string]: Rule.RuleModule; + }; +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..00df34f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "typeRoots": ["./src/typings","node_modules/@types"], + "declaration": true, + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + }, + "include": ["src/index.ts"], + "exclude": ["node_modules", "dist"] +}