@markuplint/rules 4.11.2 → 5.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/CHANGELOG.md +63 -2
- package/SKILL.md +76 -0
- package/lib/attr-check.d.ts +46 -7
- package/lib/attr-check.js +42 -8
- package/lib/attr-duplication/index.d.ts +7 -0
- package/lib/attr-duplication/index.js +7 -0
- package/lib/attr-duplication/meta.d.ts +1 -0
- package/lib/attr-duplication/meta.js +1 -0
- package/lib/attr-value-quotes/index.d.ts +10 -0
- package/lib/attr-value-quotes/index.js +10 -0
- package/lib/attr-value-quotes/meta.d.ts +1 -0
- package/lib/attr-value-quotes/meta.js +1 -0
- package/lib/case-sensitive-attr-name/index.d.ts +9 -0
- package/lib/case-sensitive-attr-name/index.js +8 -0
- package/lib/case-sensitive-attr-name/meta.d.ts +1 -0
- package/lib/case-sensitive-attr-name/meta.js +1 -0
- package/lib/case-sensitive-tag-name/index.d.ts +9 -0
- package/lib/case-sensitive-tag-name/index.js +8 -0
- package/lib/case-sensitive-tag-name/meta.d.ts +1 -0
- package/lib/case-sensitive-tag-name/meta.js +1 -0
- package/lib/character-reference/index.d.ts +9 -0
- package/lib/character-reference/index.js +20 -0
- package/lib/character-reference/meta.d.ts +1 -0
- package/lib/character-reference/meta.js +1 -0
- package/lib/class-naming/index.d.ts +9 -0
- package/lib/class-naming/index.js +8 -0
- package/lib/class-naming/meta.d.ts +1 -0
- package/lib/class-naming/meta.js +1 -0
- package/lib/correct-aspect-ratio/index.d.ts +5 -0
- package/lib/correct-aspect-ratio/index.js +80 -0
- package/lib/correct-aspect-ratio/meta.d.ts +4 -0
- package/lib/correct-aspect-ratio/meta.js +3 -0
- package/lib/correct-aspect-ratio/resolve-image-size.d.ts +24 -0
- package/lib/correct-aspect-ratio/resolve-image-size.js +165 -0
- package/lib/create-message.d.ts +29 -2
- package/lib/create-message.js +60 -2
- package/lib/debug.d.ts +9 -0
- package/lib/debug.js +5 -0
- package/lib/deprecated-attr/index.d.ts +6 -0
- package/lib/deprecated-attr/index.js +6 -0
- package/lib/deprecated-attr/meta.d.ts +1 -0
- package/lib/deprecated-attr/meta.js +1 -0
- package/lib/deprecated-element/index.d.ts +6 -0
- package/lib/deprecated-element/index.js +8 -2
- package/lib/deprecated-element/meta.d.ts +1 -0
- package/lib/deprecated-element/meta.js +1 -0
- package/lib/disallowed-element/index.d.ts +7 -0
- package/lib/disallowed-element/index.js +7 -0
- package/lib/disallowed-element/meta.d.ts +1 -0
- package/lib/disallowed-element/meta.js +1 -0
- package/lib/doctype/index.d.ts +10 -0
- package/lib/doctype/index.js +8 -0
- package/lib/doctype/meta.d.ts +1 -0
- package/lib/doctype/meta.js +1 -0
- package/lib/end-tag/index.d.ts +7 -0
- package/lib/end-tag/index.js +8 -1
- package/lib/end-tag/meta.d.ts +1 -0
- package/lib/end-tag/meta.js +1 -0
- package/lib/heading-levels/index.d.ts +6 -0
- package/lib/heading-levels/index.js +6 -1
- package/lib/heading-levels/meta.d.ts +1 -0
- package/lib/heading-levels/meta.js +1 -0
- package/lib/helpers.d.ts +105 -0
- package/lib/helpers.js +112 -16
- package/lib/id-duplication/index.d.ts +7 -0
- package/lib/id-duplication/index.js +7 -0
- package/lib/id-duplication/meta.d.ts +1 -0
- package/lib/id-duplication/meta.js +1 -0
- package/lib/index.d.ts +43 -41
- package/lib/index.js +24 -0
- package/lib/ineffective-attr/index.d.ts +7 -0
- package/lib/ineffective-attr/index.js +7 -0
- package/lib/ineffective-attr/meta.d.ts +1 -0
- package/lib/ineffective-attr/meta.js +1 -0
- package/lib/invalid-attr/index.d.ts +29 -21
- package/lib/invalid-attr/index.js +60 -109
- package/lib/invalid-attr/meta.d.ts +1 -0
- package/lib/invalid-attr/meta.js +1 -0
- package/lib/label-has-control/index.d.ts +7 -0
- package/lib/label-has-control/index.js +11 -0
- package/lib/label-has-control/meta.d.ts +1 -0
- package/lib/label-has-control/meta.js +1 -0
- package/lib/landmark-roles/index.d.ts +14 -0
- package/lib/landmark-roles/index.js +21 -0
- package/lib/landmark-roles/meta.d.ts +1 -0
- package/lib/landmark-roles/meta.js +1 -0
- package/lib/link-types/index.d.ts +10 -0
- package/lib/link-types/index.js +180 -0
- package/lib/link-types/meta.d.ts +5 -0
- package/lib/link-types/meta.js +4 -0
- package/lib/neighbor-popovers/index.d.ts +8 -0
- package/lib/neighbor-popovers/index.js +28 -6
- package/lib/neighbor-popovers/meta.d.ts +1 -0
- package/lib/neighbor-popovers/meta.js +1 -0
- package/lib/no-ambiguous-navigable-target-names/index.d.ts +8 -0
- package/lib/no-ambiguous-navigable-target-names/index.js +8 -0
- package/lib/no-ambiguous-navigable-target-names/meta.d.ts +1 -0
- package/lib/no-ambiguous-navigable-target-names/meta.js +1 -0
- package/lib/no-boolean-attr-value/index.d.ts +7 -0
- package/lib/no-boolean-attr-value/index.js +7 -0
- package/lib/no-boolean-attr-value/meta.d.ts +1 -0
- package/lib/no-boolean-attr-value/meta.js +1 -0
- package/lib/no-consecutive-br/index.d.ts +8 -0
- package/lib/no-consecutive-br/index.js +8 -0
- package/lib/no-consecutive-br/meta.d.ts +1 -0
- package/lib/no-consecutive-br/meta.js +1 -0
- package/lib/no-default-value/index.d.ts +7 -0
- package/lib/no-default-value/index.js +7 -0
- package/lib/no-default-value/meta.d.ts +1 -0
- package/lib/no-default-value/meta.js +1 -0
- package/lib/no-duplicate-dt/index.d.ts +6 -0
- package/lib/no-duplicate-dt/index.js +6 -0
- package/lib/no-duplicate-dt/meta.d.ts +1 -0
- package/lib/no-duplicate-dt/meta.js +1 -0
- package/lib/no-empty-palpable-content/index.d.ts +13 -0
- package/lib/no-empty-palpable-content/index.js +12 -0
- package/lib/no-empty-palpable-content/meta.d.ts +1 -0
- package/lib/no-empty-palpable-content/meta.js +1 -0
- package/lib/no-hard-code-id/index.d.ts +8 -0
- package/lib/no-hard-code-id/index.js +8 -0
- package/lib/no-hard-code-id/meta.d.ts +1 -0
- package/lib/no-hard-code-id/meta.js +1 -0
- package/lib/no-orphaned-end-tag/index.d.ts +7 -0
- package/lib/no-orphaned-end-tag/index.js +8 -2
- package/lib/no-orphaned-end-tag/meta.d.ts +1 -0
- package/lib/no-orphaned-end-tag/meta.js +1 -0
- package/lib/no-refer-to-non-existent-id/index.d.ts +9 -1
- package/lib/no-refer-to-non-existent-id/index.js +11 -2
- package/lib/no-refer-to-non-existent-id/meta.d.ts +1 -0
- package/lib/no-refer-to-non-existent-id/meta.js +1 -0
- package/lib/no-unsupported-features/compat-data.d.ts +71 -0
- package/lib/no-unsupported-features/compat-data.js +204 -0
- package/lib/no-unsupported-features/index.d.ts +24 -0
- package/lib/no-unsupported-features/index.js +139 -0
- package/lib/no-unsupported-features/meta.d.ts +5 -0
- package/lib/no-unsupported-features/meta.js +4 -0
- package/lib/no-unsupported-features/resolve-browsers.d.ts +30 -0
- package/lib/no-unsupported-features/resolve-browsers.js +125 -0
- package/lib/no-use-event-handler-attr/index.d.ts +10 -0
- package/lib/no-use-event-handler-attr/index.js +8 -0
- package/lib/no-use-event-handler-attr/meta.d.ts +1 -0
- package/lib/no-use-event-handler-attr/meta.js +1 -0
- package/lib/permitted-contents/choice.d.ts +17 -0
- package/lib/permitted-contents/choice.js +31 -1
- package/lib/permitted-contents/complex-branch.d.ts +11 -7
- package/lib/permitted-contents/complex-branch.js +11 -7
- package/lib/permitted-contents/content-model.d.ts +11 -0
- package/lib/permitted-contents/content-model.js +32 -0
- package/lib/permitted-contents/count-pattern.d.ts +13 -7
- package/lib/permitted-contents/count-pattern.js +24 -8
- package/lib/permitted-contents/debug.browser.d.ts +12 -0
- package/lib/permitted-contents/debug.browser.js +12 -0
- package/lib/permitted-contents/debug.d.ts +12 -0
- package/lib/permitted-contents/debug.js +12 -0
- package/lib/permitted-contents/index.d.ts +12 -0
- package/lib/permitted-contents/index.js +21 -0
- package/lib/permitted-contents/matches-selector.d.ts +19 -0
- package/lib/permitted-contents/matches-selector.js +33 -0
- package/lib/permitted-contents/meta.d.ts +1 -0
- package/lib/permitted-contents/meta.js +1 -0
- package/lib/permitted-contents/order.d.ts +14 -7
- package/lib/permitted-contents/order.js +15 -8
- package/lib/permitted-contents/recursive-branch.d.ts +17 -0
- package/lib/permitted-contents/recursive-branch.js +17 -0
- package/lib/permitted-contents/represent-transparent-nodes.d.ts +54 -0
- package/lib/permitted-contents/represent-transparent-nodes.js +119 -12
- package/lib/permitted-contents/start.d.ts +12 -6
- package/lib/permitted-contents/start.js +12 -6
- package/lib/permitted-contents/transparent.d.ts +12 -0
- package/lib/permitted-contents/transparent.js +12 -0
- package/lib/permitted-contents/types.d.ts +67 -0
- package/lib/permitted-contents/utils.d.ts +160 -0
- package/lib/permitted-contents/utils.js +216 -35
- package/lib/placeholder-label-option/index.d.ts +7 -0
- package/lib/placeholder-label-option/index.js +19 -15
- package/lib/placeholder-label-option/meta.d.ts +1 -0
- package/lib/placeholder-label-option/meta.js +1 -0
- package/lib/redundant-accessible-name/index.d.ts +10 -0
- package/lib/redundant-accessible-name/index.js +123 -0
- package/lib/redundant-accessible-name/meta.d.ts +5 -0
- package/lib/redundant-accessible-name/meta.js +4 -0
- package/lib/require-accessible-name/index.d.ts +10 -4
- package/lib/require-accessible-name/index.js +13 -5
- package/lib/require-accessible-name/meta.d.ts +1 -0
- package/lib/require-accessible-name/meta.js +1 -0
- package/lib/require-datetime/index.d.ts +12 -0
- package/lib/require-datetime/index.js +8 -0
- package/lib/require-datetime/meta.d.ts +1 -0
- package/lib/require-datetime/meta.js +1 -0
- package/lib/require-datetime/types.d.ts +7 -0
- package/lib/require-datetime/utils.d.ts +18 -5
- package/lib/require-datetime/utils.js +53 -9
- package/lib/require-dialog-autofocus/index.d.ts +22 -0
- package/lib/require-dialog-autofocus/index.js +62 -0
- package/lib/require-dialog-autofocus/meta.d.ts +5 -0
- package/lib/require-dialog-autofocus/meta.js +4 -0
- package/lib/required-attr/index.d.ts +21 -1
- package/lib/required-attr/index.js +34 -4
- package/lib/required-attr/meta.d.ts +1 -0
- package/lib/required-attr/meta.js +1 -0
- package/lib/required-element/index.d.ts +14 -0
- package/lib/required-element/index.js +14 -2
- package/lib/required-element/meta.d.ts +1 -0
- package/lib/required-element/meta.js +1 -0
- package/lib/required-h1/index.d.ts +12 -0
- package/lib/required-h1/index.js +7 -0
- package/lib/required-h1/meta.d.ts +1 -0
- package/lib/required-h1/meta.js +1 -0
- package/lib/srcset-sizes-constraint/index.d.ts +6 -0
- package/lib/srcset-sizes-constraint/index.js +108 -0
- package/lib/srcset-sizes-constraint/meta.d.ts +5 -0
- package/lib/srcset-sizes-constraint/meta.js +4 -0
- package/lib/srcset-sizes-constraint/parse-srcset.d.ts +40 -0
- package/lib/srcset-sizes-constraint/parse-srcset.js +56 -0
- package/lib/table-row-column-alignment/find-children.d.ts +10 -0
- package/lib/table-row-column-alignment/find-children.js +11 -0
- package/lib/table-row-column-alignment/grid.d.ts +60 -0
- package/lib/table-row-column-alignment/grid.js +104 -26
- package/lib/table-row-column-alignment/index.d.ts +8 -0
- package/lib/table-row-column-alignment/index.js +8 -0
- package/lib/table-row-column-alignment/meta.d.ts +1 -0
- package/lib/table-row-column-alignment/meta.js +1 -0
- package/lib/table-row-column-alignment/types.d.ts +10 -0
- package/lib/use-list/index.d.ts +16 -0
- package/lib/use-list/index.js +20 -0
- package/lib/use-list/meta.d.ts +1 -0
- package/lib/use-list/meta.js +1 -0
- package/lib/wai-aria/checkings/abstract-role.d.ts +9 -0
- package/lib/wai-aria/checkings/abstract-role.js +14 -2
- package/lib/wai-aria/checkings/default-value.d.ts +11 -0
- package/lib/wai-aria/checkings/default-value.js +11 -0
- package/lib/wai-aria/checkings/deprecated-props.d.ts +11 -0
- package/lib/wai-aria/checkings/deprecated-props.js +11 -0
- package/lib/wai-aria/checkings/deprecated-role.d.ts +10 -0
- package/lib/wai-aria/checkings/deprecated-role.js +10 -0
- package/lib/wai-aria/checkings/disallowed-prop.d.ts +14 -0
- package/lib/wai-aria/checkings/disallowed-prop.js +19 -2
- package/lib/wai-aria/checkings/implicit-props.d.ts +13 -0
- package/lib/wai-aria/checkings/implicit-props.js +13 -0
- package/lib/wai-aria/checkings/implicit-role.d.ts +9 -0
- package/lib/wai-aria/checkings/implicit-role.js +14 -2
- package/lib/wai-aria/checkings/interaction-in-hidden.d.ts +9 -0
- package/lib/wai-aria/checkings/interaction-in-hidden.js +15 -4
- package/lib/wai-aria/checkings/no-global-prop.d.ts +10 -0
- package/lib/wai-aria/checkings/no-global-prop.js +10 -0
- package/lib/wai-aria/checkings/non-existent-role.d.ts +11 -0
- package/lib/wai-aria/checkings/non-existent-role.js +19 -2
- package/lib/wai-aria/checkings/permitted-roles.d.ts +10 -0
- package/lib/wai-aria/checkings/permitted-roles.js +15 -2
- package/lib/wai-aria/checkings/presentational-children.d.ts +7 -1
- package/lib/wai-aria/checkings/presentational-children.js +17 -3
- package/lib/wai-aria/checkings/required-accessibility-parent-role.d.ts +25 -0
- package/lib/wai-aria/checkings/required-accessibility-parent-role.js +56 -0
- package/lib/wai-aria/checkings/required-owned-elements.d.ts +10 -1
- package/lib/wai-aria/checkings/required-owned-elements.js +69 -25
- package/lib/wai-aria/checkings/required-prop.d.ts +13 -0
- package/lib/wai-aria/checkings/required-prop.js +18 -2
- package/lib/wai-aria/checkings/value.d.ts +23 -0
- package/lib/wai-aria/checkings/value.js +32 -0
- package/lib/wai-aria/index.d.ts +10 -0
- package/lib/wai-aria/index.js +22 -4
- package/lib/wai-aria/meta.d.ts +1 -0
- package/lib/wai-aria/meta.js +1 -0
- package/lib/wai-aria/types.d.ts +25 -1
- package/package.json +16 -11
- package/schema.json +18 -0
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
-
};
|
|
7
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
-
};
|
|
12
|
-
var _Collection_locked, _Collection_matched, _Collection_nodes, _Collection_origin;
|
|
13
1
|
import { createSelector } from '@markuplint/selector';
|
|
14
2
|
import { bgGreen, green, bgRed, bgBlue, blue, bgMagenta, cyan } from './debug.js';
|
|
15
3
|
import { transparentMode } from './represent-transparent-nodes.js';
|
|
4
|
+
/**
|
|
5
|
+
* Determines whether a given value is a terminal model (a selector string or an array
|
|
6
|
+
* of selector strings) rather than a nested array of content model patterns.
|
|
7
|
+
* Distinguishes between `Model` (leaf-level selectors) and `PermittedContentPattern[]`
|
|
8
|
+
* (structural pattern arrays that require further recursive evaluation).
|
|
9
|
+
*
|
|
10
|
+
* @param model - The value to check, either a terminal model or a nested pattern array.
|
|
11
|
+
* @returns True if the value is a terminal model (string or string array), false if it is a pattern array.
|
|
12
|
+
*/
|
|
16
13
|
export function isModel(model) {
|
|
17
14
|
if (typeof model === 'string') {
|
|
18
15
|
return true;
|
|
@@ -29,6 +26,16 @@ export function isModel(model) {
|
|
|
29
26
|
}
|
|
30
27
|
return modelMode;
|
|
31
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Tests whether a child node matches a CSS selector string using the markuplint
|
|
31
|
+
* selector engine. Returns whether the node matched and, if not, the deepest
|
|
32
|
+
* unmatched descendant node for diagnostic purposes.
|
|
33
|
+
*
|
|
34
|
+
* @param selector - The CSS selector string to test against.
|
|
35
|
+
* @param node - The child node to test.
|
|
36
|
+
* @param specs - The spec data passed to the selector engine for attribute resolution.
|
|
37
|
+
* @returns An object with `matched: true` if the node matches, or `matched: false` with an optional `not` node.
|
|
38
|
+
*/
|
|
32
39
|
export function matches(selector,
|
|
33
40
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
34
41
|
node, specs) {
|
|
@@ -48,6 +55,14 @@ node, specs) {
|
|
|
48
55
|
not,
|
|
49
56
|
};
|
|
50
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Traverses a chain of nested `:has()` selector results to find the deepest
|
|
60
|
+
* descendant nodes that caused a selector mismatch. Used to provide precise
|
|
61
|
+
* error reporting by identifying the actual offending node in nested selectors.
|
|
62
|
+
*
|
|
63
|
+
* @param selectorResult - A matched selector result that may contain nested `:has()` results.
|
|
64
|
+
* @returns The deepest descendant child nodes from the nested selector chain.
|
|
65
|
+
*/
|
|
51
66
|
function descendants(
|
|
52
67
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
53
68
|
selectorResult) {
|
|
@@ -58,6 +73,7 @@ selectorResult) {
|
|
|
58
73
|
nodes = dep.nodes;
|
|
59
74
|
continue;
|
|
60
75
|
}
|
|
76
|
+
// eslint-disable-next-line no-useless-assignment
|
|
61
77
|
selectorResult = dep;
|
|
62
78
|
continue;
|
|
63
79
|
}
|
|
@@ -65,24 +81,74 @@ selectorResult) {
|
|
|
65
81
|
}
|
|
66
82
|
return nodes;
|
|
67
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Type guard that checks whether a content model pattern is a "require" pattern,
|
|
86
|
+
* indicating one or more required occurrences of a specific element or model.
|
|
87
|
+
*
|
|
88
|
+
* @param content - The content model pattern to check.
|
|
89
|
+
* @returns True if the pattern has a `require` property.
|
|
90
|
+
*/
|
|
68
91
|
export function isRequire(content) {
|
|
69
92
|
return 'require' in content;
|
|
70
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Type guard that checks whether a content model pattern is an "optional" pattern,
|
|
96
|
+
* indicating zero or one occurrences of a specific element or model.
|
|
97
|
+
*
|
|
98
|
+
* @param content - The content model pattern to check.
|
|
99
|
+
* @returns True if the pattern has an `optional` property.
|
|
100
|
+
*/
|
|
71
101
|
export function isOptional(content) {
|
|
72
102
|
return 'optional' in content;
|
|
73
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Type guard that checks whether a content model pattern is a "oneOrMore" pattern,
|
|
106
|
+
* indicating one or more occurrences of a specific element or model.
|
|
107
|
+
*
|
|
108
|
+
* @param content - The content model pattern to check.
|
|
109
|
+
* @returns True if the pattern has a `oneOrMore` property.
|
|
110
|
+
*/
|
|
74
111
|
export function isOneOrMore(content) {
|
|
75
112
|
return 'oneOrMore' in content;
|
|
76
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Type guard that checks whether a content model pattern is a "zeroOrMore" pattern,
|
|
116
|
+
* indicating zero or more occurrences of a specific element or model.
|
|
117
|
+
*
|
|
118
|
+
* @param content - The content model pattern to check.
|
|
119
|
+
* @returns True if the pattern has a `zeroOrMore` property.
|
|
120
|
+
*/
|
|
77
121
|
export function isZeroOrMore(content) {
|
|
78
122
|
return 'zeroOrMore' in content;
|
|
79
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Type guard that checks whether a content model pattern is a "choice" pattern,
|
|
126
|
+
* representing an alternation between multiple possible content model branches.
|
|
127
|
+
*
|
|
128
|
+
* @param content - The content model pattern to check.
|
|
129
|
+
* @returns True if the pattern has a `choice` property.
|
|
130
|
+
*/
|
|
80
131
|
export function isChoice(content) {
|
|
81
132
|
return 'choice' in content;
|
|
82
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Type guard that checks whether a content model pattern is a "transparent" pattern,
|
|
136
|
+
* indicating the element inherits its parent's content model.
|
|
137
|
+
*
|
|
138
|
+
* @param content - The content model pattern to check.
|
|
139
|
+
* @returns True if the pattern has a `transparent` property.
|
|
140
|
+
*/
|
|
83
141
|
export function isTransparent(content) {
|
|
84
142
|
return 'transparent' in content;
|
|
85
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Normalizes a quantified content model pattern (require, optional, oneOrMore, or zeroOrMore)
|
|
146
|
+
* into a uniform representation with the inner model, minimum count, maximum count,
|
|
147
|
+
* a regex-like repeat sign for debug display, and the appropriate missing-node error type.
|
|
148
|
+
*
|
|
149
|
+
* @param pattern - A quantified content model pattern to normalize.
|
|
150
|
+
* @returns An object with `model`, `min`, `max`, `repeat` sign, and optional `missingType`.
|
|
151
|
+
*/
|
|
86
152
|
export function normalizeModel(pattern) {
|
|
87
153
|
let model;
|
|
88
154
|
let min;
|
|
@@ -137,18 +203,36 @@ export function normalizeModel(pattern) {
|
|
|
137
203
|
missingType,
|
|
138
204
|
};
|
|
139
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Merges two Hints objects, combining their properties and selecting the
|
|
208
|
+
* `missing` hint with the higher `barelyMatchedElements` count (i.e., the
|
|
209
|
+
* one closest to a successful match) for the most useful error diagnostics.
|
|
210
|
+
*
|
|
211
|
+
* @param a - The first hints object.
|
|
212
|
+
* @param b - The second hints object.
|
|
213
|
+
* @returns A merged hints object with undefined properties removed.
|
|
214
|
+
*/
|
|
140
215
|
export function mergeHints(
|
|
141
216
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
142
217
|
a,
|
|
143
218
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
144
219
|
b) {
|
|
145
|
-
const missing = [a.missing, b.missing].
|
|
220
|
+
const missing = [a.missing, b.missing].toSorted((a, b) => (b?.barelyMatchedElements ?? 0) - (a?.barelyMatchedElements ?? 0))[0];
|
|
146
221
|
return cleanObject({
|
|
147
222
|
...a,
|
|
148
223
|
...b,
|
|
149
224
|
missing: missing && cleanObject(missing),
|
|
150
225
|
});
|
|
151
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Creates a shallow copy of an object with all `undefined` values removed.
|
|
229
|
+
* Used to produce clean hint objects for result reporting without
|
|
230
|
+
* extraneous undefined properties.
|
|
231
|
+
*
|
|
232
|
+
* @template T - The object type.
|
|
233
|
+
* @param object - The source object to clean.
|
|
234
|
+
* @returns A new object containing only the defined properties of the input.
|
|
235
|
+
*/
|
|
152
236
|
export function cleanObject(object) {
|
|
153
237
|
const newObject = {};
|
|
154
238
|
for (const [key, value] of Object.entries(object)) {
|
|
@@ -158,57 +242,115 @@ export function cleanObject(object) {
|
|
|
158
242
|
}
|
|
159
243
|
return newObject;
|
|
160
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Tracks matched and unmatched child nodes during content model validation.
|
|
247
|
+
* Provides operations for adding matched nodes, backtracking to a locked state,
|
|
248
|
+
* capping matches at a maximum count, and generating colored debug output.
|
|
249
|
+
*
|
|
250
|
+
* The collection maintains an ordered set of original nodes and partitions them
|
|
251
|
+
* into matched and unmatched sets as the validation algorithm progresses.
|
|
252
|
+
*/
|
|
161
253
|
export class Collection {
|
|
254
|
+
#locked = new Set();
|
|
255
|
+
#matched = new Set();
|
|
256
|
+
#nodes;
|
|
257
|
+
#origin;
|
|
258
|
+
/**
|
|
259
|
+
* Creates a new Collection from the given child nodes.
|
|
260
|
+
*
|
|
261
|
+
* @param origin - The initial list of child nodes to track.
|
|
262
|
+
*/
|
|
162
263
|
constructor(
|
|
163
264
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
164
265
|
origin) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
_Collection_nodes.set(this, void 0);
|
|
168
|
-
_Collection_origin.set(this, void 0);
|
|
169
|
-
__classPrivateFieldSet(this, _Collection_origin, [...origin], "f");
|
|
170
|
-
__classPrivateFieldSet(this, _Collection_nodes, new Set(__classPrivateFieldGet(this, _Collection_origin, "f")), "f");
|
|
266
|
+
this.#origin = [...origin];
|
|
267
|
+
this.#nodes = new Set(this.#origin);
|
|
171
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Returns a copy of the currently matched nodes in insertion order.
|
|
271
|
+
*/
|
|
172
272
|
get matched() {
|
|
173
|
-
return [...
|
|
273
|
+
return [...this.#matched];
|
|
174
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Returns the number of currently matched nodes.
|
|
277
|
+
*/
|
|
175
278
|
get matchedCount() {
|
|
176
|
-
return
|
|
279
|
+
return this.#matched.size;
|
|
177
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* Returns a copy of all original nodes in their original order.
|
|
283
|
+
*/
|
|
178
284
|
get nodes() {
|
|
179
|
-
return [...
|
|
285
|
+
return [...this.#origin];
|
|
180
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* Returns the nodes that have not yet been matched, preserving original order.
|
|
289
|
+
*/
|
|
181
290
|
get unmatched() {
|
|
182
|
-
return [...
|
|
291
|
+
return [...this.#nodes].filter(n => !this.#matched.has(n));
|
|
183
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Adds nodes to the matched set. All nodes must belong to the original
|
|
295
|
+
* collection; external nodes will cause a ReferenceError.
|
|
296
|
+
*
|
|
297
|
+
* @param nodes - The child nodes to mark as matched.
|
|
298
|
+
* @returns True if the matched set grew (new nodes were added), false otherwise.
|
|
299
|
+
*/
|
|
184
300
|
addMatched(
|
|
185
301
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
186
302
|
nodes) {
|
|
187
|
-
const i =
|
|
303
|
+
const i = this.#matched.size;
|
|
188
304
|
for (const node of nodes) {
|
|
189
|
-
if (!
|
|
305
|
+
if (!this.#nodes.has(node)) {
|
|
190
306
|
throw new ReferenceError(`External Node: ${node.nodeName}`);
|
|
191
307
|
}
|
|
192
|
-
|
|
308
|
+
this.#matched.add(node);
|
|
193
309
|
}
|
|
194
|
-
return i <
|
|
310
|
+
return i < this.#matched.size;
|
|
195
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* Reverts the matched set to the last locked state, discarding
|
|
314
|
+
* any matches added since the last `lock()` call. Used for backtracking
|
|
315
|
+
* when a pattern match attempt fails after a zero-match.
|
|
316
|
+
*/
|
|
196
317
|
back() {
|
|
197
|
-
|
|
318
|
+
this.#matched = new Set(this.#locked);
|
|
198
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* Saves the current matched set as a checkpoint that `back()` can
|
|
322
|
+
* revert to. Called after a successful backtrack recovery to preserve
|
|
323
|
+
* the known-good state.
|
|
324
|
+
*/
|
|
199
325
|
lock() {
|
|
200
|
-
|
|
326
|
+
this.#locked = new Set(this.#matched);
|
|
201
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* Trims the matched set to at most `max` entries by removing
|
|
330
|
+
* the most recently added nodes beyond the limit. Used when the
|
|
331
|
+
* maximum occurrence count for a pattern is exceeded.
|
|
332
|
+
*
|
|
333
|
+
* @param max - The maximum number of matched nodes to retain.
|
|
334
|
+
*/
|
|
202
335
|
max(max) {
|
|
203
|
-
const sliced = [...
|
|
336
|
+
const sliced = [...this.#matched].slice(max);
|
|
204
337
|
for (const n of sliced)
|
|
205
|
-
|
|
338
|
+
this.#matched.delete(n);
|
|
206
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* Returns a colored string representation of all nodes for debug logging.
|
|
342
|
+
* Matched nodes are shown in green, locked nodes in bold green, unmatched
|
|
343
|
+
* extra nodes in red (when highlighted), and transparent-mode nodes in
|
|
344
|
+
* blue/cyan/magenta variants.
|
|
345
|
+
*
|
|
346
|
+
* @param highlightExtraNodes - When true, unmatched nodes are highlighted in red/magenta.
|
|
347
|
+
* @returns A formatted string like `[ <div>, <span>, <p> ]` with ANSI colors.
|
|
348
|
+
*/
|
|
207
349
|
toString(highlightExtraNodes = false) {
|
|
208
350
|
const out = [];
|
|
209
|
-
for (const n of
|
|
351
|
+
for (const n of this.#origin) {
|
|
210
352
|
const raw = n.is(n.TEXT_NODE) ? `:text(${n.raw.replaceAll('\n', '\\n')})` : n.raw;
|
|
211
|
-
if (
|
|
353
|
+
if (this.#locked.has(n)) {
|
|
212
354
|
if (transparentMode.has(n)) {
|
|
213
355
|
out.push(bgBlue.bold(raw));
|
|
214
356
|
}
|
|
@@ -217,7 +359,7 @@ export class Collection {
|
|
|
217
359
|
}
|
|
218
360
|
continue;
|
|
219
361
|
}
|
|
220
|
-
if (
|
|
362
|
+
if (this.#matched.has(n)) {
|
|
221
363
|
if (transparentMode.has(n)) {
|
|
222
364
|
out.push(blue.bold(raw));
|
|
223
365
|
}
|
|
@@ -245,9 +387,23 @@ export class Collection {
|
|
|
245
387
|
return `[ ${out.join(', ')} ]`;
|
|
246
388
|
}
|
|
247
389
|
}
|
|
248
|
-
|
|
390
|
+
/**
|
|
391
|
+
* Error class representing an unsupported content model feature.
|
|
392
|
+
* Thrown when the validation engine encounters a pattern type or
|
|
393
|
+
* configuration that is not yet implemented.
|
|
394
|
+
*/
|
|
249
395
|
export class UnsupportedError extends Error {
|
|
250
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* Formats a content model (terminal model or pattern array) and its repeat sign
|
|
399
|
+
* into a regex-like string for debug logging output. Terminal selectors are
|
|
400
|
+
* rendered as `<selector>`, arrays of selectors as `(<s1>|<s2>)`, and
|
|
401
|
+
* nested patterns are recursively formatted.
|
|
402
|
+
*
|
|
403
|
+
* @param model - The model or pattern array to format.
|
|
404
|
+
* @param repeat - The quantifier sign to append (e.g., `+`, `*`, `?`, or `{m,n}`).
|
|
405
|
+
* @returns A human-readable regex-like string representation of the model.
|
|
406
|
+
*/
|
|
251
407
|
export function modelLog(model, repeat) {
|
|
252
408
|
if (!isModel(model)) {
|
|
253
409
|
return orderLog(model, repeat);
|
|
@@ -257,11 +413,28 @@ export function modelLog(model, repeat) {
|
|
|
257
413
|
}
|
|
258
414
|
return `(<${model.join('>|<')}>)${repeat}`;
|
|
259
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* Formats an ordered array of content model patterns into a regex-like
|
|
418
|
+
* string for debug logging, with an optional repeat quantifier wrapping
|
|
419
|
+
* the entire sequence.
|
|
420
|
+
*
|
|
421
|
+
* @param order - The ordered array of patterns to format.
|
|
422
|
+
* @param repeat - The quantifier sign to wrap around the sequence.
|
|
423
|
+
* @returns A formatted string representation of the ordered patterns.
|
|
424
|
+
*/
|
|
260
425
|
function orderLog(order, repeat) {
|
|
261
426
|
return order.length === 1 && order[0]
|
|
262
427
|
? markRepeat(patternLog(order[0]), repeat)
|
|
263
428
|
: markRepeat(order.map(pattern => patternLog(pattern)).join(''), repeat);
|
|
264
429
|
}
|
|
430
|
+
/**
|
|
431
|
+
* Formats a single content model pattern into a regex-like string for debug logging.
|
|
432
|
+
* Handles transparent, choice, and quantified patterns by dispatching to the
|
|
433
|
+
* appropriate formatting logic.
|
|
434
|
+
*
|
|
435
|
+
* @param pattern - The content model pattern to format.
|
|
436
|
+
* @returns A human-readable string representation of the pattern.
|
|
437
|
+
*/
|
|
265
438
|
function patternLog(pattern) {
|
|
266
439
|
if (isTransparent(pattern)) {
|
|
267
440
|
// 適当
|
|
@@ -273,6 +446,14 @@ function patternLog(pattern) {
|
|
|
273
446
|
const { model, repeat } = normalizeModel(pattern);
|
|
274
447
|
return modelLog(model, repeat);
|
|
275
448
|
}
|
|
449
|
+
/**
|
|
450
|
+
* Wraps a pattern string with parentheses and a repeat quantifier if a
|
|
451
|
+
* repeat sign is present. If no repeat sign is given, returns the pattern unchanged.
|
|
452
|
+
*
|
|
453
|
+
* @param pattern - The pattern string to wrap.
|
|
454
|
+
* @param repeat - The quantifier sign to append, or an empty string for no repetition.
|
|
455
|
+
* @returns The pattern string, optionally wrapped as `(pattern)quantifier`.
|
|
456
|
+
*/
|
|
276
457
|
function markRepeat(pattern, repeat) {
|
|
277
458
|
return repeat ? `(${pattern})${repeat}` : pattern;
|
|
278
459
|
}
|
|
@@ -1,2 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule that validates required `<select>` elements have a placeholder label option.
|
|
3
|
+
*
|
|
4
|
+
* Per the HTML spec, a `<select>` with `required`, without `multiple`, and with a
|
|
5
|
+
* display size of 1 must have a placeholder label option (first `<option>` with
|
|
6
|
+
* an empty value directly under `<select>`).
|
|
7
|
+
*/
|
|
1
8
|
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<boolean, undefined>>;
|
|
2
9
|
export default _default;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
import meta from './meta.js';
|
|
3
|
+
/**
|
|
4
|
+
* Rule that validates required `<select>` elements have a placeholder label option.
|
|
5
|
+
*
|
|
6
|
+
* Per the HTML spec, a `<select>` with `required`, without `multiple`, and with a
|
|
7
|
+
* display size of 1 must have a placeholder label option (first `<option>` with
|
|
8
|
+
* an empty value directly under `<select>`).
|
|
9
|
+
*/
|
|
3
10
|
export default createRule({
|
|
4
11
|
meta: meta,
|
|
5
12
|
verify({ document, report, t }) {
|
|
@@ -18,13 +25,13 @@ export default createRule({
|
|
|
18
25
|
},
|
|
19
26
|
});
|
|
20
27
|
/**
|
|
21
|
-
*
|
|
22
|
-
* > does not have a multiple attribute specified,
|
|
23
|
-
* > and has a display size of 1,
|
|
24
|
-
* > then the select element must have a placeholder label option.
|
|
28
|
+
* Determines whether a `<select>` element requires a placeholder label option.
|
|
25
29
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
30
|
+
* Per the HTML spec, a select element needs a placeholder label option when it
|
|
31
|
+
* has `required`, does not have `multiple`, and has a display size of 1.
|
|
32
|
+
*
|
|
33
|
+
* @param select - The `<select>` element to evaluate.
|
|
34
|
+
* @returns `true` if the select element requires a placeholder label option.
|
|
28
35
|
*/
|
|
29
36
|
function needPlaceholderLabelOption(
|
|
30
37
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
@@ -44,16 +51,13 @@ select) {
|
|
|
44
51
|
return true;
|
|
45
52
|
}
|
|
46
53
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* > in the select element's list of options (if any) is the empty string,
|
|
52
|
-
* > and that option element's parent node is the select element (and not an optgroup element),
|
|
53
|
-
* > then that option is the select element's **placeholder label option**.
|
|
54
|
+
* Checks whether a `<select>` element already has a valid placeholder label option.
|
|
55
|
+
*
|
|
56
|
+
* A placeholder label option is the first `<option>` whose value is the empty string
|
|
57
|
+
* and whose parent is the `<select>` element itself (not an `<optgroup>`).
|
|
54
58
|
*
|
|
55
|
-
* @param select
|
|
56
|
-
* @returns
|
|
59
|
+
* @param select - The `<select>` element to check.
|
|
60
|
+
* @returns `true` if the element has a valid placeholder label option.
|
|
57
61
|
*/
|
|
58
62
|
function hasPlaceholderLabelOption(
|
|
59
63
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RuleConfigValue } from '@markuplint/ml-core';
|
|
2
|
+
/**
|
|
3
|
+
* Rule that detects elements with multiple accessible name sources
|
|
4
|
+
* where a higher-priority source overrides a lower-priority one.
|
|
5
|
+
*/
|
|
6
|
+
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<RuleConfigValue, {
|
|
7
|
+
checkTitleFallback: boolean;
|
|
8
|
+
checkPlaceholderFallback: boolean;
|
|
9
|
+
}>>;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createRule, getComputedRole } from '@markuplint/ml-core';
|
|
2
|
+
import { ARIA_RECOMMENDED_VERSION, isExposed } from '@markuplint/ml-spec';
|
|
3
|
+
import { accnameMayBeMutable, getOwnedLabel } from '../helpers.js';
|
|
4
|
+
import meta from './meta.js';
|
|
5
|
+
/**
|
|
6
|
+
* Rule that detects elements with multiple accessible name sources
|
|
7
|
+
* where a higher-priority source overrides a lower-priority one.
|
|
8
|
+
*/
|
|
9
|
+
export default createRule({
|
|
10
|
+
meta,
|
|
11
|
+
defaultSeverity: 'warning',
|
|
12
|
+
defaultOptions: {
|
|
13
|
+
checkTitleFallback: false,
|
|
14
|
+
checkPlaceholderFallback: false,
|
|
15
|
+
},
|
|
16
|
+
async verify({ document, report, t }) {
|
|
17
|
+
await document.walkOn('Element', el => {
|
|
18
|
+
const ariaVersion = ARIA_RECOMMENDED_VERSION;
|
|
19
|
+
// Skip mutable elements (template expressions etc.)
|
|
20
|
+
if (accnameMayBeMutable(el, document)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// Skip hidden elements
|
|
24
|
+
if (!isExposed(el, document.specs, ariaVersion)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Skip nameFrom: "prohibited" roles (generic, none, presentation)
|
|
28
|
+
const computed = getComputedRole(document.specs, el, ariaVersion);
|
|
29
|
+
if (computed.role?.accessibleNameProhibited) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const sources = collectNamingSources(el, document, el.rule.options, computed);
|
|
33
|
+
if (sources.length >= 2) {
|
|
34
|
+
const winner = sources[0];
|
|
35
|
+
const overridden = sources.slice(1);
|
|
36
|
+
for (const loser of overridden) {
|
|
37
|
+
report({
|
|
38
|
+
scope: el,
|
|
39
|
+
message: t('The accessible name from "{0*}" overrides "{1*}"', winner.label, loser.label),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
function collectNamingSources(
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
48
|
+
el,
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
50
|
+
document, options,
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
52
|
+
computed) {
|
|
53
|
+
const sources = [];
|
|
54
|
+
// 1. aria-labelledby
|
|
55
|
+
const ariaLabelledby = el.getAttribute('aria-labelledby');
|
|
56
|
+
if (ariaLabelledby && ariaLabelledby.trim()) {
|
|
57
|
+
const ids = ariaLabelledby.trim().split(/\s+/);
|
|
58
|
+
const hasResolvableId = ids.some(id => document.querySelector(`[id="${id}"]`));
|
|
59
|
+
if (hasResolvableId) {
|
|
60
|
+
sources.push({ label: 'aria-labelledby' });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// 2. aria-label
|
|
64
|
+
const ariaLabel = el.getAttribute('aria-label');
|
|
65
|
+
if (ariaLabel && ariaLabel.trim()) {
|
|
66
|
+
sources.push({ label: 'aria-label' });
|
|
67
|
+
}
|
|
68
|
+
// 3. label (explicit or implicit) — only for labelable elements
|
|
69
|
+
const ownedLabel = getOwnedLabel(el, document);
|
|
70
|
+
if (ownedLabel) {
|
|
71
|
+
sources.push({ label: 'label' });
|
|
72
|
+
}
|
|
73
|
+
// 4. alt — img, area, input[type=image]
|
|
74
|
+
if (el.matches('img, area, input[type=image]')) {
|
|
75
|
+
const alt = el.getAttribute('alt');
|
|
76
|
+
if (alt != null && alt.trim()) {
|
|
77
|
+
sources.push({ label: 'alt' });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// 5. content — role allows nameFromContent
|
|
81
|
+
if (computed.role?.accessibleNameFromContent) {
|
|
82
|
+
const text = el.textContent.trim();
|
|
83
|
+
if (text) {
|
|
84
|
+
sources.push({ label: 'content' });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// 6. value — input[type=button/submit/reset]
|
|
88
|
+
if (el.matches('input[type=button], input[type=submit], input[type=reset]')) {
|
|
89
|
+
const value = el.getAttribute('value');
|
|
90
|
+
if (value != null && value.trim()) {
|
|
91
|
+
sources.push({ label: 'value' });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// 7. legend — fieldset (direct child only)
|
|
95
|
+
if (el.matches('fieldset')) {
|
|
96
|
+
const legend = el.querySelector(':scope > legend');
|
|
97
|
+
if (legend && legend.textContent.trim()) {
|
|
98
|
+
sources.push({ label: 'legend' });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// 8. caption — table (direct child only)
|
|
102
|
+
if (el.matches('table')) {
|
|
103
|
+
const caption = el.querySelector(':scope > caption');
|
|
104
|
+
if (caption && caption.textContent.trim()) {
|
|
105
|
+
sources.push({ label: 'caption' });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// 9. title (optional)
|
|
109
|
+
if (options.checkTitleFallback) {
|
|
110
|
+
const title = el.getAttribute('title');
|
|
111
|
+
if (title != null && title.trim()) {
|
|
112
|
+
sources.push({ label: 'title' });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// 10. placeholder (optional)
|
|
116
|
+
if (options.checkPlaceholderFallback) {
|
|
117
|
+
const placeholder = el.getAttribute('placeholder');
|
|
118
|
+
if (placeholder != null && placeholder.trim()) {
|
|
119
|
+
sources.push({ label: 'placeholder' });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return sources;
|
|
123
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { ARIAVersion } from '@markuplint/ml-spec';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Rule that requires elements with roles that need accessible names to have one.
|
|
4
|
+
*
|
|
5
|
+
* For each element exposed to the accessibility tree whose computed ARIA role
|
|
6
|
+
* has `accessibleNameRequired` set to `true`, this rule verifies that the element
|
|
7
|
+
* provides a non-empty accessible name.
|
|
8
|
+
*/
|
|
9
|
+
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, {
|
|
10
|
+
ariaVersion: ARIAVersion | undefined;
|
|
11
|
+
}>>;
|
|
6
12
|
export default _default;
|