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

Issue:
eslint-disable directives require C-style comments
<!--
👋 Hi, thanks for filing an issue on tslint-to-eslint-config! 💖
Please fill out all fields below to ensure your issue is addressed.

If your issue doesn't provide enough info to fully explain or reproduce your bug, it will be closed. 😦
-->

### 🐛 Bug Report

-   `tslint-to-eslint-config` version: 1.0.0
-   ESLint version: v7.5.0
-   Node version: v12.16.3

#### Actual Behavior

I had some code like this:

```ts
// See https://github.com/mikolalysenko/hash-int
export function hashInt(x: number) {
  // tslint:disable:no-bitwise
  const HASH = new Uint32Array(1);
  HASH[0] = x | 0;
  HASH[0] -= HASH[0] << 6;
  HASH[0] ^= HASH[0] >>> 17;
  HASH[0] -= HASH[0] << 9;
  HASH[0] ^= HASH[0] << 4;
  HASH[0] -= HASH[0] << 3;
  HASH[0] ^= HASH[0] << 10;
  HASH[0] ^= HASH[0] >>> 15;
  return HASH[0];
  // tslint:enable
}
```

When I migrate with `--comments`, this becomes:

```ts
export function hashInt(x: number) {
  // eslint-disable no-bitwise
  const HASH = new Uint32Array(1);
  HASH[0] = x | 0;
  HASH[0] -= HASH[0] << 6;
  HASH[0] ^= HASH[0] >>> 17;
  HASH[0] -= HASH[0] << 9;
  HASH[0] ^= HASH[0] << 4;
  HASH[0] -= HASH[0] << 3;
  HASH[0] ^= HASH[0] << 10;
  HASH[0] ^= HASH[0] >>> 15;
  return HASH[0];
  // eslint-enable
}
```

Unfortunately this allows all the no-bitwise errors to surface in eslint.

#### Expected Behavior

It seems that eslint requires C-style comments for eslint-disable directives:

```ts
export function hashInt(x: number) {
  /* eslint-disable no-bitwise */
  const HASH = new Uint32Array(1);
  HASH[0] = x | 0;
  HASH[0] -= HASH[0] << 6;
  HASH[0] ^= HASH[0] >>> 17;
  HASH[0] -= HASH[0] << 9;
  HASH[0] ^= HASH[0] << 4;
  HASH[0] -= HASH[0] << 3;
  HASH[0] ^= HASH[0] << 10;
  HASH[0] ^= HASH[0] >>> 15;
  return HASH[0];
  /* eslint-enable */
}
```

Note that this _doesn't_ seem to be the case for `eslint-disable-line` or `eslint-disable-next-line`.

#### Reproduction

<!--
Please paste a link to a repository, Gist, or other means of reproducing your error here.
Note that `tslint-to-eslint-config` includes information from `package.json`, `tsconfig.json`, and other files, so just your ESLint configuration may not be enough.
-->


Pull request description:
fix: do not convert next-line to line for comments
<!--
👋 Hi, thanks for sending a PR to tslint-to-eslint-config! 💖
Please fill out all fields below to ensure your PR is reviewed quickly.
-->

## PR Checklist

