diff --git a/src/input/findOriginalConfigurations.test.ts b/src/input/findOriginalConfigurations.test.ts index 7c476711a..f509636a2 100644 --- a/src/input/findOriginalConfigurations.test.ts +++ b/src/input/findOriginalConfigurations.test.ts @@ -47,7 +47,7 @@ const createStubDependencies = ( }); describe("findOriginalConfigurations", () => { - it("returns errors when the tslint finder returns an error", async () => { + it("returns the error when the tslint finder returns an error", async () => { // Arrange const complaint = "Complaint from TSLint"; const dependencies = createStubDependencies({ @@ -64,6 +64,23 @@ describe("findOriginalConfigurations", () => { }); }); + it("returns a package install error when the tslint finder returns a package install error", async () => { + // Arrange + const complaint = "could not require 'tslint'"; + const dependencies = createStubDependencies({ + findTSLintConfiguration: async () => new Error(complaint), + }); + + // Act + const result = await findOriginalConfigurations(dependencies, createRawSettings()); + + // Assert + expect(result).toEqual({ + complaints: ["Could not import the 'tslint' module. Do you need to install packages?"], + status: ResultStatus.ConfigurationError, + }); + }); + it("returns only tslint results when the other finders return errors", async () => { // Arrange const dependencies = createStubDependencies({ @@ -90,6 +107,33 @@ describe("findOriginalConfigurations", () => { }); }); + it.each(["Cannot find module", "could not require", "couldn't find the plugin"])( + "returns an error when an optional configuration returns a '%s' error", + async (message) => { + // Arrange + const eslint = new Error(`${message} "example"`); + const dependencies = createStubDependencies({ + findESLintConfiguration: async () => eslint, + }); + + // Act + const result = await findOriginalConfigurations( + dependencies, + createRawSettings({ + eslint: "./eslintrc.js", + }), + ); + + // Assert + expect(result).toEqual({ + complaints: [ + `Could not import the "example" module. Do you need to install packages?`, + ], + status: ResultStatus.ConfigurationError, + }); + }, + ); + it("returns an error when an optional configuration returns an error and the user asked for it", async () => { // Arrange const eslint = new Error("one"); diff --git a/src/input/findOriginalConfigurations.ts b/src/input/findOriginalConfigurations.ts index b112aae1b..872709f5d 100644 --- a/src/input/findOriginalConfigurations.ts +++ b/src/input/findOriginalConfigurations.ts @@ -1,5 +1,6 @@ import { SansDependencies } from "../binding"; import { ResultStatus, TSLintToESLintSettings, ResultWithDataStatus } from "../types"; +import { isDefined } from "../utils"; import { findESLintConfiguration, ESLintConfiguration } from "./findESLintConfiguration"; import { PackagesConfiguration, findPackagesConfiguration } from "./findPackagesConfiguration"; import { @@ -54,20 +55,42 @@ export const findOriginalConfigurations = async ( // Out of those configurations, only TSLint's is always required to run if (tslint instanceof Error) { return { - complaints: [tslint.message], + complaints: [getMissingPackageMessage(tslint) ?? tslint.message], status: ResultStatus.ConfigurationError, }; } - // Other configuration errors only halt the program if the user asked for them - const configurationErrors = [ - [eslint, rawSettings.eslint], - [packages, rawSettings.package], - [typescript, rawSettings.typescript], - ].filter(configurationResultIsError); - if (configurationErrors.length !== 0) { + const configurationResults = [ + [eslint, "eslint"], + [packages, "package"], + [typescript, "typescript"], + ] as const; + + // Other configuration errors only halt the program if... + const errorMessages = configurationResults + .map(([error, key]) => { + if (!(error instanceof Error)) { + return undefined; + } + + // * Their failure was caused by a missing package that needs to be installed + const missingPackageMessage = getMissingPackageMessage(error); + if (missingPackageMessage !== undefined) { + return missingPackageMessage; + } + + // * The user explicitly asked for them + if (typeof rawSettings[key] === "string") { + return error.message; + } + + return undefined; + }) + .filter(isDefined); + + if (errorMessages.length !== 0) { return { - complaints: configurationErrors.map(([configuration]) => configuration.message), + complaints: errorMessages, status: ResultStatus.ConfigurationError, }; } @@ -83,6 +106,13 @@ export const findOriginalConfigurations = async ( }; }; -const configurationResultIsError = (result: unknown[]): result is [Error, string] => { - return result[0] instanceof Error && typeof result[1] === "string"; +const getMissingPackageMessage = (error: Error) => { + const match = /(Cannot find module|could not require|couldn't find the plugin) ([a-zA-Z0-9-_"'@/]+)/.exec( + error.message, + ); + if (match === null) { + return undefined; + } + + return `Could not import the ${match[2]} module. Do you need to install packages?`; };