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

Issue:
Print rule notices in result reporting
Rule converters may contain a `notices` member intended to message users about behavior changes from TSLint to ESLint. For example, `no-arg`'s converter returns a notice about `arguments.callee` also being banned.

Unfortunately, there's nothing _(yet!)_ that prints these notices in the CLI. `reportConversionResults` should have code added to also print out, for each resultant ESLint rule with notices, the notices under it.

Maybe the output could look something like this?

```
📢 2 ESLint rules behave differently from their TSLint counterparts: 📢
    * no-caller:
        - `arguments.callee` will now also be banned.
    * no-invalid-this:
        - Functions in methods will no longer be ignored.
```

Pull request:
Print Rule Notices
## PR Checklist

-   [x] Addresses an existing issue: fixes #7 
-   [x] That issue was marked as [`status: accepting prs`](https://github.com/JoshuaKGoldberg/tslint-to-eslint-config/labels/status%3A%20accepting%20prs)

## Overview
This PR updates `reportConversionResults` to print out rule notices.


Patch:
diff --git a/src/reporting/reportConversionResults.ts b/src/reporting/reportConversionResults.ts
index 4ff8dce..f2a8518 100644
--- a/src/reporting/reportConversionResults.ts
+++ b/src/reporting/reportConversionResults.ts
@@ -17,6 +17,7 @@ export const reportConversionResults = (
 ) => {
     if (ruleConversionResults.converted.size !== 0) {
         logSuccessfulConversions(ruleConversionResults.converted, dependencies.logger);
+        logNotices(ruleConversionResults.converted, dependencies.logger);
     }
 
     if (ruleConversionResults.failed.length !== 0) {
@@ -93,3 +94,31 @@ const logMissingPlugins = (plugins: Set<string>, logger: Logger) => {
             .join(""),
     );
 };
+
+interface RuleWithNotices {
+    notices: any[];
+    ruleName: string;
+}
+
+const logNotices = (converted: Map<string, ESLintRuleOptions>, logger: Logger) => {
+    const rulesWithNotices = Array.from(converted.values()).filter(
+        ruleOptions => ruleOptions.notices && ruleOptions.notices.length >= 1,
+    ) as RuleWithNotices[];
+
+    if (rulesWithNotices.length > 0) {
+        logger.stdout.write(chalk.yellowBright(`📢 ${rulesWithNotices.length} ESLint`));
+        logger.stdout.write(
+            chalk.yellowBright(rulesWithNotices.length == 1 ? ` rule behaves` : ` rules behave`),
+        );
+        logger.stdout.write(
+            chalk.yellowBright(` differently from their TSLint counterparts: 📢${EOL}`),
+        );
+
+        rulesWithNotices.forEach(rule => {
+            logger.stdout.write(chalk.yellow(`* ${rule.ruleName}:${EOL}`));
+            rule.notices.forEach(notice => {
+                logger.stdout.write(chalk.yellow(`  - ${notice}${EOL}`));
+            });
+        });
+    }
+};
diff --git a/src/rules/convertRules.ts b/src/rules/convertRules.ts
index 31fef3f..8090c5a 100644
--- a/src/rules/convertRules.ts
+++ b/src/rules/convertRules.ts
@@ -69,12 +69,16 @@ export const convertRules = (
                     ),
                 );
             } else {
+                const existingNotices = existingConversion.notices || [];
+                const newNotices = newConversion.notices || [];
+
                 converted.set(changes.ruleName, {
                     ...existingConversion,
                     ruleArguments: merger(
                         existingConversion.ruleArguments,
                         newConversion.ruleArguments,
                     ),
+                    notices: [...existingNotices, ...newNotices],
                 });
             }
         }
