@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,80 @@
|
|
|
1
|
+
import { createRule } from '@markuplint/ml-core';
|
|
2
|
+
import meta from './meta.js';
|
|
3
|
+
import { getImageDimensions } from './resolve-image-size.js';
|
|
4
|
+
export default createRule({
|
|
5
|
+
meta: meta,
|
|
6
|
+
defaultSeverity: 'warning',
|
|
7
|
+
defaultOptions: {},
|
|
8
|
+
async verify({ document, report }) {
|
|
9
|
+
await document.walkOn('Element', async (el) => {
|
|
10
|
+
const localName = el.localName;
|
|
11
|
+
// Only check img and source (inside picture) elements
|
|
12
|
+
if (localName !== 'img' && localName !== 'source') {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
// source is only relevant inside <picture>
|
|
16
|
+
if (localName === 'source' && el.parentElement?.localName !== 'picture') {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (el.hasSpreadAttr) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const widthAttr = el.getAttributeNode('width');
|
|
23
|
+
const heightAttr = el.getAttributeNode('height');
|
|
24
|
+
if (!widthAttr || !heightAttr) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (widthAttr.isDynamicValue || heightAttr.isDynamicValue) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// Resolve the image URL from src (img) or srcset (source)
|
|
31
|
+
let src;
|
|
32
|
+
if (localName === 'img') {
|
|
33
|
+
const srcAttr = el.getAttributeNode('src');
|
|
34
|
+
if (!srcAttr || srcAttr.isDynamicValue) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
src = srcAttr.value;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const srcsetAttr = el.getAttributeNode('srcset');
|
|
41
|
+
if (!srcsetAttr || srcsetAttr.isDynamicValue) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const extracted = extractFirstSrcsetUrl(srcsetAttr.value);
|
|
45
|
+
if (!extracted) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
src = extracted;
|
|
49
|
+
}
|
|
50
|
+
const widthStr = widthAttr.value;
|
|
51
|
+
const heightStr = heightAttr.value;
|
|
52
|
+
// Parse width/height as integers; skip if non-numeric
|
|
53
|
+
const attrWidth = Number.parseInt(widthStr, 10);
|
|
54
|
+
const attrHeight = Number.parseInt(heightStr, 10);
|
|
55
|
+
if (!Number.isFinite(attrWidth) || !Number.isFinite(attrHeight) || attrWidth <= 0 || attrHeight <= 0) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const documentRoot = el.rule.options?.documentRoot;
|
|
59
|
+
const dimensions = await getImageDimensions(src, documentRoot, document.filename);
|
|
60
|
+
if (!dimensions) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const { width: actualWidth, height: actualHeight } = dimensions;
|
|
64
|
+
// Cross-multiplication comparison to avoid floating-point errors
|
|
65
|
+
if (attrWidth * actualHeight !== attrHeight * actualWidth) {
|
|
66
|
+
report({
|
|
67
|
+
scope: el,
|
|
68
|
+
message: `The aspect ratio of the image (${actualWidth}:${actualHeight}) does not match the width/height attributes (${widthStr}:${heightStr})`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
function extractFirstSrcsetUrl(srcset) {
|
|
75
|
+
const first = srcset.trim().split(',')[0]?.trim();
|
|
76
|
+
if (!first) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return first.split(/\s+/)[0] ?? null;
|
|
80
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
interface ImageDimensions {
|
|
2
|
+
readonly width: number;
|
|
3
|
+
readonly height: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Reset module-level caches. **Only for testing.**
|
|
7
|
+
*/
|
|
8
|
+
export declare function _resetCacheForTesting(): void;
|
|
9
|
+
/**
|
|
10
|
+
* Get the dimensions of an image file, using a cache keyed by absolute path,
|
|
11
|
+
* file size, and the original `src` value.
|
|
12
|
+
*
|
|
13
|
+
* Query strings and fragments in `src` are stripped when resolving the file
|
|
14
|
+
* path, but the original `src` is kept in the cache key so that different
|
|
15
|
+
* cache-busting suffixes produce separate cache entries (mirroring browser
|
|
16
|
+
* cache-busting semantics).
|
|
17
|
+
*
|
|
18
|
+
* @param src - The `src` attribute value
|
|
19
|
+
* @param documentRoot - Root directory for absolute paths
|
|
20
|
+
* @param documentFilename - The filename of the document being linted
|
|
21
|
+
* @returns The image dimensions, or `null` if the image cannot be resolved or read
|
|
22
|
+
*/
|
|
23
|
+
export declare function getImageDimensions(src: string, documentRoot: string | undefined, documentFilename: string | undefined): Promise<ImageDimensions | null>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { readFile, stat, mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { imageSize } from 'image-size';
|
|
6
|
+
// In-memory cache: key = "absolutePath:fileSize:src"
|
|
7
|
+
const memoryCache = new Map();
|
|
8
|
+
let cacheLoaded = false;
|
|
9
|
+
let _cacheDir;
|
|
10
|
+
function getCacheDir() {
|
|
11
|
+
if (_cacheDir != null) {
|
|
12
|
+
return _cacheDir;
|
|
13
|
+
}
|
|
14
|
+
const req = createRequire(import.meta.url);
|
|
15
|
+
const { version } = req('../../package.json');
|
|
16
|
+
_cacheDir = path.join(tmpdir(), `markuplint-v${version}`);
|
|
17
|
+
return _cacheDir;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Reset module-level caches. **Only for testing.**
|
|
21
|
+
*/
|
|
22
|
+
export function _resetCacheForTesting() {
|
|
23
|
+
memoryCache.clear();
|
|
24
|
+
cacheLoaded = false;
|
|
25
|
+
_cacheDir = undefined;
|
|
26
|
+
}
|
|
27
|
+
function getCacheFilePath() {
|
|
28
|
+
return path.join(getCacheDir(), 'image-size-cache.json');
|
|
29
|
+
}
|
|
30
|
+
async function loadCacheFromDisk() {
|
|
31
|
+
if (cacheLoaded) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
cacheLoaded = true;
|
|
35
|
+
let raw;
|
|
36
|
+
try {
|
|
37
|
+
raw = await readFile(getCacheFilePath(), 'utf8');
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
let data;
|
|
43
|
+
try {
|
|
44
|
+
data = JSON.parse(raw);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
for (const [key, value] of Object.entries(data)) {
|
|
50
|
+
memoryCache.set(key, value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function saveCacheToDisk() {
|
|
54
|
+
const dir = getCacheDir();
|
|
55
|
+
try {
|
|
56
|
+
await mkdir(dir, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const obj = {};
|
|
62
|
+
for (const [key, value] of memoryCache) {
|
|
63
|
+
obj[key] = value;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
await writeFile(getCacheFilePath(), JSON.stringify(obj));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Ignore write failures
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function stripQueryAndFragment(src) {
|
|
73
|
+
const qIndex = src.indexOf('?');
|
|
74
|
+
const hIndex = src.indexOf('#');
|
|
75
|
+
let end = src.length;
|
|
76
|
+
if (qIndex !== -1)
|
|
77
|
+
end = Math.min(end, qIndex);
|
|
78
|
+
if (hIndex !== -1)
|
|
79
|
+
end = Math.min(end, hIndex);
|
|
80
|
+
return src.slice(0, end);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Resolve the absolute path of an image from its `src` attribute value.
|
|
84
|
+
*
|
|
85
|
+
* Query strings (`?...`) and fragments (`#...`) are stripped before resolving
|
|
86
|
+
* the file path so that cache-busting suffixes do not prevent file lookup.
|
|
87
|
+
*
|
|
88
|
+
* @param src - The `src` attribute value
|
|
89
|
+
* @param documentRoot - Root directory for absolute paths (defaults to cwd)
|
|
90
|
+
* @param documentFilename - The filename of the document being linted
|
|
91
|
+
* @returns The absolute path, or `null` if the path cannot be resolved (remote URLs, data URIs)
|
|
92
|
+
*/
|
|
93
|
+
function resolveImagePath(src, documentRoot, documentFilename) {
|
|
94
|
+
// Skip remote URLs and data URIs
|
|
95
|
+
if (/^https?:\/\//i.test(src) || /^data:/i.test(src)) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const cleanSrc = stripQueryAndFragment(src);
|
|
99
|
+
// Absolute path (starts with /)
|
|
100
|
+
if (cleanSrc.startsWith('/')) {
|
|
101
|
+
return path.join(documentRoot ?? process.cwd(), cleanSrc);
|
|
102
|
+
}
|
|
103
|
+
// Relative path
|
|
104
|
+
if (documentFilename) {
|
|
105
|
+
return path.resolve(path.dirname(documentFilename), cleanSrc);
|
|
106
|
+
}
|
|
107
|
+
return path.join(documentRoot ?? process.cwd(), cleanSrc);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get the dimensions of an image file, using a cache keyed by absolute path,
|
|
111
|
+
* file size, and the original `src` value.
|
|
112
|
+
*
|
|
113
|
+
* Query strings and fragments in `src` are stripped when resolving the file
|
|
114
|
+
* path, but the original `src` is kept in the cache key so that different
|
|
115
|
+
* cache-busting suffixes produce separate cache entries (mirroring browser
|
|
116
|
+
* cache-busting semantics).
|
|
117
|
+
*
|
|
118
|
+
* @param src - The `src` attribute value
|
|
119
|
+
* @param documentRoot - Root directory for absolute paths
|
|
120
|
+
* @param documentFilename - The filename of the document being linted
|
|
121
|
+
* @returns The image dimensions, or `null` if the image cannot be resolved or read
|
|
122
|
+
*/
|
|
123
|
+
export async function getImageDimensions(src, documentRoot, documentFilename) {
|
|
124
|
+
const absolutePath = resolveImagePath(src, documentRoot, documentFilename);
|
|
125
|
+
if (absolutePath == null) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
await loadCacheFromDisk();
|
|
129
|
+
// Get file size for cache key
|
|
130
|
+
let fileSize;
|
|
131
|
+
try {
|
|
132
|
+
const fileStat = await stat(absolutePath);
|
|
133
|
+
fileSize = fileStat.size;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
const cacheKey = `${absolutePath}:${fileSize}:${src}`;
|
|
139
|
+
const cached = memoryCache.get(cacheKey);
|
|
140
|
+
if (cached) {
|
|
141
|
+
return cached;
|
|
142
|
+
}
|
|
143
|
+
// Read and measure the image
|
|
144
|
+
let buffer;
|
|
145
|
+
try {
|
|
146
|
+
buffer = await readFile(absolutePath);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
let result;
|
|
152
|
+
try {
|
|
153
|
+
result = imageSize(buffer);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
if (result.width == null || result.height == null) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const dimensions = { width: result.width, height: result.height };
|
|
162
|
+
memoryCache.set(cacheKey, dimensions);
|
|
163
|
+
await saveCacheToDisk();
|
|
164
|
+
return dimensions;
|
|
165
|
+
}
|
package/lib/create-message.d.ts
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
import type { Translator } from '@markuplint/i18n';
|
|
2
2
|
import type { AttributeType } from '@markuplint/ml-spec';
|
|
3
|
-
import type { UnmatchedResult } from '@markuplint/types';
|
|
3
|
+
import type { UnmatchedResult, Type } from '@markuplint/types';
|
|
4
4
|
import type { ReadonlyDeep } from 'type-fest';
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Builds a human-readable error message explaining why an attribute value
|
|
7
|
+
* does not match its expected type. Handles both plain types and list types
|
|
8
|
+
* (space-separated or comma-separated).
|
|
9
|
+
*
|
|
10
|
+
* @param t - The i18n translator for generating localized messages
|
|
11
|
+
* @param baseTarget - A human-readable description of the attribute being validated
|
|
12
|
+
* (e.g. `'the "href" attribute'`)
|
|
13
|
+
* @param type - The attribute type definition that the value was validated against
|
|
14
|
+
* @param matches - The result object from a failed type check, containing
|
|
15
|
+
* details about the mismatch (reason, candidate, part name, etc.)
|
|
16
|
+
* @returns A localized error message describing the expected value
|
|
17
|
+
*/
|
|
18
|
+
export declare function createMessageValueExpected(t: Translator, baseTarget: string, type: ReadonlyDeep<AttributeType | Type>, matches: UnmatchedResult): string;
|
|
19
|
+
/**
|
|
20
|
+
* Assembles the detailed portion of a value-expected error message by
|
|
21
|
+
* inspecting the mismatch reason and composing reason, expectation,
|
|
22
|
+
* candidate suggestion, and fallback parts into a single string.
|
|
23
|
+
*
|
|
24
|
+
* This is an internal helper exposed for testing and advanced use.
|
|
25
|
+
* The double-underscore prefix indicates it is not part of the stable public API.
|
|
26
|
+
*
|
|
27
|
+
* @param t - The i18n translator for generating localized messages
|
|
28
|
+
* @param baseTarget - A human-readable description of the validation target
|
|
29
|
+
* @param expected - A description of the expected value(s), or `null` if unknown
|
|
30
|
+
* @param matches - Partial match result containing reason, candidate, and other details
|
|
31
|
+
* @returns A localized error message string
|
|
32
|
+
*/
|
|
6
33
|
export declare function __createMessageValueExpected(t: Translator, baseTarget: string, expected: string | null, matches: Pick<UnmatchedResult, 'partName' | 'reason' | 'raw' | 'candidate' | 'ref' | 'extra' | 'fallbackTo'>): string;
|
package/lib/create-message.js
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
import { isList, isKeyword, isEnum, isNumber, isDirective } from '@markuplint/types';
|
|
1
|
+
import { isList, isKeyword, isEnum, isNumber, isDirective, isPattern } from '@markuplint/types';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a human-readable error message explaining why an attribute value
|
|
4
|
+
* does not match its expected type. Handles both plain types and list types
|
|
5
|
+
* (space-separated or comma-separated).
|
|
6
|
+
*
|
|
7
|
+
* @param t - The i18n translator for generating localized messages
|
|
8
|
+
* @param baseTarget - A human-readable description of the attribute being validated
|
|
9
|
+
* (e.g. `'the "href" attribute'`)
|
|
10
|
+
* @param type - The attribute type definition that the value was validated against
|
|
11
|
+
* @param matches - The result object from a failed type check, containing
|
|
12
|
+
* details about the mismatch (reason, candidate, part name, etc.)
|
|
13
|
+
* @returns A localized error message describing the expected value
|
|
14
|
+
*/
|
|
2
15
|
export function createMessageValueExpected(t, baseTarget, type, matches) {
|
|
3
16
|
let target = baseTarget;
|
|
4
17
|
let listDescriptionPart;
|
|
@@ -17,6 +30,20 @@ export function createMessageValueExpected(t, baseTarget, type, matches) {
|
|
|
17
30
|
.join(t('. '));
|
|
18
31
|
return message;
|
|
19
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Assembles the detailed portion of a value-expected error message by
|
|
35
|
+
* inspecting the mismatch reason and composing reason, expectation,
|
|
36
|
+
* candidate suggestion, and fallback parts into a single string.
|
|
37
|
+
*
|
|
38
|
+
* This is an internal helper exposed for testing and advanced use.
|
|
39
|
+
* The double-underscore prefix indicates it is not part of the stable public API.
|
|
40
|
+
*
|
|
41
|
+
* @param t - The i18n translator for generating localized messages
|
|
42
|
+
* @param baseTarget - A human-readable description of the validation target
|
|
43
|
+
* @param expected - A description of the expected value(s), or `null` if unknown
|
|
44
|
+
* @param matches - Partial match result containing reason, candidate, and other details
|
|
45
|
+
* @returns A localized error message string
|
|
46
|
+
*/
|
|
20
47
|
export function __createMessageValueExpected(t, baseTarget, expected, matches) {
|
|
21
48
|
let target = baseTarget;
|
|
22
49
|
let reasonPart;
|
|
@@ -132,7 +159,7 @@ export function __createMessageValueExpected(t, baseTarget, expected, matches) {
|
|
|
132
159
|
switch (matches.reason.type) {
|
|
133
160
|
case 'out-of-range-length-digit': {
|
|
134
161
|
const { gte, lte } = matches.reason;
|
|
135
|
-
let expectedDigits
|
|
162
|
+
let expectedDigits;
|
|
136
163
|
if (lte != null && gte === lte) {
|
|
137
164
|
expectedDigits = t('{0} digits', gte);
|
|
138
165
|
}
|
|
@@ -199,6 +226,17 @@ export function __createMessageValueExpected(t, baseTarget, expected, matches) {
|
|
|
199
226
|
}
|
|
200
227
|
return message;
|
|
201
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Derives a human-readable description of the expected value from an attribute
|
|
231
|
+
* type definition and the unmatched result. Inspects keywords, enums, numbers,
|
|
232
|
+
* directives, and explicit `expects` entries from the match result.
|
|
233
|
+
*
|
|
234
|
+
* @param type - The attribute type (excluding list wrappers)
|
|
235
|
+
* @param matches - The unmatched result containing optional `expects` hints
|
|
236
|
+
* @param t - The i18n translator
|
|
237
|
+
* @returns A localized string describing the expected value(s), or `null` if
|
|
238
|
+
* no expectation can be determined
|
|
239
|
+
*/
|
|
202
240
|
function createExpectedObject(type, matches, t) {
|
|
203
241
|
const expectedObject = [];
|
|
204
242
|
if (matches.expects && matches.expects.length > 0) {
|
|
@@ -220,6 +258,9 @@ function createExpectedObject(type, matches, t) {
|
|
|
220
258
|
else if (isDirective(type)) {
|
|
221
259
|
expectedObject.push(t('a {0}', 'directive'));
|
|
222
260
|
}
|
|
261
|
+
else if (isPattern(type)) {
|
|
262
|
+
expectedObject.push(t('{0} ({1})', 'regular expression', type.pattern));
|
|
263
|
+
}
|
|
223
264
|
const expects = expectedObject.length === 0
|
|
224
265
|
? null
|
|
225
266
|
: 1 < expectedObject.length
|
|
@@ -227,6 +268,14 @@ function createExpectedObject(type, matches, t) {
|
|
|
227
268
|
: (expectedObject[0] ?? null);
|
|
228
269
|
return expects;
|
|
229
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Converts a single `Expect` descriptor into a human-readable word or phrase.
|
|
273
|
+
*
|
|
274
|
+
* @param t - The i18n translator
|
|
275
|
+
* @param expect - The expectation descriptor from a type-check result
|
|
276
|
+
* @param type - The attribute type context for determining phrasing (e.g. CSS syntax)
|
|
277
|
+
* @returns A localized word or phrase describing the expected value
|
|
278
|
+
*/
|
|
230
279
|
function expectValueToWord(t, expect, type) {
|
|
231
280
|
switch (expect.type) {
|
|
232
281
|
case 'common': {
|
|
@@ -249,6 +298,15 @@ function expectValueToWord(t, expect, type) {
|
|
|
249
298
|
}
|
|
250
299
|
}
|
|
251
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Builds a human-readable description of an expected numeric range
|
|
303
|
+
* from a `Number` type definition (e.g. "integer greater than or equal to 0").
|
|
304
|
+
*
|
|
305
|
+
* @param t - The i18n translator
|
|
306
|
+
* @param type - The number type definition containing range constraints
|
|
307
|
+
* (`gt`, `gte`, `lt`, `lte`) and the numeric type label
|
|
308
|
+
* @returns A localized description of the expected numeric range
|
|
309
|
+
*/
|
|
252
310
|
function createExpectedNumber(t, type) {
|
|
253
311
|
if (type.gt != null) {
|
|
254
312
|
if (type.lt != null) {
|
package/lib/debug.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
import debug from 'debug';
|
|
2
|
+
/**
|
|
3
|
+
* Type alias for the debug logger function provided by the `debug` package.
|
|
4
|
+
* Used throughout the rules package for conditional diagnostic logging.
|
|
5
|
+
*/
|
|
2
6
|
export type Log = debug.Debugger;
|
|
7
|
+
/**
|
|
8
|
+
* Shared debug logger instance for the `@markuplint/rules` package.
|
|
9
|
+
* Enable by setting the `DEBUG` environment variable to include `ml-rules`
|
|
10
|
+
* (e.g. `DEBUG=ml-rules`).
|
|
11
|
+
*/
|
|
3
12
|
export declare const log: debug.Debugger;
|
package/lib/debug.js
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule that reports the use of deprecated or obsolete HTML attributes.
|
|
3
|
+
*
|
|
4
|
+
* Checks each attribute against the HTML spec and reports any that are
|
|
5
|
+
* marked as deprecated or obsolete.
|
|
6
|
+
*/
|
|
1
7
|
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
2
8
|
export default _default;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { createRule, getAttrSpecs } from '@markuplint/ml-core';
|
|
2
2
|
import meta from './meta.js';
|
|
3
|
+
/**
|
|
4
|
+
* Rule that reports the use of deprecated or obsolete HTML attributes.
|
|
5
|
+
*
|
|
6
|
+
* Checks each attribute against the HTML spec and reports any that are
|
|
7
|
+
* marked as deprecated or obsolete.
|
|
8
|
+
*/
|
|
3
9
|
export default createRule({
|
|
4
10
|
meta: meta,
|
|
5
11
|
async verify({ document, report, t }) {
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule that reports the use of deprecated or obsolete HTML elements.
|
|
3
|
+
*
|
|
4
|
+
* Walks HTML and SVG elements and checks their spec status. Reports any
|
|
5
|
+
* element that is marked as deprecated or obsolete in the HTML specification.
|
|
6
|
+
*/
|
|
1
7
|
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
2
8
|
export default _default;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { createRule, getSpec } from '@markuplint/ml-core';
|
|
2
2
|
import meta from './meta.js';
|
|
3
|
+
/**
|
|
4
|
+
* Rule that reports the use of deprecated or obsolete HTML elements.
|
|
5
|
+
*
|
|
6
|
+
* Walks HTML and SVG elements and checks their spec status. Reports any
|
|
7
|
+
* element that is marked as deprecated or obsolete in the HTML specification.
|
|
8
|
+
*/
|
|
3
9
|
export default createRule({
|
|
4
10
|
meta: meta,
|
|
5
11
|
async verify({ document, report, t }) {
|
|
@@ -10,8 +16,8 @@ export default createRule({
|
|
|
10
16
|
return;
|
|
11
17
|
}
|
|
12
18
|
const spec = getSpec(el, document.specs.specs);
|
|
13
|
-
if (spec && (spec.obsolete != null || spec.deprecated
|
|
14
|
-
const message = t('{0} is {1:c}', t('the "{0*}" {1}', el.localName, 'element'), spec.deprecated ? 'deprecated' :
|
|
19
|
+
if (spec && (spec.obsolete != null || spec.deprecated)) {
|
|
20
|
+
const message = t('{0} is {1:c}', t('the "{0*}" {1}', el.localName, 'element'), spec.deprecated ? 'deprecated' : 'obsolete');
|
|
15
21
|
report({
|
|
16
22
|
scope: el,
|
|
17
23
|
message,
|
|
@@ -1,2 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule that disallows elements matching the configured CSS selectors.
|
|
3
|
+
*
|
|
4
|
+
* Accepts an array of selector strings as its value. Queries the document
|
|
5
|
+
* (and per-element overrides) for matching elements and reports each one
|
|
6
|
+
* as disallowed.
|
|
7
|
+
*/
|
|
1
8
|
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<string[], undefined>>;
|
|
2
9
|
export default _default;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
import meta from './meta.js';
|
|
3
|
+
/**
|
|
4
|
+
* Rule that disallows elements matching the configured CSS selectors.
|
|
5
|
+
*
|
|
6
|
+
* Accepts an array of selector strings as its value. Queries the document
|
|
7
|
+
* (and per-element overrides) for matching elements and reports each one
|
|
8
|
+
* as disallowed.
|
|
9
|
+
*/
|
|
3
10
|
export default createRule({
|
|
4
11
|
meta: meta,
|
|
5
12
|
defaultValue: [],
|
package/lib/doctype/index.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
/** Configuration options for the doctype rule. */
|
|
1
2
|
type Option = {
|
|
3
|
+
/** Whether to report obsolete doctypes that include a public or system identifier. */
|
|
2
4
|
denyObsoleteType: boolean;
|
|
3
5
|
};
|
|
6
|
+
/**
|
|
7
|
+
* Rule that validates the presence and correctness of the document's DOCTYPE
|
|
8
|
+
* declaration.
|
|
9
|
+
*
|
|
10
|
+
* Reports an error when no DOCTYPE is found (skipped for document fragments)
|
|
11
|
+
* and when an obsolete DOCTYPE (one with a public or system identifier) is
|
|
12
|
+
* declared.
|
|
13
|
+
*/
|
|
4
14
|
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<"always", Option>>;
|
|
5
15
|
export default _default;
|
package/lib/doctype/index.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
import meta from './meta.js';
|
|
3
|
+
/**
|
|
4
|
+
* Rule that validates the presence and correctness of the document's DOCTYPE
|
|
5
|
+
* declaration.
|
|
6
|
+
*
|
|
7
|
+
* Reports an error when no DOCTYPE is found (skipped for document fragments)
|
|
8
|
+
* and when an obsolete DOCTYPE (one with a public or system identifier) is
|
|
9
|
+
* declared.
|
|
10
|
+
*/
|
|
3
11
|
export default createRule({
|
|
4
12
|
meta: meta,
|
|
5
13
|
defaultValue: 'always',
|
package/lib/doctype/meta.d.ts
CHANGED
package/lib/doctype/meta.js
CHANGED
package/lib/end-tag/index.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule that checks for missing end tags on non-void HTML elements.
|
|
3
|
+
*
|
|
4
|
+
* Reports elements that are neither void nor self-closing and have no
|
|
5
|
+
* closing tag. Omitted elements and elements in documents configured
|
|
6
|
+
* with `endTag: 'never'` are excluded.
|
|
7
|
+
*/
|
|
1
8
|
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<boolean, undefined>>;
|
|
2
9
|
export default _default;
|
package/lib/end-tag/index.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
import { isVoidElement } from '@markuplint/ml-spec';
|
|
3
3
|
import meta from './meta.js';
|
|
4
|
+
/**
|
|
5
|
+
* Rule that checks for missing end tags on non-void HTML elements.
|
|
6
|
+
*
|
|
7
|
+
* Reports elements that are neither void nor self-closing and have no
|
|
8
|
+
* closing tag. Omitted elements and elements in documents configured
|
|
9
|
+
* with `endTag: 'never'` are excluded.
|
|
10
|
+
*/
|
|
4
11
|
export default createRule({
|
|
5
12
|
meta: meta,
|
|
6
13
|
defaultSeverity: 'warning',
|
|
@@ -18,7 +25,7 @@ export default createRule({
|
|
|
18
25
|
if (el.closeTag != null) {
|
|
19
26
|
return;
|
|
20
27
|
}
|
|
21
|
-
if ((document.endTag === 'xml' || el.isForeignElement) && el.
|
|
28
|
+
if ((document.endTag === 'xml' || el.isForeignElement) && el.tagCloseChar.startsWith('/')) {
|
|
22
29
|
return;
|
|
23
30
|
}
|
|
24
31
|
report({
|
package/lib/end-tag/meta.d.ts
CHANGED
package/lib/end-tag/meta.js
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule that validates heading levels (h1-h6) are not skipped.
|
|
3
|
+
*
|
|
4
|
+
* Ensures that heading elements appear in a sequential order without
|
|
5
|
+
* gaps (e.g., an h4 must not follow an h2 directly without an h3 in between).
|
|
6
|
+
*/
|
|
1
7
|
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<boolean, null>>;
|
|
2
8
|
export default _default;
|