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

Issue:
TypeError: rule.ruleArguments is not iterable
### 🐛 Bug Report

-   ESLint version: ^7.0.0
-   Node version: v12.13.1

#### Actual Behavior
When i try to run the command: npx tslint-to-eslint-config i get the following error message:

```console
$ npx tslint-to-eslint-config
npx: installed 56 in 8.341s
❌ 1 error running tslint-to-eslint: ❌
  TypeError: rule.ruleArguments is not iterable
    at formatConvertedRule (C:\Users\Lorand\AppData\Roaming\npm-cache\_npx\19572\node_modules\tslint-to-eslint-config\src\creation\formatConvertedRules.js:18:39)
    at Object.exports.formatConvertedRules (C:\Users\Lorand\AppData\Roaming\npm-cache\_npx\19572\node_modules\tslint-to-eslint-config\src\creation\formatConvertedRules.js:8:28)
    at exports.writeConversionResults (C:\Users\Lorand\AppData\Roaming\npm-cache\_npx\19572\node_modules\tslint-to-eslint-config\src\creation\writeConversionResults.js:26:39)
    at exports.convertConfig (C:\Users\Lorand\AppData\Roaming\npm-cache\_npx\19572\node_modules\tslint-to-eslint-config\src\conversion\convertConfig.js:19:47)
    at async tryConvertConfig (C:\Users\Lorand\AppData\Roaming\npm-cache\_npx\19572\node_modules\tslint-to-eslint-config\src\cli\runCli.js:44:18)
    at async Object.exports.runCli (C:\Users\Lorand\AppData\Roaming\npm-cache\_npx\19572\node_modules\tslint-to-eslint-config\src\cli\runCli.js:32:24)
    at async Object.exports.main (C:\Users\Lorand\AppData\Roaming\npm-cache\_npx\19572\node_modules\tslint-to-eslint-config\src\cli\main.js:127:30)
```

#### Expected Behavior

Run without error.

#### Reproduction

**tsconfig.json**
```
{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "module": "esnext",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "importHelpers": true,
    "target": "es2015",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2018",
      "dom"
    ]
  }
}
```
**package.json**
```
{
  "name": "movilitas.mobile-3.0",
  "version": "0.0.1",
  "author": "Ionic Framework",
  "homepage": "https://ionicframework.com/",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "cypress:open": "cypress open",
    "cypress:run": "wait-on http://localhost:8100/ & cypress run --browser chrome --headless"
  },
  "private": true,
  "dependencies": {
    "@angular/common": "~8.2.14",
    "@angular/compiler": "~8.2.14",
    "@angular/core": "^8.2.14",
    "@angular/forms": "~8.2.14",
    "@angular/platform-browser": "~8.2.14",
    "@angular/platform-browser-dynamic": "~8.2.14",
    "@angular/router": "~8.2.14",
    "@auth0/angular-jwt": "^3.0.1",
    "@capacitor/android": "^2.1.0",
    "@capacitor/core": "^2.1.0",
    "@capacitor/ios": "^2.1.0",
    "@ionic-native/app-version": "^5.25.0",
    "@ionic-native/barcode-scanner": "^5.25.0",
    "@ionic-native/call-number": "^5.25.0",
    "@ionic-native/core": "^5.25.0",
    "@ionic-native/native-audio": "^5.25.0",
    "@ionic-native/splash-screen": "^5.25.0",
    "@ionic-native/status-bar": "^5.25.0",
    "@ionic/angular": "^5.1.0",
    "@ionic/pwa-elements": "^1.5.2",
    "@ngx-translate/core": "^11.0.1",
    "@ngx-translate/http-loader": "^4.0.0",
    "call-number": "^1.0.1",
    "com-darryncampbell-cordova-plugin-intent": "^2.0.0",
    "cordova-plugin-app-version": "^0.1.9",
    "cordova-plugin-nativeaudio": "^3.0.9",
    "core-js": "^3.6.5",
    "phonegap-plugin-barcodescanner": "^8.1.0",
    "rxjs": "^6.5.5",
    "tslib": "^1.10.0",
    "zone.js": "^0.10.3"
  },
  "devDependencies": {
    "@angular-devkit/architect": "^0.803.26",
    "@angular-devkit/build-angular": "^0.803.25",
    "@angular-devkit/core": "^8.3.26",
    "@angular-devkit/schematics": "^8.3.26",
    "@angular/cli": "^8.3.26",
    "@angular/compiler-cli": "~8.2.14",
    "@angular/language-service": "~8.2.14",
    "@capacitor/cli": "^2.1.0",
    "@ionic/angular-toolkit": "^2.2.0",
    "@types/jasmine": "^3.5.10",
    "@types/jasminewd2": "~2.0.8",
    "@types/node": "^12.12.38",
    "@typescript-eslint/eslint-plugin": "^2.33.0",
    "@typescript-eslint/eslint-plugin-tslint": "^2.33.0",
    "@typescript-eslint/parser": "^2.33.0",
    "codelyzer": "^5.2.2",
    "cypress": "^4.5.0",
    "eslint": "^7.0.0",
    "eslint-config-airbnb-base": "^14.1.0",
    "eslint-plugin-cypress": "^2.10.3",
    "eslint-plugin-import": "^2.20.2",
    "jasmine-core": "~3.5.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~5.0.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage-istanbul-reporter": "~2.1.1",
    "karma-jasmine": "~3.0.1",
    "karma-jasmine-html-reporter": "^1.4.2",
    "protractor": "^5.4.4",
    "ts-node": "~8.3.0",
    "tslint": "~6.1.0",
    "typescript": "~3.8.3",
    "wait-on": "^5.0.0"
  },
  "description": "An Ionic project",
  "cordova": {
    "plugins": {
      "mx.ferreyra.callnumber": {},
      "com-darryncampbell-cordova-plugin-intent": {},
      "phonegap-plugin-barcodescanner": {},
      "cordova-plugin-nativeaudio": {},
      "cordova-plugin-app-version": {}
    }
  }
}
``` 


