@markuplint/rules 5.4.0 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/CLAUDE.md +4 -3
  3. package/lib/aria-prop-requires-role/index.js +2 -2
  4. package/lib/attr-eligibility.d.ts +21 -0
  5. package/lib/attr-eligibility.js +86 -6
  6. package/lib/attr-order/index.js +2 -1
  7. package/lib/attr-value-quotes/index.js +1 -1
  8. package/lib/consistent-table-row-length/index.js +1 -1
  9. package/lib/element-supports-aria-prop/index.js +2 -2
  10. package/lib/form-attr-references-form/index.js +2 -2
  11. package/lib/helpers.d.ts +43 -5
  12. package/lib/helpers.js +88 -26
  13. package/lib/input-list-references-datalist/index.js +2 -2
  14. package/lib/itemprop-requires-itemscope/index.js +27 -10
  15. package/lib/label-for-references-labelable/index.js +2 -2
  16. package/lib/label-has-control/index.d.ts +17 -0
  17. package/lib/label-has-control/index.js +28 -1
  18. package/lib/label-no-multiple-controls/index.d.ts +4 -5
  19. package/lib/label-no-multiple-controls/index.js +6 -6
  20. package/lib/landmark-roles/roles.d.ts +13 -1
  21. package/lib/landmark-roles/roles.js +22 -0
  22. package/lib/no-ambiguous-navigable-target-names/index.js +4 -0
  23. package/lib/no-aria-on-presentational-children/index.js +4 -0
  24. package/lib/no-aria-on-unsupported-element/index.js +2 -2
  25. package/lib/no-boolean-attr-value/index.js +2 -1
  26. package/lib/no-broken-fragment-link/index.d.ts +5 -0
  27. package/lib/no-broken-fragment-link/index.js +8 -2
  28. package/lib/no-consecutive-br/index.js +1 -1
  29. package/lib/no-contradictory-aria-prop/index.js +2 -2
  30. package/lib/no-default-aria-value/index.js +2 -2
  31. package/lib/no-default-value/index.js +4 -0
  32. package/lib/no-deprecated-aria-prop/index.js +2 -2
  33. package/lib/no-disallowed-ancestor/index.d.ts +16 -1
  34. package/lib/no-disallowed-ancestor/index.js +78 -4
  35. package/lib/no-disallowed-attr/index.js +4 -0
  36. package/lib/no-duplicate-dt/index.js +1 -1
  37. package/lib/no-duplicate-h1/index.js +18 -14
  38. package/lib/no-duplicate-id/index.d.ts +9 -0
  39. package/lib/no-duplicate-id/index.js +23 -4
  40. package/lib/no-empty-table-track/index.js +1 -1
  41. package/lib/no-ineffective-attr/index.js +4 -0
  42. package/lib/no-invalid-aria-prop-value/index.js +3 -3
  43. package/lib/no-invalid-attr-value/index.js +12 -0
  44. package/lib/no-nested-top-level-landmark/index.js +20 -17
  45. package/lib/no-prohibited-naming/index.js +2 -2
  46. package/lib/no-redundant-accessible-name/index.js +8 -7
  47. package/lib/no-redundant-aria-prop/index.js +2 -2
  48. package/lib/no-refer-to-non-existent-id/index.d.ts +15 -0
  49. package/lib/no-refer-to-non-existent-id/index.js +47 -16
  50. package/lib/no-restricted-element/index.js +4 -2
  51. package/lib/no-skipped-heading-level/index.js +9 -4
  52. package/lib/no-stray-head-or-body-tag/index.d.ts +2 -1
  53. package/lib/no-stray-head-or-body-tag/index.js +2 -1
  54. package/lib/no-table-cell-overlap/index.js +1 -1
  55. package/lib/no-table-span-overflow/index.js +1 -1
  56. package/lib/no-unescaped-char/index.js +1 -1
  57. package/lib/permitted-contents/index.js +2 -4
  58. package/lib/permitted-contents/matches-selector.js +28 -5
  59. package/lib/permitted-contents/represent-transparent-nodes.js +100 -68
  60. package/lib/permitted-contents/slot-content.d.ts +2 -1
  61. package/lib/permitted-contents/slot-content.js +28 -2
  62. package/lib/permitted-contents/start.js +4 -2
  63. package/lib/permitted-contents/utils.d.ts +11 -0
  64. package/lib/permitted-contents/utils.js +15 -0
  65. package/lib/placeholder-label-option/index.js +4 -26
  66. package/lib/require-accessible-name/index.js +2 -2
  67. package/lib/require-adjacent-popover/index.js +4 -2
  68. package/lib/require-ancestor/index.d.ts +20 -0
  69. package/lib/require-ancestor/index.js +33 -7
  70. package/lib/require-datetime/index.js +1 -1
  71. package/lib/require-dialog-autofocus/index.js +8 -6
  72. package/lib/require-h1/index.js +4 -8
  73. package/lib/require-landmark-label/index.js +17 -15
  74. package/lib/role-supports-aria-prop/index.js +2 -2
  75. package/lib/sizes-auto-requires-lazy-loading/index.d.ts +4 -2
  76. package/lib/sizes-auto-requires-lazy-loading/index.js +4 -2
  77. package/lib/usemap-references-map/index.js +11 -2
  78. package/lib/valid-speculation-rules/index.js +1 -1
  79. package/lib/wai-aria/checkings/interaction-in-hidden.js +6 -2
  80. package/lib/wai-aria/checkings/presentational-children.js +38 -17
  81. package/lib/wai-aria/checkings/required-accessibility-parent-role.js +2 -1
  82. package/lib/wai-aria/checkings/required-prop.js +2 -1
  83. package/lib/wai-aria/checkings/tab-requires-tabpanel.js +10 -6
  84. package/lib/wai-aria/checkings/value.d.ts +1 -4
  85. package/lib/wai-aria/checkings/value.js +5 -17
  86. package/package.json +11 -11
