From f22711e9993dc3d1a4d7677b2c402a6e7e64d4f2 Mon Sep 17 00:00:00 2001
From: Jesse Renee Beach <splendidnoise@gmail.com>
Date: Tue, 3 Jul 2018 19:04:53 -0700
Subject: [PATCH] [#454] Fix for aria-proptypes rule failure

---
 CHANGELOG.md                               |  4 +++
 __tests__/src/rules/aria-proptypes-test.js | 38 ++++++++++++++++++++++
 src/rules/aria-proptypes.js                |  8 +++++
 3 files changed, 50 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2de3700ae..7bd7854e7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+6.1.1 / 2018-07-03
+
+- [fix] aria-proptypes support for idlist, #454
+
 6.1.0 / 2018-06-26
 ==================
 - [new] Support for eslint v5, #451
diff --git a/__tests__/src/rules/aria-proptypes-test.js b/__tests__/src/rules/aria-proptypes-test.js
index 5a14beb23..a6c28574c 100644
--- a/__tests__/src/rules/aria-proptypes-test.js
+++ b/__tests__/src/rules/aria-proptypes-test.js
@@ -35,6 +35,10 @@ const errorMessage = (name) => {
     case 'tokenlist':
       return `The value for ${name} must be a list of one or more \
 tokens from the following: ${permittedValues}.`;
+    case 'idlist':
+      return `The value for ${name} must be a list of strings that represent DOM element IDs (idlist)`;
+    case 'id':
+      return `The value for ${name} must be a string that represents a DOM element ID`;
     case 'boolean':
     case 'string':
     case 'integer':
@@ -155,6 +159,40 @@ ruleTester.run('aria-proptypes', rule, {
     { code: '<div aria-relevant={`removals additions text all`} />' },
     { code: '<div aria-relevant={foo} />' },
     { code: '<div aria-relevant={foo.bar} />' },
+
+    // ID
+    { code: '<div aria-activedescendant="ascending" />' },
+    { code: '<div aria-activedescendant="ASCENDING" />' },
+    { code: '<div aria-activedescendant={"ascending"} />' },
+    { code: '<div aria-activedescendant={`ascending`} />' },
+    { code: '<div aria-activedescendant="descending" />' },
+    { code: '<div aria-activedescendant={"descending"} />' },
+    { code: '<div aria-activedescendant={`descending`} />' },
+    { code: '<div aria-activedescendant="none" />' },
+    { code: '<div aria-activedescendant={"none"} />' },
+    { code: '<div aria-activedescendant={`none`} />' },
+    { code: '<div aria-activedescendant="other" />' },
+    { code: '<div aria-activedescendant={"other"} />' },
+    { code: '<div aria-activedescendant={`other`} />' },
+    { code: '<div aria-activedescendant={foo} />' },
+    { code: '<div aria-activedescendant={foo.bar} />' },
+
+    // IDLIST
+    { code: '<div aria-labelledby="additions" />' },
+    { code: '<div aria-labelledby={"additions"} />' },
+    { code: '<div aria-labelledby={`additions`} />' },
+    { code: '<div aria-labelledby="additions removals" />' },
+    { code: '<div aria-labelledby="additions additions" />' },
+    { code: '<div aria-labelledby={"additions removals"} />' },
+    { code: '<div aria-labelledby={`additions removals`} />' },
+    { code: '<div aria-labelledby="additions removals text" />' },
+    { code: '<div aria-labelledby={"additions removals text"} />' },
+    { code: '<div aria-labelledby={`additions removals text`} />' },
+    { code: '<div aria-labelledby="additions removals text all" />' },
+    { code: '<div aria-labelledby={"additions removals text all"} />' },
+    { code: '<div aria-labelledby={`removals additions text all`} />' },
+    { code: '<div aria-labelledby={foo} />' },
+    { code: '<div aria-labelledby={foo.bar} />' },
   ].map(parserOptionsMapper),
   invalid: [
     // BOOLEAN
diff --git a/src/rules/aria-proptypes.js b/src/rules/aria-proptypes.js
index be7d3ad9e..caad30463 100644
--- a/src/rules/aria-proptypes.js
+++ b/src/rules/aria-proptypes.js
@@ -20,6 +20,10 @@ const errorMessage = (name, type, permittedValues) => {
     case 'tokenlist':
       return `The value for ${name} must be a list of one or more \
 tokens from the following: ${permittedValues}.`;
+    case 'idlist':
+      return `The value for ${name} must be a list of strings that represent DOM element IDs (idlist)`;
+    case 'id':
+      return `The value for ${name} must be a string that represents a DOM element ID`;
     case 'boolean':
     case 'string':
     case 'integer':
@@ -34,6 +38,7 @@ const validityCheck = (value, expectedType, permittedValues) => {
     case 'boolean':
       return typeof value === 'boolean';
     case 'string':
+    case 'id':
       return typeof value === 'string';
     case 'tristate':
       return typeof value === 'boolean' || value === 'mixed';
@@ -44,6 +49,9 @@ const validityCheck = (value, expectedType, permittedValues) => {
       return typeof value !== 'boolean' && isNaN(Number(value)) === false;
     case 'token':
       return permittedValues.indexOf(typeof value === 'string' ? value.toLowerCase() : value) > -1;
+    case 'idlist':
+      return typeof value === 'string'
+        && value.split(' ').every(token => validityCheck(token, 'id', []));
     case 'tokenlist':
       return typeof value === 'string'
         && value.split(' ').every(token => permittedValues.indexOf(token.toLowerCase()) > -1);