diff --git a/src/rules/converter.ts b/src/rules/converter.ts
index 7acee8f..396d32c 100644
--- a/src/rules/converter.ts
+++ b/src/rules/converter.ts
@@ -37,6 +37,11 @@ export type ConversionResult = {
  * An ESLint rule equivalent to a previously enabled TSLint rule.
  */
 export type ConvertedRuleChanges = {
+    /**
+     * Any notices associated with that ESLint rule.
+     */
+    notices?: string[];
+
     /**
      * Any arguments for that ESLint rule.
      */
diff --git a/src/rules/types.ts b/src/rules/types.ts
index f597580..791d594 100644
--- a/src/rules/types.ts
+++ b/src/rules/types.ts
@@ -9,6 +9,7 @@ export type TSLintRuleOptions = {
 export type ESLintRuleSeverity = "warn" | "error" | "off";
 
 export type ESLintRuleOptions = {
+    notices?: any[];
     ruleArguments?: any[];
     ruleName: string;
     ruleSeverity: ESLintRuleSeverity;


Test patch:
diff --git a/src/reporting/reportConversionResults.test.ts b/src/reporting/reportConversionResults.test.ts
index bbd335d..405b0d6 100644
--- a/src/reporting/reportConversionResults.test.ts
+++ b/src/reporting/reportConversionResults.test.ts
@@ -1,3 +1,4 @@
+import { EOL } from "os";
 import { ESLintRuleOptions } from "../rules/types";
 import { reportConversionResults } from "./reportConversionResults";
 import { createStubLogger, expectEqualWrites } from "../adapters/logger.stubs";
@@ -13,6 +14,7 @@ describe("reportConversionResults", () => {
                 [
                     "tslint-rule-one",
                     {
+                        notices: ["1", "2"],
                         ruleArguments: ["a", "b"],
                         ruleName: "tslint-rule-one",
                         ruleSeverity: "error",
@@ -27,7 +29,14 @@ describe("reportConversionResults", () => {
         reportConversionResults({ logger }, conversionResults);
 
         // Assert
-        expectEqualWrites(logger.stdout.write, "✨ 1 rule replaced with its ESLint equivalent. ✨");
+        expectEqualWrites(
+            logger.stdout.write,
+            `✨ 1 rule replaced with its ESLint equivalent. ✨${EOL}` +
+                `📢 1 ESLint rule behaves differently from their TSLint counterparts: 📢${EOL}` +
+                `* tslint-rule-one:${EOL}` +
+                `  - 1${EOL}` +
+                `  - 2${EOL}`,
+        );
     });
 
     it("logs successful conversions when there are multiple converted rules", () => {
@@ -37,6 +46,7 @@ describe("reportConversionResults", () => {
                 [
                     "tslint-rule-one",
                     {
+                        notices: ["1", "2"],
                         ruleArguments: ["a", "b"],
                         ruleName: "tslint-rule-one",
                         ruleSeverity: "error",
@@ -45,6 +55,7 @@ describe("reportConversionResults", () => {
                 [
                     "tslint-rule-two",
                     {
+                        notices: ["3", "4"],
                         ruleArguments: ["c", "d"],
                         ruleName: "tslint-rule-two",
                         ruleSeverity: "warn",
@@ -61,7 +72,14 @@ describe("reportConversionResults", () => {
         // Assert
         expectEqualWrites(
             logger.stdout.write,
-            "✨ 2 rules replaced with their ESLint equivalents. ✨",
+            `✨ 2 rules replaced with their ESLint equivalents. ✨${EOL}` +
+                `📢 2 ESLint rules behave differently from their TSLint counterparts: 📢${EOL}` +
+                `* tslint-rule-one:${EOL}` +
+                `  - 1${EOL}` +
+                `  - 2${EOL}` +
+                `* tslint-rule-two:${EOL}` +
+                `  - 3${EOL}` +
+                `  - 4${EOL}`,
         );
     });
 
diff --git a/src/rules/convertRules.test.ts b/src/rules/convertRules.test.ts
index bb11089..20aa66b 100644
--- a/src/rules/convertRules.test.ts
+++ b/src/rules/convertRules.test.ts
@@ -176,6 +176,7 @@ describe("convertRules", () => {
                         ruleArguments: mergedArguments,
                         ruleName: "eslint-rule-a",
                         ruleSeverity: "error",
+                        notices: [],
                     },
                 ],
             ]),