From 59c84766f89c17f2da8b4f5212e38267c6922191 Mon Sep 17 00:00:00 2001 From: Nurassyl Zekenov Date: Wed, 1 Dec 2021 17:58:01 +0300 Subject: [PATCH 1/5] Add aria prop type list --- src/compiler/compile/nodes/Element.ts | 79 +++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 35131cefcfd..061de3c01de 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -46,6 +46,85 @@ const a11y_required_attributes = { object: ['title', 'aria-label', 'aria-labelledby'] }; +// Full list of attributes from https://www.w3.org/TR/wai-aria +const a11y_attribute_types = new Map([ + ['activedescendant', { type: 'id'}], + ['atomic', { type: 'boolean' }], + ['autocomplete', { + type: 'token', + whitelist: 'inline list both none'.split(' ') + }], + ['busy', { type: 'boolean'}], + ['checked', { type: 'tristate' }], + ['colcount', { type: 'integer'}], + ['colindex', { type: 'integer'}], + ['colspan', { type: 'integer'}], + ['controls', { type: 'idlist'}], + ['current', { + type: 'token', + whitelist: 'page step location date time true false'.split(' ') + }], + ['details', { type: 'id'}], + ['describedby', { type: 'idlist'}], + ['disabled', { type: 'boolean'}], + ['dropeffect', { + type: 'tokenlist', + whitelist: 'copy execute link move none popup'.split(' ') + }], + ['errormessage', { type: 'id'}], + ['expanded', { type: 'boolean_or_undefined' }], + ['flowto', { type: 'idlist'}], + ['grabbed', { type: 'boolean_or_undefined'}], + ['haspopup', { + type: 'token', + whitelist: 'false true menu listbox tree grid dialog'.split(' ') + }], + ['hidden', { type: 'boolean_or_undefined' }], + ['invalid', { + type: 'token', + whitelist: 'grammar false spelling true'.split(' ') + }], + ['keyshortcuts', { type: 'string' }], + ['label', { type: 'string' }], + ['labelledby', { type: 'idlist' }], + ['level', { type: 'integer' }], + ['live', { + type: 'token', + whitelist: 'assertive off polite'.split(' ') + }], + ['modal', { type: 'boolean' }], + ['multiline', { type: 'boolean' }], + ['multiselectable', { type: 'boolean' }], + ['orientation', { + type: 'token', + whitelist: 'vertical undefined horizontal'.split(' ') + }], + ['owns', { type: 'idlist' }], + ['placeholder', { type: 'string' }], + ['posinset', { type: 'integer' }], + ['pressed', { type: 'tristate' }], + ['readonly', { type: 'boolean' }], + ['relevant', { + type: 'tokenlist', + whitelist: 'additions all removals text'.split(' ') + }], + ['required', { type: 'boolean' }], + ['roledescription', { type: 'string' }], + ['rowcount', { type: 'integer' }], + ['rowindex', { type: 'integer' }], + ['rowspan', { type: 'integer' }], + ['selected', { type: 'boolean_or_undefined' }], + ['setsize', { type: 'integer' }], + ['sort', { + type: 'token', + whitelist: 'ascending descending none other'.split(' ') + }], + ['valuemax', { type: 'number' }], + ['valuemin', { type: 'number' }], + ['valuenow', { type: 'number' }], + ['valuetext', { type: 'string' }] +]); + const a11y_distracting_elements = new Set([ 'blink', 'marquee' From abfcbebd3ce48a3b64c255196668dafced551f9e Mon Sep 17 00:00:00 2001 From: kwangure Date: Wed, 1 Dec 2021 18:48:23 +0300 Subject: [PATCH 2/5] feat: check aria attribute types --- src/compiler/compile/compiler_warnings.ts | 34 ++++++++++++++++++- src/compiler/compile/nodes/Element.ts | 41 +++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/compiler/compile/compiler_warnings.ts b/src/compiler/compile/compiler_warnings.ts index 833722c3b95..db7714d9f60 100644 --- a/src/compiler/compile/compiler_warnings.ts +++ b/src/compiler/compile/compiler_warnings.ts @@ -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. . To hide this warning, use ' + message: 'No custom element \'tag\' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. . To hide this warning, use ' }, unused_export_let: (component: string, property: string) => ({ code: 'unused-export-let', @@ -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}'?)` : '') diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 061de3c01de..41e5514ffcb 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -256,6 +256,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; @@ -510,6 +539,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 From 4af29a9a55f9923ed007b8316c52647d2dd76d33 Mon Sep 17 00:00:00 2001 From: kwangure Date: Thu, 2 Dec 2021 08:51:02 +0300 Subject: [PATCH 3/5] feat: add proptype tests --- .../input.svelte | 8 ++ .../warnings.json | 32 ++++++++ .../a11y-aria-proptypes-boolean/input.svelte | 8 ++ .../a11y-aria-proptypes-boolean/warnings.json | 32 ++++++++ .../a11y-aria-proptypes-integer/input.svelte | 7 ++ .../a11y-aria-proptypes-integer/warnings.json | 47 +++++++++++ .../a11y-aria-proptypes-number/input.svelte | 7 ++ .../a11y-aria-proptypes-number/warnings.json | 47 +++++++++++ .../a11y-aria-proptypes-string/input.svelte | 5 ++ .../a11y-aria-proptypes-string/warnings.json | 17 ++++ .../a11y-aria-proptypes-token/input.svelte | 6 ++ .../a11y-aria-proptypes-token/warnings.json | 62 +++++++++++++++ .../input.svelte | 7 ++ .../warnings.json | 77 +++++++++++++++++++ .../a11y-aria-proptypes-tristate/input.svelte | 8 ++ .../warnings.json | 32 ++++++++ 16 files changed, 402 insertions(+) create mode 100644 test/validator/samples/a11y-aria-proptypes-boolean-or-undefined/input.svelte create mode 100644 test/validator/samples/a11y-aria-proptypes-boolean-or-undefined/warnings.json create mode 100644 test/validator/samples/a11y-aria-proptypes-boolean/input.svelte create mode 100644 test/validator/samples/a11y-aria-proptypes-boolean/warnings.json create mode 100644 test/validator/samples/a11y-aria-proptypes-integer/input.svelte create mode 100644 test/validator/samples/a11y-aria-proptypes-integer/warnings.json create mode 100644 test/validator/samples/a11y-aria-proptypes-number/input.svelte create mode 100644 test/validator/samples/a11y-aria-proptypes-number/warnings.json create mode 100644 test/validator/samples/a11y-aria-proptypes-string/input.svelte create mode 100644 test/validator/samples/a11y-aria-proptypes-string/warnings.json create mode 100644 test/validator/samples/a11y-aria-proptypes-token/input.svelte create mode 100644 test/validator/samples/a11y-aria-proptypes-token/warnings.json create mode 100644 test/validator/samples/a11y-aria-proptypes-tokenlist/input.svelte create mode 100644 test/validator/samples/a11y-aria-proptypes-tokenlist/warnings.json create mode 100644 test/validator/samples/a11y-aria-proptypes-tristate/input.svelte create mode 100644 test/validator/samples/a11y-aria-proptypes-tristate/warnings.json diff --git a/test/validator/samples/a11y-aria-proptypes-boolean-or-undefined/input.svelte b/test/validator/samples/a11y-aria-proptypes-boolean-or-undefined/input.svelte new file mode 100644 index 00000000000..c52e60008f3 --- /dev/null +++ b/test/validator/samples/a11y-aria-proptypes-boolean-or-undefined/input.svelte @@ -0,0 +1,8 @@ + + +
+
+
+
\ No newline at end of file diff --git a/test/validator/samples/a11y-aria-proptypes-boolean-or-undefined/warnings.json b/test/validator/samples/a11y-aria-proptypes-boolean-or-undefined/warnings.json new file mode 100644 index 00000000000..503d98f5d8e --- /dev/null +++ b/test/validator/samples/a11y-aria-proptypes-boolean-or-undefined/warnings.json @@ -0,0 +1,32 @@ +[ + { + "code": "a11y-incorrect-aria-attribute-type", + "end": { + "character": 63, + "column": 22, + "line": 5 + }, + "message": "A11y: The value of 'aria-hidden' must be exactly one of true, false, or undefined", + "pos": 46, + "start": { + "character": 46, + "column": 5, + "line": 5 + } + }, + { + "code": "a11y-incorrect-aria-attribute-type", + "end": { + "character": 87, + "column": 21, + "line": 6 + }, + "message": "A11y: The value of 'aria-hidden' must be exactly one of true, false, or undefined", + "pos": 71, + "start": { + "character": 71, + "column": 5, + "line": 6 + } + } +] \ No newline at end of file diff --git a/test/validator/samples/a11y-aria-proptypes-boolean/input.svelte b/test/validator/samples/a11y-aria-proptypes-boolean/input.svelte new file mode 100644 index 00000000000..c9929282296 --- /dev/null +++ b/test/validator/samples/a11y-aria-proptypes-boolean/input.svelte @@ -0,0 +1,8 @@ + + +