Repository: vue-a11y/eslint-plugin-vuejs-accessibility (ts)

Issue:
label-has-for: add option for adding custom controlTypes
https://github.com/vue-a11y/eslint-plugin-vuejs-accessibility/blob/master/src/rules/label-has-for.js#L9

Like we have an option for custom labels, I want to configure a custom component for the `Input` :smile: 

Pull request description:
controlComponents option for label-has-for
Fixes #38 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 209cc74..9b7b7ee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
 
 ## [Unreleased]
 
+### Added
+
+- The `controlComponents` option for the `label-has-for` rule, which allows you to configure the rule to allow additional control components.
+
 ## [0.3.1] - 2020-07-10
 
 ### Changed
diff --git a/docs/label-has-for.md b/docs/label-has-for.md
index eeb4637..7da1865 100644
--- a/docs/label-has-for.md
+++ b/docs/label-has-for.md
@@ -17,7 +17,8 @@ This rule takes one optional object argument of type object:
     "vuejs-accessibility/label-has-for": [
       "error",
       {
-        "components": ["Label"],
+        "components": ["VLabel"],
+        "controlComponents": ["VInput"],
         "required": {
           "every": ["nesting", "id"]
         },
@@ -28,7 +29,9 @@ This rule takes one optional object argument of type object:
 }
 ```
 
-For the `components` option, these strings determine which JSX elements (**always including** `<label>`) should be checked for having the `for` prop. This is a good use case when you have a wrapper component that simply renders a `label` element.
+For the `components` option, these strings determine which elements (**always including** `<label>`) should be checked for having the `for` prop. This is a good use case when you have a wrapper component that simply renders a `label` element.
+
+For the `controlComponents` option, these strings determine which elements should be counted as form control elements. By default, this includes `input`, `meter`, `progress`, `select`, and `textarea`. This is a good use case when you have a wrapper component that simplify renders a `input` element.
 
 The `required` option (defaults to `"required": { "every": ["nesting", "id"] }`) determines which checks are activated. You're allowed to pass in one of the following types:
 
diff --git a/src/rules/label-has-for.js b/src/rules/label-has-for.js
index 3b0b363..c9c3a74 100644
--- a/src/rules/label-has-for.js
+++ b/src/rules/label-has-for.js
@@ -8,7 +8,7 @@ const {
 
 const controlTypes = ["input", "meter", "progress", "select", "textarea"];
 
-const validateNesting = (node, allowChildren) =>
+const validateNesting = (node, allowChildren, controlComponents) =>
   node.children.some((child) => {
     if (child.rawName === "slot") {
       return allowChildren;
@@ -17,7 +17,9 @@ const validateNesting = (node, allowChildren) =>
     if (child.type === "VElement") {
       return (
         !isHiddenFromScreenReader(child) &&
-        (controlTypes.includes(getElementType(child)) ||
+        (controlTypes
+          .concat(controlComponents)
+          .includes(getElementType(child)) ||
           validateNesting(child, allowChildren))
       );
     }
@@ -25,10 +27,10 @@ const validateNesting = (node, allowChildren) =>
     return false;
   });
 
-const validate = (node, rule, allowChildren) => {
+const validate = (node, rule, allowChildren, controlComponents) => {
   switch (rule) {
     case "nesting":
-      return validateNesting(node, allowChildren);
+      return validateNesting(node, allowChildren, controlComponents);
     case "id":
       return getElementAttributeValue(node, "for");
     default:
@@ -36,16 +38,20 @@ const validate = (node, rule, allowChildren) => {
   }
 };
 
-const isValidLabel = (node, required, allowChildren) => {
+const isValidLabel = (node, required, allowChildren, controlComponents) => {
   if (Array.isArray(required.some)) {
-    return required.some.some((rule) => validate(node, rule, allowChildren));
+    return required.some.some((rule) =>
+      validate(node, rule, allowChildren, controlComponents)
+    );
   }
 
   if (Array.isArray(required.every)) {
-    return required.every.every((rule) => validate(node, rule, allowChildren));
+    return required.every.every((rule) =>
+      validate(node, rule, allowChildren, controlComponents)
+    );
   }
 
-  return validate(node, required, allowChildren);
+  return validate(node, required, allowChildren, controlComponents);
 };
 
 module.exports = {
@@ -67,6 +73,13 @@ module.exports = {
             },
             uniqueItems: true
           },
+          controlComponents: {
+            type: "array",
+            items: {
+              type: "string"
+            },
+            uniqueItems: true
+          },
           required: {
             oneOf: [
               {
@@ -116,12 +129,13 @@ module.exports = {
         const {
           allowChildren = false,
           components = [],
+          controlComponents = [],
           required = { every: ["nesting", "id"] }
         } = context.options[0] || {};
 
         if (
           ["label"].concat(components).includes(getElementType(node)) &&
-          !isValidLabel(node, required, allowChildren)
+          !isValidLabel(node, required, allowChildren, controlComponents)
         ) {
           context.report({ node, messageId: "default" });
         }


Test patch:
diff --git a/src/rules/__tests__/label-has-for.test.js b/src/rules/__tests__/label-has-for.test.js
index 25a39a4..d161129 100644
--- a/src/rules/__tests__/label-has-for.test.js
+++ b/src/rules/__tests__/label-has-for.test.js
@@ -13,6 +13,10 @@ makeRuleTester("label-has-for", rule, {
     {
       code: "<label for='id'><slot /></label>",
       options: [{ allowChildren: true }]
+    },
+    {
+      code: "<label for='id'><VInput /></label>",
+      options: [{ controlComponents: ["VInput"] }]
     }
   ],
   invalid: [
@@ -21,8 +25,8 @@ makeRuleTester("label-has-for", rule, {
     "<label for='id'><slot /></label>",
     "<label for='id'><div /></label>",
     {
-      code: "<Label for='id' />",
-      options: [{ components: ["Label"] }],
+      code: "<VLabel for='id' />",
+      options: [{ components: ["VLabel"] }],
       errors: [{ messageId: "default" }]
     }
   ]
diff --git a/src/rules/__tests__/makeRuleTester.js b/src/rules/__tests__/makeRuleTester.js
index 7e1986b..b1211af 100644
--- a/src/rules/__tests__/makeRuleTester.js
+++ b/src/rules/__tests__/makeRuleTester.js
@@ -8,7 +8,7 @@ const makeValidExample = (example) => {
     return { filename, code: makeTemplate(example) };
   }
 
-  return Object.assign(example, { filename });
+  return Object.assign(example, { filename, code: makeTemplate(example.code) });
 };
 
 const makeInvalidExample = (example) => {