@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.
Files changed (266) hide show
  1. package/CHANGELOG.md +63 -2
  2. package/SKILL.md +76 -0
  3. package/lib/attr-check.d.ts +46 -7
  4. package/lib/attr-check.js +42 -8
  5. package/lib/attr-duplication/index.d.ts +7 -0
  6. package/lib/attr-duplication/index.js +7 -0
  7. package/lib/attr-duplication/meta.d.ts +1 -0
  8. package/lib/attr-duplication/meta.js +1 -0
  9. package/lib/attr-value-quotes/index.d.ts +10 -0
  10. package/lib/attr-value-quotes/index.js +10 -0
  11. package/lib/attr-value-quotes/meta.d.ts +1 -0
  12. package/lib/attr-value-quotes/meta.js +1 -0
  13. package/lib/case-sensitive-attr-name/index.d.ts +9 -0
  14. package/lib/case-sensitive-attr-name/index.js +8 -0
  15. package/lib/case-sensitive-attr-name/meta.d.ts +1 -0
  16. package/lib/case-sensitive-attr-name/meta.js +1 -0
  17. package/lib/case-sensitive-tag-name/index.d.ts +9 -0
  18. package/lib/case-sensitive-tag-name/index.js +8 -0
  19. package/lib/case-sensitive-tag-name/meta.d.ts +1 -0
  20. package/lib/case-sensitive-tag-name/meta.js +1 -0
  21. package/lib/character-reference/index.d.ts +9 -0
  22. package/lib/character-reference/index.js +20 -0
  23. package/lib/character-reference/meta.d.ts +1 -0
  24. package/lib/character-reference/meta.js +1 -0
  25. package/lib/class-naming/index.d.ts +9 -0
  26. package/lib/class-naming/index.js +8 -0
  27. package/lib/class-naming/meta.d.ts +1 -0
  28. package/lib/class-naming/meta.js +1 -0
  29. package/lib/correct-aspect-ratio/index.d.ts +5 -0
  30. package/lib/correct-aspect-ratio/index.js +80 -0
  31. package/lib/correct-aspect-ratio/meta.d.ts +4 -0
  32. package/lib/correct-aspect-ratio/meta.js +3 -0
  33. package/lib/correct-aspect-ratio/resolve-image-size.d.ts +24 -0
  34. package/lib/correct-aspect-ratio/resolve-image-size.js +165 -0
  35. package/lib/create-message.d.ts +29 -2
  36. package/lib/create-message.js +60 -2
  37. package/lib/debug.d.ts +9 -0
  38. package/lib/debug.js +5 -0
  39. package/lib/deprecated-attr/index.d.ts +6 -0
  40. package/lib/deprecated-attr/index.js +6 -0
  41. package/lib/deprecated-attr/meta.d.ts +1 -0
  42. package/lib/deprecated-attr/meta.js +1 -0
  43. package/lib/deprecated-element/index.d.ts +6 -0
  44. package/lib/deprecated-element/index.js +8 -2
  45. package/lib/deprecated-element/meta.d.ts +1 -0
  46. package/lib/deprecated-element/meta.js +1 -0
  47. package/lib/disallowed-element/index.d.ts +7 -0
  48. package/lib/disallowed-element/index.js +7 -0
  49. package/lib/disallowed-element/meta.d.ts +1 -0
  50. package/lib/disallowed-element/meta.js +1 -0
  51. package/lib/doctype/index.d.ts +10 -0
  52. package/lib/doctype/index.js +8 -0
  53. package/lib/doctype/meta.d.ts +1 -0
  54. package/lib/doctype/meta.js +1 -0
  55. package/lib/end-tag/index.d.ts +7 -0
  56. package/lib/end-tag/index.js +8 -1
  57. package/lib/end-tag/meta.d.ts +1 -0
  58. package/lib/end-tag/meta.js +1 -0
  59. package/lib/heading-levels/index.d.ts +6 -0
  60. package/lib/heading-levels/index.js +6 -1
  61. package/lib/heading-levels/meta.d.ts +1 -0
  62. package/lib/heading-levels/meta.js +1 -0
  63. package/lib/helpers.d.ts +105 -0
  64. package/lib/helpers.js +112 -16
  65. package/lib/id-duplication/index.d.ts +7 -0
  66. package/lib/id-duplication/index.js +7 -0
  67. package/lib/id-duplication/meta.d.ts +1 -0
  68. package/lib/id-duplication/meta.js +1 -0
  69. package/lib/index.d.ts +43 -41
  70. package/lib/index.js +24 -0
  71. package/lib/ineffective-attr/index.d.ts +7 -0
  72. package/lib/ineffective-attr/index.js +7 -0
  73. package/lib/ineffective-attr/meta.d.ts +1 -0
  74. package/lib/ineffective-attr/meta.js +1 -0
  75. package/lib/invalid-attr/index.d.ts +29 -21
  76. package/lib/invalid-attr/index.js +60 -109
  77. package/lib/invalid-attr/meta.d.ts +1 -0
  78. package/lib/invalid-attr/meta.js +1 -0
  79. package/lib/label-has-control/index.d.ts +7 -0
  80. package/lib/label-has-control/index.js +11 -0
  81. package/lib/label-has-control/meta.d.ts +1 -0
  82. package/lib/label-has-control/meta.js +1 -0
  83. package/lib/landmark-roles/index.d.ts +14 -0
  84. package/lib/landmark-roles/index.js +21 -0
  85. package/lib/landmark-roles/meta.d.ts +1 -0
  86. package/lib/landmark-roles/meta.js +1 -0
  87. package/lib/link-types/index.d.ts +10 -0
  88. package/lib/link-types/index.js +180 -0
  89. package/lib/link-types/meta.d.ts +5 -0
  90. package/lib/link-types/meta.js +4 -0
  91. package/lib/neighbor-popovers/index.d.ts +8 -0
  92. package/lib/neighbor-popovers/index.js +28 -6
  93. package/lib/neighbor-popovers/meta.d.ts +1 -0
  94. package/lib/neighbor-popovers/meta.js +1 -0
  95. package/lib/no-ambiguous-navigable-target-names/index.d.ts +8 -0
  96. package/lib/no-ambiguous-navigable-target-names/index.js +8 -0
  97. package/lib/no-ambiguous-navigable-target-names/meta.d.ts +1 -0
  98. package/lib/no-ambiguous-navigable-target-names/meta.js +1 -0
  99. package/lib/no-boolean-attr-value/index.d.ts +7 -0
  100. package/lib/no-boolean-attr-value/index.js +7 -0
  101. package/lib/no-boolean-attr-value/meta.d.ts +1 -0
  102. package/lib/no-boolean-attr-value/meta.js +1 -0
  103. package/lib/no-consecutive-br/index.d.ts +8 -0
  104. package/lib/no-consecutive-br/index.js +8 -0
  105. package/lib/no-consecutive-br/meta.d.ts +1 -0
  106. package/lib/no-consecutive-br/meta.js +1 -0
  107. package/lib/no-default-value/index.d.ts +7 -0
  108. package/lib/no-default-value/index.js +7 -0
  109. package/lib/no-default-value/meta.d.ts +1 -0
  110. package/lib/no-default-value/meta.js +1 -0
  111. package/lib/no-duplicate-dt/index.d.ts +6 -0
  112. package/lib/no-duplicate-dt/index.js +6 -0
  113. package/lib/no-duplicate-dt/meta.d.ts +1 -0
  114. package/lib/no-duplicate-dt/meta.js +1 -0
  115. package/lib/no-empty-palpable-content/index.d.ts +13 -0
  116. package/lib/no-empty-palpable-content/index.js +12 -0
  117. package/lib/no-empty-palpable-content/meta.d.ts +1 -0
  118. package/lib/no-empty-palpable-content/meta.js +1 -0
  119. package/lib/no-hard-code-id/index.d.ts +8 -0
  120. package/lib/no-hard-code-id/index.js +8 -0
  121. package/lib/no-hard-code-id/meta.d.ts +1 -0
  122. package/lib/no-hard-code-id/meta.js +1 -0
  123. package/lib/no-orphaned-end-tag/index.d.ts +7 -0
  124. package/lib/no-orphaned-end-tag/index.js +8 -2
  125. package/lib/no-orphaned-end-tag/meta.d.ts +1 -0
  126. package/lib/no-orphaned-end-tag/meta.js +1 -0
  127. package/lib/no-refer-to-non-existent-id/index.d.ts +9 -1
  128. package/lib/no-refer-to-non-existent-id/index.js +11 -2
  129. package/lib/no-refer-to-non-existent-id/meta.d.ts +1 -0
  130. package/lib/no-refer-to-non-existent-id/meta.js +1 -0
  131. package/lib/no-unsupported-features/compat-data.d.ts +71 -0
  132. package/lib/no-unsupported-features/compat-data.js +204 -0
  133. package/lib/no-unsupported-features/index.d.ts +24 -0
  134. package/lib/no-unsupported-features/index.js +139 -0
  135. package/lib/no-unsupported-features/meta.d.ts +5 -0
  136. package/lib/no-unsupported-features/meta.js +4 -0
  137. package/lib/no-unsupported-features/resolve-browsers.d.ts +30 -0
  138. package/lib/no-unsupported-features/resolve-browsers.js +125 -0
  139. package/lib/no-use-event-handler-attr/index.d.ts +10 -0
  140. package/lib/no-use-event-handler-attr/index.js +8 -0
  141. package/lib/no-use-event-handler-attr/meta.d.ts +1 -0
  142. package/lib/no-use-event-handler-attr/meta.js +1 -0
  143. package/lib/permitted-contents/choice.d.ts +17 -0
  144. package/lib/permitted-contents/choice.js +31 -1
  145. package/lib/permitted-contents/complex-branch.d.ts +11 -7
  146. package/lib/permitted-contents/complex-branch.js +11 -7
  147. package/lib/permitted-contents/content-model.d.ts +11 -0
  148. package/lib/permitted-contents/content-model.js +32 -0
  149. package/lib/permitted-contents/count-pattern.d.ts +13 -7
  150. package/lib/permitted-contents/count-pattern.js +24 -8
  151. package/lib/permitted-contents/debug.browser.d.ts +12 -0
  152. package/lib/permitted-contents/debug.browser.js +12 -0
  153. package/lib/permitted-contents/debug.d.ts +12 -0
  154. package/lib/permitted-contents/debug.js +12 -0
  155. package/lib/permitted-contents/index.d.ts +12 -0
  156. package/lib/permitted-contents/index.js +21 -0
  157. package/lib/permitted-contents/matches-selector.d.ts +19 -0
  158. package/lib/permitted-contents/matches-selector.js +33 -0
  159. package/lib/permitted-contents/meta.d.ts +1 -0
  160. package/lib/permitted-contents/meta.js +1 -0
  161. package/lib/permitted-contents/order.d.ts +14 -7
  162. package/lib/permitted-contents/order.js +15 -8
  163. package/lib/permitted-contents/recursive-branch.d.ts +17 -0
  164. package/lib/permitted-contents/recursive-branch.js +17 -0
  165. package/lib/permitted-contents/represent-transparent-nodes.d.ts +54 -0
  166. package/lib/permitted-contents/represent-transparent-nodes.js +119 -12
  167. package/lib/permitted-contents/start.d.ts +12 -6
  168. package/lib/permitted-contents/start.js +12 -6
  169. package/lib/permitted-contents/transparent.d.ts +12 -0
  170. package/lib/permitted-contents/transparent.js +12 -0
  171. package/lib/permitted-contents/types.d.ts +67 -0
  172. package/lib/permitted-contents/utils.d.ts +160 -0
  173. package/lib/permitted-contents/utils.js +216 -35
  174. package/lib/placeholder-label-option/index.d.ts +7 -0
  175. package/lib/placeholder-label-option/index.js +19 -15
  176. package/lib/placeholder-label-option/meta.d.ts +1 -0
  177. package/lib/placeholder-label-option/meta.js +1 -0
  178. package/lib/redundant-accessible-name/index.d.ts +10 -0
  179. package/lib/redundant-accessible-name/index.js +123 -0
  180. package/lib/redundant-accessible-name/meta.d.ts +5 -0
  181. package/lib/redundant-accessible-name/meta.js +4 -0
  182. package/lib/require-accessible-name/index.d.ts +10 -4
  183. package/lib/require-accessible-name/index.js +13 -5
  184. package/lib/require-accessible-name/meta.d.ts +1 -0
  185. package/lib/require-accessible-name/meta.js +1 -0
  186. package/lib/require-datetime/index.d.ts +12 -0
  187. package/lib/require-datetime/index.js +8 -0
  188. package/lib/require-datetime/meta.d.ts +1 -0
  189. package/lib/require-datetime/meta.js +1 -0
  190. package/lib/require-datetime/types.d.ts +7 -0
  191. package/lib/require-datetime/utils.d.ts +18 -5
  192. package/lib/require-datetime/utils.js +53 -9
  193. package/lib/require-dialog-autofocus/index.d.ts +22 -0
  194. package/lib/require-dialog-autofocus/index.js +62 -0
  195. package/lib/require-dialog-autofocus/meta.d.ts +5 -0
  196. package/lib/require-dialog-autofocus/meta.js +4 -0
  197. package/lib/required-attr/index.d.ts +21 -1
  198. package/lib/required-attr/index.js +34 -4
  199. package/lib/required-attr/meta.d.ts +1 -0
  200. package/lib/required-attr/meta.js +1 -0
  201. package/lib/required-element/index.d.ts +14 -0
  202. package/lib/required-element/index.js +14 -2
  203. package/lib/required-element/meta.d.ts +1 -0
  204. package/lib/required-element/meta.js +1 -0
  205. package/lib/required-h1/index.d.ts +12 -0
  206. package/lib/required-h1/index.js +7 -0
  207. package/lib/required-h1/meta.d.ts +1 -0
  208. package/lib/required-h1/meta.js +1 -0
  209. package/lib/srcset-sizes-constraint/index.d.ts +6 -0
  210. package/lib/srcset-sizes-constraint/index.js +108 -0
  211. package/lib/srcset-sizes-constraint/meta.d.ts +5 -0
  212. package/lib/srcset-sizes-constraint/meta.js +4 -0
  213. package/lib/srcset-sizes-constraint/parse-srcset.d.ts +40 -0
  214. package/lib/srcset-sizes-constraint/parse-srcset.js +56 -0
  215. package/lib/table-row-column-alignment/find-children.d.ts +10 -0
  216. package/lib/table-row-column-alignment/find-children.js +11 -0
  217. package/lib/table-row-column-alignment/grid.d.ts +60 -0
  218. package/lib/table-row-column-alignment/grid.js +104 -26
  219. package/lib/table-row-column-alignment/index.d.ts +8 -0
  220. package/lib/table-row-column-alignment/index.js +8 -0
  221. package/lib/table-row-column-alignment/meta.d.ts +1 -0
  222. package/lib/table-row-column-alignment/meta.js +1 -0
  223. package/lib/table-row-column-alignment/types.d.ts +10 -0
  224. package/lib/use-list/index.d.ts +16 -0
  225. package/lib/use-list/index.js +20 -0
  226. package/lib/use-list/meta.d.ts +1 -0
  227. package/lib/use-list/meta.js +1 -0
  228. package/lib/wai-aria/checkings/abstract-role.d.ts +9 -0
  229. package/lib/wai-aria/checkings/abstract-role.js +14 -2
  230. package/lib/wai-aria/checkings/default-value.d.ts +11 -0
  231. package/lib/wai-aria/checkings/default-value.js +11 -0
  232. package/lib/wai-aria/checkings/deprecated-props.d.ts +11 -0
  233. package/lib/wai-aria/checkings/deprecated-props.js +11 -0
  234. package/lib/wai-aria/checkings/deprecated-role.d.ts +10 -0
  235. package/lib/wai-aria/checkings/deprecated-role.js +10 -0
  236. package/lib/wai-aria/checkings/disallowed-prop.d.ts +14 -0
  237. package/lib/wai-aria/checkings/disallowed-prop.js +19 -2
  238. package/lib/wai-aria/checkings/implicit-props.d.ts +13 -0
  239. package/lib/wai-aria/checkings/implicit-props.js +13 -0
  240. package/lib/wai-aria/checkings/implicit-role.d.ts +9 -0
  241. package/lib/wai-aria/checkings/implicit-role.js +14 -2
  242. package/lib/wai-aria/checkings/interaction-in-hidden.d.ts +9 -0
  243. package/lib/wai-aria/checkings/interaction-in-hidden.js +15 -4
  244. package/lib/wai-aria/checkings/no-global-prop.d.ts +10 -0
  245. package/lib/wai-aria/checkings/no-global-prop.js +10 -0
  246. package/lib/wai-aria/checkings/non-existent-role.d.ts +11 -0
  247. package/lib/wai-aria/checkings/non-existent-role.js +19 -2
  248. package/lib/wai-aria/checkings/permitted-roles.d.ts +10 -0
  249. package/lib/wai-aria/checkings/permitted-roles.js +15 -2
  250. package/lib/wai-aria/checkings/presentational-children.d.ts +7 -1
  251. package/lib/wai-aria/checkings/presentational-children.js +17 -3
  252. package/lib/wai-aria/checkings/required-accessibility-parent-role.d.ts +25 -0
  253. package/lib/wai-aria/checkings/required-accessibility-parent-role.js +56 -0
  254. package/lib/wai-aria/checkings/required-owned-elements.d.ts +10 -1
  255. package/lib/wai-aria/checkings/required-owned-elements.js +69 -25
  256. package/lib/wai-aria/checkings/required-prop.d.ts +13 -0
  257. package/lib/wai-aria/checkings/required-prop.js +18 -2
  258. package/lib/wai-aria/checkings/value.d.ts +23 -0
  259. package/lib/wai-aria/checkings/value.js +32 -0
  260. package/lib/wai-aria/index.d.ts +10 -0
  261. package/lib/wai-aria/index.js +22 -4
  262. package/lib/wai-aria/meta.d.ts +1 -0
  263. package/lib/wai-aria/meta.js +1 -0
  264. package/lib/wai-aria/types.d.ts +25 -1
  265. package/package.json +16 -11
  266. package/schema.json +18 -0
