Skip to content

Commit 3f2f1e5

Browse files
feat: add a11y autocomplete-valid (#8520)
Part of #820 --------- Co-authored-by: Simon Holthausen <[email protected]>
1 parent 83679e9 commit 3f2f1e5

File tree

6 files changed

+183
-1
lines changed

6 files changed

+183
-1
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
* Update interpolated style directive properly when using spread ([#8438](https://github.com/sveltejs/svelte/issues/8438))
88
* Remove style directive property when value is `undefined` ([#8462](https://github.com/sveltejs/svelte/issues/8462))
99
* Ensure version is typed as `string` instead of the literal `__VERSION__` ([#8498](https://github.com/sveltejs/svelte/issues/8498))
10+
* Add `a11y-autocomplete-valid` warning ([#8520](https://github.com/sveltejs/svelte/pull/8520))
11+
* Handle nested array rest destructuring ([#8554](https://github.com/sveltejs/svelte/issues/8554), [#8552](https://github.com/sveltejs/svelte/issues/8552))
12+
* Add `fullscreenElement` and `visibilityState` bindings for `<svelte:document>` ([#8507](https://github.com/sveltejs/svelte/pull/8507))
13+
* Add `devicePixelRatio` binding for `<svelte:window>` ([#8285](https://github.com/sveltejs/svelte/issues/8285))
14+
* Relax `a11y-no-redundant-roles` warning ([#8536](https://github.com/sveltejs/svelte/pull/8536))
1015

1116
## 3.58.0
1217

src/compiler/compile/compiler_warnings.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ export default {
162162
code: 'a11y-missing-attribute',
163163
message: `A11y: <${name}> element should have ${article} ${sequence} attribute`
164164
}),
165+
a11y_autocomplete_valid: (type: null | true | string, value: null | true | string) => ({
166+
code: 'a11y-autocomplete-valid',
167+
message: `A11y: The value '${value}' is not supported by the attribute 'autocomplete' on element <input type="${type}">`
168+
}),
165169
a11y_img_redundant_alt: {
166170
code: 'a11y-img-redundant-alt',
167171
message: 'A11y: Screenreaders already announce <img> elements as an image.'

src/compiler/compile/nodes/Element.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { Literal } from 'estree';
2525
import compiler_warnings from '../compiler_warnings';
2626
import compiler_errors from '../compiler_errors';
2727
import { ARIARoleDefinitionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } from 'aria-query';
28-
import { is_interactive_element, is_non_interactive_element, is_non_interactive_roles, is_presentation_role, is_interactive_roles, is_hidden_from_screen_reader, is_semantic_role_element, is_abstract_role, is_static_element, has_disabled_attribute } from '../utils/a11y';
28+
import { is_interactive_element, is_non_interactive_element, is_non_interactive_roles, is_presentation_role, is_interactive_roles, is_hidden_from_screen_reader, is_semantic_role_element, is_abstract_role, is_static_element, has_disabled_attribute, is_valid_autocomplete } from '../utils/a11y';
2929

3030
const aria_attributes = 'activedescendant atomic autocomplete busy checked colcount colindex colspan controls current describedby description details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowcount rowindex rowspan selected setsize sort valuemax valuemin valuenow valuetext'.split(' ');
3131
const aria_attribute_set = new Set(aria_attributes);
@@ -849,6 +849,18 @@ export default class Element extends Node {
849849
should_have_attribute(this, required_attributes, 'input type="image"');
850850
}
851851
}
852+
853+
// autocomplete-valid
854+
const autocomplete = attribute_map.get('autocomplete');
855+
856+
if (type && autocomplete) {
857+
const type_value = type.get_static_value();
858+
const autocomplete_value = autocomplete.get_static_value();
859+
860+
if (!is_valid_autocomplete(type_value, autocomplete_value)) {
861+
component.warn(autocomplete, compiler_warnings.a11y_autocomplete_valid(type_value, autocomplete_value));
862+
}
863+
}
852864
}
853865

854866
if (this.name === 'img') {

src/compiler/compile/utils/a11y.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
} from 'aria-query';
77
import { AXObjects, AXObjectRoles, elementAXObjects } from 'axobject-query';
88
import Attribute from '../nodes/Attribute';
9+
import { regex_whitespaces } from '../../utils/patterns';
910

1011
const aria_roles = roles_map.keys();
1112
const abstract_roles = new Set(aria_roles.filter(role => roles_map.get(role).abstract));
@@ -223,3 +224,104 @@ export function is_semantic_role_element(role: ARIARoleDefinitionKey, tag_name:
223224
}
224225
return false;
225226
}
227+
228+
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofilling-form-controls:-the-autocomplete-attribute
229+
const address_type_tokens = new Set(['shipping', 'billing']);
230+
const autofill_field_name_tokens = new Set([
231+
'',
232+
'on',
233+
'off',
234+
'name',
235+
'honorific-prefix',
236+
'given-name',
237+
'additional-name',
238+
'family-name',
239+
'honorific-suffix',
240+
'nickname',
241+
'username',
242+
'new-password',
243+
'current-password',
244+
'one-time-code',
245+
'organization-title',
246+
'organization',
247+
'street-address',
248+
'address-line1',
249+
'address-line2',
250+
'address-line3',
251+
'address-level4',
252+
'address-level3',
253+
'address-level2',
254+
'address-level1',
255+
'country',
256+
'country-name',
257+
'postal-code',
258+
'cc-name',
259+
'cc-given-name',
260+
'cc-additional-name',
261+
'cc-family-name',
262+
'cc-number',
263+
'cc-exp',
264+
'cc-exp-month',
265+
'cc-exp-year',
266+
'cc-csc',
267+
'cc-type',
268+
'transaction-currency',
269+
'transaction-amount',
270+
'language',
271+
'bday',
272+
'bday-day',
273+
'bday-month',
274+
'bday-year',
275+
'sex',
276+
'url',
277+
'photo'
278+
]);
279+
const contact_type_tokens = new Set(['home', 'work', 'mobile', 'fax', 'pager']);
280+
const autofill_contact_field_name_tokens = new Set([
281+
'tel',
282+
'tel-country-code',
283+
'tel-national',
284+
'tel-area-code',
285+
'tel-local',
286+
'tel-local-prefix',
287+
'tel-local-suffix',
288+
'tel-extension',
289+
'email',
290+
'impp'
291+
]);
292+
293+
export function is_valid_autocomplete(type: null | true | string, autocomplete: null | true | string) {
294+
if (typeof autocomplete !== 'string' || typeof type !== 'string') {
295+
return false;
296+
}
297+
298+
const tokens = autocomplete.trim().toLowerCase().split(regex_whitespaces);
299+
300+
if (typeof tokens[0] === 'string' && tokens[0].startsWith('section-')) {
301+
tokens.shift();
302+
}
303+
304+
if (address_type_tokens.has(tokens[0])) {
305+
tokens.shift();
306+
}
307+
308+
if (autofill_field_name_tokens.has(tokens[0])) {
309+
tokens.shift();
310+
} else {
311+
if (contact_type_tokens.has(tokens[0])) {
312+
tokens.shift();
313+
}
314+
315+
if (autofill_contact_field_name_tokens.has(tokens[0])) {
316+
tokens.shift();
317+
} else {
318+
return false;
319+
}
320+
}
321+
322+
if (tokens[0] === 'webauthn') {
323+
tokens.shift();
324+
}
325+
326+
return tokens.length === 0;
327+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!-- VALID -->
2+
<input type="text" />
3+
<input type="text" autocomplete="name" />
4+
<input type="text" autocomplete="off" />
5+
<input type="text" autocomplete="on" />
6+
<input type="text" autocomplete="billing family-name" />
7+
<input type="hidden" autocomplete="section-blue shipping street-address" />
8+
<input type="text" autocomplete="section-somewhere shipping work email" />
9+
<input type="text" autocomplete="section-somewhere shipping work email webauthn" />
10+
<input type="text" autocomplete="SECTION-SOMEWHERE SHIPPING WORK EMAIL WEBAUTHN" />
11+
<input type="TEXT" autocomplete="ON" />
12+
<input type="email" autocomplete="url" />
13+
<input type="text" autocomplete="section-blue shipping street-address" />
14+
<input type="hidden" autocomplete="off" />
15+
<input type="hidden" autocomplete="on" />
16+
<input type="text" autocomplete="" />
17+
18+
<!-- INVALID -->
19+
<input type="text" autocomplete />
20+
<input type="text" autocomplete="incorrect" />
21+
<input type="text" autocomplete="webauthn" />
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
[
2+
{
3+
"code": "a11y-autocomplete-valid",
4+
"end": {
5+
"column": 31,
6+
"line": 19
7+
},
8+
"message": "A11y: The value 'true' is not supported by the attribute 'autocomplete' on element <input type=\"text\">",
9+
"start": {
10+
"column": 19,
11+
"line": 19
12+
}
13+
},
14+
{
15+
"code": "a11y-autocomplete-valid",
16+
"end": {
17+
"column": 43,
18+
"line": 20
19+
},
20+
"message": "A11y: The value 'incorrect' is not supported by the attribute 'autocomplete' on element <input type=\"text\">",
21+
"start": {
22+
"column": 19,
23+
"line": 20
24+
}
25+
},
26+
{
27+
"code": "a11y-autocomplete-valid",
28+
"end": {
29+
"column": 42,
30+
"line": 21
31+
},
32+
"message": "A11y: The value 'webauthn' is not supported by the attribute 'autocomplete' on element <input type=\"text\">",
33+
"start": {
34+
"column": 19,
35+
"line": 21
36+
}
37+
}
38+
]

0 commit comments

Comments
 (0)