@markuplint/rules 5.5.0 → 5.6.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 (57) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/lib/attr-value-quotes/index.js +1 -1
  3. package/lib/create-message.js +6 -5
  4. package/lib/form-attr-references-form/index.js +1 -1
  5. package/lib/helpers.d.ts +54 -1
  6. package/lib/helpers.js +99 -3
  7. package/lib/input-list-references-datalist/index.js +1 -1
  8. package/lib/itemprop-requires-itemscope/index.js +1 -1
  9. package/lib/label-for-references-labelable/index.js +1 -1
  10. package/lib/label-no-multiple-controls/index.js +2 -2
  11. package/lib/link-types/index.js +8 -8
  12. package/lib/map-id-name-match/index.js +1 -1
  13. package/lib/meter-value-bounds/index.js +3 -3
  14. package/lib/no-ambiguous-navigable-target-names/index.js +1 -1
  15. package/lib/no-aria-hidden-on-hidden-until-found/index.js +1 -1
  16. package/lib/no-content-after-body/index.js +2 -2
  17. package/lib/no-disallowed-ancestor/index.js +5 -16
  18. package/lib/no-disallowed-attr/index.js +1 -1
  19. package/lib/no-duplicate-autofocus/index.js +1 -1
  20. package/lib/no-duplicate-sibling-attr/index.js +1 -1
  21. package/lib/no-duplicate-visible-main/index.js +1 -1
  22. package/lib/no-extra-selected-options/index.js +1 -1
  23. package/lib/no-input-file-value/index.js +1 -1
  24. package/lib/no-malformed-character-reference/index.js +1 -1
  25. package/lib/no-redundant-accessible-name/index.js +7 -1
  26. package/lib/no-restricted-attr/index.js +2 -1
  27. package/lib/no-stray-head-or-body-tag/index.js +2 -2
  28. package/lib/no-table-cell-overlap/index.js +2 -1
  29. package/lib/no-translate.d.ts +17 -0
  30. package/lib/no-translate.js +29 -0
  31. package/lib/no-unclosed-element-at-eof/index.js +1 -1
  32. package/lib/no-unsupported-browser-features/index.d.ts +9 -0
  33. package/lib/no-unsupported-browser-features/index.js +10 -7
  34. package/lib/permitted-contents/content-model.d.ts +14 -0
  35. package/lib/permitted-contents/content-model.js +21 -1
  36. package/lib/permitted-contents/index.js +4 -4
  37. package/lib/permitted-contents/matches-selector.js +4 -0
  38. package/lib/permitted-contents/slot-content.d.ts +16 -0
  39. package/lib/permitted-contents/slot-content.js +21 -0
  40. package/lib/permitted-contents/start.d.ts +8 -1
  41. package/lib/permitted-contents/start.js +36 -5
  42. package/lib/progress-value-bounds/index.js +2 -2
  43. package/lib/require-ancestor/index.d.ts +17 -1
  44. package/lib/require-ancestor/index.js +52 -5
  45. package/lib/require-ancestor/meta.d.ts +5 -0
  46. package/lib/require-ancestor/meta.js +5 -0
  47. package/lib/require-attr/index.js +4 -1
  48. package/lib/require-datetime/index.js +2 -1
  49. package/lib/require-element/index.js +8 -2
  50. package/lib/usemap-references-map/index.js +1 -1
  51. package/lib/wai-aria/checkings/contradictory-aria-prop.js +2 -2
  52. package/lib/wai-aria/checkings/redundant-aria-prop.js +1 -1
  53. package/lib/wai-aria/checkings/required-accessibility-parent-role.js +2 -1
  54. package/lib/wai-aria/checkings/required-owned-elements.d.ts +2 -0
  55. package/lib/wai-aria/checkings/required-owned-elements.js +175 -21
  56. package/lib/wai-aria/checkings/value.js +3 -2
  57. package/package.json +12 -9
@@ -50,7 +50,7 @@ export const checkingContradictoryAriaProp = ({ attr, propSpecs, attrSpecs }) =>
50
50
  }