@@ -1,37 +1,45 @@
1
1
  import type { AttributeType } from '@markuplint/ml-spec';
2
+ import type { Pattern } from '@markuplint/types';
3
+ /**
4
+ * Configuration options for the `invalid-attr` rule.
5
+ */
2
6
  type Option = {
3
7
  /**
8
+ * Attributes to allow beyond what the spec permits.
9
+ * Can override spec-level restrictions such as `noUse`.
10
+ *
4
11
  * @since 3.7.0
5
12
  */
6
- allowAttrs?: (string | Attr)[] | Record<string, ValueRule>;
13
+ allowAttrs?: (string | Attr)[] | Record<string, AttributeType | Pattern>;
7
14
  /**
15
+ * Attributes to disallow in addition to spec restrictions.
16
+ *
8
17
  * @since 3.7.0
9
18
  */
10
- disallowAttrs?: (string | Attr)[] | Record<string, ValueRule>;
19
+ disallowAttrs?: (string | Attr)[] | Record<string, AttributeType | Pattern>;
20
+ /** Attribute name prefix(es) to ignore during validation. */
11
21
  ignoreAttrNamePrefix?: string | string[];
22
+ /** Whether to allow additional properties for pretender elements (defaults to `true`). */
12
23
  allowToAddPropertiesForPretender?: boolean;
13
- /**
14
- * @deprecated Since version 3.7.0. Use `allowAttrs` or `disallowAttrs` instead.
15
- * This option (`attr`) is now considered ambiguous and may lead to confusion.
16
- * Please use the more explicit `allowAttrs` or `disallowAttrs` option
17
- * to specify allowed attributes for the `invalid-attr` rule.
18
- * @see {@link Option.allowAttrs}
19
- * @see {@link Option.disallowAttrs}
20
- */
21
- attrs?: Record<string, ValueRule | {
22
- disallowed: true;
23
- }>;
24
24
  };
