@markuplint/rules 2.4.4 → 2.4.6
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/lib/label-has-control/index.d.ts +2 -0
- package/lib/label-has-control/index.js +28 -0
- package/lib/permitted-contents/choice.d.ts +3 -0
- package/lib/permitted-contents/choice.js +37 -0
- package/lib/permitted-contents/complex-branch.d.ts +13 -0
- package/lib/permitted-contents/complex-branch.js +27 -0
- package/lib/permitted-contents/content-model.d.ts +2 -0
- package/lib/permitted-contents/content-model.js +49 -0
- package/lib/permitted-contents/count-pattern.d.ts +13 -0
- package/lib/permitted-contents/count-pattern.js +155 -0
- package/lib/permitted-contents/debug.d.ts +2 -0
- package/lib/permitted-contents/debug.js +5 -0
- package/lib/permitted-contents/matches-selector.d.ts +3 -0
- package/lib/permitted-contents/matches-selector.js +185 -0
- package/lib/permitted-contents/order.d.ts +13 -0
- package/lib/permitted-contents/order.js +89 -0
- package/lib/permitted-contents/recursive-branch.d.ts +4 -0
- package/lib/permitted-contents/recursive-branch.js +43 -0
- package/lib/permitted-contents/represent-transparent-nodes.d.ts +6 -0
- package/lib/permitted-contents/represent-transparent-nodes.js +79 -0
- package/lib/permitted-contents/start.d.ts +12 -0
- package/lib/permitted-contents/start.js +71 -0
- package/lib/permitted-contents/transparent.d.ts +2 -0
- package/lib/permitted-contents/transparent.js +34 -0
- package/lib/permitted-contents/types.d.ts +45 -0
- package/lib/permitted-contents/types.js +2 -0
- package/lib/permitted-contents/utils.d.ts +32 -0
- package/lib/permitted-contents/utils.js +184 -0
- package/lib/require-accessible-name/index.js +5 -0
- package/lib/wai-aria/checkings/abstract-role.d.ts +3 -0
- package/lib/wai-aria/checkings/abstract-role.js +22 -0
- package/lib/wai-aria/checkings/default-value.d.ts +6 -0
- package/lib/wai-aria/checkings/default-value.js +17 -0
- package/lib/wai-aria/checkings/deprecated-props.d.ts +7 -0
- package/lib/wai-aria/checkings/deprecated-props.js +18 -0
- package/lib/wai-aria/checkings/disallowed-prop.d.ts +7 -0
- package/lib/wai-aria/checkings/disallowed-prop.js +22 -0
- package/lib/wai-aria/checkings/implicit-props.d.ts +7 -0
- package/lib/wai-aria/checkings/implicit-props.js +50 -0
- package/lib/wai-aria/checkings/implicit-role.d.ts +3 -0
- package/lib/wai-aria/checkings/implicit-role.js +21 -0
- package/lib/wai-aria/checkings/interaction-in-hidden.d.ts +11 -0
- package/lib/wai-aria/checkings/interaction-in-hidden.js +47 -0
- package/lib/wai-aria/checkings/no-global-prop.d.ts +6 -0
- package/lib/wai-aria/checkings/no-global-prop.js +14 -0
- package/lib/wai-aria/checkings/non-existent-role.d.ts +3 -0
- package/lib/wai-aria/checkings/non-existent-role.js +28 -0
- package/lib/wai-aria/checkings/permitted-roles.d.ts +3 -0
- package/lib/wai-aria/checkings/permitted-roles.js +28 -0
- package/lib/wai-aria/checkings/presentational-children.d.ts +29 -0
- package/lib/wai-aria/checkings/presentational-children.js +60 -0
- package/lib/wai-aria/checkings/required-owned-elements.d.ts +11 -0
- package/lib/wai-aria/checkings/required-owned-elements.js +87 -0
- package/lib/wai-aria/checkings/required-prop.d.ts +9 -0
- package/lib/wai-aria/checkings/required-prop.js +27 -0
- package/lib/wai-aria/checkings/value.d.ts +13 -0
- package/lib/wai-aria/checkings/value.js +91 -0
- package/lib/wai-aria/types.d.ts +13 -0
- package/lib/wai-aria/types.js +2 -0
- package/package.json +4 -4
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkingImplicitProps = void 0;
|
|
4
|
+
const helpers_1 = require("../../helpers");
|
|
5
|
+
const checkingImplicitProps = ({ attr, propSpecs, attrSpecs }) => t => {
|
|
6
|
+
if (!attrSpecs) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (attr.isDynamicValue) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const propSpec = propSpecs.find(p => p.name === attr.name);
|
|
13
|
+
if (!(propSpec === null || propSpec === void 0 ? void 0 : propSpec.equivalentHtmlAttrs)) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
for (const equivalentHtmlAttr of propSpec.equivalentHtmlAttrs) {
|
|
17
|
+
const htmlAttrSpec = attrSpecs.find(a => a.name === equivalentHtmlAttr.htmlAttrName);
|
|
18
|
+
const isValid = (0, helpers_1.isValidAttr)(t, equivalentHtmlAttr.htmlAttrName, equivalentHtmlAttr.value || '', false, attr.ownerElement, attrSpecs);
|
|
19
|
+
if (isValid && isValid.invalidType === 'non-existent') {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const value = attr.value.trim().toLowerCase();
|
|
23
|
+
if (attr.ownerElement.hasAttribute(equivalentHtmlAttr.htmlAttrName)) {
|
|
24
|
+
const targetAttrValue = attr.ownerElement.getAttribute(equivalentHtmlAttr.htmlAttrName);
|
|
25
|
+
if ((equivalentHtmlAttr.value == null && targetAttrValue === value) ||
|
|
26
|
+
equivalentHtmlAttr.value === value) {
|
|
27
|
+
return {
|
|
28
|
+
scope: attr,
|
|
29
|
+
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')))),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if ((htmlAttrSpec === null || htmlAttrSpec === void 0 ? void 0 : htmlAttrSpec.type) === 'Boolean' && value !== 'false') {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
scope: attr,
|
|
37
|
+
message: t('{0} contradicts {1}', t('the "{0*}" {1}', attr.name, `ARIA ${propSpec.type}`), t('the current "{0}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute')),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
else if (value === 'true') {
|
|
41
|
+
if (!equivalentHtmlAttr.isNotStrictEquivalent && (htmlAttrSpec === null || htmlAttrSpec === void 0 ? void 0 : htmlAttrSpec.type) === 'Boolean') {
|
|
42
|
+
return {
|
|
43
|
+
scope: attr,
|
|
44
|
+
message: t('{0} contradicts {1}', t('the "{0*}" {1}', attr.name, `ARIA ${propSpec.type}`), t('the implicit "{0}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute')),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
exports.checkingImplicitProps = checkingImplicitProps;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkingImplicitRole = void 0;
|
|
4
|
+
const ml_spec_1 = require("@markuplint/ml-spec");
|
|
5
|
+
const checkingImplicitRole = ({ attr }) => t => {
|
|
6
|
+
var _a;
|
|
7
|
+
const implicitRole = (0, ml_spec_1.getImplicitRoleName)(attr.ownerElement, attr.rule.option.version, attr.ownerMLDocument.specs);
|
|
8
|
+
const tokens = (_a = attr.tokenList) === null || _a === void 0 ? void 0 : _a.allTokens();
|
|
9
|
+
if (!tokens) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
for (const token of tokens) {
|
|
13
|
+
if (implicitRole === token.raw) {
|
|
14
|
+
return {
|
|
15
|
+
scope: token,
|
|
16
|
+
message: t('{0} is {1}', t('the "{0*}" {1}', token.raw, 'role'), t('{0} of {1}', 'the implicit role', t('the "{0*}" {1}', attr.ownerElement.localName, 'element'))),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
exports.checkingImplicitRole = checkingImplicitRole;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Options } from '../types';
|
|
2
|
+
import type { ElementChecker } from '@markuplint/ml-core';
|
|
3
|
+
/**
|
|
4
|
+
* Including Elements in the Accessibility Tree
|
|
5
|
+
*
|
|
6
|
+
* @see https://w3c.github.io/aria/#tree_inclusion
|
|
7
|
+
* > Elements that are not hidden and may fire an accessibility API event, including:
|
|
8
|
+
* > - Elements that are currently focused, even if the element or one of its ancestor elements has its aria-hidden attribute set to true.
|
|
9
|
+
* > - Elements that are a valid target of an aria-activedescendant attribute.
|
|
10
|
+
*/
|
|
11
|
+
export declare const checkingInteractionInHidden: ElementChecker<boolean, Options>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkingInteractionInHidden = void 0;
|
|
4
|
+
const ml_spec_1 = require("@markuplint/ml-spec");
|
|
5
|
+
/**
|
|
6
|
+
* Including Elements in the Accessibility Tree
|
|
7
|
+
*
|
|
8
|
+
* @see https://w3c.github.io/aria/#tree_inclusion
|
|
9
|
+
* > Elements that are not hidden and may fire an accessibility API event, including:
|
|
10
|
+
* > - Elements that are currently focused, even if the element or one of its ancestor elements has its aria-hidden attribute set to true.
|
|
11
|
+
* > - Elements that are a valid target of an aria-activedescendant attribute.
|
|
12
|
+
*/
|
|
13
|
+
// フォーカスがあたっている場合に限り、先祖がaria-hidden=trueでもアクセシビリティツリーに提示される
|
|
14
|
+
// なので、aria-hidden=trueの子孫要素で且つインタラクティブ要素である場合、
|
|
15
|
+
// それは開発者にとってはほとんどの場合、意図しないことが多いはずなので、
|
|
16
|
+
// 仕様上不正にはならないが、十分に注意しなければならない状態である。
|
|
17
|
+
const checkingInteractionInHidden = ({ el }) => t => {
|
|
18
|
+
if (!(0, ml_spec_1.mayBeFocusable)(el, el.ownerMLDocument.specs)) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const ariaHidden = getClosestAriaHidden(el);
|
|
22
|
+
if (!ariaHidden) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (el === ariaHidden.ownerElement) {
|
|
26
|
+
return {
|
|
27
|
+
scope: el,
|
|
28
|
+
message: t('It may be focusable in spite of it has aria-hidden=true'),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
scope: el,
|
|
33
|
+
message: t('It may be focusable in spite of it has the ancestor that has aria-hidden=true'),
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
exports.checkingInteractionInHidden = checkingInteractionInHidden;
|
|
37
|
+
function getClosestAriaHidden(el) {
|
|
38
|
+
let current = el;
|
|
39
|
+
while (current) {
|
|
40
|
+
const ariaHidden = current.getAttributeNode('aria-hidden');
|
|
41
|
+
if ((ariaHidden === null || ariaHidden === void 0 ? void 0 : ariaHidden.value) === 'true') {
|
|
42
|
+
return ariaHidden;
|
|
43
|
+
}
|
|
44
|
+
current = current.parentElement;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkingNoGlobalProp = void 0;
|
|
4
|
+
const checkingNoGlobalProp = ({ attr, propSpecs }) => t => {
|
|
5
|
+
var _a, _b;
|
|
6
|
+
const propSpec = propSpecs.find(prop => prop.name === attr.name);
|
|
7
|
+
if (propSpec && !propSpec.isGlobal) {
|
|
8
|
+
return {
|
|
9
|
+
scope: attr,
|
|
10
|
+
message: t('{0} is not {1}', t('the "{0*}" {1}', attr.name, `ARIA ${(_a = propSpec.type) !== null && _a !== void 0 ? _a : 'property'}`), `global ${(_b = propSpec.type) !== null && _b !== void 0 ? _b : 'property'}`),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
exports.checkingNoGlobalProp = checkingNoGlobalProp;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkingNonExistentRole = void 0;
|
|
4
|
+
const ml_spec_1 = require("@markuplint/ml-spec");
|
|
5
|
+
const checkingNonExistentRole = ({ attr }) => t => {
|
|
6
|
+
var _a;
|
|
7
|
+
const { roles, graphicsRoles } = (0, ml_spec_1.ariaSpecs)(attr.ownerMLDocument.specs, attr.rule.option.version);
|
|
8
|
+
const tokens = (_a = attr.tokenList) === null || _a === void 0 ? void 0 : _a.allTokens();
|
|
9
|
+
if (!tokens) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
for (const token of tokens) {
|
|
13
|
+
let role = roles.find(r => r.name === token.raw);
|
|
14
|
+
if (!role && attr.ownerElement.namespaceURI === 'http://www.w3.org/2000/svg') {
|
|
15
|
+
role = graphicsRoles.find(r => r.name === token.raw);
|
|
16
|
+
}
|
|
17
|
+
if (!role) {
|
|
18
|
+
return {
|
|
19
|
+
scope: token,
|
|
20
|
+
message: t('{0} according to {1}', t('{0} does not exist', t('the "{0*}" {1}', token.raw, 'role')), 'the WAI-ARIA specification') +
|
|
21
|
+
t('.') +
|
|
22
|
+
// TODO: Translate
|
|
23
|
+
` This "${token.raw}" role does not exist in WAI-ARIA.`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
exports.checkingNonExistentRole = checkingNonExistentRole;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkingPermittedRoles = void 0;
|
|
4
|
+
const ml_spec_1 = require("@markuplint/ml-spec");
|
|
5
|
+
const checkingPermittedRoles = ({ attr }) => t => {
|
|
6
|
+
var _a;
|
|
7
|
+
const el = attr.ownerElement;
|
|
8
|
+
const permittedRoles = (0, ml_spec_1.getPermittedRoles)(el, el.rule.option.version, attr.ownerMLDocument.specs);
|
|
9
|
+
if (permittedRoles.length === 0) {
|
|
10
|
+
return {
|
|
11
|
+
scope: attr,
|
|
12
|
+
message: t('{0} according to {1}', t('Cannot overwrite {0}', t('{0} of {1}', t('the {0}', 'role'), t('the "{0*}" {1}', el.localName, 'element'))), 'ARIA in HTML specification'),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const tokens = (_a = attr.tokenList) === null || _a === void 0 ? void 0 : _a.allTokens();
|
|
16
|
+
if (!tokens) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
for (const token of tokens) {
|
|
20
|
+
if (Array.isArray(permittedRoles) && !permittedRoles.map(r => r.name).includes(token.raw)) {
|
|
21
|
+
return {
|
|
22
|
+
scope: token,
|
|
23
|
+
message: t('{0} according to {1}', t('Cannot overwrite {0} to {1}', t('the "{0*}" {1}', token.raw, 'role'), t('the "{0*}" {1}', el.localName, 'element')), 'ARIA in HTML specification'),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
exports.checkingPermittedRoles = checkingPermittedRoles;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Options } from '../types';
|
|
2
|
+
import type { ElementChecker } from '@markuplint/ml-core';
|
|
3
|
+
/**
|
|
4
|
+
* Presentational Children
|
|
5
|
+
*
|
|
6
|
+
* @see https://www.w3.org/TR/wai-aria/#childrenArePresentational
|
|
7
|
+
* @see https://w3c.github.io/aria/#childrenArePresentational
|
|
8
|
+
*
|
|
9
|
+
* > The DOM descendants are presentational.
|
|
10
|
+
* > user agents SHOULD NOT expose descendants of
|
|
11
|
+
* > this element through the platform accessibility API.
|
|
12
|
+
* > If user agents do not hide the descendant nodes,
|
|
13
|
+
* > some information may be read twice.
|
|
14
|
+
*
|
|
15
|
+
* @see https://w3c.github.io/aria/#tree_exclusion
|
|
16
|
+
*
|
|
17
|
+
* > Any descendants of elements that have the characteristic "Children Presentational: True"
|
|
18
|
+
* > unless the descendant is not allowed to be presentational
|
|
19
|
+
* > because it meets one of the conditions for exception described
|
|
20
|
+
* > in Presentational Roles Conflict Resolution.
|
|
21
|
+
* > However, the text content of any excluded descendants is included.
|
|
22
|
+
*
|
|
23
|
+
* @see https://w3c.github.io/aria/#tree_inclusion
|
|
24
|
+
*
|
|
25
|
+
* > Text equivalents for hidden referenced objects
|
|
26
|
+
* > may still be used in the name and description computation
|
|
27
|
+
* > even when not included in the accessibility tree.
|
|
28
|
+
*/
|
|
29
|
+
export declare const checkingPresentationalChildren: ElementChecker<boolean, Options>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkingPresentationalChildren = void 0;
|
|
4
|
+
const ml_spec_1 = require("@markuplint/ml-spec");
|
|
5
|
+
/**
|
|
6
|
+
* Presentational Children
|
|
7
|
+
*
|
|
8
|
+
* @see https://www.w3.org/TR/wai-aria/#childrenArePresentational
|
|
9
|
+
* @see https://w3c.github.io/aria/#childrenArePresentational
|
|
10
|
+
*
|
|
11
|
+
* > The DOM descendants are presentational.
|
|
12
|
+
* > user agents SHOULD NOT expose descendants of
|
|
13
|
+
* > this element through the platform accessibility API.
|
|
14
|
+
* > If user agents do not hide the descendant nodes,
|
|
15
|
+
* > some information may be read twice.
|
|
16
|
+
*
|
|
17
|
+
* @see https://w3c.github.io/aria/#tree_exclusion
|
|
18
|
+
*
|
|
19
|
+
* > Any descendants of elements that have the characteristic "Children Presentational: True"
|
|
20
|
+
* > unless the descendant is not allowed to be presentational
|
|
21
|
+
* > because it meets one of the conditions for exception described
|
|
22
|
+
* > in Presentational Roles Conflict Resolution.
|
|
23
|
+
* > However, the text content of any excluded descendants is included.
|
|
24
|
+
*
|
|
25
|
+
* @see https://w3c.github.io/aria/#tree_inclusion
|
|
26
|
+
*
|
|
27
|
+
* > Text equivalents for hidden referenced objects
|
|
28
|
+
* > may still be used in the name and description computation
|
|
29
|
+
* > even when not included in the accessibility tree.
|
|
30
|
+
*/
|
|
31
|
+
const checkingPresentationalChildren = ({ el }) => t => {
|
|
32
|
+
const ancestor = getAncestorHasPresentationalChildren(el);
|
|
33
|
+
if (!ancestor) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!ancestor.role) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const hasAriaAttr = Array.from(el.attributes).some(attr => /^aria-|^role$/i.test(attr.name));
|
|
40
|
+
if (!hasAriaAttr) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
scope: el,
|
|
45
|
+
message: t('it may be ineffective because {0}', t("it has {0} as an ancestor that doesn't expose its descendants to the accessibility tree", t('the "{0*}" {1}', ancestor.role.name, 'role'))),
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
exports.checkingPresentationalChildren = checkingPresentationalChildren;
|
|
49
|
+
function getAncestorHasPresentationalChildren(el) {
|
|
50
|
+
var _a;
|
|
51
|
+
let current = el.parentElement;
|
|
52
|
+
while (current) {
|
|
53
|
+
const computed = (0, ml_spec_1.getComputedRole)(el.ownerMLDocument.specs, current, el.rule.option.version);
|
|
54
|
+
if ((_a = computed.role) === null || _a === void 0 ? void 0 : _a.childrenPresentational) {
|
|
55
|
+
return computed;
|
|
56
|
+
}
|
|
57
|
+
current = current.parentElement;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Options } from '../types';
|
|
2
|
+
import type { ElementChecker } from '@markuplint/ml-core';
|
|
3
|
+
import type { ARIARole } from '@markuplint/ml-spec';
|
|
4
|
+
/**
|
|
5
|
+
* Required Owned Elements
|
|
6
|
+
*
|
|
7
|
+
* @see https://w3c.github.io/aria/#mustContain
|
|
8
|
+
*/
|
|
9
|
+
export declare const checkingRequiredOwnedElements: ElementChecker<boolean, Options, {
|
|
10
|
+
role?: ARIARole | null;
|
|
11
|
+
}>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkingRequiredOwnedElements = void 0;
|
|
4
|
+
const ml_spec_1 = require("@markuplint/ml-spec");
|
|
5
|
+
/**
|
|
6
|
+
* Required Owned Elements
|
|
7
|
+
*
|
|
8
|
+
* @see https://w3c.github.io/aria/#mustContain
|
|
9
|
+
*/
|
|
10
|
+
const checkingRequiredOwnedElements = ({ el, role }) => t => {
|
|
11
|
+
if (!role) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (!role.requiredOwnedElements.length) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* > There may be times that required owned elements are missing,
|
|
19
|
+
* > for example, while editing or while loading a data set.
|
|
20
|
+
* > When a widget is missing required owned elements due to script execution or loading,
|
|
21
|
+
* > authors MUST mark a containing element with aria-busy equal to true.
|
|
22
|
+
* > For example, until a page is fully initialized and complete,
|
|
23
|
+
* > an author could mark the document element as busy.
|
|
24
|
+
*
|
|
25
|
+
* Stop to evaluate when it has `aria-busy=true` for it considers the contents are missing.
|
|
26
|
+
*/
|
|
27
|
+
if (el.matches('[aria-busy="true" i]')) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// TODO: Needs to resolve `aria-own`
|
|
31
|
+
if (el.isEmpty()) {
|
|
32
|
+
return {
|
|
33
|
+
scope: el,
|
|
34
|
+
message: t('{0}. Or, {1}', t('require {0}', role.requiredOwnedElements.length === 1
|
|
35
|
+
? t('the "{0*}" {1}', role.requiredOwnedElements[0], 'role')
|
|
36
|
+
: t('the {0}', 'roles') + `: ${t(role.requiredOwnedElements)}`), t('require {0}', 'aria-busy="true"')),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const children = Array.from(el.childNodes).map(child => {
|
|
40
|
+
if (child.is(child.ELEMENT_NODE)) {
|
|
41
|
+
const computedChild = (0, ml_spec_1.getComputedRole)(child.ownerMLDocument.specs, child, child.rule.option.version);
|
|
42
|
+
if (role.requiredOwnedElements.some(ownedRole => (0, ml_spec_1.isRequiredOwnedElement)(computedChild, ownedRole, child.ownerMLDocument.specs, child.rule.option.version))) {
|
|
43
|
+
return [child, 'REQUIRED'];
|
|
44
|
+
}
|
|
45
|
+
return [child, 'OTHER'];
|
|
46
|
+
}
|
|
47
|
+
else if (child.is(child.MARKUPLINT_PREPROCESSOR_BLOCK)) {
|
|
48
|
+
return [child, 'PB'];
|
|
49
|
+
}
|
|
50
|
+
return [null, 'NO_ELEMENT'];
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* > Any element that will be owned by the element with this role.
|
|
54
|
+
* > For example, an element with the role list
|
|
55
|
+
* > **will own at least one element** with the role listitem.
|
|
56
|
+
*/
|
|
57
|
+
if (children.some(([, type]) => type === 'REQUIRED')) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (children.some(([, type]) => type === 'PB')) {
|
|
61
|
+
// TODO: https://github.com/markuplint/markuplint/issues/490
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Has mutable children
|
|
66
|
+
*
|
|
67
|
+
* Ex:
|
|
68
|
+
*
|
|
69
|
+
* ```jsx
|
|
70
|
+
* <table>
|
|
71
|
+
* <tbody>
|
|
72
|
+
* {list.map((item) => <tr><td>{item}</td></tr>)}
|
|
73
|
+
* </tbody>
|
|
74
|
+
* </table>
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
if (el.hasMutableChildren(true)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
scope: el,
|
|
82
|
+
message: t('{0} expects {1}', t('the "{0*}" {1}', role.name, 'role'), role.requiredOwnedElements.length === 1
|
|
83
|
+
? t('the "{0*}" {1}', role.requiredOwnedElements[0], 'role')
|
|
84
|
+
: t('the {0}', 'roles') + `: ${t(role.requiredOwnedElements)}`),
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
exports.checkingRequiredOwnedElements = checkingRequiredOwnedElements;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Options } from '../types';
|
|
2
|
+
import type { ElementChecker } from '@markuplint/ml-core';
|
|
3
|
+
import type { ARIAProperty, ARIARole } from '@markuplint/ml-spec';
|
|
4
|
+
export declare const checkingRequiredProp: ElementChecker<boolean, Options, {
|
|
5
|
+
role?: (ARIARole & {
|
|
6
|
+
isImplicit?: boolean;
|
|
7
|
+
}) | null;
|
|
8
|
+
propSpecs: ARIAProperty[];
|
|
9
|
+
}>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkingRequiredProp = void 0;
|
|
4
|
+
const checkingRequiredProp = ({ el, role, propSpecs }) => t => {
|
|
5
|
+
var _a;
|
|
6
|
+
if (!role) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (role.isImplicit) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const requiredProps = role.ownedProperties.filter(s => s.required).map(s => s.name);
|
|
13
|
+
for (const requiredProp of requiredProps) {
|
|
14
|
+
const has = el.attributes.some(attr => {
|
|
15
|
+
const attrName = attr.name.toLowerCase();
|
|
16
|
+
return attrName === requiredProp;
|
|
17
|
+
});
|
|
18
|
+
if (!has) {
|
|
19
|
+
const propSpec = propSpecs.find(p => p.name === requiredProp);
|
|
20
|
+
return {
|
|
21
|
+
scope: el,
|
|
22
|
+
message: t('{0:c} on {1}', t('Require {0}', t('the "{0*}" {1}', requiredProp, `ARIA ${(_a = propSpec === null || propSpec === void 0 ? void 0 : propSpec.type) !== null && _a !== void 0 ? _a : 'property'}`)), t('the "{0*}" {1}', role.name, 'role')),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
exports.checkingRequiredProp = checkingRequiredProp;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Options } from '../types';
|
|
2
|
+
import type { AttrChecker } from '@markuplint/ml-core';
|
|
3
|
+
import type { ARIAProperty, ARIARole } from '@markuplint/ml-spec';
|
|
4
|
+
export declare const checkingValue: AttrChecker<boolean, Options, {
|
|
5
|
+
role?: ARIARole | null;
|
|
6
|
+
propSpecs: ARIAProperty[];
|
|
7
|
+
booleanish?: boolean;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
* @see https://www.w3.org/TR/wai-aria-1.2/#propcharacteristic_value
|
|
12
|
+
*/
|
|
13
|
+
export declare function checkAriaValue(type: string, value: string, tokenEnum: string[], booleanish?: boolean): boolean;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkAriaValue = exports.checkingValue = void 0;
|
|
4
|
+
const checkingValue = ({ attr, role, propSpecs, booleanish }) => t => {
|
|
5
|
+
var _a;
|
|
6
|
+
if (attr.isDynamicValue) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const propSpec = propSpecs.find(p => p.name === attr.name);
|
|
10
|
+
const result = checkAria(propSpec, attr.value, role === null || role === void 0 ? void 0 : role.name, booleanish);
|
|
11
|
+
if (result.isValid) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
scope: attr,
|
|
16
|
+
message: t('{0:c} on {1}', t('{0} is {1:c}', t('the "{0}"', attr.value), 'disallowed'), t('the "{0*}" {1}', attr.name, `ARIA ${(_a = propSpec === null || propSpec === void 0 ? void 0 : propSpec.type) !== null && _a !== void 0 ? _a : 'property'}`)) +
|
|
17
|
+
('enum' in result && result.enum.length ? t('. ') + t('Allowed values are: {0}', t(result.enum)) : ''),
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
exports.checkingValue = checkingValue;
|
|
21
|
+
function checkAria(propSpec, currentValue, role, booleanish) {
|
|
22
|
+
if (!propSpec) {
|
|
23
|
+
return {
|
|
24
|
+
currentValue,
|
|
25
|
+
// For skipping checking
|
|
26
|
+
isValid: true,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
let valueType = propSpec.value;
|
|
30
|
+
if (role && propSpec.conditionalValue) {
|
|
31
|
+
for (const cond of propSpec.conditionalValue) {
|
|
32
|
+
if (cond.role.includes(role)) {
|
|
33
|
+
valueType = cond.value;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const isValid = checkAriaValue(valueType, currentValue, propSpec.enum, booleanish);
|
|
39
|
+
return {
|
|
40
|
+
...propSpec,
|
|
41
|
+
currentValue,
|
|
42
|
+
isValid,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
* @see https://www.w3.org/TR/wai-aria-1.2/#propcharacteristic_value
|
|
48
|
+
*/
|
|
49
|
+
function checkAriaValue(type, value, tokenEnum, booleanish) {
|
|
50
|
+
switch (type) {
|
|
51
|
+
case 'token': {
|
|
52
|
+
return tokenEnum.includes(value);
|
|
53
|
+
}
|
|
54
|
+
case 'token list': {
|
|
55
|
+
const list = value.split(/\s+/g).map(s => s.trim());
|
|
56
|
+
return list.every(token => tokenEnum.includes(token));
|
|
57
|
+
}
|
|
58
|
+
case 'string':
|
|
59
|
+
case 'ID reference':
|
|
60
|
+
case 'ID reference list': {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
case 'true/false': {
|
|
64
|
+
if (booleanish && value === '') {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return ['true', 'false'].includes(value);
|
|
68
|
+
}
|
|
69
|
+
case 'tristate': {
|
|
70
|
+
if (booleanish && value === '') {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return ['mixed', 'true', 'false', 'undefined'].includes(value);
|
|
74
|
+
}
|
|
75
|
+
case 'true/false/undefined': {
|
|
76
|
+
if (booleanish && value === '') {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return ['true', 'false', 'undefined'].includes(value);
|
|
80
|
+
}
|
|
81
|
+
case 'integer': {
|
|
82
|
+
return parseInt(value).toString() === value;
|
|
83
|
+
}
|
|
84
|
+
case 'number': {
|
|
85
|
+
return parseFloat(value).toString() === value;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// For skipping checking
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
exports.checkAriaValue = checkAriaValue;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ARIAVersion } from '@markuplint/ml-spec';
|
|
2
|
+
export type Options = {
|
|
3
|
+
checkingValue: boolean;
|
|
4
|
+
checkingDeprecatedProps: boolean;
|
|
5
|
+
permittedAriaRoles: boolean;
|
|
6
|
+
checkingRequiredOwnedElements: boolean;
|
|
7
|
+
checkingPresentationalChildren: boolean;
|
|
8
|
+
checkingInteractionInHidden: boolean;
|
|
9
|
+
disallowSetImplicitRole: boolean;
|
|
10
|
+
disallowSetImplicitProps: boolean;
|
|
11
|
+
disallowDefaultValue: boolean;
|
|
12
|
+
version: ARIAVersion;
|
|
13
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/rules",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.6",
|
|
4
4
|
"description": "Rules for markuplint",
|
|
5
5
|
"repository": "git@github.com:markuplint/markuplint.git",
|
|
6
6
|
"author": "Yusuke Hirao <yusukehirao@me.com>",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"clean": "tsc --build --clean"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@markuplint/html-spec": "2.
|
|
21
|
-
"@markuplint/ml-core": "2.3.
|
|
20
|
+
"@markuplint/html-spec": "2.9.0",
|
|
21
|
+
"@markuplint/ml-core": "2.3.5",
|
|
22
22
|
"@markuplint/ml-spec": "2.1.1",
|
|
23
23
|
"@markuplint/types": "2.2.0",
|
|
24
24
|
"debug": "^4.3.4",
|
|
@@ -28,5 +28,5 @@
|
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/debug": "^4.1.7"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "c3e38fbec92fffa4a44564d1c7a37963f40502ce"
|
|
32
32
|
}
|