51
51
  return {
52
52
  scope: attr,
53
- message: t('{0} contradicts {1}', t('the "{0*}" {1}', attr.name, `ARIA ${propSpec.type}`), t('the current "{0}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute')),
53
+ message: t('{0} contradicts {1}', t('the "{0*}" {1}', attr.name, `ARIA ${propSpec.type}`), t('the current "{0*}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute')),
54
54
  };
55
55
  }
56
56
  else if (value === 'true' &&
@@ -58,7 +58,7 @@ export const checkingContradictoryAriaProp = ({ attr, propSpecs, attrSpecs }) =>
58
58
  htmlAttrSpec?.type === 'Boolean') {
59
59
  return {
60
60
  scope: attr,
61
- message: t('{0} contradicts {1}', t('the "{0*}" {1}', attr.name, `ARIA ${propSpec.type}`), t('the implicit "{0}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute')),
61
+ message: t('{0} contradicts {1}', t('the "{0*}" {1}', attr.name, `ARIA ${propSpec.type}`), t('the implicit "{0*}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute')),
62
62
  };
63
63
  }
64
64
  }
@@ -44,7 +44,7 @@ export const checkingRedundantAriaProp = ({ attr, propSpecs, attrSpecs }) => t =
44
44
  if ((equivalentHtmlAttr.value == null && targetAttrValue === value) || equivalentHtmlAttr.value === value) {
45
45
  return {
46
46
  scope: attr,
47
- message: t('{0} has {1}', t('the "{0*}" {1}', attr.name, `ARIA ${propSpec.type}`), t('the same {0} as {1}', 'semantics', t('{0} or {1}', t('the current "{0}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute'), t('the implicit "{0}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute')))),
47
+ message: t('{0} has {1}', t('the "{0*}" {1}', attr.name, `ARIA ${propSpec.type}`), t('the same {0} as {1}', 'semantics', t('{0} or {1}', t('the current "{0*}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute'), t('the implicit "{0*}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute')))),
48
48
  };
49
49
  }
50
50
  }
@@ -1,4 +1,5 @@
1
1
  import { ARIA_RECOMMENDED_VERSION, ariaSpecs } from '@markuplint/ml-spec';
