@markuplint/rules 2.4.2 → 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.
- package/README.md +1 -5
- package/lib/attr-duplication/index.js +10 -7
- package/lib/attr-equal-space-after/index.d.ts +1 -1
- package/lib/attr-equal-space-before/index.d.ts +1 -1
- package/lib/attr-spacing/index.d.ts +3 -3
- package/lib/attr-value-quotes/index.js +21 -25
- package/lib/case-sensitive-attr-name/index.d.ts +1 -5
- package/lib/case-sensitive-attr-name/index.js +51 -81
- package/lib/case-sensitive-tag-name/index.js +38 -30
- package/lib/character-reference/index.d.ts +2 -1
- package/lib/character-reference/index.js +6 -8
- package/lib/class-naming/index.js +11 -11
- package/lib/deprecated-attr/index.js +17 -18
- package/lib/deprecated-element/index.js +5 -7
- package/lib/disallowed-element/index.js +8 -9
- package/lib/doctype/index.js +7 -7
- package/lib/end-tag/index.js +4 -1
- package/lib/helper/aria/get-aria.d.ts +7 -0
- package/lib/helper/aria/get-aria.js +53 -0
- package/lib/helper/aria/get-computed-role.d.ts +10 -0
- package/lib/helper/aria/get-computed-role.js +24 -0
- package/lib/helper/aria/get-implicit-role.d.ts +7 -0
- package/lib/helper/aria/get-implicit-role.js +12 -0
- package/lib/helper/aria/get-permitted-roles.d.ts +14 -0
- package/lib/helper/aria/get-permitted-roles.js +26 -0
- package/lib/helper/aria/resolve-version.d.ts +2 -0
- package/lib/helper/aria/resolve-version.js +21 -0
- package/lib/helper/spec/aria-spec.d.ts +5 -0
- package/lib/helper/spec/aria-spec.js +12 -0
- package/lib/helper/spec/get-role-spec.d.ts +11 -0
- package/lib/helper/spec/get-role-spec.js +39 -0
- package/lib/helper/spec/html-spec.d.ts +2 -0
- package/lib/helper/spec/html-spec.js +14 -0
- package/lib/helpers.d.ts +10 -28
- package/lib/helpers.js +29 -149
- package/lib/id-duplication/index.js +4 -6
- package/lib/indentation/index.d.ts +3 -3
- package/lib/index.d.ts +14 -19
- package/lib/index.js +0 -8
- package/lib/ineffective-attr/index.js +19 -27
- package/lib/invalid-attr/index.js +83 -83
- package/lib/landmark-roles/index.d.ts +2 -2
- package/lib/landmark-roles/index.js +9 -9
- package/lib/no-boolean-attr-value/index.js +33 -34
- package/lib/no-default-value/index.js +20 -24
- package/lib/no-hard-code-id/index.js +9 -13
- package/lib/no-refer-to-non-existent-id/index.d.ts +4 -1
- package/lib/no-refer-to-non-existent-id/index.js +77 -83
- package/lib/no-use-event-handler-attr/index.js +20 -22
- package/lib/permitted-contents/index.d.ts +5 -2
- package/lib/permitted-contents/index.js +67 -68
- package/lib/permitted-contents/permitted-content.spec-to-regexp.d.ts +5 -5
- package/lib/permitted-contents/permitted-content.spec-to-regexp.js +19 -14
- package/lib/permitted-contents/unfold-content-models-to-tags.d.ts +2 -2
- package/lib/require-accessible-name/index.d.ts +5 -1
- package/lib/require-accessible-name/index.js +6 -3
- package/lib/required-attr/index.js +16 -25
- package/lib/required-element/index.d.ts +1 -1
- package/lib/required-element/index.js +6 -6
- package/lib/required-h1/index.d.ts +2 -2
- package/lib/required-h1/index.js +3 -3
- 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/checking-required-owned-elements.d.ts +11 -0
- package/lib/wai-aria/checkings/checking-required-owned-elements.js +63 -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-existant-role.d.ts +3 -0
- package/lib/wai-aria/checkings/non-existant-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 +63 -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 +7 -0
- package/lib/wai-aria/checkings/value.js +21 -0
- package/lib/wai-aria/index.d.ts +1 -8
- package/lib/wai-aria/index.js +66 -207
- package/lib/wai-aria/types.d.ts +13 -0
- package/lib/wai-aria/types.js +2 -0
- package/package.json +6 -6
- package/schema.json +0 -12
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -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,63 @@
|
|
|
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.children.length) {
|
|
32
|
+
return {
|
|
33
|
+
scope: el,
|
|
34
|
+
message: t('{0}. Or, {1}', t('Require {0} to content', t('the {0*} {1}', t(role.requiredOwnedElements), 'role')), t('require {0}', 'aria-busy="true"')),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const children = Array.from(el.childNodes).map(child => {
|
|
38
|
+
if (child.is(child.ELEMENT_NODE)) {
|
|
39
|
+
const computedChild = (0, ml_spec_1.getComputedRole)(child.ownerMLDocument.specs, child, child.rule.option.version);
|
|
40
|
+
if (role.requiredOwnedElements.some(ownedRole => (0, ml_spec_1.isRequiredOwnedElement)(computedChild, ownedRole, child.ownerMLDocument.specs, child.rule.option.version))) {
|
|
41
|
+
return 'REQUIRED';
|
|
42
|
+
}
|
|
43
|
+
return 'OTHER';
|
|
44
|
+
}
|
|
45
|
+
else if (child.MARKUPLINT_PREPROCESSOR_BLOCK) {
|
|
46
|
+
return 'PB';
|
|
47
|
+
}
|
|
48
|
+
return 'NO_ELEMENT';
|
|
49
|
+
});
|
|
50
|
+
if (children.includes('PB')) {
|
|
51
|
+
return {
|
|
52
|
+
scope: el,
|
|
53
|
+
message: t('{0} expects {1}', t('the "{0*}" {1}', role.name, 'role'), t(role.requiredContextRole.map(ownedRole => t('the "{0*}" {1}', ownedRole, 'role')))),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (!children.includes('REQUIRED')) {
|
|
57
|
+
return {
|
|
58
|
+
scope: el,
|
|
59
|
+
message: t('{0} expects {1}', t('the "{0*}" {1}', role.name, 'role'), t('the {0*} {1}', t(role.requiredOwnedElements), 'roles')),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
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,7 @@
|
|
|
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
|
+
}>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkingValue = void 0;
|
|
4
|
+
const helpers_1 = require("../../helpers");
|
|
5
|
+
const checkingValue = ({ attr, role, propSpecs }) => t => {
|
|
6
|
+
var _a;
|
|
7
|
+
if (attr.isDynamicValue) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const propSpec = propSpecs.find(p => p.name === attr.name);
|
|
11
|
+
const result = (0, helpers_1.checkAria)(attr.ownerMLDocument.specs, attr.name, attr.value, attr.rule.option.version, role === null || role === void 0 ? void 0 : role.name);
|
|
12
|
+
if (result.isValid) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
scope: attr,
|
|
17
|
+
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'}`)) +
|
|
18
|
+
('enum' in result && result.enum.length ? t('. ') + t('Allowed values are: {0}', t(result.enum)) : ''),
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
exports.checkingValue = checkingValue;
|
package/lib/wai-aria/index.d.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
checkingValue?: boolean;
|
|
3
|
-
checkingDeprecatedProps?: boolean;
|
|
4
|
-
permittedAriaRoles?: boolean;
|
|
5
|
-
disallowSetImplicitRole?: boolean;
|
|
6
|
-
disallowSetImplicitProps?: boolean;
|
|
7
|
-
disallowDefaultValue?: boolean;
|
|
8
|
-
};
|
|
1
|
+
import type { Options } from './types';
|
|
9
2
|
declare const _default: import("@markuplint/ml-core").RuleSeed<boolean, Options>;
|
|
10
3
|
export default _default;
|
package/lib/wai-aria/index.js
CHANGED
|
@@ -2,237 +2,96 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const ml_core_1 = require("@markuplint/ml-core");
|
|
4
4
|
const helpers_1 = require("../helpers");
|
|
5
|
+
const abstract_role_1 = require("./checkings/abstract-role");
|
|
6
|
+
const default_value_1 = require("./checkings/default-value");
|
|
7
|
+
const deprecated_props_1 = require("./checkings/deprecated-props");
|
|
8
|
+
const disallowed_prop_1 = require("./checkings/disallowed-prop");
|
|
9
|
+
const implicit_props_1 = require("./checkings/implicit-props");
|
|
10
|
+
const implicit_role_1 = require("./checkings/implicit-role");
|
|
11
|
+
const interaction_in_hidden_1 = require("./checkings/interaction-in-hidden");
|
|
12
|
+
const no_global_prop_1 = require("./checkings/no-global-prop");
|
|
13
|
+
const non_existant_role_1 = require("./checkings/non-existant-role");
|
|
14
|
+
const permitted_roles_1 = require("./checkings/permitted-roles");
|
|
15
|
+
const presentational_children_1 = require("./checkings/presentational-children");
|
|
16
|
+
const required_owned_elements_1 = require("./checkings/required-owned-elements");
|
|
17
|
+
const required_prop_1 = require("./checkings/required-prop");
|
|
18
|
+
const value_1 = require("./checkings/value");
|
|
5
19
|
exports.default = (0, ml_core_1.createRule)({
|
|
6
20
|
defaultOptions: {
|
|
7
21
|
checkingValue: true,
|
|
8
22
|
checkingDeprecatedProps: true,
|
|
9
23
|
permittedAriaRoles: true,
|
|
24
|
+
checkingRequiredOwnedElements: true,
|
|
25
|
+
checkingPresentationalChildren: false,
|
|
26
|
+
checkingInteractionInHidden: false,
|
|
10
27
|
disallowSetImplicitRole: true,
|
|
11
28
|
disallowSetImplicitProps: true,
|
|
12
29
|
disallowDefaultValue: false,
|
|
30
|
+
version: '1.2',
|
|
13
31
|
},
|
|
14
32
|
async verify({ document, report, t }) {
|
|
15
|
-
await document.walkOn('Element',
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
33
|
+
await document.walkOn('Element', el => {
|
|
34
|
+
const roleAttr = el.getAttributeNode('role');
|
|
35
|
+
const propAttrs = el.attributes.filter(attr => /^aria-/i.test(attr.name));
|
|
36
|
+
const ariaAttrs = new helpers_1.Collection(roleAttr, ...propAttrs);
|
|
37
|
+
const elSpec = (0, ml_core_1.getSpec)(el, document.specs);
|
|
38
|
+
if (!elSpec) {
|
|
20
39
|
return;
|
|
21
40
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// Roles in the spec
|
|
25
|
-
if (roleAttr) {
|
|
26
|
-
const value = roleAttr.getValue().potential.trim().toLowerCase();
|
|
27
|
-
const existedRole = roles.find(role => role.name === value);
|
|
28
|
-
if (!existedRole) {
|
|
29
|
-
// Not exist
|
|
41
|
+
if (!elSpec.globalAttrs['#ARIAAttrs']) {
|
|
42
|
+
for (const ariaAttr of ariaAttrs) {
|
|
30
43
|
report({
|
|
31
|
-
scope:
|
|
32
|
-
message:
|
|
33
|
-
line: roleAttr.startLine,
|
|
34
|
-
col: roleAttr.startCol,
|
|
35
|
-
raw: roleAttr.raw,
|
|
44
|
+
scope: ariaAttr,
|
|
45
|
+
message: 'ARIA attribute is disallowed',
|
|
36
46
|
});
|
|
37
47
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
raw: roleAttr.raw,
|
|
46
|
-
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const computed = (0, ml_core_1.getComputedRole)(document.specs, el, el.rule.option.version);
|
|
51
|
+
const { props: propSpecs } = (0, ml_core_1.ariaSpecs)(document.specs, el.rule.option.version);
|
|
52
|
+
if (roleAttr) {
|
|
53
|
+
if (report((0, non_existant_role_1.checkingNonExistantRole)({ attr: roleAttr }))) {
|
|
54
|
+
return;
|
|
47
55
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const implictRole = (0, helpers_1.getImplicitRole)(document.specs, node);
|
|
51
|
-
if (implictRole && implictRole === value) {
|
|
52
|
-
// the implicit role
|
|
53
|
-
report({
|
|
54
|
-
scope: node,
|
|
55
|
-
message: t('{0} is {1}', t('the "{0*}" {1}', value, 'role'), t('{0} of {1}', 'the implicit role', t('the "{0*}" {1}', node.nodeName, 'element'))),
|
|
56
|
-
line: roleAttr.startLine,
|
|
57
|
-
col: roleAttr.startCol,
|
|
58
|
-
raw: roleAttr.raw,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
56
|
+
if (report((0, abstract_role_1.checkingAbstractRole)({ attr: roleAttr }))) {
|
|
57
|
+
return;
|
|
61
58
|
}
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
if (permittedRoles === false) {
|
|
66
|
-
report({
|
|
67
|
-
scope: node,
|
|
68
|
-
message: t('{0} according to {1}', t('Cannot overwrite {0}', t('{0} of {1}', t('the {0}', 'role'), t('the "{0*}" {1}', node.nodeName, 'element'))), 'ARIA in HTML specification'),
|
|
69
|
-
line: roleAttr.startLine,
|
|
70
|
-
col: roleAttr.startCol,
|
|
71
|
-
raw: roleAttr.raw,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
else if (Array.isArray(permittedRoles) && !permittedRoles.includes(value)) {
|
|
75
|
-
report({
|
|
76
|
-
scope: node,
|
|
77
|
-
message: t('{0} according to {1}', t('Cannot overwrite {0} to {1}', t('the "{0*}" {1}', value, 'role'), t('the "{0*}" {1}', node.nodeName, 'element')), 'ARIA in HTML specification'),
|
|
78
|
-
line: roleAttr.startLine,
|
|
79
|
-
col: roleAttr.startCol,
|
|
80
|
-
raw: roleAttr.raw,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
59
|
+
report((0, required_prop_1.checkingRequiredProp)({ el, role: computed.role, propSpecs }));
|
|
60
|
+
if (el.rule.option.disallowSetImplicitRole) {
|
|
61
|
+
report((0, implicit_role_1.checkingImplicitRole)({ attr: roleAttr }));
|
|
83
62
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (computedRole) {
|
|
87
|
-
const role = (0, helpers_1.getRoleSpec)(document.specs, computedRole.name);
|
|
88
|
-
if (role) {
|
|
89
|
-
// Checking aria-* on the role
|
|
90
|
-
for (const attr of node.attributes) {
|
|
91
|
-
const attrName = attr.getName().potential.trim().toLowerCase();
|
|
92
|
-
if (/^aria-/i.test(attrName)) {
|
|
93
|
-
const statesAndProp = role.statesAndProps.find(s => s.name === attrName);
|
|
94
|
-
if (statesAndProp) {
|
|
95
|
-
if (node.rule.option.checkingDeprecatedProps && statesAndProp.deprecated) {
|
|
96
|
-
report({
|
|
97
|
-
scope: node,
|
|
98
|
-
message: t('{0:c} on {1}', t('{0} is {1:c}', t('the "{0*}" {1}', attrName, 'ARIA state/property'), 'deprecated'), t('the "{0*}" {1}', role.name, 'role')),
|
|
99
|
-
line: attr.startLine,
|
|
100
|
-
col: attr.startCol,
|
|
101
|
-
raw: attr.raw,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
report({
|
|
107
|
-
scope: node,
|
|
108
|
-
message: t('{0:c} on {1}', t('{0} is {1:c}', t('the "{0*}" {1}', attrName, 'ARIA state/property'), 'disallowed'), t('the "{0*}" {1}', role.name, 'role')),
|
|
109
|
-
line: attr.startLine,
|
|
110
|
-
col: attr.startCol,
|
|
111
|
-
raw: attr.raw,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
// Checing required props
|
|
117
|
-
if (!computedRole.isImplicit) {
|
|
118
|
-
const requiredProps = role.statesAndProps.filter(s => s.required).map(s => s.name);
|
|
119
|
-
for (const requiredProp of requiredProps) {
|
|
120
|
-
const has = node.attributes.some(attr => {
|
|
121
|
-
const attrName = attr.getName().potential.trim().toLowerCase();
|
|
122
|
-
return attrName === requiredProp;
|
|
123
|
-
});
|
|
124
|
-
if (!has) {
|
|
125
|
-
report({
|
|
126
|
-
scope: node,
|
|
127
|
-
message: t('{0:c} on {1}', t('Require {0}', t('the "{0*}" {1}', requiredProp, 'ARIA state/property')), t('the "{0*}" {1}', role.name, 'role')),
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
63
|
+
if (el.rule.option.permittedAriaRoles) {
|
|
64
|
+
report((0, permitted_roles_1.checkingPermittedRoles)({ attr: roleAttr }));
|
|
132
65
|
}
|
|
133
66
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const attrName = attr.getName().potential.trim().toLowerCase();
|
|
139
|
-
if (/^aria-/i.test(attrName)) {
|
|
140
|
-
const ariaAttr = ariaAttrs.find(attr => attr.name === attrName);
|
|
141
|
-
if (ariaAttr && !ariaAttr.isGlobal) {
|
|
142
|
-
report({
|
|
143
|
-
scope: node,
|
|
144
|
-
message: t('{0} is not {1}', t('the "{0*}" {1}', attrName, 'ARIA state/property'), 'global state/property'),
|
|
145
|
-
line: attr.startLine,
|
|
146
|
-
col: attr.startCol,
|
|
147
|
-
raw: attr.raw,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
}
|
|
67
|
+
for (const attr of propAttrs) {
|
|
68
|
+
report((0, disallowed_prop_1.checkingDisallowedProp)({ attr, role: computed.role, propSpecs }));
|
|
69
|
+
if (el.rule.option.checkingDeprecatedProps) {
|
|
70
|
+
report((0, deprecated_props_1.checkingDeprecatedProps)({ attr, role: computed.role, propSpecs }));
|
|
151
71
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
72
|
+
if (el.rule.option.disallowSetImplicitProps) {
|
|
73
|
+
const attrSpecs = (0, ml_core_1.getAttrSpecs)(el, document.specs);
|
|
74
|
+
report((0, implicit_props_1.checkingImplicitProps)({ attr, propSpecs, attrSpecs }));
|
|
75
|
+
}
|
|
76
|
+
if (el.rule.option.checkingValue) {
|
|
77
|
+
report((0, value_1.checkingValue)({ attr, role: computed.role, propSpecs }));
|
|
78
|
+
}
|
|
79
|
+
if (el.rule.option.disallowDefaultValue) {
|
|
80
|
+
report((0, default_value_1.checkingDefaultValue)({ attr, propSpecs }));
|
|
156
81
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const value = attr.getValue().potential.trim().toLowerCase();
|
|
160
|
-
const propSpec = ariaAttrs.find(p => p.name === attrName);
|
|
161
|
-
// Checking ARIA Value
|
|
162
|
-
if (node.rule.option.checkingValue) {
|
|
163
|
-
const result = (0, helpers_1.checkAria)(document.specs, attrName, value, computedRole === null || computedRole === void 0 ? void 0 : computedRole.name);
|
|
164
|
-
if (!result.isValid) {
|
|
165
|
-
report({
|
|
166
|
-
scope: node,
|
|
167
|
-
message: t('{0:c} on {1}', t('{0} is {1:c}', t('the "{0}"', value), 'disallowed'), t('the "{0*}" {1}', attrName, 'ARIA state/property')) +
|
|
168
|
-
('enum' in result && result.enum.length
|
|
169
|
-
? t('. ') + t('Allowed values are: {0}', t(result.enum))
|
|
170
|
-
: ''),
|
|
171
|
-
line: attr.startLine,
|
|
172
|
-
col: attr.startCol,
|
|
173
|
-
raw: attr.raw,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
// Checking implicit props
|
|
178
|
-
if (node.rule.option.disallowSetImplicitProps) {
|
|
179
|
-
if (propSpec && propSpec.equivalentHtmlAttrs) {
|
|
180
|
-
for (const equivalentHtmlAttr of propSpec.equivalentHtmlAttrs) {
|
|
181
|
-
const htmlAttrSpec = attrSpecs.find(a => a.name === equivalentHtmlAttr.htmlAttrName);
|
|
182
|
-
const isValid = (0, helpers_1.isValidAttr)(t, equivalentHtmlAttr.htmlAttrName, equivalentHtmlAttr.value || '', false, node, attrSpecs);
|
|
183
|
-
if (isValid && isValid.invalidType === 'non-existent') {
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
if (node.hasAttribute(equivalentHtmlAttr.htmlAttrName)) {
|
|
187
|
-
const targetAttrValue = node.getAttribute(equivalentHtmlAttr.htmlAttrName);
|
|
188
|
-
if ((equivalentHtmlAttr.value == null && targetAttrValue === value) ||
|
|
189
|
-
equivalentHtmlAttr.value === value) {
|
|
190
|
-
report({
|
|
191
|
-
scope: node,
|
|
192
|
-
message: t('{0} has {1}', t('the "{0*}" {1}', attrName, 'ARIA state/property'), 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')))),
|
|
193
|
-
line: attr.startLine,
|
|
194
|
-
col: attr.startCol,
|
|
195
|
-
raw: attr.raw,
|
|
196
|
-
});
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
if ((htmlAttrSpec === null || htmlAttrSpec === void 0 ? void 0 : htmlAttrSpec.type) === 'Boolean' && value !== 'false') {
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
report({
|
|
203
|
-
scope: node,
|
|
204
|
-
message: t('{0} contradicts {1}', t('the "{0*}" {1}', attrName, 'ARIA state/property'), t('the current "{0}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute')),
|
|
205
|
-
line: attr.startLine,
|
|
206
|
-
col: attr.startCol,
|
|
207
|
-
raw: attr.raw,
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
else if (value === 'true') {
|
|
211
|
-
if (!equivalentHtmlAttr.isNotStrictEquivalent && (htmlAttrSpec === null || htmlAttrSpec === void 0 ? void 0 : htmlAttrSpec.type) === 'Boolean') {
|
|
212
|
-
report({
|
|
213
|
-
scope: node,
|
|
214
|
-
message: t('{0} contradicts {1}', t('the "{0*}" {1}', attrName, 'ARIA state/property'), t('the implicit "{0}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute')),
|
|
215
|
-
line: attr.startLine,
|
|
216
|
-
col: attr.startCol,
|
|
217
|
-
raw: attr.raw,
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
// Default value
|
|
225
|
-
if (node.rule.option.disallowDefaultValue && propSpec && propSpec.defaultValue === value) {
|
|
226
|
-
report({
|
|
227
|
-
scope: node,
|
|
228
|
-
message: t('It is {0}', 'default value'),
|
|
229
|
-
line: attr.startLine,
|
|
230
|
-
col: attr.startCol,
|
|
231
|
-
raw: attr.raw,
|
|
232
|
-
});
|
|
233
|
-
}
|
|
82
|
+
if (!computed.role) {
|
|
83
|
+
report((0, no_global_prop_1.checkingNoGlobalProp)({ attr, propSpecs }));
|
|
234
84
|
}
|
|
235
85
|
}
|
|
86
|
+
if (el.rule.option.checkingRequiredOwnedElements) {
|
|
87
|
+
report((0, required_owned_elements_1.checkingRequiredOwnedElements)({ el, role: computed.role }));
|
|
88
|
+
}
|
|
89
|
+
if (el.rule.option.checkingPresentationalChildren) {
|
|
90
|
+
report((0, presentational_children_1.checkingPresentationalChildren)({ el }));
|
|
91
|
+
}
|
|
92
|
+
if (el.rule.option.checkingInteractionInHidden) {
|
|
93
|
+
report((0, interaction_in_hidden_1.checkingInteractionInHidden)({ el }));
|
|
94
|
+
}
|
|
236
95
|
});
|
|
237
96
|
},
|
|
238
97
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ARIAVersion } from '@markuplint/ml-spec';
|
|
2
|
+
export declare 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": "
|
|
3
|
+
"version": "3.0.0-alpha.0",
|
|
4
4
|
"description": "Rules for markuplint",
|
|
5
5
|
"repository": "git@github.com:markuplint/markuplint.git",
|
|
6
6
|
"author": "Yusuke Hirao <yusukehirao@me.com>",
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"clean": "tsc --build --clean"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@markuplint/html-spec": "
|
|
21
|
-
"@markuplint/ml-core": "
|
|
22
|
-
"@markuplint/ml-spec": "
|
|
23
|
-
"@markuplint/types": "
|
|
20
|
+
"@markuplint/html-spec": "3.0.0-alpha.0",
|
|
21
|
+
"@markuplint/ml-core": "3.0.0-alpha.0",
|
|
22
|
+
"@markuplint/ml-spec": "3.0.0-alpha.0",
|
|
23
|
+
"@markuplint/types": "3.0.0-alpha.0",
|
|
24
24
|
"debug": "^4.3.4",
|
|
25
25
|
"html-entities": "^2.3.3",
|
|
26
26
|
"tslib": "^2.3.1"
|
|
@@ -28,5 +28,5 @@
|
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/debug": "^4.1.7"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "d32c522e016888e20bcbb5f09352c006b964f193"
|
|
32
32
|
}
|
package/schema.json
CHANGED
|
@@ -9,15 +9,6 @@
|
|
|
9
9
|
"attr-duplication": {
|
|
10
10
|
"$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/attr-duplication/schema.json"
|
|
11
11
|
},
|
|
12
|
-
"attr-equal-space-after": {
|
|
13
|
-
"$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/attr-equal-space-after/schema.json"
|
|
14
|
-
},
|
|
15
|
-
"attr-equal-space-before": {
|
|
16
|
-
"$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/attr-equal-space-before/schema.json"
|
|
17
|
-
},
|
|
18
|
-
"attr-spacing": {
|
|
19
|
-
"$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/attr-spacing/schema.json"
|
|
20
|
-
},
|
|
21
12
|
"attr-value-quotes": {
|
|
22
13
|
"$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/attr-value-quotes/schema.json"
|
|
23
14
|
},
|
|
@@ -51,9 +42,6 @@
|
|
|
51
42
|
"end-tag": {
|
|
52
43
|
"$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/end-tag/schema.json"
|
|
53
44
|
},
|
|
54
|
-
"indentation": {
|
|
55
|
-
"$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/indentation/schema.json"
|
|
56
|
-
},
|
|
57
45
|
"ineffective-attr": {
|
|
58
46
|
"$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/ineffective-attr/schema.json"
|
|
59
47
|
},
|