From 6f399a90a8080050115d52ce23341f2173dc66ea Mon Sep 17 00:00:00 2001 From: Stefan Cameron Date: Fri, 27 Dec 2024 11:26:00 -0600 Subject: [PATCH] Fix ESLint 9 flat config issues Also make it more readable with less merging of objects for the `languageOptions`. --- eslint.config.mjs | 158 +++++++++++++++++++++++++++++----------------- package-lock.json | 101 +++++++++++++++++++++++++++++ package.json | 2 + 3 files changed, 204 insertions(+), 57 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index d239f19d..8c32c723 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -8,9 +8,7 @@ import js from '@eslint/js'; import globals from 'globals'; import babel from '@babel/eslint-plugin'; import babelParser from '@babel/eslint-parser'; -// eslint-disable-next-line import/no-unresolved -- it's there...!!! import typescript from '@typescript-eslint/eslint-plugin'; -// eslint-disable-next-line import/no-unresolved -- it's there...!!! import typescriptParser from '@typescript-eslint/parser'; import prettier from 'eslint-config-prettier'; import jest from 'eslint-plugin-jest'; @@ -19,56 +17,62 @@ import cypress from 'eslint-plugin-cypress'; import importPlugin from 'eslint-plugin-import'; import testingLibrary from 'eslint-plugin-testing-library'; +const ecmaVersion = 'latest'; +const impliedStrict = true; +const tsconfigRootDir = import.meta.dirname; + // -// Base parser options and environments +// Plugins // -const languageOptions = { - ecmaVersion: 2019, +// Plugins that apply to ALL envs +const basePlugins = { + '@babel': babel, // @see https://www.npmjs.com/package/@babel/eslint-plugin }; -// @see https://eslint.org/docs/latest/use/configure/language-options#specifying-parser-options -const parserOptions = { - ecmaFeatures: { - impliedStrict: true, - }, - sourceType: 'module', +const importPluginSettings = { + 'import/resolver': { + node: { + extensions: ['.js', '.jsx', '.mjs', '.ts', '.tsx', '.mts'], + moduleDirectory: ['node_modules', 'src/', 'test/'], + }, + typescript: { + alwaysTryTypes: true, + }, + } }; -// for use with https://typescript-eslint.io/users/configs#projects-with-type-checking -// @see https://typescript-eslint.io/getting-started/typed-linting -const typedParserOptions = { - ...parserOptions, - ecmaFeatures: { - ...parserOptions.ecmaFeatures, - }, - project: true, - tsconfigRootDir: import.meta.dirname, -}; +// +// Globals +// +// Globals that apply to ALL envs const baseGlobals = { - ...globals.es2017, + // anything in addition to what `languageOptions.ecmaVersion` provides + // @see https://eslint.org/docs/latest/use/configure/language-options#predefined-global-variables }; +// Globals for repo tooling scripts const toolingGlobals = { - ...baseGlobals, ...globals.node, }; +// Globals for browser-based source code const browserGlobals = { - ...baseGlobals, ...globals.browser, }; -const jestGlobals = { - ...browserGlobals, - ...globals.jest, +// Globals for test files +const testGlobals = { ...globals.node, + ...globals.jest, + ...cypress.environments.globals.globals, }; +// Globals for BUNDLED (Webpack, Rollup, etc) source code // NOTE: these must also be defined in /src/globals.d.ts referenced in the -// /tsconfig.json as well as the `globals` property in /jest.config.js -const srcGlobals = { +// /tsconfig.json as well as the `globals` property in /jest.config.mjs +const bundlerGlobals = { }; // @@ -158,7 +162,7 @@ const baseRules = { const typescriptRules = { ...typescript.configs['recommended-type-checked'].rules, - // add overrides here as needed + // AFTER TypeScript rules to turn off `import` rules that TypeScript covers ...importPlugin.flatConfigs.typescript.rules, }; @@ -195,10 +199,14 @@ const testRules = { // not much value in this one, and it's not sophisticated enough to detect all usage // scenarios so we get false-positives 'testing-library/await-async-utils': 'off', + + //// Cypress plugin + + ...cypress.configs.recommended.rules, }; // -// Configuration generator functions +// Config generators // /** @@ -211,24 +219,36 @@ const createToolingConfig = (isModule = true, isTypescript = false) => ({ files: isModule ? (isTypescript ? ['**/*.m?ts'] : ['**/*.mjs']) : ['**/*.js'], ignores: ['src/**/*.*', 'test/**/*.*'], plugins: { + ...basePlugins, ...(isModule ? { import: importPlugin } : {}), }, languageOptions: { - ...languageOptions, + ecmaVersion, parser: isTypescript ? typescriptParser : babelParser, parserOptions: { - ...(isModule && isTypescript ? typedParserOptions : parserOptions), sourceType: isModule ? 'module' : 'script', + ...(isModule && isTypescript ? { + project: true, + tsconfigRootDir, + } : {}), + ecmaFeatures: { + impliedStrict, + jsx: false, + }, }, globals: { + ...baseGlobals, ...toolingGlobals, }, }, + settings: { + ...(isModule ? importPluginSettings : {}), + }, rules: { ...baseRules, + ...(isModule ? importPlugin.flatConfigs.recommended.rules : {}), // BEFORE TypeScript rules ...(isModule && isTypescript ? typescriptRules : {}), - ...(isModule ? importPlugin.flatConfigs.recommended.rules : {}), - 'no-console': 'off', + 'no-console': 'off', // OK in repo scripts }, }); @@ -239,18 +259,28 @@ const createToolingConfig = (isModule = true, isTypescript = false) => ({ const createSourceJSConfig = () => ({ files: ['src/**/*.js'], plugins: { - '@babel': babel, // @see https://www.npmjs.com/package/@babel/eslint-plugin + ...basePlugins, import: importPlugin, }, languageOptions: { - ...languageOptions, + ecmaVersion, parser: babelParser, - parserOptions, + parserOptions: { + sourceType: 'module', + ecmaFeatures: { + impliedStrict, + jsx: false, + }, + }, globals: { + ...baseGlobals, + ...bundlerGlobals, ...browserGlobals, - ...srcGlobals, }, }, + settings: { + ...importPluginSettings, + }, rules: { ...baseRules, ...importPlugin.flatConfigs.recommended.rules, @@ -260,22 +290,31 @@ const createSourceJSConfig = () => ({ const createSourceTSConfig = () => ({ files: ['index.d.ts', 'src/**/*.ts'], plugins: { - '@babel': babel, // @see https://www.npmjs.com/package/@babel/eslint-plugin - '@typescript-eslint': typescript, + ...basePlugins, import: importPlugin, + '@typescript-eslint': typescript, }, languageOptions: { - ...languageOptions, + ecmaVersion, parser: typescriptParser, - parserOptions: typedParserOptions, + parserOptions: { + project: true, + tsconfigRootDir, + sourceType: 'module', + ecmaFeatures: { + impliedStrict, + jsx: false, + }, + }, globals: { + ...baseGlobals, + ...bundlerGlobals, ...browserGlobals, - ...srcGlobals, }, }, rules: { ...baseRules, - ...importPlugin.flatConfigs.recommended.rules, + ...importPlugin.flatConfigs.recommended.rules, // BEFORE TypeScript rules ...typescriptRules, }, }); @@ -283,37 +322,42 @@ const createSourceTSConfig = () => ({ const createTestConfig = (isTypescript = false) => ({ files: isTypescript ? ['test/**/*.ts'] : ['test/**/*.js'], plugins: { - '@babel': babel, // @see https://www.npmjs.com/package/@babel/eslint-plugin + ...basePlugins, import: importPlugin, + ...(isTypescript ? { '@typescript-eslint': typescript } : {}), jest, 'jest-dom': jestDom, 'testing-library': testingLibrary, cypress, - ...(isTypescript ? { '@typescript-eslint': typescript } : {}), }, languageOptions: { - ...languageOptions, + ecmaVersion, parser: isTypescript ? typescriptParser : babelParser, parserOptions: { - ...parserOptions, - ...(isTypescript ? typedParserOptions : {}), + ...(isTypescript + ? { + project: true, + tsconfigRootDir, + } + : {}), + sourceType: 'module', ecmaFeatures: { - ...parserOptions.ecmaFeatures, - ...(isTypescript ? typedParserOptions.ecmaFeatures : {}), + impliedStrict, + jsx: false, }, }, globals: { - ...jestGlobals, - ...srcGlobals, - ...cypress.environments.globals.globals, + ...baseGlobals, + ...bundlerGlobals, // because tests execute code that also gets bundled + ...browserGlobals, + ...testGlobals, }, }, rules: { ...baseRules, - ...importPlugin.flatConfigs.recommended.rules, + ...importPlugin.flatConfigs.recommended.rules, // BEFORE TypeScript rules ...(isTypescript ? typescriptRules : {}), ...testRules, - ...cypress.configs.recommended.rules, }, }); diff --git a/package-lock.json b/package-lock.json index 5a060127..7c546ce1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,8 @@ "cypress": "^13.17.0", "eslint": "^9.17.0", "eslint-config-prettier": "^9.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-import-resolver-typescript": "^3.7.0", "eslint-plugin-cypress": "^4.1.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jest": "^28.10.0", @@ -3519,6 +3521,16 @@ "node": ">= 8" } }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz", @@ -8266,6 +8278,42 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.7.0.tgz", + "integrity": "sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.3.7", + "enhanced-resolve": "^5.15.0", + "fast-glob": "^3.3.2", + "get-tsconfig": "^4.7.5", + "is-bun-module": "^1.0.2", + "is-glob": "^4.0.3", + "stable-hash": "^0.0.4" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, "node_modules/eslint-module-utils": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", @@ -9501,6 +9549,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/getos": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", @@ -10333,6 +10394,29 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, + "node_modules/is-bun-module": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.3.0.tgz", + "integrity": "sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -15460,6 +15544,16 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve.exports": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", @@ -16258,6 +16352,13 @@ "node": ">=0.10.0" } }, + "node_modules/stable-hash": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz", + "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==", + "dev": true, + "license": "MIT" + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", diff --git a/package.json b/package.json index 2c249a1c..b74375f0 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,8 @@ "cypress": "^13.17.0", "eslint": "^9.17.0", "eslint-config-prettier": "^9.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-import-resolver-typescript": "^3.7.0", "eslint-plugin-cypress": "^4.1.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jest": "^28.10.0",