@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,8 +1,85 @@
1
1
  import { getContentModel } from '@markuplint/ml-spec';
2
- import { branchesToPatterns } from '@markuplint/shared';
3
2
  import { order } from './order.js';
4
3
  import { Collection, isTransparent, matches } from './utils.js';
4
+ /**
5
+ * A map tracking child nodes that are currently being evaluated through
6
+ * a transparent content model. Used to prevent infinite recursion and
7
+ * to enable special handling in error messages and debug output.
8
+ */
5
9
  export const transparentMode = new Map();
10
+ /**
11
+ * Maximum number of patterns to generate before falling back to a
12
+ * conservative all-children-merged approach. Prevents exponential
13
+ * blowup when many conditional transparent elements are siblings.
14
+ *
15
+ * ### Rationale
16
+ *
17
+ * When N transparent elements each have K conditional branches (e.g.,
18
+ * `v-if`/`v-else`), the exact cross-product yields K^N patterns.
19
+ * With K=2 and N=10, this is 2^10 = 1024 — still manageable.
20
+ * At N=11 (2^11 = 2048) the cap triggers, switching to a
21
+ * conservative fallback that merges all branch children into every
22
+ * pattern. This keeps runtime linear while covering most real-world
23
+ * conditional structures.
24
+ *
25
+ * ### Fallback precision
26
+ *
27
+ * When the cap is exceeded, the fallback produces an
28
+ * **over-approximation**: all children from all branches are included
29
+ * in every pattern. This may cause false negatives (valid violations
30
+ * go undetected because the merged pattern appears to satisfy the
31
+ * content model) but will never cause false positives (spurious
32
+ * violations are not introduced). In practice, HTML documents rarely
33
+ * exceed 10 conditional transparent siblings, so the cap is
34
+ * transparent to most users.
35
+ *
36
+ * @see https://github.com/markuplint/markuplint/issues/3249
37
+ */
38
+ const MAX_PATTERNS = 1024;
39
+ /**
40
+ * Resolves transparent content model elements by replacing them with their
41
+ * children for validation purposes. In HTML, elements like `<a>`, `<ins>`, and `<del>`
42
+ * have transparent content models, meaning their children must be valid in the
43
+ * parent's content model as if the transparent element were not present.
44
+ *
45
+ * This function:
46
+ * 1. Identifies child elements with transparent content models.
47
+ * 2. Filters out children that match non-transparent parts of the element's content model.
48
+ * 3. Replaces the transparent element with its remaining (unmatched) children.
49
+ * 4. Validates that each remaining child satisfies the transparent model's condition selector.
50
+ * 5. Recursively resolves parent-level transparent nodes to propagate errors up the tree.
51
+ * 6. Builds patterns incrementally, capping cross-products to avoid exponential blowup.
52
+ *
53
+ * ### Algorithm — incremental pattern building
54
+ *
55
+ * Patterns are built incrementally as each child node is visited:
56
+ *
57
+ * - **Non-transparent child**: appended to every existing pattern.
58
+ * Pattern count stays the same.
59
+ * - **Single-branch transparent child** (non-conditional, or
60
+ * `evaluateConditionalChildNodes` disabled): all unmatched children
61
+ * are spread into every pattern. Pattern count stays the same.
62
+ * - **Multi-branch transparent child** (conditional, e.g. `v-if`/`v-else`):
63
+ * a cross-product of existing patterns × branch groups is computed,
64
+ * capped at {@link MAX_PATTERNS}. If the cap would be exceeded, a
65
+ * conservative fallback merges all branch children into every pattern.
66
+ *
67
+ * ### Complexity
68
+ *
69
+ * - **Previous algorithm** (`branchesToPatterns` Cartesian product):
70
+ * O(K^N) time and space, where N = number of transparent elements,
71
+ * K = average branch count per element. 12 elements with 2 children
72
+ * each produced 4096 patterns and took 30+ seconds.
73
+ * - **Current algorithm**: O(N × K × min(|patterns|, MAX_PATTERNS))
74
+ * time; O(MAX_PATTERNS × P) space where P = average pattern length.
75
+ * The same 12-element case now produces 1 pattern in <100 ms.
76
+ *
77
+ * @param childNodes - The child nodes of the element being validated, some of which may be transparent.
78
+ * @param specs - The resolved spec data for content model lookups.
79
+ * @param options - Validation behavior options.
80
+ * @returns An array of possible transparent node resolutions, each with flattened nodes and accumulated errors.
81
+ * @see https://github.com/markuplint/markuplint/issues/3249
82
+ */
6
83
  export function representTransparentNodes(
7
84
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
8
85
  childNodes, specs, options) {
@@ -10,28 +87,34 @@ childNodes, specs, options) {
10
87
  const parentResults = parentElement
11
88
  ? representTransparentNodes([parentElement], specs, options)
12
89
  : [{ nodes: [], errors: [] }];
13
- const branches = [];
90
+ let patterns = [[]];
14
91
  for (const childNode of childNodes) {
15
92
  if (!childNode.is(childNode.ELEMENT_NODE)) {
16
- branches.push(childNode);
93
+ for (const p of patterns) {
94
+ p.push(childNode);
95
+ }
17
96
  continue;
18
97
  }
19
98
  const models = getContentModel(childNode, specs.specs);
20
99
  if (models == null || typeof models === 'boolean') {
21
- branches.push(childNode);
100
+ for (const p of patterns) {
101
+ p.push(childNode);
102
+ }
22
103
  continue;
23
104
  }
24
105
  const noTransparentModels = models.filter(m => !isTransparent(m));
25
106
  if (noTransparentModels.length === models.length) {
26
- branches.push(childNode);
107
+ for (const p of patterns) {
108
+ p.push(childNode);
109
+ }
27
110
  continue;
28
111
  }
29
112
  const childNodesPatterns = options.evaluateConditionalChildNodes
30
113
  ? childNode.conditionalChildNodes().map(childNodes => [...childNodes])
31
114
  : [[...childNode.childNodes].filter(child => !(child.is(child.TEXT_NODE) && child.isWhitespace()))];
32
- const representPattern = [];
33
- for (const childNodes of childNodesPatterns) {
34
- const collection = new Collection([...childNodes]);
115
+ const branchGroups = [];
116
+ for (const branchChildNodes of childNodesPatterns) {
117
+ const collection = new Collection([...branchChildNodes]);
35
118
  let unmatched;
36
119
  if (noTransparentModels.length > 0) {
37
120
  const result = order(noTransparentModels, collection.unmatched, specs, options, Number.POSITIVE_INFINITY);
@@ -44,6 +127,7 @@ childNodes, specs, options) {
44
127
  if (!transparent || !isTransparent(transparent)) {
45
128
  throw new Error('Unreachable code');
46
129
  }
130
+ const branchChildren = [];
47
131
  for (const _child of unmatched) {
48
132
  const child = _child;
49
133
  if (transparentMode.has(child)) {
@@ -53,7 +137,7 @@ childNodes, specs, options) {
53
137
  if (child.is(child.ELEMENT_NODE)) {
54
138
  const transparentCondMatched = matches(transparent.transparent, child, specs);
55
139
  if (!transparentCondMatched.matched) {
56
- representPattern.push({
140
+ branchChildren.push({
57
141
  type: 'TRANSPARENT_MODEL_DISALLOWS',
58
142
  matched: [],
59
143
  unmatched: [childNode],
@@ -67,12 +151,35 @@ childNodes, specs, options) {
67
151
  continue;
68
152
  }
69
153
  }
70
- representPattern.push(child);
154
+ branchChildren.push(child);
155
+ }
156
+ branchGroups.push(branchChildren);
157
+ }
158
+ if (branchGroups.every(g => g.length === 0)) {
159
+ continue;
160
+ }
161
+ if (branchGroups.length === 1) {
162
+ const singleGroup = branchGroups[0];
163
+ for (const p of patterns) {
164
+ p.push(...singleGroup);
165
+ }
166
+ }
167
+ else if (patterns.length * branchGroups.length <= MAX_PATTERNS) {
168
+ const newPatterns = [];
169
+ for (const p of patterns) {
170
+ for (const group of branchGroups) {
171
+ newPatterns.push([...p, ...group]);
172
+ }
173
+ }
174
+ patterns = newPatterns;
175
+ }
176
+ else {
177
+ const allChildren = branchGroups.flat();
178
+ for (const p of patterns) {
179
+ p.push(...allChildren);
71
180
  }
72
181
  }
73
- branches.push(representPattern);
74
182
  }
75
- const patterns = branchesToPatterns(branches);
76
183
  const result = parentResults.flatMap(parentResult => {
77
184
  const patternResults = patterns.map(pattern => {
78
185
  const nodes = pattern.filter((node) => 'nodeName' in node);
@@ -2,12 +2,18 @@ import type { ContentModelResult, Element, Options, Specs } from './types.js';
2
2
  import type { ContentModel } from '@markuplint/ml-spec';
3
3
  import type { ReadonlyDeep } from 'type-fest';
4
4
  /**
5
- * Check start
5
+ * Entry point for validating an element's child nodes against a content model definition.
6
+ * Handles the three forms of content model: `false` (no content allowed), `true` (any content
7
+ * allowed), or an ordered pattern array. For pattern arrays, transparent content model nodes
8
+ * are first resolved, then each resulting pattern of child nodes is validated via `order`.
6
9
  *
7
- * @param contents
8
- * @param el
9
- * @param specs
10
- * @param options
11
- * @returns
10
+ * When `options.evaluateConditionalChildNodes` is enabled, all conditional branches
11
+ * (e.g., from template directives) are evaluated independently.
12
+ *
13
+ * @param contents - The content model definition: `false` for empty, `true` for any, or an array of patterns.
14
+ * @param el - The parent element whose children are being validated.
15
+ * @param specs - The resolved spec data for content model lookups.
16
+ * @param options - Validation behavior options.
17
+ * @returns An array of content model results describing any violations found.
12
18
  */
13
19
  export declare function start(contents: ReadonlyDeep<ContentModel['contents']>, el: Element, specs: Specs, options: Options): ContentModelResult[];
@@ -1,13 +1,19 @@
1
1
  import { order } from './order.js';
2
2
  import { representTransparentNodes } from './represent-transparent-nodes.js';
3
3
  /**
4
- * Check start
4
+ * Entry point for validating an element's child nodes against a content model definition.
5
+ * Handles the three forms of content model: `false` (no content allowed), `true` (any content
6
+ * allowed), or an ordered pattern array. For pattern arrays, transparent content model nodes
7
+ * are first resolved, then each resulting pattern of child nodes is validated via `order`.
5
8
  *
6
- * @param contents
7
- * @param el
8
- * @param specs
9
- * @param options
10
- * @returns
9
+ * When `options.evaluateConditionalChildNodes` is enabled, all conditional branches
10
+ * (e.g., from template directives) are evaluated independently.
11
+ *
12
+ * @param contents - The content model definition: `false` for empty, `true` for any, or an array of patterns.
13
+ * @param el - The parent element whose children are being validated.
14
+ * @param specs - The resolved spec data for content model lookups.
15
+ * @param options - Validation behavior options.
16
+ * @returns An array of content model results describing any violations found.
11
17
  */
12
18
  export function start(contents,
13
19
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
@@ -1,2 +1,14 @@
1
1
  import type { ChildNode, Result } from './types.js';
2
+ /**
3
+ * Handles the transparent content model pattern by passing all child nodes
4
+ * through as matched. In HTML, a transparent element inherits the content model
5
+ * of its parent, so its children are validated against the parent's model instead.
6
+ *
7
+ * If the element is a component root (has no grandparent element), all children
8
+ * are treated as matched. Otherwise, validation is deferred to the parent's
9
+ * content model processing.
10
+ *
11
+ * @param childNodes - The child nodes to evaluate under the transparent model.
12
+ * @returns A result indicating all children are matched (validation deferred to parent).
13
+ */
2
14
  export declare function transparent(childNodes: readonly ChildNode[]): Result;
@@ -1,5 +1,17 @@
1
1
  import { cmLog } from './debug.js';
2
2
  const transparentLog = cmLog.extend('transparent');
3
+ /**
4
+ * Handles the transparent content model pattern by passing all child nodes
5
+ * through as matched. In HTML, a transparent element inherits the content model
6
+ * of its parent, so its children are validated against the parent's model instead.
7
+ *
8
+ * If the element is a component root (has no grandparent element), all children
9
+ * are treated as matched. Otherwise, validation is deferred to the parent's
10
+ * content model processing.
11
+ *
12
+ * @param childNodes - The child nodes to evaluate under the transparent model.
13
+ * @returns A result indicating all children are matched (validation deferred to parent).
14
+ */
3
15
  export function transparent(
4
16
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
5
17
  childNodes) {
@@ -1,8 +1,19 @@
1
1
  import type { Element as _Element, ChildNode as _ChildNode } from '@markuplint/ml-core';
2
2
  import type { ContentModel, MLMLSpec } from '@markuplint/ml-spec';
3
3
  import type { ReadonlyDeep } from 'type-fest';
4
+ /**
5
+ * An element node parameterized with the permitted-contents rule's TagRule and Options.
6
+ */
4
7
  export type Element = _Element<TagRule[], Options>;
8
+ /**
9
+ * A child node parameterized with the permitted-contents rule's TagRule and Options.
10
+ */
5
11
  export type ChildNode = _ChildNode<TagRule[], Options>;
12
+ /**
13
+ * A subset of the full ML spec containing only the fields needed
14
+ * for content model validation: citation references, global definitions,
15
+ * and per-element content model specifications.
16
+ */
6
17
  export type Specs = {
7
18
  readonly cites: MLMLSpec['cites'];
8
19
  readonly def: MLMLSpec['def'];
@@ -13,12 +24,24 @@ export type Specs = {
13
24
  };
14
25
  }[];
15
26
  };
27
+ /**
28
+ * The final result of content model validation for a single child node,
29
+ * indicating whether the node is permitted, missing, or unexpected
30
+ * within its parent element's content model.
31
+ */
16
32
  export type ContentModelResult = {
17
33
  type: MatchedReason | UnmatchedReason;
18
34
  scope: ChildNode;
19
35
  query: Result['query'];
20
36
  hint: Result['hint'];
21
37
  };
38
+ /**
39
+ * An intermediate result produced during content model pattern matching.
40
+ * Tracks which child nodes were matched, which were unmatched, and
41
+ * provides diagnostic hints about the nature of a mismatch.
42
+ *
43
+ * @template T - Additional result type strings beyond the standard matched/unmatched reasons.
44
+ */
22
45
  export type Result<T extends string = MatchedReason> = {
23
46
  type: MatchedReason | UnmatchedReason | T;
24
47
  matched: ChildNode[];
@@ -27,6 +50,11 @@ export type Result<T extends string = MatchedReason> = {
27
50
  query: string;
28
51
  hint: Hints;
29
52
  };
53
+ /**
54
+ * Diagnostic hints attached to a content model result, providing additional
55
+ * context for error reporting such as maximum allowed count, the offending
56
+ * node, or information about transparent model violations.
57
+ */
30
58
  export type Hints = {
31
59
  max?: number;
32
60
  not?: ChildNode;
@@ -36,21 +64,60 @@ export type Hints = {
36
64
  need?: string;
37
65
  };
38
66
  };
67
+ /**
68
+ * Reasons indicating that a content model pattern successfully matched.
69
+ * `MATCHED` means one or more nodes matched; `MATCHED_ZERO` means the pattern
70
+ * matched vacuously (zero nodes consumed, but the pattern allows it).
71
+ */
39
72
  export type MatchedReason = 'MATCHED' | 'MATCHED_ZERO';
73
+ /**
74
+ * Reasons indicating that a content model pattern did not match.
75
+ * Includes cases where content is disallowed entirely, an unexpected node
76
+ * was found, a transparent model forbids the node, or a required node is missing.
77
+ */
40
78
  export type UnmatchedReason = 'NOTHING' | 'UNEXPECTED_EXTRA_NODE' | 'TRANSPARENT_MODEL_DISALLOWS' | MissingNodeReason;
79
+ /**
80
+ * Specific reasons for a missing node: either a required element is absent,
81
+ * or one-or-more occurrences are expected but none were found.
82
+ */
41
83
  export type MissingNodeReason = 'MISSING_NODE_REQUIRED' | 'MISSING_NODE_ONE_OR_MORE';
84
+ /**
85
+ * A regular-expression-like repeat sign used to describe the quantifier
86
+ * of a content model pattern (e.g., `?` for optional, `+` for one-or-more,
87
+ * `*` for zero-or-more, or `{min,max}` for a specific range).
88
+ */
42
89
  export type RepeatSign = '' | '?' | '+' | '*' | `{${number},${number}}`;
90
+ /**
91
+ * Describes a transparent content model element along with any additional
92
+ * CSS selector condition that must be satisfied for the transparency to apply.
93
+ */
43
94
  export type TransparentModel = {
44
95
  el: Element;
45
96
  additionalCondition: string;
46
97
  };
98
+ /**
99
+ * A user-defined tag rule that associates an element tag name with
100
+ * its permitted content model definition, used to override or extend
101
+ * the built-in HTML spec content models.
102
+ */
47
103
  export type TagRule = {
48
104
  readonly tag: string;
49
105
  } & ReadonlyDeep<ContentModel>;
106
+ /**
107
+ * Options for the permitted-contents rule that control validation behavior.
108
+ */
50
109
  export type Options = {
110
+ /**
111
+ * When true, skips reporting missing-node errors on elements that
112
+ * have mutable children (e.g., elements whose children may be
113
+ * dynamically injected by a framework or template engine).
114
+ */
51
115
  readonly ignoreHasMutableChildren: boolean;
52
116
  /**
53
117
  * @experimental
118
+ * When true, evaluates conditional child node branches (e.g., from
119
+ * template directives like `v-if`) to validate each possible branch
120
+ * against the content model.
54
121
  */
55
122
  readonly evaluateConditionalChildNodes: boolean;
56
123
  };
@@ -1,7 +1,26 @@
1
1
  import type { ChildNode, Hints, MissingNodeReason, RepeatSign, Specs } from './types.js';
2
2
  import type { PermittedContentPattern, PermittedContentChoice, PermittedContentOneOrMore, PermittedContentOptional, PermittedContentRequire, PermittedContentTransparent, PermittedContentZeroOrMore, Model } from '@markuplint/ml-spec';
3
3
  import type { ReadonlyDeep } from 'type-fest';
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
+ */
4
13
  export declare function isModel(model: ReadonlyDeep<Model | PermittedContentPattern[]>): model is ReadonlyDeep<Model>;
14
+ /**
15
+ * Tests whether a child node matches a CSS selector string using the markuplint
16
+ * selector engine. Returns whether the node matched and, if not, the deepest
17
+ * unmatched descendant node for diagnostic purposes.
18
+ *
19
+ * @param selector - The CSS selector string to test against.
20
+ * @param node - The child node to test.
21
+ * @param specs - The spec data passed to the selector engine for attribute resolution.
22
+ * @returns An object with `matched: true` if the node matches, or `matched: false` with an optional `not` node.
23
+ */
5
24
  export declare function matches(selector: string, node: ChildNode, specs: Specs): {
6
25
  matched: boolean;
7
26
  not?: undefined;
@@ -9,12 +28,62 @@ export declare function matches(selector: string, node: ChildNode, specs: Specs)
9
28
  matched: boolean;
10
29
  not: ChildNode | undefined;
11
30
  };
31
+ /**
32
+ * Type guard that checks whether a content model pattern is a "require" pattern,
33
+ * indicating one or more required occurrences of a specific element or model.
34
+ *
35
+ * @param content - The content model pattern to check.
36
+ * @returns True if the pattern has a `require` property.
37
+ */
12
38
  export declare function isRequire(content: ReadonlyDeep<PermittedContentPattern>): content is ReadonlyDeep<PermittedContentRequire>;
39
+ /**
40
+ * Type guard that checks whether a content model pattern is an "optional" pattern,
41
+ * indicating zero or one occurrences of a specific element or model.
42
+ *
43
+ * @param content - The content model pattern to check.
44
+ * @returns True if the pattern has an `optional` property.
45
+ */
13
46
  export declare function isOptional(content: ReadonlyDeep<PermittedContentPattern>): content is ReadonlyDeep<PermittedContentOptional>;
47
+ /**
48
+ * Type guard that checks whether a content model pattern is a "oneOrMore" pattern,
49
+ * indicating one or more occurrences of a specific element or model.
50
+ *
51
+ * @param content - The content model pattern to check.
52
+ * @returns True if the pattern has a `oneOrMore` property.
53
+ */
14
54
  export declare function isOneOrMore(content: ReadonlyDeep<PermittedContentPattern>): content is ReadonlyDeep<PermittedContentOneOrMore>;
55
+ /**
56
+ * Type guard that checks whether a content model pattern is a "zeroOrMore" pattern,
57
+ * indicating zero or more occurrences of a specific element or model.
58
+ *
59
+ * @param content - The content model pattern to check.
60
+ * @returns True if the pattern has a `zeroOrMore` property.
61
+ */
15
62
  export declare function isZeroOrMore(content: ReadonlyDeep<PermittedContentPattern>): content is ReadonlyDeep<PermittedContentZeroOrMore>;
63
+ /**
64
+ * Type guard that checks whether a content model pattern is a "choice" pattern,
65
+ * representing an alternation between multiple possible content model branches.
66
+ *
67
+ * @param content - The content model pattern to check.
68
+ * @returns True if the pattern has a `choice` property.
69
+ */
16
70
  export declare function isChoice(content: ReadonlyDeep<PermittedContentPattern>): content is ReadonlyDeep<PermittedContentChoice>;
71
+ /**
72
+ * Type guard that checks whether a content model pattern is a "transparent" pattern,
73
+ * indicating the element inherits its parent's content model.
74
+ *
75
+ * @param content - The content model pattern to check.
76
+ * @returns True if the pattern has a `transparent` property.
77
+ */
17
78
  export declare function isTransparent(content: ReadonlyDeep<PermittedContentPattern>): content is ReadonlyDeep<PermittedContentTransparent>;
79
+ /**
80
+ * Normalizes a quantified content model pattern (require, optional, oneOrMore, or zeroOrMore)
81
+ * into a uniform representation with the inner model, minimum count, maximum count,
82
+ * a regex-like repeat sign for debug display, and the appropriate missing-node error type.
83
+ *
84
+ * @param pattern - A quantified content model pattern to normalize.
85
+ * @returns An object with `model`, `min`, `max`, `repeat` sign, and optional `missingType`.
86
+ */
18
87
  export declare function normalizeModel(pattern: ReadonlyDeep<PermittedContentRequire> | ReadonlyDeep<PermittedContentOptional> | ReadonlyDeep<PermittedContentOneOrMore> | ReadonlyDeep<PermittedContentZeroOrMore>): {
19
88
  model: ReadonlyDeep<PermittedContentPattern[] | Model>;
20
89
  min: number;
@@ -22,6 +91,15 @@ export declare function normalizeModel(pattern: ReadonlyDeep<PermittedContentReq
22
91
  repeat: RepeatSign;
23
92
  missingType: MissingNodeReason | undefined;
24
93
  };
94
+ /**
95
+ * Merges two Hints objects, combining their properties and selecting the
96
+ * `missing` hint with the higher `barelyMatchedElements` count (i.e., the
97
+ * one closest to a successful match) for the most useful error diagnostics.
98
+ *
99
+ * @param a - The first hints object.
100
+ * @param b - The second hints object.
101
+ * @returns A merged hints object with undefined properties removed.
102
+ */
25
103
  export declare function mergeHints(a: Readonly<Hints>, b: Readonly<Hints>): Partial<{
26
104
  missing: Partial<{
27
105
  barelyMatchedElements?: number;
@@ -31,20 +109,102 @@ export declare function mergeHints(a: Readonly<Hints>, b: Readonly<Hints>): Part
31
109
  not?: ChildNode;
32
110
  transparent?: import("./types.js").Element;
33
111
  }>;
112
+ /**
113
+ * Creates a shallow copy of an object with all `undefined` values removed.
114
+ * Used to produce clean hint objects for result reporting without
115
+ * extraneous undefined properties.
116
+ *
117
+ * @template T - The object type.
118
+ * @param object - The source object to clean.
119
+ * @returns A new object containing only the defined properties of the input.
120
+ */
34
121
  export declare function cleanObject<T extends object>(object: T): Partial<T>;
122
+ /**
123
+ * Tracks matched and unmatched child nodes during content model validation.
124
+ * Provides operations for adding matched nodes, backtracking to a locked state,
125
+ * capping matches at a maximum count, and generating colored debug output.
126
+ *
127
+ * The collection maintains an ordered set of original nodes and partitions them
128
+ * into matched and unmatched sets as the validation algorithm progresses.
129
+ */
35
130
  export declare class Collection {
36
131
  #private;
132
+ /**
133
+ * Creates a new Collection from the given child nodes.
134
+ *
135
+ * @param origin - The initial list of child nodes to track.
136
+ */
37
137
  constructor(origin: readonly ChildNode[]);
138
+ /**
139
+ * Returns a copy of the currently matched nodes in insertion order.
140
+ */
38
141
  get matched(): ChildNode[];
142
+ /**
143
+ * Returns the number of currently matched nodes.
144
+ */
39
145
  get matchedCount(): number;
146
+ /**
147
+ * Returns a copy of all original nodes in their original order.
148
+ */
40
149
  get nodes(): ChildNode[];
150
+ /**
151
+ * Returns the nodes that have not yet been matched, preserving original order.
152
+ */
41
153
  get unmatched(): ChildNode[];
154
+ /**
155
+ * Adds nodes to the matched set. All nodes must belong to the original
156
+ * collection; external nodes will cause a ReferenceError.
157
+ *
158
+ * @param nodes - The child nodes to mark as matched.
159
+ * @returns True if the matched set grew (new nodes were added), false otherwise.
160
+ */
42
161
  addMatched(nodes: ChildNode[]): boolean;
162
+ /**
163
+ * Reverts the matched set to the last locked state, discarding
164
+ * any matches added since the last `lock()` call. Used for backtracking
165
+ * when a pattern match attempt fails after a zero-match.
166
+ */
43
167
  back(): void;
168
+ /**
169
+ * Saves the current matched set as a checkpoint that `back()` can
170
+ * revert to. Called after a successful backtrack recovery to preserve
171
+ * the known-good state.
172
+ */
44
173
  lock(): void;
174
+ /**
175
+ * Trims the matched set to at most `max` entries by removing
176
+ * the most recently added nodes beyond the limit. Used when the
177
+ * maximum occurrence count for a pattern is exceeded.
178
+ *
179
+ * @param max - The maximum number of matched nodes to retain.
180
+ */
45
181
  max(max: number): void;
182
+ /**
183
+ * Returns a colored string representation of all nodes for debug logging.
184
+ * Matched nodes are shown in green, locked nodes in bold green, unmatched
185
+ * extra nodes in red (when highlighted), and transparent-mode nodes in
186
+ * blue/cyan/magenta variants.
187
+ *
188
+ * @param highlightExtraNodes - When true, unmatched nodes are highlighted in red/magenta.
189
+ * @returns A formatted string like `[ <div>, <span>, <p> ]` with ANSI colors.
190
+ */
46
191
  toString(highlightExtraNodes?: boolean): string;
47
192
  }
193
+ /**
194
+ * Error class representing an unsupported content model feature.
195
+ * Thrown when the validation engine encounters a pattern type or
196
+ * configuration that is not yet implemented.
197
+ */
48
198
  export declare class UnsupportedError extends Error {
49
199
  }
200
+ /**
201
+ * Formats a content model (terminal model or pattern array) and its repeat sign
202
+ * into a regex-like string for debug logging output. Terminal selectors are
203
+ * rendered as `<selector>`, arrays of selectors as `(<s1>|<s2>)`, and
204
+ * nested patterns are recursively formatted.
205
+ *
206
+ * @param model - The model or pattern array to format.
207
+ * @param repeat - The quantifier sign to append (e.g., `+`, `*`, `?`, or `{m,n}`).
208
+ * @returns A human-readable regex-like string representation of the model.
209
+ */
50
210
  export declare function modelLog(model: ReadonlyDeep<Model | PermittedContentPattern[]>, repeat: RepeatSign): string;