25
+ /**
26
+ * Describes a single attribute with its name and expected value constraint.
27
+ */
25
28
  type Attr = {
29
+ /** The attribute name. */
26
30
  name: string;
27
- value: AttributeType | ValueRule;
28
- };
29
- type ValueRule = {
30
- enum: [string, ...string[]];
31
- } | {
32
- pattern: string;
33
- } | {
34
- type: AttributeType;
31
+ /** The expected attribute value type or validation rule. */
32
+ value: AttributeType | Pattern;
35
33
  };
34
+ /**
35
+ * Rule that validates attributes against the HTML spec, allowed lists,
36
+ * and disallowed lists.
37
+ *
38
+ * Checks each attribute for: existence in the spec, correct value type,
39
+ * allowed/disallowed overrides from configuration, and typo suggestions
40
+ * via candidate matching. Supports `allowAttrs` and `disallowAttrs`.
41
+ * Non-existent attributes on elements that allow additional properties
42
+ * (pretenders) can be optionally permitted.
43
+ */
36
44
  declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<boolean, Option>>;
37
45
  export default _default;
@@ -1,9 +1,20 @@
1
1
  import { createRule, getAttrSpecs, getSpec } from '@markuplint/ml-core';
2
+ import { check, isEnum, isPattern } from '@markuplint/types';
2
3
  import { attrCheck } from '../attr-check.js';
