@markuplint/rules 5.0.0-rc.0 → 5.0.0-rc.2
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 +26 -0
- package/CLAUDE.md +27 -0
- package/SKILL.md +1 -0
- package/lib/attr-check.d.ts +3 -1
- package/lib/attr-check.js +10 -1
- package/lib/helpers.js +16 -1
- package/lib/index.d.ts +25 -7
- package/lib/index.js +36 -0
- package/lib/invalid-attr/index.js +12 -0
- package/lib/no-duplicate-autofocus/index.d.ts +7 -0
- package/lib/no-duplicate-autofocus/index.js +27 -0
- package/lib/no-duplicate-autofocus/meta.d.ts +4 -0
- package/lib/no-duplicate-autofocus/meta.js +3 -0
- package/lib/no-duplicate-visible-main/index.d.ts +8 -0
- package/lib/no-duplicate-visible-main/index.js +28 -0
- package/lib/no-duplicate-visible-main/meta.d.ts +4 -0
- package/lib/no-duplicate-visible-main/meta.js +3 -0
- package/lib/permitted-contents/choice.d.ts +5 -2
- package/lib/permitted-contents/choice.js +5 -2
- package/lib/permitted-contents/complex-branch.d.ts +5 -2
- package/lib/permitted-contents/complex-branch.js +6 -3
- package/lib/permitted-contents/content-model.d.ts +21 -2
- package/lib/permitted-contents/content-model.js +32 -6
- package/lib/permitted-contents/count-pattern.d.ts +5 -2
- package/lib/permitted-contents/count-pattern.js +13 -5
- package/lib/permitted-contents/index.d.ts +7 -1
- package/lib/permitted-contents/index.js +160 -65
- package/lib/permitted-contents/matches-selector.d.ts +2 -2
- package/lib/permitted-contents/matches-selector.js +43 -5
- package/lib/permitted-contents/order.d.ts +9 -2
- package/lib/permitted-contents/order.js +9 -2
- package/lib/permitted-contents/recursive-branch.d.ts +5 -2
- package/lib/permitted-contents/recursive-branch.js +7 -4
- package/lib/permitted-contents/represent-transparent-nodes.d.ts +2 -2
- package/lib/permitted-contents/represent-transparent-nodes.js +6 -6
- package/lib/permitted-contents/start.d.ts +2 -2
- package/lib/permitted-contents/start.js +3 -3
- package/lib/permitted-contents/types.d.ts +14 -0
- package/lib/permitted-contents/utils.d.ts +11 -2
- package/lib/permitted-contents/utils.js +62 -13
- package/lib/wai-aria/checkings/disallowed-prop.d.ts +2 -1
- package/lib/wai-aria/checkings/disallowed-prop.js +27 -5
- package/lib/wai-aria/checkings/permitted-roles.js +16 -3
- package/lib/wai-aria/checkings/required-accessibility-parent-role.js +1 -1
- package/lib/wai-aria/default-options.d.ts +5 -0
- package/lib/wai-aria/default-options.js +18 -0
- package/lib/wai-aria/index.js +8 -19
- package/lib/wai-aria-abstract-role/index.d.ts +4 -0
- package/lib/wai-aria-abstract-role/index.js +22 -0
- package/lib/wai-aria-abstract-role/meta.d.ts +4 -0
- package/lib/wai-aria-abstract-role/meta.js +3 -0
- package/lib/wai-aria-default-value/index.d.ts +4 -0
- package/lib/wai-aria-default-value/index.js +28 -0
- package/lib/wai-aria-default-value/meta.d.ts +5 -0
- package/lib/wai-aria-default-value/meta.js +4 -0
- package/lib/wai-aria-deprecated-props/index.d.ts +4 -0
- package/lib/wai-aria-deprecated-props/index.js +29 -0
- package/lib/wai-aria-deprecated-props/meta.d.ts +5 -0
- package/lib/wai-aria-deprecated-props/meta.js +4 -0
- package/lib/wai-aria-deprecated-role/index.d.ts +4 -0
- package/lib/wai-aria-deprecated-role/index.js +26 -0
- package/lib/wai-aria-deprecated-role/meta.d.ts +4 -0
- package/lib/wai-aria-deprecated-role/meta.js +3 -0
- package/lib/wai-aria-disallowed-props/index.d.ts +4 -0
- package/lib/wai-aria-disallowed-props/index.js +33 -0
- package/lib/wai-aria-disallowed-props/meta.d.ts +5 -0
- package/lib/wai-aria-disallowed-props/meta.js +4 -0
- package/lib/wai-aria-implicit-props/index.d.ts +4 -0
- package/lib/wai-aria-implicit-props/index.js +29 -0
- package/lib/wai-aria-implicit-props/meta.d.ts +5 -0
- package/lib/wai-aria-implicit-props/meta.js +4 -0
- package/lib/wai-aria-implicit-role/index.d.ts +4 -0
- package/lib/wai-aria-implicit-role/index.js +23 -0
- package/lib/wai-aria-implicit-role/meta.d.ts +4 -0
- package/lib/wai-aria-implicit-role/meta.js +3 -0
- package/lib/wai-aria-interaction-in-hidden/index.d.ts +4 -0
- package/lib/wai-aria-interaction-in-hidden/index.js +20 -0
- package/lib/wai-aria-interaction-in-hidden/meta.d.ts +5 -0
- package/lib/wai-aria-interaction-in-hidden/meta.js +4 -0
- package/lib/wai-aria-no-global-prop/index.d.ts +4 -0
- package/lib/wai-aria-no-global-prop/index.js +30 -0
- package/lib/wai-aria-no-global-prop/meta.d.ts +5 -0
- package/lib/wai-aria-no-global-prop/meta.js +4 -0
- package/lib/wai-aria-non-existent-role/index.d.ts +4 -0
- package/lib/wai-aria-non-existent-role/index.js +22 -0
- package/lib/wai-aria-non-existent-role/meta.d.ts +4 -0
- package/lib/wai-aria-non-existent-role/meta.js +3 -0
- package/lib/wai-aria-permitted-roles/index.d.ts +4 -0
- package/lib/wai-aria-permitted-roles/index.js +22 -0
- package/lib/wai-aria-permitted-roles/meta.d.ts +4 -0
- package/lib/wai-aria-permitted-roles/meta.js +3 -0
- package/lib/wai-aria-presentational-children/index.d.ts +4 -0
- package/lib/wai-aria-presentational-children/index.js +20 -0
- package/lib/wai-aria-presentational-children/meta.d.ts +5 -0
- package/lib/wai-aria-presentational-children/meta.js +4 -0
- package/lib/wai-aria-required-owned-elements/index.d.ts +4 -0
- package/lib/wai-aria-required-owned-elements/index.js +23 -0
- package/lib/wai-aria-required-owned-elements/meta.d.ts +4 -0
- package/lib/wai-aria-required-owned-elements/meta.js +3 -0
- package/lib/wai-aria-required-parent-role/index.d.ts +4 -0
- package/lib/wai-aria-required-parent-role/index.js +25 -0
- package/lib/wai-aria-required-parent-role/meta.d.ts +4 -0
- package/lib/wai-aria-required-parent-role/meta.js +3 -0
- package/lib/wai-aria-required-props/index.d.ts +4 -0
- package/lib/wai-aria-required-props/index.js +26 -0
- package/lib/wai-aria-required-props/meta.d.ts +4 -0
- package/lib/wai-aria-required-props/meta.js +3 -0
- package/lib/wai-aria-value/index.d.ts +4 -0
- package/lib/wai-aria-value/index.js +28 -0
- package/lib/wai-aria-value/meta.d.ts +5 -0
- package/lib/wai-aria-value/meta.js +4 -0
- package/package.json +15 -11
- package/schema.json +54 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,32 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [5.0.0-rc.2](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.1...v5.0.0-rc.2) (2026-04-15)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **rules:** allow zeroOrMore to match zero times in countPattern ([f5da340](https://github.com/markuplint/markuplint/commit/f5da340ac264c831ba0c74adfd039a1d0b6ff3fb)), closes [#3592](https://github.com/markuplint/markuplint/issues/3592)
|
|
11
|
+
- **rules:** apply permitted-contents user rule on pretended elements ([04c42d1](https://github.com/markuplint/markuplint/commit/04c42d165aabde754d6544486ca5d39ef4a868bc)), closes [#3739](https://github.com/markuplint/markuplint/issues/3739)
|
|
12
|
+
- **rules:** enforce descendantOf constraint in permitted-contents rule ([5375207](https://github.com/markuplint/markuplint/commit/5375207f169d26c65be6510f28c904d39454d08e))
|
|
13
|
+
- **rules:** preserve role name in permitted-roles message, dedupe with implicit-role ([3123cd6](https://github.com/markuplint/markuplint/commit/3123cd690e140e142a94c09d6948bb9cf0dfd23e)), closes [#3641](https://github.com/markuplint/markuplint/issues/3641) [#3641](https://github.com/markuplint/markuplint/issues/3641) [#3641](https://github.com/markuplint/markuplint/issues/3641)
|
|
14
|
+
- **types:** reject zero and negative srcset descriptors ([5584d20](https://github.com/markuplint/markuplint/commit/5584d2089fe1dfe508d43601fa14aeff4e08b265))
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
- **ml-spec:** add ConditionalAttributeType to Attribute.type union ([#3685](https://github.com/markuplint/markuplint/issues/3685)) ([a619a07](https://github.com/markuplint/markuplint/commit/a619a071d93566dd8aa7ab8dee2ed751c2e8756c)), closes [#3598](https://github.com/markuplint/markuplint/issues/3598) [#3189](https://github.com/markuplint/markuplint/issues/3189)
|
|
19
|
+
- **rules:** add #nonEmptyText matching to permitted-contents engine ([4b91cf3](https://github.com/markuplint/markuplint/commit/4b91cf37cb8d54b86e8bfe125276251f2d8cb97b))
|
|
20
|
+
- **rules:** add document uniqueness rules (autofocus, visible-main, charset) ([1befb20](https://github.com/markuplint/markuplint/commit/1befb2051015df7669fbd07da3fea3a0b6542a90))
|
|
21
|
+
- **rules:** check forbiddenAncestors in permitted-contents rule ([0633c05](https://github.com/markuplint/markuplint/commit/0633c05df91a2f51361a72efe8ebb2e604a53918))
|
|
22
|
+
- **rules:** check uniqueAttrs constraint in permitted-contents rule ([6b2cf6b](https://github.com/markuplint/markuplint/commit/6b2cf6b9fc7d5320543acdc9271de12e46677b9f))
|
|
23
|
+
- **rules:** disallow is attribute on autonomous custom elements ([ed7ec78](https://github.com/markuplint/markuplint/commit/ed7ec78c058030caacc8bd23c90c489c77c40b27))
|
|
24
|
+
- **rules:** enforce ARIA naming prohibition on elements without role ([f67bd5d](https://github.com/markuplint/markuplint/commit/f67bd5dcb33f28ef3daf3ecd3b5d30ad4ced7814)), closes [#3630](https://github.com/markuplint/markuplint/issues/3630)
|
|
25
|
+
- **rules:** implement conditional type resolution for input value ([#3598](https://github.com/markuplint/markuplint/issues/3598)) ([d478ce6](https://github.com/markuplint/markuplint/commit/d478ce669387c868dd2e4b81cad32bcccddef7f2))
|
|
26
|
+
- **rules:** split wai-aria into 16 granular sub-rules ([1aded91](https://github.com/markuplint/markuplint/commit/1aded912ca839a75b89d14f3ae6ba30c2701a61b)), closes [#3645](https://github.com/markuplint/markuplint/issues/3645)
|
|
27
|
+
|
|
28
|
+
# [5.0.0-rc.1](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.0...v5.0.0-rc.1) (2026-03-27)
|
|
29
|
+
|
|
30
|
+
**Note:** Version bump only for package @markuplint/rules
|
|
31
|
+
|
|
6
32
|
# [5.0.0-rc.0](https://github.com/markuplint/markuplint/compare/v5.0.0-alpha.3...v5.0.0-rc.0) (2026-03-12)
|
|
7
33
|
|
|
8
34
|
### Bug Fixes
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @markuplint/rules
|
|
2
|
+
|
|
3
|
+
## Test ID Convention (MANDATORY)
|
|
4
|
+
|
|
5
|
+
Every `test()` block in `src/**/*.spec.ts` MUST have a unique ID prefix:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
[rule-name-category-NNN] description
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Categories
|
|
12
|
+
|
|
13
|
+
| Category | When to use |
|
|
14
|
+
| ------------ | --------------------------------------------------------- |
|
|
15
|
+
| `valid` | Test expects 0 violations |
|
|
16
|
+
| `invalid` | Test expects 1+ violations |
|
|
17
|
+
| `fix` | Test checks `fixedCode` or auto-fix behavior |
|
|
18
|
+
| `parser` | Test uses a non-default parser (Vue, React, Pug, etc.) |
|
|
19
|
+
| `issue-NNNN` | Regression test for a GitHub issue (use the issue number) |
|
|
20
|
+
|
|
21
|
+
### Rules
|
|
22
|
+
|
|
23
|
+
- IDs go on `test()` blocks only (not `describe()`)
|
|
24
|
+
- Numbers are sequential per category per file, starting at `001`
|
|
25
|
+
- Multiple tests for the same issue: `[rule-name-issue-NNNN-001]`, `[rule-name-issue-NNNN-002]`
|
|
26
|
+
- When adding a new test, assign the next available number in the appropriate category
|
|
27
|
+
- Run `/list-rule-test` (or `node .claude/commands/scripts/list-rule-test.mjs --no-id`) to check for missing IDs
|
package/SKILL.md
CHANGED
|
@@ -58,6 +58,7 @@ const { violations } = await mlRuleTest(rule, '<button role="separator" ...>', {
|
|
|
58
58
|
- `toStrictEqual` with exact `{ severity, line, col, message, raw }` — **never** loose assertions
|
|
59
59
|
- Always include both valid and invalid cases
|
|
60
60
|
- Some roles need ARIA attributes in HTML: `separator` → `aria-valuenow`, `meter` → `aria-valuenow`
|
|
61
|
+
- **Every `test()` MUST have a unique ID prefix** — see `CLAUDE.md` in this package for the full convention
|
|
61
62
|
|
|
62
63
|
## Task: update-test-expectations
|
|
63
64
|
|
package/lib/attr-check.d.ts
CHANGED
|
@@ -34,7 +34,9 @@ type Loc = {
|
|
|
34
34
|
* 2. Verifies the attribute exists in the spec
|
|
35
35
|
* 3. Checks case-sensitive name matching
|
|
36
36
|
* 4. Checks whether the attribute is marked as `noUse` (disallowed)
|
|
37
|
-
* 5.
|
|
37
|
+
* 5. Safety-net fallback for `ConditionalAttributeType[]` (#3685); in normal flow,
|
|
38
|
+
* `isValidAttr()` resolves conditional types before calling this function (#3598)
|
|
39
|
+
* 6. Validates the attribute value against all declared types
|
|
38
40
|
*
|
|
39
41
|
* @param t - The i18n translator for generating localized error messages
|
|
40
42
|
* @param name - The attribute name to check
|
package/lib/attr-check.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isConditionalAttributeTypeArray } from '@markuplint/ml-spec';
|
|
1
2
|
import { toNonNullableArrayFromItemOrArray } from '@markuplint/shared';
|
|
2
3
|
import { check, getCandidate } from '@markuplint/types';
|
|
3
4
|
import { createMessageValueExpected } from './create-message.js';
|
|
@@ -11,7 +12,9 @@ import { log } from './debug.js';
|
|
|
11
12
|
* 2. Verifies the attribute exists in the spec
|
|
12
13
|
* 3. Checks case-sensitive name matching
|
|
13
14
|
* 4. Checks whether the attribute is marked as `noUse` (disallowed)
|
|
14
|
-
* 5.
|
|
15
|
+
* 5. Safety-net fallback for `ConditionalAttributeType[]` (#3685); in normal flow,
|
|
16
|
+
* `isValidAttr()` resolves conditional types before calling this function (#3598)
|
|
17
|
+
* 6. Validates the attribute value against all declared types
|
|
15
18
|
*
|
|
16
19
|
* @param t - The i18n translator for generating localized error messages
|
|
17
20
|
* @param name - The attribute name to check
|
|
@@ -72,6 +75,12 @@ export function attrCheck(t, name, value, isCustomRule, spec, allAttrNames) {
|
|
|
72
75
|
message: t('{0} is {1:c}', t('the "{0*}" {1}', name, 'attribute'), 'disallowed'),
|
|
73
76
|
};
|
|
74
77
|
}
|
|
78
|
+
// Safety net: In normal flow, `isValidAttr()` resolves ConditionalAttributeType[]
|
|
79
|
+
// before calling this function (#3598). This guard handles direct calls.
|
|
80
|
+
if (isConditionalAttributeTypeArray(spec.type)) {
|
|
81
|
+
log('The "%s" attribute uses ConditionalAttributeType[] — unresolved, treating as valid', name);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
75
84
|
const types = toNonNullableArrayFromItemOrArray(spec.type);
|
|
76
85
|
const invalidMap = new Map();
|
|
77
86
|
for (const type of types) {
|
package/lib/helpers.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isConditionalAttributeTypeArray } from '@markuplint/ml-spec';
|
|
1
2
|
import { attrCheck } from './attr-check.js';
|
|
2
3
|
/**
|
|
3
4
|
* Tests whether an element matches the condition specified in an attribute spec.
|
|
@@ -93,8 +94,20 @@ export function isValidAttr(t, name, value, isDynamicValue,
|
|
|
93
94
|
node, attrSpecs,
|
|
94
95
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
95
96
|
log) {
|
|
96
|
-
|
|
97
|
+
let spec = attrSpecs.find(s => s.name.toLowerCase() === name.toLowerCase());
|
|
97
98
|
log?.('Spec of the %s attr: %o', name, spec);
|
|
99
|
+
// Resolve ConditionalAttributeType[] to a concrete type based on element matching (#3598).
|
|
100
|
+
if (spec && isConditionalAttributeTypeArray(spec.type)) {
|
|
101
|
+
const matched = spec.type.find(entry => {
|
|
102
|
+
const cond = typeof entry.condition === 'string' ? entry.condition : entry.condition.join(',');
|
|
103
|
+
return node.matches(cond);
|
|
104
|
+
});
|
|
105
|
+
log?.('ConditionalAttributeType resolution for %s: %o', name, matched);
|
|
106
|
+
// Fallback to 'Any' when no condition matches: input types without an explicit
|
|
107
|
+
// entry (text, search, tel, password, hidden, checkbox, radio, file, submit,
|
|
108
|
+
// image, reset, button) have no value constraints per the HTML spec.
|
|
109
|
+
spec = { ...spec, type: matched ? matched.type : 'Any' };
|
|
110
|
+
}
|
|
98
111
|
const allAttrNames = attrSpecs.map(s => s.name);
|
|
99
112
|
let invalid = attrCheck(t, name, value, false, spec, allAttrNames);
|
|
100
113
|
if (invalid === false &&
|
|
@@ -127,6 +140,8 @@ export function toNormalizedValue(value, spec) {
|
|
|
127
140
|
if (!spec.caseSensitive) {
|
|
128
141
|
normalized = normalized.toLowerCase();
|
|
129
142
|
}
|
|
143
|
+
// When spec.type is an array (AttributeType[] or ConditionalAttributeType[]),
|
|
144
|
+
// type-specific normalization is skipped — only caseSensitive applies above.
|
|
130
145
|
if (typeof spec.type === 'string') {
|
|
131
146
|
if (spec.type[0] === '<') {
|
|
132
147
|
normalized = normalized.toLowerCase().trim().replaceAll(/\s+/g, ' ');
|
package/lib/index.d.ts
CHANGED
|
@@ -42,18 +42,18 @@ declare const rules: {
|
|
|
42
42
|
readonly 'invalid-attr': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, {
|
|
43
43
|
allowAttrs?: (string | {
|
|
44
44
|
name: string;
|
|
45
|
-
value: import("
|
|
46
|
-
})[] | Record<string, import("
|
|
45
|
+
value: import("@markuplint/ml-spec").AttributeType | import("@markuplint/types").Pattern;
|
|
46
|
+
})[] | Record<string, import("@markuplint/ml-spec").AttributeType | import("@markuplint/types").Pattern>;
|
|
47
47
|
disallowAttrs?: (string | {
|
|
48
48
|
name: string;
|
|
49
|
-
value: import("
|
|
50
|
-
})[] | Record<string, import("
|
|
49
|
+
value: import("@markuplint/ml-spec").AttributeType | import("@markuplint/types").Pattern;
|
|
50
|
+
})[] | Record<string, import("@markuplint/ml-spec").AttributeType | import("@markuplint/types").Pattern>;
|
|
51
51
|
ignoreAttrNamePrefix?: string | string[];
|
|
52
52
|
allowToAddPropertiesForPretender?: boolean;
|
|
53
53
|
}>>;
|
|
54
54
|
readonly 'label-has-control': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
55
55
|
readonly 'landmark-roles': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, {
|
|
56
|
-
ignoreRoles: (
|
|
56
|
+
ignoreRoles: ("form" | ("main" | "banner" | "contentinfo") | "complementary" | "navigation" | "region")[];
|
|
57
57
|
labelEachArea: boolean;
|
|
58
58
|
}>>;
|
|
59
59
|
readonly 'link-types': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, {
|
|
@@ -64,7 +64,9 @@ declare const rules: {
|
|
|
64
64
|
readonly 'no-boolean-attr-value': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
65
65
|
readonly 'no-consecutive-br': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
66
66
|
readonly 'no-default-value': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
67
|
+
readonly 'no-duplicate-autofocus': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, null>>;
|
|
67
68
|
readonly 'no-duplicate-dt': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
69
|
+
readonly 'no-duplicate-visible-main': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, null>>;
|
|
68
70
|
readonly 'no-empty-palpable-content': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, {
|
|
69
71
|
extendsExposableElements?: boolean;
|
|
70
72
|
ignoreIfAriaBusy?: boolean;
|
|
@@ -72,7 +74,7 @@ declare const rules: {
|
|
|
72
74
|
readonly 'no-hard-code-id': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
73
75
|
readonly 'no-orphaned-end-tag': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, null>>;
|
|
74
76
|
readonly 'no-refer-to-non-existent-id': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, {
|
|
75
|
-
ariaVersion: import("
|
|
77
|
+
ariaVersion: import("@markuplint/ml-spec").ARIAVersion | undefined;
|
|
76
78
|
fragmentRefersNameAttr: boolean;
|
|
77
79
|
}>>;
|
|
78
80
|
readonly 'no-unsupported-features': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, {
|
|
@@ -93,7 +95,7 @@ declare const rules: {
|
|
|
93
95
|
checkPlaceholderFallback: boolean;
|
|
94
96
|
}>>;
|
|
95
97
|
readonly 'require-accessible-name': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, {
|
|
96
|
-
ariaVersion: import("
|
|
98
|
+
ariaVersion: import("@markuplint/ml-spec").ARIAVersion | undefined;
|
|
97
99
|
}>>;
|
|
98
100
|
readonly 'require-datetime': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, {
|
|
99
101
|
langs?: import("./require-datetime/types.js").Lang[];
|
|
@@ -120,5 +122,21 @@ declare const rules: {
|
|
|
120
122
|
prevCodeBlock?: boolean;
|
|
121
123
|
}>>;
|
|
122
124
|
readonly 'wai-aria': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
125
|
+
readonly 'wai-aria-abstract-role': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
126
|
+
readonly 'wai-aria-default-value': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
127
|
+
readonly 'wai-aria-deprecated-props': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
128
|
+
readonly 'wai-aria-deprecated-role': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
129
|
+
readonly 'wai-aria-disallowed-props': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
130
|
+
readonly 'wai-aria-implicit-props': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
131
|
+
readonly 'wai-aria-implicit-role': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
132
|
+
readonly 'wai-aria-interaction-in-hidden': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
133
|
+
readonly 'wai-aria-no-global-prop': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
134
|
+
readonly 'wai-aria-non-existent-role': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
135
|
+
readonly 'wai-aria-permitted-roles': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
136
|
+
readonly 'wai-aria-presentational-children': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
137
|
+
readonly 'wai-aria-required-owned-elements': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
138
|
+
readonly 'wai-aria-required-parent-role': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
139
|
+
readonly 'wai-aria-required-props': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
140
|
+
readonly 'wai-aria-value': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./wai-aria/types.js").Options>>;
|
|
123
141
|
};
|
|
124
142
|
export default rules;
|
package/lib/index.js
CHANGED
|
@@ -32,7 +32,9 @@ import NoAmbiguousNavigableTargetNames from './no-ambiguous-navigable-target-nam
|
|
|
32
32
|
import NoBooleanAttrValue from './no-boolean-attr-value/index.js';
|
|
33
33
|
import NoConsecutiveBr from './no-consecutive-br/index.js';
|
|
34
34
|
import NoDefaultValue from './no-default-value/index.js';
|
|
35
|
+
import NoDuplicateAutofocus from './no-duplicate-autofocus/index.js';
|
|
35
36
|
import NoDuplicateDt from './no-duplicate-dt/index.js';
|
|
37
|
+
import NoDuplicateVisibleMain from './no-duplicate-visible-main/index.js';
|
|
36
38
|
import NoEmptyPalpableContent from './no-empty-palpable-content/index.js';
|
|
37
39
|
import NoHardCodeId from './no-hard-code-id/index.js';
|
|
38
40
|
import NoOrphanedEndTag from './no-orphaned-end-tag/index.js';
|
|
@@ -52,6 +54,22 @@ import RequiredH1 from './required-h1/index.js';
|
|
|
52
54
|
import TableRowColumnAlignment from './table-row-column-alignment/index.js';
|
|
53
55
|
import UseList from './use-list/index.js';
|
|
54
56
|
import WaiAria from './wai-aria/index.js';
|
|
57
|
+
import WaiAriaAbstractRole from './wai-aria-abstract-role/index.js';
|
|
58
|
+
import WaiAriaDefaultValue from './wai-aria-default-value/index.js';
|
|
59
|
+
import WaiAriaDeprecatedProps from './wai-aria-deprecated-props/index.js';
|
|
60
|
+
import WaiAriaDeprecatedRole from './wai-aria-deprecated-role/index.js';
|
|
61
|
+
import WaiAriaDisallowedProps from './wai-aria-disallowed-props/index.js';
|
|
62
|
+
import WaiAriaImplicitProps from './wai-aria-implicit-props/index.js';
|
|
63
|
+
import WaiAriaImplicitRole from './wai-aria-implicit-role/index.js';
|
|
64
|
+
import WaiAriaInteractionInHidden from './wai-aria-interaction-in-hidden/index.js';
|
|
65
|
+
import WaiAriaNoGlobalProp from './wai-aria-no-global-prop/index.js';
|
|
66
|
+
import WaiAriaNonExistentRole from './wai-aria-non-existent-role/index.js';
|
|
67
|
+
import WaiAriaPermittedRoles from './wai-aria-permitted-roles/index.js';
|
|
68
|
+
import WaiAriaPresentationalChildren from './wai-aria-presentational-children/index.js';
|
|
69
|
+
import WaiAriaRequiredOwnedElements from './wai-aria-required-owned-elements/index.js';
|
|
70
|
+
import WaiAriaRequiredParentRole from './wai-aria-required-parent-role/index.js';
|
|
71
|
+
import WaiAriaRequiredProps from './wai-aria-required-props/index.js';
|
|
72
|
+
import WaiAriaValue from './wai-aria-value/index.js';
|
|
55
73
|
/**
|
|
56
74
|
* Registry of all built-in markuplint rules, mapping rule names to their seed definitions.
|
|
57
75
|
* Used by the markuplint core to initialize rule instances during linting.
|
|
@@ -83,7 +101,9 @@ const rules = {
|
|
|
83
101
|
'no-boolean-attr-value': NoBooleanAttrValue,
|
|
84
102
|
'no-consecutive-br': NoConsecutiveBr,
|
|
85
103
|
'no-default-value': NoDefaultValue,
|
|
104
|
+
'no-duplicate-autofocus': NoDuplicateAutofocus,
|
|
86
105
|
'no-duplicate-dt': NoDuplicateDt,
|
|
106
|
+
'no-duplicate-visible-main': NoDuplicateVisibleMain,
|
|
87
107
|
'no-empty-palpable-content': NoEmptyPalpableContent,
|
|
88
108
|
'no-hard-code-id': NoHardCodeId,
|
|
89
109
|
'no-orphaned-end-tag': NoOrphanedEndTag,
|
|
@@ -103,5 +123,21 @@ const rules = {
|
|
|
103
123
|
'table-row-column-alignment': TableRowColumnAlignment,
|
|
104
124
|
'use-list': UseList,
|
|
105
125
|
'wai-aria': WaiAria,
|
|
126
|
+
'wai-aria-abstract-role': WaiAriaAbstractRole,
|
|
127
|
+
'wai-aria-default-value': WaiAriaDefaultValue,
|
|
128
|
+
'wai-aria-deprecated-props': WaiAriaDeprecatedProps,
|
|
129
|
+
'wai-aria-deprecated-role': WaiAriaDeprecatedRole,
|
|
130
|
+
'wai-aria-disallowed-props': WaiAriaDisallowedProps,
|
|
131
|
+
'wai-aria-implicit-props': WaiAriaImplicitProps,
|
|
132
|
+
'wai-aria-implicit-role': WaiAriaImplicitRole,
|
|
133
|
+
'wai-aria-interaction-in-hidden': WaiAriaInteractionInHidden,
|
|
134
|
+
'wai-aria-no-global-prop': WaiAriaNoGlobalProp,
|
|
135
|
+
'wai-aria-non-existent-role': WaiAriaNonExistentRole,
|
|
136
|
+
'wai-aria-permitted-roles': WaiAriaPermittedRoles,
|
|
137
|
+
'wai-aria-presentational-children': WaiAriaPresentationalChildren,
|
|
138
|
+
'wai-aria-required-owned-elements': WaiAriaRequiredOwnedElements,
|
|
139
|
+
'wai-aria-required-parent-role': WaiAriaRequiredParentRole,
|
|
140
|
+
'wai-aria-required-props': WaiAriaRequiredProps,
|
|
141
|
+
'wai-aria-value': WaiAriaValue,
|
|
106
142
|
};
|
|
107
143
|
export default rules;
|
|
@@ -25,6 +25,18 @@ export default createRule({
|
|
|
25
25
|
if (attr.isDirective) {
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
|
+
// https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is
|
|
29
|
+
// "The is attribute must not be specified on an autonomous custom element"
|
|
30
|
+
if (attr.name === 'is' && attr.ownerElement.elementType === 'web-component') {
|
|
31
|
+
report({
|
|
32
|
+
scope: attr,
|
|
33
|
+
line: attr.nameNode?.startLine,
|
|
34
|
+
col: attr.nameNode?.startCol,
|
|
35
|
+
raw: attr.nameNode?.raw,
|
|
36
|
+
message: t('The "{0}" attribute must not be specified on an autonomous custom element', 'is'),
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
28
40
|
const attrSpecs = getAttrSpecs(attr.ownerElement, document.specs);
|
|
29
41
|
const attrName = attr.nameNode;
|
|
30
42
|
const name = attr.name;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensures no more than one element in a document has the `autofocus` attribute.
|
|
3
|
+
*
|
|
4
|
+
* @see https://html.spec.whatwg.org/multipage/interaction.html#the-autofocus-attribute
|
|
5
|
+
*/
|
|
6
|
+
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<boolean, null>>;
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createRule } from '@markuplint/ml-core';
|
|
2
|
+
import meta from './meta.js';
|
|
3
|
+
/**
|
|
4
|
+
* Ensures no more than one element in a document has the `autofocus` attribute.
|
|
5
|
+
*
|
|
6
|
+
* @see https://html.spec.whatwg.org/multipage/interaction.html#the-autofocus-attribute
|
|
7
|
+
*/
|
|
8
|
+
export default createRule({
|
|
9
|
+
meta: meta,
|
|
10
|
+
defaultValue: true,
|
|
11
|
+
defaultOptions: null,
|
|
12
|
+
async verify({ document, report, t }) {
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
14
|
+
const autofocusElements = [];
|
|
15
|
+
await document.walkOn('Element', el => {
|
|
16
|
+
if (el.hasAttribute('autofocus')) {
|
|
17
|
+
autofocusElements.push(el);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
for (const el of autofocusElements.slice(1)) {
|
|
21
|
+
report({
|
|
22
|
+
scope: el,
|
|
23
|
+
message: t('The "{0}" attribute must be unique in the document', 'autofocus'),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensures no more than one visible `<main>` element exists in a document.
|
|
3
|
+
* A `<main>` element with the `hidden` attribute is not considered visible.
|
|
4
|
+
*
|
|
5
|
+
* @see https://html.spec.whatwg.org/multipage/grouping-content.html#the-main-element
|
|
6
|
+
*/
|
|
7
|
+
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<boolean, null>>;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createRule } from '@markuplint/ml-core';
|
|
2
|
+
import meta from './meta.js';
|
|
3
|
+
/**
|
|
4
|
+
* Ensures no more than one visible `<main>` element exists in a document.
|
|
5
|
+
* A `<main>` element with the `hidden` attribute is not considered visible.
|
|
6
|
+
*
|
|
7
|
+
* @see https://html.spec.whatwg.org/multipage/grouping-content.html#the-main-element
|
|
8
|
+
*/
|
|
9
|
+
export default createRule({
|
|
10
|
+
meta: meta,
|
|
11
|
+
defaultValue: true,
|
|
12
|
+
defaultOptions: null,
|
|
13
|
+
async verify({ document, report, t }) {
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
15
|
+
const visibleMains = [];
|
|
16
|
+
await document.walkOn('Element', el => {
|
|
17
|
+
if (el.localName === 'main' && !el.hasAttribute('hidden')) {
|
|
18
|
+
visibleMains.push(el);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
for (const el of visibleMains.slice(1)) {
|
|
22
|
+
report({
|
|
23
|
+
scope: el,
|
|
24
|
+
message: t('There must not be more than one visible "{0}" element in a document', 'main'),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChildNode, Options, Result, Specs } from './types.js';
|
|
1
|
+
import type { ChildNode, Mode, Options, Result, Specs, TagRule } from './types.js';
|
|
2
2
|
import type { PermittedContentChoice } from '@markuplint/ml-spec';
|
|
3
3
|
import type { ReadonlyDeep } from 'type-fest';
|
|
4
4
|
/**
|
|
@@ -13,9 +13,12 @@ import type { ReadonlyDeep } from 'type-fest';
|
|
|
13
13
|
*
|
|
14
14
|
* @param pattern - The choice pattern containing multiple alternative content model branches.
|
|
15
15
|
* @param childNodes - The child nodes to validate against the choice branches.
|
|
16
|
+
* @param rules - User-defined tag rules. Threaded through for transparent-model recursion;
|
|
17
|
+
* not consulted here directly. See `order` for the rationale.
|
|
16
18
|
* @param specs - The resolved spec data for content model lookups.
|
|
17
19
|
* @param options - Validation behavior options.
|
|
18
20
|
* @param depth - The current recursion depth, used for debug logging and nested evaluation.
|
|
21
|
+
* @param mode - Whether we are evaluating the element's `'origin'` or `'pretended'` identity.
|
|
19
22
|
* @returns A result from the best-matching branch, or the branch that came closest to matching.
|
|
20
23
|
*/
|
|
21
|
-
export declare function choice(pattern: ReadonlyDeep<PermittedContentChoice>, childNodes: readonly ChildNode[], specs: Specs, options: Options, depth: number): Result;
|
|
24
|
+
export declare function choice(pattern: ReadonlyDeep<PermittedContentChoice>, childNodes: readonly ChildNode[], rules: readonly TagRule[], specs: Specs, options: Options, depth: number, mode: Mode): Result;
|
|
@@ -18,21 +18,24 @@ const indexes = new WeakMap();
|
|
|
18
18
|
*
|
|
19
19
|
* @param pattern - The choice pattern containing multiple alternative content model branches.
|
|
20
20
|
* @param childNodes - The child nodes to validate against the choice branches.
|
|
21
|
+
* @param rules - User-defined tag rules. Threaded through for transparent-model recursion;
|
|
22
|
+
* not consulted here directly. See `order` for the rationale.
|
|
21
23
|
* @param specs - The resolved spec data for content model lookups.
|
|
22
24
|
* @param options - Validation behavior options.
|
|
23
25
|
* @param depth - The current recursion depth, used for debug logging and nested evaluation.
|
|
26
|
+
* @param mode - Whether we are evaluating the element's `'origin'` or `'pretended'` identity.
|
|
24
27
|
* @returns A result from the best-matching branch, or the branch that came closest to matching.
|
|
25
28
|
*/
|
|
26
29
|
export function choice(pattern,
|
|
27
30
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
28
|
-
childNodes, specs, options, depth) {
|
|
31
|
+
childNodes, rules, specs, options, depth, mode) {
|
|
29
32
|
const choiceLog = cmLog.extend(`choice#${depth}`);
|
|
30
33
|
const collection = new Collection(childNodes);
|
|
31
34
|
const unmatchedResults = [];
|
|
32
35
|
let i = 0;
|
|
33
36
|
for (const some of pattern.choice) {
|
|
34
37
|
choiceLog('Patterns[%s]: %s', i, modelLog(some, ''));
|
|
35
|
-
const result = order(some, collection.unmatched, specs, options, depth + 1);
|
|
38
|
+
const result = order(some, collection.unmatched, rules, specs, options, depth + 1, mode);
|
|
36
39
|
if (result.type === 'MATCHED' ||
|
|
37
40
|
result.type === 'MATCHED_ZERO' ||
|
|
38
41
|
(result.type === 'UNEXPECTED_EXTRA_NODE' && result.matched.length > 0)) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChildNode, Options, Result, Specs } from './types.js';
|
|
1
|
+
import type { ChildNode, Mode, Options, Result, Specs, TagRule } from './types.js';
|
|
2
2
|
import type { PermittedContentPattern } from '@markuplint/ml-spec';
|
|
3
3
|
import type { ReadonlyDeep } from 'type-fest';
|
|
4
4
|
/**
|
|
@@ -10,9 +10,12 @@ import type { ReadonlyDeep } from 'type-fest';
|
|
|
10
10
|
*
|
|
11
11
|
* @param pattern - A single content model pattern to evaluate.
|
|
12
12
|
* @param childNodes - The child nodes to validate against the pattern.
|
|
13
|
+
* @param rules - User-defined tag rules. Threaded through for transparent-model recursion;
|
|
14
|
+
* not consulted here directly. See `order` for the rationale.
|
|
13
15
|
* @param specs - The resolved spec data for content model lookups.
|
|
14
16
|
* @param options - Validation behavior options.
|
|
15
17
|
* @param depth - The current recursion depth, used for debug logging and nested evaluation.
|
|
18
|
+
* @param mode - Whether we are evaluating the element's `'origin'` or `'pretended'` identity.
|
|
16
19
|
* @returns A result indicating whether the child nodes match the pattern.
|
|
17
20
|
*/
|
|
18
|
-
export declare function complexBranch(pattern: ReadonlyDeep<PermittedContentPattern>, childNodes: readonly ChildNode[], specs: Specs, options: Options, depth: number): Result;
|
|
21
|
+
export declare function complexBranch(pattern: ReadonlyDeep<PermittedContentPattern>, childNodes: readonly ChildNode[], rules: readonly TagRule[], specs: Specs, options: Options, depth: number, mode: Mode): Result;
|
|
@@ -11,19 +11,22 @@ import { isChoice, isTransparent } from './utils.js';
|
|
|
11
11
|
*
|
|
12
12
|
* @param pattern - A single content model pattern to evaluate.
|
|
13
13
|
* @param childNodes - The child nodes to validate against the pattern.
|
|
14
|
+
* @param rules - User-defined tag rules. Threaded through for transparent-model recursion;
|
|
15
|
+
* not consulted here directly. See `order` for the rationale.
|
|
14
16
|
* @param specs - The resolved spec data for content model lookups.
|
|
15
17
|
* @param options - Validation behavior options.
|
|
16
18
|
* @param depth - The current recursion depth, used for debug logging and nested evaluation.
|
|
19
|
+
* @param mode - Whether we are evaluating the element's `'origin'` or `'pretended'` identity.
|
|
17
20
|
* @returns A result indicating whether the child nodes match the pattern.
|
|
18
21
|
*/
|
|
19
22
|
export function complexBranch(pattern,
|
|
20
23
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
21
|
-
childNodes, specs, options, depth) {
|
|
24
|
+
childNodes, rules, specs, options, depth, mode) {
|
|
22
25
|
if (isChoice(pattern)) {
|
|
23
|
-
return choice(pattern, childNodes, specs, options, depth);
|
|
26
|
+
return choice(pattern, childNodes, rules, specs, options, depth, mode);
|
|
24
27
|
}
|
|
25
28
|
if (isTransparent(pattern)) {
|
|
26
29
|
return transparent(childNodes);
|
|
27
30
|
}
|
|
28
|
-
return countPattern(pattern, childNodes, specs, options, depth);
|
|
31
|
+
return countPattern(pattern, childNodes, rules, specs, options, depth, mode);
|
|
29
32
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type { ContentModelResult, Element, Options, TagRule } from './types.js';
|
|
1
|
+
import type { ContentModelResult, Element, Mode, Options, Specs, TagRule } from './types.js';
|
|
2
|
+
import type { ContentModel } from '@markuplint/ml-spec';
|
|
3
|
+
import type { ReadonlyDeep } from 'type-fest';
|
|
2
4
|
/**
|
|
3
5
|
* Top-level entry point for content model validation of a single element.
|
|
4
6
|
* Resolves the element's content model from the spec (possibly augmented
|
|
@@ -8,6 +10,23 @@ import type { ContentModelResult, Element, Options, TagRule } from './types.js';
|
|
|
8
10
|
* @param el - The element whose children are to be validated against its content model.
|
|
9
11
|
* @param rules - User-defined tag rules that can override or extend built-in content models.
|
|
10
12
|
* @param options - Validation behavior options (e.g., whether to ignore mutable children).
|
|
13
|
+
* @param mode - Which identity to evaluate the element as (`'origin'` consults user
|
|
14
|
+
* rules keyed on the pre-pretender AST name; `'pretended'` uses the
|
|
15
|
+
* HTML spec for the pretender target).
|
|
11
16
|
* @returns An array of content model results, one per child node issue found (empty if all valid).
|
|
12
17
|
*/
|
|
13
|
-
export declare function contentModel(el: Element, rules: readonly TagRule[], options: Options): ContentModelResult[];
|
|
18
|
+
export declare function contentModel(el: Element, rules: readonly TagRule[], options: Options, mode: Mode): ContentModelResult[];
|
|
19
|
+
/**
|
|
20
|
+
* Resolves the content model for an element while honoring the current
|
|
21
|
+
* {@link Mode}. In `'origin'` mode for a pretendered element, the lookup
|
|
22
|
+
* first consults user-defined tag rules keyed on the element's original AST
|
|
23
|
+
* name (`rawName`); if no such rule exists the returned model is `null`,
|
|
24
|
+
* which signals the caller to skip validation for this mode. In all other
|
|
25
|
+
* cases the standard spec lookup (`getContentModel`) is used, which keys on
|
|
26
|
+
* the visible `localName` — i.e. the pretender target when pretending is
|
|
27
|
+
* active.
|
|
28
|
+
*
|
|
29
|
+
* Exported so that {@link representTransparentNodes} can reuse the same
|
|
30
|
+
* resolution logic when recursing into child content models.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveContentModel(el: Element, rules: readonly TagRule[], specs: Specs, mode: Mode): ReadonlyDeep<ContentModel['contents']> | null;
|
|
@@ -9,12 +9,15 @@ import { start } from './start.js';
|
|
|
9
9
|
* @param el - The element whose children are to be validated against its content model.
|
|
10
10
|
* @param rules - User-defined tag rules that can override or extend built-in content models.
|
|
11
11
|
* @param options - Validation behavior options (e.g., whether to ignore mutable children).
|
|
12
|
+
* @param mode - Which identity to evaluate the element as (`'origin'` consults user
|
|
13
|
+
* rules keyed on the pre-pretender AST name; `'pretended'` uses the
|
|
14
|
+
* HTML spec for the pretender target).
|
|
12
15
|
* @returns An array of content model results, one per child node issue found (empty if all valid).
|
|
13
16
|
*/
|
|
14
17
|
export function contentModel(
|
|
15
18
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
16
|
-
el, rules, options) {
|
|
17
|
-
const { model, specs } = createModel(el, rules);
|
|
19
|
+
el, rules, options, mode) {
|
|
20
|
+
const { model, specs } = createModel(el, rules, mode);
|
|
18
21
|
if (model == null) {
|
|
19
22
|
return [
|
|
20
23
|
{
|
|
@@ -25,28 +28,51 @@ el, rules, options) {
|
|
|
25
28
|
},
|
|
26
29
|
];
|
|
27
30
|
}
|
|
28
|
-
const result = start(model, el, specs, options);
|
|
31
|
+
const result = start(model, el, rules, specs, options, mode);
|
|
29
32
|
return result;
|
|
30
33
|
}
|
|
31
34
|
/**
|
|
32
35
|
* Builds the content model and merged specs for a given element.
|
|
33
36
|
* Combines the element's document-level specs with any user-defined
|
|
34
|
-
* tag rules, then
|
|
37
|
+
* tag rules, then resolves the content model for the current {@link Mode}.
|
|
35
38
|
*
|
|
36
39
|
* @param el - The element to look up the content model for.
|
|
37
40
|
* @param rules - User-defined tag rules to merge into the spec.
|
|
41
|
+
* @param mode - Which identity to resolve against.
|
|
38
42
|
* @returns An object containing the resolved content model and merged specs.
|
|
39
43
|
*/
|
|
40
44
|
function createModel(
|
|
41
45
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
42
|
-
el, rules) {
|
|
46
|
+
el, rules, mode) {
|
|
43
47
|
const specs = cachedSpecs(el.ownerMLDocument.specs, rules);
|
|
44
|
-
const model =
|
|
48
|
+
const model = resolveContentModel(el, rules, specs, mode);
|
|
45
49
|
return {
|
|
46
50
|
model,
|
|
47
51
|
specs,
|
|
48
52
|
};
|
|
49
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Resolves the content model for an element while honoring the current
|
|
56
|
+
* {@link Mode}. In `'origin'` mode for a pretendered element, the lookup
|
|
57
|
+
* first consults user-defined tag rules keyed on the element's original AST
|
|
58
|
+
* name (`rawName`); if no such rule exists the returned model is `null`,
|
|
59
|
+
* which signals the caller to skip validation for this mode. In all other
|
|
60
|
+
* cases the standard spec lookup (`getContentModel`) is used, which keys on
|
|
61
|
+
* the visible `localName` — i.e. the pretender target when pretending is
|
|
62
|
+
* active.
|
|
63
|
+
*
|
|
64
|
+
* Exported so that {@link representTransparentNodes} can reuse the same
|
|
65
|
+
* resolution logic when recursing into child content models.
|
|
66
|
+
*/
|
|
67
|
+
export function resolveContentModel(
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
69
|
+
el, rules, specs, mode) {
|
|
70
|
+
if (mode === 'origin' && el.pretenderContext?.type === 'pretender') {
|
|
71
|
+
const userRule = rules.find(r => r.tag === el.rawName);
|
|
72
|
+
return userRule?.contents ?? null;
|
|
73
|
+
}
|
|
74
|
+
return getContentModel(el, specs.specs);
|
|
75
|
+
}
|
|
50
76
|
/**
|
|
51
77
|
* Cache for merged specs keyed by the JSON-serialized tag rules.
|
|
52
78
|
* Avoids re-merging the same set of user-defined tag rules on every element check.
|