Repository: vue-a11y/eslint-plugin-vuejs-accessibility
Pull request #55 (2020-08-31 20:57:19)

Casing
Solves #45


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8bc2994..e25ab5f 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]
 
+### Changed
+
+- Ensure all rules that allow configuring extra components that should match certain conditions support both forms of casing (`VFoo` and `v-foo`).
+
 ## [0.4.0] - 2020-08-15
 
 ### Added
diff --git a/src/rules/anchor-has-content.js b/src/rules/anchor-has-content.js
index 80a3e8c..6660fd1 100644
--- a/src/rules/anchor-has-content.js
+++ b/src/rules/anchor-has-content.js
@@ -3,7 +3,8 @@ const {
   getElementType,
   hasAriaLabel,
   hasContent,
-  makeDocsURL
+  makeDocsURL,
+  makeKebabCase
 } = require("../utils");
 
 module.exports = {
@@ -32,7 +33,7 @@ module.exports = {
       VElement(node) {
         const { components = [] } = context.options[0] || {};
 
-        const elementTypes = ["a"].concat(components);
+        const elementTypes = ["a"].concat(components.map(makeKebabCase));
         const elementType = getElementType(node);
 
         if (
diff --git a/src/rules/heading-has-content.js b/src/rules/heading-has-content.js
index a37fda9..0b6fae1 100644
--- a/src/rules/heading-has-content.js
+++ b/src/rules/heading-has-content.js
@@ -2,7 +2,8 @@ const {
   defineTemplateBodyVisitor,
   getElementType,
   hasContent,
-  makeDocsURL
+  makeDocsURL,
+  makeKebabCase
 } = require("../utils");
 
 const headings = ["h1", "h2", "h3", "h4", "h5", "h6"];
@@ -33,7 +34,7 @@ module.exports = {
       VElement(node) {
         const { components = [] } = context.options[0] || {};
 
-        const elementTypes = headings.concat(components);
+        const elementTypes = headings.concat(components.map(makeKebabCase));
         const elementType = getElementType(node);
 
         if (elementTypes.includes(elementType) && !hasContent(node)) {
diff --git a/src/rules/label-has-for.js b/src/rules/label-has-for.js
index ccf3c19..13ab89f 100644
--- a/src/rules/label-has-for.js
+++ b/src/rules/label-has-for.js
@@ -3,7 +3,8 @@ const {
   getElementAttributeValue,
   getElementType,
   isHiddenFromScreenReader,
-  makeDocsURL
+  makeDocsURL,
+  makeKebabCase
 } = require("../utils");
 
 const controlTypes = ["input", "meter", "progress", "select", "textarea"];
@@ -131,9 +132,15 @@ module.exports = {
           required = { every: ["nesting", "id"] }
         } = context.options[0] || {};
 
+        const labelComponents = components.map(makeKebabCase).concat("label");
+        const options = {
+          allowChildren,
+          controlComponents: controlComponents.map(makeKebabCase)
+        };
+
         if (
-          ["label"].concat(components).includes(getElementType(node)) &&
-          !isValidLabel(node, required, { allowChildren, controlComponents })
+          labelComponents.includes(getElementType(node)) &&
+          !isValidLabel(node, required, options)
         ) {
           context.report({ node, messageId: "default" });
         }
diff --git a/src/rules/media-has-caption.js b/src/rules/media-has-caption.js
index 08bb7eb..ea9ca07 100644
--- a/src/rules/media-has-caption.js
+++ b/src/rules/media-has-caption.js
@@ -4,7 +4,8 @@ const {
   getElementAttribute,
   getElementAttributeValue,
   getElementType,
-  makeDocsURL
+  makeDocsURL,
+  makeKebabCase
 } = require("../utils");
 
 const isCaptionsTrackElement = (node) => {
@@ -46,7 +47,10 @@ module.exports = {
       VElement(node) {
         const { audio = [], track = [], video = [] } = context.options[0] || {};
 
-        const mediaElementTypes = audio.concat(video).concat("audio", "video");
+        const mediaElementTypes = audio
+          .concat(video)
+          .map(makeKebabCase)
+          .concat("audio", "video");
         if (!mediaElementTypes.includes(getElementType(node))) {
           return;
         }
@@ -56,7 +60,7 @@ module.exports = {
           return;
         }
 
-        const trackElementTypes = track.concat("track");
+        const trackElementTypes = track.map(makeKebabCase).concat("track");
         const trackElements = node.children.filter(
           (child) =>
             child.type === "VElement" &&
diff --git a/src/rules/no-distracting-elements.js b/src/rules/no-distracting-elements.js
index 554e920..142fc7e 100644
--- a/src/rules/no-distracting-elements.js
+++ b/src/rules/no-distracting-elements.js
@@ -1,7 +1,8 @@
 const {
   defineTemplateBodyVisitor,
   getElementType,
-  makeDocsURL
+  makeDocsURL,
+  makeKebabCase
 } = require("../utils");
 
 const defaultElements = ["marquee", "blink"];
@@ -34,7 +35,7 @@ module.exports = {
         const { elements = defaultElements } = context.options[0] || {};
         const elementType = getElementType(node);
 
-        if (elements.includes(elementType)) {
+        if (elements.map(makeKebabCase).includes(elementType)) {
           context.report({ node, messageId: "default", data: { elementType } });
         }
       }
diff --git a/src/utils.js b/src/utils.js
index 7a4e191..bbcbec8 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -16,5 +16,6 @@ module.exports = {
   isInteractiveElement: require("./utils/isInteractiveElement"),
   isPresentationRole: require("./utils/isPresentationRole"),
   makeDocsURL: require("./utils/makeDocsURL"),
+  makeKebabCase: require("./utils/makeKebabCase"),
   matchesElementRole: require("./utils/matchesElementRole")
 };
diff --git a/src/utils/getElementType.js b/src/utils/getElementType.js
index 8dafe1d..f376b38 100644
--- a/src/utils/getElementType.js
+++ b/src/utils/getElementType.js
@@ -1,6 +1,7 @@
 const getElementAttributeValue = require("./getElementAttributeValue");
+const makeKebabCase = require("./makeKebabCase");
 
 const getElementType = (node) =>
-  getElementAttributeValue(node, "is") || node.rawName;
+  makeKebabCase(getElementAttributeValue(node, "is") || node.rawName);
 
 module.exports = getElementType;
diff --git a/src/utils/makeKebabCase.js b/src/utils/makeKebabCase.js
new file mode 100644
index 0000000..80e43a0
--- /dev/null
+++ b/src/utils/makeKebabCase.js
@@ -0,0 +1,7 @@
+const makeKebabCase = (value) =>
+  value
+    .replace(/_/gu, "-")
+    .replace(/\B([A-Z])/gu, "-$1")
+    .toLowerCase();
+
+module.exports = makeKebabCase;


Test patch:
diff --git a/src/rules/__tests__/anchor-has-content.test.js b/src/rules/__tests__/anchor-has-content.test.js
index 7b0c8af..38a1690 100644
--- a/src/rules/__tests__/anchor-has-content.test.js
+++ b/src/rules/__tests__/anchor-has-content.test.js
@@ -8,14 +8,14 @@ makeRuleTester("anchor-has-content", rule, {
     "<a v-text='msg' />",
     "<a v-html='msg' />",
     "<a><slot /></a>",
-    "<Anchor  />",
+    "<VAnchor  />",
     "<a aria-label='This is my label' />"
   ],
   invalid: [
     "<a />",
     {
-      code: "<Anchor  />",
-      options: [{ components: ["Anchor"] }],
+      code: "<v-anchor  />",
+      options: [{ components: ["VAnchor"] }],
       errors: [{ messageId: "default" }]
     }
   ]
diff --git a/src/rules/__tests__/heading-has-content.test.js b/src/rules/__tests__/heading-has-content.test.js
index 0537567..d9ca15c 100644
--- a/src/rules/__tests__/heading-has-content.test.js
+++ b/src/rules/__tests__/heading-has-content.test.js
@@ -14,6 +14,11 @@ makeRuleTester("heading-has-content", rule, {
     "<h1 />",
     "<h1><span /></h1>",
     "<h1><span aria-hidden='true'>test</span></h1>",
-    "<h1>   </h1>"
+    "<h1>   </h1>",
+    {
+      code: "<v-heading />",
+      options: [{ components: ["VHeading"] }],
+      errors: [{ messageId: "default" }]
+    }
   ]
 });
diff --git a/src/rules/__tests__/label-has-for.test.js b/src/rules/__tests__/label-has-for.test.js
index 710fe13..b040b6d 100644
--- a/src/rules/__tests__/label-has-for.test.js
+++ b/src/rules/__tests__/label-has-for.test.js
@@ -19,7 +19,7 @@ makeRuleTester("label-has-for", rule, {
       options: [{ controlComponents: ["VInput"] }]
     },
     {
-      code: "<label for='id'><VInput /></label>",
+      code: "<label for='id'><v-input /></label>",
       options: [{ controlComponents: ["VInput"] }]
     }
   ],
@@ -31,7 +31,7 @@ makeRuleTester("label-has-for", rule, {
     "<label for='id'><VInput /></label>",
     "<label for='id'><div><VInput /></div></label>",
     {
-      code: "<VLabel for='id' />",
+      code: "<v-label for='id' />",
       options: [{ components: ["VLabel"] }],
       errors: [{ messageId: "default" }]
     }
diff --git a/src/rules/__tests__/media-has-caption.test.js b/src/rules/__tests__/media-has-caption.test.js
index 8916369..511eb24 100644
--- a/src/rules/__tests__/media-has-caption.test.js
+++ b/src/rules/__tests__/media-has-caption.test.js
@@ -3,9 +3,9 @@ const makeRuleTester = require("./makeRuleTester");
 
 const options = [
   {
-    audio: ["Audio"],
-    video: ["Video"],
-    track: ["Track"]
+    audio: ["VAudio"],
+    video: ["VVideo"],
+    track: ["VTrack"]
   }
 ];
 
@@ -23,43 +23,43 @@ makeRuleTester("media-has-caption", rule, {
     "<video muted='true' />",
     "<video muted />",
     {
-      code: "<Audio><track kind='captions' /></Audio>",
+      code: "<VAudio><track kind='captions' /></VAudio>",
       options
     },
     {
-      code: "<audio><Track kind='captions' /></audio>",
+      code: "<audio><VTrack kind='captions' /></audio>",
       options
     },
     {
-      code: "<Video><track kind='captions' /></Video>",
+      code: "<VVideo><track kind='captions' /></VVideo>",
       options
     },
     {
-      code: "<video><Track kind='captions' /></video>",
+      code: "<video><VTrack kind='captions' /></video>",
       options
     },
     {
-      code: "<Audio><Track kind='captions' /></Audio>",
+      code: "<VAudio><VTrack kind='captions' /></VAudio>",
       options
     },
     {
-      code: "<Video><Track kind='captions' /></Video>",
+      code: "<VVideo><VTrack kind='captions' /></VVideo>",
       options
     },
     {
-      code: "<Video muted />",
+      code: "<VVideo muted />",
       options
     },
     {
-      code: "<Video muted='true' />",
+      code: "<VVideo muted='true' />",
       options
     },
     {
-      code: "<Audio muted />",
+      code: "<VAudio muted />",
       options
     },
     {
-      code: "<Audio muted='true' />",
+      code: "<VAudio muted='true' />",
       options
     }
   ],
@@ -70,12 +70,12 @@ makeRuleTester("media-has-caption", rule, {
     "<video><track /></video>",
     "<video><track kind='subtitles' /></video>",
     {
-      code: "<Audio muted='false' />",
+      code: "<VAudio muted='false' />",
       options,
       errors: [{ messageId: "default" }]
     },
     {
-      code: "<Video muted='false' />",
+      code: "<VVideo muted='false' />",
       options,
       errors: [{ messageId: "default" }]
     },
@@ -83,32 +83,37 @@ makeRuleTester("media-has-caption", rule, {
     "<audio>Foo</audio>",
     "<video>Foo</video>",
     {
-      code: "<Audio />",
+      code: "<VAudio />",
       options,
       errors: [{ messageId: "default" }]
     },
     {
-      code: "<Video />",
+      code: "<VVideo />",
       options,
       errors: [{ messageId: "default" }]
     },
     {
-      code: "<audio><Track /></audio>",
+      code: "<audio><VTrack /></audio>",
       options,
       errors: [{ messageId: "default" }]
     },
     {
-      code: "<video><Track /></video>",
+      code: "<video><VTrack /></video>",
       options,
       errors: [{ messageId: "default" }]
     },
     {
-      code: "<Audio><Track kind='subtitles' /></Audio>",
+      code: "<VAudio><VTrack kind='subtitles' /></VAudio>",
       options,
       errors: [{ messageId: "default" }]
     },
     {
-      code: "<Video><Track kind='subtitles' /></Video>",
+      code: "<VVideo><VTrack kind='subtitles' /></VVideo>",
+      options,
+      errors: [{ messageId: "default" }]
+    },
+    {
+      code: "<v-video><v-track kind='subtitles' /></v-video>",
       options,
       errors: [{ messageId: "default" }]
     }
diff --git a/src/rules/__tests__/no-distracting-elements.test.js b/src/rules/__tests__/no-distracting-elements.test.js
index 30c91ed..ece06af 100644
--- a/src/rules/__tests__/no-distracting-elements.test.js
+++ b/src/rules/__tests__/no-distracting-elements.test.js
@@ -11,6 +11,11 @@ makeRuleTester("no-distracting-elements", rule, {
     {
       code: "<marquee />",
       errors: [{ messageId: "default", data: { elementType: "marquee" } }]
+    },
+    {
+      code: "<v-distract />",
+      options: [{ elements: ["VDistract"] }],
+      errors: [{ messageId: "default", data: { elementType: "v-distract" } }]
     }
   ]
 });