@markuplint/ml-spec 2.1.0 → 3.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/lib/dom-traverse/accname-computation.d.ts +1 -0
  2. package/lib/dom-traverse/accname-computation.js +8 -0
  3. package/lib/dom-traverse/focusability-computation.d.ts +1 -0
  4. package/lib/dom-traverse/focusability-computation.js +8 -0
  5. package/lib/dom-traverse/get-aria.d.ts +9 -0
  6. package/lib/dom-traverse/get-aria.js +54 -0
  7. package/lib/dom-traverse/get-attr-specs.d.ts +2 -0
  8. package/lib/dom-traverse/get-attr-specs.js +8 -0
  9. package/lib/dom-traverse/get-computed-role.d.ts +2 -0
  10. package/lib/dom-traverse/get-computed-role.js +170 -0
  11. package/lib/dom-traverse/get-explicit-role.d.ts +2 -0
  12. package/lib/dom-traverse/get-explicit-role.js +102 -0
  13. package/lib/dom-traverse/get-implicit-role.d.ts +3 -0
  14. package/lib/dom-traverse/get-implicit-role.js +37 -0
  15. package/lib/dom-traverse/get-non-presentational-ancestor.d.ts +5 -0
  16. package/lib/dom-traverse/get-non-presentational-ancestor.js +21 -0
  17. package/lib/dom-traverse/get-permitted-roles.d.ts +5 -0
  18. package/lib/dom-traverse/get-permitted-roles.js +8 -0
  19. package/lib/dom-traverse/get-spec.d.ts +2 -0
  20. package/lib/dom-traverse/get-spec.js +8 -0
  21. package/lib/dom-traverse/has-required-owned-elements.d.ts +3 -0
  22. package/lib/dom-traverse/has-required-owned-elements.js +81 -0
  23. package/lib/dom-traverse/may-be-focusable.d.ts +2 -0
  24. package/lib/dom-traverse/may-be-focusable.js +22 -0
  25. package/lib/index.d.ts +17 -6
  26. package/lib/index.js +17 -6
  27. package/lib/specs/aria-spec.d.ts +5 -0
  28. package/lib/specs/aria-spec.js +12 -0
  29. package/lib/specs/aria-specs.d.ts +6 -0
  30. package/lib/specs/aria-specs.js +8 -0
  31. package/lib/specs/content-model-category-to-tag-names.d.ts +3 -0
  32. package/lib/specs/content-model-category-to-tag-names.js +15 -0
  33. package/lib/specs/get-aria.d.ts +3 -0
  34. package/lib/specs/get-aria.js +71 -0
  35. package/lib/specs/get-attr-specs.d.ts +8 -0
  36. package/lib/{get-attr-specs.js → specs/get-attr-specs.js} +8 -6
  37. package/lib/specs/get-implicit-role.d.ts +2 -0
  38. package/lib/specs/get-implicit-role.js +12 -0
  39. package/lib/specs/get-permitted-roles.d.ts +12 -0
  40. package/lib/specs/get-permitted-roles.js +61 -0
  41. package/lib/specs/get-role-spec.d.ts +5 -0
  42. package/lib/specs/get-role-spec.js +52 -0
  43. package/lib/specs/get-selectors-by-content-model-category.d.ts +3 -0
  44. package/lib/specs/get-selectors-by-content-model-category.js +8 -0
  45. package/lib/specs/get-spec-by-tag-name.d.ts +2 -0
  46. package/lib/specs/get-spec-by-tag-name.js +16 -0
  47. package/lib/{get-spec.d.ts → specs/get-spec.d.ts} +4 -4
  48. package/lib/{get-spec.js → specs/get-spec.js} +1 -1
  49. package/lib/specs/html-spec.d.ts +6 -0
  50. package/lib/specs/html-spec.js +16 -0
  51. package/lib/specs/is-presentational.d.ts +1 -0
  52. package/lib/specs/is-presentational.js +10 -0
  53. package/lib/specs/resolve-namespace.d.ts +9 -0
  54. package/lib/specs/resolve-namespace.js +26 -0
  55. package/lib/specs/resolve-version.d.ts +3 -0
  56. package/lib/specs/resolve-version.js +21 -0
  57. package/lib/specs/schema-to-spec.d.ts +13 -0
  58. package/lib/specs/schema-to-spec.js +93 -0
  59. package/lib/types/aria.d.ts +130 -0
  60. package/lib/{attributes.js → types/aria.js} +0 -0
  61. package/lib/{attributes.d.ts → types/attributes.d.ts} +2 -10
  62. package/lib/{permitted-structres.js → types/attributes.js} +0 -0
  63. package/lib/{types.d.ts → types/index.d.ts} +49 -64
  64. package/lib/{types.js → types/index.js} +0 -0
  65. package/lib/{permitted-structres.d.ts → types/permitted-structres.d.ts} +14 -23
  66. package/lib/types/permitted-structres.js +8 -0
  67. package/lib/utils/cache.d.ts +10 -0
  68. package/lib/utils/cache.js +42 -0
  69. package/lib/utils/get-ns.d.ts +2 -0
  70. package/lib/{get-ns.js → utils/get-ns.js} +3 -0
  71. package/lib/utils/merge-array.d.ts +5 -0
  72. package/lib/utils/merge-array.js +36 -0
  73. package/lib/utils/resolve-namespace.d.ts +9 -0
  74. package/lib/utils/resolve-namespace.js +31 -0
  75. package/lib/utils.d.ts +6 -3
  76. package/lib/utils.js +15 -1
  77. package/package.json +10 -6
  78. package/schemas/aria.schema.json +258 -0
  79. package/schemas/attributes.schema.json +3 -22
  80. package/schemas/{permitted-structures.schema.json → content-models.schema.json} +52 -81
  81. package/schemas/element.schema.json +11 -0
  82. package/src/dom-traverse/accname-computation.spec.ts +69 -0
  83. package/src/dom-traverse/accname-computation.ts +5 -0
  84. package/src/dom-traverse/get-attr-specs.ts +8 -0
  85. package/src/dom-traverse/get-computed-role.spec.ts +134 -0
  86. package/src/dom-traverse/get-computed-role.ts +181 -0
  87. package/src/dom-traverse/get-explicit-role.ts +112 -0
  88. package/src/dom-traverse/get-implicit-role.ts +36 -0
  89. package/src/dom-traverse/get-non-presentational-ancestor.ts +20 -0
  90. package/src/dom-traverse/get-permitted-roles.ts +7 -0
  91. package/src/dom-traverse/get-spec.ts +7 -0
  92. package/src/dom-traverse/has-required-owned-elements.spec.ts +36 -0
  93. package/src/dom-traverse/has-required-owned-elements.ts +85 -0
  94. package/src/dom-traverse/may-be-focusable.ts +21 -0
  95. package/src/index.ts +17 -6
  96. package/src/specs/aria-specs.ts +6 -0
  97. package/src/specs/content-model-category-to-tag-names.ts +15 -0
  98. package/src/specs/get-aria.ts +85 -0
  99. package/src/{get-attr-specs.spec.ts → specs/get-attr-specs.spec.ts} +4 -7
  100. package/src/{get-attr-specs.ts → specs/get-attr-specs.ts} +12 -7
  101. package/src/specs/get-implicit-role.spec.ts +81 -0
  102. package/src/specs/get-implicit-role.ts +17 -0
  103. package/src/specs/get-permitted-roles.spec.ts +420 -0
  104. package/src/specs/get-permitted-roles.ts +87 -0
  105. package/src/specs/get-role-spec.spec.ts +67 -0
  106. package/src/specs/get-role-spec.ts +72 -0
  107. package/src/specs/get-selectors-by-content-model-category.ts +10 -0
  108. package/src/specs/get-spec-by-tag-name.spec.ts +68 -0
  109. package/src/specs/get-spec-by-tag-name.ts +16 -0
  110. package/src/specs/is-presentational.ts +6 -0
  111. package/src/specs/resolve-version.ts +20 -0
  112. package/src/{get-spec.spec.ts → specs/schema-to-spec.spec.ts} +2 -2
  113. package/src/{get-spec.ts → specs/schema-to-spec.ts} +22 -8
  114. package/src/types/aria.ts +147 -0
  115. package/src/{attributes.ts → types/attributes.ts} +2 -12
  116. package/src/{types.ts → types/index.ts} +69 -68
  117. package/src/{permitted-structres.ts → types/permitted-structres.ts} +15 -25
  118. package/src/{get-ns.ts → utils/get-ns.ts} +6 -1
  119. package/src/{utils.ts → utils/merge-array.ts} +0 -0
  120. package/src/utils/resolve-namespace.spec.ts +37 -0
  121. package/src/utils/resolve-namespace.ts +40 -0
  122. package/tsconfig.tsbuildinfo +1 -1
  123. package/lib/get-attr-specs.d.ts +0 -7
  124. package/lib/get-ns.d.ts +0 -1
  125. package/lib/get-spec-by-tag-name.d.ts +0 -2
  126. package/lib/get-spec-by-tag-name.js +0 -31
  127. package/schemas/wai-aria.schema.json +0 -87
  128. package/src/get-spec-by-tag-name.ts +0 -32
