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

Issue:
Missing converter: variable-name
<!--
👋 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. 😦
-->

### 💥 Missing Converter

-   `tslint-to-eslint-config` version: 0.2.7
-   `typescript-eslint` version: 2.3.2

#### TSLint Rule Name

`variable-name`

The issue here is that this package doesn't know how to convert the TSLint rule into the equivalent ESLint rule. The equivalent ESlint rule doesn't show on the [eslint-plugin Roadmap](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/ROADMAP.md).


Pull request description:
Implement variable-name rule converter
<!--
👋 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 #219
-   [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. -->

- Added a rule converter for `variable-name` in the TSLint ruleset.
- Added proper handling for the options that come with the rule itself.
- Added tests to make sure that the right comparative rules are being output.
- Added a couple of notices so that the user is informed of things not automatically converted.

This is my first PR so don't expect it to be 100% perfect so awaiting for any feedback from members/contributors.

Patch:
diff --git a/src/rules/converters.ts b/src/rules/converters.ts
index d7846f6..0ce0b08 100644
--- a/src/rules/converters.ts
+++ b/src/rules/converters.ts
@@ -12,7 +12,6 @@ import { convertClassName } from "./converters/class-name";
 import { convertCurly } from "./converters/curly";
 import { convertCyclomaticComplexity } from "./converters/cyclomatic-complexity";
 import { convertEofline } from "./converters/eofline";
-import { convertMemberAccess } from "./converters/member-access";
 import { convertFileNameCasing } from "./converters/file-name-casing";
 import { convertForin } from "./converters/forin";
 import { convertFunctionConstructor } from "./converters/function-constructor";
@@ -25,6 +24,7 @@ import { convertLinebreakStyle } from "./converters/linebreak-style";
 import { convertMaxClassesPerFile } from "./converters/max-classes-per-file";
 import { convertMaxFileLineCount } from "./converters/max-file-line-count";
 import { convertMaxLineLength } from "./converters/max-line-length";
+import { convertMemberAccess } from "./converters/member-access";
 import { convertMemberOrdering } from "./converters/member-ordering";
 import { convertNewlineBeforeReturn } from "./converters/newline-before-return";
 import { convertNewlinePerChainedCall } from "./converters/newline-per-chained-call";
@@ -105,12 +105,14 @@ import { convertPreferObjectSpread } from "./converters/prefer-object-spread";
 import { convertPreferReadonly } from "./converters/prefer-readonly";
 import { convertPreferTemplate } from "./converters/prefer-template";
 import { convertPromiseFunctionAsync } from "./converters/promise-function-async";
+import { convertQuotemark } from "./converters/quotemark";
 import { convertRadix } from "./converters/radix";
 import { convertRestrictPlusOperands } from "./converters/restrict-plus-operands";
 import { convertSemicolon } from "./converters/semicolon";
 import { convertSpaceBeforeFunctionParen } from "./converters/space-before-function-paren";
 import { convertSpaceWithinParens } from "./converters/space-within-parens";
 import { convertSwitchDefault } from "./converters/switch-default";
+import { convertTripleEquals } from "./converters/triple-equals";
 import { convertTypedefWhitespace } from "./converters/typedef-whitespace";
 import { convertTypeLiteralDelimiter } from "./converters/type-literal-delimiter";
 import { convertTypeofCompare } from "./converters/typeof-compare";
@@ -119,8 +121,7 @@ import { convertUnnecessaryBind } from "./converters/unnecessary-bind";
 import { convertUnnecessaryConstructor } from "./converters/unnecessary-constructor";
 import { convertUseDefaultTypeParameter } from "./converters/use-default-type-parameter";
 import { convertUseIsnan } from "./converters/use-isnan";
-import { convertQuotemark } from "./converters/quotemark";
-import { convertTripleEquals } from "./converters/triple-equals";
+import { convertVariableName } from "./converters/variable-name";
 
 /**
  * Keys TSLint rule names to their ESLint rule converters.
@@ -249,6 +250,7 @@ export const converters = new Map([
     ["unnecessary-constructor", convertUnnecessaryConstructor],
     ["use-default-type-parameter", convertUseDefaultTypeParameter],
     ["use-isnan", convertUseIsnan],
+    ["variable-name", convertVariableName],
 
     // These converters are all for rules that need more complex option conversions.
     // Some of them will likely need to have notices about changed lint behaviors...
@@ -262,7 +264,6 @@ export const converters = new Map([
     // ["no-void-expression", convertNoVoidExpression], // (no exact equivalent)
     // ["quotemark", convertQuotemark], // quotes
     // ["triple-equals", convertTripleEquals], // eqeqeq
-    // ["variable-name", convertVariableName], // a bunch of rules...
 
     // tslint-microsoft-contrib rules:
     // ["max-func-body-length", convertMaxFuncBodyLength],
diff --git a/src/rules/converters/variable-name.ts b/src/rules/converters/variable-name.ts
new file mode 100644
index 0000000..1a10009
--- /dev/null
+++ b/src/rules/converters/variable-name.ts
@@ -0,0 +1,109 @@
+import { RuleConverter } from "../converter";
+
+export const IgnoreLeadingTrailingUnderscoreMsg =
+    "Leading and trailing underscores (_) in variable names will now be ignored.";
+export const IgnoreOnlyLeadingUnderscoreMsg =
+    "Leading undescores in variable names will now be ignored.";
+export const IgnoreOnlyTrailingUnderscoreMsg =
+    "Trailing undescores in variable names will now be ignored.";
+export const ConstRequiredForAllCapsMsg =
+    "ESLint's camel-case will throw a warning if const name is not uppercase.";
+export const ForbiddenPascalSnakeMsg =
+    "ESLint's camel-case rule does not allow pascal or snake case variable names. Those cases are reserved for class names and static methods.";
+export const IgnoreLeadingTrailingIdentifierMsg =
+    "Leading and trailing underscores (_) on identifiers will now be ignored.";
+export const ForbiddenLeadingTrailingIdentifierMsg =
+    "Leading or trailing underscores (_) on identifiers will now be forbidden.";
+
+export const convertVariableName: RuleConverter = tslintRule => {
+    const hasCheckFormat = tslintRule.ruleArguments.includes("check-format");
+    const allowedLeadingUnderscore = tslintRule.ruleArguments.includes("allow-leading-underscore");
+    const allowedTrailingUnderscore = tslintRule.ruleArguments.includes(
+        "allow-trailing-underscore",
+    );
+    const constRequiredForAllCaps = tslintRule.ruleArguments.includes("require-const-for-all-caps");
+    const allowPascalSnakeCase =
+        tslintRule.ruleArguments.includes("allow-pascal-case") ||
+        tslintRule.ruleArguments.includes("allow-snake-case");
+
+    const getCamelCaseRuleOptions = () => {
+        const camelCaseOptionNotice: string[] = [];
+
+        if (hasCheckFormat) {
+            if (!allowedLeadingUnderscore && !allowedTrailingUnderscore) {
+                camelCaseOptionNotice.push(IgnoreLeadingTrailingUnderscoreMsg);
+            } else if (allowedLeadingUnderscore && !allowedTrailingUnderscore) {
+                camelCaseOptionNotice.push(IgnoreOnlyLeadingUnderscoreMsg);
+            } else if (!allowedLeadingUnderscore && allowedTrailingUnderscore) {
+                camelCaseOptionNotice.push(IgnoreOnlyTrailingUnderscoreMsg);
+            }
+        } else {
+            camelCaseOptionNotice.push(IgnoreLeadingTrailingUnderscoreMsg);
+        }
+
+        if (constRequiredForAllCaps) {
+            camelCaseOptionNotice.push(ConstRequiredForAllCapsMsg);
+        }
+
+        if (allowPascalSnakeCase) {
+            camelCaseOptionNotice.push(ForbiddenPascalSnakeMsg);
+        }
+
+        return {
+            ruleName: "camelcase",
+            notices: camelCaseOptionNotice,
+        };
+    };
+
+    const getUnderscoreDangleRuleOptions = () => {
+        const underscoreDangleOptionArguments: string[] = [];
+        const underscoreDangleOptionNotice: string[] = [];
+
+        if (hasCheckFormat && (allowedLeadingUnderscore || allowedTrailingUnderscore)) {
+            underscoreDangleOptionArguments.push("off");
+            underscoreDangleOptionNotice.push(IgnoreLeadingTrailingIdentifierMsg);
+        } else {
+            underscoreDangleOptionNotice.push(ForbiddenLeadingTrailingIdentifierMsg);
+        }
+
+        return {
+            ruleName: "no-underscore-dangle",
+            ruleArguments: underscoreDangleOptionArguments,
+            notices: underscoreDangleOptionNotice,
+        };
+    };
+
+    const getBlackListRuleOptions = () => {
+        const blackListOptionArguments: string[] = [];
+
+        if (tslintRule.ruleArguments.includes("ban-keywords")) {
+            blackListOptionArguments.push(
+                "any",
+                "Number",
+                "number",
+                "String",
+                "string",
+                "Boolean",
+                "boolean",
+                "Undefined",
+                "undefined",
+            );
+        }
+
+        return {
+            ruleName: "id-blacklist",
+            ruleArguments: blackListOptionArguments,
+        };
+    };
+
+    return {
+        rules: [
+            getCamelCaseRuleOptions(),
+            getUnderscoreDangleRuleOptions(),
+            getBlackListRuleOptions(),
+            {
+                ruleName: "id-match",
+            },
+        ],
+    };
+};


Test patch:
diff --git a/src/rules/converters/tests/variable-name.test.ts b/src/rules/converters/tests/variable-name.test.ts
new file mode 100644
index 0000000..e98201c
--- /dev/null
+++ b/src/rules/converters/tests/variable-name.test.ts
@@ -0,0 +1,331 @@
+import {
+    convertVariableName,
+    IgnoreLeadingTrailingUnderscoreMsg,
+    ForbiddenLeadingTrailingIdentifierMsg,
+    IgnoreLeadingTrailingIdentifierMsg,
+    ForbiddenPascalSnakeMsg,
+    ConstRequiredForAllCapsMsg,
+    IgnoreOnlyLeadingUnderscoreMsg,
+    IgnoreOnlyTrailingUnderscoreMsg,
+} from "../variable-name";
+
+describe(convertVariableName, () => {
+    test("conversion without arguments", () => {
+        const result = convertVariableName({
+            ruleArguments: [],
+        });
+
+        expect(result).toEqual({
+            rules: [
+                {
+                    ruleName: "camelcase",
+                    notices: [IgnoreLeadingTrailingUnderscoreMsg],
+                },
+                {
+                    ruleName: "no-underscore-dangle",
+                    ruleArguments: [],
+                    notices: [ForbiddenLeadingTrailingIdentifierMsg],
+                },
+                {
+                    ruleName: "id-blacklist",
+                    ruleArguments: [],
+                },
+                {
+                    ruleName: "id-match",
+                },
+            ],
+        });
+    });
+
+    test("conversion with require-const-for-all-caps argument", () => {
+        const result = convertVariableName({
+            ruleArguments: ["require-const-for-all-caps"],
+        });
+
+        expect(result).toEqual({
+            rules: [
+                {
+                    ruleName: "camelcase",
+                    notices: [IgnoreLeadingTrailingUnderscoreMsg, ConstRequiredForAllCapsMsg],
+                },
+                {
+                    ruleName: "no-underscore-dangle",
+                    ruleArguments: [],
+                    notices: [ForbiddenLeadingTrailingIdentifierMsg],
+                },
+                {
+                    ruleName: "id-blacklist",
+                    ruleArguments: [],
+                },
+                {
+                    ruleName: "id-match",
+                },
+            ],
+        });
+    });
+
+    test("conversion with allow-pascal-case argument", () => {
+        const result = convertVariableName({
+            ruleArguments: ["allow-pascal-case"],
+        });
+
+        expect(result).toEqual({
+            rules: [
+                {
+                    ruleName: "camelcase",
+                    notices: [IgnoreLeadingTrailingUnderscoreMsg, ForbiddenPascalSnakeMsg],
+                },
+                {
+                    ruleName: "no-underscore-dangle",
+                    ruleArguments: [],
+                    notices: [ForbiddenLeadingTrailingIdentifierMsg],
+                },
+                {
+                    ruleName: "id-blacklist",
+                    ruleArguments: [],
+                },
+                {
+                    ruleName: "id-match",
+                },
+            ],
+        });
+    });
+
+    test("conversion with allow-snake-case argument", () => {
+        const result = convertVariableName({
+            ruleArguments: ["allow-snake-case"],
+        });
+
+        expect(result).toEqual({
+            rules: [
+                {
+                    ruleName: "camelcase",
+                    notices: [IgnoreLeadingTrailingUnderscoreMsg, ForbiddenPascalSnakeMsg],
+                },
+                {
+                    ruleName: "no-underscore-dangle",
+                    ruleArguments: [],
+                    notices: [ForbiddenLeadingTrailingIdentifierMsg],
+                },
+                {
+                    ruleName: "id-blacklist",
+                    ruleArguments: [],
+                },
+                {
+                    ruleName: "id-match",
+                },
+            ],
+        });
+    });
+
+    test("conversion with allow-leading-underscore without check-format argument", () => {
+        const result = convertVariableName({
+            ruleArguments: ["allow-leading-underscore"],
+        });
+
+        expect(result).toEqual({
+            rules: [
+                {
+                    ruleName: "camelcase",
+                    notices: [IgnoreLeadingTrailingUnderscoreMsg],
+                },
+                {
+                    ruleName: "no-underscore-dangle",
+                    ruleArguments: [],
+                    notices: [ForbiddenLeadingTrailingIdentifierMsg],
+                },
+                {
+                    ruleName: "id-blacklist",
+                    ruleArguments: [],
+                },
+                {
+                    ruleName: "id-match",
+                },
+            ],
+        });
+    });
+
+    test("conversion with allow-trailing-underscore without check-format argument", () => {
+        const result = convertVariableName({
+            ruleArguments: ["allow-trailing-underscore"],
+        });
+
+        expect(result).toEqual({
+            rules: [
+                {
+                    ruleName: "camelcase",
+                    notices: [IgnoreLeadingTrailingUnderscoreMsg],
+                },
+                {
+                    ruleName: "no-underscore-dangle",
+                    ruleArguments: [],
+                    notices: [ForbiddenLeadingTrailingIdentifierMsg],
+                },
+                {
+                    ruleName: "id-blacklist",
+                    ruleArguments: [],
+                },
+                {
+                    ruleName: "id-match",
+                },
+            ],
+        });
+    });
+
+    test("conversion with check-format argument", () => {
+        const result = convertVariableName({
+            ruleArguments: ["check-format"],
+        });
+
+        expect(result).toEqual({
+            rules: [
+                {
+                    ruleName: "camelcase",
+                    notices: [IgnoreLeadingTrailingUnderscoreMsg],
+                },
+                {
+                    ruleName: "no-underscore-dangle",
+                    ruleArguments: [],
+                    notices: [ForbiddenLeadingTrailingIdentifierMsg],
+                },
+                {
+                    ruleName: "id-blacklist",
+                    ruleArguments: [],
+                },
+                {
+                    ruleName: "id-match",
+                },
+            ],
+        });
+    });
+
+    test("conversion with allow-leading-underscore and check-format argument", () => {
+        const result = convertVariableName({
+            ruleArguments: ["check-format", "allow-leading-underscore"],
+        });
+
+        expect(result).toEqual({
+            rules: [
+                {
+                    ruleName: "camelcase",
+                    notices: [IgnoreOnlyLeadingUnderscoreMsg],
+                },
+                {
+                    ruleName: "no-underscore-dangle",
+                    ruleArguments: ["off"],
+                    notices: [IgnoreLeadingTrailingIdentifierMsg],
+                },
+                {
+                    ruleName: "id-blacklist",
+                    ruleArguments: [],
+                },
+                {
+                    ruleName: "id-match",
+                },
+            ],
+        });
+    });
+
+    test("conversion with allow-trailing-underscore and check-format argument", () => {
+        const result = convertVariableName({
+            ruleArguments: ["check-format", "allow-trailing-underscore"],
+        });
+
+        expect(result).toEqual({
+            rules: [
+                {
+                    ruleName: "camelcase",
+                    notices: [IgnoreOnlyTrailingUnderscoreMsg],
+                },
+                {
+                    ruleName: "no-underscore-dangle",
+                    ruleArguments: ["off"],
+                    notices: [IgnoreLeadingTrailingIdentifierMsg],
+                },
+                {
+                    ruleName: "id-blacklist",
+                    ruleArguments: [],
+                },
+                {
+                    ruleName: "id-match",
+                },
+            ],
+        });
+    });
+
+    test("conversion with allow-leading-underscore, allow-trailing-underscore and check-format argument", () => {
+        const result = convertVariableName({
+            ruleArguments: [
+                "check-format",
+                "allow-leading-underscore",
+                "allow-trailing-underscore",
+            ],
+        });
+
+        expect(result).toEqual({
+            rules: [
+                {
+                    ruleName: "camelcase",
+                    notices: [],
+                },
+                {
+                    ruleName: "no-underscore-dangle",
+                    ruleArguments: ["off"],
+                    notices: [IgnoreLeadingTrailingIdentifierMsg],
+                },
+                {
+                    ruleName: "id-blacklist",
+                    ruleArguments: [],
+                },
+                {
+                    ruleName: "id-match",
+                },
+            ],
+        });
+    });
+
+    test("conversion with all arguments", () => {
+        const result = convertVariableName({
+            ruleArguments: [
+                "check-format",
+                "allow-leading-underscore",
+                "allow-pascal-case",
+                "allow-snake-case",
+                "allow-trailing-underscore",
+                "require-const-for-all-caps",
+                "ban-keywords",
+            ],
+        });
+
+        expect(result).toEqual({
+            rules: [
+                {
+                    ruleName: "camelcase",
+                    notices: [ConstRequiredForAllCapsMsg, ForbiddenPascalSnakeMsg],
+                },
+                {
+                    ruleName: "no-underscore-dangle",
+                    ruleArguments: ["off"],
+                    notices: [IgnoreLeadingTrailingIdentifierMsg],
+                },
+                {
+                    ruleName: "id-blacklist",
+                    ruleArguments: [
+                        "any",
+                        "Number",
+                        "number",
+                        "String",
+                        "string",
+                        "Boolean",
+                        "boolean",
+                        "Undefined",
+                        "undefined",
+                    ],
+                },
+                {
+                    ruleName: "id-match",
+                },
+            ],
+        });
+    });
+});