Repository: JoshuaKGoldberg/tslint-to-eslint-config
Pull request #42 (2019-06-30 21:29:27)

Set up pipeline of reading ESLint and TypeScript configs
Prep work necessary to start on #33 and #35. Adds an as-of-yet `unknown` result field for each of ESLint and TypeScript to the configuration find commands. Later on they'll be used.


Patch:
diff --git a/docs/Development.md b/docs/Development.md
index 0456c2d..b05e529 100644
--- a/docs/Development.md
+++ b/docs/Development.md
@@ -32,7 +32,16 @@ Any method with dependencies that should be stubbed out during tests, such as fi
 takes in a first argument of name `dependencies`.
 Its dependencies object is manually created in `src/cli/main.ts` and bound to the method with `bind`.
 
-`tslint-to-eslint-config` requires "100%" unit test coverage, excluding the following:
+### Unit Tests
+
+Each `src/**/*.ts` source file should have an equivalent `*.test.ts` next to it.
+Tests should include comments above each section of the "AAA" testing format:
+
+-   `// Arrange`: Set up dependencies for the code under test
+-   `// Act`: Perform the logic under test
+-   `// Assert`: Check the results of the acted logic
+
+`tslint-to-eslint-config` requires 100% unit test coverage, excluding the following:
 
 -   `src/cli/main.ts`
 -   `src/adapters/*.ts`
