@markuplint/ml-core 5.0.0-rc.2 → 5.0.0-rc.5
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 +37 -0
- package/README.md +0 -5
- package/lib/cursor-offset.js +0 -3
- package/lib/fix-applier.js +3 -9
- package/lib/ml-core.d.ts +18 -2
- package/lib/ml-core.js +223 -61
- package/lib/ml-dom/helper/accname.d.ts +0 -8
- package/lib/ml-dom/helper/accname.js +7 -10
- package/lib/ml-dom/node/attr.js +3 -1
- package/lib/ml-dom/node/block.d.ts +6 -0
- package/lib/ml-dom/node/block.js +6 -0
- package/lib/ml-dom/node/child-node.d.ts +0 -9
- package/lib/ml-dom/node/child-node.js +0 -9
- package/lib/ml-dom/node/document.d.ts +20 -1
- package/lib/ml-dom/node/document.js +24 -13
- package/lib/ml-dom/node/element-close-tag.d.ts +12 -0
- package/lib/ml-dom/node/element-close-tag.js +12 -0
- package/lib/ml-dom/node/element.d.ts +22 -0
- package/lib/ml-dom/node/element.js +37 -11
- package/lib/ml-dom/node/node-store.d.ts +0 -3
- package/lib/ml-dom/node/node-store.js +0 -3
- package/lib/ml-dom/node/node.d.ts +34 -1
- package/lib/ml-dom/node/node.js +34 -16
- package/lib/ml-dom/node/parent-node.js +0 -6
- package/lib/ml-dom/node/rule-mapper.d.ts +8 -0
- package/lib/ml-dom/node/rule-mapper.js +8 -0
- package/lib/ml-rule/ml-rule.d.ts +19 -0
- package/lib/ml-rule/ml-rule.js +38 -7
- package/lib/ml-rule/types.d.ts +110 -1
- package/lib/ml-rule/types.js +28 -1
- package/lib/ruleset/index.d.ts +2 -1
- package/lib/ruleset/index.js +2 -1
- package/lib/test/index.js +1 -1
- package/lib/virtual-rule.d.ts +10 -0
- package/lib/virtual-rule.js +1 -24
- package/package.json +14 -14
- package/ARCHITECTURE.ja.md +0 -676
- package/ARCHITECTURE.md +0 -726
- package/SKILL.md +0 -61
- package/docs/linting-pipeline.ja.md +0 -307
- package/docs/linting-pipeline.md +0 -307
- package/docs/maintenance.ja.md +0 -210
- package/docs/maintenance.md +0 -210
- package/docs/ml-dom/attr.ja.md +0 -103
- package/docs/ml-dom/attr.md +0 -103
- package/docs/ml-dom/block.ja.md +0 -272
- package/docs/ml-dom/block.md +0 -272
- package/docs/ml-dom/document.ja.md +0 -134
- package/docs/ml-dom/document.md +0 -134
- package/docs/ml-dom/element.ja.md +0 -161
- package/docs/ml-dom/element.md +0 -161
- package/docs/ml-dom/helpers.ja.md +0 -203
- package/docs/ml-dom/helpers.md +0 -203
- package/docs/ml-dom/node.ja.md +0 -199
- package/docs/ml-dom/node.md +0 -199
- package/docs/ml-dom/others.ja.md +0 -120
- package/docs/ml-dom/others.md +0 -120
- package/docs/ml-dom/overview.ja.md +0 -102
- package/docs/ml-dom/overview.md +0 -102
- package/docs/ml-dom/pretender.ja.md +0 -269
- package/docs/ml-dom/pretender.md +0 -269
- package/docs/ml-dom/rule-mapping.ja.md +0 -371
- package/docs/ml-dom/rule-mapping.md +0 -371
- package/docs/ml-dom.ja.md +0 -18
- package/docs/ml-dom.md +0 -18
- package/docs/rule-system.ja.md +0 -287
- package/docs/rule-system.md +0 -287
|
@@ -1,14 +1,6 @@
|
|
|
1
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
|
-
*/
|
|
12
4
|
export function getAccname(
|
|
13
5
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
14
6
|
el, version) {
|
|
@@ -18,8 +10,13 @@ el, version) {
|
|
|
18
10
|
// environment differences (e.g., Deno lacking certain DOM APIs).
|
|
19
11
|
// A single element's failure must not abort the entire linting process,
|
|
20
12
|
// so we catch all errors and return an empty name (= "unnamed").
|
|
21
|
-
// This is an intentional exception to the Tier 1 re-throw policy
|
|
22
|
-
//
|
|
13
|
+
// This is an intentional exception to the Tier 1 re-throw policy (see
|
|
14
|
+
// `isFatalError()` in `@markuplint/shared`): Tier-1-shaped errors here
|
|
15
|
+
// can be caused by the runtime environment, not only implementation bugs.
|
|
16
|
+
// The empty-string result is not converted to a violation either — a
|
|
17
|
+
// single element's accname failure must not create noise across
|
|
18
|
+
// unrelated rules, and "unnamed" still drives meaningful a11y rule
|
|
19
|
+
// violations downstream.
|
|
23
20
|
try {
|
|
24
21
|
const resolver = createMLCoreResolver(el, version);
|
|
25
22
|
const result = computeAccessibleName(el, resolver);
|
package/lib/ml-dom/node/attr.js
CHANGED
|
@@ -131,7 +131,9 @@ export class MLAttr extends MLNode {
|
|
|
131
131
|
this.isDirective = this._astToken.isDirective;
|
|
132
132
|
this.isDuplicatable = this._astToken.isDuplicatable;
|
|
133
133
|
}
|
|
134
|
-
// IDL attribute resolution (after directivePatterns)
|
|
134
|
+
// IDL attribute resolution (after directivePatterns).
|
|
135
|
+
// Performed in the core, not in each parser: any spec opting in via
|
|
136
|
+
// `acceptedAttrNames` shares the same IDL-to-content-attribute mapping.
|
|
135
137
|
if (ownElement.ownerMLDocument.specs.acceptedAttrNames && !this.isDirective) {
|
|
136
138
|
const { contentAttrName, idlPropName } = searchIDLAttribute(this.#potentialName);
|
|
137
139
|
if (contentAttrName && contentAttrName !== this.#potentialName) {
|
|
@@ -9,6 +9,12 @@ import { MLNode } from './node.js';
|
|
|
9
9
|
* These nodes correspond to template engine constructs such as conditionals (`if`/`else`),
|
|
10
10
|
* loops (`each`), and other preprocessor directives that are not part of standard HTML.
|
|
11
11
|
*
|
|
12
|
+
* Serves as the bridge between template syntax and HTML content model
|
|
13
|
+
* validation: transparency keeps the wrapper invisible to DOM traversal so
|
|
14
|
+
* rules such as `permitted-contents` see the effective HTML children, while
|
|
15
|
+
* `blockBehavior` lets `conditionalChildNodes()` enumerate every possible
|
|
16
|
+
* rendering branch.
|
|
17
|
+
*
|
|
12
18
|
* @template T - The rule configuration value type
|
|
13
19
|
* @template O - The rule options type
|
|
14
20
|
*/
|
package/lib/ml-dom/node/block.js
CHANGED
|
@@ -5,6 +5,12 @@ import { MLNode } from './node.js';
|
|
|
5
5
|
* These nodes correspond to template engine constructs such as conditionals (`if`/`else`),
|
|
6
6
|
* loops (`each`), and other preprocessor directives that are not part of standard HTML.
|
|
7
7
|
*
|
|
8
|
+
* Serves as the bridge between template syntax and HTML content model
|
|
9
|
+
* validation: transparency keeps the wrapper invisible to DOM traversal so
|
|
10
|
+
* rules such as `permitted-contents` see the effective HTML children, while
|
|
11
|
+
* `blockBehavior` lets `conditionalChildNodes()` enumerate every possible
|
|
12
|
+
* rendering branch.
|
|
13
|
+
*
|
|
8
14
|
* @template T - The rule configuration value type
|
|
9
15
|
* @template O - The rule options type
|
|
10
16
|
*/
|
|
@@ -15,13 +15,4 @@ import type { PlainData, RuleConfigValue } from '@markuplint/ml-config';
|
|
|
15
15
|
* @see https://dom.spec.whatwg.org/#idl-index
|
|
16
16
|
*/
|
|
17
17
|
export type MLChildNode<T extends RuleConfigValue, O extends PlainData = undefined> = MLDocumentType<T, O> | MLCharacterData<T, O> | MLElement<T, O> | MLBlock<T, O>;
|
|
18
|
-
/**
|
|
19
|
-
* Determines whether the given node is a child node type
|
|
20
|
-
* (DocumentType, CDATA, Comment, Text, Element, or preprocessor block).
|
|
21
|
-
*
|
|
22
|
-
* @template T - The rule configuration value type
|
|
23
|
-
* @template O - The rule options type
|
|
24
|
-
* @param node - The node to check
|
|
25
|
-
* @returns True if the node is one of the child node types
|
|
26
|
-
*/
|
|
27
18
|
export declare function isChildNode<T extends RuleConfigValue, O extends PlainData = undefined>(node: MLNode<T, O>): node is MLChildNode<T, O>;
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Determines whether the given node is a child node type
|
|
3
|
-
* (DocumentType, CDATA, Comment, Text, Element, or preprocessor block).
|
|
4
|
-
*
|
|
5
|
-
* @template T - The rule configuration value type
|
|
6
|
-
* @template O - The rule options type
|
|
7
|
-
* @param node - The node to check
|
|
8
|
-
* @returns True if the node is one of the child node types
|
|
9
|
-
*/
|
|
10
1
|
export function isChildNode(
|
|
11
2
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
12
3
|
node) {
|
|
@@ -10,7 +10,7 @@ import type { Ruleset } from '../../ruleset/index.js';
|
|
|
10
10
|
import type { MLSchema } from '../../types.js';
|
|
11
11
|
import type { Walker } from '../helper/walkers.js';
|
|
12
12
|
import type { MLToken } from '../token/token.js';
|
|
13
|
-
import type { EndTagType, MLASTDocument, MLASTNodeTreeItem } from '@markuplint/ml-ast';
|
|
13
|
+
import type { EndTagType, MLASTDocument, MLASTNodeTreeItem, MLASTParseError } from '@markuplint/ml-ast';
|
|
14
14
|
import type { PlainData, Pretender, RuleCommonSettings, RuleConfigValue } from '@markuplint/ml-config';
|
|
15
15
|
import type { ARIAVersion, MLMLSpec } from '@markuplint/ml-spec';
|
|
16
16
|
import { MLParentNode } from './parent-node.js';
|
|
@@ -250,6 +250,25 @@ export declare class MLDocument<T extends RuleConfigValue, O extends PlainData =
|
|
|
250
250
|
* @implements DOM API: `Document`
|
|
251
251
|
*/
|
|
252
252
|
get dir(): string;
|
|
253
|
+
/**
|
|
254
|
+
* Non-fatal parser conformance errors collected during tokenisation by
|
|
255
|
+
* the underlying parser (currently `@markuplint/html-parser` and the
|
|
256
|
+
* template-engine parsers that delegate to it). Empty array when the
|
|
257
|
+
* parser does not produce events or the source had none.
|
|
258
|
+
*
|
|
259
|
+
* Rules that have claimed responsibility for parse5 events via
|
|
260
|
+
* `meta.mirrorsParseErrorCodes` typically read this array to surface
|
|
261
|
+
* the corresponding violations themselves. `character-reference` is the
|
|
262
|
+
* canonical example: its self-detection covers unescaped `<`, `>`, `&`,
|
|
263
|
+
* `"` (the "missed escape" direction), and reading `parseErrors` adds
|
|
264
|
+
* coverage for parse5's malformed-reference codes (`&xyz;`, etc.) under
|
|
265
|
+
* the same rule id.
|
|
266
|
+
*
|
|
267
|
+
* ml-core's built-in parse-error channel suppresses the mirrored codes
|
|
268
|
+
* unconditionally — so the rule that reads them here is the only place
|
|
269
|
+
* the user sees the violation.
|
|
270
|
+
*/
|
|
271
|
+
get parseErrors(): readonly MLASTParseError[];
|
|
253
272
|
/**
|
|
254
273
|
* @implements DOM API: `Document`
|
|
255
274
|
*/
|
|
@@ -48,9 +48,6 @@ export class MLDocument extends MLParentNode {
|
|
|
48
48
|
* @default "omittable"
|
|
49
49
|
*/
|
|
50
50
|
endTag;
|
|
51
|
-
/**
|
|
52
|
-
* The file path of the source document, if available.
|
|
53
|
-
*/
|
|
54
51
|
#astNodeMap;
|
|
55
52
|
#filename;
|
|
56
53
|
/**
|
|
@@ -332,6 +329,27 @@ export class MLDocument extends MLParentNode {
|
|
|
332
329
|
get dir() {
|
|
333
330
|
throw new UnexpectedCallError('Not supported "dir" property');
|
|
334
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* Non-fatal parser conformance errors collected during tokenisation by
|
|
334
|
+
* the underlying parser (currently `@markuplint/html-parser` and the
|
|
335
|
+
* template-engine parsers that delegate to it). Empty array when the
|
|
336
|
+
* parser does not produce events or the source had none.
|
|
337
|
+
*
|
|
338
|
+
* Rules that have claimed responsibility for parse5 events via
|
|
339
|
+
* `meta.mirrorsParseErrorCodes` typically read this array to surface
|
|
340
|
+
* the corresponding violations themselves. `character-reference` is the
|
|
341
|
+
* canonical example: its self-detection covers unescaped `<`, `>`, `&`,
|
|
342
|
+
* `"` (the "missed escape" direction), and reading `parseErrors` adds
|
|
343
|
+
* coverage for parse5's malformed-reference codes (`&xyz;`, etc.) under
|
|
344
|
+
* the same rule id.
|
|
345
|
+
*
|
|
346
|
+
* ml-core's built-in parse-error channel suppresses the mirrored codes
|
|
347
|
+
* unconditionally — so the rule that reads them here is the only place
|
|
348
|
+
* the user sees the violation.
|
|
349
|
+
*/
|
|
350
|
+
get parseErrors() {
|
|
351
|
+
return this._astToken.parseErrors ?? [];
|
|
352
|
+
}
|
|
335
353
|
/**
|
|
336
354
|
* @implements DOM API: `Document`
|
|
337
355
|
*/
|
|
@@ -2318,9 +2336,9 @@ export class MLDocument extends MLParentNode {
|
|
|
2318
2336
|
throw new UnexpectedCallError('Not supported "writeln" method');
|
|
2319
2337
|
}
|
|
2320
2338
|
/**
|
|
2321
|
-
*
|
|
2322
|
-
*
|
|
2323
|
-
*
|
|
2339
|
+
* Must run before `#ruleMapping`: rule selectors (e.g. a `nodeRules`
|
|
2340
|
+
* entry targeting `button`) are matched against the pretender identity,
|
|
2341
|
+
* so the pretender link has to exist when rules are mapped.
|
|
2324
2342
|
*/
|
|
2325
2343
|
#pretending(pretenders) {
|
|
2326
2344
|
if (docLog.enabled) {
|
|
@@ -2332,13 +2350,6 @@ export class MLDocument extends MLParentNode {
|
|
|
2332
2350
|
}
|
|
2333
2351
|
}
|
|
2334
2352
|
}
|
|
2335
|
-
/**
|
|
2336
|
-
* Maps the ruleset configuration to each node in the document.
|
|
2337
|
-
* Applies global rules, node-specific rules (by selector), and
|
|
2338
|
-
* child-node rules to build the per-node rule configuration.
|
|
2339
|
-
*
|
|
2340
|
-
* @param ruleset - The ruleset containing rules, nodeRules, and childNodeRules
|
|
2341
|
-
*/
|
|
2342
2353
|
#ruleMapping(ruleset) {
|
|
2343
2354
|
if (docLog.enabled) {
|
|
2344
2355
|
docLog('Rule Mapping: %O', Object.keys(ruleset.rules));
|
|
@@ -3,6 +3,18 @@ import type { MLElement } from './element.js';
|
|
|
3
3
|
import type { MLASTElementCloseTag } from '@markuplint/ml-ast';
|
|
4
4
|
import type { PlainData, RuleConfigValue } from '@markuplint/ml-config';
|
|
5
5
|
import { MLNode } from './node.js';
|
|
6
|
+
/**
|
|
7
|
+
* The close tag paired with its opening `MLElement`.
|
|
8
|
+
*
|
|
9
|
+
* Close tags are not part of the document's `nodeList`; each instance exists
|
|
10
|
+
* only as a satellite of its paired element (`MLElement.closeTag`). The class
|
|
11
|
+
* exists for two purposes:
|
|
12
|
+
*
|
|
13
|
+
* 1. Reporting violations at the close tag's own source location instead of
|
|
14
|
+
* the open tag (e.g. the `case-sensitive-tag-name` rule).
|
|
15
|
+
* 2. Detecting close-tag presence: `MLElement.closeTag` is `null` for void,
|
|
16
|
+
* self-closing, or omitted end tags (e.g. the `end-tag` rule).
|
|
17
|
+
*/
|
|
6
18
|
export declare class MLElementCloseTag<T extends RuleConfigValue, O extends PlainData = undefined> extends MLNode<T, O, MLASTElementCloseTag> {
|
|
7
19
|
readonly pair: MLElement<T, O>;
|
|
8
20
|
constructor(astNode: MLASTElementCloseTag, document: MLDocument<T, O>, pair: MLElement<T, O>);
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { MLNode } from './node.js';
|
|
2
|
+
/**
|
|
3
|
+
* The close tag paired with its opening `MLElement`.
|
|
4
|
+
*
|
|
5
|
+
* Close tags are not part of the document's `nodeList`; each instance exists
|
|
6
|
+
* only as a satellite of its paired element (`MLElement.closeTag`). The class
|
|
7
|
+
* exists for two purposes:
|
|
8
|
+
*
|
|
9
|
+
* 1. Reporting violations at the close tag's own source location instead of
|
|
10
|
+
* the open tag (e.g. the `case-sensitive-tag-name` rule).
|
|
11
|
+
* 2. Detecting close-tag presence: `MLElement.closeTag` is `null` for void,
|
|
12
|
+
* self-closing, or omitted end tags (e.g. the `end-tag` rule).
|
|
13
|
+
*/
|
|
2
14
|
export class MLElementCloseTag extends MLNode {
|
|
3
15
|
pair;
|
|
4
16
|
constructor(astNode,
|
|
@@ -53,6 +53,10 @@ export declare class MLElement<T extends RuleConfigValue, O extends PlainData =
|
|
|
53
53
|
/**
|
|
54
54
|
* The pretender context if this element is participating in pretender behavior,
|
|
55
55
|
* or null if it is not a pretender or pretended element.
|
|
56
|
+
*
|
|
57
|
+
* The virtual element created by `pretending()` is not registered in the
|
|
58
|
+
* document's `nodeList`; walkers visit only the original element, which
|
|
59
|
+
* delegates its name and attribute getters to the virtual element.
|
|
56
60
|
*/
|
|
57
61
|
pretenderContext: PretenderContext<MLElement<T, O>, T, O> | null;
|
|
58
62
|
/**
|
|
@@ -1637,6 +1641,9 @@ export declare class MLElement<T extends RuleConfigValue, O extends PlainData =
|
|
|
1637
1641
|
/**
|
|
1638
1642
|
* Returns the rule configuration for this element, respecting the pretender context.
|
|
1639
1643
|
* If the element is a pretended origin, returns the rule from the pretending element.
|
|
1644
|
+
* Rules are mapped only to nodes in the document's `nodeList`; the virtual
|
|
1645
|
+
* pretender element never appears there, so it must read the resolved
|
|
1646
|
+
* rules from the original element.
|
|
1640
1647
|
*
|
|
1641
1648
|
* @implements `@markuplint/ml-core` API: `MLNode`
|
|
1642
1649
|
*/
|
|
@@ -1901,6 +1908,10 @@ export declare class MLElement<T extends RuleConfigValue, O extends PlainData =
|
|
|
1901
1908
|
* Gets the attribute value from the original (non-pretended) attributes list,
|
|
1902
1909
|
* bypassing any pretender context that might be active.
|
|
1903
1910
|
*
|
|
1911
|
+
* Exists for the pretender ARIA `{ fromAttr }` accessible-name resolution:
|
|
1912
|
+
* the source attribute (e.g. `label` on `<MyButton label="...">`) lives on
|
|
1913
|
+
* the original component element, not on the virtual pretender element.
|
|
1914
|
+
*
|
|
1904
1915
|
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
1905
1916
|
* @param attrName - The attribute name to look up (case-insensitive)
|
|
1906
1917
|
* @returns The attribute value, or null if the attribute is not found
|
|
@@ -2015,6 +2026,12 @@ export declare class MLElement<T extends RuleConfigValue, O extends PlainData =
|
|
|
2015
2026
|
* such as preprocessor-specific blocks, slot elements, or (optionally) elements
|
|
2016
2027
|
* with dynamic attributes.
|
|
2017
2028
|
*
|
|
2029
|
+
* Blocks that carry a `blockBehavior` are not treated as mutable because
|
|
2030
|
+
* their branches are deterministically enumerable via
|
|
2031
|
+
* `conditionalChildNodes()`; blocks without one (e.g. expression output
|
|
2032
|
+
* like `{value}`) can produce arbitrary content, so the children are
|
|
2033
|
+
* considered mutable.
|
|
2034
|
+
*
|
|
2018
2035
|
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
2019
2036
|
* @param attr - When true, also considers children with mutable attributes as mutable
|
|
2020
2037
|
* @returns True if this element has potentially mutable children
|
|
@@ -2090,6 +2107,11 @@ export declare class MLElement<T extends RuleConfigValue, O extends PlainData =
|
|
|
2090
2107
|
* returning detailed match results. When the element is a pretender,
|
|
2091
2108
|
* it attempts to match both as the pretender and as the original element.
|
|
2092
2109
|
*
|
|
2110
|
+
* The two-phase strategy lets both targeting styles work: selectors for
|
|
2111
|
+
* the semantic element (e.g. `button`) match via the pretender identity,
|
|
2112
|
+
* while selectors for the component name (e.g. `MyButton`) still match
|
|
2113
|
+
* the original.
|
|
2114
|
+
*
|
|
2093
2115
|
* @param selector - The CSS selector string or regex selector to match against
|
|
2094
2116
|
* @param scope - An optional scope node for scoped selector matching
|
|
2095
2117
|
* @returns The detailed match result including captured groups from regex selectors
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* global StylePropertyMap, StylePropertyMapReadOnly */
|
|
2
|
-
import { resolveNamespace } from '@markuplint/ml-spec';
|
|
2
|
+
import { getSpecByTagName, resolveNamespace } from '@markuplint/ml-spec';
|
|
3
3
|
import { matchSelector } from '@markuplint/selector';
|
|
4
4
|
import { getAccname } from '../helper/accname.js';
|
|
5
5
|
import { after, before, nextElementSibling, previousElementSibling, remove, replaceWith, } from '../manipulations/child-node-methods.js';
|
|
@@ -49,8 +49,6 @@ export class MLElement extends MLParentNode {
|
|
|
49
49
|
*/
|
|
50
50
|
namespaceURI;
|
|
51
51
|
/**
|
|
52
|
-
* Memoization cache for the accessible name computation, keyed by ARIA version.
|
|
53
|
-
*
|
|
54
52
|
* ## Why this cache exists
|
|
55
53
|
*
|
|
56
54
|
* Multiple rules and the `:aria(has name)` selector evaluate the accessible name
|
|
@@ -68,14 +66,6 @@ export class MLElement extends MLParentNode {
|
|
|
68
66
|
* logic is needed. The cache is garbage-collected together with the
|
|
69
67
|
* MLElement instance when the document is released.
|
|
70
68
|
*
|
|
71
|
-
* ## Consumers that benefit
|
|
72
|
-
*
|
|
73
|
-
* - `require-accessible-name` rule (direct call)
|
|
74
|
-
* - `wai-aria` rule via `:aria(has name)` selector
|
|
75
|
-
* - `neighbor-popovers` rule
|
|
76
|
-
* - `landmark-roles` rule
|
|
77
|
-
* - `MLDocument.getAccessibilityProp()` (accessibility tree builder)
|
|
78
|
-
*
|
|
79
69
|
* Introduced to resolve {@link https://github.com/markuplint/markuplint/issues/2179 | #2179}
|
|
80
70
|
* (AccName performance bottleneck).
|
|
81
71
|
*
|
|
@@ -91,6 +81,10 @@ export class MLElement extends MLParentNode {
|
|
|
91
81
|
/**
|
|
92
82
|
* The pretender context if this element is participating in pretender behavior,
|
|
93
83
|
* or null if it is not a pretender or pretended element.
|
|
84
|
+
*
|
|
85
|
+
* The virtual element created by `pretending()` is not registered in the
|
|
86
|
+
* document's `nodeList`; walkers visit only the original element, which
|
|
87
|
+
* delegates its name and attribute getters to the virtual element.
|
|
94
88
|
*/
|
|
95
89
|
pretenderContext = null;
|
|
96
90
|
/**
|
|
@@ -2125,6 +2119,9 @@ export class MLElement extends MLParentNode {
|
|
|
2125
2119
|
/**
|
|
2126
2120
|
* Returns the rule configuration for this element, respecting the pretender context.
|
|
2127
2121
|
* If the element is a pretended origin, returns the rule from the pretending element.
|
|
2122
|
+
* Rules are mapped only to nodes in the document's `nodeList`; the virtual
|
|
2123
|
+
* pretender element never appears there, so it must read the resolved
|
|
2124
|
+
* rules from the original element.
|
|
2128
2125
|
*
|
|
2129
2126
|
* @implements `@markuplint/ml-core` API: `MLNode`
|
|
2130
2127
|
*/
|
|
@@ -2501,6 +2498,10 @@ export class MLElement extends MLParentNode {
|
|
|
2501
2498
|
* Gets the attribute value from the original (non-pretended) attributes list,
|
|
2502
2499
|
* bypassing any pretender context that might be active.
|
|
2503
2500
|
*
|
|
2501
|
+
* Exists for the pretender ARIA `{ fromAttr }` accessible-name resolution:
|
|
2502
|
+
* the source attribute (e.g. `label` on `<MyButton label="...">`) lives on
|
|
2503
|
+
* the original component element, not on the virtual pretender element.
|
|
2504
|
+
*
|
|
2504
2505
|
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
2505
2506
|
* @param attrName - The attribute name to look up (case-insensitive)
|
|
2506
2507
|
* @returns The attribute value, or null if the attribute is not found
|
|
@@ -2692,6 +2693,12 @@ export class MLElement extends MLParentNode {
|
|
|
2692
2693
|
* such as preprocessor-specific blocks, slot elements, or (optionally) elements
|
|
2693
2694
|
* with dynamic attributes.
|
|
2694
2695
|
*
|
|
2696
|
+
* Blocks that carry a `blockBehavior` are not treated as mutable because
|
|
2697
|
+
* their branches are deterministically enumerable via
|
|
2698
|
+
* `conditionalChildNodes()`; blocks without one (e.g. expression output
|
|
2699
|
+
* like `{value}`) can produce arbitrary content, so the children are
|
|
2700
|
+
* considered mutable.
|
|
2701
|
+
*
|
|
2695
2702
|
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
2696
2703
|
* @param attr - When true, also considers children with mutable attributes as mutable
|
|
2697
2704
|
* @returns True if this element has potentially mutable children
|
|
@@ -2816,6 +2823,11 @@ export class MLElement extends MLParentNode {
|
|
|
2816
2823
|
* returning detailed match results. When the element is a pretender,
|
|
2817
2824
|
* it attempts to match both as the pretender and as the original element.
|
|
2818
2825
|
*
|
|
2826
|
+
* The two-phase strategy lets both targeting styles work: selectors for
|
|
2827
|
+
* the semantic element (e.g. `button`) match via the pretender identity,
|
|
2828
|
+
* while selectors for the component name (e.g. `MyButton`) still match
|
|
2829
|
+
* the original.
|
|
2830
|
+
*
|
|
2819
2831
|
* @param selector - The CSS selector string or regex selector to match against
|
|
2820
2832
|
* @param scope - An optional scope node for scoped selector matching
|
|
2821
2833
|
* @returns The detailed match result including captured groups from regex selectors
|
|
@@ -2850,6 +2862,20 @@ export class MLElement extends MLParentNode {
|
|
|
2850
2862
|
* @param pretenders - Optional array of pretender configurations to match against
|
|
2851
2863
|
*/
|
|
2852
2864
|
pretending(pretenders) {
|
|
2865
|
+
// Pretender must not apply to a recognised standard HTML element (e.g. <marquee>,
|
|
2866
|
+
// <h1>, <button>). Allowing such elements to masquerade as another would silently
|
|
2867
|
+
// mask spec-driven rules — deprecation, ARIA role restrictions, browser support —
|
|
2868
|
+
// keyed on the original tag. See issue #3740.
|
|
2869
|
+
//
|
|
2870
|
+
// Names that the HTML parser cannot distinguish from typos (PascalCase JSX-like
|
|
2871
|
+
// usage in plain HTML such as `<SimpleButton>`) get `elementType === 'html'`
|
|
2872
|
+
// from the parser but have no spec entry; those remain pretender-eligible
|
|
2873
|
+
// (this is what `pretenders.scan` relies on). The legacy "no inline `as=` on
|
|
2874
|
+
// HTML elements" guard is preserved further down for that case.
|
|
2875
|
+
if (this.elementType === 'html' &&
|
|
2876
|
+
getSpecByTagName(this.ownerMLDocument.specs.specs, this.localName, this.namespaceURI) != null) {
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2853
2879
|
const pretenderConfig = pretenders?.find(option => this.matches(option.selector));
|
|
2854
2880
|
const asAttrValue = this.getAttribute('as');
|
|
2855
2881
|
const pretenderElement = pretenderConfig?.as ??
|
|
@@ -8,8 +8,5 @@ declare class NodeStore {
|
|
|
8
8
|
getNodeByUuid<T extends RuleConfigValue, O extends PlainData = undefined>(uuid: string): MLNode<T, O, any>;
|
|
9
9
|
setNode<A extends MLASTNode, T extends RuleConfigValue, O extends PlainData = undefined>(astNode: A, node: MLNode<T, O, A>): void;
|
|
10
10
|
}
|
|
11
|
-
/**
|
|
12
|
-
* `NodeStore` Singleton
|
|
13
|
-
*/
|
|
14
11
|
export declare const nodeStore: NodeStore;
|
|
15
12
|
export {};
|
|
@@ -13,6 +13,18 @@ import { MLToken } from '../token/token.js';
|
|
|
13
13
|
* Extends `MLToken` with DOM `Node` interface compliance, tree traversal,
|
|
14
14
|
* rule configuration access, and child node management.
|
|
15
15
|
*
|
|
16
|
+
* The `implements Node` declaration is a maintenance strategy: conforming to
|
|
17
|
+
* the built-in DOM interfaces makes the compiler report errors whenever the
|
|
18
|
+
* TypeScript DOM type definitions gain new members, so the MLDOM API surface
|
|
19
|
+
* never drifts from the DOM Standard unnoticed. Members that are meaningless
|
|
20
|
+
* in static analysis (mutation, events, layout) are stubbed to throw
|
|
21
|
+
* `UnexpectedCallError` instead of being implemented.
|
|
22
|
+
*
|
|
23
|
+
* The `T`/`O` generics exist solely for rule authors using `createRule`:
|
|
24
|
+
* they propagate through the node tree so that `node.rule` is typed as
|
|
25
|
+
* `RuleInfo<T, O>` inside `verify()`/`fix()` callbacks. They are a
|
|
26
|
+
* compile-time-only mechanism — see the `rules` field for the runtime side.
|
|
27
|
+
*
|
|
16
28
|
* @template T - The rule configuration value type
|
|
17
29
|
* @template O - The rule options type
|
|
18
30
|
* @template A - The underlying AST node type
|
|
@@ -121,7 +133,9 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
|
|
|
121
133
|
*/
|
|
122
134
|
readonly isFragment: boolean;
|
|
123
135
|
/**
|
|
124
|
-
*
|
|
136
|
+
* Rules mapped to this node by `RuleMapper`. Deliberately untyped
|
|
137
|
+
* (`AnyRule`) storage: the `T`/`O` generics are not enforced at runtime —
|
|
138
|
+
* the `rule` getter recovers the typed `RuleInfo<T, O>` via a cast.
|
|
125
139
|
*/
|
|
126
140
|
readonly rules: Record<string, AnyRule>;
|
|
127
141
|
protected _astToken: A;
|
|
@@ -169,6 +183,9 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
|
|
|
169
183
|
*/
|
|
170
184
|
get lastChild(): MLChildNode<T, O> | null;
|
|
171
185
|
/**
|
|
186
|
+
* The next node in the syntactical sibling list which, unlike
|
|
187
|
+
* `nextSibling`, includes `MLBlock` nodes (AST-level traversal).
|
|
188
|
+
*
|
|
172
189
|
* @implements `@markuplint/ml-core` API: `MLNode`
|
|
173
190
|
*/
|
|
174
191
|
get nextNode(): MLNode<T, O> | null;
|
|
@@ -276,10 +293,18 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
|
|
|
276
293
|
*/
|
|
277
294
|
get parentNode(): MLDocument<any, any> | MLDocumentFragment<any, any> | MLElement<T, O> | null;
|
|
278
295
|
/**
|
|
296
|
+
* The previous node in the syntactical sibling list which, unlike
|
|
297
|
+
* `previousSibling`, includes `MLBlock` nodes (AST-level traversal).
|
|
298
|
+
*
|
|
279
299
|
* @implements `@markuplint/ml-core` API: `MLNode`
|
|
280
300
|
*/
|
|
281
301
|
get prevNode(): MLNode<T, O> | null;
|
|
282
302
|
/**
|
|
303
|
+
* The previous node in the document-order `nodeList`.
|
|
304
|
+
* Omitted (ghost) elements are skipped because they have no source tokens;
|
|
305
|
+
* including them would break offset chains used for indentation analysis
|
|
306
|
+
* and source reconstruction.
|
|
307
|
+
*
|
|
283
308
|
* @implements `@markuplint/ml-core` API: `MLNode`
|
|
284
309
|
*/
|
|
285
310
|
get prevToken(): MLNode<T, O> | null;
|
|
@@ -383,6 +408,14 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
|
|
|
383
408
|
*
|
|
384
409
|
* Note: NodeList doesn't include whitespace nodes.
|
|
385
410
|
*
|
|
411
|
+
* For `if`/`switch` groups, a `null` sentinel is appended to each branch
|
|
412
|
+
* group to represent the case where no branch renders at all, so that
|
|
413
|
+
* content-model rules also validate the "empty branch" pattern
|
|
414
|
+
* (`branchesToPatterns` filters the `null` out of the generated patterns).
|
|
415
|
+
* `each` blocks intentionally do not start a conditional mode: their content
|
|
416
|
+
* is flattened as always-present rather than treated as an alternative
|
|
417
|
+
* branch, even though a loop may render zero times.
|
|
418
|
+
*
|
|
386
419
|
* @returns An array of NodeLists representing the conditional child nodes.
|
|
387
420
|
*
|
|
388
421
|
* @experemental
|
package/lib/ml-dom/node/node.js
CHANGED
|
@@ -8,6 +8,18 @@ import { nodeStore } from './node-store.js';
|
|
|
8
8
|
* Extends `MLToken` with DOM `Node` interface compliance, tree traversal,
|
|
9
9
|
* rule configuration access, and child node management.
|
|
10
10
|
*
|
|
11
|
+
* The `implements Node` declaration is a maintenance strategy: conforming to
|
|
12
|
+
* the built-in DOM interfaces makes the compiler report errors whenever the
|
|
13
|
+
* TypeScript DOM type definitions gain new members, so the MLDOM API surface
|
|
14
|
+
* never drifts from the DOM Standard unnoticed. Members that are meaningless
|
|
15
|
+
* in static analysis (mutation, events, layout) are stubbed to throw
|
|
16
|
+
* `UnexpectedCallError` instead of being implemented.
|
|
17
|
+
*
|
|
18
|
+
* The `T`/`O` generics exist solely for rule authors using `createRule`:
|
|
19
|
+
* they propagate through the node tree so that `node.rule` is typed as
|
|
20
|
+
* `RuleInfo<T, O>` inside `verify()`/`fix()` callbacks. They are a
|
|
21
|
+
* compile-time-only mechanism — see the `rules` field for the runtime side.
|
|
22
|
+
*
|
|
11
23
|
* @template T - The rule configuration value type
|
|
12
24
|
* @template O - The rule options type
|
|
13
25
|
* @template A - The underlying AST node type
|
|
@@ -110,31 +122,18 @@ export class MLNode extends MLToken {
|
|
|
110
122
|
* @see https://dom.spec.whatwg.org/#interface-node
|
|
111
123
|
*/
|
|
112
124
|
TEXT_NODE = 3;
|
|
113
|
-
/**
|
|
114
|
-
* Cached `childNodes` property
|
|
115
|
-
*/
|
|
116
125
|
#pureChildNodesCache;
|
|
117
126
|
/**
|
|
118
127
|
* Returns child nodes when parent node access by `childNodes` property.
|
|
119
128
|
*/
|
|
120
129
|
isFragment;
|
|
121
|
-
/**
|
|
122
|
-
* Owner `Document`
|
|
123
|
-
*
|
|
124
|
-
* @implements DOM API: `Node`
|
|
125
|
-
* @see https://dom.spec.whatwg.org/#ref-for-dom-node-ownerdocument
|
|
126
|
-
*/
|
|
127
130
|
#ownerDocument;
|
|
128
|
-
/**
|
|
129
|
-
* Cached `prevToken` property
|
|
130
|
-
*/
|
|
131
131
|
#prevToken;
|
|
132
|
-
/**
|
|
133
|
-
* Cached `conditionalChildNodes` method
|
|
134
|
-
*/
|
|
135
132
|
#conditionalChildNodes;
|
|
136
133
|
/**
|
|
137
|
-
*
|
|
134
|
+
* Rules mapped to this node by `RuleMapper`. Deliberately untyped
|
|
135
|
+
* (`AnyRule`) storage: the `T`/`O` generics are not enforced at runtime —
|
|
136
|
+
* the `rule` getter recovers the typed `RuleInfo<T, O>` via a cast.
|
|
138
137
|
*/
|
|
139
138
|
rules = {};
|
|
140
139
|
_astToken;
|
|
@@ -210,6 +209,9 @@ export class MLNode extends MLToken {
|
|
|
210
209
|
return this.childNodes[this.childNodes.length - 1] ?? null;
|
|
211
210
|
}
|
|
212
211
|
/**
|
|
212
|
+
* The next node in the syntactical sibling list which, unlike
|
|
213
|
+
* `nextSibling`, includes `MLBlock` nodes (AST-level traversal).
|
|
214
|
+
*
|
|
213
215
|
* @implements `@markuplint/ml-core` API: `MLNode`
|
|
214
216
|
*/
|
|
215
217
|
get nextNode() {
|
|
@@ -369,6 +371,9 @@ export class MLNode extends MLToken {
|
|
|
369
371
|
return parentNode;
|
|
370
372
|
}
|
|
371
373
|
/**
|
|
374
|
+
* The previous node in the syntactical sibling list which, unlike
|
|
375
|
+
* `previousSibling`, includes `MLBlock` nodes (AST-level traversal).
|
|
376
|
+
*
|
|
372
377
|
* @implements `@markuplint/ml-core` API: `MLNode`
|
|
373
378
|
*/
|
|
374
379
|
get prevNode() {
|
|
@@ -377,6 +382,11 @@ export class MLNode extends MLToken {
|
|
|
377
382
|
return siblings[index - 1] ?? null;
|
|
378
383
|
}
|
|
379
384
|
/**
|
|
385
|
+
* The previous node in the document-order `nodeList`.
|
|
386
|
+
* Omitted (ghost) elements are skipped because they have no source tokens;
|
|
387
|
+
* including them would break offset chains used for indentation analysis
|
|
388
|
+
* and source reconstruction.
|
|
389
|
+
*
|
|
380
390
|
* @implements `@markuplint/ml-core` API: `MLNode`
|
|
381
391
|
*/
|
|
382
392
|
get prevToken() {
|
|
@@ -549,6 +559,14 @@ export class MLNode extends MLToken {
|
|
|
549
559
|
*
|
|
550
560
|
* Note: NodeList doesn't include whitespace nodes.
|
|
551
561
|
*
|
|
562
|
+
* For `if`/`switch` groups, a `null` sentinel is appended to each branch
|
|
563
|
+
* group to represent the case where no branch renders at all, so that
|
|
564
|
+
* content-model rules also validate the "empty branch" pattern
|
|
565
|
+
* (`branchesToPatterns` filters the `null` out of the generated patterns).
|
|
566
|
+
* `each` blocks intentionally do not start a conditional mode: their content
|
|
567
|
+
* is flattened as always-present rather than treated as an alternative
|
|
568
|
+
* branch, even though a loop may render zero times.
|
|
569
|
+
*
|
|
552
570
|
* @returns An array of NodeLists representing the conditional child nodes.
|
|
553
571
|
*
|
|
554
572
|
* @experemental
|
|
@@ -8,13 +8,7 @@ import { UnexpectedCallError } from '@markuplint/shared';
|
|
|
8
8
|
* @see https://dom.spec.whatwg.org/#interface-parentnode
|
|
9
9
|
*/
|
|
10
10
|
export class MLParentNode extends MLNode {
|
|
11
|
-
/**
|
|
12
|
-
* Cached `children`
|
|
13
|
-
*/
|
|
14
11
|
#children = null;
|
|
15
|
-
/**
|
|
16
|
-
* Cached elements that created from `querySelectorAll`
|
|
17
|
-
*/
|
|
18
12
|
#selectedElements = new Map();
|
|
19
13
|
/**
|
|
20
14
|
* @implements DOM API: `Element`, `Document`, `DocumentFragment`
|
|
@@ -8,6 +8,14 @@ type MappingLayer = {
|
|
|
8
8
|
readonly specificity: Specificity;
|
|
9
9
|
readonly rule: AnyRule;
|
|
10
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* Accumulates rule-to-node mappings and applies them to each node's `rules`
|
|
13
|
+
* record. Conflicts for the same rule on the same node are resolved by CSS
|
|
14
|
+
* selector specificity: higher specificity wins regardless of order, and on
|
|
15
|
+
* equal specificity the later `set()` call wins — so declaration order in
|
|
16
|
+
* the config arrays decides. Entries from `childNodeRules` carry the
|
|
17
|
+
* specificity of the parent's selector, not the child's.
|
|
18
|
+
*/
|
|
11
19
|
export declare class RuleMapper {
|
|
12
20
|
#private;
|
|
13
21
|
constructor(document: MLDocument<any, any>);
|
|
@@ -3,6 +3,14 @@ import { log as coreLog } from '../../debug.js';
|
|
|
3
3
|
const ruleMapperLog = coreLog.extend('rule-mapper');
|
|
4
4
|
const ruleMapperNodeLog = ruleMapperLog.extend('node');
|
|
5
5
|
const ruleMapperNodeRuleLog = ruleMapperNodeLog.extend('rule');
|
|
6
|
+
/**
|
|
7
|
+
* Accumulates rule-to-node mappings and applies them to each node's `rules`
|
|
8
|
+
* record. Conflicts for the same rule on the same node are resolved by CSS
|
|
9
|
+
* selector specificity: higher specificity wins regardless of order, and on
|
|
10
|
+
* equal specificity the later `set()` call wins — so declaration order in
|
|
11
|
+
* the config arrays decides. Entries from `childNodeRules` carry the
|
|
12
|
+
* specificity of the parent's selector, not the child's.
|
|
13
|
+
*/
|
|
6
14
|
export class RuleMapper {
|
|
7
15
|
#nodeList;
|
|
8
16
|
#ruleMap = new Map();
|