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

Issue:
Empty objects from extended configurations are added to output ESLint configurations
### 🐛 Bug Report

-   `tslint-to-eslint-config` version: 1.0.0-beta
-   ESLint version: 7.0.0
-   Node version: 12.4.0

#### Actual Behavior

See comment in https://github.com/typescript-eslint/tslint-to-eslint-config/pull/448#issuecomment-626239380: if you run `tslint-to-eslint-config` _twice_, it looks like some config values are getting picked up from extended configs.

```js
    // ...
    "ignorePatterns": [],
    // ...
```

#### Expected Behavior

Don't add those things. Do not want.

#### Reproduction

With a `tslint.json` that contains `{ extends: "tslint:recommended" }`:

```
npx tslint-to-eslint-config
npx tslint-to-eslint-config
```


Pull request:
Stopped writing empty root members in output configurations
## PR Checklist

-   [x] Addresses an existing issue: fixes #537
-   [x] That issue was marked as [`status: accepting prs`](https://github.com/typescript-eslint/tslint-to-eslint-config/labels/status%3A%20accepting%20prs)

## Overview

Cleans up the output configuration format a little bit to not redundantly print empty objects. Just a little cleanup 🧹 

Patch:
diff --git a/src/creation/writeConversionResults.ts b/src/creation/writeConversionResults.ts
index d1c1c23..5d24761 100644
--- a/src/creation/writeConversionResults.ts
+++ b/src/creation/writeConversionResults.ts
@@ -1,5 +1,6 @@
 import { FileSystem } from "../adapters/fileSystem";
 import { AllOriginalConfigurations } from "../input/findOriginalConfigurations";
+import { removeEmptyMembers } from "../utils";
 import { createEnv } from "./eslint/createEnv";
 import { formatConvertedRules } from "./formatConvertedRules";
 import { formatOutput } from "./formatting/formatOutput";
@@ -22,11 +23,11 @@ export const writeConversionResults = async (
         plugins.push("@typescript-eslint/tslint");
     }
 
-    const output = {
+    const output = removeEmptyMembers({
         ...eslint?.full,
         env: createEnv(originalConfigurations),
         ...(eslint && { globals: eslint.raw.globals }),
-        ...(summarizedResults.extends?.length !== 0 && { extends: summarizedResults.extends }),
+        ...(summarizedResults.extends && { extends: summarizedResults.extends }),
         parser: "@typescript-eslint/parser",
         parserOptions: {
             project: "tsconfig.json",
@@ -37,7 +38,7 @@ export const writeConversionResults = async (
             // ...trimESLintRules(eslint?.full.rules, summarizedResults.extensionRules),
             ...formatConvertedRules(summarizedResults, tslint.full),
         },
-    };
+    });
 
     return await dependencies.fileSystem.writeFile(outputPath, formatOutput(outputPath, output));
 };
diff --git a/src/utils.ts b/src/utils.ts
index 46de58c..2adbd44 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -4,6 +4,21 @@ export const isError = <Item>(item: Item | Error): item is Error => item instanc
 
 export const isTruthy = <Item>(item: Item | false | undefined | null | 0): item is Item => !!item;
 
+export const removeEmptyMembers = <Item>(items: Item): Item => {
+    const result: any = {};
+
+    for (const [key, value] of Object.entries(items)) {
+        if (
+            !(value instanceof Array && value.length === 0) &&
+            !(value instanceof Object && Object.keys(value).length === 0)
+        ) {
+            result[key] = value;
+        }
+    }
+
+    return result;
+};
+
 export const separateErrors = <Item>(mixed: (Error | Item)[]): [Error[], Item[]] => {
     const errors: Error[] = [];
     const items: Item[] = [];


Test patch:
diff --git a/src/creation/writeConversionResults.test.ts b/src/creation/writeConversionResults.test.ts
index d78c66e..93c2f97 100644
--- a/src/creation/writeConversionResults.test.ts
+++ b/src/creation/writeConversionResults.test.ts
@@ -46,7 +46,6 @@ describe("writeConversionResults", () => {
                     sourceType: "module",
                 },
                 plugins: ["@typescript-eslint"],
-                rules: {},
             }),
         );
     });
@@ -137,8 +136,6 @@ describe("writeConversionResults", () => {
                     es6: true,
                     node: true,
                 },
-                extends: [],
-                rules: {},
                 parser: "@typescript-eslint/parser",
                 parserOptions: {
                     project: "tsconfig.json",
@@ -182,7 +179,6 @@ describe("writeConversionResults", () => {
                     sourceType: "module",
                 },
                 plugins: ["@typescript-eslint"],
-                rules: {},
             }),
         );
     });
@@ -224,8 +220,6 @@ describe("writeConversionResults", () => {
                     es6: true,
                     node: true,
                 },
-                extends: [],
-                rules: {},
                 globals: {
                     Promise: true,
                 },
diff --git a/src/utils.test.ts b/src/utils.test.ts
index e93823a..dd1fa97 100644
--- a/src/utils.test.ts
+++ b/src/utils.test.ts
@@ -1,4 +1,4 @@
-import { isDefined, isError, uniqueFromSources, separateErrors } from "./utils";
+import { isDefined, isError, uniqueFromSources, separateErrors, removeEmptyMembers } from "./utils";
 
 describe("isDefined", () => {
     it("returns true when the item is defined", () => {
@@ -48,6 +48,41 @@ describe("isError", () => {
     });
 });
 
+describe("removeEmptyMembers", () => {
+    it("remove an object member when it is empty", () => {
+        // Arrange
+        const items = { kept: { value: true }, removed: {} };
+
+        // Act
+        const result = removeEmptyMembers(items);
+
+        // Assert
+        expect(result).toEqual({ kept: { value: true } });
+    });
+
+    it("removes an array member when it is empty", () => {
+        // Arrange
+        const items = { kept: [true], removed: [] };
+
+        // Act
+        const result = removeEmptyMembers(items);
+
+        // Assert
+        expect(result).toEqual({ kept: [true] });
+    });
+
+    it("keeps a member when it isn't an array or object", () => {
+        // Arrange
+        const items = { kept: true };
+
+        // Act
+        const result = removeEmptyMembers(items);
+
+        // Assert
+        expect(result).toEqual({ kept: true });
+    });
+});
+
 describe("separateErrors", () => {
     it("splits the input array into errors and items", () => {
         // Arrange