@@ -0,0 +1,420 @@
1
+ import type { ARIAVersion } from '../types';
2
+
3
+ import specs from '@markuplint/html-spec';
4
+ import { createSelector } from '@markuplint/selector';
5
+ import { createJSDOMElement } from '@markuplint/test-tools';
6
+
7
+ import { getPermittedRoles } from './get-permitted-roles';
8
+
9
+ function c(html: string, version: ARIAVersion, selector?: string) {
10
+ const el = createJSDOMElement(html, selector);
11
+ const roles = getPermittedRoles(
12
+ specs,
13
+ el.localName,
14
+ el.namespaceURI,
15
+ version,
16
+ selector => !!createSelector(selector, specs).match(el),
17
+ );
18
+ if (Array.isArray(roles)) {
19
+ return roles.map(r => r.name);
20
+ }
21
+ return roles;
22
+ }
23
+
24
+ const coreRoles = [
25
+ 'alert',
26
+ 'alertdialog',
27
+ 'application',
28
+ 'article',
29
+ 'banner',
30
+ 'blockquote',
31
+ 'button',
32
+ 'caption',
33
+ 'cell',
34
+ 'checkbox',
35
+ 'code',
36
+ 'columnheader',
37
+ 'combobox',
38
+ 'complementary',
39
+ 'contentinfo',
40
+ 'definition',
41
+ 'deletion',
42
+ 'dialog',
43
+ 'directory',
44
+ 'document',
45
+ 'emphasis',
46
+ 'feed',
47
+ 'figure',
48
+ 'form',
49
+ 'generic',
50
+ 'grid',
51
+ 'gridcell',
52
+ 'group',
53
+ 'heading',
54
+ 'img',
55
+ 'insertion',
56
+ 'link',
57
+ 'list',
58
+ 'listbox',
59
+ 'listitem',
60
+ 'log',
61
+ 'main',
62
+ 'marquee',
63
+ 'math',
64
+ 'menu',
65
+ 'menubar',
66
+ 'menuitem',
67
+ 'menuitemcheckbox',
68
+ 'menuitemradio',
69
+ 'meter',
70
+ 'navigation',
71
+ 'none',
72
+ 'note',
73
+ 'option',
74
+ 'paragraph',
75
+ 'presentation',
76
+ 'progressbar',
77
+ 'radio',
78
+ 'radiogroup',
79
+ 'region',
80
+ 'row',
81
+ 'rowgroup',
82
+ 'rowheader',
83
+ 'scrollbar',
84
+ 'search',
85
+ 'searchbox',
86
+ 'separator',
87
+ 'slider',
88
+ 'spinbutton',
89
+ 'status',
90
+ 'strong',
91
+ 'subscript',
92
+ 'superscript',
93
+ 'switch',
94
+ 'tab',
95
+ 'table',
96
+ 'tablist',
97
+ 'tabpanel',
98
+ 'term',
99
+ 'textbox',
100
+ 'time',
101
+ 'timer',
102
+ 'toolbar',
103
+ 'tooltip',
104
+ 'tree',
105
+ 'treegrid',
106
+ 'treeitem',
107
+ ];
108
+
109
+ describe('getPermittedRoles', () => {
110
+ test('the a element', () => {
111
+ expect(c('<a></a>', '1.2')).toStrictEqual(coreRoles);
112
+ expect(c('<a href="path/to"></a>', '1.2')).toStrictEqual([
113
+ 'link',
114
+ 'button',
115
+ 'checkbox',
116
+ 'menuitem',
117
+ 'menuitemcheckbox',
118
+ 'menuitemradio',
119
+ 'option',
120
+ 'radio',
121
+ 'switch',
122
+ 'tab',
123
+ 'treeitem',
124
+ ]);
125
+ });
126
+
127
+ test('the area element', () => {
128
+ expect(c('<area></area>', '1.2')).toStrictEqual(['button', 'link']);
129
+ expect(c('<area></area>', '1.1')).toStrictEqual([]);
130
+ expect(c('<area href="path/to"></area>', '1.2')).toStrictEqual(['link']);
131
+ });
132
+
133
+ test('the figure element', () => {
134
+ expect(c('<figure></figure>', '1.2')).toStrictEqual(coreRoles);
135
+ expect(c('<figure><figcaption></figcaption></figure>', '1.2')).toStrictEqual(['figure']);
136
+ });
137
+
138
+ test('the img element', () => {
139
+ expect(c('<img>', '1.2')).toStrictEqual(['img']);
140
+ expect(c('<img alt="">', '1.2')).toStrictEqual(['none', 'presentation']);
141
+ expect(c('<img alt="photo: something">', '1.2')).toStrictEqual([
142
+ 'img',
143
+ 'button',
144
+ 'checkbox',
145
+ 'link',
146
+ 'menuitem',
147
+ 'menuitemcheckbox',
148
+ 'menuitemradio',
149
+ 'option',
150
+ 'progressbar',
151
+ 'radio',
152
+ 'scrollbar',
153
+ 'separator',
154
+ 'slider',
155
+ 'switch',
156
+ 'tab',
157
+ 'treeitem',
158
+ ]);
159
+ expect(c('<img alt="photo: something">', '1.1')).toStrictEqual([
160
+ 'img',
161
+ 'button',
162
+ 'checkbox',
163
+ 'link',
164
+ 'menuitem',
165
+ 'menuitemcheckbox',
166
+ 'menuitemradio',
167
+ 'option',
168
+ 'progressbar',
169
+ 'scrollbar',
170
+ 'separator',
171
+ 'slider',
172
+ 'switch',
173
+ 'tab',
174
+ 'treeitem',
175
+ ]);
176
+ });
177
+
178
+ test('the input element', () => {
179
+ expect(c('<input>', '1.2')).toStrictEqual(['textbox', 'combobox', 'searchbox', 'spinbutton']);
180
+ expect(c('<input>', '1.1')).toStrictEqual(['textbox', 'combobox', 'searchbox', 'spinbutton']);
181
+ expect(c('<input type="button">', '1.2')).toStrictEqual([
182
+ 'button',
183
+ 'checkbox',
184
+ 'combobox',
185
+ 'link',
186
+ 'menuitem',
187
+ 'menuitemcheckbox',
188
+ 'menuitemradio',
189
+ 'option',
190
+ 'radio',
191
+ 'switch',
192
+ 'tab',
193
+ ]);
194
+ expect(c('<input type="button">', '1.1')).toStrictEqual([
195
+ 'button',
196
+ 'link',
197
+ 'menuitem',
198
+ 'menuitemcheckbox',
199
+ 'menuitemradio',
200
+ 'option',
201
+ 'radio',
202
+ 'switch',
203
+ 'tab',
204
+ ]);
205
+ expect(c('<input type="checkbox" aria-pressed="true">', '1.2')).toStrictEqual(['checkbox', 'button']);
206
+ });
207
+
208
+ test('the img element', () => {
209
+ const imgPermitted = [
210
+ 'img',
211
+ 'button',
212
+ 'checkbox',
213
+ 'link',
214
+ 'menuitem',
215
+ 'menuitemcheckbox',
216
+ 'menuitemradio',
217
+ 'option',
218
+ 'progressbar',
219
+ 'radio',
220
+ 'scrollbar',
221
+ 'separator',
222
+ 'slider',
223
+ 'switch',
224
+ 'tab',
225
+ 'treeitem',
226
+ ];
227
+ expect(c('<img />', '1.2')).toStrictEqual(['img']);
228
+ expect(c('<img alt />', '1.2')).toStrictEqual(['none', 'presentation']);
229
+ expect(c('<img alt="" />', '1.2')).toStrictEqual(['none', 'presentation']);
230
+ expect(c('<img alt="foo" />', '1.2')).toStrictEqual(imgPermitted);
231
+ expect(c('<img aria-label="foo" />', '1.2')).toStrictEqual(imgPermitted);
232
+ });
233
+
234
+ test('the form element', () => {
235
+ expect(c('<form></form>', '1.2')).toStrictEqual(['form', 'none', 'presentation', 'search']);
236
+ expect(c('<form></form>', '1.1')).toStrictEqual(['none', 'presentation', 'search']);
237
+ expect(c('<form aria-label="foo"></form>', '1.2')).toStrictEqual(['form', 'none', 'presentation', 'search']);
238
+ expect(c('<form aria-label="foo"></form>', '1.1')).toStrictEqual(['form', 'none', 'presentation', 'search']);
239
+ });
240
+
241
+ test('the svg element', () => {
242
+ expect(c('<svg></svg>', '1.2')).toStrictEqual([
243
+ 'alert',
244
+ 'alertdialog',
245
+ 'application',
246
+ 'article',
247
+ 'banner',
248
+ 'blockquote',
249
+ 'button',
250
+ 'caption',
251
+ 'cell',
252
+ 'checkbox',
253
+ 'code',
254
+ 'columnheader',
255
+ 'combobox',
256
+ 'complementary',
257
+ 'contentinfo',
258
+ 'definition',
259
+ 'deletion',
260
+ 'dialog',
261
+ 'directory',
262
+ 'document',
263
+ 'emphasis',
264
+ 'feed',
265
+ 'figure',
266
+ 'form',
267
+ 'generic',
268
+ 'grid',
269
+ 'gridcell',
270
+ 'group',
271
+ 'heading',
272
+ 'img',
273
+ 'insertion',
274
+ 'link',
275
+ 'list',
276
+ 'listbox',
277
+ 'listitem',
278
+ 'log',
279
+ 'main',
280
+ 'marquee',
281
+ 'math',
282
+ 'menu',
283
+ 'menubar',
284
+ 'menuitem',
285
+ 'menuitemcheckbox',
286
+ 'menuitemradio',
287
+ 'meter',
288
+ 'navigation',
289
+ 'none',
290
+ 'note',
291
+ 'option',
292
+ 'paragraph',
293
+ 'presentation',
294
+ 'progressbar',
295
+ 'radio',
296
+ 'radiogroup',
297
+ 'region',
298
+ 'row',
299
+ 'rowgroup',
300
+ 'rowheader',
301
+ 'scrollbar',
302
+ 'search',
303
+ 'searchbox',
304
+ 'separator',
305
+ 'slider',
306
+ 'spinbutton',
307
+ 'status',
308
+ 'strong',
309
+ 'subscript',
310
+ 'superscript',
311
+ 'switch',
312
+ 'tab',
313
+ 'table',
314
+ 'tablist',
315
+ 'tabpanel',
316
+ 'term',
317
+ 'textbox',
318
+ 'time',
319
+ 'timer',
320
+ 'toolbar',
321
+ 'tooltip',
322
+ 'tree',
323
+ 'treegrid',
324
+ 'treeitem',
325
+ 'graphics-document',
326
+ 'graphics-object',
327
+ 'graphics-symbol',
328
+ ]);
329
+ });
330
+
331
+ test('the rect element', () => {
332
+ expect(c('<svg><rect></rect></svg>', '1.2')).toStrictEqual([
333
+ 'alert',
334
+ 'alertdialog',
335
+ 'application',
336
+ 'article',
337
+ 'banner',
338
+ 'blockquote',
339
+ 'button',
340
+ 'caption',
341
+ 'cell',
342
+ 'checkbox',
343
+ 'code',
344
+ 'columnheader',
345
+ 'combobox',
346
+ 'complementary',
347
+ 'contentinfo',
348
+ 'definition',
349
+ 'deletion',
350
+ 'dialog',
351
+ 'directory',
352
+ 'document',
353
+ 'emphasis',
354
+ 'feed',
355
+ 'figure',
356
+ 'form',
357
+ 'generic',
358
+ 'grid',
359
+ 'gridcell',
360
+ 'group',
361
+ 'heading',
362
+ 'img',
363
+ 'insertion',
364
+ 'link',
365
+ 'list',
366
+ 'listbox',
367
+ 'listitem',
368
+ 'log',
369
+ 'main',
370
+ 'marquee',
371
+ 'math',
372
+ 'menu',
373
+ 'menubar',
374
+ 'menuitem',
375
+ 'menuitemcheckbox',
376
+ 'menuitemradio',
377
+ 'meter',
378
+ 'navigation',
379
+ 'none',
380
+ 'note',
381
+ 'option',
382
+ 'paragraph',
383
+ 'presentation',
384
+ 'progressbar',
385
+ 'radio',
386
+ 'radiogroup',
387
+ 'region',
388
+ 'row',
389
+ 'rowgroup',
390
+ 'rowheader',
391
+ 'scrollbar',
392
+ 'search',
393
+ 'searchbox',
394
+ 'separator',
395
+ 'slider',
396
+ 'spinbutton',
397
+ 'status',
398
+ 'strong',
399
+ 'subscript',
400
+ 'superscript',
401
+ 'switch',
402
+ 'tab',
403
+ 'table',
404
+ 'tablist',
405
+ 'tabpanel',
406
+ 'term',
407
+ 'textbox',
408
+ 'time',
409
+ 'timer',
410
+ 'toolbar',
411
+ 'tooltip',
412
+ 'tree',
413
+ 'treegrid',
414
+ 'treeitem',
415
+ 'graphics-document',
416
+ 'graphics-object',
417
+ 'graphics-symbol',
418
+ ]);
419
+ });
420
+ });
@@ -0,0 +1,87 @@
1
+ import type { ARIAVersion, Matches, MLMLSpec } from '../types';
2
+
3
+ import { mergeArray } from '../utils/merge-array';
4
+
5
+ import { ariaSpecs } from './aria-specs';
6
+ import { getARIA } from './get-aria';
7
+
8
+ /**
9
+ * Getting permitted ARIA roles.
10
+ *
11
+ * - If an array, it is role list.
12
+ * - If `true`, this mean is "Any".
13
+ * - If `false`, this mean is "No".
14
+ */
15
+ export function getPermittedRoles(
16
+ specs: Readonly<MLMLSpec>,
17
+ localName: string,
18
+ namespace: string | null,
19
+ version: ARIAVersion,
20
+ matches: Matches,
21
+ ): { name: string; deprecated?: boolean }[] {
22
+ const aria = getARIA(specs, localName, namespace, version, matches);
23
+ if (!aria) {
24
+ return [];
25
+ }
26
+ const { implicitRole, permittedRoles } = aria;
27
+ const { roles, graphicsRoles } = ariaSpecs(specs, version);
28
+
29
+ let permittedRoleList: { name: string }[] = [];
30
+
31
+ if (permittedRoles === true) {
32
+ permittedRoleList = mergeArray(
33
+ permittedRoleList,
34
+ roles
35
+ .filter(role => !role.isAbstract)
36
+ .map(role => ({
37
+ name: role.name,
38
+ })),
39
+ );
40
+ }
41
+
42
+ if (Array.isArray(permittedRoles)) {
43
+ permittedRoleList = mergeArray(
44
+ permittedRoleList,
45
+ permittedRoles.map(role => {
46
+ if (typeof role === 'string') {
47
+ return {
48
+ name: role,
49
+ };
50
+ }
51
+ return role;
52
+ }),
53
+ );
54
+ } else if (!(typeof permittedRoles === 'boolean')) {
55
+ if (permittedRoles['core-aam']) {
56
+ permittedRoleList = mergeArray(
57
+ permittedRoleList,
58
+ roles
59
+ .filter(role => !role.isAbstract)
60
+ .map(role => ({
61
+ name: role.name,
62
+ })),
63
+ );
64
+ }
65
+ if (permittedRoles['graphics-aam']) {
66
+ permittedRoleList = mergeArray(
67
+ permittedRoleList,
68
+ graphicsRoles
69
+ .filter(role => !role.isAbstract)
70
+ .map(role => ({
71
+ name: role.name,
72
+ })),
73
+ );
74
+ }
75
+ }
76
+
77
+ if (implicitRole) {
78
+ const implicitRoles =
79
+ implicitRole === 'presentation' || implicitRole === 'none' ? ['none', 'presentation'] : [implicitRole];
80
+ return mergeArray(
81
+ implicitRoles.map(r => ({ name: r })),
82
+ permittedRoleList,
83
+ );
84
+ }
85
+
86
+ return permittedRoleList;
87
+ }
@@ -0,0 +1,67 @@
1
+ import specs from '@markuplint/html-spec';
2
+
3
+ import { getRoleSpec } from './get-role-spec';
4
+
5
+ describe('getRoleSpec', () => {
6
+ test('the button role', () => {
7
+ const role = getRoleSpec(specs, 'button', 'http://www.w3.org/1999/xhtml', '1.2')!;
8
+ const superClassRoles = role.superClassRoles.map(r => r.name);
9
+ expect(role.ownedProperties.map(p => p.name + (p.deprecated ? ':deprecated' : ''))).toStrictEqual([
10
+ 'aria-atomic',
11
+ 'aria-busy',
12
+ 'aria-controls',
13
+ 'aria-current',
14
+ 'aria-describedby',
15
+ 'aria-details',
16
+ 'aria-disabled',
17
+ 'aria-dropeffect',
18
+ 'aria-errormessage:deprecated',
19
+ 'aria-expanded',
20
+ 'aria-flowto',
21
+ 'aria-grabbed',
22
+ 'aria-haspopup',
23
+ 'aria-hidden',
24
+ 'aria-invalid:deprecated',
25
+ 'aria-keyshortcuts',
26
+ 'aria-label',
27
+ 'aria-labelledby',
28
+ 'aria-live',
29
+ 'aria-owns',
30
+ 'aria-pressed',
31
+ 'aria-relevant',
32
+ 'aria-roledescription',
33
+ ]);
34
+ expect(role.isAbstract).toBe(false);
35
+ expect(superClassRoles).toStrictEqual(['command', 'widget', 'roletype']);
36
+ });
37
+
38
+ test('the roletype role', () => {
39
+ const role = getRoleSpec(specs, 'roletype', 'http://www.w3.org/1999/xhtml', '1.2')!;
40
+ const superClassRoles = role.superClassRoles.map(r => r.name);
41
+ expect(role.ownedProperties.map(p => p.name + (p.deprecated ? ':deprecated' : ''))).toStrictEqual([
42
+ 'aria-atomic',
43
+ 'aria-busy',
44
+ 'aria-controls',
45
+ 'aria-current',
46
+ 'aria-describedby',
47
+ 'aria-details',
48
+ 'aria-disabled:deprecated',
49
+ 'aria-dropeffect',
50
+ 'aria-errormessage:deprecated',
51
+ 'aria-flowto',
52
+ 'aria-grabbed',
53
+ 'aria-haspopup:deprecated',
54
+ 'aria-hidden',
55
+ 'aria-invalid:deprecated',
56
+ 'aria-keyshortcuts',
57
+ 'aria-label',
58
+ 'aria-labelledby',
59
+ 'aria-live',
60
+ 'aria-owns',
61
+ 'aria-relevant',
62
+ 'aria-roledescription',
63
+ ]);
64
+ expect(role.isAbstract).toBe(true);
65
+ expect(superClassRoles).toStrictEqual([]);
66
+ });
67
+ });
@@ -0,0 +1,72 @@
1
+ import type { ARIAVersion, ARIARoleInSchema, MLMLSpec, ARIARole } from '../types';
2
+ import type { NamespaceURI } from '@markuplint/ml-ast';
3
+
4
+ import { ariaSpecs } from './aria-specs';
5
+
6
+ export function getRoleSpec(
7
+ specs: Readonly<MLMLSpec>,
8
+ roleName: string,
9
+ namespace: NamespaceURI,
10
+ version: ARIAVersion,
11
+ ): (ARIARole & { superClassRoles: ARIARoleInSchema[] }) | null {
12
+ const role = getRoleByName(specs, roleName, namespace, version);
13
+ if (!role) {
14
+ return null;
15
+ }
16
+ const superClassRoles = recursiveTraverseSuperClassRoles(specs, roleName, namespace, version);
17
+ return {
18
+ name: role.name,
19
+ isAbstract: !!role.isAbstract,
20
+ requiredContextRole: role.requiredContextRole ?? [],
21
+ requiredOwnedElements: role.requiredOwnedElements ?? [],
22
+ accessibleNameRequired: !!role.accessibleNameRequired,
23
+ accessibleNameFromAuthor: !!role.accessibleNameFromAuthor,
24
+ accessibleNameFromContent: !!role.accessibleNameFromContent,
25
+ accessibleNameProhibited: !!role.accessibleNameProhibited,
26
+ childrenPresentational: !!role.childrenPresentational,
27
+ ownedProperties: role.ownedProperties ?? [],
28
+ prohibitedProperties: role.prohibitedProperties ?? [],
29
+ superClassRoles,
30
+ };
31
+ }
32
+
33
+ function recursiveTraverseSuperClassRoles(
34
+ specs: Readonly<MLMLSpec>,
35
+ roleName: string,
36
+ namespace: NamespaceURI,
37
+ version: ARIAVersion,
38
+ ) {
39
+ const roles: ARIARoleInSchema[] = [];
40
+ const superClassRoles = getSuperClassRoles(specs, roleName, namespace, version);
41
+ if (superClassRoles) {
42
+ roles.push(...superClassRoles);
43
+ for (const superClassRole of superClassRoles) {
44
+ const ancestorRoles = recursiveTraverseSuperClassRoles(specs, superClassRole.name, namespace, version);
45
+ roles.push(...ancestorRoles);
46
+ }
47
+ }
48
+ return roles;
49
+ }
50
+
51
+ function getSuperClassRoles(
52
+ specs: Readonly<MLMLSpec>,
53
+ roleName: string,
54
+ namespace: NamespaceURI,
55
+ version: ARIAVersion,
56
+ ) {
57
+ const role = getRoleByName(specs, roleName, namespace, version);
58
+ return (
59
+ role?.generalization
60
+ ?.map(roleName => getRoleByName(specs, roleName, namespace, version))
61
+ .filter((role): role is ARIARoleInSchema => !!role) || null
62
+ );
63
+ }
64
+
65
+ function getRoleByName(specs: Readonly<MLMLSpec>, roleName: string, namespace: NamespaceURI, version: ARIAVersion) {
66
+ const { roles, graphicsRoles } = ariaSpecs(specs, version);
67
+ let role = roles.find(r => r.name === roleName);
68
+ if (!role && namespace === 'http://www.w3.org/2000/svg') {
69
+ role = graphicsRoles.find(r => r.name === roleName);
70
+ }
71
+ return role;
72
+ }
@@ -0,0 +1,10 @@
1
+ import type { MLMLSpec } from '../types';
2
+ import type { Category } from '../types/permitted-structres';
3
+
4
+ export function getSelectorsByContentModelCategory(
5
+ specs: Readonly<MLMLSpec>,
6
+ category: Category,
7
+ ): ReadonlyArray<string> {
8
+ const selectors = specs.def['#contentModels'][category];
9
+ return selectors || [];
10
+ }