@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.
- package/CHANGELOG.md +13 -0
- package/lib/attr-value-quotes/index.js +1 -1
- package/lib/create-message.js +6 -5
- package/lib/form-attr-references-form/index.js +1 -1
- package/lib/helpers.d.ts +54 -1
- package/lib/helpers.js +99 -3
- package/lib/input-list-references-datalist/index.js +1 -1
- package/lib/itemprop-requires-itemscope/index.js +1 -1
- package/lib/label-for-references-labelable/index.js +1 -1
- package/lib/label-no-multiple-controls/index.js +2 -2
- package/lib/link-types/index.js +8 -8
- package/lib/map-id-name-match/index.js +1 -1
- package/lib/meter-value-bounds/index.js +3 -3
- package/lib/no-ambiguous-navigable-target-names/index.js +1 -1
- package/lib/no-aria-hidden-on-hidden-until-found/index.js +1 -1
- package/lib/no-content-after-body/index.js +2 -2
- package/lib/no-disallowed-ancestor/index.js +5 -16
- package/lib/no-disallowed-attr/index.js +1 -1
- package/lib/no-duplicate-autofocus/index.js +1 -1
- package/lib/no-duplicate-sibling-attr/index.js +1 -1
- package/lib/no-duplicate-visible-main/index.js +1 -1
- package/lib/no-extra-selected-options/index.js +1 -1
- package/lib/no-input-file-value/index.js +1 -1
- package/lib/no-malformed-character-reference/index.js +1 -1
- package/lib/no-redundant-accessible-name/index.js +7 -1
- package/lib/no-restricted-attr/index.js +2 -1
- package/lib/no-stray-head-or-body-tag/index.js +2 -2
- package/lib/no-table-cell-overlap/index.js +2 -1
- package/lib/no-translate.d.ts +17 -0
- package/lib/no-translate.js +29 -0
- package/lib/no-unclosed-element-at-eof/index.js +1 -1
- package/lib/no-unsupported-browser-features/index.d.ts +9 -0
- package/lib/no-unsupported-browser-features/index.js +10 -7
- package/lib/permitted-contents/content-model.d.ts +14 -0
- package/lib/permitted-contents/content-model.js +21 -1
- package/lib/permitted-contents/index.js +4 -4
- package/lib/permitted-contents/matches-selector.js +4 -0
- package/lib/permitted-contents/slot-content.d.ts +16 -0
- package/lib/permitted-contents/slot-content.js +21 -0
- package/lib/permitted-contents/start.d.ts +8 -1
- package/lib/permitted-contents/start.js +36 -5
- package/lib/progress-value-bounds/index.js +2 -2
- package/lib/require-ancestor/index.d.ts +17 -1
- package/lib/require-ancestor/index.js +52 -5
- package/lib/require-ancestor/meta.d.ts +5 -0
- package/lib/require-ancestor/meta.js +5 -0
- package/lib/require-attr/index.js +4 -1
- package/lib/require-datetime/index.js +2 -1
- package/lib/require-element/index.js +8 -2
- package/lib/usemap-references-map/index.js +1 -1
- package/lib/wai-aria/checkings/contradictory-aria-prop.js +2 -2
- package/lib/wai-aria/checkings/redundant-aria-prop.js +1 -1
- package/lib/wai-aria/checkings/required-accessibility-parent-role.js +2 -1
- package/lib/wai-aria/checkings/required-owned-elements.d.ts +2 -0
- package/lib/wai-aria/checkings/required-owned-elements.js +175 -21
- package/lib/wai-aria/checkings/value.js +3 -2
- 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
|
|
41
|
-
// should also be considered as owned elements per the
|
|
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
|
|
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 (
|
|
79
|
+
if (isOwnedContentMutable(component)) {
|
|
75
80
|
return;
|
|
76
81
|
}
|
|
77
|
-
if (mayBeBeforeCreated(
|
|
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
|
|
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
|
|
97
|
-
* @returns `true` if the
|
|
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
|
|
135
|
-
*
|
|
136
|
-
* that
|
|
137
|
-
*
|
|
138
|
-
*
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
35
|
-
"@markuplint/ml-config": "5.
|
|
36
|
-
"@markuplint/ml-core": "5.
|
|
37
|
-
"@markuplint/ml-spec": "5.
|
|
38
|
-
"@markuplint/selector": "5.
|
|
39
|
-
"@markuplint/shared": "5.
|
|
40
|
-
"@markuplint/types": "5.
|
|
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
|
-
"
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@markuplint/test-tools": "5.6.0"
|
|
52
|
+
},
|
|
53
|
+
"gitHead": "eade3d7202682e567f3c1b6985478f1d4b5c5481"
|
|
51
54
|
}
|