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

Issue:
TypeError: Cannot read property 'length' of undefined
<!--
👋 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: 0.1.7
-   ESLint version: n/a
-   Node version: v11.15.0

#### Actual Behavior

```
❌ 1 error running tslint-to-eslint: ❌
TypeError: Cannot read property 'length' of undefined
    at formatRuleArguments (/usr/local/share/powershell/Modules/nvm/2.3.0/vs/v11.15.0/lib/node_modules/tslint-to-eslint-config/src/creation/formatMissingRules.js:19:28)
    at Object.exports.formatMissingRules (/usr/local/share/powershell/Modules/nvm/2.3.0/vs/v11.15.0/lib/node_modules/tslint-to-eslint-config/src/creation/formatMissingRules.js:7:36)
    at Object.exports.formatConvertedRules (/usr/local/share/powershell/Modules/nvm/2.3.0/vs/v11.15.0/lib/node_modules/tslint-to-eslint-config/src/creation/formatConvertedRules.js:11:75)
    at exports.writeConversionResults (/usr/local/share/powershell/Modules/nvm/2.3.0/vs/v11.15.0/lib/node_modules/tslint-to-eslint-config/src/creation/writeConversionResults.js:18:39)
    at exports.convertConfig (/usr/local/share/powershell/Modules/nvm/2.3.0/vs/v11.15.0/lib/node_modules/tslint-to-eslint-config/src/conversion/convertConfig.js:10:24)
    at processTicksAndRejections (internal/process/task_queues.js:86:5)
```
#### Expected Behavior

Not throw error

#### 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.
-->

Ran it in this repo https://github.com/sourcegraph/eslint-config referencing the tslint.json from https://github.com/sourcegraph/tslint-config


Pull request description:
Fixed .length errors in formatMissingRules
## PR Checklist

