Skip to content

Commit

Permalink
feat: check aria attribute types
Browse files Browse the repository at this point in the history
  • Loading branch information
kwangure committed Dec 2, 2021
1 parent 6ef4253 commit cf6641e
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 1 deletion.
34 changes: 33 additions & 1 deletion src/compiler/compile/compiler_warnings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
export default {
custom_element_no_tag: {
code: 'custom-element-no-tag',
message: 'No custom element \'tag\' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. <svelte:options tag="my-thing"/>. To hide this warning, use <svelte:options tag={null}/>'
message: 'No custom element \'tag\' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. <svelte:options tag="my-thing"/>. To hide this warning, use <svelte:options tag={null}/>'
},
unused_export_let: (component: string, property: string) => ({
code: 'unused-export-let',
Expand Down Expand Up @@ -60,6 +60,38 @@ export default {
code: 'a11y-aria-attributes',
message: `A11y: <${name}> should not have aria-* attributes`
}),
a11y_incorrect_attribute_type: (schema: any, attribute: string) => {
let message;
switch (schema.type) {
case 'boolean':
message = `The value of '${attribute}' must be exactly one of true or false`;
break;
case 'boolean_or_undefined':
message = `The value of '${attribute}' must be exactly one of true, false, or undefined`;
break;
case 'id':
message = `The value of '${attribute}' must be a string that represents a DOM element ID`;
break;
case 'idlist':
message = `The value of '${attribute}' must be a space-separated list of strings that represent DOM element IDs`;
break;
case 'tristate':
message = `The value of '${attribute}' must be exactly one of true, false, or mixed`;
break;
case 'token':
message = `The value of '${attribute}' must be exactly one of ${schema.whitelist.join(', ')}`;
break;
case 'tokenlist':
message = `The value of '${attribute}' must be a space-separated list of one or more of ${schema.whitelist.join(', ')}`;
break;
default:
message = `The value of '${attribute}' must be of type ${schema.type}`;
}
return {
code: 'a11y-incorrect-aria-attribute-type',
message: `A11y: ${message}`
};
},
a11y_unknown_aria_attribute: (attribute: string, suggestion?: string) => ({
code: 'a11y-unknown-aria-attribute',
message: `A11y: Unknown aria attribute 'aria-${attribute}'` + (suggestion ? ` (did you mean '${suggestion}'?)` : '')
Expand Down
41 changes: 41 additions & 0 deletions src/compiler/compile/nodes/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,35 @@ function get_namespace(parent: Element, element: Element, explicit_namespace: st
return parent_element.namespace;
}

function is_valid_aria_attribute_value(schema: any, value: any): boolean {
switch (schema.type) {
case 'boolean':
case 'boolean_or_undefined':
return typeof value === 'boolean';
case 'string':
return typeof value === 'string';
case 'id':
return typeof value === 'string';
case 'tristate':
return typeof value === 'boolean' || value === 'mixed';
case 'integer':
return typeof value !== 'boolean' && isNaN(Number(value)) === false;
case 'number':
return typeof value !== 'boolean' && isNaN(Number(value)) === false;
case 'token': // single token
return schema.whitelist
.indexOf(typeof value === 'string' ? value.toLowerCase() : value) > -1;
case 'idlist': // if list of ids, split each
return typeof value === 'string'
&& value.split(' ').every((id) => typeof id === 'string');
case 'tokenlist': // if list of tokens, split each
return typeof value === 'string'
&& value.split(' ').every((token) => schema.whitelist.indexOf(token.toLowerCase()) > -1);
default:
return false;
}
}

export default class Element extends Node {
type: 'Element';
name: string;
Expand Down Expand Up @@ -414,6 +443,18 @@ export default class Element extends Node {
if (name === 'aria-hidden' && /^h[1-6]$/.test(this.name)) {
component.warn(attribute, compiler_warnings.a11y_hidden(this.name));
}

// aria-proptypes
let value = attribute.get_static_value();
if (value === 'true') value = true;
if (value === 'false') value = false;

if (value !== null && value !== undefined && a11y_attribute_types.has(type)) {
const schema = a11y_attribute_types.get(type);
if (!is_valid_aria_attribute_value(schema, value)) {
component.warn(attribute, compiler_warnings.a11y_incorrect_attribute_type(schema, name));
}
}
}

// aria-role
Expand Down

0 comments on commit cf6641e

Please sign in to comment.