@markuplint/ml-core 5.0.0-rc.6 → 5.0.0-rc.7
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 +6 -0
- package/lib/ml-core.js +19 -0
- package/lib/ml-dom/node/document.js +44 -2
- package/lib/ruleset/index.d.ts +24 -6
- package/lib/ruleset/index.js +26 -6
- package/package.json +12 -12
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
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.7](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.6...v5.0.0-rc.7) (2026-08-31)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **ml-core:** fix named rule group disable propagation and validation ([139f466](https://github.com/markuplint/markuplint/commit/139f4661a12ac10b15ade305c30bbd8234b07b0f))
|
|
11
|
+
|
|
6
12
|
# [5.0.0-rc.6](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.5...v5.0.0-rc.6) (2026-08-30)
|
|
7
13
|
|
|
8
14
|
### Features
|
package/lib/ml-core.js
CHANGED
|
@@ -94,6 +94,11 @@ export class MLCore {
|
|
|
94
94
|
nodeRules: nodeRuleResult.transformedNodeRules,
|
|
95
95
|
childNodeRules: childNodeRuleResult.transformedNodeRules,
|
|
96
96
|
baseRuleToVirtualNames: buildBaseRuleToVirtualNames(namedRulesResult.virtualRules),
|
|
97
|
+
baseRuleToScopedVirtualNames: buildBaseRuleToVirtualNames([
|
|
98
|
+
...nodeRuleResult.virtualRules,
|
|
99
|
+
...childNodeRuleResult.virtualRules,
|
|
100
|
+
]),
|
|
101
|
+
knownNamedRuleGroupKeys: ruleset.knownNamedRuleGroupKeys ?? new Set(),
|
|
97
102
|
mappingErrors: [],
|
|
98
103
|
};
|
|
99
104
|
this.#disabledNamespaces = extractDisabledNamespaces(resolvedRules);
|
|
@@ -151,6 +156,11 @@ export class MLCore {
|
|
|
151
156
|
nodeRules: nodeRuleResult.transformedNodeRules,
|
|
152
157
|
childNodeRules: childNodeRuleResult.transformedNodeRules,
|
|
153
158
|
baseRuleToVirtualNames: buildBaseRuleToVirtualNames(namedRulesResult.virtualRules),
|
|
159
|
+
baseRuleToScopedVirtualNames: buildBaseRuleToVirtualNames([
|
|
160
|
+
...nodeRuleResult.virtualRules,
|
|
161
|
+
...childNodeRuleResult.virtualRules,
|
|
162
|
+
]),
|
|
163
|
+
knownNamedRuleGroupKeys: ruleset?.knownNamedRuleGroupKeys ?? this.#ruleset.knownNamedRuleGroupKeys,
|
|
154
164
|
mappingErrors: [],
|
|
155
165
|
};
|
|
156
166
|
this.#disabledNamespaces = extractDisabledNamespaces(resolvedRules);
|
|
@@ -191,6 +201,15 @@ export class MLCore {
|
|
|
191
201
|
if (setRuleName.endsWith('/*')) {
|
|
192
202
|
continue;
|
|
193
203
|
}
|
|
204
|
+
// Skip a named rule group disabled via top-level `rules` (e.g. "html-standard/foo": false).
|
|
205
|
+
// `expandNamedRules` intentionally doesn't instantiate a virtual rule when the merged value
|
|
206
|
+
// already resolved to `false` (there's nothing to run), so it never appears in `definedRuleName`.
|
|
207
|
+
// `knownNamedRuleGroupKeys` (threaded from config merge — see its JSDoc) tells a genuine
|
|
208
|
+
// named-group disable apart from a typo'd/nonexistent rule name that also happens to be
|
|
209
|
+
// set to `false` with a `/` in its key; only the former is exempt from this check.
|
|
210
|
+
if (this.#ruleset.rules[setRuleName] === false && this.#ruleset.knownNamedRuleGroupKeys.has(setRuleName)) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
194
213
|
if (!definedRuleName.has(setRuleName)) {
|
|
195
214
|
configViolations.push({
|
|
196
215
|
ruleId: CONFIG_ERROR_RULE_ID,
|
|
@@ -2440,7 +2440,9 @@ export class MLDocument extends MLParentNode {
|
|
|
2440
2440
|
specificity: matches.specificity,
|
|
2441
2441
|
rule: mergedRule,
|
|
2442
2442
|
});
|
|
2443
|
-
// Propagate to virtual rules that wrap this base rule
|
|
2443
|
+
// Propagate to top-level-group virtual rules that wrap this base rule. These
|
|
2444
|
+
// wrappers have no selector scope of their own (they mirror the base rule
|
|
2445
|
+
// globally), so any matched value is safe to forward.
|
|
2444
2446
|
const virtualNames = ruleset.baseRuleToVirtualNames.get(ruleName);
|
|
2445
2447
|
if (virtualNames) {
|
|
2446
2448
|
for (const vName of virtualNames) {
|
|
@@ -2453,6 +2455,26 @@ export class MLDocument extends MLParentNode {
|
|
|
2453
2455
|
});
|
|
2454
2456
|
}
|
|
2455
2457
|
}
|
|
2458
|
+
// Propagate a disable to selector-scoped (nodeRules/childNodeRules-named) virtual
|
|
2459
|
+
// rules that wrap this base rule. Limited to `false`: unlike the top-level-group
|
|
2460
|
+
// case above, these wrappers DO have their own selector scope, so forwarding a
|
|
2461
|
+
// non-false value would apply their semantics (e.g. a required attribute name) to
|
|
2462
|
+
// every node this (unrelated) nodeRule matches, even where the virtual rule's own
|
|
2463
|
+
// selector never matched — see issue #4023's fix history for the regression this caused.
|
|
2464
|
+
if (convertedRule === false) {
|
|
2465
|
+
const scopedVirtualNames = ruleset.baseRuleToScopedVirtualNames.get(ruleName);
|
|
2466
|
+
if (scopedVirtualNames) {
|
|
2467
|
+
for (const vName of scopedVirtualNames) {
|
|
2468
|
+
// No merge needed: mergeRule(_, false) always returns false, so the
|
|
2469
|
+
// disable is unconditional regardless of any global config for vName.
|
|
2470
|
+
ruleMapper.set(node, vName, {
|
|
2471
|
+
from: 'nodeRules',
|
|
2472
|
+
specificity: matches.specificity,
|
|
2473
|
+
rule: false,
|
|
2474
|
+
});
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2456
2478
|
}
|
|
2457
2479
|
}
|
|
2458
2480
|
// overwrite rule to child node
|
|
@@ -2518,7 +2540,8 @@ export class MLDocument extends MLParentNode {
|
|
|
2518
2540
|
rule: mergedRule,
|
|
2519
2541
|
});
|
|
2520
2542
|
}
|
|
2521
|
-
// Propagate to virtual rules
|
|
2543
|
+
// Propagate to top-level-group virtual rules — see the matching nodeRules
|
|
2544
|
+
// propagation above for why any value is safe to forward here.
|
|
2522
2545
|
const virtualNames = ruleset.baseRuleToVirtualNames.get(ruleName);
|
|
2523
2546
|
if (virtualNames) {
|
|
2524
2547
|
for (const vName of virtualNames) {
|
|
@@ -2533,6 +2556,25 @@ export class MLDocument extends MLParentNode {
|
|
|
2533
2556
|
}
|
|
2534
2557
|
}
|
|
2535
2558
|
}
|
|
2559
|
+
// Propagate a disable to selector-scoped virtual rules. Limited to `false` —
|
|
2560
|
+
// see the matching nodeRules propagation above for why a non-false value must
|
|
2561
|
+
// not be forwarded to a virtual rule whose own selector didn't match.
|
|
2562
|
+
if (convertedRule === false) {
|
|
2563
|
+
const scopedVirtualNames = ruleset.baseRuleToScopedVirtualNames.get(ruleName);
|
|
2564
|
+
if (scopedVirtualNames) {
|
|
2565
|
+
for (const vName of scopedVirtualNames) {
|
|
2566
|
+
// No merge needed: mergeRule(_, false) always returns false, so the
|
|
2567
|
+
// disable is unconditional regardless of any global config for vName.
|
|
2568
|
+
for (const descendant of targetDescendants) {
|
|
2569
|
+
ruleMapper.set(descendant, vName, {
|
|
2570
|
+
from: 'childNodeRules',
|
|
2571
|
+
specificity: matches.specificity,
|
|
2572
|
+
rule: false,
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2536
2578
|
}
|
|
2537
2579
|
}
|
|
2538
2580
|
}
|
package/lib/ruleset/index.d.ts
CHANGED
|
@@ -5,14 +5,31 @@ import type { ChildNodeRule, Config, NodeRule, Rules } from '@markuplint/ml-conf
|
|
|
5
5
|
*/
|
|
6
6
|
export declare class Ruleset {
|
|
7
7
|
/**
|
|
8
|
-
* Maps base rule names to
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Maps base rule names to the virtual rule names created by top-level `rules`
|
|
9
|
+
* NamedRuleGroups (e.g. `"a11y/landmark-roles": { rules: { "no-nested-top-level-landmark": true } }`).
|
|
10
|
+
* These wrappers have no selector scope of their own — they mirror the base rule
|
|
11
|
+
* globally — so nodeRules/childNodeRules may propagate *any* matched value to them,
|
|
12
|
+
* not just a disable.
|
|
12
13
|
*/
|
|
13
14
|
readonly baseRuleToVirtualNames: ReadonlyMap<string, readonly string[]>;
|
|
15
|
+
/**
|
|
16
|
+
* Maps base rule names to the virtual rule names created by `nodeRules[].name` /
|
|
17
|
+
* `childNodeRules[].name` (selector-scoped named groups), as opposed to
|
|
18
|
+
* {@link baseRuleToVirtualNames}'s top-level `rules` groups. Because these wrappers
|
|
19
|
+
* DO have their own selector scope, only a disable (`false`) may be propagated to
|
|
20
|
+
* them from an unrelated nodeRules/childNodeRules entry — forwarding a non-false
|
|
21
|
+
* value would apply the wrapper's semantics to nodes its own selector never matched
|
|
22
|
+
* (see issue #4023's fix history).
|
|
23
|
+
*/
|
|
24
|
+
readonly baseRuleToScopedVirtualNames: ReadonlyMap<string, readonly string[]>;
|
|
14
25
|
/** Rule overrides that apply to child nodes matching specific selectors */
|
|
15
26
|
readonly childNodeRules: readonly ChildNodeRule[];
|
|
27
|
+
/**
|
|
28
|
+
* `rules` keys that are a genuine NamedRuleGroup in some layer of the
|
|
29
|
+
* `extends`/override chain, carried over from {@link Config.knownNamedRuleGroupKeys}
|
|
30
|
+
* even where the final merged value collapsed to `false`. See that field's JSDoc.
|
|
31
|
+
*/
|
|
32
|
+
readonly knownNamedRuleGroupKeys: ReadonlySet<string>;
|
|
16
33
|
/**
|
|
17
34
|
* Errors collected during rule mapping (e.g., invalid wildcard usage).
|
|
18
35
|
* Consumed by MLCore and reported as config-error violations.
|
|
@@ -24,7 +41,8 @@ export declare class Ruleset {
|
|
|
24
41
|
readonly rules: Rules;
|
|
25
42
|
/**
|
|
26
43
|
* @param config - The markuplint configuration to extract rules from
|
|
27
|
-
* @param baseRuleToVirtualNames - Mapping from base rule names to virtual rule names
|
|
44
|
+
* @param baseRuleToVirtualNames - Mapping from base rule names to top-level-group virtual rule names
|
|
45
|
+
* @param baseRuleToScopedVirtualNames - Mapping from base rule names to selector-scoped (nodeRules/childNodeRules) virtual rule names
|
|
28
46
|
*/
|
|
29
|
-
constructor(config: Config, baseRuleToVirtualNames?: ReadonlyMap<string, readonly string[]>);
|
|
47
|
+
constructor(config: Config, baseRuleToVirtualNames?: ReadonlyMap<string, readonly string[]>, baseRuleToScopedVirtualNames?: ReadonlyMap<string, readonly string[]>);
|
|
30
48
|
}
|
package/lib/ruleset/index.js
CHANGED
|
@@ -4,14 +4,31 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export class Ruleset {
|
|
6
6
|
/**
|
|
7
|
-
* Maps base rule names to
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Maps base rule names to the virtual rule names created by top-level `rules`
|
|
8
|
+
* NamedRuleGroups (e.g. `"a11y/landmark-roles": { rules: { "no-nested-top-level-landmark": true } }`).
|
|
9
|
+
* These wrappers have no selector scope of their own — they mirror the base rule
|
|
10
|
+
* globally — so nodeRules/childNodeRules may propagate *any* matched value to them,
|
|
11
|
+
* not just a disable.
|
|
11
12
|
*/
|
|
12
13
|
baseRuleToVirtualNames;
|
|
14
|
+
/**
|
|
15
|
+
* Maps base rule names to the virtual rule names created by `nodeRules[].name` /
|
|
16
|
+
* `childNodeRules[].name` (selector-scoped named groups), as opposed to
|
|
17
|
+
* {@link baseRuleToVirtualNames}'s top-level `rules` groups. Because these wrappers
|
|
18
|
+
* DO have their own selector scope, only a disable (`false`) may be propagated to
|
|
19
|
+
* them from an unrelated nodeRules/childNodeRules entry — forwarding a non-false
|
|
20
|
+
* value would apply the wrapper's semantics to nodes its own selector never matched
|
|
21
|
+
* (see issue #4023's fix history).
|
|
22
|
+
*/
|
|
23
|
+
baseRuleToScopedVirtualNames;
|
|
13
24
|
/** Rule overrides that apply to child nodes matching specific selectors */
|
|
14
25
|
childNodeRules;
|
|
26
|
+
/**
|
|
27
|
+
* `rules` keys that are a genuine NamedRuleGroup in some layer of the
|
|
28
|
+
* `extends`/override chain, carried over from {@link Config.knownNamedRuleGroupKeys}
|
|
29
|
+
* even where the final merged value collapsed to `false`. See that field's JSDoc.
|
|
30
|
+
*/
|
|
31
|
+
knownNamedRuleGroupKeys;
|
|
15
32
|
/**
|
|
16
33
|
* Errors collected during rule mapping (e.g., invalid wildcard usage).
|
|
17
34
|
* Consumed by MLCore and reported as config-error violations.
|
|
@@ -23,12 +40,15 @@ export class Ruleset {
|
|
|
23
40
|
rules;
|
|
24
41
|
/**
|
|
25
42
|
* @param config - The markuplint configuration to extract rules from
|
|
26
|
-
* @param baseRuleToVirtualNames - Mapping from base rule names to virtual rule names
|
|
43
|
+
* @param baseRuleToVirtualNames - Mapping from base rule names to top-level-group virtual rule names
|
|
44
|
+
* @param baseRuleToScopedVirtualNames - Mapping from base rule names to selector-scoped (nodeRules/childNodeRules) virtual rule names
|
|
27
45
|
*/
|
|
28
|
-
constructor(config, baseRuleToVirtualNames) {
|
|
46
|
+
constructor(config, baseRuleToVirtualNames, baseRuleToScopedVirtualNames) {
|
|
29
47
|
this.rules = config.rules ?? {};
|
|
30
48
|
this.nodeRules = config.nodeRules ?? [];
|
|
31
49
|
this.childNodeRules = config.childNodeRules ?? [];
|
|
32
50
|
this.baseRuleToVirtualNames = baseRuleToVirtualNames ?? new Map();
|
|
51
|
+
this.baseRuleToScopedVirtualNames = baseRuleToScopedVirtualNames ?? new Map();
|
|
52
|
+
this.knownNamedRuleGroupKeys = new Set(config.knownNamedRuleGroupKeys);
|
|
33
53
|
}
|
|
34
54
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/ml-core",
|
|
3
|
-
"version": "5.0.0-rc.
|
|
3
|
+
"version": "5.0.0-rc.7",
|
|
4
4
|
"description": "The core module of markuplint",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,20 +34,20 @@
|
|
|
34
34
|
"./lib/configs.js": "./lib/configs.browser.js"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@markuplint/config-presets": "5.0.0-rc.
|
|
38
|
-
"@markuplint/html-parser": "5.0.0-rc.
|
|
39
|
-
"@markuplint/html-spec": "5.0.0-rc.
|
|
40
|
-
"@markuplint/i18n": "5.0.0-rc.
|
|
41
|
-
"@markuplint/ml-ast": "5.0.0-rc.
|
|
42
|
-
"@markuplint/ml-config": "5.0.0-rc.
|
|
43
|
-
"@markuplint/ml-spec": "5.0.0-rc.
|
|
44
|
-
"@markuplint/parser-utils": "5.0.0-rc.
|
|
45
|
-
"@markuplint/selector": "5.0.0-rc.
|
|
46
|
-
"@markuplint/shared": "5.0.0-rc.
|
|
37
|
+
"@markuplint/config-presets": "5.0.0-rc.7",
|
|
38
|
+
"@markuplint/html-parser": "5.0.0-rc.7",
|
|
39
|
+
"@markuplint/html-spec": "5.0.0-rc.7",
|
|
40
|
+
"@markuplint/i18n": "5.0.0-rc.7",
|
|
41
|
+
"@markuplint/ml-ast": "5.0.0-rc.7",
|
|
42
|
+
"@markuplint/ml-config": "5.0.0-rc.7",
|
|
43
|
+
"@markuplint/ml-spec": "5.0.0-rc.7",
|
|
44
|
+
"@markuplint/parser-utils": "5.0.0-rc.7",
|
|
45
|
+
"@markuplint/selector": "5.0.0-rc.7",
|
|
46
|
+
"@markuplint/shared": "5.0.0-rc.7",
|
|
47
47
|
"@types/debug": "4.1.13",
|
|
48
48
|
"debug": "4.4.3",
|
|
49
49
|
"is-plain-object": "5.0.0",
|
|
50
50
|
"type-fest": "5.6.0"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "8d7a3b801e0f5f88438c003e7eee2bd45e6aedd0"
|
|
53
53
|
}
|