@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,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].sort((a, b) => (b?.barelyMatchedElements ?? 0) - (a?.barelyMatchedElements ?? 0))[0];
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
- _Collection_locked.set(this, new Set());
166
- _Collection_matched.set(this, new Set());
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 [...__classPrivateFieldGet(this, _Collection_matched, "f")];
273
+ return [...this.#matched];
174
274
  }
275
+ /**
276
+ * Returns the number of currently matched nodes.
277
+ */
175
278
  get matchedCount() {
176
- return __classPrivateFieldGet(this, _Collection_matched, "f").size;
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 [...__classPrivateFieldGet(this, _Collection_origin, "f")];
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 [...__classPrivateFieldGet(this, _Collection_nodes, "f")].filter(n => !__classPrivateFieldGet(this, _Collection_matched, "f").has(n));
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 = __classPrivateFieldGet(this, _Collection_matched, "f").size;
303
+ const i = this.#matched.size;
188
304
  for (const node of nodes) {
189
- if (!__classPrivateFieldGet(this, _Collection_nodes, "f").has(node)) {
305
+ if (!this.#nodes.has(node)) {
190
306
  throw new ReferenceError(`External Node: ${node.nodeName}`);
191
307
  }
192
- __classPrivateFieldGet(this, _Collection_matched, "f").add(node);
308
+ this.#matched.add(node);
193
309
  }
194
- return i < __classPrivateFieldGet(this, _Collection_matched, "f").size;
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
- __classPrivateFieldSet(this, _Collection_matched, new Set(__classPrivateFieldGet(this, _Collection_locked, "f")), "f");
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
- __classPrivateFieldSet(this, _Collection_locked, new Set(__classPrivateFieldGet(this, _Collection_matched, "f")), "f");
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 = [...__classPrivateFieldGet(this, _Collection_matched, "f")].slice(max);
336
+ const sliced = [...this.#matched].slice(max);
204
337
  for (const n of sliced)
205
- __classPrivateFieldGet(this, _Collection_matched, "f").delete(n);
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 __classPrivateFieldGet(this, _Collection_origin, "f")) {
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 (__classPrivateFieldGet(this, _Collection_locked, "f").has(n)) {
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 (__classPrivateFieldGet(this, _Collection_matched, "f").has(n)) {
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
- _Collection_locked = new WeakMap(), _Collection_matched = new WeakMap(), _Collection_nodes = new WeakMap(), _Collection_origin = new WeakMap();
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
- * > If a select element has a required attribute specified,
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
- * @param select
27
- * @returns
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
- * > If a select element has a required attribute specified,
48
- * > does not have a multiple attribute specified,
49
- * > and has a display size of 1;
50
- * > and if the value of the first option element
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
@@ -1,3 +1,4 @@
1
+ /** Rule metadata for the `placeholder-label-option` rule, categorized as validation. */
1
2
  declare const _default: {
2
3
  readonly category: "validation";
3
4
  };
@@ -1,3 +1,4 @@
1
+ /** Rule metadata for the `placeholder-label-option` rule, categorized as validation. */
1
2
  export default {
2
3
  category: 'validation',
3
4
  };
@@ -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
+ }
@@ -0,0 +1,5 @@
1
+ /** Rule metadata for the `redundant-accessible-name` rule, categorized as accessibility. */
2
+ declare const _default: {
3
+ readonly category: "a11y";
4
+ };
5
+ export default _default;
@@ -0,0 +1,4 @@
1
+ /** Rule metadata for the `redundant-accessible-name` rule, categorized as accessibility. */
2
+ export default {
3
+ category: 'a11y',
4
+ };
@@ -1,6 +1,12 @@
1
1
  import type { ARIAVersion } from '@markuplint/ml-spec';
2
- type Option = {
3
- ariaVersion: ARIAVersion;
4
- };
5
- declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<boolean, Option>>;
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;