2
+ import { noTranslate } from '../../no-translate.js';
2
3
  /**
3
4
  * Checks whether an element with an explicit role satisfies its
4
5
  * "Required Accessibility Parent Role" (called "Required Context Role" in ARIA 1.2).
@@ -50,7 +51,7 @@ export const checkingRequiredAccessibilityParentRole = ({ el, computed }) => t =
50
51
  message: t('{0} requires {1}', t('the "{0*}" {1}', roleSpec.name, 'role'), parentRoles.length === 1 && parentRoles[0]
51
52
  ? t('an accessibility parent with the "{0*}" {1}', parentRoles[0], 'role')
52
53
  : t('an accessibility parent with one of the {0}', 'roles') +
53
- `: ${t(parentRoles)}`),
54
+ `: ${t(parentRoles.map(noTranslate))}`),
54
55
  };
55
56
  }
56
57
  }
@@ -11,6 +11,8 @@ import type { ARIARole } from '@markuplint/ml-spec';
11
11
  * preprocessor blocks, and mutable children from template engines.
12
12
  * For a pretended component, the owned elements are the ones the component renders
13
13
  * itself (`contents`) together with the children given to it, not the usage site alone.
14
+ * For a shadow host, they are the elements of its declarative shadow root, which is
15
+ * rendered in place of its light children (see `getOwnedChildNodes`).
14
16
  *
15
17
  * @see https://w3c.github.io/aria/#mustContain
16
18
  * @param el - The element node to inspect for allowed accessibility child roles.
@@ -1,5 +1,6 @@
1
- import { ARIA_RECOMMENDED_VERSION, getComputedRole, isRequiredOwnedElement, isTransparentForOwnership, } from '@markuplint/ml-spec';
1
+ import { ARIA_RECOMMENDED_VERSION, getComposedParentElement, getComputedRole, isRequiredOwnedElement, isSlotElement, isTransparentForOwnership, } from '@markuplint/ml-spec';
2
2
  import { expandChildren, isContentMutable } from '../../permitted-contents/slot-content.js';
3
+ import { noTranslate } from '../../no-translate.js';
3
4
  /**
4
5
  * Checks whether an element with a role that has "Allowed Accessibility Child Roles"
5
6
  * (called "Required Owned Elements" in ARIA 1.2) actually contains children
@@ -10,6 +11,8 @@ import { expandChildren, isContentMutable } from '../../permitted-contents/slot-
10
11
  * preprocessor blocks, and mutable children from template engines.
11
12
  * For a pretended component, the owned elements are the ones the component renders
12
13
  * itself (`contents`) together with the children given to it, not the usage site alone.
14
+ * For a shadow host, they are the elements of its declarative shadow root, which is
15
+ * rendered in place of its light children (see `getOwnedChildNodes`).
13
16
  *
14
17
  * @see https://w3c.github.io/aria/#mustContain
15
18
  * @param el - The element node to inspect for allowed accessibility child roles.
@@ -37,10 +40,12 @@ export const checkingRequiredOwnedElements = ({ el, role }) => t => {
37
40
  return;
38
41
  }
39
42
  // TODO: Needs to resolve `aria-owns` references to include virtually owned elements.
40
- // Currently, only DOM-tree children are checked. Elements referenced via `aria-owns`
41
- // should also be considered as owned elements per the ARIA specification.
43
+ // Currently, only the child nodes (those of the shadow root for a shadow host) are checked.
44
+ // Elements referenced via `aria-owns` should also be considered as owned elements per the
45
+ // ARIA specification.
42
46
  const ariaVersion = el.rule.options?.version ?? el.ownerMLDocument.ruleCommonSettings?.ariaVersion ?? ARIA_RECOMMENDED_VERSION;
43
- const children = classifyChildren(el, role, ariaVersion);
47
+ const ownedChildNodes = getOwnedChildNodes(el);
48
+ const children = classifyChildren(el, ownedChildNodes, role, ariaVersion);
44
49
  if (children.some(([, type]) => type === 'BUSY' || type === 'UNKNOWN')) {
45
50
  return;
46
51
  }
@@ -71,30 +76,30 @@ export const checkingRequiredOwnedElements = ({ el, role }) => t => {
71
76
  */
72
77
  // A virtual slot wrapper stands for the component, whose nodes are the ones given to it.
73
78
  const component = el.pretenderContext?.type === 'origin' ? el.pretenderContext.origin : el;
74
- if (component.hasMutableChildren(true) || isContentMutable(component, 'pretended')) {
79
+ if (isOwnedContentMutable(component)) {
75
80
  return;
76
81
  }
77
- if (mayBeBeforeCreated(getOwnedChildNodes(el) ?? [])) {
82
+ if (mayBeBeforeCreated(ownedChildNodes ?? [])) {
78
83
  return {
79
84
  scope: component,
80
85
  message: t('{0}. Or, {1}', t('{0} requires {1}', t('the {0}', 'child element'), role.allowedAccessibilityChildRoles.length === 1 && role.allowedAccessibilityChildRoles[0]
81
86
  ? t('the "{0*}" {1}', role.allowedAccessibilityChildRoles[0], 'role')
82
- : t('the {0}', 'roles') + `: ${t(role.allowedAccessibilityChildRoles)}`), t('require {0}', 'aria-busy="true"')),
87
+ : t('the {0}', 'roles') + `: ${t(role.allowedAccessibilityChildRoles.map(noTranslate))}`), t('require {0*}', 'aria-busy="true"')),
83
88
  };
84
89
  }