@@ -9,8 +9,10 @@
9
9
  * `auto` (`allowsAutoSizes`), not `loading="lazy"` alone.
10
10
  *
11
11
  * Gated on `srcset` being present, matching the source rule this was split
12
- * from: `sizes="auto"` has nothing to auto-size without candidates to choose
13
- * from.
12
+ * from. An `<img>` without `srcset` is left to the `@markuplint/html-spec`
13
+ * data of its `sizes`: the condition allows it only on a lazily loaded image
14
+ * (`no-disallowed-attr`) and the type only as the value `auto`
15
+ * (`no-invalid-attr-value`).
14
16
  */
15
17
  declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<boolean, undefined>>;
16
18
  export default _default;
@@ -13,8 +13,10 @@ import meta from './meta.js';
13
13
  * `auto` (`allowsAutoSizes`), not `loading="lazy"` alone.
14
14
  *
15
15
  * Gated on `srcset` being present, matching the source rule this was split
16
- * from: `sizes="auto"` has nothing to auto-size without candidates to choose
17
- * from.
16
+ * from. An `<img>` without `srcset` is left to the `@markuplint/html-spec`
17
+ * data of its `sizes`: the condition allows it only on a lazily loaded image
18
+ * (`no-disallowed-attr`) and the type only as the value `auto`
19
+ * (`no-invalid-attr-value`).
18
20
  */