-   [x] Addresses an existing issue: fixes #645
-   [x] That issue was marked as [`status: accepting prs`](https://github.com/typescript-eslint/tslint-to-eslint-config/labels/status%3A%20accepting%20prs)

## Overview

<!-- Brief description of what is changed and how the code change does that. -->

This fixes the comment conversion for the case `// tslint:disable-next-line` -> `// eslint-disable-next-line` and also correct the tests for the case `/* tslint:enable */` -> `/* eslint-enable */`.

~By the way, wasn't able to reproduce the linked issue, for some reason it works on the test and my sample repo~ 🤔 

Also fixes the conversion of `// tslint:disable` and `// tslint:enable` cases into `/* eslint-disable */` and `/* eslint-enable */`.

Patch:
diff --git a/README.md b/README.md
index cdda729..577d0fb 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,7 @@ Each of these flags is optional:
 #### `comments`
 
 ```shell
-npx tslint-to-eslint-config --comments src/**/*.ts
+npx tslint-to-eslint-config --comments 'src/**/*.ts'
 ```
 
 _Default: none_
diff --git a/package-lock.json b/package-lock.json
index e2cb7fc..00580d1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3756,9 +3756,9 @@
             }
         },
         "@types/jest": {
-            "version": "26.0.6",
-            "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.6.tgz",
-            "integrity": "sha512-XhJCNf+oPyA90jyPhOT3yZo6xR8R7auyUAibzRewPwbOb649AOPXX/JSjbafcbxJrxs/+BymBmHSETM8VpGMOw==",
+            "version": "26.0.7",
+            "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.7.tgz",
+            "integrity": "sha512-+x0077/LoN6MjqBcVOe1y9dpryWnfDZ+Xfo3EqGeBcfPRJlQp3Lw62RvNlWxuGv7kOEwlHriAa54updi3Jvvwg==",
             "dev": true,
             "requires": {
                 "jest-diff": "^25.2.1",
diff --git a/package.json b/package.json
index 7e6eaca..aa3eb46 100644
--- a/package.json
+++ b/package.json
@@ -26,7 +26,7 @@
         "@babel/preset-typescript": "7.10.4",
         "@types/eslint-config-prettier": "6.11.0",
         "@types/glob": "7.1.3",
-        "@types/jest": "26.0.6",
+        "@types/jest": "26.0.7",
         "@types/node": "12.12.21",
         "@typescript-eslint/eslint-plugin": "3.7.0",
         "@typescript-eslint/parser": "3.7.0",
diff --git a/src/comments/replaceFileComments.ts b/src/comments/replaceFileComments.ts
index 81835b1..7ffd856 100644
--- a/src/comments/replaceFileComments.ts
+++ b/src/comments/replaceFileComments.ts
@@ -29,12 +29,12 @@ export const replaceFileComments = (
     };
 
     for (const comment of [...comments].reverse()) {
-        const directive = comment.directive
-            .replace("tslint:", "eslint-")
-            .replace("next-line", "line");
+        const directive = comment.directive.replace("tslint:", "eslint-");
         const ruleLists = uniqueFromSources(comment.ruleNames.map(getNewRuleLists)).filter(Boolean);
         const [left, right] =
-            comment.commentKind === ts.SyntaxKind.SingleLineCommentTrivia
+            comment.commentKind === ts.SyntaxKind.SingleLineCommentTrivia &&
+            comment.directive !== "tslint:disable" &&
+            comment.directive !== "tslint:enable"
                 ? ["// ", ""]
                 : ["/* ", " */"];
 
diff --git a/src/rules/converters/eslint-plugin-react/jsx-key.ts b/src/rules/converters/eslint-plugin-react/jsx-key.ts
new file mode 100644
index 0000000..bf61910
--- /dev/null
+++ b/src/rules/converters/eslint-plugin-react/jsx-key.ts
@@ -0,0 +1,12 @@
+import { RuleConverter } from "../../converter";
+
+export const convertJsxKey: RuleConverter = () => {
+    return {
+        rules: [
+            {
+                ruleName: "react/jsx-key",
+            },
+        ],
+        plugins: ["eslint-plugin-react"],
+    };
+};
diff --git a/src/rules/rulesConverters.ts b/src/rules/rulesConverters.ts
index 6e7423d..dea85ee 100644
--- a/src/rules/rulesConverters.ts
+++ b/src/rules/rulesConverters.ts
@@ -174,6 +174,7 @@ import { convertUsePipeTransformInterface } from "./converters/codelyzer/use-pip
 
 // ESLint-React converters
 import { convertJsxBooleanValue } from "./converters/eslint-plugin-react/jsx-boolean-value";
+import { convertJsxKey } from "./converters/eslint-plugin-react/jsx-key";
 
 /**
  * Keys TSLint rule names to their ESLint rule converters.
@@ -212,6 +213,7 @@ export const rulesConverters = new Map([
     ["interface-over-type-literal", convertInterfaceOverTypeLiteral],
     ["jsdoc-format", convertJSDocFormat],
     ["jsx-boolean-value", convertJsxBooleanValue],
+    ["jsx-key", convertJsxKey],
     ["label-position", convertLabelPosition],
     ["linebreak-style", convertLinebreakStyle],
     ["max-classes-per-file", convertMaxClassesPerFile],


Test patch:
diff --git a/src/comments/convertFileComments.test.ts b/src/comments/convertFileComments.test.ts
index 244eb87..808a25f 100644
--- a/src/comments/convertFileComments.test.ts
+++ b/src/comments/convertFileComments.test.ts
@@ -57,14 +57,16 @@ export const b = true;
 // tslint:enable
 export const c = true;
 
+export const d = true; // tslint:disable-line
+
 /* tslint:disable */
-export const d = true;
+export const e = true;
 
 /* tslint:disable-next-line */
-export const e = true;
+export const f = true;
 
 /* tslint:enable */
-export const f = true;
+export const g = true;
 `);
 
         // Act
@@ -74,23 +76,25 @@ export const f = true;
         expect(dependencies.fileSystem.writeFile).toHaveBeenCalledWith(
             stubFileName,
             `
-// eslint-disable
+/* eslint-disable */
 export const a = true;
 
-// eslint-disable-line
+// eslint-disable-next-line
 export const b = true;
 
-// eslint-enable
+/* eslint-enable */
 export const c = true;
 
-/* eslint-disable */
-export const d = true;
+export const d = true; // eslint-disable-line
 
-/* eslint-disable-line */
+/* eslint-disable */
 export const e = true;
 
-/* eslint-enable */
+/* eslint-disable-next-line */
 export const f = true;
+
+/* eslint-enable */
+export const g = true;
 `,
         );
     });
@@ -98,7 +102,7 @@ export const f = true;
     it("parses rule names when they exist", async () => {
         // Arrange
         const dependencies = createStubDependencies(`
-// tslint:disable:ts-a
+/* tslint:disable:ts-a */
 export const a = true;
 
 // tslint:disable-next-line: ts-a ts-b
@@ -112,10 +116,10 @@ export const b = true;
         expect(dependencies.fileSystem.writeFile).toHaveBeenCalledWith(
             stubFileName,
             `
-// eslint-disable es-a
+/* eslint-disable es-a */
 export const a = true;
 
-// eslint-disable-line es-a, es-b1, es-b2
+// eslint-disable-next-line es-a, es-b1, es-b2
 export const b = true;
 `,
         );
@@ -124,7 +128,7 @@ export const b = true;
     it("re-uses a rule conversion from cache when it was already converted", async () => {
         // Arrange
         const dependencies = createStubDependencies(`
-// tslint:disable:ts-a
+/* tslint:disable:ts-a */
 export const a = true;
 `);
 
@@ -135,7 +139,7 @@ export const a = true;
         expect(dependencies.fileSystem.writeFile).toHaveBeenCalledWith(
             stubFileName,
             `
-// eslint-disable es-cached
+/* eslint-disable es-cached */
 export const a = true;
 `,
         );
@@ -144,7 +148,7 @@ export const a = true;
     it("ignores comment text when there is no matching converter", async () => {
         // Arrange
         const dependencies = createStubDependencies(`
-// tslint:disable:ts-z
+/* tslint:disable:ts-z */
 export const a = true;
 `);
 
@@ -155,7 +159,7 @@ export const a = true;
         expect(dependencies.fileSystem.writeFile).toHaveBeenCalledWith(
             stubFileName,
             `
-// eslint-disable
+/* eslint-disable */
 export const a = true;
 `,
         );
@@ -164,7 +168,7 @@ export const a = true;
     it("ignores comment text when its matching converter results in an error", async () => {
         // Arrange
         const dependencies = createStubDependencies(`
-// tslint:disable:ts-error
+/* tslint:disable:ts-error */
 export const a = true;
 `);
 
@@ -175,7 +179,7 @@ export const a = true;
         expect(dependencies.fileSystem.writeFile).toHaveBeenCalledWith(
             stubFileName,
             `
-// eslint-disable
+/* eslint-disable */
 export const a = true;
 `,
         );
diff --git a/src/rules/converters/eslint-plugin-react/tests/jsx-key.test.ts b/src/rules/converters/eslint-plugin-react/tests/jsx-key.test.ts
new file mode 100644
index 0000000..8a80048
--- /dev/null
+++ b/src/rules/converters/eslint-plugin-react/tests/jsx-key.test.ts
@@ -0,0 +1,18 @@
+import { convertJsxKey } from "../jsx-key";
+
+describe(convertJsxKey, () => {
+    test("conversion without arguments", () => {
+        const result = convertJsxKey({
+            ruleArguments: [],
+        });
+
+        expect(result).toEqual({
+            rules: [
+                {
+                    ruleName: "react/jsx-key",
+                },
+            ],
+            plugins: ["eslint-plugin-react"],
+        });
+    });
+});