85
90
  return {
86
91
  scope: component,
87
92
  message: t('{0} expects {1}', t('the "{0*}" {1}', role.name, 'role'), role.allowedAccessibilityChildRoles.length === 1 && role.allowedAccessibilityChildRoles[0]
88
93
  ? t('the "{0*}" {1}', role.allowedAccessibilityChildRoles[0], 'role')
89
- : t('the {0}', 'roles') + `: ${t(role.allowedAccessibilityChildRoles)}`),
94
+ : t('the {0}', 'roles') + `: ${t(role.allowedAccessibilityChildRoles.map(noTranslate))}`),
90
95
  };
91
96
  };
92
97
  /**
93
- * Determines whether the element's children may not yet exist (e.g., empty or
98
+ * Determines whether the owned child nodes may not yet exist (e.g., empty or
94
99
  * containing only `<script>` / `<template>` elements that could dynamically create content).
95
100
  *
96
- * @param el - The element to inspect.
97
- * @returns `true` if the element is empty or only contains script/template children.
101
+ * @param childNodes - The child nodes the element owns (see `getOwnedChildNodes`).
102
+ * @returns `true` if the nodes are empty or only contain script/template elements.
98
103
  */
99
104
  function mayBeBeforeCreated(childNodes) {
100
105
  if (childNodes.every(child => child.is(child.TEXT_NODE) && child.textContent?.trim() === '')) {
@@ -112,14 +117,14 @@ function mayBeBeforeCreated(childNodes) {
112
117
  * are evaluated as if they were direct children of the owning element.
113
118
  *
114
119
  * @param el - The parent element whose children to classify.
120
+ * @param childNodes - The child nodes `el` owns, from `getOwnedChildNodes`.
115
121
  * @param role - The ARIA role of the parent, which defines allowed child roles.
116
122
  * @param version - The WAI-ARIA version for version-gated transparency behavior.
117
123
  * @returns An array of classified child nodes.
118
124
  */
119
125
  function classifyChildren(
120
126
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
121
- el, role, version) {
122
- const childNodes = getOwnedChildNodes(el);
127
+ el, childNodes, role, version) {
123
128
  if (!childNodes) {
124
129
  return [[el, 'UNKNOWN']];
125
130
  }
@@ -130,24 +135,30 @@ el, role, version) {
130
135
  result.push([child, 'BUSY']);
131
136
  continue;
132
137
  }
138
+ if (isSlotElement(child)) {
139
+ result.push(...classifySlot(child, role, version));
140
+ continue;
141
+ }
133
142
  /**
134
- * The role of a child is resolved against its DOM parent. A child that a pretender
135
- * places somewhere other than under its DOM parent (a node given at the usage site
136
- * that the component renders inside its slot wrapper, or an element a fragment
137
- * component renders in place) would be judged against the wrong parent and lose
138
- * its role, so it is resolved on its own.
143
+ * The role of a child is resolved against its parent in the accessibility tree,
144
+ * which is the host for a child at the top of a declarative shadow root. A child
145
+ * that a pretender places somewhere other than under that parent (a node given at
146
+ * the usage site that the component renders inside its slot wrapper, or an element
147
+ * a fragment component renders in place) would be judged against the wrong parent
148
+ * and lose its role, so it is resolved on its own.
139
149
  */
140
- const computedChild = getComputedRole(child.ownerMLDocument.specs, child, version, child.parentElement !== el);
150
+ const computedChild = getComputedRole(child.ownerMLDocument.specs, child, version, getComposedParentElement(child) !== el);
141
151
  if (role.allowedAccessibilityChildRoles.some(ownedRole => isRequiredOwnedElement(computedChild.el, computedChild.role, ownedRole, child.ownerMLDocument.specs, version))) {
142
152
  result.push([child, 'REQUIRED']);
143
153
  continue;
144
154
  }
145
155
  if (isTransparentForOwnership(computedChild.role?.name, version)) {
146
- if (isContentMutable(child, 'pretended')) {
156
+ const shadowRootTemplate = getShadowRootTemplate(child);
157
+ if (shadowRootTemplate !== 'unknown' && isContentMutable(shadowRootTemplate ?? child, 'pretended')) {
147
158
  result.push([child, 'UNKNOWN']);
148
159
  continue;
149
160
  }
150
- result.push(...classifyChildren(child, role, version));
161
+ result.push(...classifyChildren(child, getOwnedChildNodes(child), role, version));
151
162
  continue;
152
163
  }
153
164
  result.push([child, 'OTHER']);
@@ -161,6 +172,45 @@ el, role, version) {
161
172
  }
162
173
  return result;
163
174
  }
175
+ /**
176
+ * Classifies what a `slot` that the owned elements are looked for through renders: the nodes assigned to
177
+ * it, or its own children when none is, in its place. A slot is not an element of the accessibility tree.
178
+ *
179
+ * The slot is reached only through the elements that are transparent for ownership, so what it renders
180
+ * is owned by the element as if it were written in place of the slot. A slot that is not reached (one
181
+ * in a `p`, or in the content of a nested `template`) places nothing as an owned element, and is not
182
+ * looked at. Where the assignment is not known (a slot that is not in a shadow root, a framework's, manual
183
+ * assignment, template syntax among the light children), what it renders is not known: `UNKNOWN`.
184
+ *
185
+ * The light children assigned to the slot are not in the shadow root that the mutability gate reads
186
+ * (`isOwnedContentMutable`), so the template syntax and the mutable attributes in them are looked at
187
+ * here: a node that may not be what it is written as keeps the rule from deciding.
188
+ *
189
+ * > If slottables is the empty list, then append each slottable child of slot, in tree order, to slottables.
190
+ *
191
+ * @see https://dom.spec.whatwg.org/#find-flattened-slotables
192
+ * @param slot - The `slot` element
193
+ * @param role - The ARIA role of the owner, which defines allowed child roles.
194
+ * @param version - The WAI-ARIA version for version-gated transparency behavior.
195
+ * @returns The classified nodes the slot renders.
196
+ */
197
+ function classifySlot(
198
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
199
+ slot, role, version) {
200
+ const assigned = slot.getAssignedNodes();
201
+ if (!assigned) {
202
+ return [[slot, 'UNKNOWN']];
203
+ }
204
+ if (assigned.length === 0) {
205
+ return classifyChildren(slot, getOwnedChildNodes(slot), role, version);
206
+ }
207
+ const mutable = assigned.some(node => node.is(node.ELEMENT_NODE) &&
208
+ (node.hasMutableAttributes() || node.hasMutableChildren(true, isFlattenedSlot)));
209
+ if (mutable) {
210
+ return [[slot, 'UNKNOWN']];
211
+ }
212
+ return classifyChildren(slot, assigned, role, version);
213
+ }
164
214
  /**
165
215
  * The child nodes that `el` owns as the page renders them.
166
216
  *
@@ -174,12 +224,37 @@ el, role, version) {
174
224
  * - Several slot wrappers leave the owner of each node unknown, so `null` is returned
175
225
  * and nothing is claimed about the element.
176
226
  *
227
+ * A shadow host owns the child nodes of its declarative shadow root, of either mode, and
228
+ * nothing else: the shadow root is rendered in place of the light children, which include
229
+ * the `template` that declares it and what a pretended component renders itself. This
230
+ * comes first, as ml-core attaches the root to a pretended host that renders the children
231
+ * written in it in itself. A `template` written in a block or a `template` with a directive
232
+ * may not be rendered, so `null` is returned for it (`getShadowRootTemplate`). The light children
233
+ * are rendered only where a `slot` in the shadow root places them: the nodes assigned to it
234
+ * (`MLElement#getAssignedNodes`) are classified in its place, and the light children that
235
+ * no slot takes are not owned (`classifySlot`).
236
+ *
237
+ * `getRenderedChildNodes` of `@markuplint/ml-core` (the accessible name computation)
238
+ * resolves the rendered children in the same order, but does not expand fragment
239
+ * components.
240
+ *
241
+ * > […] the flat tree is the top-level DOM tree, but shadow hosts are filled with their shadow tree children instead of their light tree children […]
242
+ *
243
+ * @see https://drafts.csswg.org/css-shadow-1/#flattening
177
244
  * @param el - The element, or the virtual slot wrapper of a pretended component.
178
245
  * @returns The child nodes, or `null` when they cannot be known.
179
246
  */
180
247
  function getOwnedChildNodes(
181
248
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
182
249
  el) {
250
+ // The content of the `template` is the shadow root, and holds the same nodes as its children.
251
+ const shadowRootTemplate = getShadowRootTemplate(el);
252
+ if (shadowRootTemplate === 'unknown') {
253
+ return null;
254
+ }
255
+ if (shadowRootTemplate) {
256
+ return expandChildren(shadowRootTemplate, [...shadowRootTemplate.childNodes], 'pretended');
257
+ }
183
258
  const context = el.pretenderContext;
184
259
  if (context?.type === 'pretender') {
185
260
  const wrapper = context.slotContent?.wrapper;
@@ -201,3 +276,82 @@ el) {
201
276
  }
202
277
  return expandChildren(el, [...el.childNodes], 'pretended');
203
278
  }
279
+ /**
280
+ * Whether a `slot` is one that `classifyChildren` cannot see: a parser that makes the `slot` of its
281
+ * framework a fragment (Vue, Astro) has it flattened out of the child nodes, and what it renders is
282
+ * not known. Any other `slot` is classified where the owned elements reach it (`classifySlot`), and
283
+ * one that is not reached places nothing as an owned element.
284
+ *
285
+ * @param slot - A `slot` element
286
+ * @returns `true` if the slot is flattened out of the child nodes
287
+ */
288
+ function isFlattenedSlot(
289
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
290
+ slot) {
291
+ return slot.isFragment;
292
+ }
293
+ /**
294
+ * Whether the nodes `el` owns may be rendered by something the markup does not show:
295
+ * template syntax, a mutable attribute or a component that no pretender resolved among them
296
+ * (`hasMutableChildren`), or a pretender whose `contents` are unknown (`isContentMutable`).
297
+ * For a shadow host they are read in the shadow root, as that is what it owns: a light
298
+ * child that no `slot` places is not rendered, mutable or not.
299
+ *
300
+ * A `slot` is not counted here unless it is flattened out of the child nodes (`isFlattenedSlot`):
301
+ * `classifySlot` reads what it renders where the owned elements are looked for through it, and a slot
302
+ * that is not reached places nothing as an owned element.
303
+ *
304
+ * @param el - The element, or the component a virtual slot wrapper stands for.
305
+ * @returns `true` when the owned nodes may differ from the markup.
306
+ */
307
+ function isOwnedContentMutable(
308
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
309
+ el) {
310
+ const template = getShadowRootTemplate(el);
311
+ if (template === 'unknown') {
312
+ return true;
313
+ }
314
+ const scope = template ?? el;
315
+ return scope.hasMutableChildren(true, isFlattenedSlot) || isContentMutable(scope, 'pretended');
316
+ }
317
+ /**
318
+ * The `template` element whose content is the declarative shadow root of `host`, which ml-core
319
+ * exposes without the element.
320
+ *
321
+ * ml-core finds the `template` through what a template engine renders away (a block, a
322
+ * `template` with a directive such as `v-if`), as the id lookups need a tree either way. Such
323
+ * a wrapper, or a conditional branch that leaves the `template` out, may render it or not,
324
+ * and the host owns the shadow root or its light children accordingly. So what the host owns
325
+ * is unknown unless the `template` is written right in the host and in every branch of its
326
+ * children (`conditionalChildNodes`), and no block among them has a range that the syntax
327
+ * tree does not give.
328
+ *
329
+ * @param host - An element.
330
+ * @returns The `template` element; `'unknown'` when it is written in such a wrapper; `null`
331
+ * when no shadow root is attached to `host`.
332
+ */
333
+ function getShadowRootTemplate(
334
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
335
+ host) {
336
+ const shadowRoot = host.declarativeShadowRoot;
337
+ if (!shadowRoot) {
338
+ return null;
339
+ }
340
+ // A block without a behavior may be a marker of a language whose blocks the syntax tree does
341
+ // not nest (`{% if %}` … `{% endif %}`), so whether it encloses the `template` is not known.
342
+ for (const child of host.getPureChildNodes()) {
343
+ if (child.is(child.MARKUPLINT_PREPROCESSOR_BLOCK) && !child.blockBehavior) {
344
+ return 'unknown';
345
+ }
346
+ }
347
+ for (const child of host.childNodes) {
348
+ if (child.is(child.ELEMENT_NODE) && child.content === shadowRoot) {
349
+ if (child.syntacticalParentNode !== host) {
350
+ return 'unknown';
351
+ }
352
+ const branches = host.conditionalChildNodes();
353
+ return branches.every(branch => [...branch].includes(child)) ? child : 'unknown';
354
+ }
355
+ }
356
+ return 'unknown';
357
+ }
@@ -1,3 +1,4 @@
1
+ import { noTranslate } from '../../no-translate.js';
1
2
  /**
2
3
  * Checks whether the value of an ARIA property or state conforms to its expected type.
3
4
  *
@@ -22,9 +23,9 @@ export const checkingValue = ({ attr, role, propSpecs }) => t => {
22
23
  }
23
24
  return {
24
25
  scope: attr,
25
- message: t('{0:c} on {1}', t('{0} is {1:c}', t('the "{0}"', attr.value), 'disallowed'), t('the "{0*}" {1}', attr.name, `ARIA ${propSpec?.type ?? 'property'}`)) +
26
+ message: t('{0:c} on {1}', t('{0} is {1:c}', t('the "{0*}"', attr.value), 'disallowed'), t('the "{0*}" {1}', attr.name, `ARIA ${propSpec?.type ?? 'property'}`)) +
26
27
  ('enum' in result && result.enum.length > 0
27
- ? t('. ') + t('Allowed values are: {0}', t(result.enum))
28
+ ? t('. ') + t('Allowed values are: {0}', t(result.enum.map(noTranslate)))
28
29
  : ''),
29
30
  };
30
31
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/rules",
3
- "version": "5.5.0",
3
+ "version": "5.6.0",
4
4
  "description": "Built-in rules of markuplint",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,13 +31,13 @@
31
31
  "./lib/permitted-contents/debug.js": "./lib/permitted-contents/debug.browser.js"
32
32
  },
33
33
  "dependencies": {
34
- "@markuplint/html-spec": "5.5.0",
35
- "@markuplint/ml-config": "5.5.0",
36
- "@markuplint/ml-core": "5.5.0",
37
- "@markuplint/ml-spec": "5.5.0",
38
- "@markuplint/selector": "5.5.0",
39
- "@markuplint/shared": "5.5.0",
40
- "@markuplint/types": "5.5.0",
34
+ "@markuplint/html-spec": "5.6.0",
35
+ "@markuplint/ml-config": "5.6.0",
36
+ "@markuplint/ml-core": "5.6.0",
37
+ "@markuplint/ml-spec": "5.6.0",
38
+ "@markuplint/selector": "5.6.0",
39
+ "@markuplint/shared": "5.6.0",
40
+ "@markuplint/types": "5.6.0",
41
41
  "@mdn/browser-compat-data": "^8.1.8",
42
42
  "@types/debug": "^4.1.13",
43
43
  "ansi-colors": "^4.1.3",
@@ -47,5 +47,8 @@
47
47
  "image-size": "^2.0.4",
48
48
  "type-fest": "^5.10.0"
49
49
  },
50
- "gitHead": "fedebcfd56eaec4642e68643b2f9e10750d810e1"
50
+ "devDependencies": {
51
+ "@markuplint/test-tools": "5.6.0"
52
+ },
53
+ "gitHead": "eade3d7202682e567f3c1b6985478f1d4b5c5481"
51
54
  }