@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.
- package/CHANGELOG.md +63 -2
- package/SKILL.md +76 -0
- package/lib/attr-check.d.ts +46 -7
- package/lib/attr-check.js +42 -8
- package/lib/attr-duplication/index.d.ts +7 -0
- package/lib/attr-duplication/index.js +7 -0
- package/lib/attr-duplication/meta.d.ts +1 -0
- package/lib/attr-duplication/meta.js +1 -0
- package/lib/attr-value-quotes/index.d.ts +10 -0
- package/lib/attr-value-quotes/index.js +10 -0
- package/lib/attr-value-quotes/meta.d.ts +1 -0
- package/lib/attr-value-quotes/meta.js +1 -0
- package/lib/case-sensitive-attr-name/index.d.ts +9 -0
- package/lib/case-sensitive-attr-name/index.js +8 -0
- package/lib/case-sensitive-attr-name/meta.d.ts +1 -0
- package/lib/case-sensitive-attr-name/meta.js +1 -0
- package/lib/case-sensitive-tag-name/index.d.ts +9 -0
- package/lib/case-sensitive-tag-name/index.js +8 -0
- package/lib/case-sensitive-tag-name/meta.d.ts +1 -0
- package/lib/case-sensitive-tag-name/meta.js +1 -0
- package/lib/character-reference/index.d.ts +9 -0
- package/lib/character-reference/index.js +20 -0
- package/lib/character-reference/meta.d.ts +1 -0
- package/lib/character-reference/meta.js +1 -0
- package/lib/class-naming/index.d.ts +9 -0
- package/lib/class-naming/index.js +8 -0
- package/lib/class-naming/meta.d.ts +1 -0
- package/lib/class-naming/meta.js +1 -0
- package/lib/correct-aspect-ratio/index.d.ts +5 -0
- package/lib/correct-aspect-ratio/index.js +80 -0
- package/lib/correct-aspect-ratio/meta.d.ts +4 -0
- package/lib/correct-aspect-ratio/meta.js +3 -0
- package/lib/correct-aspect-ratio/resolve-image-size.d.ts +24 -0
- package/lib/correct-aspect-ratio/resolve-image-size.js +165 -0
- package/lib/create-message.d.ts +29 -2
- package/lib/create-message.js +60 -2
- package/lib/debug.d.ts +9 -0
- package/lib/debug.js +5 -0
- package/lib/deprecated-attr/index.d.ts +6 -0
- package/lib/deprecated-attr/index.js +6 -0
- package/lib/deprecated-attr/meta.d.ts +1 -0
- package/lib/deprecated-attr/meta.js +1 -0
- package/lib/deprecated-element/index.d.ts +6 -0
- package/lib/deprecated-element/index.js +8 -2
- package/lib/deprecated-element/meta.d.ts +1 -0
- package/lib/deprecated-element/meta.js +1 -0
- package/lib/disallowed-element/index.d.ts +7 -0
- package/lib/disallowed-element/index.js +7 -0
- package/lib/disallowed-element/meta.d.ts +1 -0
- package/lib/disallowed-element/meta.js +1 -0
- package/lib/doctype/index.d.ts +10 -0
- package/lib/doctype/index.js +8 -0
- package/lib/doctype/meta.d.ts +1 -0
- package/lib/doctype/meta.js +1 -0
- package/lib/end-tag/index.d.ts +7 -0
- package/lib/end-tag/index.js +8 -1
- package/lib/end-tag/meta.d.ts +1 -0
- package/lib/end-tag/meta.js +1 -0
- package/lib/heading-levels/index.d.ts +6 -0
- package/lib/heading-levels/index.js +6 -1
- package/lib/heading-levels/meta.d.ts +1 -0
- package/lib/heading-levels/meta.js +1 -0
- package/lib/helpers.d.ts +105 -0
- package/lib/helpers.js +112 -16
- package/lib/id-duplication/index.d.ts +7 -0
- package/lib/id-duplication/index.js +7 -0
- package/lib/id-duplication/meta.d.ts +1 -0
- package/lib/id-duplication/meta.js +1 -0
- package/lib/index.d.ts +43 -41
- package/lib/index.js +24 -0
- package/lib/ineffective-attr/index.d.ts +7 -0
- package/lib/ineffective-attr/index.js +7 -0
- package/lib/ineffective-attr/meta.d.ts +1 -0
- package/lib/ineffective-attr/meta.js +1 -0
- package/lib/invalid-attr/index.d.ts +29 -21
- package/lib/invalid-attr/index.js +60 -109
- package/lib/invalid-attr/meta.d.ts +1 -0
- package/lib/invalid-attr/meta.js +1 -0
- package/lib/label-has-control/index.d.ts +7 -0
- package/lib/label-has-control/index.js +11 -0
- package/lib/label-has-control/meta.d.ts +1 -0
- package/lib/label-has-control/meta.js +1 -0
- package/lib/landmark-roles/index.d.ts +14 -0
- package/lib/landmark-roles/index.js +21 -0
- package/lib/landmark-roles/meta.d.ts +1 -0
- package/lib/landmark-roles/meta.js +1 -0
- package/lib/link-types/index.d.ts +10 -0
- package/lib/link-types/index.js +180 -0
- package/lib/link-types/meta.d.ts +5 -0
- package/lib/link-types/meta.js +4 -0
- package/lib/neighbor-popovers/index.d.ts +8 -0
- package/lib/neighbor-popovers/index.js +28 -6
- package/lib/neighbor-popovers/meta.d.ts +1 -0
- package/lib/neighbor-popovers/meta.js +1 -0
- package/lib/no-ambiguous-navigable-target-names/index.d.ts +8 -0
- package/lib/no-ambiguous-navigable-target-names/index.js +8 -0
- package/lib/no-ambiguous-navigable-target-names/meta.d.ts +1 -0
- package/lib/no-ambiguous-navigable-target-names/meta.js +1 -0
- package/lib/no-boolean-attr-value/index.d.ts +7 -0
- package/lib/no-boolean-attr-value/index.js +7 -0
- package/lib/no-boolean-attr-value/meta.d.ts +1 -0
- package/lib/no-boolean-attr-value/meta.js +1 -0
- package/lib/no-consecutive-br/index.d.ts +8 -0
- package/lib/no-consecutive-br/index.js +8 -0
- package/lib/no-consecutive-br/meta.d.ts +1 -0
- package/lib/no-consecutive-br/meta.js +1 -0
- package/lib/no-default-value/index.d.ts +7 -0
- package/lib/no-default-value/index.js +7 -0
- package/lib/no-default-value/meta.d.ts +1 -0
- package/lib/no-default-value/meta.js +1 -0
- package/lib/no-duplicate-dt/index.d.ts +6 -0
- package/lib/no-duplicate-dt/index.js +6 -0
- package/lib/no-duplicate-dt/meta.d.ts +1 -0
- package/lib/no-duplicate-dt/meta.js +1 -0
- package/lib/no-empty-palpable-content/index.d.ts +13 -0
- package/lib/no-empty-palpable-content/index.js +12 -0
- package/lib/no-empty-palpable-content/meta.d.ts +1 -0
- package/lib/no-empty-palpable-content/meta.js +1 -0
- package/lib/no-hard-code-id/index.d.ts +8 -0
- package/lib/no-hard-code-id/index.js +8 -0
- package/lib/no-hard-code-id/meta.d.ts +1 -0
- package/lib/no-hard-code-id/meta.js +1 -0
- package/lib/no-orphaned-end-tag/index.d.ts +7 -0
- package/lib/no-orphaned-end-tag/index.js +8 -2
- package/lib/no-orphaned-end-tag/meta.d.ts +1 -0
- package/lib/no-orphaned-end-tag/meta.js +1 -0
- package/lib/no-refer-to-non-existent-id/index.d.ts +9 -1
- package/lib/no-refer-to-non-existent-id/index.js +11 -2
- package/lib/no-refer-to-non-existent-id/meta.d.ts +1 -0
- package/lib/no-refer-to-non-existent-id/meta.js +1 -0
- package/lib/no-unsupported-features/compat-data.d.ts +71 -0
- package/lib/no-unsupported-features/compat-data.js +204 -0
- package/lib/no-unsupported-features/index.d.ts +24 -0
- package/lib/no-unsupported-features/index.js +139 -0
- package/lib/no-unsupported-features/meta.d.ts +5 -0
- package/lib/no-unsupported-features/meta.js +4 -0
- package/lib/no-unsupported-features/resolve-browsers.d.ts +30 -0
- package/lib/no-unsupported-features/resolve-browsers.js +125 -0
- package/lib/no-use-event-handler-attr/index.d.ts +10 -0
- package/lib/no-use-event-handler-attr/index.js +8 -0
- package/lib/no-use-event-handler-attr/meta.d.ts +1 -0
- package/lib/no-use-event-handler-attr/meta.js +1 -0
- package/lib/permitted-contents/choice.d.ts +17 -0
- package/lib/permitted-contents/choice.js +31 -1
- package/lib/permitted-contents/complex-branch.d.ts +11 -7
- package/lib/permitted-contents/complex-branch.js +11 -7
- package/lib/permitted-contents/content-model.d.ts +11 -0
- package/lib/permitted-contents/content-model.js +32 -0
- package/lib/permitted-contents/count-pattern.d.ts +13 -7
- package/lib/permitted-contents/count-pattern.js +24 -8
- package/lib/permitted-contents/debug.browser.d.ts +12 -0
- package/lib/permitted-contents/debug.browser.js +12 -0
- package/lib/permitted-contents/debug.d.ts +12 -0
- package/lib/permitted-contents/debug.js +12 -0
- package/lib/permitted-contents/index.d.ts +12 -0
- package/lib/permitted-contents/index.js +21 -0
- package/lib/permitted-contents/matches-selector.d.ts +19 -0
- package/lib/permitted-contents/matches-selector.js +33 -0
- package/lib/permitted-contents/meta.d.ts +1 -0
- package/lib/permitted-contents/meta.js +1 -0
- package/lib/permitted-contents/order.d.ts +14 -7
- package/lib/permitted-contents/order.js +15 -8
- package/lib/permitted-contents/recursive-branch.d.ts +17 -0
- package/lib/permitted-contents/recursive-branch.js +17 -0
- package/lib/permitted-contents/represent-transparent-nodes.d.ts +54 -0
- package/lib/permitted-contents/represent-transparent-nodes.js +119 -12
- package/lib/permitted-contents/start.d.ts +12 -6
- package/lib/permitted-contents/start.js +12 -6
- package/lib/permitted-contents/transparent.d.ts +12 -0
- package/lib/permitted-contents/transparent.js +12 -0
- package/lib/permitted-contents/types.d.ts +67 -0
- package/lib/permitted-contents/utils.d.ts +160 -0
- package/lib/permitted-contents/utils.js +216 -35
- package/lib/placeholder-label-option/index.d.ts +7 -0
- package/lib/placeholder-label-option/index.js +19 -15
- package/lib/placeholder-label-option/meta.d.ts +1 -0
- package/lib/placeholder-label-option/meta.js +1 -0
- package/lib/redundant-accessible-name/index.d.ts +10 -0
- package/lib/redundant-accessible-name/index.js +123 -0
- package/lib/redundant-accessible-name/meta.d.ts +5 -0
- package/lib/redundant-accessible-name/meta.js +4 -0
- package/lib/require-accessible-name/index.d.ts +10 -4
- package/lib/require-accessible-name/index.js +13 -5
- package/lib/require-accessible-name/meta.d.ts +1 -0
- package/lib/require-accessible-name/meta.js +1 -0
- package/lib/require-datetime/index.d.ts +12 -0
- package/lib/require-datetime/index.js +8 -0
- package/lib/require-datetime/meta.d.ts +1 -0
- package/lib/require-datetime/meta.js +1 -0
- package/lib/require-datetime/types.d.ts +7 -0
- package/lib/require-datetime/utils.d.ts +18 -5
- package/lib/require-datetime/utils.js +53 -9
- package/lib/require-dialog-autofocus/index.d.ts +22 -0
- package/lib/require-dialog-autofocus/index.js +62 -0
- package/lib/require-dialog-autofocus/meta.d.ts +5 -0
- package/lib/require-dialog-autofocus/meta.js +4 -0
- package/lib/required-attr/index.d.ts +21 -1
- package/lib/required-attr/index.js +34 -4
- package/lib/required-attr/meta.d.ts +1 -0
- package/lib/required-attr/meta.js +1 -0
- package/lib/required-element/index.d.ts +14 -0
- package/lib/required-element/index.js +14 -2
- package/lib/required-element/meta.d.ts +1 -0
- package/lib/required-element/meta.js +1 -0
- package/lib/required-h1/index.d.ts +12 -0
- package/lib/required-h1/index.js +7 -0
- package/lib/required-h1/meta.d.ts +1 -0
- package/lib/required-h1/meta.js +1 -0
- package/lib/srcset-sizes-constraint/index.d.ts +6 -0
- package/lib/srcset-sizes-constraint/index.js +108 -0
- package/lib/srcset-sizes-constraint/meta.d.ts +5 -0
- package/lib/srcset-sizes-constraint/meta.js +4 -0
- package/lib/srcset-sizes-constraint/parse-srcset.d.ts +40 -0
- package/lib/srcset-sizes-constraint/parse-srcset.js +56 -0
- package/lib/table-row-column-alignment/find-children.d.ts +10 -0
- package/lib/table-row-column-alignment/find-children.js +11 -0
- package/lib/table-row-column-alignment/grid.d.ts +60 -0
- package/lib/table-row-column-alignment/grid.js +104 -26
- package/lib/table-row-column-alignment/index.d.ts +8 -0
- package/lib/table-row-column-alignment/index.js +8 -0
- package/lib/table-row-column-alignment/meta.d.ts +1 -0
- package/lib/table-row-column-alignment/meta.js +1 -0
- package/lib/table-row-column-alignment/types.d.ts +10 -0
- package/lib/use-list/index.d.ts +16 -0
- package/lib/use-list/index.js +20 -0
- package/lib/use-list/meta.d.ts +1 -0
- package/lib/use-list/meta.js +1 -0
- package/lib/wai-aria/checkings/abstract-role.d.ts +9 -0
- package/lib/wai-aria/checkings/abstract-role.js +14 -2
- package/lib/wai-aria/checkings/default-value.d.ts +11 -0
- package/lib/wai-aria/checkings/default-value.js +11 -0
- package/lib/wai-aria/checkings/deprecated-props.d.ts +11 -0
- package/lib/wai-aria/checkings/deprecated-props.js +11 -0
- package/lib/wai-aria/checkings/deprecated-role.d.ts +10 -0
- package/lib/wai-aria/checkings/deprecated-role.js +10 -0
- package/lib/wai-aria/checkings/disallowed-prop.d.ts +14 -0
- package/lib/wai-aria/checkings/disallowed-prop.js +19 -2
- package/lib/wai-aria/checkings/implicit-props.d.ts +13 -0
- package/lib/wai-aria/checkings/implicit-props.js +13 -0
- package/lib/wai-aria/checkings/implicit-role.d.ts +9 -0
- package/lib/wai-aria/checkings/implicit-role.js +14 -2
- package/lib/wai-aria/checkings/interaction-in-hidden.d.ts +9 -0
- package/lib/wai-aria/checkings/interaction-in-hidden.js +15 -4
- package/lib/wai-aria/checkings/no-global-prop.d.ts +10 -0
- package/lib/wai-aria/checkings/no-global-prop.js +10 -0
- package/lib/wai-aria/checkings/non-existent-role.d.ts +11 -0
- package/lib/wai-aria/checkings/non-existent-role.js +19 -2
- package/lib/wai-aria/checkings/permitted-roles.d.ts +10 -0
- package/lib/wai-aria/checkings/permitted-roles.js +15 -2
- package/lib/wai-aria/checkings/presentational-children.d.ts +7 -1
- package/lib/wai-aria/checkings/presentational-children.js +17 -3
- package/lib/wai-aria/checkings/required-accessibility-parent-role.d.ts +25 -0
- package/lib/wai-aria/checkings/required-accessibility-parent-role.js +56 -0
- package/lib/wai-aria/checkings/required-owned-elements.d.ts +10 -1
- package/lib/wai-aria/checkings/required-owned-elements.js +69 -25
- package/lib/wai-aria/checkings/required-prop.d.ts +13 -0
- package/lib/wai-aria/checkings/required-prop.js +18 -2
- package/lib/wai-aria/checkings/value.d.ts +23 -0
- package/lib/wai-aria/checkings/value.js +32 -0
- package/lib/wai-aria/index.d.ts +10 -0
- package/lib/wai-aria/index.js +22 -4
- package/lib/wai-aria/meta.d.ts +1 -0
- package/lib/wai-aria/meta.js +1 -0
- package/lib/wai-aria/types.d.ts +25 -1
- package/package.json +16 -11
- 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,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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.theadGrid = createGrid(
|
|
28
|
-
this.tbodyGrid = createGrid(
|
|
29
|
-
this.tfootGrid = createGrid(
|
|
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 [...
|
|
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:
|
|
85
|
+
elements: this.#theadRowElements,
|
|
55
86
|
},
|
|
56
87
|
{
|
|
57
88
|
section: this.tbodyGrid,
|
|
58
|
-
elements:
|
|
89
|
+
elements: this.#tbodyRowElements,
|
|
59
90
|
},
|
|
60
91
|
{
|
|
61
92
|
section: this.tfootGrid,
|
|
62
|
-
elements:
|
|
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
|
-
|
|
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,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';
|
package/lib/use-list/index.d.ts
CHANGED
|
@@ -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;
|
package/lib/use-list/index.js
CHANGED
|
@@ -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] ?? '';
|
package/lib/use-list/meta.d.ts
CHANGED
package/lib/use-list/meta.js
CHANGED