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

Issue:
Missing react-a11y converter: react-a11y-img-has-alt
### 💥 Missing Converter

-   `tslint-to-eslint-config` version: 2.0.1
-   `typescript-eslint` version: 4.9.1

#### TSLint Rule Name

`react-a11y-img-has-alt`

Per https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/ROADMAP.md, the equivalent rule is now `jsx-a11y/alt-text`.

#### Package Source

https://github.com/teamairship/tslint-react-a11y
Missing react-a11y converter: react-a11y-image-button-has-alt
### 💥 Missing Converter

-   `tslint-to-eslint-config` version: 2.0.1
-   `typescript-eslint` version: 4.9.1

#### TSLint Rule Name

`react-a11y-image-button-has-alt`

Per https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/ROADMAP.md, the equivalent rule is now `jsx-a11y/alt-text`.

#### Package Source

https://github.com/teamairship/tslint-react-a11y

Pull request:
Added converter for react-a11y-image-button-has-alt, react-a11y-img-has-alt
## PR Checklist

-   [x] Addresses an existing issue: fixes #903, fixes #904
-   [x] That issue was marked as [`status: accepting prs`](https://github.com/typescript-eslint/tslint-to-eslint-config/labels/status%3A%20accepting%20prs)

## Overview

https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/alt-text.md

Since both input TSLint rules go into the same output ESLint rule, I'm putting them into the same PR. Each input TSLint rule has a notice that the output ESLint rule covers more cases.

Patch:
diff --git a/src/converters/lintConfigs/rules/ruleConverters.ts b/src/converters/lintConfigs/rules/ruleConverters.ts
index ee5f175..88e8aef 100644
--- a/src/converters/lintConfigs/rules/ruleConverters.ts
+++ b/src/converters/lintConfigs/rules/ruleConverters.ts
@@ -255,6 +255,8 @@ import { convertQuotemark } from "./ruleConverters/quotemark";
 import { convertRadix } from "./ruleConverters/radix";
 import { convertReactA11yAccessibleHeadings } from "./ruleConverters/react-a11y-accessible-headings";
 import { convertReactA11yAnchors } from "./ruleConverters/react-a11y-anchors";
+import { convertReactA11yImageButtonHasAlt } from "./ruleConverters/react-a11y-image-button-has-alt";
+import { convertReactA11yImgHasAlt } from "./ruleConverters/react-a11y-img-has-alt";
 import { convertReactA11yProps } from "./ruleConverters/react-a11y-props";
 import { convertReactA11yTabIndexNoPositive } from "./ruleConverters/react-a11y-tabindex-no-positive";
 import { convertReactNoDangerousHtml } from "./ruleConverters/react-no-dangerous-html";
@@ -494,6 +496,8 @@ export const ruleConverters = new Map([
     ["quotemark", convertQuotemark],
     ["radix", convertRadix],
     ["react-a11y-anchors", convertReactA11yAnchors],
+    ["react-a11y-image-button-has-alt", convertReactA11yImageButtonHasAlt],
+    ["react-a11y-img-has-alt", convertReactA11yImgHasAlt],
     ["react-a11y-props", convertReactA11yProps],
     ["react-a11y-tabindex-no-positive", convertReactA11yTabIndexNoPositive],
     ["react-no-dangerous-html", convertReactNoDangerousHtml],
diff --git a/src/converters/lintConfigs/rules/ruleConverters/react-a11y-image-button-has-alt.ts b/src/converters/lintConfigs/rules/ruleConverters/react-a11y-image-button-has-alt.ts
new file mode 100644
index 0000000..eb262ff
--- /dev/null
+++ b/src/converters/lintConfigs/rules/ruleConverters/react-a11y-image-button-has-alt.ts
@@ -0,0 +1,13 @@
+import { RuleConverter } from "../ruleConverter";
+
+export const convertReactA11yImageButtonHasAlt: RuleConverter = () => {
+    return {
+        notices: ["jsx-a11y/alt-text covers more cases than react-a11y-image-button-has-alt"],
+        plugins: ["jsx-a11y"],
+        rules: [
+            {
+                ruleName: "jsx-a11y/alt-text",
+            },
+        ],
+    };
+};
diff --git a/src/converters/lintConfigs/rules/ruleConverters/react-a11y-img-has-alt.ts b/src/converters/lintConfigs/rules/ruleConverters/react-a11y-img-has-alt.ts
new file mode 100644
index 0000000..14d6fba
--- /dev/null
+++ b/src/converters/lintConfigs/rules/ruleConverters/react-a11y-img-has-alt.ts
@@ -0,0 +1,20 @@
+import { RuleConverter } from "../ruleConverter";
+
+export const convertReactA11yImgHasAlt: RuleConverter = (tslintRule) => {
+    return {
+        notices: ["jsx-a11y/alt-text covers more cases than react-a11y-img-has-alt"],
+        plugins: ["jsx-a11y"],
+        rules: [
+            {
+                ...(tslintRule.ruleArguments.length !== 0 && {
+                    ruleArguments: [
+                        {
+                            elements: tslintRule.ruleArguments,
+                        },
+                    ],
+                }),
+                ruleName: "jsx-a11y/alt-text",
+            },
+        ],
+    };
+};
diff --git a/src/converters/lintConfigs/rules/ruleMergers.ts b/src/converters/lintConfigs/rules/ruleMergers.ts
index ff6cb3b..ab96f5a 100644
--- a/src/converters/lintConfigs/rules/ruleMergers.ts
+++ b/src/converters/lintConfigs/rules/ruleMergers.ts
@@ -1,6 +1,7 @@
 import { mergeBanTypes } from "./ruleMergers/ban-types";
 import { mergeConsistentTypeAssertions } from "./ruleMergers/consistent-type-assertions";
 import { mergeIndent } from "./ruleMergers/indent";
+import { mergeJsxA11yAltText } from "./ruleMergers/jsx-a11y-alt-text";
 import { mergeNoMemberDelimiterStyle } from "./ruleMergers/member-delimiter-style";
 import { mergeNamingConvention } from "./ruleMergers/naming-convention";
 import { mergeNoEmpty } from "./ruleMergers/no-empty";
@@ -18,6 +19,7 @@ export const ruleMergers = new Map([
     ["@typescript-eslint/no-use-before-define", mergeNoUseBeforeDefine],
     ["@typescript-eslint/no-unnecessary-type-assertion", mergeNoUnnecessaryTypeAssertion],
     ["@typescript-eslint/triple-slash-reference", mergeTripleSlashReference],
+    ["jsx-a11y/alt-text", mergeJsxA11yAltText],
     ["no-empty", mergeNoEmpty],
     ["no-eval", mergeNoEval],
 ]);
diff --git a/src/converters/lintConfigs/rules/ruleMergers/jsx-a11y-alt-text.ts b/src/converters/lintConfigs/rules/ruleMergers/jsx-a11y-alt-text.ts
new file mode 100644
index 0000000..4d8ce83
--- /dev/null
+++ b/src/converters/lintConfigs/rules/ruleMergers/jsx-a11y-alt-text.ts
@@ -0,0 +1,15 @@
+import { uniqueFromSources } from "../../../../utils";
+import { RuleMerger } from "../ruleMerger";
+
+export const mergeJsxA11yAltText: RuleMerger = (existingOptions, newOptions) => {
+    const existingElements = existingOptions?.[0]?.elements;
+    const newElements = newOptions?.[0]?.elements;
+
+    return existingElements || newElements
+        ? [
+              {
+                  elements: uniqueFromSources(existingElements ?? [], newElements ?? []),
+              },
+          ]
+        : [];
+};


Test patch:
diff --git a/src/converters/lintConfigs/rules/ruleConverters/tests/react-a11y-image-button-has-alt.test.ts b/src/converters/lintConfigs/rules/ruleConverters/tests/react-a11y-image-button-has-alt.test.ts
new file mode 100644
index 0000000..007ebeb
--- /dev/null
+++ b/src/converters/lintConfigs/rules/ruleConverters/tests/react-a11y-image-button-has-alt.test.ts
@@ -0,0 +1,19 @@
+import { convertReactA11yImageButtonHasAlt } from "../react-a11y-image-button-has-alt";
+
+describe(convertReactA11yImageButtonHasAlt, () => {
+    test("conversion without arguments", () => {
+        const result = convertReactA11yImageButtonHasAlt({
+            ruleArguments: [],
+        });
+
+        expect(result).toEqual({
+            notices: ["jsx-a11y/alt-text covers more cases than react-a11y-image-button-has-alt"],
+            plugins: ["jsx-a11y"],
+            rules: [
+                {
+                    ruleName: "jsx-a11y/alt-text",
+                },
+            ],
+        });
+    });
+});
diff --git a/src/converters/lintConfigs/rules/ruleConverters/tests/react-a11y-img-has-alt.test.ts b/src/converters/lintConfigs/rules/ruleConverters/tests/react-a11y-img-has-alt.test.ts
new file mode 100644
index 0000000..d4239e1
--- /dev/null
+++ b/src/converters/lintConfigs/rules/ruleConverters/tests/react-a11y-img-has-alt.test.ts
@@ -0,0 +1,37 @@
+import { convertReactA11yImgHasAlt } from "../react-a11y-img-has-alt";
+
+describe(convertReactA11yImgHasAlt, () => {
+    test("conversion without arguments", () => {
+        const result = convertReactA11yImgHasAlt({
+            ruleArguments: [],
+        });
+
+        expect(result).toEqual({
+            notices: ["jsx-a11y/alt-text covers more cases than react-a11y-img-has-alt"],
+            plugins: ["jsx-a11y"],
+            rules: [
+                {
+                    ruleName: "jsx-a11y/alt-text",
+                },
+            ],
+        });
+    });
+
+    test("conversion with an argument", () => {
+        const elements = ["Image"];
+        const result = convertReactA11yImgHasAlt({
+            ruleArguments: elements,
+        });
+
+        expect(result).toEqual({
+            notices: ["jsx-a11y/alt-text covers more cases than react-a11y-img-has-alt"],
+            plugins: ["jsx-a11y"],
+            rules: [
+                {
+                    ruleArguments: [{ elements }],
+                    ruleName: "jsx-a11y/alt-text",
+                },
+            ],
+        });
+    });
+});
diff --git a/src/converters/lintConfigs/rules/ruleMergers/tests/jsx-a11y-alt-text.test.ts b/src/converters/lintConfigs/rules/ruleMergers/tests/jsx-a11y-alt-text.test.ts
new file mode 100644
index 0000000..9a28cc4
--- /dev/null
+++ b/src/converters/lintConfigs/rules/ruleMergers/tests/jsx-a11y-alt-text.test.ts
@@ -0,0 +1,30 @@
+import { mergeJsxA11yAltText } from "../jsx-a11y-alt-text";
+
+describe(mergeJsxA11yAltText, () => {
+    test("neither options existing", () => {
+        const result = mergeJsxA11yAltText(undefined, undefined);
+
+        expect(result).toEqual([]);
+    });
+
+    test("original elements existing", () => {
+        const result = mergeJsxA11yAltText([{ elements: ["Image"] }], undefined);
+
+        expect(result).toEqual([{ elements: ["Image"] }]);
+    });
+
+    test("new elements existing", () => {
+        const result = mergeJsxA11yAltText(undefined, [{ elements: ["Image"] }]);
+
+        expect(result).toEqual([{ elements: ["Image"] }]);
+    });
+
+    test("both elements existing", () => {
+        const result = mergeJsxA11yAltText(
+            [{ elements: ["Button", "Image"] }],
+            [{ elements: ["Image"] }],
+        );
+
+        expect(result).toEqual([{ elements: ["Button", "Image"] }]);
+    });
+});