Repository: JoshuaKGoldberg/tslint-to-eslint-config (ts)

Issue:
Don't include rule flags that are the same as plugins
Following #5: if a rule's options are the same as what it's been configured to be following a plugin included in an output configuration, don't bother explicitly including it in the configuration.
Apply eslintrc before factoring in list of eslint plugins
As a precursor to #5, if a user's previously created ESLint config specifies using an ESLint plugin, we should recognize that.

Pull request description:
Respect ESLint extensions in deduplicating rules and config writing
## PR Checklist

-   [x] Addresses an existing issue: fixes #13; fixes #34
-   [x] That issue was marked as [`status: accepting prs`](https://github.com/JoshuaKGoldberg/tslint-to-eslint-config/labels/status%3A%20accepting%20prs)

## Overview

The original ESLint configuration is now included in the `writeConversionResults` written file with a single `...`.

Additionally skipped outputting rules that match an existing ESLint plugin. If an extended ESLint plugin defines a rule with the same values as the resultant config, the config will skip printing it.
This necessitated expanding "failures" to allow a new `ConfigurationError` type of error for unfound ESLint configurations.

Renames "packages" in the configuration reading/converting logic areas to "plugins", as that's what ESLint calls them.


Patch:
diff --git a/.eslintrc.js b/.eslintrc.js
index 03f9d23..d51ea9b 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -17,18 +17,19 @@ module.exports = {
         "@typescript-eslint/no-use-before-define": 0,
         "@typescript-eslint/prefer-interface": 0,
         "default-case": 0,
+        "guard-for-in": 0,
         "import/no-extraneous-dependencies": [
             "error",
             { devDependencies: ["**/*.stubs.ts", "**/*.test.*"] },
         ],
         "import/first": 0,
-        "import/newline-after-import": 0,
         "import/no-unresolved": 0,
         "import/prefer-default-export": 0,
         "no-console": 0,
         "no-continue": 0,
         "no-empty-function": 0,
         "no-restricted-syntax": 0,
+        "no-param-reassign": 0,
         "no-shadow": 0,
         "no-undef": 0,
         "no-useless-constructor": 0,
diff --git a/README.md b/README.md
index 10a3007..e41a563 100644
--- a/README.md
+++ b/README.md
@@ -66,7 +66,7 @@ npx tslint-to-eslint-config --eslint ./path/to/eslintrc.js
 _Default: `--config`'s value_
 
 Path to an ESLint configuration file to read settings from.
-This isn't yet used for anything, but will eventually inform settings for the generated ESLint configuration file.
+The generated ESLint configuration file will include any settings `import`ed from this file.
 
 #### `package`
 
diff --git a/src/adapters/importer.ts b/src/adapters/importer.ts
new file mode 100644
index 0000000..7aa40a2
--- /dev/null
+++ b/src/adapters/importer.ts
@@ -0,0 +1,9 @@
+export type Importer = (moduleName: string) => unknown | Error;
+
+export const nativeImporter = async (moduleName: string) => {
+    try {
+        return await import(moduleName);
+    } catch (error) {
+        return error;
+    }
+};
diff --git a/src/cli/main.ts b/src/cli/main.ts
index 15ae45b..cbfd1f1 100644
--- a/src/cli/main.ts
+++ b/src/cli/main.ts
@@ -1,11 +1,20 @@
 import { EOL } from "os";
 
-import { bind } from "../binding";
-import { runCli, RunCliDependencies } from "./runCli";
+import { nativeImporter } from "../adapters/importer";
 import { processLogger } from "../adapters/processLogger";
 import { childProcessExec } from "../adapters/childProcessExec";
 import { fsFileSystem } from "../adapters/fsFileSystem";
+import { bind } from "../binding";
 import { ConvertConfigDependencies, convertConfig } from "../conversion/convertConfig";
+import { removeExtendsDuplicatedRules } from "../creation/simplification/removeExtendsDuplicatedRules";
+import {
+    RetrieveExtendsValuesDependencies,
+    retrieveExtendsValues,
+} from "../creation/simplification/retrieveExtendsValues";
+import {
+    simplifyPackageRules,
+    SimplifyPackageRulesDependencies,
+} from "../creation/simplification/simplifyPackageRules";
 import {
     writeConversionResults,
     WriteConversionResultsDependencies,
@@ -14,6 +23,7 @@ import {
     findOriginalConfigurations,
     FindOriginalConfigurationsDependencies,
 } from "../input/findOriginalConfigurations";
+import { findPackagesConfiguration } from "../input/findPackagesConfiguration";
 import { findESLintConfiguration } from "../input/findESLintConfiguration";
 import { findTSLintConfiguration } from "../input/findTSLintConfiguration";
 import { findTypeScriptConfiguration } from "../input/findTypeScriptConfiguration";
@@ -24,7 +34,7 @@ import {
 import { converters } from "../rules/converters";
 import { convertRules } from "../rules/convertRules";
 import { mergers } from "../rules/mergers";
-import { findPackagesConfiguration } from "../input/findPackagesConfiguration";
+import { runCli, RunCliDependencies } from "./runCli";
 
 const convertRulesDependencies = {
     converters,
@@ -46,6 +56,15 @@ const reportConversionResultsDependencies: ReportConversionResultsDependencies =
     logger: processLogger,
 };
 
+const retrieveExtendsValuesDependencies: RetrieveExtendsValuesDependencies = {
+    importer: nativeImporter,
+};
+
+const simplifyPackageRulesDependencies: SimplifyPackageRulesDependencies = {
+    removeExtendsDuplicatedRules,
+    retrieveExtendsValues: bind(retrieveExtendsValues, retrieveExtendsValuesDependencies),
+};
+
 const writeConversionResultsDependencies: WriteConversionResultsDependencies = {
     fileSystem: fsFileSystem,
 };
@@ -57,6 +76,7 @@ const convertConfigDependencies: ConvertConfigDependencies = {
         findOriginalConfigurationsDependencies,
     ),
     reportConversionResults: bind(reportConversionResults, reportConversionResultsDependencies),
+    simplifyPackageRules: bind(simplifyPackageRules, simplifyPackageRulesDependencies),
     writeConversionResults: bind(writeConversionResults, writeConversionResultsDependencies),
 };
 
diff --git a/src/conversion/conversionResults.stubs.ts b/src/conversion/conversionResults.stubs.ts
index 0acb2d7..57249ad 100644
--- a/src/conversion/conversionResults.stubs.ts
+++ b/src/conversion/conversionResults.stubs.ts
@@ -1,11 +1,11 @@
 import { RuleConversionResults } from "../rules/convertRules";
 
 export const createEmptyConversionResults = (
-    overrides: Partial<RuleConversionResults>,
+    overrides: Partial<RuleConversionResults> = {},
 ): RuleConversionResults => ({
     converted: new Map(),
     failed: [],
     missing: [],
-    packages: new Set(),
+    plugins: new Set(),
     ...overrides,
 });
diff --git a/src/conversion/convertConfig.ts b/src/conversion/convertConfig.ts
index 6701fd2..81e8fbb 100644
--- a/src/conversion/convertConfig.ts
+++ b/src/conversion/convertConfig.ts
@@ -1,4 +1,5 @@
 import { SansDependencies } from "../binding";
+import { simplifyPackageRules } from "../creation/simplification/simplifyPackageRules";
 import { writeConversionResults } from "../creation/writeConversionResults";
 import { findOriginalConfigurations } from "../input/findOriginalConfigurations";
 import { reportConversionResults } from "../reporting/reportConversionResults";
@@ -9,6 +10,7 @@ export type ConvertConfigDependencies = {
     convertRules: SansDependencies<typeof convertRules>;
     findOriginalConfigurations: SansDependencies<typeof findOriginalConfigurations>;
     reportConversionResults: SansDependencies<typeof reportConversionResults>;
+    simplifyPackageRules: SansDependencies<typeof simplifyPackageRules>;
     writeConversionResults: SansDependencies<typeof writeConversionResults>;
 };
 
@@ -24,13 +26,20 @@ export const convertConfig = async (
     const ruleConversionResults = dependencies.convertRules(
         originalConfigurations.data.tslint.rules,
     );
+    const mergedConfiguration = {
+        ...ruleConversionResults,
+        ...(await dependencies.simplifyPackageRules(
+            originalConfigurations.data.eslint,
+            ruleConversionResults,
+        )),
+    };
 
     await dependencies.writeConversionResults(
         settings.config,
-        ruleConversionResults,
+        mergedConfiguration,
         originalConfigurations.data,
     );
-    dependencies.reportConversionResults(ruleConversionResults);
+    dependencies.reportConversionResults(mergedConfiguration);
 
     return {
         status: ResultStatus.Succeeded,
diff --git a/src/creation/simplification/removeExtendsDuplicatedRules.ts b/src/creation/simplification/removeExtendsDuplicatedRules.ts
new file mode 100644
index 0000000..2e17446
--- /dev/null
+++ b/src/creation/simplification/removeExtendsDuplicatedRules.ts
@@ -0,0 +1,87 @@
+import { isDeepStrictEqual } from "util";
+
+import {
+    ESLintConfiguration,
+    ESLintConfigurationRuleValue,
+} from "../../input/findESLintConfiguration";
+import { convertRawESLintRuleSeverity } from "../../rules/convertRuleSeverity";
+import { ESLintRuleOptions } from "../../rules/types";
+
+export const removeExtendsDuplicatedRules = (
+    allRules: Map<string, ESLintRuleOptions>,
+    extensions: Partial<ESLintConfiguration>[],
+): Map<string, ESLintRuleOptions> => {
+    const differentRules = new Map<string, ESLintRuleOptions>();
+    const mergedExtensionRules = mergeExtensions(extensions);
+
+    for (const [ruleName, value] of allRules) {
+        if (!ruleValuesAreTheSame(value, mergedExtensionRules.get(ruleName))) {
+            differentRules.set(ruleName, value);
+        }
+    }
+
+    return differentRules;
+};
+
+const mergeExtensions = (extensions: Partial<ESLintConfiguration>[]) => {
+    const mergedRules = new Map<string, ESLintRuleOptions>();
+
+    for (const extension of extensions) {
+        if (extension.rules === undefined) {
+            continue;
+        }
+
+        for (const ruleName in extension.rules) {
+            mergedRules.set(ruleName, formatRuleArguments(ruleName, extension.rules[ruleName]));
+        }
+    }
+
+    return mergedRules;
+};
+
+const formatRuleArguments = (
+    ruleName: string,
+    originalValue: ESLintConfigurationRuleValue,
+): ESLintRuleOptions => {
+    if (typeof originalValue === "number") {
+        return {
+            ruleArguments: [],
+            ruleName,
+            ruleSeverity: convertRawESLintRuleSeverity(originalValue),
+        };
+    }
+
+    if (typeof originalValue === "string") {
+        return {
+            ruleArguments: [],
+            ruleName,
+            ruleSeverity: originalValue,
+        };
+    }
+
+    return {
+        ruleArguments: originalValue.slice(1),
+        ruleName,
+        ruleSeverity: convertRawESLintRuleSeverity(originalValue[0]),
+    };
+};
+
+const ruleValuesAreTheSame = (
+    configurationValue: ESLintRuleOptions,
+    extensionValue: ESLintRuleOptions | undefined,
+) => {
+    return (
+        extensionValue !== undefined &&
+        configurationValue.ruleSeverity === extensionValue.ruleSeverity &&
+        isDeepStrictEqual(
+            {
+                ruleArguments: [],
+                ...configurationValue.ruleArguments,
+            },
+            {
+                ruleArguments: [],
+                ...extensionValue.ruleArguments,
+            },
+        )
+    );
+};
diff --git a/src/creation/simplification/resolveExtensionNames.ts b/src/creation/simplification/resolveExtensionNames.ts
new file mode 100644
index 0000000..7b1b1b6
--- /dev/null
+++ b/src/creation/simplification/resolveExtensionNames.ts
@@ -0,0 +1,13 @@
+export const resolveExtensionNames = (rawExtensionNames: string | string[]) => {
+    if (typeof rawExtensionNames === "string") {
+        rawExtensionNames = [rawExtensionNames];
+    }
+
+    return rawExtensionNames.map(rawExtensionName =>
+        rawExtensionName.startsWith(".") ||
+        rawExtensionName.startsWith("eslint-plugin-") ||
+        rawExtensionName.startsWith("eslint:")
+            ? rawExtensionName
+            : `eslint-plugin-${rawExtensionName}`,
+    );
+};
diff --git a/src/creation/simplification/retrieveExtendsValues.ts b/src/creation/simplification/retrieveExtendsValues.ts
new file mode 100644
index 0000000..aedb533
--- /dev/null
+++ b/src/creation/simplification/retrieveExtendsValues.ts
@@ -0,0 +1,61 @@
+import { Importer } from "../../adapters/importer";
+import { ConfigurationError } from "../../errors/configurationError";
+import { ESLintConfiguration } from "../../input/findESLintConfiguration";
+import { resolveExtensionNames } from "./resolveExtensionNames";
+
+export type RetrieveExtendsValuesDependencies = {
+    importer: Importer;
+};
+
+export type RetrievedExtensionValues = {
+    configurationErrors: ConfigurationError[];
+    importedExtensions: Partial<ESLintConfiguration>[];
+};
+
+const builtInExtensionGetters = new Map<
+    string,
+    (importer: Importer) => Promise<ESLintConfiguration>
+>([
+    [
+        "eslint:all",
+        async importer => (await importer("eslint/conf/eslint-all")) as ESLintConfiguration,
+    ],
+    [
+        "eslint:recommended",
+        async importer => (await importer("eslint/conf/eslint-recommended")) as ESLintConfiguration,
+    ],
+]);
+
+export const retrieveExtendsValues = async (
+    dependencies: RetrieveExtendsValuesDependencies,
+    rawExtensionNames: string | string[],
+): Promise<RetrievedExtensionValues> => {
+    const importedExtensions: Partial<ESLintConfiguration>[] = [];
+    const configurationErrors: ConfigurationError[] = [];
+    const extensionNames = resolveExtensionNames(rawExtensionNames);
+
+    await Promise.all(
+        extensionNames.map(async extensionName => {
+            const getBuiltInExtension = builtInExtensionGetters.get(extensionName);
+            if (getBuiltInExtension !== undefined) {
+                importedExtensions.push(await getBuiltInExtension(dependencies.importer));
+                return;
+            }
+
+            const imported = await dependencies.importer(extensionName);
+
+            if (imported instanceof Error) {
+                configurationErrors.push(
+                    new ConfigurationError(
+                        imported,
+                        `Could not resolve ESLint extension '${extensionName}'.`,
+                    ),
+                );
+            } else {
+                importedExtensions.push(imported as Partial<ESLintConfiguration>);
+            }
+        }),
+    );
+
+    return { configurationErrors, importedExtensions };
+};
diff --git a/src/creation/simplification/simplifyPackageRules.ts b/src/creation/simplification/simplifyPackageRules.ts
new file mode 100644
index 0000000..550fff9
--- /dev/null
+++ b/src/creation/simplification/simplifyPackageRules.ts
@@ -0,0 +1,36 @@
+import { SansDependencies } from "../../binding";
+import { RuleConversionResults } from "../../rules/convertRules";
+import { removeExtendsDuplicatedRules } from "./removeExtendsDuplicatedRules";
+import { retrieveExtendsValues } from "./retrieveExtendsValues";
+import { ESLintConfiguration } from "../../input/findESLintConfiguration";
+
+export type SimplifyPackageRulesDependencies = {
+    removeExtendsDuplicatedRules: typeof removeExtendsDuplicatedRules;
+    retrieveExtendsValues: SansDependencies<typeof retrieveExtendsValues>;
+};
+
+export type SimplifiedRuleConversionResults = Pick<RuleConversionResults, "converted" | "failed">;
+
+export const simplifyPackageRules = async (
+    dependencies: SimplifyPackageRulesDependencies,
+    eslint: Partial<ESLintConfiguration> | undefined,
+    ruleConversionResults: SimplifiedRuleConversionResults,
+): Promise<SimplifiedRuleConversionResults> => {
+    if (eslint === undefined || eslint.extends === undefined || eslint.extends.length === 0) {
+        return ruleConversionResults;
+    }
+
+    const { configurationErrors, importedExtensions } = await dependencies.retrieveExtendsValues(
+        eslint.extends,
+    );
+
+    const converted = dependencies.removeExtendsDuplicatedRules(
+        ruleConversionResults.converted,
+        importedExtensions,
+    );
+
+    return {
+        converted,
+        failed: [...ruleConversionResults.failed, ...configurationErrors],
+    };
+};
diff --git a/src/creation/writeConversionResults.ts b/src/creation/writeConversionResults.ts
index 07f6802..4aeb043 100644
--- a/src/creation/writeConversionResults.ts
+++ b/src/creation/writeConversionResults.ts
@@ -20,7 +20,9 @@ export const writeConversionResults = async (
     if (ruleConversionResults.missing.length !== 0) {
         plugins.push("@typescript-eslint/tslint");
     }
+
     const output = {
+        ...(originalConfigurations.eslint && originalConfigurations.eslint),
         env: createEnv(originalConfigurations),
         parser: "@typescript-eslint/parser",
         parserOptions: {
diff --git a/src/errors/configurationError.ts b/src/errors/configurationError.ts
new file mode 100644
index 0000000..5b76627
--- /dev/null
+++ b/src/errors/configurationError.ts
@@ -0,0 +1,3 @@
+export class ConfigurationError {
+    public constructor(public readonly error: Error, public readonly complaint: string) {}
+}
diff --git a/src/rules/conversionError.ts b/src/errors/conversionError.ts
similarity index 75%
rename from src/rules/conversionError.ts
rename to src/errors/conversionError.ts
index 367db4a..9a06ee3 100644
--- a/src/rules/conversionError.ts
+++ b/src/errors/conversionError.ts
@@ -1,4 +1,4 @@
-import { TSLintRuleOptions } from "./types";
+import { TSLintRuleOptions } from "../rules/types";
 
 export class ConversionError {
     public constructor(
diff --git a/src/input/findESLintConfiguration.ts b/src/input/findESLintConfiguration.ts
index 4171dda..7a4817c 100644
--- a/src/input/findESLintConfiguration.ts
+++ b/src/input/findESLintConfiguration.ts
@@ -1,3 +1,4 @@
+import { ESLintRuleSeverity } from "../rules/types";
 import { TSLintToESLintSettings } from "../types";
 import { findConfiguration, FindConfigurationDependencies } from "./findConfiguration";
 
@@ -5,13 +6,24 @@ export type ESLintConfiguration = {
     env: {
         [i: string]: boolean;
     };
-    rules: {
-        [i: string]: number | [string, any];
-    };
+    extends: string | string[];
+    rules: ESLintConfigurationRules;
+};
+
+export type ESLintConfigurationRules = {
+    [i: string]: ESLintConfigurationRuleValue;
 };
 
+export type ESLintConfigurationRuleValue =
+    | 0
+    | 1
+    | 2
+    | ESLintRuleSeverity
+    | [ESLintRuleSeverity, any];
+
 const defaultESLintConfiguration = {
     env: {},
+    extends: [],
     rules: {},
 };
 
diff --git a/src/reporting/reportConversionResults.ts b/src/reporting/reportConversionResults.ts
index dd32b42..4ff8dce 100644
--- a/src/reporting/reportConversionResults.ts
+++ b/src/reporting/reportConversionResults.ts
@@ -2,7 +2,8 @@ import chalk from "chalk";
 import { EOL } from "os";
 
 import { Logger } from "../adapters/logger";
-import { ConversionError } from "../rules/conversionError";
+import { ConfigurationError } from "../errors/configurationError";
+import { ConversionError } from "../errors/conversionError";
 import { RuleConversionResults } from "../rules/convertRules";
 import { TSLintRuleOptions, ESLintRuleOptions } from "../rules/types";
 
@@ -26,8 +27,8 @@ export const reportConversionResults = (
         logMissingRules(ruleConversionResults.missing, dependencies.logger);
     }
 
-    if (ruleConversionResults.packages.size !== 0) {
-        logMissingPackages(ruleConversionResults.packages, dependencies.logger);
+    if (ruleConversionResults.plugins.size !== 0) {
+        logMissingPlugins(ruleConversionResults.plugins, dependencies.logger);
     }
 };
 
@@ -41,19 +42,18 @@ const logSuccessfulConversions = (converted: Map<string, ESLintRuleOptions>, log
     logger.stdout.write(chalk.greenBright(` ✨${EOL}`));
 };
 
-const logFailedConversions = (failed: ConversionError[], logger: Logger) => {
+const logFailedConversions = (failed: (ConfigurationError | ConversionError)[], logger: Logger) => {
     logger.stderr.write(`${chalk.redBright(`💀 ${failed.length}`)}`);
-    logger.stderr.write(
-        chalk.red(` rule${failed.length === 1 ? " threw an error" : "s threw errors"}`),
-    );
-    logger.stderr.write(chalk.red("; using eslint-plugin-tslint instead."));
+    logger.stderr.write(chalk.red(` error${failed.length === 1 ? "" : "s"}`));
+    logger.stderr.write(chalk.red(" thrown."));
     logger.stderr.write(chalk.redBright(` 💀${EOL}`));
 
     logger.info.write(
         failed
-            .map(
-                failed =>
-                    `${failed.tslintRule.ruleName} threw an error during conversion: ${failed.error.stack}.${EOL}`,
+            .map(failed =>
+                failed instanceof ConfigurationError
+                    ? `${failed.complaint}: ${failed.error.stack}${EOL}`
+                    : `${failed.tslintRule.ruleName} threw an error during conversion: ${failed.error.stack}${EOL}`,
             )
             .join(""),
     );
@@ -80,16 +80,16 @@ const logMissingRules = (missing: TSLintRuleOptions[], logger: Logger) => {
     );
 };
 
-const logMissingPackages = (packages: Set<string>, logger: Logger) => {
-    logger.stdout.write(chalk.cyanBright(`⚡ ${packages.size}`));
+const logMissingPlugins = (plugins: Set<string>, logger: Logger) => {
+    logger.stdout.write(chalk.cyanBright(`⚡ ${plugins.size}`));
     logger.stdout.write(chalk.cyan(" package"));
-    logger.stdout.write(chalk.cyan(packages.size === 1 ? " is" : "s are"));
+    logger.stdout.write(chalk.cyan(plugins.size === 1 ? " is" : "s are"));
     logger.stdout.write(chalk.cyan(` required for new ESLint rules.`));
     logger.stdout.write(chalk.cyanBright(` ⚡${EOL}`));
 
     logger.stdout.write(
-        Array.from(packages)
-            .map(packageName => `\t${chalk.cyanBright(packageName)}${EOL}`)
+        Array.from(plugins)
+            .map(pluginName => `\t${chalk.cyanBright(pluginName)}${EOL}`)
             .join(""),
     );
 };
diff --git a/src/rules/convertRule.ts b/src/rules/convertRule.ts
index a9a4622..80a6ded 100644
--- a/src/rules/convertRule.ts
+++ b/src/rules/convertRule.ts
@@ -1,6 +1,6 @@
-import { TSLintRuleOptions } from "./types";
-import { ConversionError } from "./conversionError";
+import { ConversionError } from "../errors/conversionError";
 import { RuleConverter } from "./converter";
+import { TSLintRuleOptions } from "./types";
 
 export const convertRule = (
     tslintRule: TSLintRuleOptions,
diff --git a/src/rules/convertRuleSeverity.ts b/src/rules/convertRuleSeverity.ts
index a33b957..861a1ea 100644
--- a/src/rules/convertRuleSeverity.ts
+++ b/src/rules/convertRuleSeverity.ts
@@ -3,6 +3,26 @@ import { ESLintRuleSeverity, TSLintRuleSeverity } from "./types";
 /**
  * Converts a TSLint rule severity string to the ESLint equivalent.
  */
-export const convertRuleSeverity = (tslintSeverity: TSLintRuleSeverity): ESLintRuleSeverity => {
+export const convertTSLintRuleSeverity = (
+    tslintSeverity: TSLintRuleSeverity,
+): ESLintRuleSeverity => {
     return tslintSeverity === "warning" ? "warn" : tslintSeverity;
 };
+
+export const convertRawESLintRuleSeverity = (
+    rawSeverity: 0 | 1 | 2 | ESLintRuleSeverity,
+): ESLintRuleSeverity => {
+    switch (rawSeverity) {
+        case 0:
+            return "off";
+
+        case 1:
+            return "warn";
+
+        case 2:
+            return "error";
+
+        default:
+            return rawSeverity;
+    }
+};
diff --git a/src/rules/convertRules.ts b/src/rules/convertRules.ts
index d5bf176..31fef3f 100644
--- a/src/rules/convertRules.ts
+++ b/src/rules/convertRules.ts
@@ -1,8 +1,9 @@
+import { ConfigurationError } from "../errors/configurationError";
+import { ConversionError } from "../errors/conversionError";
 import { TSLintConfigurationRules } from "../input/findTSLintConfiguration";
-import { ConversionError } from "./conversionError";
 import { RuleConverter } from "./converter";
 import { convertRule } from "./convertRule";
-import { convertRuleSeverity } from "./convertRuleSeverity";
+import { convertTSLintRuleSeverity } from "./convertRuleSeverity";
 import { formatRawTslintRule } from "./formatRawTslintRule";
 import { RuleMerger } from "./merger";
 import { TSLintRuleOptions, ESLintRuleOptions } from "./types";
@@ -14,9 +15,9 @@ export type ConvertRulesDependencies = {
 
 export type RuleConversionResults = {
     converted: Map<string, ESLintRuleOptions>;
-    failed: ConversionError[];
+    failed: (ConfigurationError | ConversionError)[];
     missing: TSLintRuleOptions[];
-    packages: Set<string>;
+    plugins: Set<string>;
 };
 
 export const convertRules = (
@@ -26,7 +27,7 @@ export const convertRules = (
     const converted = new Map<string, ESLintRuleOptions>();
     const failed: ConversionError[] = [];
     const missing: TSLintRuleOptions[] = [];
-    const packages = new Set<string>();
+    const plugins = new Set<string>();
 
     for (const [ruleName, value] of Object.entries(rawTslintRules)) {
         const tslintRule = formatRawTslintRule(ruleName, value);
@@ -49,7 +50,7 @@ export const convertRules = (
             const existingConversion = converted.get(changes.ruleName);
             const newConversion = {
                 ...changes,
-                ruleSeverity: convertRuleSeverity(tslintRule.ruleSeverity),
+                ruleSeverity: convertTSLintRuleSeverity(tslintRule.ruleSeverity),
             };
 
             if (existingConversion === undefined) {
@@ -78,12 +79,12 @@ export const convertRules = (
             }
         }
 
-        if (conversion.packages !== undefined) {
-            for (const newPackage of conversion.packages) {
-                packages.add(newPackage);
+        if (conversion.plugins !== undefined) {
+            for (const newPlugin of conversion.plugins) {
+                plugins.add(newPlugin);
             }
         }
     }
 
-    return { converted, failed, missing, packages };
+    return { converted, failed, missing, plugins };
 };
diff --git a/src/rules/converter.ts b/src/rules/converter.ts
index 1a275b7..77d63fb 100644
--- a/src/rules/converter.ts
+++ b/src/rules/converter.ts
@@ -1,4 +1,4 @@
-import { ConversionError } from "./conversionError";
+import { ConversionError } from "../errors/conversionError";
 import { TSLintRuleOptions } from "./types";
 
 /**
@@ -23,9 +23,9 @@ export type ConversionResult = {
     notices?: string[];
 
     /**
-     * Any packages that should now be installed if not already.
+     * Any plugins that should now be installed if not already.
      */
-    packages?: string[];
+    plugins?: string[];
 
     /**
      * At least one equivalent ESLint rule and options.


Test patch:
diff --git a/src/conversion/convertConfig.test.ts b/src/conversion/convertConfig.test.ts
index fb21682..ddb9735 100644
--- a/src/conversion/convertConfig.test.ts
+++ b/src/conversion/convertConfig.test.ts
@@ -4,9 +4,10 @@ import { OriginalConfigurations } from "../input/findOriginalConfigurations";
 
 const createStubDependencies = (
     overrides: Pick<ConvertConfigDependencies, "findOriginalConfigurations">,
-) => ({
+): ConvertConfigDependencies => ({
     convertRules: jest.fn(),
     reportConversionResults: jest.fn(),
+    simplifyPackageRules: async (_configurations, data) => data,
     writeConversionResults: jest.fn().mockReturnValue(Promise.resolve()),
     ...overrides,
 });
diff --git a/src/creation/simplification/removeExtendsDuplicatedRules.test.ts b/src/creation/simplification/removeExtendsDuplicatedRules.test.ts
new file mode 100644
index 0000000..5c64be1
--- /dev/null
+++ b/src/creation/simplification/removeExtendsDuplicatedRules.test.ts
@@ -0,0 +1,165 @@
+import {
+    ESLintConfiguration,
+    ESLintConfigurationRuleValue,
+} from "../../input/findESLintConfiguration";
+import { ESLintRuleOptions } from "../../rules/types";
+import { removeExtendsDuplicatedRules } from "./removeExtendsDuplicatedRules";
+
+const prepareTestRule = (
+    ruleOptions: Partial<ESLintRuleOptions>,
+    extensionConfiguration: ESLintConfigurationRuleValue = 2,
+) => {
+    const ruleName = "rule-a";
+    const allRules = new Map<string, ESLintRuleOptions>([
+        [
+            ruleName,
+            {
+                ruleArguments: [],
+                ruleName,
+                ruleSeverity: "off",
+                ...ruleOptions,
+            },
+        ],
+    ]);
+    const extensions: Partial<ESLintConfiguration>[] = [
+        {
+            rules: {
+                [ruleName]: extensionConfiguration,
+            },
+        },
+    ];
+
+    return { ruleName, allRules, extensions };
+};
+
+describe("removeExtendsDuplicatedRules", () => {
+    it("keeps a rule when there are no rules in the extension", () => {
+        // Arrange
+        const { allRules } = prepareTestRule(
+            {
+                ruleName: "mismatched",
+            },
+            2,
+        );
+
+        // Act
+        const differentRules = removeExtendsDuplicatedRules(allRules, [{}]);
+
+        // Assert
+        expect(differentRules.size).toBe(1);
+    });
+
+    it("keeps a rule when it doesn't match any existing rule", () => {
+        // Arrange
+        const { allRules, extensions } = prepareTestRule(
+            {
+                ruleName: "mismatched",
+            },
+            2,
+        );
+
+        // Act
+        const differentRules = removeExtendsDuplicatedRules(allRules, extensions);
+
+        // Assert
+        expect(differentRules.size).toBe(1);
+    });
+
+    it("removes a rule when it matches an existing rule as numbers", () => {
+        // Arrange
+        const { allRules, extensions } = prepareTestRule(
+            {
+                ruleSeverity: "warn",
+            },
+            1,
+        );
+
+        // Act
+        const differentRules = removeExtendsDuplicatedRules(allRules, extensions);
+
+        // Assert
+        expect(differentRules.size).toBe(0);
+    });
+
+    it("keeps a rule when it conflicts with an existing rule as numbers", () => {
+        // Arrange
+        const { allRules, extensions } = prepareTestRule(
+            {
+                ruleSeverity: "warn",
+            },
+            2,
+        );
+
+        // Act
+        const differentRules = removeExtendsDuplicatedRules(allRules, extensions);
+
+        // Assert
+        expect(differentRules.size).toBe(1);
+    });
+
+    it("removes a rule when it matches an existing rule as strings", () => {
+        // Arrange
+        const { allRules, extensions } = prepareTestRule(
+            {
+                ruleSeverity: "warn",
+            },
+            "warn",
+        );
+
+        // Act
+        const differentRules = removeExtendsDuplicatedRules(allRules, extensions);
+
+        // Assert
+        expect(differentRules.size).toBe(0);
+    });
+
+    it("keeps a rule when it conflicts with an existing rule as strings", () => {
+        // Arrange
+        const { allRules, extensions } = prepareTestRule(
+            {
+                ruleSeverity: "warn",
+            },
+            "error",
+        );
+
+        // Act
+        const differentRules = removeExtendsDuplicatedRules(allRules, extensions);
+
+        // Assert
+        expect(differentRules.size).toBe(1);
+    });
+
+    it("removes a rule when it matches an existing rule as objects", () => {
+        // Arrange
+        const { allRules, extensions } = prepareTestRule(
+            {
+                ruleArguments: ["some-argument"],
+                ruleSeverity: "warn",
+            },
+            ["warn", "some-argument"],
+        );
+
+        // Act
+        const differentRules = removeExtendsDuplicatedRules(allRules, extensions);
+
+        // Assert
+        expect(differentRules.size).toBe(0);
+    });
+
+    it("keeps a rule when it conflicts with an existing rule as objects", () => {
+        // Arrange
+        const { allRules, extensions } = prepareTestRule(
+            {
+                ruleArguments: ["some-argument-one"],
+                ruleSeverity: "warn",
+            },
+            ["warn", "some-argument-modified"],
+        );
+
+        // Act
+        const differentRules = removeExtendsDuplicatedRules(allRules, extensions);
+
+        // Assert
+        expect(differentRules.size).toBe(1);
+    });
+});
diff --git a/src/creation/simplification/resolveExtensionNames.test.ts b/src/creation/simplification/resolveExtensionNames.test.ts
new file mode 100644
index 0000000..5fa8322
--- /dev/null
+++ b/src/creation/simplification/resolveExtensionNames.test.ts
@@ -0,0 +1,68 @@
+import { resolveExtensionNames } from "./resolveExtensionNames";
+
+describe("resolveExtensionNames", () => {
+    it("returns a single plugin name as an array when given a single string", () => {
+        // Arrange
+        const rawExtensionName = "eslint-plugin-linting";
+
+        // Act
+        const extensionNames = resolveExtensionNames(rawExtensionName);
+
+        // Assert
+        expect(extensionNames).toEqual([rawExtensionName]);
+    });
+
+    it("returns multiple plugin names as an array when given an array", () => {
+        // Arrange
+        const rawExtensionNames = ["eslint-plugin-one", "eslint-plugin-two"];
+
+        // Act
+        const extensionNames = resolveExtensionNames(rawExtensionNames);
+
+        // Assert
+        expect(extensionNames).toEqual(rawExtensionNames);
+    });
+
+    it("prepends eslint-plugin- when a plugin doesn't start with it", () => {
+        // Arrange
+        const rawExtensionName = "custom";
+
+        // Act
+        const extensionNames = resolveExtensionNames(rawExtensionName);
+
+        // Assert
+        expect(extensionNames).toEqual(["eslint-plugin-custom"]);
+    });
+
+    it("doesn't prepend eslint-plugin- when a plugin starts with .", () => {
+        // Arrange
+        const rawExtensionName = "../my-config.js";
+
+        // Act
+        const extensionNames = resolveExtensionNames(rawExtensionName);
+
+        // Assert
+        expect(extensionNames).toEqual(["../my-config.js"]);
+    });
+
+    it("doesn't prepend eslint-plugin- when a plugin starts with t-plugin-", () => {
+        // Arrange
+        const rawExtensionName = "eslint-plugin-value";
+
+        // Act
+        const extensionNames = resolveExtensionNames(rawExtensionName);
+
+        // Assert
+        expect(extensionNames).toEqual(["eslint-plugin-value"]);
+    });
+    it("doesn't prepend eslint-plugin- when a plugin starts with eslint:", () => {
+        // Arrange
+        const rawExtensionName = "eslint:recommended";
+
+        // Act
+        const extensionNames = resolveExtensionNames(rawExtensionName);
+
+        // Assert
+        expect(extensionNames).toEqual(["eslint:recommended"]);
+    });
+});
diff --git a/src/creation/simplification/retrieveExtendsValues.test.ts b/src/creation/simplification/retrieveExtendsValues.test.ts
new file mode 100644
index 0000000..aec6c80
--- /dev/null
+++ b/src/creation/simplification/retrieveExtendsValues.test.ts
@@ -0,0 +1,87 @@
+import { retrieveExtendsValues } from "./retrieveExtendsValues";
+
+describe("retrieveExtendsValues", () => {
+    it("retrieves eslint-all when an extension is named eslint:all", async () => {
+        // Arrange
+        const eslintAll = { rules: {} };
+        const importer = async (extensionName: string) =>
+            extensionName === "eslint/conf/eslint-all"
+                ? eslintAll
+                : new Error(`Unknown extension name: '${extensionName}`);
+
+        // Act
+        const { importedExtensions } = await retrieveExtendsValues({ importer }, "eslint:all");
+
+        // Assert
+        expect(importedExtensions).toEqual([eslintAll]);
+    });
+
+    it("retrieves eslint-recommended when an extension is named eslint:recommended", async () => {
+        // Arrange
+        const eslintRecommended = { rules: {} };
+        const importer = async (extensionName: string) =>
+            extensionName === "eslint/conf/eslint-recommended"
+                ? eslintRecommended
+                : new Error(`Unknown extension name: '${extensionName}`);
+
+        // Act
+        const { importedExtensions } = await retrieveExtendsValues(
+            { importer },
+            "eslint:recommended",
+        );
+
+        // Assert
+        expect(importedExtensions).toEqual([eslintRecommended]);
+    });
+
+    it("reports a failure when an extension fails to import", async () => {
+        // Arrange
+        const error = new Error("Oh no");
+        const importer = async () => error;
+
+        // Act
+        const { configurationErrors } = await retrieveExtendsValues({ importer }, "extension-name");
+
+        // Assert
+        expect(configurationErrors).toEqual([expect.objectContaining({ error })]);
+    });
+
+    it("retrieves an extension when an import succeeds", async () => {
+        // Arrange
+        const extension = { rules: {} };
+        const importer = async () => extension;
+
+        // Act
+        const { importedExtensions } = await retrieveExtendsValues({ importer }, "extension-name");
+
+        // Assert
+        expect(importedExtensions).toEqual([extension]);
+    });
+
+    it("retrieves multiple extensions when multiple are provided", async () => {
+        // Arrange
+        const extensions = {
+            "eslint-plugin-one": {
+                rules: {
+                    "rule-one": {},
+                },
+            },
+            "eslint-plugin-two": {
+                rules: {
+                    "rule-two": {},
+                },
+            },
+        } as const;
+        const importer = async (extensionName: string) =>
+            extensions[extensionName as keyof typeof extensions];
+
+        // Act
+        const { importedExtensions } = await retrieveExtendsValues(
+            { importer },
+            Object.keys(extensions),
+        );
+
+        // Assert
+        expect(importedExtensions).toEqual(Object.values(extensions));
+    });
+});
diff --git a/src/creation/simplification/simplifyPackageRules.test.ts b/src/creation/simplification/simplifyPackageRules.test.ts
new file mode 100644
index 0000000..b95b730
--- /dev/null
+++ b/src/creation/simplification/simplifyPackageRules.test.ts
@@ -0,0 +1,103 @@
+import { ConfigurationError } from "../../errors/configurationError";
+import { ESLintRuleOptions } from "../../rules/types";
+import { createEmptyConversionResults } from "../../conversion/conversionResults.stubs";
+import { simplifyPackageRules } from "./simplifyPackageRules";
+
+const createStubDependencies = () => ({
+    removeExtendsDuplicatedRules: jest.fn(),
+    retrieveExtendsValues: jest.fn(),
+});
+
+describe("simplifyPackageRules", () => {
+    it("returns the conversion results directly when there is no loaded eslint configuration", async () => {
+        // Arrange
+        const dependencies = createStubDependencies();
+        const eslint = undefined;
+        const ruleConversionResults = createEmptyConversionResults();
+
+        // Act
+        const simplifiedResults = await simplifyPackageRules(
+            dependencies,
+            eslint,
+            ruleConversionResults,
+        );
+
+        // Assert
+        expect(simplifiedResults).toBe(ruleConversionResults);
+    });
+
+    it("returns the conversion results directly when the eslint configuration doesn't extend", async () => {
+        // Arrange
+        const dependencies = createStubDependencies();
+        const eslint = {};
+        const ruleConversionResults = createEmptyConversionResults();
+
+        // Act
+        const simplifiedResults = await simplifyPackageRules(
+            dependencies,
+            eslint,
+            ruleConversionResults,
+        );
+
+        // Assert
+        expect(simplifiedResults).toBe(ruleConversionResults);
+    });
+
+    it("returns the conversion results directly when the eslint configuration has an empty extends", async () => {
+        // Arrange
+        const dependencies = createStubDependencies();
+        const eslint = {
+            extends: [],
+        };
+        const ruleConversionResults = createEmptyConversionResults();
+
+        // Act
+        const simplifiedResults = await simplifyPackageRules(
+            dependencies,
+            eslint,
+            ruleConversionResults,
+        );
+
+        // Assert
+        expect(simplifiedResults).toBe(ruleConversionResults);
+    });
+
+    it("includes deduplicated rules and extension failures when the eslint configuration extends", async () => {
+        // Arrange
+        const configurationErrors = [new ConfigurationError(new Error("oh no"), "darn")];
+        const deduplicatedRules = new Map<string, ESLintRuleOptions>([
+            [
+                "rule-name",
+                {
+                    ruleArguments: [],
+                    ruleName: "rule-name",
+                    ruleSeverity: "warn",
+                },
+            ],
+        ]);
+        const dependencies = {
+            removeExtendsDuplicatedRules: () => deduplicatedRules,
+            retrieveExtendsValues: async () => ({
+                configurationErrors,
+                importedExtensions: [],
+            }),
+        };
+        const eslint = {
+            extends: ["extension-name"],
+        };
+        const ruleConversionResults = createEmptyConversionResults();
+
+        // Act
+        const simplifiedResults = await simplifyPackageRules(
+            dependencies,
+            eslint,
+            ruleConversionResults,
+        );
+
+        // Assert
+        expect(simplifiedResults).toEqual({
+            converted: deduplicatedRules,
+            failed: configurationErrors,
+        });
+    });
+});
diff --git a/src/creation/writeConversionResults.test.ts b/src/creation/writeConversionResults.test.ts
index 2da4e79..5af5c92 100644
--- a/src/creation/writeConversionResults.test.ts
+++ b/src/creation/writeConversionResults.test.ts
@@ -1,13 +1,15 @@
 import { createEmptyConversionResults } from "../conversion/conversionResults.stubs";
 import { writeConversionResults } from "./writeConversionResults";
+import { OriginalConfigurations } from "../input/findOriginalConfigurations";
 import { formatJsonOutput } from "./formatting/formatters/formatJsonOutput";
 
-const originalConfigurations = {
+const createStubOriginalConfigurations = (overrides: Partial<OriginalConfigurations> = {}) => ({
     tslint: {
         rulesDirectory: [],
         rules: {},
     },
-};
+    ...overrides,
+});
 
 describe("writeConversionResults", () => {
     it("excludes the tslint plugin when there are no missing rules", async () => {
@@ -22,7 +24,7 @@ describe("writeConversionResults", () => {
             { fileSystem },
             ".eslintrc.json",
             conversionResults,
-            originalConfigurations,
+            createStubOriginalConfigurations(),
         );
 
         // Assert
@@ -64,7 +66,7 @@ describe("writeConversionResults", () => {
             { fileSystem },
             ".eslintrc.json",
             conversionResults,
-            originalConfigurations,
+            createStubOriginalConfigurations(),
         );
 
         // Assert
@@ -95,4 +97,51 @@ describe("writeConversionResults", () => {
             }),
         );
     });
+
+    it("includes the original eslint configuration when it exists", async () => {
+        // Arrange
+        const conversionResults = createEmptyConversionResults({
+            converted: new Map(),
+        });
+        const eslint = {
+            env: {},
+            extends: [],
+            globals: {
+                Promise: true,
+            },
+            rules: {},
+        };
+        const originalConfigurations = createStubOriginalConfigurations({
+            eslint,
+        });
+        const fileSystem = { writeFile: jest.fn().mockReturnValue(Promise.resolve()) };
+
+        // Act
+        await writeConversionResults(
+            { fileSystem },
+            ".eslintrc.json",
+            conversionResults,
+            originalConfigurations,
+        );
+
+        // Assert
+        expect(fileSystem.writeFile).toHaveBeenLastCalledWith(
+            ".eslintrc.json",
+            formatJsonOutput({
+                ...eslint,
+                env: {
+                    browser: true,
+                    es6: true,
+                    node: true,
+                },
+                parser: "@typescript-eslint/parser",
+                parserOptions: {
+                    project: "tsconfig.json",
+                    sourceType: "module",
+                },
+                plugins: ["@typescript-eslint"],
+                rules: {},
+            }),
+        );
+    });
 });
diff --git a/src/input/findESLintConfiguration.test.ts b/src/input/findESLintConfiguration.test.ts
index 08118a9..d19dba9 100644
--- a/src/input/findESLintConfiguration.test.ts
+++ b/src/input/findESLintConfiguration.test.ts
@@ -65,6 +65,7 @@ describe("findESLintConfiguration", () => {
         // Assert
         expect(result).toEqual({
             env: {},
+            extends: [],
             rules: {},
         });
     });
diff --git a/src/input/findOriginalConfigurations.test.ts b/src/input/findOriginalConfigurations.test.ts
index c1777a8..cd476f2 100644
--- a/src/input/findOriginalConfigurations.test.ts
+++ b/src/input/findOriginalConfigurations.test.ts
@@ -14,6 +14,7 @@ const createRawSettings = () => ({
 const createDependencies = (overrides: Partial<FindOriginalConfigurationsDependencies> = {}) => ({
     findESLintConfiguration: async () => ({
         env: {},
+        extends: [],
         rules: {},
     }),
     findPackagesConfiguration: async () => ({
@@ -85,6 +86,7 @@ describe("findOriginalConfigurations", () => {
             data: {
                 eslint: {
                     env: {},
+                    extends: [],
                     rules: {},
                 },
                 packages: {
diff --git a/src/reporting/reportConversionResults.test.ts b/src/reporting/reportConversionResults.test.ts
index ddc3e64..bbd335d 100644
--- a/src/reporting/reportConversionResults.test.ts
+++ b/src/reporting/reportConversionResults.test.ts
@@ -1,8 +1,9 @@
-import { ConversionError } from "../rules/conversionError";
 import { ESLintRuleOptions } from "../rules/types";
 import { reportConversionResults } from "./reportConversionResults";
 import { createStubLogger, expectEqualWrites } from "../adapters/logger.stubs";
 import { createEmptyConversionResults } from "../conversion/conversionResults.stubs";
+import { ConversionError } from "../errors/conversionError";
+import { ConfigurationError } from "../errors/configurationError";
 
 describe("reportConversionResults", () => {
     it("logs a successful conversion when there is one converted rule", () => {
@@ -64,6 +65,25 @@ describe("reportConversionResults", () => {
         );
     });
 
+    it("logs a failed configuration when there is one failed configuration error", () => {
+        // Arrange
+        const conversionResults = createEmptyConversionResults({
+            failed: [new ConfigurationError(new Error("and a one"), "some complaint")],
+        });
+
+        const logger = createStubLogger();
+
+        // Act
+        reportConversionResults({ logger }, conversionResults);
+
+        // Assert
+        expectEqualWrites(
+            logger.stderr.write,
+            "💀 1 error thrown. 💀",
+            `Check ${logger.debugFileName} for details.`,
+        );
+    });
+
     it("logs a failed conversion when there is one failed conversion", () => {
         // Arrange
         const conversionResults = createEmptyConversionResults({
@@ -84,7 +104,7 @@ describe("reportConversionResults", () => {
         // Assert
         expectEqualWrites(
             logger.stderr.write,
-            "💀 1 rule threw an error; using eslint-plugin-tslint instead. 💀",
+            "💀 1 error thrown. 💀",
             `Check ${logger.debugFileName} for details.`,
         );
     });
@@ -114,7 +134,7 @@ describe("reportConversionResults", () => {
         // Assert
         expectEqualWrites(
             logger.stderr.write,
-            "💀 2 rules threw errors; using eslint-plugin-tslint instead. 💀",
+            "💀 2 errors thrown. 💀",
             `Check ${logger.debugFileName} for details.`,
         );
     });
@@ -181,10 +201,10 @@ describe("reportConversionResults", () => {
         );
     });
 
-    it("logs a missing package when there is a missing package", () => {
+    it("logs a missing plugin when there is a missing plugin", () => {
         // Arrange
         const conversionResults = createEmptyConversionResults({
-            packages: new Set(["package-one"]),
+            plugins: new Set(["plugin-one"]),
         });
 
         const logger = createStubLogger();
@@ -196,14 +216,14 @@ describe("reportConversionResults", () => {
         expectEqualWrites(
             logger.stdout.write,
             "⚡ 1 package is required for new ESLint rules. ⚡",
-            "\tpackage-one",
+            "\tplugin-one",
         );
     });
 
-    it("logs missing packages when there are missing packages", () => {
+    it("logs missing plugins when there are missing plugins", () => {
         // Arrange
         const conversionResults = createEmptyConversionResults({
-            packages: new Set(["package-one", "package-two"]),
+            plugins: new Set(["plugin-one", "plugin-two"]),
         });
 
         const logger = createStubLogger();
@@ -215,8 +235,8 @@ describe("reportConversionResults", () => {
         expectEqualWrites(
             logger.stdout.write,
             "⚡ 2 packages are required for new ESLint rules. ⚡",
-            "\tpackage-one",
-            "\tpackage-two",
+            "\tplugin-one",
+            "\tplugin-two",
         );
     });
 });
diff --git a/src/rules/convertRule.test.ts b/src/rules/convertRule.test.ts
index 3448d6e..74776ae 100644
--- a/src/rules/convertRule.test.ts
+++ b/src/rules/convertRule.test.ts
@@ -1,6 +1,6 @@
+import { ConversionError } from "../errors/conversionError";
 import { convertRule } from "./convertRule";
 import { RuleConverter } from "./converter";
-import { ConversionError } from "./conversionError";
 import { TSLintRuleOptions } from "./types";
 
 describe("convertRule", () => {
diff --git a/src/rules/convertRuleSeverity.test.ts b/src/rules/convertRuleSeverity.test.ts
index a200969..c72b200 100644
--- a/src/rules/convertRuleSeverity.test.ts
+++ b/src/rules/convertRuleSeverity.test.ts
@@ -1,4 +1,4 @@
-import { convertRuleSeverity } from "./convertRuleSeverity";
+import { convertTSLintRuleSeverity, convertRawESLintRuleSeverity } from "./convertRuleSeverity";
 
 describe("convertRuleSeverity", () => {
     it("returns error when the severity is error", () => {
@@ -6,7 +6,7 @@ describe("convertRuleSeverity", () => {
         const tslintSeverity = "error";
 
         // Act
-        const eslintSeverity = convertRuleSeverity(tslintSeverity);
+        const eslintSeverity = convertTSLintRuleSeverity(tslintSeverity);
 
         // Assert
         expect(eslintSeverity).toEqual("error");
@@ -17,7 +17,7 @@ describe("convertRuleSeverity", () => {
         const tslintSeverity = "off";
 
         // Act
-        const eslintSeverity = convertRuleSeverity(tslintSeverity);
+        const eslintSeverity = convertTSLintRuleSeverity(tslintSeverity);
 
         // Assert
         expect(eslintSeverity).toEqual("off");
@@ -28,9 +28,55 @@ describe("convertRuleSeverity", () => {
         const tslintSeverity = "warning";
 
         // Act
-        const eslintSeverity = convertRuleSeverity(tslintSeverity);
+        const eslintSeverity = convertTSLintRuleSeverity(tslintSeverity);
 
         // Assert
         expect(eslintSeverity).toEqual("warn");
     });
 });
+
+describe("convertRawESLintRuleSeverity", () => {
+    it("returns off when the severity is 0", () => {
+        // Arrange
+        const rawSeverity = 0;
+
+        // Act
+        const converted = convertRawESLintRuleSeverity(rawSeverity);
+
+        // Assert
+        expect(converted).toEqual("off");
+    });
+
+    it("returns off when the severity is 2", () => {
+        // Arrange
+        const rawSeverity = 1;
+
+        // Act
+        const converted = convertRawESLintRuleSeverity(rawSeverity);
+
+        // Assert
+        expect(converted).toEqual("warn");
+    });
+
+    it("returns off when the severity is 3", () => {
+        // Arrange
+        const rawSeverity = 2;
+
+        // Act
+        const converted = convertRawESLintRuleSeverity(rawSeverity);
+
+        // Assert
+        expect(converted).toEqual("error");
+    });
+
+    it("returns the original severity when it's a string", () => {
+        // Arrange
+        const rawSeverity = "warn";
+
+        // Act
+        const converted = convertRawESLintRuleSeverity(rawSeverity);
+
+        // Assert
+        expect(converted).toEqual("warn");
+    });
+});
diff --git a/src/rules/convertRules.test.ts b/src/rules/convertRules.test.ts
index 5b3c351..bb11089 100644
--- a/src/rules/convertRules.test.ts
+++ b/src/rules/convertRules.test.ts
@@ -1,6 +1,6 @@
+import { ConversionError } from "../errors/conversionError";
 import { convertRules } from "./convertRules";
 import { TSLintRuleOptions } from "./types";
-import { ConversionError } from "./conversionError";
 
 describe("convertRules", () => {
     it("doesn't marks a disabled rule as missing when its converter returns undefined", () => {
@@ -182,7 +182,7 @@ describe("convertRules", () => {
         );
     });
 
-    it("marks a new package when a conversion has a new package", () => {
+    it("marks a new plugin when a conversion has a new plugin", () => {
         // Arrange
         const tslintRule: TSLintRuleOptions = {
             ruleArguments: [],
@@ -190,19 +190,19 @@ describe("convertRules", () => {
             ruleSeverity: "error",
         };
         const conversionResult = {
-            packages: ["extra-package"],
+            plugins: ["extra-plugin"],
             rules: [],
         };
         const converters = new Map([[tslintRule.ruleName, () => conversionResult]]);
         const mergers = new Map();
 
         // Act
-        const { packages } = convertRules(
+        const { plugins } = convertRules(
             { converters, mergers },
             { [tslintRule.ruleName]: tslintRule },
         );
 
         // Assert
-        expect(packages).toEqual(new Set(["extra-package"]));
+        expect(plugins).toEqual(new Set(["extra-plugin"]));
     });
 });