diff --git a/lib/cli-engine/config-array-factory.js b/lib/cli-engine/config-array-factory.js index 360ca0f4e9b..1bd314850d5 100644 --- a/lib/cli-engine/config-array-factory.js +++ b/lib/cli-engine/config-array-factory.js @@ -427,28 +427,22 @@ class ConfigArrayFactory { _loadConfigDataInDirectory(directoryPath, name) { for (const filename of configFilenames) { const filePath = path.join(directoryPath, filename); - const originalDebugEnabled = debug.enabled; - let configData; - // Make silent temporary because of too verbose. - debug.enabled = false; - try { - configData = loadConfigFile(filePath); - } catch (error) { - if ( - error.code !== "ENOENT" && - error.code !== "MODULE_NOT_FOUND" && - error.code !== "ESLINT_CONFIG_FIELD_NOT_FOUND" - ) { - throw error; + if (fs.existsSync(filePath)) { + let configData; + + try { + configData = loadConfigFile(filePath); + } catch (error) { + if (!error || error.code !== "ESLINT_CONFIG_FIELD_NOT_FOUND") { + throw error; + } } - } finally { - debug.enabled = originalDebugEnabled; - } - if (configData) { - debug(`Config file found: ${filePath}`); - return this._normalizeConfigData(configData, filePath, name); + if (configData) { + debug(`Config file found: ${filePath}`); + return this._normalizeConfigData(configData, filePath, name); + } } } @@ -695,20 +689,20 @@ class ConfigArrayFactory { ); } - try { - const filePath = ModuleResolver.resolve(request, relativeTo); + let filePath; - writeDebugLogForLoading(request, relativeTo, filePath); - - return this._loadConfigData(filePath, `${importerName} » ${request}`); + try { + filePath = ModuleResolver.resolve(request, relativeTo); } catch (error) { - /* istanbul ignore next */ - if (!error || error.code !== "MODULE_NOT_FOUND") { - throw error; + /* istanbul ignore else */ + if (error && error.code === "MODULE_NOT_FOUND") { + throw configMissingError(extendName); } + throw error; } - throw configMissingError(extendName); + writeDebugLogForLoading(request, relativeTo, filePath); + return this._loadConfigData(filePath, `${importerName} » ${request}`); } /** @@ -797,6 +791,7 @@ class ConfigArrayFactory { const { additionalPluginPool, resolvePluginsRelativeTo } = internalSlotsMap.get(this); const request = naming.normalizePackageName(name, "eslint-plugin"); const id = naming.getShorthandName(request, "eslint-plugin"); + const relativeTo = path.join(resolvePluginsRelativeTo, "__placeholder__.js"); if (name.match(/\s+/u)) { const error = Object.assign( @@ -830,25 +825,15 @@ class ConfigArrayFactory { }); } - try { - - // Resolve the plugin file - const relativeTo = path.join(resolvePluginsRelativeTo, "__placeholder__.js"); - const filePath = ModuleResolver.resolve(request, relativeTo); - - writeDebugLogForLoading(request, relativeTo, filePath); + let filePath; + let error; - return new ConfigDependency({ - definition: normalizePlugin(require(filePath)), - filePath, - id, - importerName, - importerPath - }); - } catch (error) { - debug("Failed to load plugin '%s' declared in '%s'.", name, importerName); - - if (error && error.code === "MODULE_NOT_FOUND" && error.message.includes(request)) { + try { + filePath = ModuleResolver.resolve(request, relativeTo); + } catch (resolveError) { + error = resolveError; + /* istanbul ignore else */ + if (error && error.code === "MODULE_NOT_FOUND") { error.messageTemplate = "plugin-missing"; error.messageData = { pluginName: request, @@ -856,15 +841,31 @@ class ConfigArrayFactory { importerName }; } - error.message = `Failed to load plugin '${name}' declared in '${importerName}': ${error.message}`; + } - return new ConfigDependency({ - error, - id, - importerName, - importerPath - }); + if (filePath) { + try { + writeDebugLogForLoading(request, relativeTo, filePath); + return new ConfigDependency({ + definition: normalizePlugin(require(filePath)), + filePath, + id, + importerName, + importerPath + }); + } catch (loadError) { + error = loadError; + } } + + debug("Failed to load plugin '%s' declared in '%s'.", name, importerName); + error.message = `Failed to load plugin '${name}' declared in '${importerName}': ${error.message}`; + return new ConfigDependency({ + error, + id, + importerName, + importerPath + }); } /** diff --git a/lib/util/relative-module-resolver.js b/lib/util/relative-module-resolver.js index 4d544826230..92e2f65a2ff 100644 --- a/lib/util/relative-module-resolver.js +++ b/lib/util/relative-module-resolver.js @@ -42,8 +42,14 @@ module.exports = { try { return createRequire(relativeToPath).resolve(moduleName); } catch (error) { - if (error && error.code === "MODULE_NOT_FOUND" && error.message.includes(moduleName)) { - error.message += ` relative to '${relativeToPath}'`; + if ( + typeof error === "object" && + error !== null && + error.code === "MODULE_NOT_FOUND" && + !error.requireStack && + error.message.includes(moduleName) + ) { + error.message += `\nRequire stack:\n- ${relativeToPath}`; } throw error; } diff --git a/tests/fixtures/module-not-found/.eslintrc.yml b/tests/fixtures/module-not-found/.eslintrc.yml new file mode 100644 index 00000000000..19345774951 --- /dev/null +++ b/tests/fixtures/module-not-found/.eslintrc.yml @@ -0,0 +1 @@ +root: true diff --git a/tests/fixtures/module-not-found/extends-js/.eslintrc.yml b/tests/fixtures/module-not-found/extends-js/.eslintrc.yml new file mode 100644 index 00000000000..46d4d52d396 --- /dev/null +++ b/tests/fixtures/module-not-found/extends-js/.eslintrc.yml @@ -0,0 +1 @@ +extends: nonexistent-config diff --git a/tests/fixtures/module-not-found/extends-plugin/.eslintrc.yml b/tests/fixtures/module-not-found/extends-plugin/.eslintrc.yml new file mode 100644 index 00000000000..5a236da708b --- /dev/null +++ b/tests/fixtures/module-not-found/extends-plugin/.eslintrc.yml @@ -0,0 +1 @@ +extends: plugin:nonexistent-plugin/foo diff --git a/tests/fixtures/module-not-found/node_modules/eslint-config-throw.js b/tests/fixtures/module-not-found/node_modules/eslint-config-throw.js new file mode 100644 index 00000000000..534f851190a --- /dev/null +++ b/tests/fixtures/module-not-found/node_modules/eslint-config-throw.js @@ -0,0 +1 @@ +require("eslint/lib/util/glob-utils") diff --git a/tests/fixtures/module-not-found/node_modules/eslint-plugin-throw.js b/tests/fixtures/module-not-found/node_modules/eslint-plugin-throw.js new file mode 100644 index 00000000000..534f851190a --- /dev/null +++ b/tests/fixtures/module-not-found/node_modules/eslint-plugin-throw.js @@ -0,0 +1 @@ +require("eslint/lib/util/glob-utils") diff --git a/tests/fixtures/module-not-found/plugins/.eslintrc.yml b/tests/fixtures/module-not-found/plugins/.eslintrc.yml new file mode 100644 index 00000000000..c85e0bb90eb --- /dev/null +++ b/tests/fixtures/module-not-found/plugins/.eslintrc.yml @@ -0,0 +1 @@ +plugins: [nonexistent-plugin] diff --git a/tests/fixtures/module-not-found/throw-in-config-itself/.eslintrc.js b/tests/fixtures/module-not-found/throw-in-config-itself/.eslintrc.js new file mode 100644 index 00000000000..534f851190a --- /dev/null +++ b/tests/fixtures/module-not-found/throw-in-config-itself/.eslintrc.js @@ -0,0 +1 @@ +require("eslint/lib/util/glob-utils") diff --git a/tests/fixtures/module-not-found/throw-in-extends-js/.eslintrc.yml b/tests/fixtures/module-not-found/throw-in-extends-js/.eslintrc.yml new file mode 100644 index 00000000000..13127ea3f72 --- /dev/null +++ b/tests/fixtures/module-not-found/throw-in-extends-js/.eslintrc.yml @@ -0,0 +1 @@ +extends: throw diff --git a/tests/fixtures/module-not-found/throw-in-extends-plugin/.eslintrc.yml b/tests/fixtures/module-not-found/throw-in-extends-plugin/.eslintrc.yml new file mode 100644 index 00000000000..a7ebd39544e --- /dev/null +++ b/tests/fixtures/module-not-found/throw-in-extends-plugin/.eslintrc.yml @@ -0,0 +1 @@ +extends: plugin:throw/foo diff --git a/tests/fixtures/module-not-found/throw-in-plugins/.eslintrc.yml b/tests/fixtures/module-not-found/throw-in-plugins/.eslintrc.yml new file mode 100644 index 00000000000..14e1f8cae9b --- /dev/null +++ b/tests/fixtures/module-not-found/throw-in-plugins/.eslintrc.yml @@ -0,0 +1 @@ +plugins: [throw] diff --git a/tests/lib/cli-engine.js b/tests/lib/cli-engine.js index 97d0d9e414a..2ba6eba965b 100644 --- a/tests/lib/cli-engine.js +++ b/tests/lib/cli-engine.js @@ -3031,6 +3031,103 @@ describe("CLIEngine", () => { assert.deepStrictEqual(results[0].output, "fixed;"); }); }); + + describe("MODULE_NOT_FOUND error handling", () => { + const cwd = getFixturePath("module-not-found"); + + beforeEach(() => { + engine = new CLIEngine({ cwd }); + }); + + it("should throw an error with a message template when 'extends' property has a non-existence JavaScript config.", () => { + try { + engine.executeOnText("test", "extends-js/test.js"); + } catch (err) { + assert.strictEqual(err.messageTemplate, "extend-config-missing"); + assert.deepStrictEqual(err.messageData, { + configName: "nonexistent-config" + }); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with a message template when 'extends' property has a non-existence plugin config.", () => { + try { + engine.executeOnText("test", "extends-plugin/test.js"); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, "plugin-missing"); + assert.deepStrictEqual(err.messageData, { + importerName: `extends-plugin${path.sep}.eslintrc.yml`, + pluginName: "eslint-plugin-nonexistent-plugin", + resolvePluginsRelativeTo: cwd + }); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with a message template when 'plugins' property has a non-existence plugin.", () => { + try { + engine.executeOnText("test", "plugins/test.js"); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, "plugin-missing"); + assert.deepStrictEqual(err.messageData, { + importerName: `plugins${path.sep}.eslintrc.yml`, + pluginName: "eslint-plugin-nonexistent-plugin", + resolvePluginsRelativeTo: cwd + }); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when a JavaScript config threw a 'MODULE_NOT_FOUND' error.", () => { + try { + engine.executeOnText("test", "throw-in-config-itself/test.js"); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when 'extends' property has a JavaScript config that throws a 'MODULE_NOT_FOUND' error.", () => { + try { + engine.executeOnText("test", "throw-in-extends-js/test.js"); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when 'extends' property has a plugin config that throws a 'MODULE_NOT_FOUND' error.", () => { + try { + engine.executeOnText("test", "throw-in-extends-plugin/test.js"); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when 'plugins' property has a plugin config that throws a 'MODULE_NOT_FOUND' error.", () => { + try { + engine.executeOnText("test", "throw-in-plugins/test.js"); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + }); }); describe("getConfigForFile", () => {