3
4
  import { log as ruleLog } from '../debug.js';
4
- import { isValidAttr, match } from '../helpers.js';
5
+ import { isValidAttr } from '../helpers.js';
5
6
  import meta from './meta.js';
6
7
  const log = ruleLog.extend('invalid-attr');
8
+ /**
9
+ * Rule that validates attributes against the HTML spec, allowed lists,
10
+ * and disallowed lists.
11
+ *
12
+ * Checks each attribute for: existence in the spec, correct value type,
13
+ * allowed/disallowed overrides from configuration, and typo suggestions
14
+ * via candidate matching. Supports `allowAttrs` and `disallowAttrs`.
15
+ * Non-existent attributes on elements that allow additional properties
16
+ * (pretenders) can be optionally permitted.
17
+ */
7
18
  export default createRule({
8
19
  meta: meta,
9
20
  defaultOptions: {},
@@ -47,19 +58,15 @@ export default createRule({
47
58
  if (Array.isArray(attr.rule.options.allowAttrs)) {
48
59
  for (const allowAttr of attr.rule.options.allowAttrs) {
49
60
  if (typeof allowAttr === 'string') {
50
- allowAttrs[allowAttr] = { type: 'Any' };
61
+ allowAttrs[allowAttr] = 'Any';
51
62
  continue;
52
63
  }
53
- if (isValueRule(allowAttr.value)) {
54
- allowAttrs[allowAttr.name] = allowAttr.value;
55
- continue;
56
- }
57
- allowAttrs[allowAttr.name] = { type: allowAttr.value };
64
+ allowAttrs[allowAttr.name] = allowAttr.value;
58
65
  }
59
66
  }
60
67
  else {
61
- for (const [attrName, valueRule] of Object.entries(attr.rule.options.allowAttrs)) {
62
- allowAttrs[attrName] = valueRule;
68
+ for (const [attrName, attrType] of Object.entries(attr.rule.options.allowAttrs)) {
69
+ allowAttrs[attrName] = attrType;
63
70
  }
64
71
  }
65
72
  }
@@ -67,99 +74,50 @@ export default createRule({
67
74
  if (Array.isArray(attr.rule.options.disallowAttrs)) {
68
75
  for (const disallowAttr of attr.rule.options.disallowAttrs) {
69
76
  if (typeof disallowAttr === 'string') {
70
- disallowAttrs[disallowAttr] = { type: 'Any' };
77
+ disallowAttrs[disallowAttr] = 'Any';
71
78
  continue;
72
79
  }
73
- if (isValueRule(disallowAttr.value)) {
74
- disallowAttrs[disallowAttr.name] = disallowAttr.value;
75
- continue;
76
- }
77
- disallowAttrs[disallowAttr.name] = { type: disallowAttr.value };
80
+ disallowAttrs[disallowAttr.name] = disallowAttr.value;
78
81
  }
79
82
  }
80
83
  else {
81
- for (const [attrName, valueRule] of Object.entries(attr.rule.options.disallowAttrs)) {
82
- disallowAttrs[attrName] = valueRule;
83
- }
84
- }
85
- }
86
- if (attr.rule.options.attrs) {
87
- for (const [attrName, valueRule] of Object.entries(attr.rule.options.attrs)) {
88
- if ('disallowed' in valueRule) {
89
- disallowAttrs[attrName] = { type: 'Any' };
90
- }
91
- else {
92
- allowAttrs[attrName] = valueRule;
84
+ for (const [attrName, attrType] of Object.entries(attr.rule.options.disallowAttrs)) {
85
+ disallowAttrs[attrName] = attrType;
93
86
  }
94
87
  }
95
88
  }
96
89
  const allowValue = allowAttrs[name] ?? null;
97
90
  const disallowValue = disallowAttrs[name] ?? null;
98
- if (allowValue) {
99
- if ('enum' in allowValue) {
100
- invalid = attrCheck(t, name.toLowerCase(), value, true, {
101
- name,
102
- type: {
103
- enum: allowValue.enum,
104
- },
105
- description: '',
106
- });
107
- }
108
- else if ('pattern' in allowValue) {
109
- if (!match(value, allowValue.pattern)) {
110
- invalid = {
111
- invalidType: 'invalid-value',
112
- message: t('{0} is unmatched with the below patterns: {1}', t('the "{0*}" {1}', name, 'attribute'), allowValue.pattern),
113
- };
114
- }
115
- }
116
- else if ('type' in allowValue) {
117
- invalid = attrCheck(t, name, value, true, { name, type: allowValue.type, description: '' });
118
- }
91
+ if (allowValue !== null) {
92
+ invalid = attrCheck(t, name, value, true, {
93
+ name,
94
+ type: allowValue,
95
+ description: '',
96
+ });
119
97
  }
120
- else if (disallowValue) {
121
- if ('enum' in disallowValue) {
122
- const checkResult = attrCheck(t, name.toLowerCase(), value, true, {
123
- name,
124
- type: {
125
- enum: disallowValue.enum,
126
- },
127
- description: '',
128
- });
129
- if (checkResult === false) {
130
- invalid = {
131
- invalidType: 'invalid-value',
132
- message: t('{0} is disallowed to accept the following values: {1}', t('the "{0*}" {1}', name, 'attribute'), t(disallowValue.enum)),
133
- };
134
- }
98
+ else if (disallowValue !== null) {
99
+ if (disallowValue === 'Any') {
100
+ invalid = {
101
+ invalidType: 'non-existent',
102
+ message: t('{0} is disallowed', t('the "{0*}" {1}', name, 'attribute')),
103
+ };
135
104
  }
136
- else if ('pattern' in disallowValue) {
137
- if (match(value, disallowValue.pattern)) {
138
- invalid = {
139
- invalidType: 'invalid-value',
140
- message: t('{0} is matched with the below disallowed patterns: {1}', t('the "{0*}" {1}', name, 'attribute'), disallowValue.pattern),
141
- };
142
- }
143
- }
144
- else if ('type' in disallowValue) {
145
- if (disallowValue.type === 'Any') {
146
- invalid = {
147
- invalidType: 'non-existent',
148
- message: t('{0} is disallowed', t('the "{0*}" {1}', name, 'attribute')),
149
- };
150
- }
151
- else {
152
- const checkResult = attrCheck(t, name, value, true, {
105
+ else {
106
+ const checkResult = isPattern(disallowValue)
107
+ ? check(value, disallowValue)
108
+ : attrCheck(t, name, value, true, {
153
109
  name,
154
- type: disallowValue.type,
110
+ type: disallowValue,
155
111
  description: '',
156
112
  });
157
- if (checkResult === false) {
158
- invalid = {
159
- invalidType: 'invalid-value',
160
- message: t('{0} is disallowed', t('{0} of {1}', t('the {0}', 'type'), t('the "{0*}" {1}', name, 'attribute'))),
161
- };
162
- }
113
+ const isMatched = isPattern(disallowValue)
114
+ ? checkResult.matched
115
+ : checkResult === false;
116
+ if (isMatched) {
117
+ invalid = {
118
+ invalidType: 'invalid-value',
119
+ message: createDisallowMessage(t, name, disallowValue),
120
+ };
163
121
  }
164
122
  }
165
123
  }
@@ -216,29 +174,22 @@ export default createRule({
216
174
  });
217
175
  },
218
176
  });
219
- function isValueRule(
177
+ /**
178
+ * Creates an appropriate disallow message based on the type structure.
179
+ *
180
+ * @param t - The i18n translator
181
+ * @param name - The attribute name
182
+ * @param type - The attribute type or pattern
183
+ * @returns A localized message string
184
+ */
185
+ function createDisallowMessage(t, name,
220
186
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
221
- value) {
222
- if (typeof value === 'string') {
223
- return false;
224
- }
225
- if ('enum' in value) {
226
- if (Object.keys(value).length > 1) {
227
- return false;
228
- }
229
- return true;
230
- }
231
- if ('token' in value) {
232
- return false;
233
- }
234
- if ('pattern' in value) {
235
- return true;
236
- }
237
- if (value.type === 'integer' || value.type === 'float') {
238
- return false;
187
+ type) {
188
+ if (isPattern(type)) {
189
+ return t('{0} is matched with the below disallowed patterns: {1}', t('the "{0*}" {1}', name, 'attribute'), type.pattern);
239
190
  }
240
- if (Object.keys(value).length > 1) {
241
- return false;
191
+ if (typeof type !== 'string' && isEnum(type)) {
192
+ return t('{0} is disallowed to accept the following values: {1}', t('the "{0*}" {1}', name, 'attribute'), t(type.enum));
242
193
  }
243
- return true;
194
+ return t('{0} is disallowed', t('{0} of {1}', t('the {0}', 'type'), t('the "{0*}" {1}', name, 'attribute')));
244
195
  }
@@ -1,3 +1,4 @@
1
+ /** Rule metadata for `invalid-attr`: categorized as a validation rule. */
1
2
  declare const _default: {
2
3
  readonly category: "validation";
3
4
  };
@@ -1,3 +1,4 @@
1
+ /** Rule metadata for `invalid-attr`: categorized as a validation rule. */
1
2
  export default {
2
3
  category: 'validation',
3
4
  };
@@ -1,2 +1,9 @@
1
+ /**
2
+ * Rule that validates `<label>` elements are properly associated with form controls.
3
+ *
4
+ * Checks that each label either wraps a control or has a `for` attribute,
5
+ * and warns when a label contains more than one form control (only the first
6
+ * is associated per the HTML spec).
7
+ */
1
8
  declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
2
9
  export default _default;
@@ -1,6 +1,17 @@
1
1
  import { createRule } from '@markuplint/ml-core';
2
2
  import meta from './meta.js';
3
+ /**
4
+ * CSS selector that matches form control elements eligible for label association.
5
+ * Excludes hidden inputs since they do not need labels.
6
+ */
3
7
  const controlSelector = ["input:not([type='hidden' i])", 'select', 'textarea'].join(',');
8
+ /**
9
+ * Rule that validates `<label>` elements are properly associated with form controls.
10
+ *
11
+ * Checks that each label either wraps a control or has a `for` attribute,
12
+ * and warns when a label contains more than one form control (only the first
13
+ * is associated per the HTML spec).
14
+ */
4
15
  export default createRule({
5
16
  meta: meta,
6
17
  defaultSeverity: 'warning',
@@ -1,3 +1,4 @@
1
+ /** Rule metadata for the `label-has-control` rule, categorized as accessibility. */
1
2
  declare const _default: {
2
3
  readonly category: "a11y";
3
4
  };
@@ -1,3 +1,4 @@
1
+ /** Rule metadata for the `label-has-control` rule, categorized as accessibility. */
1
2
  export default {
2
3
  category: 'a11y',
3
4
  };
@@ -1,8 +1,22 @@
1
+ /**
2
+ * Configuration options for the landmark-roles rule.
3
+ */
1
4
  type Options = {
5
+ /** Landmark roles to exclude from validation. */
2
6
  ignoreRoles: Roles[];
7
+ /** Whether to require a unique accessible name when duplicate landmark roles exist. */
3
8
  labelEachArea: boolean;
4
9
  };
10
+ /** Landmark roles that should appear at the top level of the document. */
5
11
  type TopLevelRoles = 'banner' | 'main' | 'complementary' | 'contentinfo';
12
+ /** All recognized ARIA landmark roles for this rule. */
6
13
  type Roles = TopLevelRoles | 'form' | 'navigation' | 'region';
14
+ /**
15
+ * Rule that validates proper usage of ARIA landmark roles.
16
+ *
17
+ * Checks that `banner`, `main`, `complementary`, and `contentinfo` landmarks are
18
+ * top-level (not nested inside other landmarks), and that when duplicate landmarks
19
+ * of the same role exist, each has a unique accessible name.
20
+ */
7
21
  declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<boolean, Options>>;
8
22
  export default _default;
@@ -1,5 +1,6 @@
1
1
  import { createRule } from '@markuplint/ml-core';
2
2
  import meta from './meta.js';
3
+ /** CSS selectors used to identify elements with each landmark role. */
3
4
  const selectors = {
4
5
  complementary: ['[role="complementary"]', 'aside'],
5
6
  contentinfo: ['[role="contentinfo"]'],
@@ -9,7 +10,15 @@ const selectors = {
9
10
  navigation: ['[role="navigation"]', 'nav'],
10
11
  region: ['[role="region"]', 'section[aria-labelledby]', 'section[aria-label]', 'section[title]'],
11
12
  };
13
+ /** Roles that are required to be top-level landmarks per WAI-ARIA practices. */
12
14
  const topLevelRoles = ['banner', 'main', 'complementary', 'contentinfo'];
15
+ /**
16
+ * Rule that validates proper usage of ARIA landmark roles.
17
+ *
18
+ * Checks that `banner`, `main`, `complementary`, and `contentinfo` landmarks are
19
+ * top-level (not nested inside other landmarks), and that when duplicate landmarks
20
+ * of the same role exist, each has a unique accessible name.
21
+ */
13
22
  export default createRule({
14
23
  meta: meta,
15
24
  defaultSeverity: 'warning',
@@ -105,11 +114,23 @@ export default createRule({
105
114
  }
106
115
  },
107
116
  });
117
+ /**
118
+ * Collects all UUIDs of elements that have landmark roles.
119
+ *
120
+ * @param roleset - The mapping of roles to their matched elements.
121
+ * @returns A flat array of UUIDs for all landmark role elements.
122
+ */
108
123
  function landmarkRoleElementUUIDList(
109
124
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
110
125
  roleset) {
111
126
  return Object.values(roleset).flatMap(elements => elements.map(element => element.uuid));
112
127
  }
128
+ /**
129
+ * Checks whether an element has an accessible label via `aria-label` or `aria-labelledby`.
130
+ *
131
+ * @param el - The landmark element to check.
132
+ * @returns `true` if the element has an accessible name, `false` otherwise.
133
+ */
113
134
  function hasLabel(
114
135
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
115
136
  el) {
@@ -1,3 +1,4 @@
1
+ /** Rule metadata for the `landmark-roles` rule, categorized as accessibility. */
1
2
  declare const _default: {
2
3
  readonly category: "a11y";
3
4
  };
@@ -1,3 +1,4 @@
1
+ /** Rule metadata for the `landmark-roles` rule, categorized as accessibility. */
1
2
  export default {
2
3
  category: 'a11y',
3
4
  };
@@ -0,0 +1,10 @@
1
+ type Options = {
2
+ readonly allowMicroformats?: boolean | readonly string[];
3
+ };
4
+ /**
5
+ * Rule that validates link type keywords in the `rel` attribute on
6
+ * `<link>`, `<a>`, `<area>`, and `<form>` elements against the WHATWG
7
+ * standard and optionally the Microformats registry.
8
+ */
9
+ declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<boolean, Options>>;
10
+ export default _default;
@@ -0,0 +1,180 @@
1
+ import { createRule } from '@markuplint/ml-core';
2
+ import { DEF_LINK_TYPE_WHATWG, ALLOWED_LINK_TYPE_MICROFORMATS, DEF_LINK_TYPE_MICROFORMATS_DROPPED, DEF_LINK_TYPE_MICROFORMATS_DROPPED_WITHOUT_PREJUDICE, DEF_LINK_TYPE_MICROFORMATS_REJECTED, DEF_LINK_TYPE_MICROFORMATS_NON_HTML_REL_VALUES, } from '@markuplint/types';
3
+ import meta from './meta.js';
4
+ const TARGET_ELEMENTS = new Set(['link', 'a', 'area', 'form']);
5
+ /**
6
+ * Rule that validates link type keywords in the `rel` attribute on
7
+ * `<link>`, `<a>`, `<area>`, and `<form>` elements against the WHATWG
8
+ * standard and optionally the Microformats registry.
9
+ */
10
+ export default createRule({
11
+ meta: meta,
12
+ defaultOptions: {
13
+ allowMicroformats: false,
14
+ },
15
+ async verify({ document, report, t }) {
16
+ await document.walkOn('Element', el => {
17
+ if (!TARGET_ELEMENTS.has(el.localName)) {
18
+ return;
19
+ }
20
+ const relAttr = el.getAttributeNode('rel');
21
+ if (!relAttr) {
22
+ return;
23
+ }
24
+ if (relAttr.isDynamicValue) {
25
+ return;
26
+ }
27
+ const tokenList = relAttr.tokenList;
28
+ if (!tokenList || tokenList.length === 0) {
29
+ return;
30
+ }
31
+ const context = getElementContext(el);
32
+ if (!context) {
33
+ return;
34
+ }
35
+ const options = el.rule.options;
36
+ for (const tokenInfo of tokenList.allTokens()) {
37
+ const keyword = tokenInfo.raw;
38
+ const keywordLower = keyword.toLowerCase();
39
+ const message = validateKeyword(keywordLower, context, options, t);
40
+ if (message) {
41
+ report({
42
+ scope: el,
43
+ line: tokenInfo.startLine,
44
+ col: tokenInfo.startCol,
45
+ raw: keyword,
46
+ message,
47
+ });
48
+ }
49
+ }
50
+ });
51
+ },
52
+ });
53
+ function getElementContext(
54
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
55
+ el) {
56
+ const tagName = el.localName;
57
+ switch (tagName) {
58
+ case 'link': {
59
+ if (el.closest('body') !== null) {
60
+ return 'body-link';
61
+ }
62
+ return 'link';
63
+ }
64
+ case 'a':
65
+ case 'area': {
66
+ return 'a-area';
67
+ }
68
+ case 'form': {
69
+ return 'form';
70
+ }
71
+ default: {
72
+ return null;
73
+ }
74
+ }
75
+ }
76
+ function validateKeyword(keyword, context, options, t) {
77
+ // Check WHATWG standard keywords
78
+ const whatwgDef = DEF_LINK_TYPE_WHATWG.find(def => def.keyword.toLowerCase() === keyword);
79
+ if (whatwgDef) {
80
+ return validateWhatwgKeyword(whatwgDef, context, t);
81
+ }
82
+ // Check dropped keywords
83
+ if (DEF_LINK_TYPE_MICROFORMATS_DROPPED.some(def => def.keyword.toLowerCase() === keyword)) {
84
+ return t('{0} is {1:c}', `"${keyword}"`, 'dropped');
85
+ }
86
+ // Check dropped without prejudice keywords
87
+ if (DEF_LINK_TYPE_MICROFORMATS_DROPPED_WITHOUT_PREJUDICE.some(def => def.keyword.toLowerCase() === keyword)) {
88
+ return t('{0} is {1:c}', `"${keyword}"`, 'dropped');
89
+ }
90
+ // Check rejected keywords
91
+ if (DEF_LINK_TYPE_MICROFORMATS_REJECTED.some(def => def.keyword.toLowerCase() === keyword)) {
92
+ return t('{0} is {1:c}', `"${keyword}"`, 'rejected');
93
+ }
94
+ // Check non-HTML rel values
95
+ if (DEF_LINK_TYPE_MICROFORMATS_NON_HTML_REL_VALUES.some(def => def.keyword.toLowerCase() === keyword)) {
96
+ return t('{0} is {1:c}', `"${keyword}"`, 'not allowed');
97
+ }
98
+ // Handle Microformats
99
+ const { allowMicroformats } = options;
100
+ if (allowMicroformats === false || allowMicroformats === undefined) {
101
+ return t('The "{0*}" {1} is {2:c}', keyword, 'keyword', 'not allowed');
102
+ }
103
+ if (allowMicroformats === true) {
104
+ return validateMicroformatKeyword(keyword, context, t);
105
+ }
106
+ // allowMicroformats is string[]
107
+ if (Array.isArray(allowMicroformats)) {
108
+ const isAllowed = allowMicroformats.some(allowed => allowed.toLowerCase() === keyword);
109
+ if (!isAllowed) {
110
+ return t('The "{0*}" {1} is {2:c}', keyword, 'keyword', 'not allowed');
111
+ }
112
+ // Even if in the allow list, check element context
113
+ const microDef = ALLOWED_LINK_TYPE_MICROFORMATS.find(def => def.keyword.toLowerCase() === keyword);
114
+ if (microDef) {
115
+ return validateMicroformatContext(microDef, context, t);
116
+ }
117
+ // Keyword is in the user's allow list but not in any registry — allow it
118
+ return null;
119
+ }
120
+ return null;
121
+ }
122
+ function validateWhatwgKeyword(def, context, t) {
123
+ switch (context) {
124
+ case 'link': {
125
+ if (def.link === 'not allowed') {
126
+ return t('The "{0*}" {1} is not allowed on the {2}', def.keyword, 'keyword', '"link" element');
127
+ }
128
+ return null;
129
+ }
130
+ case 'body-link': {
131
+ if (def.link === 'not allowed') {
132
+ return t('The "{0*}" {1} is not allowed on the {2}', def.keyword, 'keyword', '"link" element');
133
+ }
134
+ if (def.bodyOk !== 'Yes') {
135
+ return t('The "{0*}" {1} is not allowed on the {2} inside the {3}', def.keyword, 'keyword', '"link" element', '"body" element');
136
+ }
137
+ return null;
138
+ }
139
+ case 'a-area': {
140
+ if (def.a === 'not allowed') {
141
+ return t('The "{0*}" {1} is not allowed on the {2}', def.keyword, 'keyword', '"a" element');
142
+ }
143
+ return null;
144
+ }
145
+ case 'form': {
146
+ if (def.form === 'not allowed') {
147
+ return t('The "{0*}" {1} is not allowed on the {2}', def.keyword, 'keyword', '"form" element');
148
+ }
149
+ return null;
150
+ }
151
+ }
152
+ }
153
+ function validateMicroformatKeyword(keyword, context, t) {
154
+ const microDef = ALLOWED_LINK_TYPE_MICROFORMATS.find(def => def.keyword.toLowerCase() === keyword);
155
+ if (!microDef) {
156
+ return t('The "{0*}" {1} is {2:c}', keyword, 'keyword', 'not allowed');
157
+ }
158
+ return validateMicroformatContext(microDef, context, t);
159
+ }
160
+ function validateMicroformatContext(def, context, t) {
161
+ switch (context) {
162
+ case 'link':
163
+ case 'body-link': {
164
+ if (!def.link) {
165
+ return t('The "{0*}" {1} is not allowed on the {2}', def.keyword, 'keyword', '"link" element');
166
+ }
167
+ return null;
168
+ }
169
+ case 'a-area': {
170
+ if (!def.a) {
171
+ return t('The "{0*}" {1} is not allowed on the {2}', def.keyword, 'keyword', '"a" element');
172
+ }
173
+ return null;
174
+ }
175
+ case 'form': {
176
+ // Microformats don't define form context; reject
177
+ return t('The "{0*}" {1} is not allowed on the {2}', def.keyword, 'keyword', '"form" element');
178
+ }
179
+ }
180
+ }
@@ -0,0 +1,5 @@
1
+ /** Rule metadata for the `link-types` rule, categorized as validation. */
2
+ declare const _default: {
3
+ readonly category: "validation";
4
+ };
5
+ export default _default;
@@ -0,0 +1,4 @@
1
+ /** Rule metadata for the `link-types` rule, categorized as validation. */
2
+ export default {
3
+ category: 'validation',
4
+ };
@@ -1,2 +1,10 @@
1
+ /**
2
+ * Rule that detects perceptible content between a popover trigger and its target.
3
+ *
4
+ * When a `[popovertarget]` element or a `[commandfor]` element with a popover-related
5
+ * `command` attribute and its corresponding `[popover]` target exist in the DOM, any
6
+ * focusable elements, elements with accessible names, or non-whitespace text nodes
7
+ * between them are reported as violations.
8
+ */
1
9
  declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
2
10
  export default _default;