@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
@@ -0,0 +1,108 @@
1
+ import { createRule } from '@markuplint/ml-core';
2
+ import meta from './meta.js';
3
+ import { hasSizesAuto, parseSrcset } from './parse-srcset.js';
4
+ /**
5
+ * Rule that enforces WHATWG constraints between the `srcset`, `sizes`,
6
+ * and `loading` attributes on `<img>` and `<source>` elements.
7
+ */
8
+ export default createRule({
9
+ meta: meta,
10
+ async verify({ document, report }) {
11
+ await document.walkOn('Element', el => {
12
+ const localName = el.localName;
13
+ // Only check img and source (inside picture) elements
14
+ if (localName !== 'img' && localName !== 'source') {
15
+ return;
16
+ }
17
+ // source is only relevant inside <picture>
18
+ if (localName === 'source' && el.parentElement?.localName !== 'picture') {
19
+ return;
20
+ }
21
+ // Skip if element has spread attributes (dynamic props)
22
+ if (el.hasSpreadAttr) {
23
+ return;
24
+ }
25
+ const srcsetAttr = el.getAttributeNode('srcset');
26
+ if (!srcsetAttr) {
27
+ return;
28
+ }
29
+ const srcsetIsDynamic = srcsetAttr.isDynamicValue;
30
+ const sizesAttr = el.getAttributeNode('sizes');
31
+ const sizesValue = sizesAttr?.value ?? null;
32
+ const sizesIsDynamic = sizesAttr?.isDynamicValue;
33
+ // Parse srcset unless dynamic
34
+ const parsed = srcsetIsDynamic ? null : parseSrcset(srcsetAttr.value);
35
+ // Check 2: width and density descriptors must not be mixed
36
+ if (parsed && parsed.hasWidth && (parsed.hasDensity || parsed.hasImplied)) {
37
+ report({
38
+ scope: el,
39
+ line: srcsetAttr.valueNode?.startLine,
40
+ col: srcsetAttr.valueNode?.startCol,
41
+ raw: srcsetAttr.valueNode?.raw,
42
+ message: 'The "srcset" attribute must not mix width and pixel density descriptors',
43
+ });
44
+ }
45
+ // Check 1: sizes present → srcset must use width descriptors
46
+ if (sizesAttr && !sizesIsDynamic && parsed && !parsed.hasWidth) {
47
+ report({
48
+ scope: el,
49
+ line: srcsetAttr.valueNode?.startLine,
50
+ col: srcsetAttr.valueNode?.startCol,
51
+ raw: srcsetAttr.valueNode?.raw,
52
+ message: 'The "srcset" attribute requires width descriptors when the "sizes" attribute is present',
53
+ });
54
+ }
55
+ // Check 3: img[sizes=auto] → loading=lazy required
56
+ if (localName === 'img' && sizesValue != null && !sizesIsDynamic && hasSizesAuto(sizesValue)) {
57
+ const loading = el.getAttribute('loading');
58
+ if (loading !== 'lazy') {
59
+ report({
60
+ scope: el,
61
+ line: sizesAttr.valueNode?.startLine,
62
+ col: sizesAttr.valueNode?.startCol,
63
+ raw: sizesAttr.valueNode?.raw,
64
+ message: 'The "sizes" attribute with "auto" requires the "loading" attribute to be "lazy"',
65
+ });
66
+ }
67
+ }
68
+ // Check 4: source[sizes=auto] → following sibling img must have loading=lazy
69
+ if (localName === 'source' && sizesValue != null && !sizesIsDynamic && hasSizesAuto(sizesValue)) {
70
+ const img = findFollowingImg(el);
71
+ if (!img || img.getAttribute('loading') !== 'lazy') {
72
+ report({
73
+ scope: el,
74
+ line: sizesAttr.valueNode?.startLine,
75
+ col: sizesAttr.valueNode?.startCol,
76
+ raw: sizesAttr.valueNode?.raw,
77
+ message: 'The "source" element with sizes="auto" requires the following sibling "img" element to have loading="lazy"',
78
+ });
79
+ }
80
+ }
81
+ // Check 5: img with w descriptors → sizes required
82
+ if (localName === 'img' && parsed && parsed.hasWidth && !sizesAttr) {
83
+ report({
84
+ scope: el,
85
+ message: 'The "sizes" attribute is required when the "srcset" attribute uses width descriptors',
86
+ });
87
+ }
88
+ });
89
+ },
90
+ });
91
+ /**
92
+ * Find the first following sibling `<img>` element.
93
+ * Per the spec, the img does not have to be the immediately next sibling.
94
+ *
95
+ * @param el - The starting element to search from
96
+ * @returns The first following sibling `<img>` element, or `null` if none found
97
+ */
98
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
99
+ function findFollowingImg(el) {
100
+ let sibling = el.nextElementSibling;
101
+ while (sibling != null) {
102
+ if (sibling.localName === 'img') {
103
+ return sibling;
104
+ }
105
+ sibling = sibling.nextElementSibling;
106
+ }
107
+ return null;
108
+ }
@@ -0,0 +1,5 @@
1
+ /** Rule metadata for `srcset-sizes-constraint`: categorized as a validation rule. */
2
+ declare const _default: {
3
+ readonly category: "validation";
4
+ };
5
+ export default _default;
@@ -0,0 +1,4 @@
1
+ /** Rule metadata for `srcset-sizes-constraint`: categorized as a validation rule. */
2
+ export default {
3
+ category: 'validation',
4
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Descriptor type for an image candidate in a srcset attribute.
3
+ * - `width`: ends with `w` (e.g., `480w`)
4
+ * - `density`: ends with `x` (e.g., `2x`)
5
+ * - `none`: no descriptor (implies `1x`)
6
+ */
7
+ type DescriptorType = 'width' | 'density' | 'none';
8
+ type SrcsetCandidate = {
9
+ readonly url: string;
10
+ readonly descriptorType: DescriptorType;
11
+ };
12
+ type ParseResult = {
13
+ readonly candidates: readonly SrcsetCandidate[];
14
+ /** At least one candidate has a width descriptor (`w`). */
15
+ readonly hasWidth: boolean;
16
+ /** At least one candidate has a pixel density descriptor (`x`). */
17
+ readonly hasDensity: boolean;
18
+ /** At least one candidate has no descriptor (implied `1x`). */
19
+ readonly hasImplied: boolean;
20
+ };
21
+ /**
22
+ * Parse a `srcset` attribute value into its image candidates.
23
+ *
24
+ * @param value - The raw `srcset` attribute value string
25
+ * @returns Parsed result with candidates and descriptor flags
26
+ * @see https://html.spec.whatwg.org/multipage/images.html#srcset-attributes
27
+ */
28
+ export declare function parseSrcset(value: string): ParseResult;
29
+ /**
30
+ * Check whether a `sizes` attribute value starts with `auto`.
31
+ *
32
+ * Per the spec, `auto` is only valid as the first entry in a sizes list.
33
+ * Examples: `"auto"`, `"auto, 100vw"`, `"AUTO"`.
34
+ *
35
+ * @param value - The raw `sizes` attribute value string
36
+ * @returns `true` if the value starts with `auto` (case-insensitive)
37
+ * @see https://html.spec.whatwg.org/multipage/embedded-content.html#the-img-element
38
+ */
39
+ export declare function hasSizesAuto(value: string): boolean;
40
+ export {};
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Parse a `srcset` attribute value into its image candidates.
3
+ *
4
+ * @param value - The raw `srcset` attribute value string
5
+ * @returns Parsed result with candidates and descriptor flags
6
+ * @see https://html.spec.whatwg.org/multipage/images.html#srcset-attributes
7
+ */
8
+ export function parseSrcset(value) {
9
+ const raw = value.trim();
10
+ if (raw === '') {
11
+ return { candidates: [], hasWidth: false, hasDensity: false, hasImplied: false };
12
+ }
13
+ const candidates = [];
14
+ let hasWidth = false;
15
+ let hasDensity = false;
16
+ let hasImplied = false;
17
+ for (const segment of raw.split(',')) {
18
+ const tokens = segment.trim().split(/\s+/);
19
+ const url = tokens[0];
20
+ if (!url) {
21
+ continue;
22
+ }
23
+ const descriptor = tokens[1];
24
+ let descriptorType = 'none';
25
+ if (descriptor) {
26
+ if (/^[1-9]\d*w$/.test(descriptor)) {
27
+ descriptorType = 'width';
28
+ hasWidth = true;
29
+ }
30
+ else if (/^\d+(?:\.\d+)?x$/.test(descriptor)) {
31
+ descriptorType = 'density';
32
+ hasDensity = true;
33
+ }
34
+ // Invalid descriptors are ignored here; type validation handles them.
35
+ }
36
+ else {
37
+ hasImplied = true;
38
+ }
39
+ candidates.push({ url, descriptorType });
40
+ }
41
+ return { candidates, hasWidth, hasDensity, hasImplied };
42
+ }
43
+ /**
44
+ * Check whether a `sizes` attribute value starts with `auto`.
45
+ *
46
+ * Per the spec, `auto` is only valid as the first entry in a sizes list.
47
+ * Examples: `"auto"`, `"auto, 100vw"`, `"AUTO"`.
48
+ *
49
+ * @param value - The raw `sizes` attribute value string
50
+ * @returns `true` if the value starts with `auto` (case-insensitive)
51
+ * @see https://html.spec.whatwg.org/multipage/embedded-content.html#the-img-element
52
+ */
53
+ export function hasSizesAuto(value) {
54
+ const v = value.trim().toLowerCase();
55
+ return v === 'auto' || /^auto[\s,]/.test(v);
56
+ }
@@ -1,2 +1,12 @@
1
1
  import type { Element } from '@markuplint/ml-core';
2
+ /**
3
+ * Finds direct children of an element matching the given CSS selector, with caching.
4
+ *
5
+ * Results are cached by element UUID and selector to avoid redundant DOM traversals
6
+ * when the same element/selector combination is queried multiple times.
7
+ *
8
+ * @param el - The parent element to search within.
9
+ * @param selector - The CSS selector to match children against.
10
+ * @returns An array of matching child elements.
11
+ */
2
12
  export declare function findChildren(el: Element<boolean>, selector: string): Element<boolean, undefined>[];
@@ -1,4 +1,15 @@
1
+ /** Cache for `findChildren` results, keyed by element UUID and selector. */
1
2
  const findCache = {};
3
+ /**
4
+ * Finds direct children of an element matching the given CSS selector, with caching.
5
+ *
6
+ * Results are cached by element UUID and selector to avoid redundant DOM traversals
7
+ * when the same element/selector combination is queried multiple times.
8
+ *
9
+ * @param el - The parent element to search within.
10
+ * @param selector - The CSS selector to match children against.
11
+ * @returns An array of matching child elements.
12
+ */
2
13
  export function findChildren(
3
14
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
4
15
  el, selector) {
@@ -1,22 +1,82 @@
1
1
  import type { CellType } from './types.js';
2
2
  import type { Element } from '@markuplint/ml-core';
3
+ /**
4
+ * Represents a table as a grid model, splitting it into thead, tbody, and tfoot sections.
5
+ *
6
+ * Each section is modeled as a 2D array of `CellType` values that account for
7
+ * `colspan` and `rowspan` attributes. Used by the `table-row-column-alignment`
8
+ * rule to detect misaligned rows and overlapping cells.
9
+ */
3
10
  export declare class Grid {
4
11
  #private;
12
+ /** The grid model for the `<tbody>` section. */
5
13
  readonly tbodyGrid: ReadonlyArray<ReadonlyArray<CellType>>;
14
+ /** The grid model for the `<tfoot>` section. */
6
15
  readonly tfootGrid: ReadonlyArray<ReadonlyArray<CellType>>;
16
+ /** The grid model for the `<thead>` section. */
7
17
  readonly theadGrid: ReadonlyArray<ReadonlyArray<CellType>>;
18
+ /**
19
+ * Constructs a grid model from a `<table>` element.
20
+ *
21
+ * @param table - The table element to model.
22
+ */
8
23
  constructor(table: Element<boolean>);
24
+ /**
25
+ * Returns all `<tr>` elements across all table sections in document order.
26
+ *
27
+ * @returns Combined array of row elements from thead, tbody, and tfoot.
28
+ */
9
29
  getAllRowElements(): Element<boolean, undefined>[];
30
+ /**
31
+ * Returns all grid rows that contain at least one actual cell element.
32
+ *
33
+ * @returns Combined array of grid rows from thead, tbody, and tfoot, filtered to rows with real cells.
34
+ */
10
35
  getAllRows(): (readonly CellType[])[];
36
+ /**
37
+ * Determines the expected (base) column count for the table.
38
+ *
39
+ * Prefers the thead section if available, then tfoot, then tbody.
40
+ *
41
+ * @returns The base number of columns that rows should have.
42
+ */
11
43
  getBaseColLength(): number;
44
+ /**
45
+ * Returns the grid and row elements for each table section (thead, tbody, tfoot).
46
+ *
47
+ * @returns An array of objects, each containing the section grid and its row elements.
48
+ */
12
49
  getSections(): {
13
50
  section: readonly (readonly CellType[])[];
14
51
  elements: readonly Element<boolean, undefined>[];
15
52
  }[];
53
+ /**
54
+ * Checks whether any cell in the table grid has been marked as overlapping.
55
+ *
56
+ * @returns `true` if any cell has the overlap marker `'x'`.
57
+ */
16
58
  hasOverlapped(): boolean;
59
+ /** Logs the grid data for all three table sections to the console (for debugging). */
17
60
  log(): void;
18
61
  }
62
+ /**
63
+ * Detects a rowspan that extends beyond the available rows in a table section.
64
+ *
65
+ * When a grid row exists without a corresponding row element, it indicates
66
+ * a rowspan overflow. Returns the offending `rowspan` attribute node if found.
67
+ *
68
+ * @param rows - The grid rows for a single table section.
69
+ * @param rowElements - The `<tr>` elements for the section.
70
+ * @returns An object containing the overflowing `rowSpan` attribute, or `null`.
71
+ */
19
72
  export declare function getOverflowRowSpan(rows: ReadonlyArray<ReadonlyArray<CellType>>, rowElements: ReadonlyArray<Element<boolean>>): {
20
73
  rowSpan: import("@markuplint/ml-core").Attr<boolean, undefined>;
21
74
  } | null;
75
+ /**
76
+ * Maps each cell in a grid row to its source cell index (for cells that correspond
77
+ * to actual `<td>`/`<th>` elements), or `null` for spanned cells.
78
+ *
79
+ * @param row - A single row of cell types from the grid model.
80
+ * @returns An array of cell indices or `null` values, one per grid column.
81
+ */
22
82
  export declare function getIndexes(row: readonly CellType[]): (number | null)[];
@@ -1,36 +1,55 @@
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 _Grid_tbodyRowElements, _Grid_tfootRowElements, _Grid_theadRowElements;
13
1
  import { findChildren } from './find-children.js';
2
+ /**
3
+ * Represents a table as a grid model, splitting it into thead, tbody, and tfoot sections.
4
+ *
5
+ * Each section is modeled as a 2D array of `CellType` values that account for
6
+ * `colspan` and `rowspan` attributes. Used by the `table-row-column-alignment`
7
+ * rule to detect misaligned rows and overlapping cells.
8
+ */
14
9
  export class Grid {
10
+ /** The grid model for the `<tbody>` section. */
11
+ tbodyGrid;
12
+ /** The grid model for the `<tfoot>` section. */
13
+ tfootGrid;
14
+ /** The grid model for the `<thead>` section. */
15
+ theadGrid;
16
+ /** The `<tr>` elements within `<tbody>`. */
17
+ #tbodyRowElements;
18
+ /** The `<tr>` elements within `<tfoot>`. */
19
+ #tfootRowElements;
20
+ /** The `<tr>` elements within `<thead>`. */
21
+ #theadRowElements;
22
+ /**
23
+ * Constructs a grid model from a `<table>` element.
24
+ *
25
+ * @param table - The table element to model.
26
+ */
15
27
  constructor(
16
28
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
17
29
  table) {
18
- _Grid_tbodyRowElements.set(this, void 0);
19
- _Grid_tfootRowElements.set(this, void 0);
20
- _Grid_theadRowElements.set(this, void 0);
21
30
  const thead = findChildren(table, 'thead')[0];
22
31
  const tbody = findChildren(table, 'tbody')[0];
23
32
  const tfoot = findChildren(table, 'tfoot')[0];
24
- __classPrivateFieldSet(this, _Grid_theadRowElements, thead ? findChildren(thead, 'tr') : [], "f");
25
- __classPrivateFieldSet(this, _Grid_tbodyRowElements, tbody ? findChildren(tbody, 'tr') : [], "f");
26
- __classPrivateFieldSet(this, _Grid_tfootRowElements, tfoot ? findChildren(tfoot, 'tr') : [], "f");
27
- this.theadGrid = createGrid(__classPrivateFieldGet(this, _Grid_theadRowElements, "f"));
28
- this.tbodyGrid = createGrid(__classPrivateFieldGet(this, _Grid_tbodyRowElements, "f"));
29
- this.tfootGrid = createGrid(__classPrivateFieldGet(this, _Grid_tfootRowElements, "f"));
33
+ this.#theadRowElements = thead ? findChildren(thead, 'tr') : [];
34
+ this.#tbodyRowElements = tbody ? findChildren(tbody, 'tr') : [];
35
+ this.#tfootRowElements = tfoot ? findChildren(tfoot, 'tr') : [];
36
+ this.theadGrid = createGrid(this.#theadRowElements);
37
+ this.tbodyGrid = createGrid(this.#tbodyRowElements);
38
+ this.tfootGrid = createGrid(this.#tfootRowElements);
30
39
  }
40
+ /**
41
+ * Returns all `<tr>` elements across all table sections in document order.
42
+ *
43
+ * @returns Combined array of row elements from thead, tbody, and tfoot.
44
+ */
31
45
  getAllRowElements() {
32
- return [...__classPrivateFieldGet(this, _Grid_theadRowElements, "f"), ...__classPrivateFieldGet(this, _Grid_tbodyRowElements, "f"), ...__classPrivateFieldGet(this, _Grid_tfootRowElements, "f")];
46
+ return [...this.#theadRowElements, ...this.#tbodyRowElements, ...this.#tfootRowElements];
33
47
  }
48
+ /**
49
+ * Returns all grid rows that contain at least one actual cell element.
50
+ *
51
+ * @returns Combined array of grid rows from thead, tbody, and tfoot, filtered to rows with real cells.
52
+ */
34
53
  getAllRows() {
35
54
  return [
36
55
  ...this.theadGrid.filter(hasElementFilter),
@@ -38,6 +57,13 @@ export class Grid {
38
57
  ...this.tfootGrid.filter(hasElementFilter),
39
58
  ];
40
59
  }
60
+ /**
61
+ * Determines the expected (base) column count for the table.
62
+ *
63
+ * Prefers the thead section if available, then tfoot, then tbody.
64
+ *
65
+ * @returns The base number of columns that rows should have.
66
+ */
41
67
  getBaseColLength() {
42
68
  if (this.theadGrid.length > 0) {
43
69
  return getBaseColLength(this.theadGrid);
@@ -47,27 +73,38 @@ export class Grid {
47
73
  }
48
74
  return getBaseColLength(this.tbodyGrid);
49
75
  }
76
+ /**
77
+ * Returns the grid and row elements for each table section (thead, tbody, tfoot).
78
+ *
79
+ * @returns An array of objects, each containing the section grid and its row elements.
80
+ */
50
81
  getSections() {
51
82
  return [
52
83
  {
53
84
  section: this.theadGrid,
54
- elements: __classPrivateFieldGet(this, _Grid_theadRowElements, "f"),
85
+ elements: this.#theadRowElements,
55
86
  },
56
87
  {
57
88
  section: this.tbodyGrid,
58
- elements: __classPrivateFieldGet(this, _Grid_tbodyRowElements, "f"),
89
+ elements: this.#tbodyRowElements,
59
90
  },
60
91
  {
61
92
  section: this.tfootGrid,
62
- elements: __classPrivateFieldGet(this, _Grid_tfootRowElements, "f"),
93
+ elements: this.#tfootRowElements,
63
94
  },
64
95
  ];
65
96
  }
97
+ /**
98
+ * Checks whether any cell in the table grid has been marked as overlapping.
99
+ *
100
+ * @returns `true` if any cell has the overlap marker `'x'`.
101
+ */
66
102
  hasOverlapped() {
67
103
  return (this.tbodyGrid.some(row => row.includes('x')) ||
68
104
  this.tfootGrid.some(row => row.includes('x')) ||
69
105
  this.theadGrid.some(row => row.includes('x')));
70
106
  }
107
+ /** Logs the grid data for all three table sections to the console (for debugging). */
71
108
  log() {
72
109
  // eslint-disable-next-line no-console
73
110
  console.table(this.theadGrid);
@@ -77,7 +114,16 @@ export class Grid {
77
114
  console.table(this.tfootGrid);
78
115
  }
79
116
  }
80
- _Grid_tbodyRowElements = new WeakMap(), _Grid_tfootRowElements = new WeakMap(), _Grid_theadRowElements = new WeakMap();
117
+ /**
118
+ * Detects a rowspan that extends beyond the available rows in a table section.
119
+ *
120
+ * When a grid row exists without a corresponding row element, it indicates
121
+ * a rowspan overflow. Returns the offending `rowspan` attribute node if found.
122
+ *
123
+ * @param rows - The grid rows for a single table section.
124
+ * @param rowElements - The `<tr>` elements for the section.
125
+ * @returns An object containing the overflowing `rowSpan` attribute, or `null`.
126
+ */
81
127
  export function getOverflowRowSpan(rows,
82
128
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
83
129
  rowElements) {
@@ -114,10 +160,26 @@ rowElements) {
114
160
  }
115
161
  return null;
116
162
  }
163
+ /**
164
+ * Maps each cell in a grid row to its source cell index (for cells that correspond
165
+ * to actual `<td>`/`<th>` elements), or `null` for spanned cells.
166
+ *
167
+ * @param row - A single row of cell types from the grid model.
168
+ * @returns An array of cell indices or `null` values, one per grid column.
169
+ */
117
170
  export function getIndexes(row) {
118
171
  let indexCounter = 0;
119
172
  return row.map(col => (col === '●' || col === '◎' ? indexCounter++ : null));
120
173
  }
174
+ /**
175
+ * Calculates the base (expected) column count from a section grid.
176
+ *
177
+ * For grids with 3 or more rows, uses the row length closest to the
178
+ * average to handle minor variations from spans. Otherwise uses the first row.
179
+ *
180
+ * @param grid - The 2D grid for a table section.
181
+ * @returns The base number of columns.
182
+ */
121
183
  function getBaseColLength(grid) {
122
184
  let baseColLength;
123
185
  if (grid.length >= 3) {
@@ -136,6 +198,16 @@ function getBaseColLength(grid) {
136
198
  }
137
199
  return baseColLength;
138
200
  }
201
+ /**
202
+ * Builds a 2D grid model from an array of `<tr>` elements.
203
+ *
204
+ * Processes each row's `<th>` and `<td>` cells, expanding `colspan` and `rowspan`
205
+ * attributes into the grid. Marks cells with appropriate cell types including
206
+ * span origins, continuations, and overlaps.
207
+ *
208
+ * @param rows - The `<tr>` elements to convert into a grid.
209
+ * @returns A 2D array of cell types representing the table section layout.
210
+ */
139
211
  function createGrid(
140
212
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
141
213
  rows) {
@@ -181,6 +253,12 @@ rows) {
181
253
  }
182
254
  return matrix;
183
255
  }
256
+ /**
257
+ * Filter predicate that returns `true` if a grid row contains at least one actual cell element.
258
+ *
259
+ * @param row - A single row of cell types.
260
+ * @returns `true` if the row has a regular or span-origin cell.
261
+ */
184
262
  function hasElementFilter(row) {
185
263
  return row.some(cell => cell === '●' || cell === '◎');
186
264
  }
@@ -1,2 +1,10 @@
1
+ /**
2
+ * Rule that validates table row and column alignment.
3
+ *
4
+ * Builds a grid model of each `<table>` and checks that all rows have a
5
+ * consistent number of columns, that `colspan`/`rowspan` attributes do not
6
+ * cause cell overlap, and that `rowspan` values do not exceed the available
7
+ * rows in their table section.
8
+ */
1
9
  declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<boolean, undefined>>;
2
10
  export default _default;
@@ -2,6 +2,14 @@ import { createRule } from '@markuplint/ml-core';
2
2
  import { findChildren } from './find-children.js';
3
3
  import { Grid, getIndexes, getOverflowRowSpan } from './grid.js';
4
4
  import meta from './meta.js';
5
+ /**
6
+ * Rule that validates table row and column alignment.
7
+ *
8
+ * Builds a grid model of each `<table>` and checks that all rows have a
9
+ * consistent number of columns, that `colspan`/`rowspan` attributes do not
10
+ * cause cell overlap, and that `rowspan` values do not exceed the available
11
+ * rows in their table section.
12
+ */
5
13
  export default createRule({
6
14
  meta,
7
15
  defaultSeverity: 'warning',
@@ -1,3 +1,4 @@
1
+ /** Rule metadata for the `table-row-column-alignment` 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 `table-row-column-alignment` rule, categorized as accessibility. */
1
2
  export default {
2
3
  category: 'a11y',
3
4
  };
@@ -1,2 +1,12 @@
1
+ /** A grid cell coordinate key in "row:column" format. */
1
2
  export type Key = `${number}:${number}`;
3
+ /**
4
+ * Represents the type of a cell in the table grid model.
5
+ *
6
+ * - `'●'` - A regular cell (single-span).
7
+ * - `'◎'` - The origin cell of a colspan or rowspan.
8
+ * - `'↓'` - A cell occupied by a rowspan from a row above.
9
+ * - `'→'` - A cell occupied by a colspan from a column to the left.
10
+ * - `'x'` - An overlapping cell caused by conflicting spans.
11
+ */
2
12
  export type CellType = '●' | '◎' | '↓' | '→' | 'x';
@@ -1,10 +1,26 @@
1
+ /** An array of characters recognized as bullet-like symbols indicating list items. */
1
2
  type Bullets = readonly string[];
3
+ /**
4
+ * Configuration options for the use-list rule.
5
+ */
2
6
  type Options = {
7
+ /** Bullet characters that require a space after them to be considered list items. */
3
8
  spaceNeededBullets?: string[];
9
+ /** Whether to report text nodes with no preceding sibling. */
4
10
  noPrev?: boolean;
11
+ /** Whether to report text nodes preceded by an element node. */
5
12
  prevElement?: boolean;
13
+ /** Whether to report text nodes preceded by a comment node. */
6
14
  prevComment?: boolean;
15
+ /** Whether to report text nodes preceded by a preprocessor block. */
7
16
  prevCodeBlock?: boolean;
8
17
  };
18
+ /**
19
+ * Rule that suggests using `<li>` elements instead of text with bullet characters.
20
+ *
21
+ * Detects text nodes that start with bullet-like characters (Unicode bullets,
22
+ * markdown-style dashes/asterisks, etc.) and recommends converting them into
23
+ * proper list markup using `<li>` elements.
24
+ */
9
25
  declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<Bullets, Options>>;
10
26
  export default _default;
@@ -1,6 +1,13 @@
1
1
  import { createRule } from '@markuplint/ml-core';
2
2
  import { decodeEntities } from '@markuplint/shared';
3
3
  import meta from './meta.js';
4
+ /**
5
+ * Rule that suggests using `<li>` elements instead of text with bullet characters.
6
+ *
7
+ * Detects text nodes that start with bullet-like characters (Unicode bullets,
8
+ * markdown-style dashes/asterisks, etc.) and recommends converting them into
9
+ * proper list markup using `<li>` elements.
10
+ */
4
11
  export default createRule({
5
12
  meta: meta,
6
13
  defaultValue: [
@@ -94,6 +101,19 @@ export default createRule({
94
101
  });
95
102
  },
96
103
  });
104
+ /**
105
+ * Determines whether a text string appears to be a list item based on its first character.
106
+ *
107
+ * Checks if the text starts with a recognized bullet character. For bullets
108
+ * that require a trailing space (e.g., `-`, `*`, `+`), also verifies that the
109
+ * second character is whitespace. Consecutive identical characters are not
110
+ * treated as bullets (e.g., `--` is not a list item).
111
+ *
112
+ * @param text - The text content to evaluate.
113
+ * @param bullets - Characters recognized as bullet symbols.
114
+ * @param spaceNeededBullets - Bullet characters that require a following space.
115
+ * @returns `true` if the text appears to be a list item.
116
+ */
97
117
  function isMayListItem(text, bullets, spaceNeededBullets) {
98
118
  const textArray = [...text];
99
119
  const firstLetter = textArray[0] ?? '';
@@ -1,3 +1,4 @@
1
+ /** Rule metadata for the `use-list` 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 `use-list` rule, categorized as accessibility. */
1
2
  export default {
2
3
  category: 'a11y',
3
4
  };