diff --git a/docs/src/use/configure/configuration-files.md b/docs/src/use/configure/configuration-files.md index 653e5a30196f..6e7a4479cc1b 100644 --- a/docs/src/use/configure/configuration-files.md +++ b/docs/src/use/configure/configuration-files.md @@ -529,7 +529,7 @@ npx eslint --flag unstable_ts_config For more information about using feature flags, see [Feature Flags](../../flags/). -For Deno and Bun, TypeScript configuration files are natively supported; for Node.js, you must install the optional dev dependency [`jiti`](https://github.com/unjs/jiti) in your project (this dependency is not automatically installed by ESLint): +For Deno and Bun, TypeScript configuration files are natively supported; for Node.js, you must install the optional dev dependency [`jiti`](https://github.com/unjs/jiti) in version 2.0.0 or later in your project (this dependency is not automatically installed by ESLint): ```bash npm install -D jiti diff --git a/lib/config/config-loader.js b/lib/config/config-loader.js index a4e38fe9b5d3..82e0ca714903 100644 --- a/lib/config/config-loader.js +++ b/lib/config/config-loader.js @@ -173,21 +173,22 @@ async function loadConfigFile(filePath, allowTS) { */ if (allowTS && isTS && !isDeno && !isBun) { - const createJiti = await import("jiti").then(jitiModule => (typeof jitiModule?.createJiti === "function" ? jitiModule.createJiti : jitiModule.default), () => { + // eslint-disable-next-line no-use-before-define -- `ConfigLoader.loadJiti` can be overwritten for testing + const { createJiti } = await ConfigLoader.loadJiti().catch(() => { throw new Error("The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it."); }); + // `createJiti` was added in jiti v2. + if (typeof createJiti !== "function") { + throw new Error("You are using an outdated version of the 'jiti' library. Please update to the latest version of 'jiti' to ensure compatibility and access to the latest features."); + } + /* * Disabling `moduleCache` allows us to reload a * config file when the last modified timestamp changes. */ const jiti = createJiti(__filename, { moduleCache: false, interopDefault: false }); - - if (typeof jiti?.import !== "function") { - throw new Error("You are using an outdated version of the 'jiti' library. Please update to the latest version of 'jiti' to ensure compatibility and access to the latest features."); - } - const config = await jiti.import(fileURL.href); importedConfigFileModificationTime.set(filePath, mtime); @@ -294,7 +295,6 @@ async function calculateConfigArray(configFilePath, basePath, options) { return configs; } - //----------------------------------------------------------------------------- // Exports //----------------------------------------------------------------------------- @@ -517,6 +517,15 @@ class ConfigLoader { return this.#configArrays.get(configFilePath); } + /** + * Used to import the jiti dependency. This method is exposed internally for testing purposes. + * @returns {Promise>} A promise that fulfills with a module object + * or rejects with an error if jiti is not found. + */ + static loadJiti() { + return import("jiti"); + } + } /** diff --git a/tests/fixtures/ts-config-files/ts/eslint.undefined.config.ts b/tests/fixtures/ts-config-files/ts/eslint.undefined.config.ts new file mode 100644 index 000000000000..ed9f3b675335 --- /dev/null +++ b/tests/fixtures/ts-config-files/ts/eslint.undefined.config.ts @@ -0,0 +1 @@ +export = void 0; diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 64902d90875a..64bb7a08f7a2 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -1313,6 +1313,61 @@ describe("ESLint", () => { }); + it("should fail to load a TS config file if jiti is not installed", async () => { + + const { ConfigLoader } = require("../../../lib/config/config-loader"); + + sinon.stub(ConfigLoader, "loadJiti").rejects(); + + const cwd = getFixturePath("ts-config-files", "ts"); + + eslint = new ESLint({ + cwd, + flags: tsFlags + }); + + await assert.rejects( + eslint.lintText("foo();"), + { message: "The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it." } + ); + }); + + it("should fail to load a TS config file if an outdated version of jiti is installed", async () => { + + const { ConfigLoader } = require("../../../lib/config/config-loader"); + + sinon.stub(ConfigLoader, "loadJiti").resolves({}); + + const cwd = getFixturePath("ts-config-files", "ts"); + + eslint = new ESLint({ + cwd, + flags: tsFlags + }); + + await assert.rejects( + eslint.lintText("foo();"), + { message: "You are using an outdated version of the 'jiti' library. Please update to the latest version of 'jiti' to ensure compatibility and access to the latest features." } + ); + }); + + it("should fail to load a CommonJS TS config file that exports undefined with a helpful error message", async () => { + + const cwd = getFixturePath("ts-config-files", "ts"); + + eslint = new ESLint({ + cwd, + flags: tsFlags, + overrideConfigFile: "eslint.undefined.config.ts" + }); + + await assert.rejects( + eslint.lintText("foo"), + { message: "Config (unnamed): Unexpected undefined config at user-defined index 0." } + ); + + }); + }); it("should pass BOM through processors", async () => { @@ -5753,6 +5808,61 @@ describe("ESLint", () => { }); + it("should fail to load a TS config file if jiti is not installed", async () => { + + const { ConfigLoader } = require("../../../lib/config/config-loader"); + + sinon.stub(ConfigLoader, "loadJiti").rejects(); + + const cwd = getFixturePath("ts-config-files", "ts"); + + eslint = new ESLint({ + cwd, + flags: newFlags + }); + + await assert.rejects( + eslint.lintFiles("foo.js"), + { message: "The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it." } + ); + }); + + it("should fail to load a TS config file if an outdated version of jiti is installed", async () => { + + const { ConfigLoader } = require("../../../lib/config/config-loader"); + + sinon.stub(ConfigLoader, "loadJiti").resolves({}); + + const cwd = getFixturePath("ts-config-files", "ts"); + + eslint = new ESLint({ + cwd, + flags: newFlags + }); + + await assert.rejects( + eslint.lintFiles("foo.js"), + { message: "You are using an outdated version of the 'jiti' library. Please update to the latest version of 'jiti' to ensure compatibility and access to the latest features." } + ); + }); + + it("should fail to load a CommonJS TS config file that exports undefined with a helpful error message", async () => { + + const cwd = getFixturePath("ts-config-files", "ts"); + + eslint = new ESLint({ + cwd, + flags: newFlags, + overrideConfigFile: "eslint.undefined.config.ts" + }); + + await assert.rejects( + eslint.lintFiles("foo.js"), + { message: "Config (unnamed): Unexpected undefined config at user-defined index 0." } + ); + + }); + }); it("should stop linting files if a rule crashes", async () => {