-
Notifications
You must be signed in to change notification settings - Fork 235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add new rule no-invalid-aria-attributes
#2276
Merged
bmish
merged 11 commits into
ember-template-lint:master
from
judithhinlung:no-invalid-aria-attributes
Jan 18, 2022
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
0763126
Add new rule `no-invalid-aria-attributes`
judithhinlung ed6d1ad
Removed intermediary helper to make code more efficient
judithhinlung 2f56850
Updated README formatting
judithhinlung d241316
Updated docs
judithhinlung 7658a0b
Updated rule to import from aria-query
judithhinlung 729fafe
Added aria-attribute validation
judithhinlung 25ecb72
Merge branch 'master' into no-invalid-aria-attributes
judithhinlung 6db7ef2
Expanded test coverage
judithhinlung 7e31004
Merge branch 'master' into no-invalid-aria-attributes
judithhinlung f1748a7
Updated rule to be compatible with v4
judithhinlung 4d29ca3
added tests for attributes that allow `undefined` values, reordered t…
judithhinlung File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# no-invalid-aria-attributes | ||
|
||
ARIA attributes are used to provide an element with specific accessibility functions. An ARIA attribute is invalid if its name or values are either misspelled or do not currently exist in the [WAI-ARIA States and Properties spec](https://www.w3.org/WAI/PF/aria-1.1/states_and_properties). | ||
|
||
This rule disallows the use of invalid ARIA attributes. | ||
|
||
## Examples | ||
|
||
This rule **forbids** the following: | ||
|
||
```hbs | ||
<input type="text" aria-not-real="true" /> | ||
<div role="region" aria-live="bogus">Inaccessible live region</div>', | ||
<button type="submit" aria-invalid={{if this.foo "true" "woosh"}}>Submit</button> | ||
``` | ||
|
||
This rule **allows** the following: | ||
|
||
```hbs | ||
<input type="text" aria-required="true" /> | ||
<div role="region" aria-live="polite">Accessible live region</div>', | ||
<button type="submit" aria-invalid={{if this.hasNoSpellingErrors "false" "spelling"}}>Send now</button> | ||
``` | ||
|
||
## References | ||
|
||
- [Using ARIA, Roles, States, and Properties](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ export default { | |
'no-duplicate-landmark-elements': 'error', | ||
'no-empty-headings': 'error', | ||
'no-heading-inside-button': 'error', | ||
'no-invalid-aria-attributes': 'error', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I imagine we should add this to |
||
'no-invalid-interactive': 'error', | ||
'no-invalid-link-text': 'error', | ||
'no-invalid-link-title': 'error', | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import { aria } from 'aria-query'; | ||
|
||
import Rule from './_base.js'; | ||
|
||
const VALID_ARIA_ATTRIBUTES = aria; | ||
|
||
function createInvalidAriaAttributeMessage(name) { | ||
return `${name} is an unrecognized ARIA attribute.`; | ||
} | ||
|
||
function createInvalidAttributeTypeErrorMessage(name, type, permittedValues) { | ||
switch (type) { | ||
case 'tristate': | ||
return `The value for ${name} must be a boolean or the string "mixed".`; | ||
case 'token': | ||
return `The value for ${name} must be a single token from the following: ${permittedValues.join( | ||
', ' | ||
)}.`; | ||
case 'tokenlist': | ||
return `The value for ${name} must be a list of one or more tokens from the following: ${permittedValues.join( | ||
', ' | ||
)}.`; | ||
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 'integer': | ||
return `The value for ${name} must be an integer.`; | ||
default: | ||
return `The value for ${name} must be a ${type}.`; | ||
} | ||
} | ||
|
||
function isBoolean(value) { | ||
return typeof value === 'boolean' || value === 'true' || value === 'false'; | ||
} | ||
|
||
function isNumeric(value) { | ||
if (typeof value === 'string') { | ||
value = Number.parseInt(value, 10); | ||
judithhinlung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
return !isBoolean(value) && !Number.isNaN(value); | ||
} | ||
|
||
function validityCheck(expectedType, permittedValues, allowUndefined, value) { | ||
if (value === 'undefined') { | ||
return allowUndefined; | ||
} | ||
switch (expectedType) { | ||
case 'boolean': | ||
return isBoolean(value); | ||
case 'string': | ||
case 'id': | ||
return typeof value === 'string' && !isBoolean(value); | ||
case 'idlist': | ||
return ( | ||
typeof value === 'string' && | ||
value.split(' ').every((token) => validityCheck('id', [], false, token)) | ||
); | ||
case 'tristate': | ||
return isBoolean(value) || value === 'mixed'; | ||
case 'integer': | ||
case 'number': | ||
return isNumeric(value); | ||
case 'token': | ||
return typeof value === 'string' && permittedValues.includes(value); | ||
case 'tokenlist': | ||
return ( | ||
typeof value === 'string' && | ||
value.split(' ').every((token) => permittedValues.includes(token.toLowerCase())) | ||
); | ||
} | ||
} | ||
|
||
function getValuesFromMustache(mustacheNode) { | ||
let valuesList = []; | ||
if (['BooleanLiteral', 'NumberLiteral', 'StringLiteral'].includes(mustacheNode.path.type)) { | ||
valuesList.push(mustacheNode.path.original); | ||
} else if (mustacheNode.path.type === 'PathExpression') { | ||
if (mustacheNode.path.original === 'if' || mustacheNode.path.original === 'unless') { | ||
if (mustacheNode.params.length === 2 || mustacheNode.params.length === 3) { | ||
valuesList.push(mustacheNode.params[1].value); | ||
} | ||
if (mustacheNode.params.length === 3) { | ||
valuesList.push(mustacheNode.params[2].value); | ||
} | ||
} | ||
} | ||
return valuesList; | ||
} | ||
|
||
export default class NoInvalidAriaAttributes extends Rule { | ||
visitor() { | ||
return { | ||
ElementNode(node) { | ||
let foundAriaAttributes = []; | ||
for (const attribute of node.attributes) { | ||
if (attribute.name.startsWith('aria-')) { | ||
if (!VALID_ARIA_ATTRIBUTES.has(attribute.name)) { | ||
this.log({ | ||
message: createInvalidAriaAttributeMessage(attribute.name), | ||
node, | ||
}); | ||
return; | ||
} else { | ||
foundAriaAttributes.push(attribute); | ||
} | ||
} | ||
} | ||
for (let attribute of foundAriaAttributes) { | ||
let validAriaAttribute = VALID_ARIA_ATTRIBUTES.get(attribute.name); | ||
let expectedType = validAriaAttribute.type; | ||
let permittedValues = validAriaAttribute.values; | ||
let allowUndefined = validAriaAttribute.allowundefined || false; | ||
let isValidValue; | ||
if (attribute.value.type === 'MustacheStatement') { | ||
if (attribute.value.path) { | ||
let valuesList = getValuesFromMustache(attribute.value); | ||
if (valuesList.length === 0) { | ||
isValidValue = true; | ||
} else { | ||
for (let value of valuesList) { | ||
isValidValue = validityCheck( | ||
expectedType, | ||
permittedValues, | ||
allowUndefined, | ||
value | ||
); | ||
} | ||
} | ||
} | ||
} else { | ||
isValidValue = validityCheck( | ||
expectedType, | ||
permittedValues, | ||
allowUndefined, | ||
attribute.value.chars | ||
); | ||
} | ||
if (!isValidValue) { | ||
this.log({ | ||
message: createInvalidAttributeTypeErrorMessage( | ||
attribute.name, | ||
expectedType, | ||
permittedValues | ||
), | ||
node, | ||
}); | ||
} | ||
} | ||
}, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there anything else we can validate about ARIA attributes? Anything we validate about their values or what attributes they are used on? It's easier to make this rule comprehensive now and cover multiple things, since adding violations later will be a breaking change.