19
21
  export default createRule({
20
22
  meta,
@@ -1,4 +1,5 @@
1
1
  import { createRule } from '@markuplint/ml-core';
2
+ import { getDOMValue, querySelectorAllInTree } from '../helpers.js';
2
3
  import meta from './meta.js';
3
4
  /**
4
5
  * Require the `usemap` attribute on an `<img>` element to be a valid
@@ -27,11 +28,19 @@ export default createRule({
27
28
  return;
28
29
  if (usemapAttr.isDynamicValue)
29
30
  return;
30
- const value = usemapAttr.value;
31
+ // The character references are resolved on both sides, as the DOM has the strings
32
+ const value = getDOMValue(usemapAttr) ?? usemapAttr.value;
31
33
  if (!value.startsWith('#') || value.length <= 1)
32
34
  return;
33
35
  const targetName = value.slice(1);
34
- const target = [...document.querySelectorAll('map')].find(candidate => candidate.getAttribute('name') === targetName);
36
+ const target = querySelectorAllInTree(el, 'map').find(candidate => {
37
+ const name = candidate.getAttributeNode('name');
38
+ if (!name) {
39
+ return false;
40
+ }
41
+ // A name that is not known until it is rendered may be the one that the reference means
42
+ return name.isDynamicValue || getDOMValue(name) === targetName;
43
+ });
35
44
  if (!target) {
36
45
  report({
37
46
  scope: usemapAttr,
@@ -216,7 +216,7 @@ function validateUrls(urls, label, reportAt, t) {
216
216
  * Validate a document-rule `where` predicate. A predicate object must contain
217
217
  * exactly one of `and` / `or` / `not` / `href_matches` / `selector_matches`.
218
218
  *
219
- * @see https://wicg.github.io/nav-speculation/speculation-rules.html#document-rule-predicates
219
+ * @see https://html.spec.whatwg.org/multipage/speculative-loading.html#parse-a-document-rule-predicate
220
220
  */
221
221
  function validatePredicate(predicate, label, reportAt, t) {
222
222
  if (!isPlainObject(predicate)) {
@@ -1,4 +1,4 @@
1
- import { mayBeFocusable } from '@markuplint/ml-spec';
1
+ import { getComposedParentElement, mayBeFocusable } from '@markuplint/ml-spec';
2
2
  /**
3
3
  * Checks whether a focusable interactive element is inside an `aria-hidden=true` subtree.
4
4
  *
@@ -38,6 +38,10 @@ export const checkingInteractionInHidden = ({ el }) => t => {
38
38
  /**
39
39
  * Traverses the element's ancestor chain to find the closest element with `aria-hidden="true"`.
40
40
  *
41
+ * The chain is the one the accessibility tree is built from ({@link getComposedParentElement}): it ends at the
42
+ * top of the content of an ordinary `template` element, which is not rendered and whose placement is not known,
43
+ * and it goes on from a declarative shadow root to its host.
44
+ *
41
45
  * @param el - The element to start searching from (inclusive).
42
46
  * @returns The `aria-hidden` attribute node if found, or `null` if no ancestor is hidden.
43
47
  */
@@ -50,7 +54,7 @@ el) {
50
54
  if (ariaHidden?.value === 'true') {
51
55
  return ariaHidden;
52
56
  }
53
- current = current.parentElement;
57
+ current = getComposedParentElement(current);
54
58
  }
55
59
  return null;
56
60
  }
@@ -1,5 +1,5 @@
1
- import { isAriaAttr, labelable, memoizeByObject, namespaceKeyOf } from '../../helpers.js';
2
- import { ARIA_RECOMMENDED_VERSION, EMBEDDED_CONTROL_ROLES, getComputedRole, getImplicitRole, isExposedAsPresentational, isNativeEmbeddedControl, } from '@markuplint/ml-spec';
1
+ import { getDOMValue, getElementByIdInTree, isAriaAttr, labelable, memoizeByObject, namespaceKeyOf, } from '../../helpers.js';
2
+ import { ARIA_RECOMMENDED_VERSION, EMBEDDED_CONTROL_ROLES, getComposedParentElement, getComputedRole, getImplicitRole, isExposedAsPresentational, isNativeEmbeddedControl, } from '@markuplint/ml-spec';
3
3
  /**
4
4
  * Checks whether an ARIA attribute or `role` is applied to a descendant of an
5
5
  * element whose role has the `childrenArePresentational` characteristic.
@@ -75,6 +75,9 @@ const getContext = memoizeByObject(
75
75
  * Finds the closest ancestor whose computed role has the
76
76
  * `childrenArePresentational` characteristic.
77
77
  *
78
+ * The ancestors are those of the accessibility tree ({@link getComposedParentElement}): the search ends at the
79
+ * top of the content of an ordinary `template` element, and it goes on from a declarative shadow root to its host.
80
+ *
78
81
  * @param el - The element to start searching from (exclusive, starts from parent).
79
82
  * @param ariaVersion - The ARIA version to compute the roles with.
80
83
  * @returns The role of that ancestor, or `null` if none is found.
@@ -82,13 +85,13 @@ const getContext = memoizeByObject(
82
85
  function getClosestPresentationalChildrenRole(
83
86
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
84
87
  el, ariaVersion) {
85
- let current = el.parentElement;
88
+ let current = getComposedParentElement(el);
86
89
  while (current) {
87
90
  const role = getComputedRole(el.ownerMLDocument.specs, current, ariaVersion).role;
88
91
  if (role?.childrenPresentational) {
89
92
  return role;
90
93
  }
91
- current = current.parentElement;
94
+ current = getComposedParentElement(current);
92
95
  }
93
96
  return null;
94
97
  }
@@ -104,6 +107,10 @@ el, ariaVersion) {
104
107
  * - the accessible name of every control that a `label` ancestor can name: the
105
108
  * element its `for` refers to, and its labelable descendants (HTML-AAM)
106
109
  *
110
+ * The ancestors are those of the accessibility tree ({@link getComposedParentElement}): an ancestor
111
+ * outside of the content of an ordinary `template` element reads nothing in it, as the content is not
112
+ * rendered there.
113
+ *
107
114
  * @see https://w3c.github.io/accname/#comp_name_from_content
108
115
  * @see https://w3c.github.io/accname/#comp_labelledby_traversal
109
116
  * @see https://w3c.github.io/html-aam/#accessible-name-and-description-computation
@@ -128,13 +135,17 @@ el, ariaVersion) {
128
135
  if (current !== el) {
129
136
  add(current);
130
137
  }
131
- if (current.id && referenced.has(current.id) && document.getElementById(current.id) === current) {
138
+ // An `aria-labelledby` refers to an element of its own tree
139
+ const idValue = getDOMValue(current.getAttributeNode('id'));
140
+ if (idValue &&
141
+ referenced.get(current.getRootNode())?.has(idValue) &&
142
+ getElementByIdInTree(current, idValue, true) === current) {
132
143
  add(current, true);
133
144
  }
134
145
  for (const control of getLabeledControls(current)) {
135
146
  add(control);
136
147
  }
137
- current = current.parentElement;
148
+ current = getComposedParentElement(current);
138
149
  }
139
150
  return visits;
140
151
  }
@@ -151,27 +162,36 @@ const getLabeledControls = memoizeByObject(
151
162
  if (el.localName !== 'label' || el.namespaceURI !== 'http://www.w3.org/1999/xhtml') {
152
163
  return [];
153
164
  }
154
- const htmlFor = el.getAttribute('for');
155
- const target = htmlFor == null ? null : el.ownerMLDocument.getElementById(htmlFor);
165
+ const forAttr = el.getAttributeNode('for');
166
+ const target = forAttr == null ? null : getElementByIdInTree(el, forAttr.value, forAttr.isExpressionValue);
156
167
  return [...(target ? [target] : []), ...el.querySelectorAll(labelable.join(', '))];
157
168
  });
158
169
  /**
159
- * The IDs that an `aria-labelledby` or `aria-describedby` of the document refers to.
170
+ * The IDs that an `aria-labelledby` or `aria-describedby` refers to, by the tree of the referrer: an
171
+ * id is resolved in the tree of the element that refers to it.
160
172
  */
161
173
  const getReferencedIds = memoizeByObject(
162
174
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
163
175
  (document) => {
164
- const ids = new Set();
165
- for (const referrer of document.querySelectorAll('[aria-labelledby], [aria-describedby]')) {
176
+ const idsOfTree = new Map();
177
+ for (const referrer of document.deepQuerySelectorAll('[aria-labelledby], [aria-describedby]')) {
178
+ const root = referrer.getRootNode();
179
+ let ids = idsOfTree.get(root);
180
+ if (!ids) {
181
+ ids = new Set();
182
+ idsOfTree.set(root, ids);
183
+ }
166
184
  for (const name of ['aria-labelledby', 'aria-describedby']) {
167
- for (const id of referrer.getAttribute(name)?.split(/\s+/) ?? []) {
168
- if (id !== '') {
169
- ids.add(id);
185
+ const attr = referrer.getAttributeNode(name);
186
+ for (const id of attr?.value.split(/\s+/) ?? []) {
187
+ const domId = id === '' ? null : getDOMValue(attr, id);
188
+ if (domId != null) {
189
+ ids.add(domId);
170
190
  }
171
191
  }
172
192
  }
173
193
  }
174
- return ids;
194
+ return idsOfTree;
175
195
  });
176
196
  /**
177
197
  * Whether the attribute changes what its element contributes to a text
@@ -275,11 +295,12 @@ el, computed) {
275
295
  function labelledbyGivesText(
276
296
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
277
297
  el, ariaVersion) {
278
- return (el.getAttribute('aria-labelledby')?.split(/\s+/) ?? []).some(id => {
298
+ const labelledbyAttr = el.getAttributeNode('aria-labelledby');
299
+ return (labelledbyAttr?.value.split(/\s+/) ?? []).some(id => {
279
300
  if (id === '') {
280
301
  return false;
281
302
  }
282
- const referenced = el.ownerMLDocument.getElementById(id);
303
+ const referenced = getElementByIdInTree(el, id, labelledbyAttr?.isExpressionValue);
283
304
  return referenced != null && referenced.getReferencedTextAlternative(ariaVersion) !== '';
284
305
  });
285
306
  }
@@ -23,7 +23,8 @@ export const checkingRequiredAccessibilityParentRole = ({ el, computed }) => t =
23
23
  (computed.errorType !== 'INVALID_REQUIRED_CONTEXT_ROLE' &&
24
24
  // NO_OWNER with role: null means all ancestors were transparent
25
25
  // and no valid context could be found. NO_OWNER with role kept
26
- // (parentElement === null) is a root fragment — skip.
26
+ // (no parent in the accessibility tree) is a root fragment, or the top
27
+ // of the content of an ordinary `template` element — skip.
27
28
  !(computed.errorType === 'NO_OWNER' && !computed.role))) {
28
29
  return;
29
30
  }
@@ -34,7 +34,8 @@ export const checkingRequiredProp = ({ el, role, propSpecs }) => t => {
34
34
  return true;
35
35
  });
36
36
  for (const { name: requiredProp } of requiredProps) {
37
- const has = el.attributes.some(attr => attr.nameKey === requiredProp);
37
+ // An attribute that the framework does not output (`aria-checked={null}`) is not there
38
+ const has = el.attributes.some(attr => !attr.isAbsent && attr.nameKey === requiredProp);
38
39
  if (!has) {
39
40
  const propSpec = propSpecs.find(p => p.name === requiredProp);
40
41
  const ariaVersion = el.rule.options?.version ??
@@ -1,4 +1,5 @@
1
1
  import { ARIA_RECOMMENDED_VERSION, getComputedRole } from '@markuplint/ml-spec';
2
+ import { getDOMValue, getElementByIdInTree, querySelectorAllInTree } from '../../helpers.js';
2
3
  /**
3
4
  * Checks that an active `tab` role element has a corresponding `tabpanel` role element.
4
5
  *
@@ -30,21 +31,24 @@ export const checkingTabRequiresTabpanel = ({ el, role }) => t => {
30
31
  .split(/\s+/)
31
32
  .filter(Boolean)
32
33
  .some(id => {
33
- const target = document.getElementById(id);
34
+ const target = getElementByIdInTree(el, id, controlsAttr.isExpressionValue);
34
35
  return !!target && isTabpanel(target);
35
36
  });
36
37
  if (hasControlledTabpanel) {
37
38
  return;
38
39
  }
39
40
  }
40
- const idAttr = el.getAttributeNode('id');
41
- if (idAttr && !idAttr.isDynamicValue) {
42
- const tabId = idAttr.value;
43
- const hasLabellingTabpanel = [...document.querySelectorAll('[aria-labelledby]')].some(candidate => {
41
+ // The tabpanel refers to the tab in the tree of the tab, and the ids are compared as the DOM has them
42
+ const tabId = getDOMValue(el.getAttributeNode('id'));
43
+ if (tabId != null) {
44
+ const hasLabellingTabpanel = querySelectorAllInTree(el, '[aria-labelledby]').some(candidate => {
44
45
  const labelledbyAttr = candidate.getAttributeNode('aria-labelledby');
45
46
  if (!labelledbyAttr || labelledbyAttr.isDynamicValue)
46
47
  return false;
47
- return labelledbyAttr.value.split(/\s+/).includes(tabId) && isTabpanel(candidate);
48
+ return (labelledbyAttr.value
49
+ .split(/\s+/)
50
+ .map(id => getDOMValue(labelledbyAttr, id))
51
+ .includes(tabId) && isTabpanel(candidate));
48
52
  });
49
53
  if (hasLabellingTabpanel) {
50
54
  return;
@@ -12,13 +12,11 @@ import type { ARIAProperty, ARIARole } from '@markuplint/ml-spec';
12
12
  * @param attr - The ARIA attribute node to inspect.
13
13
  * @param role - The computed ARIA role, used to resolve conditional value types.
14
14
  * @param propSpecs - The list of ARIA property specifications for value type lookup.
15
- * @param booleanish - Whether the document supports booleanish attribute values (e.g., JSX).
16
15
  * @returns A violation if the attribute value does not match the expected type.
17
16
  */
18
17
  export declare const checkingValue: AttrChecker<boolean, Options, {
19
18
  role?: ARIARole | null;
20
19
  propSpecs: readonly ARIAProperty[];
21
- booleanish?: boolean;
22
20
  }>;
23
21
  /**
24
22
  * Validates a raw ARIA value against the expected value type defined in the specification.
@@ -30,7 +28,6 @@ export declare const checkingValue: AttrChecker<boolean, Options, {
30
28
  * @param type - The ARIA value type (e.g., `"token"`, `"true/false"`, `"integer"`).
31
29
  * @param value - The raw attribute value to validate.
32
30
  * @param tokenEnum - The list of allowed token values for token-based types.
33
- * @param booleanish - Whether empty string is accepted as a boolean `true` value.
34
31
  * @returns `true` if the value is valid for the given type, `false` otherwise.
35
32
  */
36
- export declare function checkAriaValue(type: string, value: string, tokenEnum: readonly string[], booleanish?: boolean): boolean;
33
+ export declare function checkAriaValue(type: string, value: string, tokenEnum: readonly string[]): boolean;
@@ -9,15 +9,14 @@
9
9
  * @param attr - The ARIA attribute node to inspect.
10
10
  * @param role - The computed ARIA role, used to resolve conditional value types.
11
11
  * @param propSpecs - The list of ARIA property specifications for value type lookup.
12
- * @param booleanish - Whether the document supports booleanish attribute values (e.g., JSX).
13
12
  * @returns A violation if the attribute value does not match the expected type.
14
13
  */
15
- export const checkingValue = ({ attr, role, propSpecs, booleanish }) => t => {
14
+ export const checkingValue = ({ attr, role, propSpecs }) => t => {
16
15
  if (attr.isDynamicValue) {
17
16
  return;
18
17
  }
19
18
  const propSpec = propSpecs.find(p => p.name === attr.nameKey);
20
- const result = checkAria(propSpec, attr.value, role?.name, booleanish);
19
+ const result = checkAria(propSpec, attr.value, role?.name);
21
20
  if (result.isValid) {
22
21
  return;
23
22
  }
@@ -35,10 +34,9 @@ export const checkingValue = ({ attr, role, propSpecs, booleanish }) => t => {
35
34
  * @param propSpec - The ARIA property specification, or `undefined` if the property is unknown.
36
35
  * @param currentValue - The current attribute value to validate.
37
36
  * @param role - The name of the computed role, used to resolve conditional value types.
38
- * @param booleanish - Whether booleanish values (empty string as `true`) are accepted.
39
37
  * @returns An object containing the validation result and, for enum types, the allowed values.
40
38
  */
41
- function checkAria(propSpec, currentValue, role, booleanish) {
39
+ function checkAria(propSpec, currentValue, role) {
42
40
  if (!propSpec) {
43
41
  return {
44
42
  currentValue,
@@ -55,7 +53,7 @@ function checkAria(propSpec, currentValue, role, booleanish) {
55
53
  }
56
54
  }
57
55
  }
58
- const isValid = checkAriaValue(valueType, currentValue, propSpec.enum, booleanish);
56
+ const isValid = checkAriaValue(valueType, currentValue, propSpec.enum);
59
57
  return {
60
58
  ...propSpec,
61
59
  currentValue,
@@ -72,10 +70,9 @@ function checkAria(propSpec, currentValue, role, booleanish) {
72
70
  * @param type - The ARIA value type (e.g., `"token"`, `"true/false"`, `"integer"`).
73
71
  * @param value - The raw attribute value to validate.
74
72
  * @param tokenEnum - The list of allowed token values for token-based types.
75
- * @param booleanish - Whether empty string is accepted as a boolean `true` value.
76
73
  * @returns `true` if the value is valid for the given type, `false` otherwise.
77
74
  */
78
- export function checkAriaValue(type, value, tokenEnum, booleanish) {
75
+ export function checkAriaValue(type, value, tokenEnum) {
79
76
  switch (type) {
80
77
  case 'token': {
81
78
  return tokenEnum.includes(value);
@@ -90,21 +87,12 @@ export function checkAriaValue(type, value, tokenEnum, booleanish) {
90
87
  return true;
91
88
  }
92
89
  case 'true/false': {
93
- if (booleanish && value === '') {
94
- return true;
95
- }
96
90
  return ['true', 'false'].includes(value);
97
91
  }
98
92
  case 'tristate': {
99
- if (booleanish && value === '') {
100
- return true;
101
- }
102
93
  return ['mixed', 'true', 'false', 'undefined'].includes(value);
103
94
  }
104
95
  case 'true/false/undefined': {
105
- if (booleanish && value === '') {
106
- return true;
107
- }
108
96
  return ['true', 'false', 'undefined'].includes(value);
109
97
  }
110
98
  case 'integer': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/rules",
3
- "version": "5.4.0",
3
+ "version": "5.5.0",
4
4
  "description": "Built-in rules of markuplint",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,21 +31,21 @@
31
31
  "./lib/permitted-contents/debug.js": "./lib/permitted-contents/debug.browser.js"
32
32
  },
33
33
  "dependencies": {
34
- "@markuplint/html-spec": "5.4.0",
35
- "@markuplint/ml-config": "5.4.0",
36
- "@markuplint/ml-core": "5.4.0",
37
- "@markuplint/ml-spec": "5.4.0",
38
- "@markuplint/selector": "5.4.0",
39
- "@markuplint/shared": "5.4.0",
40
- "@markuplint/types": "5.4.0",
41
- "@mdn/browser-compat-data": "^8.1.6",
34
+ "@markuplint/html-spec": "5.5.0",
35
+ "@markuplint/ml-config": "5.5.0",
36
+ "@markuplint/ml-core": "5.5.0",
37
+ "@markuplint/ml-spec": "5.5.0",
38
+ "@markuplint/selector": "5.5.0",
39
+ "@markuplint/shared": "5.5.0",
40
+ "@markuplint/types": "5.5.0",
41
+ "@mdn/browser-compat-data": "^8.1.8",
42
42
  "@types/debug": "^4.1.13",
43
43
  "ansi-colors": "^4.1.3",
44
- "browserslist": "^4.29.3",
44
+ "browserslist": "^4.29.5",
45
45
  "chrono-node": "^2.10.3",
46
46
  "debug": "^4.4.3",
47
47
  "image-size": "^2.0.4",
48
48
  "type-fest": "^5.10.0"
49
49
  },
50
- "gitHead": "647f6fb5d5743f060678599c97e9c84b58970747"
50
+ "gitHead": "fedebcfd56eaec4642e68643b2f9e10750d810e1"
51
51
  }