Pull request description:
Fixed crash when ESLint rule has multiple rule arguments
## PR Checklist

-   [x] Addresses an existing issue: fixes #581
-   [x] That issue was marked as [`status: accepting prs`](https://github.com/typescript-eslint/tslint-to-eslint-config/labels/status%3A%20accepting%20prs)

## Overview

I could only reproduce #581's crash for an existing `.eslintrc.js` containing a rule specifying multiple arguments:

```json
"@typescript-eslint/indent": [
    "error",
    4,
    {
        "ignoreComments": false
    }
],
```

The root issue was that `normalizeESLintRules` was converting the raw rules into an object with a single value in `ruleArguments`, not an array:

```ts
const [rawRuleSeverity, ruleArguments] =
    configuration instanceof Array ? configuration : [configuration, {}];
const ruleSeverity = normalizeRawESLintRuleSeverity(rawRuleSeverity);
```

```js
{
  ruleArguments: 4,
  ruleName: '@typescript-eslint/indent',
  ruleSeverity: 'error'
}
```

If `configuration` is an array, it should be `[configuration[0], configuration.slice(1)]` to capture the rule arguments as an array.

Patch:
diff --git a/package-lock.json b/package-lock.json
index 601a73b..573a69c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2854,9 +2854,9 @@
             }
         },
         "@types/jest": {
-            "version": "25.2.2",
-            "resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.2.2.tgz",
-            "integrity": "sha512-aRctFbG8Pb7DSLzUt/fEtL3q/GKb9mretFuYhRub2J0q6NhzBYbx9HTQzHrWgBNIxYOlxGNVe6Z54cpbUt+Few==",
+            "version": "25.2.3",
+            "resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.2.3.tgz",
+            "integrity": "sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw==",
             "dev": true,
             "requires": {
                 "jest-diff": "^25.2.1",
diff --git a/package.json b/package.json
index 4e396bb..d62aa9a 100644
--- a/package.json
+++ b/package.json
@@ -26,7 +26,7 @@
         "@babel/preset-typescript": "7.9.0",
         "@types/eslint-config-prettier": "6.11.0",
         "@types/glob": "7.1.1",
-        "@types/jest": "25.2.2",
+        "@types/jest": "25.2.3",
         "@types/node": "12.12.21",
         "@typescript-eslint/eslint-plugin": "2.34.0",
         "@typescript-eslint/parser": "2.34.0",
diff --git a/src/creation/summarization/normalizeESLintRules.ts b/src/creation/summarization/normalizeESLintRules.ts
index 7af8660..9c0855c 100644
--- a/src/creation/summarization/normalizeESLintRules.ts
+++ b/src/creation/summarization/normalizeESLintRules.ts
@@ -1,5 +1,8 @@
-import { ESLintConfigurationRules } from "../../input/findESLintConfiguration";
-import { ESLintRuleOptions } from "../../rules/types";
+import {
+    ESLintConfigurationRules,
+    ESLintConfigurationRuleValue,
+} from "../../input/findESLintConfiguration";
+import { ESLintRuleOptions, RawESLintRuleSeverity } from "../../rules/types";
 import { normalizeRawESLintRuleSeverity } from "../pruning/normalizeRawESLintRuleSeverity";
 
 /**
@@ -8,9 +11,8 @@ import { normalizeRawESLintRuleSeverity } from "../pruning/normalizeRawESLintRul
 export const normalizeESLintRules = (userRules: ESLintConfigurationRules | undefined) => {
     const output: Map<string, ESLintRuleOptions> = new Map();
 
-    for (const [ruleName, configuration] of Object.entries(userRules ?? {})) {
-        const [rawRuleSeverity, ruleArguments] =
-            configuration instanceof Array ? configuration : [configuration, {}];
+    for (const [ruleName, rawRuleValue] of Object.entries(userRules ?? {})) {
+        const [rawRuleSeverity, ruleArguments] = parseRawRuleValue(rawRuleValue);
         const ruleSeverity = normalizeRawESLintRuleSeverity(rawRuleSeverity);
 
         output.set(ruleName, { ruleArguments, ruleName, ruleSeverity });
@@ -18,3 +20,11 @@ export const normalizeESLintRules = (userRules: ESLintConfigurationRules | undef
 
     return output;
 };
+
+const parseRawRuleValue = (
+    configuration: ESLintConfigurationRuleValue,
+): [RawESLintRuleSeverity, any[]] => {
+    return configuration instanceof Array
+        ? [configuration[0], configuration.slice(1)]
+        : [configuration, [{}]];
+};
diff --git a/src/creation/writeConversionResults.ts b/src/creation/writeConversionResults.ts
index 5d24761..088df2c 100644
--- a/src/creation/writeConversionResults.ts
+++ b/src/creation/writeConversionResults.ts
@@ -34,10 +34,7 @@ export const writeConversionResults = async (
             sourceType: "module",
         },
         plugins,
-        rules: {
-            // ...trimESLintRules(eslint?.full.rules, summarizedResults.extensionRules),
-            ...formatConvertedRules(summarizedResults, tslint.full),
-        },
+        rules: formatConvertedRules(summarizedResults, tslint.full),
     });
 
     return await dependencies.fileSystem.writeFile(outputPath, formatOutput(outputPath, output));
diff --git a/src/input/findESLintConfiguration.ts b/src/input/findESLintConfiguration.ts
index 0442b07..5b301d2 100644
--- a/src/input/findESLintConfiguration.ts
+++ b/src/input/findESLintConfiguration.ts
@@ -21,8 +21,7 @@ export type ESLintConfigurationRules = {
 
 export type ESLintConfigurationRuleValue =
     | RawESLintRuleSeverity
-    | [RawESLintRuleSeverity]
-    | [RawESLintRuleSeverity, any];
+    | [RawESLintRuleSeverity, ...any[]];
 
 const defaultESLintConfiguration = {
     env: {},


Test patch:
diff --git a/src/creation/summarization/normalizeESLintRules.test.ts b/src/creation/summarization/normalizeESLintRules.test.ts
index ac793e4..a3779b7 100644
--- a/src/creation/summarization/normalizeESLintRules.test.ts
+++ b/src/creation/summarization/normalizeESLintRules.test.ts
@@ -15,6 +15,30 @@ describe("normalizeESLintRules", () => {
         expect(result).toEqual(new Map());
     });
 
+    it("converts a rule when given as a severity level", () => {
+        // Arrange
+        const userRules: ESLintConfigurationRules = {
+            [ruleName]: "error",
+        };
+
+        // Act
+        const result = normalizeESLintRules(userRules);
+
+        // Assert
+        expect(result).toEqual(
+            new Map([
+                [
+                    ruleName,
+                    {
+                        ruleArguments: [{}],
+                        ruleName: "rule-a",
+                        ruleSeverity: "error",
+                    },
+                ],
+            ]),
+        );
+    });
+
     it("converts a rule when given as an array", () => {
         // Arrange
         const userRules: ESLintConfigurationRules = {
@@ -30,7 +54,7 @@ describe("normalizeESLintRules", () => {
                 [
                     ruleName,
                     {
-                        ruleArguments: {},
+                        ruleArguments: [{}],
                         ruleName: "rule-a",
                         ruleSeverity: "error",
                     },
@@ -39,10 +63,10 @@ describe("normalizeESLintRules", () => {
         );
     });
 
-    it("converts a rule when given as a severity level", () => {
+    it("converts a rule when given as an array with multiple arguments", () => {
         // Arrange
         const userRules: ESLintConfigurationRules = {
-            [ruleName]: "error",
+            [ruleName]: ["error", 4, { value: true }],
         };
 
         // Act
@@ -54,7 +78,7 @@ describe("normalizeESLintRules", () => {
                 [
                     ruleName,
                     {
-                        ruleArguments: {},
+                        ruleArguments: [4, { value: true }],
                         ruleName: "rule-a",
                         ruleSeverity: "error",
                     },