@@ -22,15 +22,18 @@ public function evaluate(Tokens $tokens, int $tokenIndex, callable $emit): void
2222 return ;
2323 }
2424
25- // Extended mapping of some roles to required attributes (non-exhaustive)
25+ // Extended mapping of some roles to required attributes (non-exhaustive).
26+ // Each entry is a list of OR-groups: the role is valid when at least one attribute
27+ // in each group is present. A group with a single entry means that one attribute is mandatory.
28+ // A group with multiple entries means any one of them is sufficient.
2629 $ requiredMap = [
27- 'img ' => ['alt ' ],
28- 'link ' => ['href ' ],
29- 'textbox ' => ['aria-label ' , 'aria-labelledby ' ],
30- 'combobox ' => ['aria-controls ' ],
30+ 'img ' => [[ 'alt ' ] ],
31+ 'link ' => [[ 'href ' ] ],
32+ 'textbox ' => [[ 'aria-label ' , 'aria-labelledby ' ]], // either is acceptable
33+ 'combobox ' => [[ 'aria-controls ' ] ],
3134 'button ' => [],
32- 'checkbox ' => ['aria-checked ' ],
33- 'radio ' => ['aria-checked ' ],
35+ 'checkbox ' => [[ 'aria-checked ' ] ],
36+ 'radio ' => [[ 'aria-checked ' ] ],
3437 ];
3538
3639 if (preg_match_all ('/<([a-z0-9]+)([^>]*)>/i ' , $ full , $ tags , PREG_SET_ORDER )) {
@@ -39,12 +42,23 @@ public function evaluate(Tokens $tokens, int $tokenIndex, callable $emit): void
3942 if (preg_match ('/role\s*=\s*(?:"| \')([^" \']+)(?:"| \')/i ' , $ attrs , $ m )) {
4043 $ role = strtolower ($ m [1 ]);
4144 if (isset ($ requiredMap [$ role ])) {
42- foreach ($ requiredMap [$ role ] as $ attr ) {
43- if (!preg_match ('/\b ' .preg_quote ($ attr , '/ ' ).'\s*=\s*(?:"| \')/i ' , $ attrs )) {
45+ foreach ($ requiredMap [$ role ] as $ group ) {
46+ // The group is satisfied when at least one of its attributes is present
47+ $ satisfied = false ;
48+ foreach ($ group as $ attr ) {
49+ if (preg_match ('/\b ' .preg_quote ($ attr , '/ ' ).'\s*=\s*(?:"| \')/i ' , $ attrs )) {
50+ $ satisfied = true ;
51+
52+ break ;
53+ }
54+ }
55+
56+ if (!$ satisfied ) {
4457 $ tokenRef = $ tokens ->get (0 );
45- $ emit (sprintf ('Role "%s" requires attribute "%s". ' , $ role , $ attr ), $ tokenRef , 'AriaRequired.Missing ' );
58+ $ missing = implode ('" or " ' , $ group );
59+ $ emit (sprintf ('Role "%s" requires attribute "%s". ' , $ role , $ missing ), $ tokenRef , 'AriaRequired.Missing ' );
4660
47- // stop after first missing attribute found for test determinism
61+ // stop after first missing group found for test determinism
4862 return ;
4963 }
5064 }
0 commit comments