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

Issue:
Make trivia/whitespace (Prettier) rules opt-out
### 🚀 Feature Request

Following #2 and #301, I really want to harp on the opinion that a linter (ESLint) should be separate from the formatter (Prettier). Having folks need to opt-in with a CLI flag in order to get that behavior will further that cause. 

#### Existing Behavior

Right now, all existing TSLint rules will be output to the ESLint output - even Prettier-related ones such as `whitespace`.

#### Change Proposal

If a rule is disabled by `tslint-config-prettier`, it should only be output if a new CLI flag is provided.
If a tslint.json doesn't extend from tslint-config-prettier, recommend Prettier
😄. TSLint maintainers (myself included) have been recommending moving off of whitespace rules for quite some time now. I believe ESLint maintainers are of the same opinion, though it'd be nice to have a source on that. 

Pull request:
Added recommended --prettier flag to include eslint-config-prettier
## PR Checklist

-   [x] Addresses an existing issue: fixes #2, fixes #302
-   [x] That issue was marked as [`status: accepting prs`](https://github.com/typescript-eslint/tslint-to-eslint-config/labels/status%3A%20accepting%20prs)

## Overview

When `--prettier` is enabled, [`eslint-config-prettier`](https://github.com/prettier/eslint-config-prettier) will always be added to the list of ESLint plugins.

When `--prettier` isn't enabled:

-   If the output configuration already doesn't enable any formatting rules, it'll extend from `eslint-config-prettier`.
-   Otherwise, a CLI message will suggest running with `--prettier`.

![Screenshot of the new red CLI output for --prettier (see below)](https://user-images.githubusercontent.com/3335181/80316581-9ae50580-87cc-11ea-9bdb-02c09ba9c60f.png)

```
✨ 90 rules replaced with their ESLint equivalents. ✨

❗ 10 ESLint rules behave differently from their TSLint counterparts ❗
  Check ./tslint-to-eslint-config.log for details.

❌ 1 error thrown. ❌
  Check ./tslint-to-eslint-config.log for details.
️
❓ 1 rule is not known by tslint-to-eslint-config to have an ESLint equivalent. ❓
  The "@typescript-eslint/tslint/config" section of ./.eslintrc.js configures eslint-plugin-tslint to run it in TSLint within ESLint.
  Check ./tslint-to-eslint-config.log for details.

⚡ 7 packages are required for this ESLint configuration. ⚡
  npm install @typescript-eslint/eslint-plugin @typescript-eslint/parser @typescript-eslint/tslint-eslint-plugin eslint eslint-plugin-import eslint-plugin-jsdoc eslint-plugin-prefer-arrow --save-dev

☠ Prettier plugins are missing from your configuration. ☠
  We highly recommend running tslint-to-eslint-config --prettier to disable formatting ESLint rules.
  See https://github/typescript-eslint/tslint-to-eslint-config/docs/FAQs.md#should-i-use-prettier.
️
❓ 1 editor setting is not known by tslint-to-eslint-config to have an ESLint equivalent. ❓
  Check ./tslint-to-eslint-config.log for details.

✅ All is well! ✅
```

Declares typings in a `.d.ts` file pending https://github.com/DefinitelyTyped/DefinitelyTyped/pull/44245.

Patch:
diff --git a/.gitignore b/.gitignore
index b34ba9f..0b68bcb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 *.tsbuildinfo
 *.d.ts
+!src/typings/**/*.d.ts
 *.log
 *.map
 coverage/
diff --git a/README.md b/README.md
index a50d7d0..f88739e 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,7 @@ Each of these flags is optional:
 -   **[`editor`](#editor)**: Path to an editor configuration file to convert linter settings within.
 -   **[`eslint`](#eslint)**: Path to an ESLint configuration file to read settings from.
 -   **[`package`](#package)**: Path to a package.json file to read dependencies from.
+-   **[`prettier`](#prettier)**: Add `eslint-config-prettier` to the plugins list.
 -   **[`tslint`](#tslint)**: Path to a TSLint configuration file to read settings from.
 -   **[`typescript`](#typescript)**: Path to a TypeScript configuration file to read TypeScript compiler options from.
 
@@ -97,6 +98,22 @@ _Default: `package.json`_
 Path to a `package.json` file to read dependencies from.
 This will help inform the generated ESLint configuration file's [env](https://eslint.org/docs/user-guide/configuring#specifying-parser-options) settings.
 
+#### `prettier`
+
+```shell
+npx tslint-to-eslint-config --prettier
+```
+
+_Default: `false`_
+
+Add [`eslint-config-prettier`](https://github.com/prettier/eslint-config-prettier) to the list of ESLint plugins.
+We [highly recommend](./docs/FAQs.md#should-i-use-prettier) you use [Prettier](http://prettier.io) for code formatting.
+
+When `--prettier` isn't enabled:
+
+-   If the output configuration already doesn't enable any formatting rules, it'll extend from `eslint-config-prettier`.
+-   Otherwise, a CLI message will suggest running with `--prettier`.
+
 #### `tslint`
 
 ```shell
diff --git a/docs/Architecture.md b/docs/Architecture.md
index ec2224a..d7c10dc 100644
--- a/docs/Architecture.md
+++ b/docs/Architecture.md
@@ -14,6 +14,7 @@ Within `src/conversion/convertConfig.ts`, the following steps occur:
 1. Existing configurations are read from disk
 2. TSLint rules are converted into their ESLint configurations
 3. ESLint configurations are simplified based on extended ESLint and TSLint presets
+    - 3a. If no output rules conflict with `eslint-config-prettier`, it's added in
 4. The simplified configuration is written to the output config file
 5. A summary of the results is printed to the user's console
 
diff --git a/package-lock.json b/package-lock.json
index 7bcbbd5..6f6046d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4558,7 +4558,6 @@
             "version": "6.11.0",
             "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz",
             "integrity": "sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==",
-            "dev": true,
             "requires": {
                 "get-stdin": "^6.0.0"
             }
@@ -5057,8 +5056,7 @@
         "get-stdin": {
             "version": "6.0.0",
             "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
-            "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
-            "dev": true
+            "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g=="
         },
         "get-stream": {
             "version": "4.1.0",
diff --git a/package.json b/package.json
index 95033d7..63ac398 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
     "dependencies": {
         "chalk": "4.0.0",
         "commander": "5.1.0",
+        "eslint-config-prettier": "^6.11.0",
         "strip-json-comments": "3.1.0",
         "tslint": "6.1.1",
         "typescript": "3.8.3"
@@ -28,7 +29,6 @@
         "@typescript-eslint/parser": "2.29.0",
         "babel-jest": "25.4.0",
         "eslint": "6.8.0",
-        "eslint-config-prettier": "6.11.0",
         "husky": "4.2.5",
         "jest": "25.4.0",
         "lint-staged": "10.1.7",
diff --git a/src/cli/main.ts b/src/cli/main.ts
index 37d8ef9..bc28831 100644
--- a/src/cli/main.ts
+++ b/src/cli/main.ts
@@ -10,6 +10,7 @@ import {
     convertEditorConfig,
     ConvertEditorConfigDependencies,
 } from "../conversion/convertEditorConfig";
+import { addPrettierExtensions } from "../creation/simplification/prettier/addPrettierExtensions";
 import { removeExtendsDuplicatedRules } from "../creation/simplification/removeExtendsDuplicatedRules";
 import {
     retrieveExtendsValues,
@@ -103,6 +104,7 @@ const retrieveExtendsValuesDependencies: RetrieveExtendsValuesDependencies = {
 };
 
 const simplifyPackageRulesDependencies: SimplifyPackageRulesDependencies = {
+    addPrettierExtensions,
     removeExtendsDuplicatedRules,
     retrieveExtendsValues: bind(retrieveExtendsValues, retrieveExtendsValuesDependencies),
 };
diff --git a/src/cli/runCli.ts b/src/cli/runCli.ts
index a1a3538..92223d4 100644
--- a/src/cli/runCli.ts
+++ b/src/cli/runCli.ts
@@ -20,11 +20,12 @@ export const runCli = async (
     const command = new Command()
         .usage("[options] <file ...> --language [language]")
         .option("--config [config]", "eslint configuration file to output to")
+        .option("--editor [editor]", "editor configuration file to convert")
         .option("--eslint [eslint]", "eslint configuration file to convert using")
         .option("--package [package]", "package configuration file to convert using")
+        .option("--prettier [prettier]", "add eslint-config-prettier to the plugins list")
         .option("--tslint [tslint]", "tslint configuration file to convert using")
         .option("--typescript [typescript]", "typescript configuration file to convert using")
-        .option("--editor [editor]", "editor configuration file to convert")
         .option("-V --version", "output the package version");
 
     const parsedArgv = {
diff --git a/src/conversion/conversionResults.stubs.ts b/src/conversion/conversionResults.stubs.ts
index b3a6ffd..fbcc05d 100644
--- a/src/conversion/conversionResults.stubs.ts
+++ b/src/conversion/conversionResults.stubs.ts
@@ -1,9 +1,9 @@
 import { EditorSettingConversionResults } from "../editorSettings/convertEditorSettings";
-import { RuleConversionResults } from "../rules/convertRules";
+import { SimplifiedResultsConfiguration } from "../creation/simplification/simplifyPackageRules";
 
 export const createEmptyConversionResults = (
-    overrides: Partial<RuleConversionResults> = {},
-): RuleConversionResults => ({
+    overrides: Partial<SimplifiedResultsConfiguration> = {},
+): SimplifiedResultsConfiguration => ({
     converted: new Map(),
     failed: [],
     missing: [],
diff --git a/src/conversion/convertConfig.ts b/src/conversion/convertConfig.ts
index 67e8b93..d7bbae2 100644
--- a/src/conversion/convertConfig.ts
+++ b/src/conversion/convertConfig.ts
@@ -34,14 +34,12 @@ export const convertConfig = async (
     );
 
     // 3. ESLint configurations are simplified based on extended ESLint and TSLint presets
-    const simplifiedConfiguration = {
-        ...ruleConversionResults,
-        ...(await dependencies.simplifyPackageRules(
-            originalConfigurations.data.eslint,
-            originalConfigurations.data.tslint,
-            ruleConversionResults,
-        )),
-    };
+    const simplifiedConfiguration = await dependencies.simplifyPackageRules(
+        originalConfigurations.data.eslint,
+        originalConfigurations.data.tslint,
+        ruleConversionResults,
+        settings.prettier,
+    );
 
     // 4. The simplified configuration is written to the output config file
     const fileWriteError = await dependencies.writeConversionResults(
diff --git a/src/creation/simplification/prettier/addPrettierExtensions.ts b/src/creation/simplification/prettier/addPrettierExtensions.ts
new file mode 100644
index 0000000..703cb6f
--- /dev/null
+++ b/src/creation/simplification/prettier/addPrettierExtensions.ts
@@ -0,0 +1,21 @@
+import prettierRuleSettings from "eslint-config-prettier";
+
+import { RuleConversionResults } from "../../../rules/convertRules";
+
+export const addPrettierExtensions = async (
+    ruleConversionResults: Pick<RuleConversionResults, "converted">,
+    prettierRequested?: boolean,
+) => {
+    if (prettierRequested) {
+        return true;
+    }
+
+    for (const rule in prettierRuleSettings.rules) {
+        const convertedRule = ruleConversionResults.converted.get(rule);
+        if (convertedRule !== undefined && convertedRule.ruleSeverity !== "off") {
+            return false;
+        }
+    }
+
+    return true;
+};
diff --git a/src/creation/simplification/simplifyPackageRules.ts b/src/creation/simplification/simplifyPackageRules.ts
index b7b9d8b..e16f18b 100644
--- a/src/creation/simplification/simplifyPackageRules.ts
+++ b/src/creation/simplification/simplifyPackageRules.ts
@@ -5,18 +5,17 @@ import { TSLintConfiguration } from "../../input/findTSLintConfiguration";
 import { RuleConversionResults } from "../../rules/convertRules";
 import { uniqueFromSources } from "../../utils";
 import { collectTSLintRulesets } from "./collectTSLintRulesets";
+import { addPrettierExtensions } from "./prettier/addPrettierExtensions";
 import { removeExtendsDuplicatedRules } from "./removeExtendsDuplicatedRules";
 import { retrieveExtendsValues } from "./retrieveExtendsValues";
 
 export type SimplifyPackageRulesDependencies = {
+    addPrettierExtensions: typeof addPrettierExtensions;
     removeExtendsDuplicatedRules: typeof removeExtendsDuplicatedRules;
     retrieveExtendsValues: SansDependencies<typeof retrieveExtendsValues>;
 };
 
-export type SimplifiedRuleConversionResults = Pick<
-    RuleConversionResults,
-    "converted" | "failed"
-> & {
+export type SimplifiedResultsConfiguration = RuleConversionResults & {
     extends?: string[];
 };
 
@@ -28,11 +27,18 @@ export const simplifyPackageRules = async (
     dependencies: SimplifyPackageRulesDependencies,
     eslint: Pick<OriginalConfigurations<ESLintConfiguration>, "full"> | undefined,
     tslint: OriginalConfigurations<Pick<TSLintConfiguration, "extends">>,
-    ruleConversionResults: SimplifiedRuleConversionResults,
-): Promise<SimplifiedRuleConversionResults> => {
+    ruleConversionResults: RuleConversionResults,
+    prettierRequested?: boolean,
+): Promise<SimplifiedResultsConfiguration> => {
     const extendedESLintRulesets = eslint?.full.extends ?? [];
     const extendedTSLintRulesets = collectTSLintRulesets(tslint);
     const allExtensions = uniqueFromSources(extendedESLintRulesets, extendedTSLintRulesets);
+
+    // 3a. If no output rules conflict with `eslint-config-prettier`, it's added in
+    if (await dependencies.addPrettierExtensions(ruleConversionResults, prettierRequested)) {
+        allExtensions.push("eslint-config-prettier", "eslint-config-prettier/@typescript-eslint");
+    }
+
     if (allExtensions.length === 0) {
         return ruleConversionResults;
     }
@@ -47,6 +53,7 @@ export const simplifyPackageRules = async (
     );
 
     return {
+        ...ruleConversionResults,
         converted,
         extends: allExtensions,
         failed: [...ruleConversionResults.failed, ...configurationErrors],
diff --git a/src/creation/writeConversionResults.ts b/src/creation/writeConversionResults.ts
index de39fb2..cc37921 100644
--- a/src/creation/writeConversionResults.ts
+++ b/src/creation/writeConversionResults.ts
@@ -1,10 +1,9 @@
 import { FileSystem } from "../adapters/fileSystem";
-import { RuleConversionResults } from "../rules/convertRules";
 import { AllOriginalConfigurations } from "../input/findOriginalConfigurations";
 import { createEnv } from "./eslint/createEnv";
 import { formatConvertedRules } from "./formatConvertedRules";
 import { formatOutput } from "./formatting/formatOutput";
-import { SimplifiedRuleConversionResults } from "./simplification/simplifyPackageRules";
+import { SimplifiedResultsConfiguration } from "./simplification/simplifyPackageRules";
 
 export type WriteConversionResultsDependencies = {
     fileSystem: Pick<FileSystem, "writeFile">;
@@ -13,7 +12,7 @@ export type WriteConversionResultsDependencies = {
 export const writeConversionResults = async (
     dependencies: WriteConversionResultsDependencies,
     outputPath: string,
-    ruleConversionResults: RuleConversionResults & SimplifiedRuleConversionResults,
+    ruleConversionResults: SimplifiedResultsConfiguration,
     originalConfigurations: AllOriginalConfigurations,
 ) => {
     const plugins = ["@typescript-eslint"];
diff --git a/src/input/findESLintConfiguration.ts b/src/input/findESLintConfiguration.ts
index 0b26dc7..f066520 100644
--- a/src/input/findESLintConfiguration.ts
+++ b/src/input/findESLintConfiguration.ts
@@ -39,9 +39,9 @@ export type FindESLintConfigurationDependencies = {
 
 export const findESLintConfiguration = async (
     dependencies: FindESLintConfigurationDependencies,
-    rawSettings: Pick<TSLintToESLintSettings, "config" | "eslint">,
+    config: Pick<TSLintToESLintSettings, "config" | "eslint">,
 ): Promise<OriginalConfigurations<ESLintConfiguration> | Error> => {
-    const filePath = rawSettings.eslint ?? rawSettings.config;
+    const filePath = config.eslint ?? config.config;
     const [rawConfiguration, reportedConfiguration] = await Promise.all([
         findRawConfiguration<ESLintConfiguration>(dependencies.importer, filePath, {
             extends: [],
@@ -67,7 +67,7 @@ export const findESLintConfiguration = async (
         full: {
             ...defaultESLintConfiguration,
             ...reportedConfiguration,
-            extends: Array.from(new Set(extensions)),
+            extends: extensions,
         },
         raw: rawConfiguration,
     };
diff --git a/src/reporting/reportConversionResults.ts b/src/reporting/reportConversionResults.ts
index 76c4782..30f1c3a 100644
--- a/src/reporting/reportConversionResults.ts
+++ b/src/reporting/reportConversionResults.ts
@@ -3,7 +3,7 @@ import { EOL } from "os";
 
 import { Logger } from "../adapters/logger";
 import { SansDependencies } from "../binding";
-import { RuleConversionResults } from "../rules/convertRules";
+import { SimplifiedResultsConfiguration } from "../creation/simplification/simplifyPackageRules";
 import { ESLintRuleOptions, TSLintRuleOptions } from "../rules/types";
 import { choosePackageManager } from "./packages/choosePackageManager";
 import {
@@ -21,7 +21,7 @@ export type ReportConversionResultsDependencies = {
 export const reportConversionResults = async (
     dependencies: ReportConversionResultsDependencies,
     outputPath: string,
-    ruleConversionResults: RuleConversionResults,
+    ruleConversionResults: SimplifiedResultsConfiguration,
 ) => {
     const packageManager = await dependencies.choosePackageManager();
 
@@ -49,6 +49,10 @@ export const reportConversionResults = async (
     }
 
     logMissingPackages(ruleConversionResults, packageManager, dependencies.logger);
+
+    if (!ruleConversionResults.extends?.includes("eslint-config-prettier")) {
+        logPrettierExtension(dependencies.logger);
+    }
 };
 
 type RuleWithNotices = {
@@ -70,9 +74,9 @@ const logNotices = (converted: Map<string, ESLintRuleOptions>, logger: Logger) =
             ? " behaves differently from its TSLint counterpart"
             : "s behave differently from their TSLint counterparts";
 
-    logger.stdout.write(
-        chalk.blueBright(`${EOL}❗ ${rulesWithNotices.length} ESLint rule${behavior} ❗${EOL}`),
-    );
+    logger.stdout.write(chalk.blueBright(`${EOL}❗ ${rulesWithNotices.length}`));
+    logger.stdout.write(chalk.blue(` ESLint rule${behavior} `));
+    logger.stdout.write(chalk.blueBright(`❗${EOL}`));
     logger.stdout.write(chalk.blue(`  Check ${logger.debugFileName} for details.${EOL}`));
     logger.info.write(`${rulesWithNotices.length} ESLint rule${behavior}:${EOL}`);
 
@@ -86,3 +90,17 @@ const logNotices = (converted: Map<string, ESLintRuleOptions>, logger: Logger) =
 
     logger.info.write(EOL);
 };
+
+const logPrettierExtension = (logger: Logger) => {
+    logger.stdout.write(chalk.redBright(`${EOL}☠ Prettier`));
+    logger.stdout.write(chalk.red(` plugins are missing from your configuration. `));
+    logger.stdout.write(chalk.redBright(`☠${EOL}`));
+    logger.stdout.write(chalk.red(`  We highly recommend running `));
+    logger.stdout.write(chalk.redBright(`tslint-to-eslint-config --prettier`));
+    logger.stdout.write(chalk.red(` to disable formatting ESLint rules.${EOL}`));
+    logger.stdout.write(
+        chalk.red(
+            `  See https://github/typescript-eslint/tslint-to-eslint-config/docs/FAQs.md#should-i-use-prettier.${EOL}`,
+        ),
+    );
+};
diff --git a/src/reporting/reportOutputs.ts b/src/reporting/reportOutputs.ts
index e400eab..a925cd1 100644
--- a/src/reporting/reportOutputs.ts
+++ b/src/reporting/reportOutputs.ts
@@ -2,11 +2,12 @@ import chalk from "chalk";
 import { EOL } from "os";
 
 import { Logger } from "../adapters/logger";
+import { SimplifiedResultsConfiguration } from "../creation/simplification/simplifyPackageRules";
 import { EditorSetting } from "../editorSettings/types";
 import { ErrorSummary } from "../errors/errorSummary";
 import { ESLintRuleOptions } from "../rules/types";
+import { uniqueFromSources } from "../utils";
 import { PackageManager, installationMessages } from "./packages/packageManagers";
-import { RuleConversionResults } from "../rules/convertRules";
 
 export type EditorSettingEntry = Pick<EditorSetting, "editorSettingName">;
 
@@ -70,22 +71,25 @@ export const logMissingConversionTarget = <T>(
 };
 
 export const logMissingPackages = (
-    ruleConversionResults: Pick<RuleConversionResults, "missing" | "plugins">,
+    ruleConversionResults: Pick<SimplifiedResultsConfiguration, "extends" | "missing" | "plugins">,
     packageManager: PackageManager,
     logger: Logger,
 ) => {
-    const packageNames = [
+    const packageNames = uniqueFromSources([
         "@typescript-eslint/eslint-plugin",
         "@typescript-eslint/parser",
         ruleConversionResults.missing.length !== 0 && "@typescript-eslint/tslint-eslint-plugin",
         "eslint",
+        ...Array.from(
+            ruleConversionResults.extends?.map((extension) => extension.split("/")[0]) ?? [],
+        ),
         ...Array.from(ruleConversionResults.plugins),
-    ]
+    ])
         .filter(Boolean)
         .sort();
 
     logger.stdout.write(chalk.cyanBright(`${EOL}⚡ ${packageNames.length}`));
-    logger.stdout.write(chalk.cyan(" packages are required for running with ESLint."));
+    logger.stdout.write(chalk.cyan(" packages are required for this ESLint configuration."));
     logger.stdout.write(chalk.cyanBright(" ⚡"));
     logger.stdout.write(`${EOL}  `);
     logger.stdout.write(chalk.cyan(installationMessages[packageManager](packageNames.join(" "))));
diff --git a/src/types.ts b/src/types.ts
index b4d7f7c..8a3d7ec 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -4,6 +4,11 @@ export type TSLintToESLintSettings = {
      */
     config: string;
 
+    /**
+     * Original Editor configuration file path, such as `.vscode/settings.json`.
+     */
+    editor?: string;
+
     /**
      * Original ESLint configuration file path, such as `.eslintrc.js`.
      */
@@ -14,6 +19,11 @@ export type TSLintToESLintSettings = {
      */
     package?: string;
 
+    /**
+     * Add `eslint-config-prettier` to the plugins list.
+     */
+    prettier?: boolean;
+
     /**
      * Original TSLint configuration file path, such as `tslint.json`.
      */
@@ -23,11 +33,6 @@ export type TSLintToESLintSettings = {
      * Original TypeScript configuration file path, such as `tsconfig.json`.
      */
     typescript?: string;
-
-    /**
-     * Original Editor configuration file path, such as `.vscode/settings.json`.
-     */
-    editor?: string;
 };
 
 export type TSLintToESLintResult = ResultWithStatus;
diff --git a/src/typings/eslint-config-prettier.d.ts b/src/typings/eslint-config-prettier.d.ts
new file mode 100644
index 0000000..69c1d23
--- /dev/null
+++ b/src/typings/eslint-config-prettier.d.ts
@@ -0,0 +1,4 @@
+// Pending https://github.com/DefinitelyTyped/DefinitelyTyped/pull/44245...
+declare module "eslint-config-prettier" {
+    export const rules: Record<string, 0 | "off">;
+}


Test patch:
diff --git a/src/conversion/convertConfig.test.ts b/src/conversion/convertConfig.test.ts
index 66f202d..c3f4c8b 100644
--- a/src/conversion/convertConfig.test.ts
+++ b/src/conversion/convertConfig.test.ts
@@ -17,7 +17,10 @@ const createStubDependencies = (
     simplifyPackageRules: async (_configurations, data) => ({
         ...data,
         converted: new Map(),
+        extends: [],
         failed: [],
+        missing: [],
+        plugins: new Set(),
     }),
     writeConversionResults: jest.fn().mockReturnValue(Promise.resolve()),
     ...overrides,
diff --git a/src/creation/simplification/prettier/addPrettierExtensions.test.ts b/src/creation/simplification/prettier/addPrettierExtensions.test.ts
new file mode 100644
index 0000000..2f21ece
--- /dev/null
+++ b/src/creation/simplification/prettier/addPrettierExtensions.test.ts
@@ -0,0 +1,59 @@
+import { createEmptyConversionResults } from "../../../conversion/conversionResults.stubs";
+import { addPrettierExtensions } from "./addPrettierExtensions";
+
+const createStubRuleConversions = (ruleName: string, ruleSeverity: "error" | "off") =>
+    new Map([[ruleName, { ruleName, ruleSeverity }]]);
+
+describe("addPrettierExtensions", () => {
+    it("returns false when a matching converted rule is enabled", async () => {
+        // Arrange
+        const ruleConversionResults = createEmptyConversionResults({
+            converted: createStubRuleConversions("max-len", "error"),
+        });
+
+        // Act
+        const result = await addPrettierExtensions(ruleConversionResults);
+
+        // Assert
+        expect(result).toEqual(false);
+    });
+
+    it("returns true when a matching converted rule is disabled", async () => {
+        // Arrange
+        const ruleConversionResults = createEmptyConversionResults({
+            converted: createStubRuleConversions("max-len", "off"),
+        });
+
+        // Act
+        const result = await addPrettierExtensions(ruleConversionResults);
+
+        // Assert
+        expect(result).toEqual(true);
+    });
+
+    it("returns true when there are no matching converted rules", async () => {
+        // Arrange
+        const ruleConversionResults = createEmptyConversionResults({
+            converted: createStubRuleConversions("unknown", "error"),
+        });
+
+        // Act
+        const result = await addPrettierExtensions(ruleConversionResults);
+
+        // Assert
+        expect(result).toEqual(true);
+    });
+
+    it("returns true when prettier is requested", async () => {
+        // Arrange
+        const ruleConversionResults = createEmptyConversionResults({
+            converted: createStubRuleConversions("max-len", "off"),
+        });
+
+        // Act
+        const result = await addPrettierExtensions(ruleConversionResults, true);
+
+        // Assert
+        expect(result).toEqual(true);
+    });
+});
diff --git a/src/creation/simplification/simplifyPackageRules.test.ts b/src/creation/simplification/simplifyPackageRules.test.ts
index d4fdee0..ea119d4 100644
--- a/src/creation/simplification/simplifyPackageRules.test.ts
+++ b/src/creation/simplification/simplifyPackageRules.test.ts
@@ -1,14 +1,16 @@
 import { ConfigurationError } from "../../errors/configurationError";
 import { ESLintRuleOptions } from "../../rules/types";
 import { createEmptyConversionResults } from "../../conversion/conversionResults.stubs";
-import { simplifyPackageRules } from "./simplifyPackageRules";
+import { simplifyPackageRules, SimplifyPackageRulesDependencies } from "./simplifyPackageRules";
 
-const createStubDependencies = () => ({
+const createStubDependencies = (overrides: Partial<SimplifyPackageRulesDependencies> = {}) => ({
+    addPrettierExtensions: jest.fn(),
     removeExtendsDuplicatedRules: jest.fn(),
     retrieveExtendsValues: async () => ({
         configurationErrors: [],
         importedExtensions: [],
     }),
+    ...overrides,
 });
 
 const createStubESLintConfiguration = (fullExtends: string[]) => ({
@@ -25,7 +27,7 @@ const createStubTSLintConfiguration = () => ({
 });
 
 describe("simplifyPackageRules", () => {
-    it("returns the conversion results directly when there is no loaded ESLint configuration and no TSLint extensions", async () => {
+    it("returns equivalent conversion results when there is no loaded ESLint configuration and no TSLint extensions", async () => {
         // Arrange
         const dependencies = createStubDependencies();
         const eslint = undefined;
@@ -41,10 +43,36 @@ describe("simplifyPackageRules", () => {
         );
 
         // Assert
-        expect(simplifiedResults).toBe(ruleConversionResults);
+        expect(simplifiedResults).toEqual(ruleConversionResults);
     });
 
-    it("returns the conversion results directly when there is an empty ESLint configuration and no TSLint extensions", async () => {
+    it("adds Prettier extensions when addPrettierExtensions returns true", async () => {
+        // Arrange
+        const dependencies = createStubDependencies({
+            addPrettierExtensions: async () => true,
+        });
+        const eslint = undefined;
+        const tslint = createStubTSLintConfiguration();
+        const ruleConversionResults = createEmptyConversionResults();
+
+        // Act
+        const simplifiedResults = await simplifyPackageRules(
+            dependencies,
+            eslint,
+            tslint,
+            ruleConversionResults,
+            true,
+        );
+
+        // Assert
+        expect(simplifiedResults).toEqual({
+            ...ruleConversionResults,
+            converted: undefined,
+            extends: ["eslint-config-prettier", "eslint-config-prettier/@typescript-eslint"],
+        });
+    });
+
+    it("returns equivalent conversion results when there is an empty ESLint configuration and no TSLint extensions", async () => {
         // Arrange
         const dependencies = createStubDependencies();
         const eslint = createStubESLintConfiguration([]);
@@ -60,7 +88,7 @@ describe("simplifyPackageRules", () => {
         );
 
         // Assert
-        expect(simplifiedResults).toBe(ruleConversionResults);
+        expect(simplifiedResults).toEqual(ruleConversionResults);
     });
 
     it("includes deduplicated rules and extension failures when the ESLint configuration extends", async () => {
@@ -76,13 +104,13 @@ describe("simplifyPackageRules", () => {
                 },
             ],
         ]);
-        const dependencies = {
+        const dependencies = createStubDependencies({
             removeExtendsDuplicatedRules: () => deduplicatedRules,
             retrieveExtendsValues: async () => ({
                 configurationErrors,
                 importedExtensions: [],
             }),
-        };
+        });
         const eslintExtends = ["extension-name"];
         const eslint = createStubESLintConfiguration(eslintExtends);
         const tslint = createStubTSLintConfiguration();
@@ -98,8 +126,9 @@ describe("simplifyPackageRules", () => {
 
         // Assert
         expect(simplifiedResults).toEqual({
+            ...ruleConversionResults,
             converted: deduplicatedRules,
-            extends: eslintExtends,
+            extends: [...eslintExtends],
             failed: configurationErrors,
         });
     });
diff --git a/src/creation/writeConversionResults.test.ts b/src/creation/writeConversionResults.test.ts
index 1ada480..95c66ff 100644
--- a/src/creation/writeConversionResults.test.ts
+++ b/src/creation/writeConversionResults.test.ts
@@ -1,11 +1,11 @@
 import { createEmptyConversionResults } from "../conversion/conversionResults.stubs";
-import { writeConversionResults } from "./writeConversionResults";
 import { AllOriginalConfigurations } from "../input/findOriginalConfigurations";
 import { formatJsonOutput } from "./formatting/formatters/formatJsonOutput";
-import { SimplifiedRuleConversionResults } from "./simplification/simplifyPackageRules";
+import { SimplifiedResultsConfiguration } from "./simplification/simplifyPackageRules";
+import { writeConversionResults } from "./writeConversionResults";
 
 const createStubOriginalConfigurations = (
-    overrides: Partial<AllOriginalConfigurations & SimplifiedRuleConversionResults> = {},
+    overrides: Partial<AllOriginalConfigurations & SimplifiedResultsConfiguration> = {},
 ) => ({
     tslint: {
         full: {
diff --git a/src/reporting/reportConversionResults.test.ts b/src/reporting/reportConversionResults.test.ts
index fb95250..75ca324 100644
--- a/src/reporting/reportConversionResults.test.ts
+++ b/src/reporting/reportConversionResults.test.ts
@@ -13,6 +13,8 @@ const createStubDependencies = (packageManager = PackageManager.Yarn) => {
     return { choosePackageManager, logger };
 };
 
+const basicExtends = ["eslint-config-prettier", "eslint-config-prettier/@typescript-eslint"];
+
 describe("reportConversionResults", () => {
     it("logs a successful conversion without notices when there is one converted rule without notices", async () => {
         // Arrange
@@ -27,6 +29,7 @@ describe("reportConversionResults", () => {
                     },
                 ],
             ]),
+            extends: basicExtends,
         });
 
         const { choosePackageManager, logger } = createStubDependencies();
@@ -43,8 +46,8 @@ describe("reportConversionResults", () => {
             logger.stdout.write,
             `✨ 1 rule replaced with its ESLint equivalent. ✨`,
             ``,
-            `⚡ 3 packages are required for running with ESLint. ⚡`,
-            `  yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint --dev`,
+            `⚡ 4 packages are required for this ESLint configuration. ⚡`,
+            `  yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-prettier --dev`,
         );
     });
 
@@ -62,6 +65,7 @@ describe("reportConversionResults", () => {
                     },
                 ],
             ]),
+            extends: basicExtends,
         });
 
         const { choosePackageManager, logger } = createStubDependencies();
@@ -80,8 +84,8 @@ describe("reportConversionResults", () => {
             `❗ 1 ESLint rule behaves differently from its TSLint counterpart ❗`,
             `  Check ${logger.debugFileName} for details.`,
             ``,
-            `⚡ 3 packages are required for running with ESLint. ⚡`,
-            `  yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint --dev`,
+            `⚡ 4 packages are required for this ESLint configuration. ⚡`,
+            `  yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-prettier --dev`,
         );
         expectEqualWrites(
             logger.info.write,
@@ -115,6 +119,7 @@ describe("reportConversionResults", () => {
                     },
                 ],
             ]),
+            extends: basicExtends,
         });
 
         const { choosePackageManager, logger } = createStubDependencies();
@@ -134,8 +139,8 @@ describe("reportConversionResults", () => {
             `❗ 2 ESLint rules behave differently from their TSLint counterparts ❗`,
             `  Check ${logger.debugFileName} for details.`,
             ``,
-            `⚡ 3 packages are required for running with ESLint. ⚡`,
-            `  yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint --dev`,
+            `⚡ 4 packages are required for this ESLint configuration. ⚡`,
+            `  yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-prettier --dev`,
         );
         expectEqualWrites(
             logger.info.write,
@@ -153,6 +158,7 @@ describe("reportConversionResults", () => {
         // Arrange
         const conversionResults = createEmptyConversionResults({
             failed: [{ getSummary: () => "It broke." }],
+            extends: basicExtends,
         });
 
         const { choosePackageManager, logger } = createStubDependencies();
@@ -175,6 +181,7 @@ describe("reportConversionResults", () => {
     it("logs failed conversions when there are multiple failed conversions", async () => {
         // Arrange
         const conversionResults = createEmptyConversionResults({
+            extends: basicExtends,
             failed: [{ getSummary: () => "It broke." }, { getSummary: () => "It really broke." }],
         });
 
@@ -198,6 +205,7 @@ describe("reportConversionResults", () => {
     it("logs a missing rule when there is a missing rule", async () => {
         // Arrange
         const conversionResults = createEmptyConversionResults({
+            extends: basicExtends,
             missing: [
                 {
                     ruleArguments: ["a", "b"],
@@ -223,8 +231,8 @@ describe("reportConversionResults", () => {
             `  The "@typescript-eslint/tslint/config" section of .eslintrc.js configures eslint-plugin-tslint to run it in TSLint within ESLint.`,
             `  Check ${logger.debugFileName} for details.`,
             ``,
-            `⚡ 4 packages are required for running with ESLint. ⚡`,
-            `  yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser @typescript-eslint/tslint-eslint-plugin eslint --dev`,
+            `⚡ 5 packages are required for this ESLint configuration. ⚡`,
+            `  yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser @typescript-eslint/tslint-eslint-plugin eslint eslint-config-prettier --dev`,
         );
         expectEqualWrites(
             logger.info.write,
@@ -236,6 +244,7 @@ describe("reportConversionResults", () => {
     it("logs missing rules when there are missing rules", async () => {
         // Arrange
         const conversionResults = createEmptyConversionResults({
+            extends: basicExtends,
             missing: [
                 {
                     ruleArguments: ["a", "b"],
@@ -266,8 +275,8 @@ describe("reportConversionResults", () => {
             `  The "@typescript-eslint/tslint/config" section of .eslintrc.js configures eslint-plugin-tslint to run them in TSLint within ESLint.`,
             `  Check ${logger.debugFileName} for details.`,
             ``,
-            `⚡ 4 packages are required for running with ESLint. ⚡`,
-            `  yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser @typescript-eslint/tslint-eslint-plugin eslint --dev`,
+            `⚡ 5 packages are required for this ESLint configuration. ⚡`,
+            `  yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser @typescript-eslint/tslint-eslint-plugin eslint eslint-config-prettier --dev`,
         );
         expectEqualWrites(
             logger.info.write,
@@ -280,6 +289,7 @@ describe("reportConversionResults", () => {
     it("logs missing plugins when there are missing plugins", async () => {
         // Arrange
         const conversionResults = createEmptyConversionResults({
+            extends: basicExtends,
             plugins: new Set(["plugin-one", "plugin-two"]),
         });
 
@@ -295,8 +305,35 @@ describe("reportConversionResults", () => {
         // Assert
         expectEqualWrites(
             logger.stdout.write,
-            `⚡ 5 packages are required for running with ESLint. ⚡`,
-            `  yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint plugin-one plugin-two --dev`,
+            `⚡ 6 packages are required for this ESLint configuration. ⚡`,
+            `  yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-prettier plugin-one plugin-two --dev`,
+        );
+    });
+
+    it("logs a Prettier recommendation when eslint-config-prettier isn't extended", async () => {
+        // Arrange
+        const conversionResults = createEmptyConversionResults({
+            extends: [],
+        });
+
+        const { choosePackageManager, logger } = createStubDependencies();
+
+        // Act
+        await reportConversionResults(
+            { choosePackageManager, logger },
+            ".eslintrc.js",
+            conversionResults,
+        );
+
+        // Assert
+        expectEqualWrites(
+            logger.stdout.write,
+            `⚡ 3 packages are required for this ESLint configuration. ⚡`,
+            `  yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint --dev`,
+            ``,
+            `☠ Prettier plugins are missing from your configuration. ☠`,
+            `  We highly recommend running tslint-to-eslint-config --prettier to disable formatting ESLint rules.`,
+            `  See https://github/typescript-eslint/tslint-to-eslint-config/docs/FAQs.md#should-i-use-prettier.`,
         );
     });
 });
diff --git a/src/reporting/reportOutputs.test.ts b/src/reporting/reportOutputs.test.ts
index 04464bb..4e7f01c 100644
--- a/src/reporting/reportOutputs.test.ts
+++ b/src/reporting/reportOutputs.test.ts
@@ -2,11 +2,15 @@ import { createStubLogger, expectEqualWrites } from "../adapters/logger.stubs";
 import { createEmptyConversionResults } from "../conversion/conversionResults.stubs";
 import { PackageManager } from "./packages/packageManagers";
 import { logMissingPackages } from "./reportOutputs";
+import { SimplifiedResultsConfiguration } from "../creation/simplification/simplifyPackageRules";
 
-const createStubDependencies = (packageManager: PackageManager) => ({
+const createStubDependencies = (
+    packageManager: PackageManager,
+    results?: Partial<SimplifiedResultsConfiguration>,
+) => ({
     logger: createStubLogger(),
     packageManager,
-    ruleConversionResults: createEmptyConversionResults(),
+    ruleConversionResults: createEmptyConversionResults(results),
 });
 
 describe("reportOutputs", () => {
@@ -23,7 +27,7 @@ describe("reportOutputs", () => {
         // Assert
         expectEqualWrites(
             logger.stdout.write,
-            `⚡ 3 packages are required for running with ESLint. ⚡`,
+            `⚡ 3 packages are required for this ESLint configuration. ⚡`,
             `  npm install @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint --save-dev`,
         );
     });
@@ -40,7 +44,7 @@ describe("reportOutputs", () => {
         // Assert
         expectEqualWrites(
             logger.stdout.write,
-            `⚡ 3 packages are required for running with ESLint. ⚡`,
+            `⚡ 3 packages are required for this ESLint configuration. ⚡`,
             `  pnpm add @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint --save-dev`,
         );
     });
@@ -57,8 +61,28 @@ describe("reportOutputs", () => {
         // Assert
         expectEqualWrites(
             logger.stdout.write,
-            `⚡ 3 packages are required for running with ESLint. ⚡`,
+            `⚡ 3 packages are required for this ESLint configuration. ⚡`,
             `  yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint --dev`,
         );
     });
+
+    it("adds extensions to the missing packages list when they exist", () => {
+        // Arrange
+        const { logger, packageManager, ruleConversionResults } = createStubDependencies(
+            PackageManager.Yarn,
+            {
+                extends: ["eslint-config-prettier", "eslint-config-prettier/@typescript-eslint"],
+            },
+        );
+
+        // Act
+        logMissingPackages(ruleConversionResults, packageManager, logger);
+
+        // Assert
+        expectEqualWrites(
+            logger.stdout.write,
+            `⚡ 4 packages are required for this ESLint configuration. ⚡`,
+            `  yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-prettier --dev`,
+        );
+    });
 });