@markuplint/ml-core 5.0.0-rc.5 → 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 +33 -0
- package/lib/ml-core.d.ts +16 -2
- package/lib/ml-core.js +103 -32
- 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/lib/types.d.ts +8 -1
- package/package.json +12 -12
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,39 @@
|
|
|
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
|
+
|
|
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)
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
- split rule-deprecation notices out of config-error ([#4013](https://github.com/markuplint/markuplint/issues/4013)) ([812e6f3](https://github.com/markuplint/markuplint/commit/812e6f356839af8f257cfd91e6b16cfdfdd7cf33))
|
|
17
|
+
|
|
18
|
+
### BREAKING CHANGES
|
|
19
|
+
|
|
20
|
+
- violations for deprecated rule names now have
|
|
21
|
+
`ruleId: 'rule-deprecation'` instead of `ruleId: 'config-error'`. Any
|
|
22
|
+
consumer filtering `MLCore.verify()` output (or the markuplint CLI/API) by
|
|
23
|
+
`ruleId === 'config-error'` to catch deprecation messages must also check
|
|
24
|
+
for `rule-deprecation`.
|
|
25
|
+
|
|
26
|
+
- feat(markuplint): add --severity-deprecation CLI flag
|
|
27
|
+
|
|
28
|
+
Wires the new severity.deprecation config option (@markuplint/ml-config)
|
|
29
|
+
and the rule-deprecation ruleId (@markuplint/ml-core) through the CLI:
|
|
30
|
+
|
|
31
|
+
- --severity-deprecation flag, mirroring --severity-parse-error
|
|
32
|
+
- --show-config details now also surfaces ruleDeprecations
|
|
33
|
+
- per-run dedupe and failed-file counting generalized to cover both
|
|
34
|
+
config-level ruleIds (config-error and rule-deprecation), not just
|
|
35
|
+
config-error
|
|
36
|
+
|
|
37
|
+
* docs(website): document severity.deprecation (EN + JA)
|
|
38
|
+
|
|
6
39
|
# [5.0.0-rc.5](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.4...v5.0.0-rc.5) (2026-08-28)
|
|
7
40
|
|
|
8
41
|
### Bug Fixes
|
package/lib/ml-core.d.ts
CHANGED
|
@@ -2,6 +2,20 @@ import type { MLFabric } from './types.js';
|
|
|
2
2
|
import type { PlainData, RuleConfigValue, TextEdit, Violation } from '@markuplint/ml-config';
|
|
3
3
|
import { ParserError } from '@markuplint/parser-utils';
|
|
4
4
|
import { Document } from './ml-dom/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* `ruleId` of a genuinely broken config (unresolved rule reference, plugin
|
|
7
|
+
* resolution failure, ...). Exported so consumers that need to recognize
|
|
8
|
+
* config-level violations (e.g. the CLI's per-run dedupe and failed-file
|
|
9
|
+
* counting in `packages/markuplint/src/cli/command.ts`) reference the same
|
|
10
|
+
* literal `MLCore.verify()` emits, instead of duplicating the string.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CONFIG_ERROR_RULE_ID = "config-error";
|
|
13
|
+
/**
|
|
14
|
+
* `ruleId` of a deprecated-but-working rule name notice — see
|
|
15
|
+
* {@link CONFIG_ERROR_RULE_ID} for why this is exported rather than a
|
|
16
|
+
* private literal.
|
|
17
|
+
*/
|
|
18
|
+
export declare const RULE_DEPRECATION_RULE_ID = "rule-deprecation";
|
|
5
19
|
/**
|
|
6
20
|
* Summary of the multi-pass fix process.
|
|
7
21
|
*/
|
|
@@ -74,7 +88,7 @@ export type MLCoreParams = {
|
|
|
74
88
|
*/
|
|
75
89
|
export declare class MLCore {
|
|
76
90
|
#private;
|
|
77
|
-
constructor({ parser, sourceCode, ruleset, rules, locale, schemas, ruleCommonSettings, parserOptions, severity, pretenders, filename, debug, configErrors, }: MLCoreParams);
|
|
91
|
+
constructor({ parser, sourceCode, ruleset, rules, locale, schemas, ruleCommonSettings, parserOptions, severity, pretenders, filename, debug, configErrors, ruleDeprecations, }: MLCoreParams);
|
|
78
92
|
/**
|
|
79
93
|
* The parsed document, or a {@link ParserError} if parsing failed.
|
|
80
94
|
*/
|
|
@@ -91,7 +105,7 @@ export declare class MLCore {
|
|
|
91
105
|
*
|
|
92
106
|
* @param fabric - Partial fabric with the properties to update
|
|
93
107
|
*/
|
|
94
|
-
update({ parser, ruleset, rules, locale, schemas, parserOptions, pretenders, configErrors }: Partial<MLFabric>): void;
|
|
108
|
+
update({ parser, ruleset, rules, locale, schemas, parserOptions, pretenders, configErrors, ruleDeprecations, }: Partial<MLFabric>): void;
|
|
95
109
|
/**
|
|
96
110
|
* Runs all configured rules against the parsed document and returns violations.
|
|
97
111
|
*
|
package/lib/ml-core.js
CHANGED
|
@@ -4,6 +4,20 @@ import { applyFixes } from './fix-applier.js';
|
|
|
4
4
|
import { Document } from './ml-dom/index.js';
|
|
5
5
|
import { expandNamedNodeRules, expandNamedRules } from './virtual-rule.js';
|
|
6
6
|
const resultLog = log.extend('result');
|
|
7
|
+
/**
|
|
8
|
+
* `ruleId` of a genuinely broken config (unresolved rule reference, plugin
|
|
9
|
+
* resolution failure, ...). Exported so consumers that need to recognize
|
|
10
|
+
* config-level violations (e.g. the CLI's per-run dedupe and failed-file
|
|
11
|
+
* counting in `packages/markuplint/src/cli/command.ts`) reference the same
|
|
12
|
+
* literal `MLCore.verify()` emits, instead of duplicating the string.
|
|
13
|
+
*/
|
|
14
|
+
export const CONFIG_ERROR_RULE_ID = 'config-error';
|
|
15
|
+
/**
|
|
16
|
+
* `ruleId` of a deprecated-but-working rule name notice — see
|
|
17
|
+
* {@link CONFIG_ERROR_RULE_ID} for why this is exported rather than a
|
|
18
|
+
* private literal.
|
|
19
|
+
*/
|
|
20
|
+
export const RULE_DEPRECATION_RULE_ID = 'rule-deprecation';
|
|
7
21
|
/**
|
|
8
22
|
* The core linting engine for markuplint.
|
|
9
23
|
*
|
|
@@ -29,6 +43,13 @@ export class MLCore {
|
|
|
29
43
|
* Set once per construction/`update()` and never mutated by re-parsing.
|
|
30
44
|
*/
|
|
31
45
|
#configErrors;
|
|
46
|
+
/**
|
|
47
|
+
* Deprecated-rule-name notices, kept structured and separate from
|
|
48
|
+
* `#configErrors` so `verify()` can report them under their own
|
|
49
|
+
* `rule-deprecation` ruleId. Set once per construction/`update()`, same
|
|
50
|
+
* lifecycle as `#configErrors`.
|
|
51
|
+
*/
|
|
52
|
+
#ruleDeprecations;
|
|
32
53
|
/**
|
|
33
54
|
* Rule-mapping errors for the CURRENT document. Reset (not accumulated) on
|
|
34
55
|
* every `#createDocument()` so repeated `setCode()` calls don't duplicate
|
|
@@ -44,7 +65,7 @@ export class MLCore {
|
|
|
44
65
|
#originalNodeRules;
|
|
45
66
|
#originalChildNodeRules;
|
|
46
67
|
#disabledNamespaces;
|
|
47
|
-
constructor({ parser, sourceCode, ruleset, rules, locale, schemas, ruleCommonSettings, parserOptions, severity, pretenders, filename, debug, configErrors, }) {
|
|
68
|
+
constructor({ parser, sourceCode, ruleset, rules, locale, schemas, ruleCommonSettings, parserOptions, severity, pretenders, filename, debug, configErrors, ruleDeprecations, }) {
|
|
48
69
|
if (debug) {
|
|
49
70
|
enableDebug();
|
|
50
71
|
}
|
|
@@ -58,6 +79,7 @@ export class MLCore {
|
|
|
58
79
|
this.#severity = severity;
|
|
59
80
|
this.#pretenders = [...pretenders];
|
|
60
81
|
this.#configErrors = [...(configErrors ?? [])];
|
|
82
|
+
this.#ruleDeprecations = [...(ruleDeprecations ?? [])];
|
|
61
83
|
// Preserve pre-expansion nodeRules for hot-reload
|
|
62
84
|
this.#originalNodeRules = ruleset.nodeRules ?? [];
|
|
63
85
|
this.#originalChildNodeRules = ruleset.childNodeRules ?? [];
|
|
@@ -72,6 +94,11 @@ export class MLCore {
|
|
|
72
94
|
nodeRules: nodeRuleResult.transformedNodeRules,
|
|
73
95
|
childNodeRules: childNodeRuleResult.transformedNodeRules,
|
|
74
96
|
baseRuleToVirtualNames: buildBaseRuleToVirtualNames(namedRulesResult.virtualRules),
|
|
97
|
+
baseRuleToScopedVirtualNames: buildBaseRuleToVirtualNames([
|
|
98
|
+
...nodeRuleResult.virtualRules,
|
|
99
|
+
...childNodeRuleResult.virtualRules,
|
|
100
|
+
]),
|
|
101
|
+
knownNamedRuleGroupKeys: ruleset.knownNamedRuleGroupKeys ?? new Set(),
|
|
75
102
|
mappingErrors: [],
|
|
76
103
|
};
|
|
77
104
|
this.#disabledNamespaces = extractDisabledNamespaces(resolvedRules);
|
|
@@ -101,12 +128,13 @@ export class MLCore {
|
|
|
101
128
|
*
|
|
102
129
|
* @param fabric - Partial fabric with the properties to update
|
|
103
130
|
*/
|
|
104
|
-
update({ parser, ruleset, rules, locale, schemas, parserOptions, pretenders, configErrors }) {
|
|
131
|
+
update({ parser, ruleset, rules, locale, schemas, parserOptions, pretenders, configErrors, ruleDeprecations, }) {
|
|
105
132
|
this.#parser = parser ?? this.#parser;
|
|
106
133
|
this.#locale = locale ?? this.#locale;
|
|
107
134
|
this.#schemas = schemas ?? this.#schemas;
|
|
108
135
|
this.#pretenders = pretenders ? [...pretenders] : this.#pretenders;
|
|
109
136
|
this.#configErrors = [...(configErrors ?? [])];
|
|
137
|
+
this.#ruleDeprecations = [...(ruleDeprecations ?? [])];
|
|
110
138
|
const baseRules = rules ? [...rules] : this.#rules.filter(r => !r.baseRuleId);
|
|
111
139
|
const incomingNodeRules = ruleset?.nodeRules ?? this.#originalNodeRules;
|
|
112
140
|
const incomingChildNodeRules = ruleset?.childNodeRules ?? this.#originalChildNodeRules;
|
|
@@ -128,6 +156,11 @@ export class MLCore {
|
|
|
128
156
|
nodeRules: nodeRuleResult.transformedNodeRules,
|
|
129
157
|
childNodeRules: childNodeRuleResult.transformedNodeRules,
|
|
130
158
|
baseRuleToVirtualNames: buildBaseRuleToVirtualNames(namedRulesResult.virtualRules),
|
|
159
|
+
baseRuleToScopedVirtualNames: buildBaseRuleToVirtualNames([
|
|
160
|
+
...nodeRuleResult.virtualRules,
|
|
161
|
+
...childNodeRuleResult.virtualRules,
|
|
162
|
+
]),
|
|
163
|
+
knownNamedRuleGroupKeys: ruleset?.knownNamedRuleGroupKeys ?? this.#ruleset.knownNamedRuleGroupKeys,
|
|
131
164
|
mappingErrors: [],
|
|
132
165
|
};
|
|
133
166
|
this.#disabledNamespaces = extractDisabledNamespaces(resolvedRules);
|
|
@@ -168,9 +201,18 @@ export class MLCore {
|
|
|
168
201
|
if (setRuleName.endsWith('/*')) {
|
|
169
202
|
continue;
|
|
170
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
|
+
}
|
|
171
213
|
if (!definedRuleName.has(setRuleName)) {
|
|
172
214
|
configViolations.push({
|
|
173
|
-
ruleId:
|
|
215
|
+
ruleId: CONFIG_ERROR_RULE_ID,
|
|
174
216
|
severity: 'warning',
|
|
175
217
|
message: `Rule not found: ${setRuleName}`,
|
|
176
218
|
col: 1,
|
|
@@ -181,7 +223,7 @@ export class MLCore {
|
|
|
181
223
|
}
|
|
182
224
|
for (const error of [...this.#configErrors, ...this.#mappingErrors]) {
|
|
183
225
|
configViolations.push({
|
|
184
|
-
ruleId:
|
|
226
|
+
ruleId: CONFIG_ERROR_RULE_ID,
|
|
185
227
|
severity: 'warning',
|
|
186
228
|
message: error.message,
|
|
187
229
|
col: 1,
|
|
@@ -189,6 +231,19 @@ export class MLCore {
|
|
|
189
231
|
raw: '',
|
|
190
232
|
});
|
|
191
233
|
}
|
|
234
|
+
const deprecationSeverity = this.#resolveDeprecationSeverity();
|
|
235
|
+
if (deprecationSeverity != null) {
|
|
236
|
+
for (const { deprecatedName, replacedBy } of this.#ruleDeprecations) {
|
|
237
|
+
configViolations.push({
|
|
238
|
+
ruleId: RULE_DEPRECATION_RULE_ID,
|
|
239
|
+
severity: deprecationSeverity,
|
|
240
|
+
message: `Rule "${deprecatedName}" is deprecated and will be removed in v6. Use ${replacedBy.join(', ')} instead.`,
|
|
241
|
+
col: 1,
|
|
242
|
+
line: 1,
|
|
243
|
+
raw: '',
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
192
247
|
violations.push(...configViolations);
|
|
193
248
|
const ruleViolations = await this.#runAllRules(fix);
|
|
194
249
|
violations.push(...ruleViolations);
|
|
@@ -363,6 +418,22 @@ export class MLCore {
|
|
|
363
418
|
}
|
|
364
419
|
}
|
|
365
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* Resolves `severity.deprecation`, honouring its single-value form only
|
|
423
|
+
* (unlike `severity.parseError`, there's no fixed enum of deprecated rule
|
|
424
|
+
* names to key a per-code `Record` on).
|
|
425
|
+
*
|
|
426
|
+
* Unlike `#createParseError`'s parse-error channel, this defaults to
|
|
427
|
+
* `'warning'` (not off/suppressed) when unset — the deprecation channel
|
|
428
|
+
* is being carved out of the always-on `config-error` channel, not
|
|
429
|
+
* introduced cold, so leaving the option unset must not silence a notice
|
|
430
|
+
* users already see today.
|
|
431
|
+
*
|
|
432
|
+
* @returns the resolved severity, or `null` if suppressed.
|
|
433
|
+
*/
|
|
434
|
+
#resolveDeprecationSeverity() {
|
|
435
|
+
return resolveUniformSeverity(this.#severity.deprecation, 'warning');
|
|
436
|
+
}
|
|
366
437
|
/**
|
|
367
438
|
* Builds a `ruleId: 'parse-error'` violation, honouring
|
|
368
439
|
* `severity.parseError`.
|
|
@@ -377,36 +448,18 @@ export class MLCore {
|
|
|
377
448
|
*/
|
|
378
449
|
#createParseError(message, line, col, raw, code) {
|
|
379
450
|
const cfg = this.#severity.parseError;
|
|
380
|
-
|
|
451
|
+
// Fatal ParserErrors (no `code`) can't be targeted by the per-code
|
|
452
|
+
// Record form, so they fall back to `'error'` there; under the
|
|
453
|
+
// uniform form they default to `'error'` too, while non-fatal entries
|
|
454
|
+
// default to suppressed when unset — see `resolveUniformSeverity`.
|
|
455
|
+
const severity = typeof cfg === 'object'
|
|
456
|
+
? code == null
|
|
457
|
+
? 'error'
|
|
458
|
+
: resolveUniformSeverity(cfg[code], null)
|
|
459
|
+
: resolveUniformSeverity(cfg, code == null ? 'error' : null);
|
|
460
|
+
if (severity == null) {
|
|
381
461
|
return null;
|
|
382
462
|
}
|
|
383
|
-
let severity;
|
|
384
|
-
if (typeof cfg === 'object') {
|
|
385
|
-
if (code == null) {
|
|
386
|
-
// Fatal ParserError without a code; the Record form cannot target
|
|
387
|
-
// it, so fall back to `'error'` (the channel is otherwise enabled).
|
|
388
|
-
severity = 'error';
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
const perCode = cfg[code];
|
|
392
|
-
if (perCode == null || perCode === false || perCode === 'off') {
|
|
393
|
-
return null;
|
|
394
|
-
}
|
|
395
|
-
severity = perCode === true ? 'error' : perCode;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
else if (cfg == null) {
|
|
399
|
-
// New default: non-fatal `parseErrors` entries are off; fatal
|
|
400
|
-
// `ParserError`s (no code) still emit at `'error'`.
|
|
401
|
-
if (code != null) {
|
|
402
|
-
return null;
|
|
403
|
-
}
|
|
404
|
-
severity = 'error';
|
|
405
|
-
}
|
|
406
|
-
else {
|
|
407
|
-
// Uniform string/boolean form (legacy).
|
|
408
|
-
severity = cfg === true ? 'error' : cfg;
|
|
409
|
-
}
|
|
410
463
|
return {
|
|
411
464
|
ruleId: 'parse-error',
|
|
412
465
|
severity,
|
|
@@ -597,6 +650,24 @@ export class MLCore {
|
|
|
597
650
|
}
|
|
598
651
|
}
|
|
599
652
|
}
|
|
653
|
+
/**
|
|
654
|
+
* Normalizes a single-value `Severity | 'off' | boolean | undefined` option
|
|
655
|
+
* (the shape `severity.deprecation` and `severity.parseError`'s uniform/
|
|
656
|
+
* per-code leaf values share) to a resolved severity or `null` (suppressed).
|
|
657
|
+
*
|
|
658
|
+
* Shared so the same three-way rule — `false`/`'off'` → suppressed, `true` →
|
|
659
|
+
* `'error'`, unset → `defaultWhenUnset` — isn't reimplemented at each call
|
|
660
|
+
* site as channels using this shape are added.
|
|
661
|
+
*/
|
|
662
|
+
function resolveUniformSeverity(cfg, defaultWhenUnset) {
|
|
663
|
+
if (cfg === false || cfg === 'off') {
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
if (cfg == null) {
|
|
667
|
+
return defaultWhenUnset;
|
|
668
|
+
}
|
|
669
|
+
return cfg === true ? 'error' : cfg;
|
|
670
|
+
}
|
|
600
671
|
function extractDisabledNamespaces(rules) {
|
|
601
672
|
return Object.entries(rules)
|
|
602
673
|
.filter(([key, value]) => key.endsWith('/*') && value === false)
|
|
@@ -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/lib/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { AnyMLRule } from './ml-rule/index.js';
|
|
|
2
2
|
import type { Ruleset } from './ruleset/index.js';
|
|
3
3
|
import type { LocaleSet } from '@markuplint/i18n';
|
|
4
4
|
import type { MLParser, ParserOptions } from '@markuplint/ml-ast';
|
|
5
|
-
import type { Pretender, RuleCommonSettings, SeverityOptions } from '@markuplint/ml-config';
|
|
5
|
+
import type { Pretender, RuleAliasWarning, RuleCommonSettings, SeverityOptions } from '@markuplint/ml-config';
|
|
6
6
|
import type { ExtendedSpec, MLMLSpec } from '@markuplint/ml-spec';
|
|
7
7
|
/**
|
|
8
8
|
* A tuple of the base HTML/ARIA specification and zero or more
|
|
@@ -24,4 +24,11 @@ export type MLFabric = {
|
|
|
24
24
|
readonly severity: SeverityOptions;
|
|
25
25
|
readonly pretenders: readonly Pretender[];
|
|
26
26
|
readonly configErrors?: readonly Readonly<Error>[];
|
|
27
|
+
/**
|
|
28
|
+
* Deprecated-rule-name notices found while applying the rule-alias table
|
|
29
|
+
* (v5 rule-system redesign, #3989). Kept structured, separate from
|
|
30
|
+
* {@link configErrors}, so `MLCore.verify()` can report them under their
|
|
31
|
+
* own `rule-deprecation` ruleId instead of `config-error`.
|
|
32
|
+
*/
|
|
33
|
+
readonly ruleDeprecations?: readonly RuleAliasWarning[];
|
|
27
34
|
};
|
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
|
}
|