diff --git a/jest.config.js b/jest.config.js
index 95b153d..1ab81f1 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -4,6 +4,8 @@ module.exports = {
         "!./src/**/*.d.ts",
         "!./src/**/*.stubs.ts",
         "!./src/adapters/*.ts",
+        "!./src/rules/converters.ts",
+        "!./src/rules/mergers.ts",
         "!./src/cli/main.ts",
     ],
     coverageThreshold: {
diff --git a/src/adapters/exec.stubs.ts b/src/adapters/exec.stubs.ts
new file mode 100644
index 0000000..1190005
--- /dev/null
+++ b/src/adapters/exec.stubs.ts
@@ -0,0 +1,10 @@
+export type CreateStubExecSettings = {
+    stderr: string;
+    stdout: string;
+};
+
+export const createStubExec = ({ stderr = "", stdout = "" } = {}) =>
+    jest.fn().mockReturnValue(Promise.resolve({ stderr, stdout }));
+
+export const createStubThrowingExec = ({ stderr = "" } = {}) =>
+    jest.fn().mockRejectedValue(Promise.resolve(new Error(stderr)));
diff --git a/src/cli/main.ts b/src/cli/main.ts
index 0375d15..30e91ab 100644
--- a/src/cli/main.ts
+++ b/src/cli/main.ts
@@ -1,42 +1,61 @@
 import { EOL } from "os";
 
 import { bind } from "../binding";
-import {
-    createNewConfiguration,
-    CreateNewConfigurationDependencies,
-} from "../creation/createNewConfiguration";
-import { fsFileSystem } from "../adapters/fsFileSystem";
-import {
-    findTslintConfiguration,
-    FindTSlintConfigurationDependencies,
-} from "../input/findTslintConfiguration";
 import { runCli, RunCliDependencies } from "./runCli";
 import { processLogger } from "../adapters/processLogger";
+import { childProcessExec } from "../adapters/childProcessExec";
+import { fsFileSystem } from "../adapters/fsFileSystem";
 import { ConvertConfigDependencies, convertConfig } from "../conversion/convertConfig";
+import {
+    writeConversionResults,
+    WriteConversionResultsDependencies,
+} from "../creation/writeConversionResults";
+import {
+    findOriginalConfigurations,
+    FindOriginalConfigurationsDependencies,
+} from "../input/findOriginalConfigurations";
+import { findESLintConfiguration } from "../input/findESLintConfiguration";
+import { findTSLintConfiguration } from "../input/findTSLintConfiguration";
+import { findTypeScriptConfiguration } from "../input/findTypeScriptConfiguration";
 import {
     reportConversionResults,
     ReportConversionResultsDependencies,
 } from "../reporting/reportConversionResults";
-import { childProcessExec } from "../adapters/childProcessExec";
+import { converters } from "../rules/converters";
+import { convertRules } from "../rules/convertRules";
+import { mergers } from "../rules/mergers";
 
-const createNewConfigurationDependencies: CreateNewConfigurationDependencies = {
-    fileSystem: fsFileSystem,
+const convertRulesDependencies = {
+    converters,
+    mergers,
 };
 
-const findTslintConfigurationDependencies: FindTSlintConfigurationDependencies = {
+const findConfigurationDependencies = {
     exec: childProcessExec,
 };
 
+const findOriginalConfigurationsDependencies: FindOriginalConfigurationsDependencies = {
+    findESLintConfiguration: bind(findESLintConfiguration, findConfigurationDependencies),
+    findTypeScriptConfiguration: bind(findTypeScriptConfiguration, findConfigurationDependencies),
+    findTSLintConfiguration: bind(findTSLintConfiguration, findConfigurationDependencies),
+};
+
 const reportConversionResultsDependencies: ReportConversionResultsDependencies = {
     logger: processLogger,
 };
 
-const convertConfigDependencies: ConvertConfigDependencies = {
-    createNewConfiguration: bind(createNewConfiguration, createNewConfigurationDependencies),
+const writeConversionResultsDependencies: WriteConversionResultsDependencies = {
     fileSystem: fsFileSystem,
-    findTslintConfiguration: bind(findTslintConfiguration, findTslintConfigurationDependencies),
-    logger: processLogger,
+};
+
+const convertConfigDependencies: ConvertConfigDependencies = {
+    convertRules: bind(convertRules, convertRulesDependencies),
+    findOriginalConfigurations: bind(
+        findOriginalConfigurations,
+        findOriginalConfigurationsDependencies,
+    ),
     reportConversionResults: bind(reportConversionResults, reportConversionResultsDependencies),
+    writeConversionResults: bind(writeConversionResults, writeConversionResultsDependencies),
 };
 
 const runCliDependencies: RunCliDependencies = {
diff --git a/src/cli/runCli.ts b/src/cli/runCli.ts
index b7ebdd7..44fa5fd 100644
--- a/src/cli/runCli.ts
+++ b/src/cli/runCli.ts
@@ -35,7 +35,7 @@ export const runCli = async (
         result = await dependencies.convertConfig(parsedArgv);
     } catch (error) {
         result = {
-            error: error as Error,
+            errors: [error as Error],
             status: ResultStatus.Failed,
         };
     }
@@ -47,16 +47,22 @@ export const runCli = async (
 
         case ResultStatus.ConfigurationError:
             dependencies.logger.stderr.write(chalk.redBright("❌ "));
-            dependencies.logger.stderr.write(chalk.yellow("Could not start tslint-to-eslint: "));
-            dependencies.logger.stderr.write(chalk.yellowBright(result.complaint));
+            dependencies.logger.stderr.write(chalk.red("Could not start tslint-to-eslint:"));
             dependencies.logger.stderr.write(chalk.redBright(` ❌${EOL}`));
+            for (const complaint of result.complaints) {
+                dependencies.logger.stderr.write(chalk.yellowBright(`${complaint}${EOL}`));
+            }
             break;
 
         case ResultStatus.Failed:
             dependencies.logger.stderr.write(chalk.redBright("❌ "));
-            dependencies.logger.stderr.write(chalk.yellow("Error running tslint-to-eslint:"));
+            dependencies.logger.stderr.write(chalk.yellow(`${result.errors.length} error`));
+            dependencies.logger.stderr.write(chalk.yellow(result.errors.length === 1 ? "" : "s"));
+            dependencies.logger.stderr.write(chalk.yellow(" running tslint-to-eslint:"));
             dependencies.logger.stderr.write(chalk.redBright(` ❌${EOL}`));
-            dependencies.logger.stderr.write(chalk.yellowBright(`${result.error.stack}${EOL}`));
+            for (const error of result.errors) {
+                dependencies.logger.stderr.write(chalk.yellowBright(`${error.stack}${EOL}`));
+            }
             break;
     }
 
diff --git a/src/conversion/convertConfig.ts b/src/conversion/convertConfig.ts
index b8ea952..7933872 100644
--- a/src/conversion/convertConfig.ts
+++ b/src/conversion/convertConfig.ts
@@ -1,53 +1,35 @@
-import { FileSystem } from "../adapters/fileSystem";
-import { Logger } from "../adapters/logger";
 import { SansDependencies } from "../binding";
-import { createNewConfiguration } from "../creation/createNewConfiguration";
-import { findTslintConfiguration } from "../input/findTslintConfiguration";
-import { convertRules } from "../rules/convertRules";
-import { converters } from "../rules/converters";
-import { mergers } from "../rules/mergers";
-import { TSLintToESLintSettings, TSLintToESLintResult, ResultStatus } from "../types";
+import { writeConversionResults } from "../creation/writeConversionResults";
+import { findOriginalConfigurations } from "../input/findOriginalConfigurations";
 import { reportConversionResults } from "../reporting/reportConversionResults";
+import { convertRules } from "../rules/convertRules";
+import { ResultStatus, ResultWithStatus, TSLintToESLintSettings } from "../types";
 
 export type ConvertConfigDependencies = {
-    createNewConfiguration: SansDependencies<typeof createNewConfiguration>;
-    fileSystem: Pick<FileSystem, "fileExists">;
-    findTslintConfiguration: SansDependencies<typeof findTslintConfiguration>;
-    logger: Logger;
+    convertRules: SansDependencies<typeof convertRules>;
+    findOriginalConfigurations: SansDependencies<typeof findOriginalConfigurations>;
     reportConversionResults: SansDependencies<typeof reportConversionResults>;
+    writeConversionResults: SansDependencies<typeof writeConversionResults>;
 };
 
 export const convertConfig = async (
     dependencies: ConvertConfigDependencies,
     settings: TSLintToESLintSettings,
-): Promise<TSLintToESLintResult> => {
-    const { config = "./tslint.json" } = settings;
-    if (!(await dependencies.fileSystem.fileExists(config))) {
-        return {
-            complaint: `${config} does not seem to exist.`,
-            status: ResultStatus.ConfigurationError,
-        };
+): Promise<ResultWithStatus> => {
+    const originalConfigurations = await dependencies.findOriginalConfigurations(settings);
+    if (originalConfigurations.status !== ResultStatus.Succeeded) {
+        return originalConfigurations;
     }
 
-    const originalConfiguration = await dependencies.findTslintConfiguration(config);
-    if (originalConfiguration instanceof Error) {
-        return {
-            error: originalConfiguration,
-            status: ResultStatus.Failed,
-        };
-    }
-
-    const convertedRules = convertRules(
-        Object.entries(originalConfiguration.rules).map(([ruleName, value]) => ({
-            ruleName,
-            ...value,
-        })),
-        converters,
-        mergers,
+    const configConversonResults = dependencies.convertRules(
+        originalConfigurations.data.tslint.rules,
     );
 
-    await dependencies.createNewConfiguration(convertedRules, originalConfiguration);
-    dependencies.reportConversionResults(convertedRules);
+    await dependencies.writeConversionResults(
+        configConversonResults,
+        originalConfigurations.data.tslint,
+    );
+    dependencies.reportConversionResults(configConversonResults);
 
     return {
         status: ResultStatus.Succeeded,
diff --git a/src/creation/formatConvertedRules.ts b/src/creation/formatConvertedRules.ts
index 8d875e8..8712b21 100644
--- a/src/creation/formatConvertedRules.ts
+++ b/src/creation/formatConvertedRules.ts
@@ -1,11 +1,11 @@
+import { TSLintConfiguration } from "../input/findTSLintConfiguration";
 import { ConfigConversionResults } from "../rules/convertRules";
 import { ESLintRuleOptions } from "../rules/types";
 import { formatMissingRules } from "./formatMissingRules";
-import { TSLintConfiguration } from "../input/findTslintConfiguration";
 
 export const formatConvertedRules = (
     conversionResults: ConfigConversionResults,
-    originalConfiguration: TSLintConfiguration,
+    tslintConfiguration: TSLintConfiguration,
 ) => {
     const output: { [i: string]: string | any[] } = {};
     const sortedRuleEntries = Array.from(conversionResults.converted).sort(
@@ -19,7 +19,7 @@ export const formatConvertedRules = (
     if (conversionResults.missing.length !== 0) {
         output["@typescript-eslint/tslint/config"] = formatMissingRules(
             conversionResults.missing,
-            originalConfiguration.ruleDirectories,
+            tslintConfiguration.ruleDirectories,
         );
     }
 
diff --git a/src/creation/createNewConfiguration.ts b/src/creation/writeConversionResults.ts
similarity index 65%
rename from src/creation/createNewConfiguration.ts
rename to src/creation/writeConversionResults.ts
index 12aa90e..451f57f 100644
--- a/src/creation/createNewConfiguration.ts
+++ b/src/creation/writeConversionResults.ts
@@ -1,16 +1,16 @@
 import { FileSystem } from "../adapters/fileSystem";
-import { TSLintConfiguration } from "../input/findTslintConfiguration";
+import { TSLintConfiguration } from "../input/findTSLintConfiguration";
 import { ConfigConversionResults } from "../rules/convertRules";
 import { formatConvertedRules } from "./formatConvertedRules";
 
-export type CreateNewConfigurationDependencies = {
+export type WriteConversionResultsDependencies = {
     fileSystem: Pick<FileSystem, "writeFile">;
 };
 
-export const createNewConfiguration = async (
-    dependencies: CreateNewConfigurationDependencies,
+export const writeConversionResults = async (
+    dependencies: WriteConversionResultsDependencies,
     conversionResults: ConfigConversionResults,
-    originalConfiguration: TSLintConfiguration,
+    tslintConfiguration: TSLintConfiguration,
 ) => {
     const output = {
         parser: "@typescript-eslint/parser",
@@ -20,7 +20,7 @@ export const createNewConfiguration = async (
         ...(conversionResults.missing.length && {
             plugins: ["@typescript-eslint/tslint"],
         }),
-        rules: formatConvertedRules(conversionResults, originalConfiguration),
+        rules: formatConvertedRules(conversionResults, tslintConfiguration),
     };
 
     await dependencies.fileSystem.writeFile(".eslintrc.json", JSON.stringify(output, undefined, 4));
diff --git a/src/input/findESLintConfiguration.ts b/src/input/findESLintConfiguration.ts
new file mode 100644
index 0000000..e544cfa
--- /dev/null
+++ b/src/input/findESLintConfiguration.ts
@@ -0,0 +1,22 @@
+import { Exec } from "../adapters/exec";
+import { findLintConfiguration } from "./findLintConfiguration";
+
+// Soon, this will be filled out with real information...
+export type ESLintConfiguration = unknown;
+
+const defaultESLintConfiguration = {};
+
+export type FindESLintConfigurationDependencies = {
+    exec: Exec;
+};
+
+export const findESLintConfiguration = (
+    dependencies: FindESLintConfigurationDependencies,
+    config: string | undefined,
+) =>
+    findLintConfiguration<ESLintConfiguration>(
+        dependencies.exec,
+        "eslint --print-config",
+        config || "./eslintrc.js",
+        defaultESLintConfiguration,
+    );
diff --git a/src/input/findLintConfiguration.ts b/src/input/findLintConfiguration.ts
new file mode 100644
index 0000000..d382cfc
--- /dev/null
+++ b/src/input/findLintConfiguration.ts
@@ -0,0 +1,37 @@
+import { Exec } from "../adapters/exec";
+
+export const findLintConfiguration = async <Configuration>(
+    exec: Exec,
+    command: string,
+    config: string,
+    defaultValues: Configuration,
+): Promise<Configuration | Error> => {
+    const fullCommand = `${command} ${config}`;
+    const stdout = await execAndCatch(exec, fullCommand);
+    if (stdout instanceof Error) {
+        return stdout;
+    }
+
+    try {
+        return {
+            ...defaultValues,
+            ...(JSON.parse(stdout) as Partial<Configuration>),
+        };
+    } catch (error) {
+        return new Error(`Error parsing configuration: ${error}`);
+    }
+};
+
+const execAndCatch = async (exec: Exec, fullCommand: string) => {
+    try {
+        const { stderr, stdout } = await exec(fullCommand);
+
+        if (stderr) {
+            return new Error(stderr);
+        }
+
+        return stdout;
+    } catch (error) {
+        return error;
+    }
+};
diff --git a/src/input/findOriginalConfigurations.ts b/src/input/findOriginalConfigurations.ts
new file mode 100644
index 0000000..e969891
--- /dev/null
+++ b/src/input/findOriginalConfigurations.ts
@@ -0,0 +1,47 @@
+import { SansDependencies } from "../binding";
+import { ResultStatus, TSLintToESLintSettings, ResultWithDataStatus } from "../types";
+import { findESLintConfiguration, ESLintConfiguration } from "./findESLintConfiguration";
+import {
+    findTypeScriptConfiguration,
+    TypeScriptConfiguration,
+} from "./findTypeScriptConfiguration";
+import { findTSLintConfiguration, TSLintConfiguration } from "./findTSLintConfiguration";
+
+export type FindOriginalConfigurationsDependencies = {
+    findESLintConfiguration: SansDependencies<typeof findESLintConfiguration>;
+    findTypeScriptConfiguration: SansDependencies<typeof findTypeScriptConfiguration>;
+    findTSLintConfiguration: SansDependencies<typeof findTSLintConfiguration>;
+};
+
+export type OriginalConfigurationsData = {
+    eslint?: ESLintConfiguration;
+    tslint: TSLintConfiguration;
+    typescript?: TypeScriptConfiguration;
+};
+
+export const findOriginalConfigurations = async (
+    dependencies: FindOriginalConfigurationsDependencies,
+    rawSettings: TSLintToESLintSettings,
+): Promise<ResultWithDataStatus<OriginalConfigurationsData>> => {
+    const [eslint, tslint, typescript] = await Promise.all([
+        dependencies.findESLintConfiguration(rawSettings.eslintConfig),
+        dependencies.findTSLintConfiguration(rawSettings.tslintConfig),
+        dependencies.findTypeScriptConfiguration(rawSettings.typescriptConfig),
+    ]);
+
+    if (tslint instanceof Error) {
+        return {
+            complaints: [tslint.message],
+            status: ResultStatus.ConfigurationError,
+        };
+    }
+
+    return {
+        data: {
+            ...(!(eslint instanceof Error) && { eslint }),
+            tslint,
+            ...(!(typescript instanceof Error) && { typescript }),
+        },
+        status: ResultStatus.Succeeded,
+    };
+};
diff --git a/src/input/findTSLintConfiguration.ts b/src/input/findTSLintConfiguration.ts
new file mode 100644
index 0000000..ea02605
--- /dev/null
+++ b/src/input/findTSLintConfiguration.ts
@@ -0,0 +1,31 @@
+import { Exec } from "../adapters/exec";
+import { findLintConfiguration } from "./findLintConfiguration";
+
+export type TSLintConfiguration = {
+    ruleDirectories: string[];
+    rules: TSLintConfigurationRules;
+};
+
+export type TSLintConfigurationRules = {
+    [i: string]: any;
+};
+
+const defaultTSLintConfiguration = {
+    ruleDirectories: [],
+    rules: {},
+};
+
+export type FindTSLintConfigurationDependencies = {
+    exec: Exec;
+};
+
+export const findTSLintConfiguration = (
+    dependencies: FindTSLintConfigurationDependencies,
+    config: string | undefined,
+) =>
+    findLintConfiguration<TSLintConfiguration>(
+        dependencies.exec,
+        "tslint --print-config",
+        config || "./tslint.json",
+        defaultTSLintConfiguration,
+    );
diff --git a/src/input/findTslintConfiguration.ts b/src/input/findTslintConfiguration.ts
deleted file mode 100644
index a487627..0000000
--- a/src/input/findTslintConfiguration.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { Exec } from "../adapters/exec";
-
-export type TSLintConfiguration = {
-    ruleDirectories: string[];
-    rules: {
-        [i: string]: any;
-    };
-};
-
-export type FindTSlintConfigurationDependencies = {
-    exec: Exec;
-};
-
-export const findTslintConfiguration = async (
-    dependencies: FindTSlintConfigurationDependencies,
-    config: string,
-): Promise<TSLintConfiguration | Error> => {
-    const command = buildCommand(config);
-    const { stderr, stdout } = await dependencies.exec(command);
-
-    if (stderr) {
-        return new Error(stderr);
-    }
-
-    try {
-        return {
-            ruleDirectories: [],
-            rules: {},
-            ...(JSON.parse(stdout) as Partial<TSLintConfiguration>),
-        };
-    } catch (error) {
-        return new Error(`Error parsing TSLint configuration: ${error}`);
-    }
-};
-
-const buildCommand = (config: string) => {
-    return ["tslint --print-config", config].filter(Boolean).join(" ");
-};
diff --git a/src/input/findTypeScriptConfiguration.ts b/src/input/findTypeScriptConfiguration.ts
new file mode 100644
index 0000000..9e15b53
--- /dev/null
+++ b/src/input/findTypeScriptConfiguration.ts
@@ -0,0 +1,22 @@
+import { Exec } from "../adapters/exec";
+import { findLintConfiguration } from "./findLintConfiguration";
+
+// Soon, this will be filled out with real information...
+export type TypeScriptConfiguration = unknown;
+
+const defaultTypeScriptConfiguration = {};
+
+export type FindTypeScriptConfigurationDependencies = {
+    exec: Exec;
+};
+
+export const findTypeScriptConfiguration = (
+    dependencies: FindTypeScriptConfigurationDependencies,
+    config: string | undefined,
+) =>
+    findLintConfiguration<TypeScriptConfiguration>(
+        dependencies.exec,
+        "cat",
+        config || "./tsconfig.json",
+        defaultTypeScriptConfiguration,
+    );
diff --git a/src/rules/convertRules.ts b/src/rules/convertRules.ts
index da6928a..b42bf65 100644
--- a/src/rules/convertRules.ts
+++ b/src/rules/convertRules.ts
@@ -1,3 +1,4 @@
+import { TSLintConfigurationRules } from "../input/findTSLintConfiguration";
 import { ConversionError } from "./conversionError";
 import { convertRule } from "./convertRule";
 import { convertRuleSeverity } from "./convertRuleSeverity";
@@ -5,6 +6,11 @@ import { TSLintRuleOptions, ESLintRuleOptions } from "./types";
 import { RuleConverter } from "./converter";
 import { RuleMerger } from "./merger";
 
+export type ConvertRulesDependencies = {
+    converters: Map<string, RuleConverter>;
+    mergers: Map<string, RuleMerger>;
+};
+
 export type ConfigConversionResults = {
     converted: Map<string, ESLintRuleOptions>;
     failed: ConversionError[];
@@ -13,17 +19,20 @@ export type ConfigConversionResults = {
 };
 
 export const convertRules = (
-    tslintRules: TSLintRuleOptions[],
-    converters: Map<string, RuleConverter>,
-    mergers: Map<string, RuleMerger>,
+    dependencies: ConvertRulesDependencies,
+    rawTslintRules: TSLintConfigurationRules,
 ): ConfigConversionResults => {
     const converted = new Map<string, ESLintRuleOptions>();
     const failed: ConversionError[] = [];
     const missing: TSLintRuleOptions[] = [];
     const packages = new Set<string>();
 
-    for (const tslintRule of tslintRules) {
-        const conversion = convertRule(tslintRule, converters);
+    for (const [ruleName, value] of Object.entries(rawTslintRules)) {
+        const tslintRule = {
+            ruleName,
+            ...value,
+        };
+        const conversion = convertRule(tslintRule, dependencies.converters);
         if (conversion === undefined) {
             if (tslintRule.ruleSeverity !== "off") {
                 missing.push(tslintRule);
@@ -49,7 +58,7 @@ export const convertRules = (
                 continue;
             }
 
-            const merger = mergers.get(changes.ruleName);
+            const merger = dependencies.mergers.get(changes.ruleName);
             if (merger === undefined) {
                 failed.push(
                     new ConversionError(
diff --git a/src/types.ts b/src/types.ts
index 7993dfd..4faf571 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,25 +1,50 @@
 export type TSLintToESLintSettings = {
-    config?: string;
+    /**
+     * Original ESLint configuration file path, such as `.eslintrc.js`.
+     */
+    eslintConfig?: string;
+
+    /**
+     * Original TSLint configuration file path, such as `tslint.json`.
+     */
+    tslintConfig?: string;
+
+    /**
+     * Original TypeScript configuration file path, such as `tsconfig.json`.
+     */
+    typescriptConfig?: string;
 };
 
+export type TSLintToESLintResult = ResultWithStatus;
+
 export enum ResultStatus {
     Succeeded = 0,
     Failed = 1,
     ConfigurationError = 2,
 }
 
-export type TSLintToESLintResult = ConfigurationErrorResult | FailedResult | SucceededResult;
+export type ResultWithStatus = ConfigurationErrorResult | FailedResult | SucceededResult;
+
+export type ResultWithDataStatus<Data> =
+    | ConfigurationErrorResult
+    | FailedResult
+    | SucceededDataResult<Data>;
 
 export type ConfigurationErrorResult = {
-    readonly complaint: string;
+    readonly complaints: string[];
     readonly status: ResultStatus.ConfigurationError;
 };
 
 export type FailedResult = {
-    readonly error: Error;
+    readonly errors: Error[];
     readonly status: ResultStatus.Failed;
 };
 
 export type SucceededResult = {
     readonly status: ResultStatus.Succeeded;
 };
+
+export type SucceededDataResult<Data> = {
+    readonly data: Data;
+    readonly status: ResultStatus.Succeeded;
+};
diff --git a/src/utils.ts b/src/utils.ts
index 2bc494a..0d87a30 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,1 +1,7 @@
-export const isDefined = <T>(item: T | undefined): item is T => !!item;
+export const isDefined = <Item>(item: Item | undefined): item is Item => !!item;
+
+export const isError = <Item>(item: Item | Error): item is Error => item instanceof Error;
+
+export type RemoveErrors<Items> = {
+    [P in keyof Items]: Exclude<Items[P], Error>;
+};


Test patch:
diff --git a/src/binding.test.ts b/src/binding.test.ts
new file mode 100644
index 0000000..74503f1
--- /dev/null
+++ b/src/binding.test.ts
@@ -0,0 +1,16 @@
+import { bind } from "./binding";
+
+describe("bind", () => {
+    it("calls the original method with bound dependencies when called", () => {
+        // Arrange
+        const dependencies = { original: true };
+        const method = jest.fn();
+        const bound = bind(method, dependencies);
+
+        // Act
+        bound("first", "second");
+
+        // Assert
+        expect(method).toHaveBeenLastCalledWith(dependencies, "first", "second");
+    });
+});
diff --git a/src/cli/runCli.test.ts b/src/cli/runCli.test.ts
index fd34884..07fae68 100644
--- a/src/cli/runCli.test.ts
+++ b/src/cli/runCli.test.ts
@@ -3,27 +3,23 @@ import { EOL } from "os";
 import { version } from "../../package.json";
 import { createStubLogger, expectEqualWrites } from "../adapters/logger.stubs";
 import { ResultStatus, TSLintToESLintResult } from "../types";
-import { runCli } from "./runCli";
+import { runCli, RunCliDependencies } from "./runCli";
 
 const createStubArgv = (argv: string[] = []) => ["node", "some/path/bin/file", ...argv];
 
-const createStubDependencies = (
-    convertConfig = async (): Promise<TSLintToESLintResult> => ({ status: ResultStatus.Succeeded }),
+const createStubRunCliDependencies = (
+    overrides: Partial<Pick<RunCliDependencies, "convertConfig">> = {},
 ) => ({
-    convertConfig,
-    fileSystem: {
-        fileExists: jest.fn(),
-        readFile: jest.fn(),
-        writeFile: jest.fn(),
-    },
+    convertConfig: async (): Promise<TSLintToESLintResult> => ({ status: ResultStatus.Succeeded }),
     logger: createStubLogger(),
+    ...overrides,
 });
 
 describe("runCli", () => {
     it("prints the package version when --version is provided", async () => {
         // Arrange
         const rawArgv = createStubArgv(["--version"]);
-        const dependencies = createStubDependencies();
+        const dependencies = createStubRunCliDependencies();
 
         // Act
         await runCli(dependencies, rawArgv);
@@ -35,7 +31,9 @@ describe("runCli", () => {
     it("logs an error to stderr when convertConfig throws an error", async () => {
         // Arrange
         const message = "Oh no";
-        const dependencies = createStubDependencies(() => Promise.reject(new Error(message)));
+        const dependencies = createStubRunCliDependencies({
+            convertConfig: () => Promise.reject(new Error(message)),
+        });
 
         // Act
         const status = await runCli(dependencies, createStubArgv());
@@ -50,12 +48,13 @@ describe("runCli", () => {
     it("returns a configuration complaint when convertConfig fails", async () => {
         // Arrange
         const complaint = "too much unit testing coverage";
-        const dependencies = createStubDependencies(() =>
-            Promise.resolve({
-                complaint,
-                status: ResultStatus.ConfigurationError,
-            }),
-        );
+        const dependencies = createStubRunCliDependencies({
+            convertConfig: () =>
+                Promise.resolve({
+                    complaints: [complaint],
+                    status: ResultStatus.ConfigurationError,
+                }),
+        });
 
         // Act
         const status = await runCli(dependencies, createStubArgv());
@@ -64,19 +63,21 @@ describe("runCli", () => {
         expect(status).toBe(ResultStatus.ConfigurationError);
         expectEqualWrites(
             dependencies.logger.stderr.write,
-            `❌ Could not start tslint-to-eslint: ${complaint} ❌`,
+            "❌ Could not start tslint-to-eslint: ❌",
+            complaint,
         );
     });
 
-    it("returns a failed status when convertConfig fails", async () => {
+    it("returns a singular failed status when convertConfig fails with one error", async () => {
         // Arrange
         const error = new Error("too much unit testing coverage");
-        const dependencies = createStubDependencies(() =>
-            Promise.resolve({
-                error,
-                status: ResultStatus.Failed,
-            }),
-        );
+        const dependencies = createStubRunCliDependencies({
+            convertConfig: () =>
+                Promise.resolve({
+                    errors: [error],
+                    status: ResultStatus.Failed,
+                }),
+        });
 
         // Act
         const status = await runCli(dependencies, createStubArgv());
@@ -85,14 +86,41 @@ describe("runCli", () => {
         expect(status).toBe(ResultStatus.Failed);
         expectEqualWrites(
             dependencies.logger.stderr.write,
-            "❌ Error running tslint-to-eslint: ❌",
+            "❌ 1 error running tslint-to-eslint: ❌",
             `${error.stack}`,
         );
     });
 
+    it("returns a plural failed status when convertConfig fails with two errors", async () => {
+        // Arrange
+        const errors = [
+            new Error("too much unit testing coverage"),
+            new Error("too much branch coverage"),
+        ];
+        const dependencies = createStubRunCliDependencies({
+            convertConfig: () =>
+                Promise.resolve({
+                    errors,
+                    status: ResultStatus.Failed,
+                }),
+        });
+
+        // Act
+        const status = await runCli(dependencies, createStubArgv());
+
+        // Assert
+        expect(status).toBe(ResultStatus.Failed);
+        expectEqualWrites(
+            dependencies.logger.stderr.write,
+            "❌ 2 errors running tslint-to-eslint: ❌",
+            `${errors[0].stack}`,
+            `${errors[1].stack}`,
+        );
+    });
+
     it("returns a successful status when convertConfig succeeds", async () => {
         // Arrange
-        const dependencies = createStubDependencies();
+        const dependencies = createStubRunCliDependencies();
 
         // Act
         const status = await runCli(dependencies, createStubArgv());
diff --git a/src/conversion/convertConfig.test.ts b/src/conversion/convertConfig.test.ts
index cf68b63..179c96c 100644
--- a/src/conversion/convertConfig.test.ts
+++ b/src/conversion/convertConfig.test.ts
@@ -1,97 +1,54 @@
-import { ResultStatus, TSLintToESLintSettings } from "../types";
-import { createStubLogger } from "../adapters/logger.stubs";
+import { ResultStatus, FailedResult, SucceededDataResult } from "../types";
 import { convertConfig, ConvertConfigDependencies } from "./convertConfig";
+import { OriginalConfigurationsData } from "../input/findOriginalConfigurations";
 
-const createStubDependencies = (overrides: Partial<ConvertConfigDependencies> = {}) => ({
-    createNewConfiguration: jest.fn().mockReturnValue(Promise.resolve()),
-    fileSystem: { fileExists: jest.fn().mockReturnValue(Promise.resolve(true)) },
-    findTslintConfiguration: jest.fn().mockReturnValue(Promise.resolve(new Error())),
-    logger: createStubLogger(),
+const createStubDependencies = (
+    overrides: Pick<ConvertConfigDependencies, "findOriginalConfigurations">,
+) => ({
+    convertRules: jest.fn(),
     reportConversionResults: jest.fn(),
+    writeConversionResults: jest.fn().mockReturnValue(Promise.resolve()),
     ...overrides,
 });
 
-const createStubSettings = (settings: Partial<TSLintToESLintSettings> = {}) => ({
-    ...settings,
+const createStubOriginalConfigurationsData = () => ({
+    eslint: {},
+    tslint: {
+        rules: [],
+        ruleDirectories: [],
+    },
+    typescript: {},
 });
 
 describe("convertConfig", () => {
-    it("complains when the provided config file does not exist", async () => {
+    it("returns the failure result when finding the original configurations fails", async () => {
         // Arrange
+        const findError: FailedResult = {
+            errors: [],
+            status: ResultStatus.Failed,
+        };
         const dependencies = createStubDependencies({
-            fileSystem: {
-                fileExists: jest.fn().mockReturnValue(Promise.resolve(false)),
-            },
-        });
-        const settings = createStubSettings();
-
-        // Act
-        const result = await convertConfig(dependencies, settings);
-
-        // Assert
-        expect(result).toEqual({
-            complaint: `./tslint.json does not seem to exist.`,
-            status: ResultStatus.ConfigurationError,
-        });
-    });
-
-    it("searches for settings.config when settings.config is provided", async () => {
-        // Arrange
-        const dependencies = createStubDependencies();
-        const settings = createStubSettings({
-            config: "./stub/tslint.json",
-        });
-
-        // Act
-        await convertConfig(dependencies, settings);
-
-        // Assert
-        expect(dependencies.findTslintConfiguration).toHaveBeenLastCalledWith(settings.config);
-    });
-
-    it("searches for ./tslint.json by default when no settings.config is provided", async () => {
-        // Arrange
-        const dependencies = createStubDependencies();
-        const settings = createStubSettings();
-
-        // Act
-        await convertConfig(dependencies, settings);
-
-        // Assert
-        expect(dependencies.findTslintConfiguration).toHaveBeenLastCalledWith("./tslint.json");
-    });
-
-    it("returns a failure result when findTslintConfiguration returns an error", async () => {
-        // Arrange
-        const error = new Error("oh no");
-        const dependencies = createStubDependencies({
-            findTslintConfiguration: jest.fn().mockReturnValue(Promise.resolve(error)),
+            findOriginalConfigurations: async () => findError,
         });
-        const settings = createStubSettings();
+        const settings = {};
 
         // Act
         const result = await convertConfig(dependencies, settings);
 
         // Assert
-        expect(result).toEqual({
-            error,
-            status: ResultStatus.Failed,
-        });
+        expect(result).toEqual(findError);
     });
 
-    it("creates a new configuration when findTslintConfiguration returns rules", async () => {
+    it("returns a successful result when finding the original configurations succeeds", async () => {
         // Arrange
+        const findSuccess: SucceededDataResult<OriginalConfigurationsData> = {
+            data: createStubOriginalConfigurationsData(),
+            status: ResultStatus.Succeeded,
+        };
         const dependencies = createStubDependencies({
-            findTslintConfiguration: jest.fn().mockReturnValue({
-                rules: {
-                    "sample-rule": {
-                        ruleArguments: ["one", "two"],
-                        ruleName: "sample-rule",
-                    },
-                },
-            }),
+            findOriginalConfigurations: async () => findSuccess,
         });
-        const settings = createStubSettings();
+        const settings = {};
 
         // Act
         const result = await convertConfig(dependencies, settings);
diff --git a/src/creation/createNewConfiguration.test.ts b/src/creation/writeConversionResults.test.ts
similarity index 87%
rename from src/creation/createNewConfiguration.test.ts
rename to src/creation/writeConversionResults.test.ts
index 0c777af..4e5bfc4 100644
--- a/src/creation/createNewConfiguration.test.ts
+++ b/src/creation/writeConversionResults.test.ts
@@ -1,12 +1,12 @@
 import { createEmptyConversionResults } from "../conversion/conversionResults.stubs";
-import { createNewConfiguration } from "./createNewConfiguration";
+import { writeConversionResults } from "./writeConversionResults";
 
-const originalConfiguration = {
+const tslintConfiguration = {
     ruleDirectories: [],
     rules: {},
 };
 
-describe("createNewConfiguration", () => {
+describe("writeConversionResults", () => {
     it("excludes the tslint plugin when there are no missing rules", async () => {
         // Arrange
         const conversionResults = createEmptyConversionResults({
@@ -15,7 +15,7 @@ describe("createNewConfiguration", () => {
         const fileSystem = { writeFile: jest.fn().mockReturnValue(Promise.resolve()) };
 
         // Act
-        await createNewConfiguration({ fileSystem }, conversionResults, originalConfiguration);
+        await writeConversionResults({ fileSystem }, conversionResults, tslintConfiguration);
 
         // Assert
         expect(fileSystem.writeFile).toHaveBeenLastCalledWith(
@@ -49,7 +49,7 @@ describe("createNewConfiguration", () => {
         const fileSystem = { writeFile: jest.fn().mockReturnValue(Promise.resolve()) };
 
         // Act
-        await createNewConfiguration({ fileSystem }, conversionResults, originalConfiguration);
+        await writeConversionResults({ fileSystem }, conversionResults, tslintConfiguration);
 
         // Assert
         expect(fileSystem.writeFile).toHaveBeenLastCalledWith(
diff --git a/src/input/findESLintConfiguration.test.ts b/src/input/findESLintConfiguration.test.ts
new file mode 100644
index 0000000..b6cd82f
--- /dev/null
+++ b/src/input/findESLintConfiguration.test.ts
@@ -0,0 +1,41 @@
+import { findESLintConfiguration } from "./findESLintConfiguration";
+import { createStubExec } from "../adapters/exec.stubs";
+
+describe("findESLintConfiguration", () => {
+    it("defaults the configuration file when one isn't provided", async () => {
+        // Arrange
+        const dependencies = { exec: createStubExec() };
+
+        // Act
+        await findESLintConfiguration(dependencies, undefined);
+
+        // Assert
+        expect(dependencies.exec).toHaveBeenLastCalledWith("eslint --print-config ./eslintrc.js");
+    });
+
+    it("includes a configuration file in the ESLint command when one is provided", async () => {
+        // Arrange
+        const dependencies = { exec: createStubExec() };
+        const config = "./custom/eslintrc.js";
+
+        // Act
+        await findESLintConfiguration(dependencies, config);
+
+        // Assert
+        expect(dependencies.exec).toHaveBeenLastCalledWith(
+            "eslint --print-config ./custom/eslintrc.js",
+        );
+    });
+
+    it("applies ESLint defaults when none are provided", async () => {
+        // Arrange
+        const dependencies = { exec: createStubExec({ stdout: "{}" }) };
+        const config = "./custom/eslintrc.js";
+
+        // Act
+        const result = await findESLintConfiguration(dependencies, config);
+
+        // Assert
+        expect(result).toEqual({});
+    });
+});
diff --git a/src/input/findLintConfiguration.test.ts b/src/input/findLintConfiguration.test.ts
new file mode 100644
index 0000000..131eeaa
--- /dev/null
+++ b/src/input/findLintConfiguration.test.ts
@@ -0,0 +1,85 @@
+import { createStubExec, createStubThrowingExec } from "../adapters/exec.stubs";
+import { findLintConfiguration } from "./findLintConfiguration";
+
+describe("findLintConfiguration", () => {
+    it("returns stderr as an error when the command fails with a zero exit code", async () => {
+        // Arrange
+        const stderr = "error";
+        const exec = createStubExec({ stderr });
+
+        // Act
+        const result = await findLintConfiguration(exec, "command", "sample.json", {});
+
+        // Assert
+        expect(result).toEqual(new Error(stderr));
+    });
+
+    it("returns stderr as an error when the command fails with a non-zero exit code", async () => {
+        // Arrange
+        const stderr = "error";
+        const exec = createStubThrowingExec({ stderr });
+
+        // Act
+        const result = await findLintConfiguration(exec, "command", "sample.json", {});
+
+        // Assert
+        expect(result).toEqual(new Error(stderr));
+    });
+
+    it("returns a parse error when the command returns invalid JSON", async () => {
+        // Arrange
+        const stdout = "invalid";
+        const exec = createStubExec({ stdout });
+
+        // Act
+        const result = await findLintConfiguration(exec, "command", "sample.json", {});
+
+        // Assert
+        expect(result).toEqual(
+            new Error(
+                "Error parsing configuration: SyntaxError: Unexpected token i in JSON at position 0",
+            ),
+        );
+    });
+
+    it("returns parsed JSON when the command returns valid JSON", async () => {
+        // Arrange
+        const rules = { "rule-a": true };
+        const stdout = JSON.stringify({ rules });
+        const exec = createStubExec({ stdout });
+
+        // Act
+        const result = await findLintConfiguration(exec, "command", "sample.json", {});
+
+        // Assert
+        expect(result).toEqual({
+            rules,
+        });
+    });
+
+    it("fills in configuration defaults the command returns valid but empty JSON", async () => {
+        // Arrange
+        const defaultConfiguration = {
+            default: true,
+        };
+        const originalConfiguration = {
+            original: true,
+        };
+        const stdout = JSON.stringify(originalConfiguration);
+        const exec = createStubExec({ stdout });
+
+        // Act
+        const result = await findLintConfiguration(
+            exec,
+            "command",
+            "sample.json",
+            defaultConfiguration,
+        );
+
+        // Assert
+        expect(result).toEqual({
+            ...defaultConfiguration,
+            ...originalConfiguration,
+        });
+    });
+});
diff --git a/src/input/findOriginalConfigurations.test.ts b/src/input/findOriginalConfigurations.test.ts
new file mode 100644
index 0000000..c18c6a3
--- /dev/null
+++ b/src/input/findOriginalConfigurations.test.ts
@@ -0,0 +1,84 @@
+import { findOriginalConfigurations } from "./findOriginalConfigurations";
+import { ResultStatus } from "../types";
+
+const createRawSettings = () => ({
+    eslintConfig: "",
+    tslintConfig: "",
+    typescriptConfig: "",
+});
+
+describe("findOriginalConfigurations", () => {
+    it("returns an errors when the tslint finder returns an error", async () => {
+        // Arrange
+        const complaint = "Complaint from TSLint";
+        const dependencies = {
+            findESLintConfiguration: async () => ({}),
+            findTSLintConfiguration: async () => new Error(complaint),
+            findTypeScriptConfiguration: async () => ({}),
+        };
+
+        // Act
+        const result = await findOriginalConfigurations(dependencies, createRawSettings());
+
+        // Assert
+        expect(result).toEqual({
+            complaints: [complaint],
+            status: ResultStatus.ConfigurationError,
+        });
+    });
+
+    it("returns only tslint results when the other finders return errors", async () => {
+        // Arrange
+        const complaints = ["Complaint from ESLint", "Complaint from TypeScript"];
+        const dependencies = {
+            findESLintConfiguration: async () => new Error(complaints[0]),
+            findTSLintConfiguration: async () => ({
+                ruleDirectories: [],
+                rules: {},
+            }),
+            findTypeScriptConfiguration: async () => new Error(complaints[1]),
+        };
+
+        // Act
+        const result = await findOriginalConfigurations(dependencies, createRawSettings());
+
+        // Assert
+        expect(result).toEqual({
+            data: {
+                tslint: {
+                    ruleDirectories: [],
+                    rules: {},
+                },
+            },
+            status: ResultStatus.Succeeded,
+        });
+    });
+
+    it("returns successful results when all finders succeed", async () => {
+        // Arrange
+        const dependencies = {
+            findESLintConfiguration: async () => ({}),
+            findTSLintConfiguration: async () => ({
+                ruleDirectories: [],
+                rules: {},
+            }),
+            findTypeScriptConfiguration: async () => ({}),
+        };
+
+        // Act
+        const result = await findOriginalConfigurations(dependencies, createRawSettings());
+
+        // Assert
+        expect(result).toEqual({
+            data: {
+                eslint: {},
+                tslint: {
+                    ruleDirectories: [],
+                    rules: {},
+                },
+                typescript: {},
+            },
+            status: ResultStatus.Succeeded,
+        });
+    });
+});
diff --git a/src/input/findTslintConfiguration.test.ts b/src/input/findTslintConfiguration.test.ts
index 75abbf0..f2a64cd 100644
--- a/src/input/findTslintConfiguration.test.ts
+++ b/src/input/findTslintConfiguration.test.ts
@@ -1,58 +1,39 @@
-import { findTslintConfiguration } from "./findTslintConfiguration";
+import { findTSLintConfiguration } from "./findTSLintConfiguration";
+import { createStubExec } from "../adapters/exec.stubs";
 
-describe("findTslintConfiguration", () => {
-    it("returns stderr as an error when the command fails", async () => {
+describe("findTSLintConfiguration", () => {
+    it("defaults the configuration file when one isn't provided", async () => {
         // Arrange
-        const [stderr, stdout] = ["error", ""];
-        const exec = () => Promise.resolve({ stderr, stdout });
+        const dependencies = { exec: createStubExec() };
 
         // Act
-        const result = await findTslintConfiguration({ exec }, "tslint.json");
+        await findTSLintConfiguration(dependencies, undefined);
 
         // Assert
-        expect(result).toEqual(new Error(stderr));
+        expect(dependencies.exec).toHaveBeenLastCalledWith("tslint --print-config ./tslint.json");
     });
 
-    it("returns a parse error when the command returns invalid JSON", async () => {
+    it("includes a configuration file in the TSLint command when one is provided", async () => {
         // Arrange
-        const [stderr, stdout] = ["", "invalid"];
-        const exec = () => Promise.resolve({ stderr, stdout });
+        const dependencies = { exec: createStubExec() };
+        const config = "./custom/tslint.json";
 
         // Act
-        const result = await findTslintConfiguration({ exec }, "tslint.json");
+        await findTSLintConfiguration(dependencies, config);
 
         // Assert
-        expect(result).toEqual(
-            new Error(
-                "Error parsing TSLint configuration: SyntaxError: Unexpected token i in JSON at position 0",
-            ),
+        expect(dependencies.exec).toHaveBeenLastCalledWith(
+            "tslint --print-config ./custom/tslint.json",
         );
     });
 
-    it("returns parsed JSON when the command returns valid JSON", async () => {
+    it("applies TSLint defaults when none are provided", async () => {
         // Arrange
-        const rules = { "rule-a": true };
-        const [stderr, stdout] = ["", JSON.stringify({ rules })];
-        const exec = () => Promise.resolve({ stderr, stdout });
+        const dependencies = { exec: createStubExec({ stdout: "{}" }) };
+        const config = "./custom/tslint.json";
 
         // Act
-        const result = await findTslintConfiguration({ exec }, "tslint.json");
-
-        // Assert
-        expect(result).toEqual({
-            ruleDirectories: [],
-            rules,
-        });
-    });
-
-    it("fills in configuration defaults the command returns valid but empty JSON", async () => {
-        // Arrange
-        const originalConfiguration = {};
-        const [stderr, stdout] = ["", JSON.stringify(originalConfiguration)];
-        const exec = () => Promise.resolve({ stderr, stdout });
-
-        // Act
-        const result = await findTslintConfiguration({ exec }, "tslint.json");
+        const result = await findTSLintConfiguration(dependencies, config);
 
         // Assert
         expect(result).toEqual({
diff --git a/src/input/findTypeScriptConfiguration.test.ts b/src/input/findTypeScriptConfiguration.test.ts
new file mode 100644
index 0000000..7303f70
--- /dev/null
+++ b/src/input/findTypeScriptConfiguration.test.ts
@@ -0,0 +1,39 @@
+import { findTypeScriptConfiguration } from "./findTypeScriptConfiguration";
+import { createStubExec } from "../adapters/exec.stubs";
+
+describe("findTypeScriptConfiguration", () => {
+    it("defaults the configuration file when one isn't provided", async () => {
+        // Arrange
+        const dependencies = { exec: createStubExec() };
+
+        // Act
+        await findTypeScriptConfiguration(dependencies, undefined);
+
+        // Assert
+        expect(dependencies.exec).toHaveBeenLastCalledWith("cat ./tsconfig.json");
+    });
+
+    it("includes a configuration file in the TypeScript command when one is provided", async () => {
+        // Arrange
+        const dependencies = { exec: createStubExec() };
+        const config = "./custom/tsconfig.json";
+
+        // Act
+        await findTypeScriptConfiguration(dependencies, config);
+
+        // Assert
+        expect(dependencies.exec).toHaveBeenLastCalledWith("cat ./custom/tsconfig.json");
+    });
+
+    it("applies TypeScript defaults when none are provided", async () => {
+        // Arrange
+        const dependencies = { exec: createStubExec({ stdout: "{}" }) };
+        const config = "./tsconfig.json";
+
+        // Act
+        const result = await findTypeScriptConfiguration(dependencies, config);
+
+        // Assert
+        expect(result).toEqual({});
+    });
+});
diff --git a/src/rules/convertRules.test.ts b/src/rules/convertRules.test.ts
index e27bcaa..5b3c351 100644
--- a/src/rules/convertRules.test.ts
+++ b/src/rules/convertRules.test.ts
@@ -14,7 +14,10 @@ describe("convertRules", () => {
         const mergers = new Map();
 
         // Act
-        const { missing } = convertRules([tslintRule], converters, mergers);
+        const { missing } = convertRules(
+            { converters, mergers },
+            { [tslintRule.ruleName]: tslintRule },
+        );
 
         // Assert
         expect(missing).toEqual([]);
@@ -31,7 +34,10 @@ describe("convertRules", () => {
         const mergers = new Map();
 
         // Act
-        const { missing } = convertRules([tslintRule], converters, mergers);
+        const { missing } = convertRules(
+            { converters, mergers },
+            { [tslintRule.ruleName]: tslintRule },
+        );
 
         // Assert
         expect(missing).toEqual([tslintRule]);
@@ -49,7 +55,10 @@ describe("convertRules", () => {
         const mergers = new Map();
 
         // Act
-        const { failed } = convertRules([tslintRule], converters, mergers);
+        const { failed } = convertRules(
+            { converters, mergers },
+            { [tslintRule.ruleName]: tslintRule },
+        );
 
         // Assert
         expect(failed).toEqual([conversionError]);
@@ -73,7 +82,10 @@ describe("convertRules", () => {
         const mergers = new Map();
 
         // Act
-        const { converted } = convertRules([tslintRule], converters, mergers);
+        const { converted } = convertRules(
+            { converters, mergers },
+            { [tslintRule.ruleName]: tslintRule },
+        );
 
         // Assert
         expect(converted).toEqual(
@@ -110,7 +122,10 @@ describe("convertRules", () => {
         const mergers = new Map();
 
         // Act
-        const { failed } = convertRules([tslintRule], converters, mergers);
+        const { failed } = convertRules(
+            { converters, mergers },
+            { [tslintRule.ruleName]: tslintRule },
+        );
 
         // Assert
         expect(failed).toEqual(
@@ -147,7 +162,10 @@ describe("convertRules", () => {
         const mergers = new Map([[conversionResult.rules[0].ruleName, () => mergedArguments]]);
 
         // Act
-        const { converted } = convertRules([tslintRule], converters, mergers);
+        const { converted } = convertRules(
+            { converters, mergers },
+            { [tslintRule.ruleName]: tslintRule },
+        );
 
         // Assert
         expect(converted).toEqual(
@@ -179,7 +197,10 @@ describe("convertRules", () => {
         const mergers = new Map();
 
         // Act
-        const { packages } = convertRules([tslintRule], converters, mergers);
+        const { packages } = convertRules(
+            { converters, mergers },
+            { [tslintRule.ruleName]: tslintRule },
+        );
 
         // Assert
         expect(packages).toEqual(new Set(["extra-package"]));
diff --git a/src/utils.test.ts b/src/utils.test.ts
index f5911a8..11417a9 100644
--- a/src/utils.test.ts
+++ b/src/utils.test.ts
@@ -1,4 +1,4 @@
-import { isDefined } from "./utils";
+import { isDefined, isError } from "./utils";
 
 describe("isDefined", () => {
     it("returns true when the item is defined", () => {
@@ -23,3 +23,27 @@ describe("isDefined", () => {
         expect(defined).toBe(false);
     });
 });
+
+describe("isError", () => {
+    it("returns true when the item is an error", () => {
+        // Arrange
+        const item = new Error();
+
+        // Act
+        const result = isError(item);
+
+        // Assert
+        expect(result).toBe(true);
+    });
+
+    it("returns false when the item is not an error", () => {
+        // Arrange
+        const item = {};
+
+        // Act
+        const result = isError(item);
+
+        // Assert
+        expect(result).toBe(false);
+    });
+});