-   [x] Addresses an existing issue: fixes #69. _Nice_.
-   [x] That issue was marked as [`status: accepting prs`](https://github.com/JoshuaKGoldberg/tslint-to-eslint-config/labels/status%3A%20accepting%20prs)

## Overview
Two problems:\n* It's `rulesDirectory`, not `ruleDirectories`...

`tslint.json` rule values that didn't provide `ruleArguments` weren't being given `[]` as defaults when being parsed. Added a unit test for this.


Patch:
diff --git a/src/adapters/fsFileSystem.ts b/src/adapters/fsFileSystem.ts
index 0eb448a..9740bcd 100644
--- a/src/adapters/fsFileSystem.ts
+++ b/src/adapters/fsFileSystem.ts
@@ -1,7 +1,11 @@
 import * as fs from "fs";
+import { promisify } from "util";
 
 import { FileSystem } from "./fileSystem";
 
+const readFile = promisify(fs.readFile);
+const writeFile = promisify(fs.writeFile);
+
 export const fsFileSystem: FileSystem = {
     fileExists: async (filePath: string) => {
         try {
@@ -12,14 +16,14 @@ export const fsFileSystem: FileSystem = {
     },
     readFile: async (filePath: string) => {
         try {
-            return (await fs.promises.readFile(filePath)).toString();
+            return (await readFile(filePath)).toString();
         } catch (error) {
             return error;
         }
     },
     writeFile: async (filePath: string, contents: string) => {
         try {
-            return fs.promises.writeFile(filePath, contents);
+            return writeFile(filePath, contents);
         } catch (error) {
             return error;
         }
diff --git a/src/creation/formatConvertedRules.ts b/src/creation/formatConvertedRules.ts
index 8836410..1b63dca 100644
--- a/src/creation/formatConvertedRules.ts
+++ b/src/creation/formatConvertedRules.ts
@@ -19,7 +19,7 @@ export const formatConvertedRules = (
     if (conversionResults.missing.length !== 0) {
         output["@typescript-eslint/tslint/config"] = formatMissingRules(
             conversionResults.missing,
-            tslintConfiguration.ruleDirectories,
+            tslintConfiguration.rulesDirectory,
         );
     }
 
diff --git a/src/creation/formatMissingRules.ts b/src/creation/formatMissingRules.ts
index 1bc924c..4497abd 100644
--- a/src/creation/formatMissingRules.ts
+++ b/src/creation/formatMissingRules.ts
@@ -1,6 +1,6 @@
 import { TSLintRuleOptions } from "../rules/types";
 
-export const formatMissingRules = (missing: TSLintRuleOptions[], ruleDirectories: string[]) => {
+export const formatMissingRules = (missing: TSLintRuleOptions[], rulesDirectory: string[]) => {
     const rules: { [i: string]: unknown } = {};
 
     for (const rule of missing.sort((a, b) => a.ruleName.localeCompare(b.ruleName))) {
@@ -12,7 +12,7 @@ export const formatMissingRules = (missing: TSLintRuleOptions[], ruleDirectories
     return [
         "error",
         {
-            ...(ruleDirectories.length !== 0 && { ruleDirectories }),
+            ...(rulesDirectory.length !== 0 && { rulesDirectory }),
             rules,
         },
     ];
diff --git a/src/input/findTSLintConfiguration.ts b/src/input/findTSLintConfiguration.ts
index 87e92e5..129be08 100644
--- a/src/input/findTSLintConfiguration.ts
+++ b/src/input/findTSLintConfiguration.ts
@@ -2,7 +2,7 @@ import { Exec } from "../adapters/exec";
 import { findConfiguration } from "./findConfiguration";
 
 export type TSLintConfiguration = {
-    ruleDirectories: string[];
+    rulesDirectory: string[];
     rules: TSLintConfigurationRules;
 };
 
@@ -11,7 +11,7 @@ export type TSLintConfigurationRules = {
 };
 
 const defaultTSLintConfiguration = {
-    ruleDirectories: [],
+    rulesDirectory: [],
     rules: {},
 };
 
diff --git a/src/rules/convertRules.ts b/src/rules/convertRules.ts
index 0c29e7c..d5bf176 100644
--- a/src/rules/convertRules.ts
+++ b/src/rules/convertRules.ts
@@ -1,10 +1,11 @@
 import { TSLintConfigurationRules } from "../input/findTSLintConfiguration";
 import { ConversionError } from "./conversionError";
+import { RuleConverter } from "./converter";
 import { convertRule } from "./convertRule";
 import { convertRuleSeverity } from "./convertRuleSeverity";
-import { TSLintRuleOptions, ESLintRuleOptions } from "./types";
-import { RuleConverter } from "./converter";
+import { formatRawTslintRule } from "./formatRawTslintRule";
 import { RuleMerger } from "./merger";
+import { TSLintRuleOptions, ESLintRuleOptions } from "./types";
 
 export type ConvertRulesDependencies = {
     converters: Map<string, RuleConverter>;
@@ -28,11 +29,9 @@ export const convertRules = (
     const packages = new Set<string>();
 
     for (const [ruleName, value] of Object.entries(rawTslintRules)) {
-        const tslintRule = {
-            ruleName,
-            ...value,
-        };
+        const tslintRule = formatRawTslintRule(ruleName, value);
         const conversion = convertRule(tslintRule, dependencies.converters);
+
         if (conversion === undefined) {
             if (tslintRule.ruleSeverity !== "off") {
                 missing.push(tslintRule);
diff --git a/src/rules/formatRawTslintRule.ts b/src/rules/formatRawTslintRule.ts
new file mode 100644
index 0000000..a792117
--- /dev/null
+++ b/src/rules/formatRawTslintRule.ts
@@ -0,0 +1,11 @@
+import { TSLintRuleOptions } from "./types";
+
+export const formatRawTslintRule = (
+    ruleName: string,
+    value: Partial<TSLintRuleOptions>,
+): TSLintRuleOptions => ({
+    ruleArguments: [],
+    ruleName,
+    ruleSeverity: "error",
+    ...value,
+});


Test patch:
diff --git a/src/conversion/convertConfig.test.ts b/src/conversion/convertConfig.test.ts
index bb603d9..8a8718f 100644
--- a/src/conversion/convertConfig.test.ts
+++ b/src/conversion/convertConfig.test.ts
@@ -14,7 +14,7 @@ const createStubDependencies = (
 const createStubOriginalConfigurationsData = () => ({
     tslint: {
         rules: [],
-        ruleDirectories: [],
+        rulesDirectory: [],
     },
 });
 
diff --git a/src/creation/formatConvertedRules.test.ts b/src/creation/formatConvertedRules.test.ts
index 77ac18b..fcee498 100644
--- a/src/creation/formatConvertedRules.test.ts
+++ b/src/creation/formatConvertedRules.test.ts
@@ -2,7 +2,7 @@ import { createEmptyConversionResults } from "../conversion/conversionResults.st
 import { formatConvertedRules } from "./formatConvertedRules";
 
 const originalConfiguration = {
-    ruleDirectories: [],
+    rulesDirectory: [],
     rules: {},
 };
 
diff --git a/src/creation/formatMissingRules.test.ts b/src/creation/formatMissingRules.test.ts
index 2baf620..21913b2 100644
--- a/src/creation/formatMissingRules.test.ts
+++ b/src/creation/formatMissingRules.test.ts
@@ -111,7 +111,7 @@ describe("formatMissingRules", () => {
 
     it("includes rule directories when there are rule directories", () => {
         // Arrange
-        const ruleDirectories = ["./path/to/rules"];
+        const rulesDirectory = ["./path/to/rules"];
         const missing: TSLintRuleOptions[] = [
             {
                 ruleArguments: [],
@@ -121,13 +121,13 @@ describe("formatMissingRules", () => {
         ];
 
         // Act
-        const output = formatMissingRules(missing, ruleDirectories);
+        const output = formatMissingRules(missing, rulesDirectory);
 
         // Assert
         expect(output).toEqual([
             "error",
             {
-                ruleDirectories,
+                rulesDirectory,
                 rules: {
                     "tslint-rule-a": true,
                 },
diff --git a/src/creation/writeConversionResults.test.ts b/src/creation/writeConversionResults.test.ts
index 303079a..65258c1 100644
--- a/src/creation/writeConversionResults.test.ts
+++ b/src/creation/writeConversionResults.test.ts
@@ -3,7 +3,7 @@ import { writeConversionResults } from "./writeConversionResults";
 
 const originalConfigurations = {
     tslint: {
-        ruleDirectories: [],
+        rulesDirectory: [],
         rules: {},
     },
 };
diff --git a/src/input/findOriginalConfigurations.test.ts b/src/input/findOriginalConfigurations.test.ts
index 71ca157..c2a36a8 100644
--- a/src/input/findOriginalConfigurations.test.ts
+++ b/src/input/findOriginalConfigurations.test.ts
@@ -20,7 +20,7 @@ const createDependencies = (overrides: Partial<FindOriginalConfigurationsDepende
         devDependencies: {},
     }),
     findTSLintConfiguration: async () => ({
-        ruleDirectories: [],
+        rulesDirectory: [],
         rules: {},
     }),
     findTypeScriptConfiguration: async () => ({
@@ -64,7 +64,7 @@ describe("findOriginalConfigurations", () => {
         expect(result).toEqual({
             data: {
                 tslint: {
-                    ruleDirectories: [],
+                    rulesDirectory: [],
                     rules: {},
                 },
             },
@@ -91,7 +91,7 @@ describe("findOriginalConfigurations", () => {
                     devDependencies: {},
                 },
                 tslint: {
-                    ruleDirectories: [],
+                    rulesDirectory: [],
                     rules: {},
                 },
                 typescript: {
diff --git a/src/input/findTslintConfiguration.test.ts b/src/input/findTslintConfiguration.test.ts
index ccccf61..5e74550 100644
--- a/src/input/findTslintConfiguration.test.ts
+++ b/src/input/findTslintConfiguration.test.ts
@@ -53,7 +53,7 @@ describe("findTSLintConfiguration", () => {
 
         // Assert
         expect(result).toEqual({
-            ruleDirectories: [],
+            rulesDirectory: [],
             rules: {},
         });
     });
diff --git a/src/rules/formatRawTslintRule.test.ts b/src/rules/formatRawTslintRule.test.ts
new file mode 100644
index 0000000..2dd8f06
--- /dev/null
+++ b/src/rules/formatRawTslintRule.test.ts
@@ -0,0 +1,18 @@
+import { formatRawTslintRule } from "./formatRawTslintRule";
+
+describe("formatRawTslintRule", () => {
+    it("supplies default values when none are provided", () => {
+        // Arrange
+        const ruleName = "tslint-rule";
+
+        // Act
+        const formatted = formatRawTslintRule(ruleName, {});
+
+        // Assert
+        expect(formatted).toEqual({
+            ruleArguments: [],
+            ruleName,
+            ruleSeverity: "error",
+        });
+    });
+});