@markuplint/ml-core 4.13.3 → 5.0.0-alpha.1
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/ARCHITECTURE.ja.md +92 -35
- package/ARCHITECTURE.md +85 -28
- package/CHANGELOG.md +54 -0
- package/docs/linting-pipeline.ja.md +18 -14
- package/docs/linting-pipeline.md +18 -14
- package/docs/maintenance.ja.md +1 -1
- package/docs/maintenance.md +1 -1
- package/docs/ml-dom/attr.ja.md +8 -0
- package/docs/ml-dom/attr.md +8 -0
- package/docs/ml-dom/block.ja.md +33 -33
- package/docs/ml-dom/block.md +33 -33
- package/docs/ml-dom/element.ja.md +17 -17
- package/docs/ml-dom/element.md +17 -17
- package/docs/ml-dom/node.ja.md +4 -5
- package/docs/ml-dom/node.md +4 -5
- package/docs/ml-dom/others.ja.md +2 -1
- package/docs/ml-dom/others.md +5 -4
- package/docs/rule-system.ja.md +23 -6
- package/docs/rule-system.md +23 -6
- package/lib/index.d.ts +4 -3
- package/lib/index.js +1 -1
- package/lib/ml-core.d.ts +1 -1
- package/lib/ml-core.js +142 -82
- package/lib/ml-dom/helper/accname.d.ts +8 -0
- package/lib/ml-dom/helper/accname.js +71 -55
- package/lib/ml-dom/helper/create-node.js +1 -0
- package/lib/ml-dom/helper/get-indent.js +17 -29
- package/lib/ml-dom/node/attr.js +122 -73
- package/lib/ml-dom/node/block.d.ts +3 -3
- package/lib/ml-dom/node/block.js +10 -1
- package/lib/ml-dom/node/document-type.js +12 -0
- package/lib/ml-dom/node/document.d.ts +14 -3
- package/lib/ml-dom/node/document.js +75 -34
- package/lib/ml-dom/node/dom-token-list.js +17 -30
- package/lib/ml-dom/node/element-close-tag.js +1 -0
- package/lib/ml-dom/node/element.d.ts +19 -7
- package/lib/ml-dom/node/element.js +134 -55
- package/lib/ml-dom/node/node-store.js +6 -15
- package/lib/ml-dom/node/node.d.ts +3 -1
- package/lib/ml-dom/node/node.js +159 -166
- package/lib/ml-dom/node/parent-node.js +14 -30
- package/lib/ml-dom/node/rule-mapper.js +7 -20
- package/lib/ml-dom/node/text.d.ts +7 -0
- package/lib/ml-dom/node/text.js +9 -0
- package/lib/ml-dom/token/token.js +23 -39
- package/lib/ml-rule/create-rule.d.ts +8 -1
- package/lib/ml-rule/create-rule.js +0 -9
- package/lib/ml-rule/ml-rule-context.js +7 -11
- package/lib/ml-rule/ml-rule.d.ts +33 -1
- package/lib/ml-rule/ml-rule.js +65 -25
- package/lib/ruleset/index.js +6 -0
- package/lib/test/index.js +4 -1
- package/lib/types.d.ts +2 -1
- package/lib/violation-collector.js +15 -28
- package/lib/virtual-rule.d.ts +72 -0
- package/lib/virtual-rule.js +233 -0
- package/package.json +16 -13
package/lib/ml-core.js
CHANGED
|
@@ -1,18 +1,7 @@
|
|
|
1
|
-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
-
};
|
|
7
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
-
};
|
|
12
|
-
var _MLCore_ast, _MLCore_document, _MLCore_filename, _MLCore_locale, _MLCore_parser, _MLCore_parserOptions, _MLCore_severity, _MLCore_pretenders, _MLCore_rules, _MLCore_ruleset, _MLCore_schemas, _MLCore_sourceCode, _MLCore_configErrors;
|
|
13
1
|
import { ParserError } from '@markuplint/parser-utils';
|
|
14
2
|
import { log, enableDebug } from './debug.js';
|
|
15
3
|
import { Document } from './ml-dom/index.js';
|
|
4
|
+
import { expandNamedNodeRules, expandNamedRules } from './virtual-rule.js';
|
|
16
5
|
const resultLog = log.extend('result');
|
|
17
6
|
/**
|
|
18
7
|
* The core linting engine for markuplint.
|
|
@@ -21,38 +10,65 @@ const resultLog = log.extend('result');
|
|
|
21
10
|
* and verifies it against configured rules to produce violations.
|
|
22
11
|
*/
|
|
23
12
|
export class MLCore {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
13
|
+
#ast = null;
|
|
14
|
+
#document;
|
|
15
|
+
#filename;
|
|
16
|
+
#locale;
|
|
17
|
+
#parser;
|
|
18
|
+
#parserOptions;
|
|
19
|
+
#severity;
|
|
20
|
+
#pretenders;
|
|
21
|
+
#rules;
|
|
22
|
+
#ruleset;
|
|
23
|
+
#schemas;
|
|
24
|
+
#ruleCommonSettings;
|
|
25
|
+
#sourceCode;
|
|
26
|
+
#configErrors;
|
|
27
|
+
/**
|
|
28
|
+
* Pre-expansion nodeRules preserved for hot-reload.
|
|
29
|
+
* When `update()` is called without a new ruleset, these are used as the
|
|
30
|
+
* source for `expandNamedNodeRules()` instead of the already-transformed
|
|
31
|
+
* `#ruleset.nodeRules` (which has alias keys and no `name` property).
|
|
32
|
+
*/
|
|
33
|
+
#originalNodeRules;
|
|
34
|
+
#originalChildNodeRules;
|
|
35
|
+
/**
|
|
36
|
+
* Pre-computed namespace prefixes from wildcard disable entries.
|
|
37
|
+
* e.g., `rules["a11y/*"]: false` yields `"a11y/"`.
|
|
38
|
+
*/
|
|
39
|
+
#disabledNamespaces;
|
|
40
|
+
constructor({ parser, sourceCode, ruleset, rules, locale, schemas, ruleCommonSettings, parserOptions, severity, pretenders, filename, debug, configErrors, }) {
|
|
38
41
|
if (debug) {
|
|
39
42
|
enableDebug();
|
|
40
43
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
44
|
+
this.#parser = parser;
|
|
45
|
+
this.#sourceCode = sourceCode;
|
|
46
|
+
this.#parserOptions = parserOptions;
|
|
47
|
+
this.#locale = locale;
|
|
48
|
+
this.#schemas = schemas;
|
|
49
|
+
this.#ruleCommonSettings = ruleCommonSettings;
|
|
50
|
+
this.#filename = filename;
|
|
51
|
+
this.#severity = severity;
|
|
52
|
+
this.#pretenders = [...pretenders];
|
|
53
|
+
this.#configErrors = [...(configErrors ?? [])];
|
|
54
|
+
// Preserve pre-expansion nodeRules for hot-reload
|
|
55
|
+
this.#originalNodeRules = ruleset.nodeRules ?? [];
|
|
56
|
+
this.#originalChildNodeRules = ruleset.childNodeRules ?? [];
|
|
57
|
+
// Expand named rule groups in the rules section
|
|
58
|
+
const namedRulesResult = expandNamedRules(ruleset.rules ?? {}, rules);
|
|
59
|
+
// Expand named nodeRules into virtual rules (using expanded rules as base)
|
|
60
|
+
const allRulesForExpansion = [...rules, ...namedRulesResult.virtualRules];
|
|
61
|
+
const nodeRuleResult = expandNamedNodeRules(this.#originalNodeRules, allRulesForExpansion);
|
|
62
|
+
const childNodeRuleResult = expandNamedNodeRules(this.#originalChildNodeRules, allRulesForExpansion);
|
|
63
|
+
const resolvedRules = namedRulesResult.resolvedRules;
|
|
64
|
+
this.#rules = [...allRulesForExpansion, ...nodeRuleResult.virtualRules, ...childNodeRuleResult.virtualRules];
|
|
65
|
+
this.#ruleset = {
|
|
66
|
+
rules: resolvedRules,
|
|
67
|
+
nodeRules: nodeRuleResult.transformedNodeRules,
|
|
68
|
+
childNodeRules: childNodeRuleResult.transformedNodeRules,
|
|
69
|
+
};
|
|
70
|
+
this.#disabledNamespaces = extractDisabledNamespaces(resolvedRules);
|
|
71
|
+
this.#configErrors.push(...namedRulesResult.errors, ...nodeRuleResult.errors, ...childNodeRuleResult.errors);
|
|
56
72
|
this._parse();
|
|
57
73
|
this._createDocument();
|
|
58
74
|
}
|
|
@@ -60,7 +76,7 @@ export class MLCore {
|
|
|
60
76
|
* The parsed document, or a {@link ParserError} if parsing failed.
|
|
61
77
|
*/
|
|
62
78
|
get document() {
|
|
63
|
-
return
|
|
79
|
+
return this.#document;
|
|
64
80
|
}
|
|
65
81
|
/**
|
|
66
82
|
* Replaces the source code and re-parses the document.
|
|
@@ -68,7 +84,7 @@ export class MLCore {
|
|
|
68
84
|
* @param sourceCode - The new markup source code
|
|
69
85
|
*/
|
|
70
86
|
setCode(sourceCode) {
|
|
71
|
-
|
|
87
|
+
this.#sourceCode = sourceCode;
|
|
72
88
|
this._parse();
|
|
73
89
|
this._createDocument();
|
|
74
90
|
}
|
|
@@ -79,19 +95,39 @@ export class MLCore {
|
|
|
79
95
|
* @param fabric - Partial fabric with the properties to update
|
|
80
96
|
*/
|
|
81
97
|
update({ parser, ruleset, rules, locale, schemas, parserOptions, configErrors }) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
98
|
+
this.#parser = parser ?? this.#parser;
|
|
99
|
+
this.#locale = locale ?? this.#locale;
|
|
100
|
+
this.#schemas = schemas ?? this.#schemas;
|
|
101
|
+
this.#configErrors = [...(configErrors ?? [])];
|
|
102
|
+
const baseRules = rules?.slice() ?? this.#rules.filter(r => !r.baseRuleId);
|
|
103
|
+
// Use pre-expansion originals as fallback when ruleset is not provided
|
|
104
|
+
const incomingNodeRules = ruleset?.nodeRules ?? this.#originalNodeRules;
|
|
105
|
+
const incomingChildNodeRules = ruleset?.childNodeRules ?? this.#originalChildNodeRules;
|
|
106
|
+
// Expand named rule groups in the rules section
|
|
107
|
+
const incomingRules = ruleset?.rules ?? this.#ruleset.rules;
|
|
108
|
+
const namedRulesResult = expandNamedRules(incomingRules, baseRules);
|
|
109
|
+
const allRulesForExpansion = [...baseRules, ...namedRulesResult.virtualRules];
|
|
110
|
+
const nodeRuleResult = expandNamedNodeRules(incomingNodeRules, allRulesForExpansion);
|
|
111
|
+
const childNodeRuleResult = expandNamedNodeRules(incomingChildNodeRules, allRulesForExpansion);
|
|
112
|
+
// Update originals if new data was provided
|
|
113
|
+
if (ruleset?.nodeRules) {
|
|
114
|
+
this.#originalNodeRules = ruleset.nodeRules;
|
|
115
|
+
}
|
|
116
|
+
if (ruleset?.childNodeRules) {
|
|
117
|
+
this.#originalChildNodeRules = ruleset.childNodeRules;
|
|
118
|
+
}
|
|
119
|
+
const resolvedRules = namedRulesResult.resolvedRules;
|
|
120
|
+
this.#rules = [...allRulesForExpansion, ...nodeRuleResult.virtualRules, ...childNodeRuleResult.virtualRules];
|
|
121
|
+
this.#ruleset = {
|
|
122
|
+
rules: resolvedRules,
|
|
123
|
+
nodeRules: nodeRuleResult.transformedNodeRules,
|
|
124
|
+
childNodeRules: childNodeRuleResult.transformedNodeRules,
|
|
125
|
+
};
|
|
126
|
+
this.#disabledNamespaces = extractDisabledNamespaces(resolvedRules);
|
|
127
|
+
this.#configErrors.push(...namedRulesResult.errors, ...nodeRuleResult.errors, ...childNodeRuleResult.errors);
|
|
92
128
|
if (parserOptions &&
|
|
93
|
-
(parserOptions.ignoreFrontMatter !==
|
|
94
|
-
parserOptions.authoredElementName !==
|
|
129
|
+
(parserOptions.ignoreFrontMatter !== this.#parserOptions.ignoreFrontMatter ||
|
|
130
|
+
parserOptions.authoredElementName !== this.#parserOptions.authoredElementName)) {
|
|
95
131
|
this._parse();
|
|
96
132
|
}
|
|
97
133
|
this._createDocument();
|
|
@@ -108,22 +144,26 @@ export class MLCore {
|
|
|
108
144
|
async verify(fix = false) {
|
|
109
145
|
log('verify: start');
|
|
110
146
|
const violations = [];
|
|
111
|
-
if (
|
|
112
|
-
const parseError = this._createParseError(
|
|
147
|
+
if (this.#document instanceof ParserError) {
|
|
148
|
+
const parseError = this._createParseError(this.#document.message, this.#document.line, this.#document.col, this.#document.raw);
|
|
113
149
|
if (!parseError) {
|
|
114
150
|
return [];
|
|
115
151
|
}
|
|
116
152
|
violations.push(parseError);
|
|
117
|
-
log('verify: error %o',
|
|
153
|
+
log('verify: error %o', this.#document.message);
|
|
118
154
|
return violations;
|
|
119
155
|
}
|
|
120
|
-
const definedRuleName = new Set(
|
|
156
|
+
const definedRuleName = new Set(this.#rules.map(rule => rule.name));
|
|
121
157
|
const setRuleNames = new Set([
|
|
122
|
-
...Object.keys(
|
|
123
|
-
...
|
|
124
|
-
...
|
|
158
|
+
...Object.keys(this.#ruleset.rules),
|
|
159
|
+
...this.#ruleset.nodeRules.flatMap(nodeRule => Object.keys(nodeRule.rules ?? {})),
|
|
160
|
+
...this.#ruleset.childNodeRules.flatMap(childNodeRule => Object.keys(childNodeRule.rules ?? {})),
|
|
125
161
|
]);
|
|
126
162
|
for (const setRuleName of setRuleNames) {
|
|
163
|
+
// Skip wildcard patterns (e.g., "a11y/*") — they are namespace disable entries, not rule references
|
|
164
|
+
if (setRuleName.endsWith('/*')) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
127
167
|
if (!definedRuleName.has(setRuleName)) {
|
|
128
168
|
violations.push({
|
|
129
169
|
ruleId: 'config-error',
|
|
@@ -135,7 +175,7 @@ export class MLCore {
|
|
|
135
175
|
});
|
|
136
176
|
}
|
|
137
177
|
}
|
|
138
|
-
for (const error of
|
|
178
|
+
for (const error of this.#configErrors) {
|
|
139
179
|
violations.push({
|
|
140
180
|
ruleId: 'config-error',
|
|
141
181
|
severity: 'warning',
|
|
@@ -145,13 +185,25 @@ export class MLCore {
|
|
|
145
185
|
raw: '',
|
|
146
186
|
});
|
|
147
187
|
}
|
|
148
|
-
for (const rule of
|
|
149
|
-
|
|
188
|
+
for (const rule of this.#rules) {
|
|
189
|
+
// For virtual rules, check disable conditions:
|
|
190
|
+
// 1. Exact name match: rules["alias/name"]: false
|
|
191
|
+
// 2. Group disable: rules["groupName"]: false (multi-entry named nodeRules)
|
|
192
|
+
// 3. Namespace wildcard: rules["scope/*"]: false
|
|
193
|
+
// Note: base rule name disable (rules["baseRuleName"]: false) is handled
|
|
194
|
+
// during expandNamedRules for named rule groups in the rules section.
|
|
195
|
+
if (rule.baseRuleId &&
|
|
196
|
+
(this.#ruleset.rules[rule.name] === false ||
|
|
197
|
+
(rule.groupName && this.#ruleset.rules[rule.groupName] === false) ||
|
|
198
|
+
this.#disabledNamespaces.some(ns => rule.name.startsWith(ns)))) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const ruleInfo = rule.getRuleInfo(this.#ruleset, rule.name);
|
|
150
202
|
if (ruleInfo.disabled && ruleInfo.nodeRules.length === 0 && ruleInfo.childNodeRules.length === 0) {
|
|
151
203
|
continue;
|
|
152
204
|
}
|
|
153
205
|
log('%s Rule: verify', rule.name);
|
|
154
|
-
const results = await rule.verify(
|
|
206
|
+
const results = await rule.verify(this.#document, this.#locale, fix).catch(error => {
|
|
155
207
|
if (error instanceof ParserError) {
|
|
156
208
|
return error;
|
|
157
209
|
}
|
|
@@ -188,21 +240,21 @@ export class MLCore {
|
|
|
188
240
|
return violations;
|
|
189
241
|
}
|
|
190
242
|
_createDocument() {
|
|
191
|
-
if (!
|
|
243
|
+
if (!this.#ast) {
|
|
192
244
|
return;
|
|
193
245
|
}
|
|
194
246
|
try {
|
|
195
|
-
|
|
196
|
-
filename:
|
|
197
|
-
endTag:
|
|
198
|
-
booleanish:
|
|
199
|
-
tagNameCaseSensitive:
|
|
200
|
-
pretenders:
|
|
201
|
-
})
|
|
247
|
+
this.#document = new Document(this.#ast, this.#ruleset, this.#schemas, this.#ruleCommonSettings, {
|
|
248
|
+
filename: this.#filename,
|
|
249
|
+
endTag: this.#parser.endTag,
|
|
250
|
+
booleanish: this.#parser.booleanish,
|
|
251
|
+
tagNameCaseSensitive: this.#parser.tagNameCaseSensitive,
|
|
252
|
+
pretenders: this.#pretenders,
|
|
253
|
+
});
|
|
202
254
|
}
|
|
203
255
|
catch (error) {
|
|
204
256
|
if (error instanceof ParserError) {
|
|
205
|
-
|
|
257
|
+
this.#document = error;
|
|
206
258
|
}
|
|
207
259
|
else {
|
|
208
260
|
throw error;
|
|
@@ -210,13 +262,13 @@ export class MLCore {
|
|
|
210
262
|
}
|
|
211
263
|
}
|
|
212
264
|
_createParseError(message, line, col, raw) {
|
|
213
|
-
if (
|
|
265
|
+
if (this.#severity.parseError === false || this.#severity.parseError === 'off') {
|
|
214
266
|
return null;
|
|
215
267
|
}
|
|
216
268
|
// Default severity is 'error'
|
|
217
|
-
const severity =
|
|
269
|
+
const severity = this.#severity.parseError === true || this.#severity.parseError == null
|
|
218
270
|
? 'error'
|
|
219
|
-
:
|
|
271
|
+
: this.#severity.parseError;
|
|
220
272
|
return {
|
|
221
273
|
ruleId: 'parse-error',
|
|
222
274
|
severity,
|
|
@@ -228,13 +280,13 @@ export class MLCore {
|
|
|
228
280
|
}
|
|
229
281
|
_parse() {
|
|
230
282
|
try {
|
|
231
|
-
|
|
283
|
+
this.#ast = this.#parser.parse(this.#sourceCode, this.#parserOptions);
|
|
232
284
|
}
|
|
233
285
|
catch (error) {
|
|
234
286
|
log('Caught the parse error: %O', error);
|
|
235
|
-
|
|
287
|
+
this.#ast = null;
|
|
236
288
|
if (error instanceof ParserError) {
|
|
237
|
-
|
|
289
|
+
this.#document = error;
|
|
238
290
|
}
|
|
239
291
|
else {
|
|
240
292
|
throw error;
|
|
@@ -242,4 +294,12 @@ export class MLCore {
|
|
|
242
294
|
}
|
|
243
295
|
}
|
|
244
296
|
}
|
|
245
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Extracts namespace prefixes from wildcard disable entries in rules.
|
|
299
|
+
* e.g., `{ "a11y/*": false }` yields `["a11y/"]`.
|
|
300
|
+
*/
|
|
301
|
+
function extractDisabledNamespaces(rules) {
|
|
302
|
+
return Object.entries(rules)
|
|
303
|
+
.filter(([key, value]) => key.endsWith('/*') && value === false)
|
|
304
|
+
.map(([key]) => key.slice(0, -1)); // "a11y/*" → "a11y/"
|
|
305
|
+
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import type { MLElement } from '../node/element.js';
|
|
2
2
|
import type { ARIAVersion } from '@markuplint/ml-spec';
|
|
3
|
+
/**
|
|
4
|
+
* Computes the accessible name for an MLElement using the HTML-AAM algorithm.
|
|
5
|
+
* Creates an MLCore-specific resolver that bridges MLElement to the AccnameResolver interface.
|
|
6
|
+
*
|
|
7
|
+
* @param el - The MLElement to compute the accessible name for
|
|
8
|
+
* @param version - The ARIA specification version to use for role resolution
|
|
9
|
+
* @returns The computed accessible name string, or an empty string on error
|
|
10
|
+
*/
|
|
3
11
|
export declare function getAccname(el: MLElement<any, any>, version: ARIAVersion): string;
|
|
@@ -1,71 +1,87 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { computeAccessibleName, escapeCSS, getComputedRole, EMBEDDED_CONTROL_ROLES, isNativeEmbeddedControl, } from '@markuplint/ml-spec';
|
|
2
2
|
import { log } from '../../debug.js';
|
|
3
3
|
const accnameLog = log.extend('accname');
|
|
4
|
+
/**
|
|
5
|
+
* Computes the accessible name for an MLElement using the HTML-AAM algorithm.
|
|
6
|
+
* Creates an MLCore-specific resolver that bridges MLElement to the AccnameResolver interface.
|
|
7
|
+
*
|
|
8
|
+
* @param el - The MLElement to compute the accessible name for
|
|
9
|
+
* @param version - The ARIA specification version to use for role resolution
|
|
10
|
+
* @returns The computed accessible name string, or an empty string on error
|
|
11
|
+
*/
|
|
4
12
|
export function getAccname(
|
|
5
13
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
6
14
|
el, version) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
if (isHidden(el)) {
|
|
16
|
-
return '';
|
|
17
|
-
}
|
|
18
|
-
if (isFromContent(el, version)) {
|
|
19
|
-
return [...el.childNodes]
|
|
20
|
-
.map(child => {
|
|
21
|
-
if (child.is(child.ELEMENT_NODE)) {
|
|
22
|
-
return getAccname(child, version);
|
|
23
|
-
}
|
|
24
|
-
if (child.is(child.TEXT_NODE)) {
|
|
25
|
-
return child.textContent ?? '';
|
|
26
|
-
}
|
|
27
|
-
return '';
|
|
28
|
-
})
|
|
29
|
-
.join('');
|
|
30
|
-
}
|
|
31
|
-
return '';
|
|
32
|
-
}
|
|
33
|
-
function safeGet(
|
|
34
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
35
|
-
el) {
|
|
15
|
+
// AccName computation involves DOM traversal, role resolution, and label
|
|
16
|
+
// lookup via querySelector. These may fail on unexpected DOM states (e.g.,
|
|
17
|
+
// detached nodes, malformed attributes) or incomplete spec data.
|
|
18
|
+
// A single element's failure must not abort the entire linting process,
|
|
19
|
+
// so we catch operational errors and return an empty name (= "unnamed").
|
|
20
|
+
// Programmer errors (TypeError, ReferenceError) are rethrown to avoid
|
|
21
|
+
// masking implementation bugs.
|
|
36
22
|
try {
|
|
37
|
-
const
|
|
38
|
-
|
|
23
|
+
const resolver = createMLCoreResolver(el, version);
|
|
24
|
+
const result = computeAccessibleName(el, resolver);
|
|
25
|
+
return result.name;
|
|
39
26
|
}
|
|
40
27
|
catch (error) {
|
|
28
|
+
if (error instanceof TypeError || error instanceof ReferenceError) {
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
41
31
|
accnameLog('Raw: %s', el.raw);
|
|
42
32
|
accnameLog('Error: %O', error);
|
|
43
33
|
return '';
|
|
44
34
|
}
|
|
45
35
|
}
|
|
46
|
-
function
|
|
47
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
48
|
-
el) {
|
|
49
|
-
if (el.pretenderContext?.type === 'pretender' && el.pretenderContext.aria?.name != null) {
|
|
50
|
-
if (typeof el.pretenderContext.aria.name === 'boolean') {
|
|
51
|
-
return 'some-name(Pretender Options)';
|
|
52
|
-
}
|
|
53
|
-
const attrName = el.pretenderContext.aria.name.fromAttr;
|
|
54
|
-
const attrValue = el.getAttributePretended(attrName);
|
|
55
|
-
if (attrValue) {
|
|
56
|
-
return attrValue;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return '';
|
|
60
|
-
}
|
|
61
|
-
function isHidden(
|
|
62
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
63
|
-
el) {
|
|
64
|
-
return el.getAttribute('aria-hidden') === 'true' || el.hasAttribute('hidden');
|
|
65
|
-
}
|
|
66
|
-
function isFromContent(
|
|
36
|
+
function createMLCoreResolver(
|
|
67
37
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
68
38
|
el, version) {
|
|
69
|
-
const
|
|
70
|
-
return
|
|
39
|
+
const doc = el.ownerMLDocument;
|
|
40
|
+
return {
|
|
41
|
+
getElementById(id) {
|
|
42
|
+
const found = doc.querySelector(`#${escapeCSS(id)}`);
|
|
43
|
+
return found ?? null;
|
|
44
|
+
},
|
|
45
|
+
getLabelsForId(id) {
|
|
46
|
+
const labels = doc.querySelectorAll(`label[for="${escapeCSS(id)}"]`);
|
|
47
|
+
return [...labels];
|
|
48
|
+
},
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
50
|
+
allowsNameFromContent(target) {
|
|
51
|
+
// TODO: Remove cast when getComputedRole accepts AccnameElement (#3178)
|
|
52
|
+
const role = getComputedRole(doc.specs, target, version);
|
|
53
|
+
return !!role.role?.accessibleNameFromContent;
|
|
54
|
+
},
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
56
|
+
isHidden(target) {
|
|
57
|
+
return target.getAttribute('aria-hidden') === 'true' || target.hasAttribute('hidden');
|
|
58
|
+
},
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
60
|
+
isEmbeddedControl(target) {
|
|
61
|
+
// TODO: Remove cast when getComputedRole accepts AccnameElement (#3178)
|
|
62
|
+
const role = getComputedRole(doc.specs, target, version);
|
|
63
|
+
if (role.role?.name) {
|
|
64
|
+
return EMBEDDED_CONTROL_ROLES.has(role.role.name);
|
|
65
|
+
}
|
|
66
|
+
return isNativeEmbeddedControl(target);
|
|
67
|
+
},
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
69
|
+
getPrecomputedName(target) {
|
|
70
|
+
// TODO: Remove cast when AccnameResolver is generic (#3178)
|
|
71
|
+
const targetEl = target;
|
|
72
|
+
if (!targetEl.pretenderContext || targetEl.pretenderContext.type !== 'pretender') {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const ariaName = targetEl.pretenderContext.aria?.name;
|
|
76
|
+
if (ariaName == null) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
if (typeof ariaName === 'boolean') {
|
|
80
|
+
return 'some-name(Pretender Options)';
|
|
81
|
+
}
|
|
82
|
+
const attrName = ariaName.fromAttr;
|
|
83
|
+
const attrValue = targetEl.getAttributePretended(attrName);
|
|
84
|
+
return attrValue || null;
|
|
85
|
+
},
|
|
86
|
+
};
|
|
71
87
|
}
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
-
};
|
|
7
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
-
};
|
|
12
|
-
var _MLDOMIndentation_fixed, _MLDOMIndentation_node, _MLDOMIndentation_parent;
|
|
13
1
|
/**
|
|
14
2
|
* Computes the indentation preceding the given node by analyzing
|
|
15
3
|
* the whitespace in adjacent text nodes.
|
|
@@ -62,52 +50,52 @@ node) {
|
|
|
62
50
|
return null;
|
|
63
51
|
}
|
|
64
52
|
class MLDOMIndentation {
|
|
53
|
+
#fixed;
|
|
54
|
+
line;
|
|
55
|
+
#node;
|
|
56
|
+
#parent;
|
|
65
57
|
constructor(
|
|
66
58
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
67
59
|
originTextNode, raw, line,
|
|
68
60
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
69
61
|
parentNode) {
|
|
70
|
-
_MLDOMIndentation_fixed.set(this, void 0);
|
|
71
|
-
_MLDOMIndentation_node.set(this, void 0);
|
|
72
|
-
_MLDOMIndentation_parent.set(this, void 0);
|
|
73
62
|
this.line = line;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
63
|
+
this.#node = originTextNode;
|
|
64
|
+
this.#parent = parentNode;
|
|
65
|
+
this.#fixed = raw;
|
|
77
66
|
}
|
|
78
67
|
get raw() {
|
|
79
|
-
if (!
|
|
68
|
+
if (!this.#parent.is(this.#parent.TEXT_NODE) && this.line !== this.#node.endLine) {
|
|
80
69
|
return '';
|
|
81
70
|
}
|
|
82
|
-
return
|
|
71
|
+
return this.#fixed;
|
|
83
72
|
}
|
|
84
73
|
get type() {
|
|
85
|
-
if (!
|
|
74
|
+
if (!this.#parent.is(this.#parent.TEXT_NODE) && this.line !== this.#node.endLine) {
|
|
86
75
|
return 'none';
|
|
87
76
|
}
|
|
88
|
-
const raw =
|
|
77
|
+
const raw = this.#fixed;
|
|
89
78
|
return raw === '' ? 'none' : /^\t+$/.test(raw) ? 'tab' : /^[^\t]+$/.test(raw) ? 'space' : 'mixed';
|
|
90
79
|
}
|
|
91
80
|
get width() {
|
|
92
|
-
if (!
|
|
81
|
+
if (!this.#parent.is(this.#parent.TEXT_NODE) && this.line !== this.#node.endLine) {
|
|
93
82
|
return 0;
|
|
94
83
|
}
|
|
95
|
-
return
|
|
84
|
+
return this.#fixed.length;
|
|
96
85
|
}
|
|
97
86
|
fix(raw) {
|
|
98
|
-
const current =
|
|
99
|
-
|
|
100
|
-
const node =
|
|
87
|
+
const current = this.#fixed;
|
|
88
|
+
this.#fixed = raw;
|
|
89
|
+
const node = this.#node;
|
|
101
90
|
const line = node.startLine;
|
|
102
91
|
const lines = node.raw.split(/\r?\n/);
|
|
103
92
|
const index = this.line - line;
|
|
104
93
|
if (lines[index] != null) {
|
|
105
|
-
lines[index] = lines[index].replace(current,
|
|
94
|
+
lines[index] = lines[index].replace(current, this.#fixed);
|
|
106
95
|
}
|
|
107
96
|
node.fix(lines.join('\n'));
|
|
108
97
|
}
|
|
109
98
|
}
|
|
110
|
-
_MLDOMIndentation_fixed = new WeakMap(), _MLDOMIndentation_node = new WeakMap(), _MLDOMIndentation_parent = new WeakMap();
|
|
111
99
|
function isFirstToken(
|
|
112
100
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
113
101
|
node) {
|