@markuplint/ml-core 4.13.2 → 5.0.0-alpha.0

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.
Files changed (92) hide show
  1. package/ARCHITECTURE.ja.md +524 -0
  2. package/ARCHITECTURE.md +524 -0
  3. package/CHANGELOG.md +52 -2
  4. package/README.md +5 -0
  5. package/SKILL.md +61 -0
  6. package/docs/linting-pipeline.ja.md +307 -0
  7. package/docs/linting-pipeline.md +307 -0
  8. package/docs/maintenance.ja.md +210 -0
  9. package/docs/maintenance.md +210 -0
  10. package/docs/ml-dom/attr.ja.md +103 -0
  11. package/docs/ml-dom/attr.md +103 -0
  12. package/docs/ml-dom/block.ja.md +272 -0
  13. package/docs/ml-dom/block.md +272 -0
  14. package/docs/ml-dom/document.ja.md +141 -0
  15. package/docs/ml-dom/document.md +141 -0
  16. package/docs/ml-dom/element.ja.md +176 -0
  17. package/docs/ml-dom/element.md +176 -0
  18. package/docs/ml-dom/helpers.ja.md +203 -0
  19. package/docs/ml-dom/helpers.md +203 -0
  20. package/docs/ml-dom/node.ja.md +199 -0
  21. package/docs/ml-dom/node.md +199 -0
  22. package/docs/ml-dom/others.ja.md +120 -0
  23. package/docs/ml-dom/others.md +120 -0
  24. package/docs/ml-dom/overview.ja.md +102 -0
  25. package/docs/ml-dom/overview.md +102 -0
  26. package/docs/ml-dom/pretender.ja.md +269 -0
  27. package/docs/ml-dom/pretender.md +269 -0
  28. package/docs/ml-dom/rule-mapping.ja.md +371 -0
  29. package/docs/ml-dom/rule-mapping.md +371 -0
  30. package/docs/ml-dom.ja.md +18 -0
  31. package/docs/ml-dom.md +18 -0
  32. package/docs/rule-system.ja.md +287 -0
  33. package/docs/rule-system.md +287 -0
  34. package/lib/convert-ruleset.d.ts +7 -0
  35. package/lib/convert-ruleset.js +7 -0
  36. package/lib/debug.d.ts +4 -0
  37. package/lib/debug.js +4 -0
  38. package/lib/index.d.ts +4 -3
  39. package/lib/index.js +1 -1
  40. package/lib/ml-core.d.ts +37 -1
  41. package/lib/ml-core.js +171 -82
  42. package/lib/ml-dom/helper/accname.d.ts +8 -0
  43. package/lib/ml-dom/helper/accname.js +71 -55
  44. package/lib/ml-dom/helper/create-node.js +1 -0
  45. package/lib/ml-dom/helper/get-indent.d.ts +4 -1
  46. package/lib/ml-dom/helper/get-indent.js +21 -30
  47. package/lib/ml-dom/node/attr.d.ts +65 -4
  48. package/lib/ml-dom/node/attr.js +151 -53
  49. package/lib/ml-dom/node/block.d.ts +23 -2
  50. package/lib/ml-dom/node/block.js +24 -1
  51. package/lib/ml-dom/node/child-node.d.ts +9 -0
  52. package/lib/ml-dom/node/child-node.js +9 -0
  53. package/lib/ml-dom/node/comment.d.ts +7 -0
  54. package/lib/ml-dom/node/comment.js +7 -0
  55. package/lib/ml-dom/node/document-fragment.d.ts +8 -0
  56. package/lib/ml-dom/node/document-fragment.js +8 -0
  57. package/lib/ml-dom/node/document-type.d.ts +22 -0
  58. package/lib/ml-dom/node/document-type.js +25 -0
  59. package/lib/ml-dom/node/document.d.ts +88 -7
  60. package/lib/ml-dom/node/document.js +128 -32
  61. package/lib/ml-dom/node/dom-token-list.js +17 -30
  62. package/lib/ml-dom/node/element-close-tag.js +1 -0
  63. package/lib/ml-dom/node/element.d.ts +151 -5
  64. package/lib/ml-dom/node/element.js +242 -50
  65. package/lib/ml-dom/node/node-store.js +6 -15
  66. package/lib/ml-dom/node/node.d.ts +19 -1
  67. package/lib/ml-dom/node/node.js +175 -166
  68. package/lib/ml-dom/node/parent-node.js +14 -30
  69. package/lib/ml-dom/node/rule-mapper.js +7 -20
  70. package/lib/ml-dom/node/text.d.ts +19 -0
  71. package/lib/ml-dom/node/text.js +21 -0
  72. package/lib/ml-dom/node/types.d.ts +68 -0
  73. package/lib/ml-dom/token/token.d.ts +42 -0
  74. package/lib/ml-dom/token/token.js +59 -39
  75. package/lib/ml-rule/create-rule.d.ts +17 -1
  76. package/lib/ml-rule/ml-rule-context.js +7 -11
  77. package/lib/ml-rule/ml-rule.d.ts +66 -1
  78. package/lib/ml-rule/ml-rule.js +95 -25
  79. package/lib/ml-rule/types.d.ts +41 -0
  80. package/lib/plugin/plugin.d.ts +8 -0
  81. package/lib/plugin/plugin.js +8 -0
  82. package/lib/plugin/types.d.ts +21 -0
  83. package/lib/ruleset/index.d.ts +10 -0
  84. package/lib/ruleset/index.js +13 -0
  85. package/lib/test/index.d.ts +42 -1
  86. package/lib/test/index.js +39 -2
  87. package/lib/types.d.ts +10 -1
  88. package/lib/violation-collector.d.ts +33 -0
  89. package/lib/violation-collector.js +48 -28
  90. package/lib/virtual-rule.d.ts +72 -0
  91. package/lib/virtual-rule.js +233 -0
  92. package/package.json +16 -13
@@ -1,21 +1,8 @@
1
1
  /* global StylePropertyMap, StylePropertyMapReadOnly */
2
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
- if (kind === "m") throw new TypeError("Private method is not writable");
4
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
- 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");
6
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
- };
8
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
- 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");
11
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
- };
13
- var _MLElement_attributes, _MLElement_fixedNodeName, _MLElement_getChildElementsAndTextNodeWithoutWhitespacesCache, _MLElement_localName, _MLElement_normalizedAttrs, _MLElement_normalizedString;
14
2
  import { resolveNamespace } from '@markuplint/ml-spec';
15
3
  import { matchSelector } from '@markuplint/selector';
16
4
  import { getAccname } from '../helper/accname.js';
17
5
  import { after, before, nextElementSibling, previousElementSibling, remove, replaceWith, } from '../manipulations/child-node-methods.js';
18
- import { MLToken } from '../token/token.js';
19
6
  import { MLAttr } from './attr.js';
20
7
  import { MLDomTokenList } from './dom-token-list.js';
21
8
  import { MLElementCloseTag } from './element-close-tag.js';
@@ -24,30 +11,123 @@ import { toHTMLCollection } from './node-list.js';
24
11
  import { MLParentNode } from './parent-node.js';
25
12
  import { UnexpectedCallError } from './unexpected-call-error.js';
26
13
  const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
14
+ /**
15
+ * Represents a DOM Element node wrapper in the markuplint DOM tree.
16
+ * Provides access to element attributes, tag names, namespace, ARIA properties,
17
+ * accessibility information, pretender context, and CSS selector matching.
18
+ * This is the primary class used for linting HTML elements.
19
+ *
20
+ * @template T - The rule configuration value type
21
+ * @template O - The rule options type
22
+ */
27
23
  export class MLElement extends MLParentNode {
24
+ #attributes;
25
+ /**
26
+ * The closing tag for this element, or null if the element is self-closing or void.
27
+ */
28
+ closeTag;
29
+ /**
30
+ * Element type
31
+ *
32
+ * - `html`: From native HTML Standard
33
+ * - `web-component`: As the Web Component according to HTML Standard
34
+ * - `authored`: Authored element (JSX Element etc.) through the view framework or the template engine.
35
+ */
36
+ elementType;
37
+ #fixedNodeName;
38
+ #getChildElementsAndTextNodeWithoutWhitespacesCache = null;
39
+ /**
40
+ * Whether this element belongs to a non-HTML namespace (e.g., SVG or MathML).
41
+ */
42
+ isForeignElement;
43
+ /**
44
+ * Whether this element was implicitly created (e.g., an omitted `<body>` tag in HTML).
45
+ */
46
+ isOmitted;
47
+ #localName;
48
+ /**
49
+ * The namespace URI of this element (e.g., `http://www.w3.org/1999/xhtml` for HTML elements).
50
+ */
51
+ namespaceURI;
52
+ /**
53
+ * Memoization cache for the accessible name computation, keyed by ARIA version.
54
+ *
55
+ * ## Why this cache exists
56
+ *
57
+ * Multiple rules and the `:aria(has name)` selector evaluate the accessible name
58
+ * of every element during a single lint pass. Without caching, the same
59
+ * expensive tree-walking algorithm (AccName Computation 1.2) runs repeatedly
60
+ * for the same element — once per consumer. Benchmarks on a 500-element page
61
+ * show **14,626 total calls** of which **11,573 (79%) are cache hits**,
62
+ * eliminating redundant computation.
63
+ *
64
+ * ## Why memoization is safe
65
+ *
66
+ * The MLDOM is **immutable** once constructed — no attributes or child nodes
67
+ * change during a lint pass. Therefore the accessible name for a given ARIA
68
+ * version is deterministic and will never become stale. No invalidation
69
+ * logic is needed. The cache is garbage-collected together with the
70
+ * MLElement instance when the document is released.
71
+ *
72
+ * ## Consumers that benefit
73
+ *
74
+ * - `require-accessible-name` rule (direct call)
75
+ * - `wai-aria` rule via `:aria(has name)` selector
76
+ * - `neighbor-popovers` rule
77
+ * - `landmark-roles` rule
78
+ * - `MLDocument.getAccessibilityProp()` (accessibility tree builder)
79
+ *
80
+ * Introduced to resolve {@link https://github.com/markuplint/markuplint/issues/2179 | #2179}
81
+ * (AccName performance bottleneck).
82
+ *
83
+ * @see {@link getAccessibleName} — the public method that reads/writes this cache
84
+ */
85
+ #accessibleNameCache = new Map();
86
+ #normalizedAttrs = new Map();
87
+ #normalizedString = null;
88
+ ontouchcancel;
89
+ ontouchend;
90
+ ontouchmove;
91
+ ontouchstart;
92
+ /**
93
+ * The pretender context if this element is participating in pretender behavior,
94
+ * or null if it is not a pretender or pretended element.
95
+ */
96
+ pretenderContext = null;
97
+ /**
98
+ * Block behavior associated with this element, if any.
99
+ */
100
+ blockBehavior;
101
+ /**
102
+ * The tag close character string (e.g., `>` or `/>` or `%>`).
103
+ */
104
+ tagCloseChar;
105
+ /**
106
+ * The tag open character string (e.g., `<` or `<%`).
107
+ */
108
+ tagOpenChar;
109
+ /**
110
+ * Creates a new MLElement instance from an AST element node.
111
+ *
112
+ * @param astNode - The AST element node to wrap
113
+ * @param document - The owning document
114
+ */
28
115
  constructor(astNode,
29
116
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
30
117
  document) {
31
118
  super(astNode, document, astNode.isFragment);
32
- _MLElement_attributes.set(this, void 0);
33
- _MLElement_fixedNodeName.set(this, void 0);
34
- _MLElement_getChildElementsAndTextNodeWithoutWhitespacesCache.set(this, null);
35
- _MLElement_localName.set(this, void 0);
36
- _MLElement_normalizedAttrs.set(this, new Map());
37
- _MLElement_normalizedString.set(this, null);
38
- this.pretenderContext = null;
39
- __classPrivateFieldSet(this, _MLElement_attributes, astNode.attributes.map(attr => new MLAttr(attr, this)), "f");
40
- this.selfClosingSolidus = astNode.selfClosingSolidus ? new MLToken(astNode.selfClosingSolidus) : null;
119
+ this.#attributes = astNode.attributes.map(attr => new MLAttr(attr, this));
41
120
  this.closeTag = astNode.pairNode ? new MLElementCloseTag(astNode.pairNode, document, this) : null;
42
121
  const ns = resolveNamespace(astNode.nodeName, astNode.namespace);
43
- this.namespaceURI = ns.namespaceURI;
122
+ this.namespaceURI = astNode.namespace;
44
123
  this.elementType = astNode.elementType;
45
- __classPrivateFieldSet(this, _MLElement_localName, ns.localName, "f");
124
+ this.#localName = ns.localName;
46
125
  this.isForeignElement = this.namespaceURI !== HTML_NAMESPACE;
47
- __classPrivateFieldSet(this, _MLElement_fixedNodeName, astNode.nodeName, "f");
126
+ this.#fixedNodeName = astNode.nodeName;
48
127
  this.isOmitted = astNode.isGhost;
49
128
  this.tagOpenChar = astNode.tagOpenChar;
50
129
  this.tagCloseChar = astNode.tagCloseChar;
130
+ this.blockBehavior = astNode.blockBehavior;
51
131
  }
52
132
  /**
53
133
  * **IT THROWS AN ERROR WHEN CALLING THIS.**
@@ -589,6 +669,12 @@ export class MLElement extends MLParentNode {
589
669
  get assignedSlot() {
590
670
  throw new UnexpectedCallError('Not supported "assignedSlot" property');
591
671
  }
672
+ /**
673
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
674
+ *
675
+ * @unsupported
676
+ * @implements DOM API: `Element`
677
+ */
592
678
  get attributeStyleMap() {
593
679
  throw new UnexpectedCallError('Not supported "attributeStyleMap" property');
594
680
  }
@@ -597,9 +683,9 @@ export class MLElement extends MLParentNode {
597
683
  * @see https://dom.spec.whatwg.org/#dom-element-attributes
598
684
  */
599
685
  get attributes() {
600
- const origin = this.pretenderContext?.type === 'pretender' ? __classPrivateFieldGet(this.pretenderContext.as, _MLElement_attributes, "f") : __classPrivateFieldGet(this, _MLElement_attributes, "f");
601
- if (__classPrivateFieldGet(this, _MLElement_normalizedAttrs, "f").has(origin)) {
602
- return __classPrivateFieldGet(this, _MLElement_normalizedAttrs, "f").get(origin);
686
+ const origin = this.pretenderContext?.type === 'pretender' ? this.pretenderContext.as.#attributes : this.#attributes;
687
+ if (this.#normalizedAttrs.has(origin)) {
688
+ return this.#normalizedAttrs.get(origin);
603
689
  }
604
690
  const names = new Set();
605
691
  const attrs = [];
@@ -615,7 +701,7 @@ export class MLElement extends MLParentNode {
615
701
  attrs.push(attr);
616
702
  }
617
703
  const map = toNamedNodeMap(attrs);
618
- __classPrivateFieldGet(this, _MLElement_normalizedAttrs, "f").set(origin, map);
704
+ this.#normalizedAttrs.set(origin, map);
619
705
  return map;
620
706
  }
621
707
  /**
@@ -765,13 +851,19 @@ export class MLElement extends MLParentNode {
765
851
  throw new UnexpectedCallError('Not supported "enterKeyHint" property');
766
852
  }
767
853
  /**
854
+ * Returns the fixed (potentially corrected) node name, which may differ from the
855
+ * original node name after lint fixes such as case normalization.
856
+ *
768
857
  * @implements `@markuplint/ml-core` API: `MLElement`
769
858
  */
770
859
  get fixedNodeName() {
771
- return __classPrivateFieldGet(this, _MLElement_fixedNodeName, "f");
860
+ return this.#fixedNodeName;
772
861
  }
862
+ /**
863
+ * Whether this element has any spread attributes (e.g., `{...props}` in JSX).
864
+ */
773
865
  get hasSpreadAttr() {
774
- return __classPrivateFieldGet(this, _MLElement_attributes, "f").some(attr => attr.localName === '#spread');
866
+ return this.#attributes.some(attr => attr.localName === '#spread');
775
867
  }
776
868
  /**
777
869
  * **IT THROWS AN ERROR WHEN CALLING THIS.**
@@ -871,12 +963,12 @@ export class MLElement extends MLParentNode {
871
963
  return this.pretenderContext.as.localName;
872
964
  }
873
965
  if (this.isForeignElement || this.elementType !== 'html') {
874
- return __classPrivateFieldGet(this, _MLElement_localName, "f");
966
+ return this.#localName;
875
967
  }
876
968
  if (this.ownerMLDocument.tagNameCaseSensitive) {
877
- return __classPrivateFieldGet(this, _MLElement_localName, "f");
969
+ return this.#localName;
878
970
  }
879
- return __classPrivateFieldGet(this, _MLElement_localName, "f").toLowerCase();
971
+ return this.#localName.toLowerCase();
880
972
  }
881
973
  /**
882
974
  * The element immediately following the specified one in its parent's children list.
@@ -2000,6 +2092,9 @@ export class MLElement extends MLParentNode {
2000
2092
  return previousElementSibling(this);
2001
2093
  }
2002
2094
  /**
2095
+ * Returns the original raw element name exactly as it appears in the AST,
2096
+ * without any case normalization or pretender resolution.
2097
+ *
2003
2098
  * @implements `@markuplint/ml-core` API: `MLElement`
2004
2099
  */
2005
2100
  get rawName() {
@@ -2016,6 +2111,9 @@ export class MLElement extends MLParentNode {
2016
2111
  throw new UnexpectedCallError('Not supported "role" property');
2017
2112
  }
2018
2113
  /**
2114
+ * Returns the rule configuration for this element, respecting the pretender context.
2115
+ * If the element is a pretended origin, returns the rule from the pretending element.
2116
+ *
2019
2117
  * @implements `@markuplint/ml-core` API: `MLNode`
2020
2118
  */
2021
2119
  get rule() {
@@ -2287,8 +2385,14 @@ export class MLElement extends MLParentNode {
2287
2385
  computedStyleMap() {
2288
2386
  throw new UnexpectedCallError('Not supported "computedStyleMap" method');
2289
2387
  }
2388
+ /**
2389
+ * Overrides the fixed node name for this element, used when the element's
2390
+ * tag name needs to be corrected during linting (e.g., case normalization).
2391
+ *
2392
+ * @param name - The new node name to set
2393
+ */
2290
2394
  fixNodeName(name) {
2291
- __classPrivateFieldSet(this, _MLElement_fixedNodeName, name, "f");
2395
+ this.#fixedNodeName = name;
2292
2396
  }
2293
2397
  /**
2294
2398
  * **IT THROWS AN ERROR WHEN CALLING THIS.**
@@ -2303,10 +2407,34 @@ export class MLElement extends MLParentNode {
2303
2407
  throw new UnexpectedCallError('Not supported "focus" method');
2304
2408
  }
2305
2409
  /**
2410
+ * Returns the accessible name of this element, with per-element memoization.
2411
+ *
2412
+ * On the first call for a given ARIA version the full AccName Computation
2413
+ * algorithm runs (tree walk, `aria-labelledby` resolution, `<label>` lookup,
2414
+ * etc.) and the result is stored in {@link #accessibleNameCache}.
2415
+ * Subsequent calls for the same version return the cached value in O(1).
2416
+ *
2417
+ * **All callers that need the accessible name of an MLElement should use
2418
+ * this method** rather than importing `getAccname()` directly, so that
2419
+ * every consumer benefits from the shared cache. This includes the
2420
+ * `:aria(has name)` selector in `@markuplint/selector`, which uses
2421
+ * duck-typing to detect and call this method.
2422
+ *
2423
+ * Added as part of the fix for {@link https://github.com/markuplint/markuplint/issues/2179 | #2179}
2424
+ * to eliminate redundant AccName computation across rules.
2425
+ *
2306
2426
  * @implements `@markuplint/ml-core` API: `MLElement`
2427
+ * @param version - The ARIA specification version to use for computation
2428
+ * @returns The computed accessible name string (may be empty)
2307
2429
  */
2308
2430
  getAccessibleName(version) {
2309
- return getAccname(this, version);
2431
+ const cached = this.#accessibleNameCache.get(version);
2432
+ if (cached != null) {
2433
+ return cached;
2434
+ }
2435
+ const name = getAccname(this, version);
2436
+ this.#accessibleNameCache.set(version, name);
2437
+ return name;
2310
2438
  }
2311
2439
  /**
2312
2440
  * **IT THROWS AN ERROR WHEN CALLING THIS.**
@@ -2367,10 +2495,15 @@ export class MLElement extends MLParentNode {
2367
2495
  throw new UnexpectedCallError('Not supported "getAttributeNodeNS" method');
2368
2496
  }
2369
2497
  /**
2498
+ * Gets the attribute value from the original (non-pretended) attributes list,
2499
+ * bypassing any pretender context that might be active.
2500
+ *
2370
2501
  * @implements `@markuplint/ml-core` API: `MLElement`
2502
+ * @param attrName - The attribute name to look up (case-insensitive)
2503
+ * @returns The attribute value, or null if the attribute is not found
2371
2504
  */
2372
2505
  getAttributePretended(attrName) {
2373
- for (const attr of __classPrivateFieldGet(this, _MLElement_attributes, "f")) {
2506
+ for (const attr of this.#attributes) {
2374
2507
  if (attr.name.toLowerCase() === attrName.toLowerCase()) {
2375
2508
  return attr.value;
2376
2509
  }
@@ -2378,7 +2511,12 @@ export class MLElement extends MLParentNode {
2378
2511
  return null;
2379
2512
  }
2380
2513
  /**
2514
+ * Returns all attribute tokens matching the given name, including duplicates.
2515
+ * Unlike `getAttribute`, this returns the full `MLAttr` token objects.
2516
+ *
2381
2517
  * @implements `@markuplint/ml-core` API: `MLElement`
2518
+ * @param attrName - The attribute name to look up (case-insensitive)
2519
+ * @returns An array of matching attribute tokens
2382
2520
  */
2383
2521
  getAttributeToken(attrName) {
2384
2522
  const attrs = [];
@@ -2391,10 +2529,14 @@ export class MLElement extends MLParentNode {
2391
2529
  return attrs;
2392
2530
  }
2393
2531
  /**
2532
+ * Returns all attribute tokens for this element, respecting the pretender context.
2533
+ * If the element is pretending to be another, returns the pretender's attributes.
2534
+ *
2394
2535
  * @implements `@markuplint/ml-core` API: `MLElement`
2536
+ * @returns A frozen array of all attribute tokens
2395
2537
  */
2396
2538
  getAttributeTokens() {
2397
- return Object.freeze(this.pretenderContext?.type === 'pretender' ? __classPrivateFieldGet(this.pretenderContext.as, _MLElement_attributes, "f") : __classPrivateFieldGet(this, _MLElement_attributes, "f"));
2539
+ return Object.freeze(this.pretenderContext?.type === 'pretender' ? this.pretenderContext.as.#attributes : this.#attributes);
2398
2540
  }
2399
2541
  /**
2400
2542
  * **IT THROWS AN ERROR WHEN CALLING THIS.**
@@ -2406,9 +2548,16 @@ export class MLElement extends MLParentNode {
2406
2548
  getBoundingClientRect() {
2407
2549
  throw new UnexpectedCallError('Not supported "getBoundingClientRect" method');
2408
2550
  }
2551
+ /**
2552
+ * Returns child elements and non-whitespace text nodes, skipping omitted elements
2553
+ * by flattening their children into the result. Results are cached for performance.
2554
+ *
2555
+ * @implements `@markuplint/ml-core` API: `MLElement`
2556
+ * @returns An array of child elements and non-whitespace text nodes
2557
+ */
2409
2558
  getChildElementsAndTextNodeWithoutWhitespaces() {
2410
- if (__classPrivateFieldGet(this, _MLElement_getChildElementsAndTextNodeWithoutWhitespacesCache, "f")) {
2411
- return __classPrivateFieldGet(this, _MLElement_getChildElementsAndTextNodeWithoutWhitespacesCache, "f");
2559
+ if (this.#getChildElementsAndTextNodeWithoutWhitespacesCache) {
2560
+ return this.#getChildElementsAndTextNodeWithoutWhitespacesCache;
2412
2561
  }
2413
2562
  const filteredNodes = [];
2414
2563
  for (const node of this.childNodes) {
@@ -2425,7 +2574,7 @@ export class MLElement extends MLParentNode {
2425
2574
  filteredNodes.push(node);
2426
2575
  }
2427
2576
  }
2428
- __classPrivateFieldSet(this, _MLElement_getChildElementsAndTextNodeWithoutWhitespacesCache, filteredNodes, "f");
2577
+ this.#getChildElementsAndTextNodeWithoutWhitespacesCache = filteredNodes;
2429
2578
  return filteredNodes;
2430
2579
  }
2431
2580
  /**
@@ -2481,7 +2630,10 @@ export class MLElement extends MLParentNode {
2481
2630
  throw new UnexpectedCallError('Does not implement "getHTML" method yet');
2482
2631
  }
2483
2632
  /**
2633
+ * Returns the source location of the element's tag name (excluding the opening `<` character).
2634
+ *
2484
2635
  * @implements `@markuplint/ml-core` API: `MLElement`
2636
+ * @returns An object with `offset`, `line`, and `col` properties indicating where the name starts
2485
2637
  */
2486
2638
  getNameLocation() {
2487
2639
  return {
@@ -2515,7 +2667,11 @@ export class MLElement extends MLParentNode {
2515
2667
  return this.attributes.length > 0;
2516
2668
  }
2517
2669
  /**
2670
+ * Checks whether this element has any mutable attributes, such as spread
2671
+ * attributes or attributes with dynamic values from template expressions.
2672
+ *
2518
2673
  * @implements `@markuplint/ml-core` API: `MLElement`
2674
+ * @returns True if any attribute is dynamic or lacks a name node (spread)
2519
2675
  */
2520
2676
  hasMutableAttributes() {
2521
2677
  for (const attr of this.attributes) {
@@ -2529,14 +2685,18 @@ export class MLElement extends MLParentNode {
2529
2685
  return false;
2530
2686
  }
2531
2687
  /**
2532
- * This element has "Preprocessor Specific Block". In other words, Its children are potentially mutable.
2688
+ * Checks whether this element has children that are potentially mutable,
2689
+ * such as preprocessor-specific blocks, slot elements, or (optionally) elements
2690
+ * with dynamic attributes.
2533
2691
  *
2534
2692
  * @implements `@markuplint/ml-core` API: `MLElement`
2693
+ * @param attr - When true, also considers children with mutable attributes as mutable
2694
+ * @returns True if this element has potentially mutable children
2535
2695
  */
2536
2696
  hasMutableChildren(attr = false) {
2537
2697
  for (const child of this.getPureChildNodes()) {
2538
2698
  if (child.is(child.MARKUPLINT_PREPROCESSOR_BLOCK)) {
2539
- if (child.conditionalType) {
2699
+ if (child.blockBehavior) {
2540
2700
  continue;
2541
2701
  }
2542
2702
  return true;
@@ -2611,7 +2771,12 @@ export class MLElement extends MLParentNode {
2611
2771
  throw new UnexpectedCallError('Does not implement "insertAdjacentText" method yet');
2612
2772
  }
2613
2773
  /**
2774
+ * Checks whether this element is a descendant of any element whose UUID
2775
+ * is in the given list, by walking up the parent element chain.
2776
+ *
2614
2777
  * @implements `@markuplint/ml-core` API: `MLElement`
2778
+ * @param uuidList - A list of element UUIDs to check against
2779
+ * @returns True if any ancestor element's UUID is in the list
2615
2780
  */
2616
2781
  isDescendantByUUIDList(uuidList) {
2617
2782
  let el = this.parentElement;
@@ -2627,7 +2792,11 @@ export class MLElement extends MLParentNode {
2627
2792
  return false;
2628
2793
  }
2629
2794
  /**
2795
+ * Checks whether this element has no meaningful child content
2796
+ * (only whitespace-only text nodes or no children at all).
2797
+ *
2630
2798
  * @implements `@markuplint/ml-core` API: `MLElement`
2799
+ * @returns True if the element has no non-whitespace child content
2631
2800
  */
2632
2801
  isEmpty() {
2633
2802
  for (const childNode of this.childNodes) {
@@ -2646,6 +2815,15 @@ export class MLElement extends MLParentNode {
2646
2815
  scope) {
2647
2816
  return this.matchMLSelector(selector, scope).matched;
2648
2817
  }
2818
+ /**
2819
+ * Matches this element against a CSS selector or regex selector pattern,
2820
+ * returning detailed match results. When the element is a pretender,
2821
+ * it attempts to match both as the pretender and as the original element.
2822
+ *
2823
+ * @param selector - The CSS selector string or regex selector to match against
2824
+ * @param scope - An optional scope node for scoped selector matching
2825
+ * @returns The detailed match result including captured groups from regex selectors
2826
+ */
2649
2827
  matchMLSelector(selector,
2650
2828
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
2651
2829
  scope) {
@@ -2669,7 +2847,11 @@ export class MLElement extends MLParentNode {
2669
2847
  return matched;
2670
2848
  }
2671
2849
  /**
2672
- * Pretenders Initialization
2850
+ * Initializes the pretender context for this element based on the given pretender
2851
+ * configurations. If a matching pretender is found or the element has an `as` attribute,
2852
+ * sets up the pretender/pretended relationship between elements.
2853
+ *
2854
+ * @param pretenders - Optional array of pretender configurations to match against
2673
2855
  */
2674
2856
  pretending(pretenders) {
2675
2857
  const pretenderConfig = pretenders?.find(option => this.matches(option.selector));
@@ -2685,7 +2867,7 @@ export class MLElement extends MLParentNode {
2685
2867
  return;
2686
2868
  }
2687
2869
  let nodeName;
2688
- let namespace = 'html';
2870
+ let namespace = 'http://www.w3.org/1999/xhtml';
2689
2871
  const attributes = [];
2690
2872
  let aria;
2691
2873
  if (typeof pretenderElement === 'string') {
@@ -2693,7 +2875,7 @@ export class MLElement extends MLParentNode {
2693
2875
  }
2694
2876
  else {
2695
2877
  nodeName = pretenderElement.element;
2696
- namespace = pretenderElement.namespace ?? namespace;
2878
+ namespace = pretenderElement.namespace === 'svg' ? 'http://www.w3.org/2000/svg' : namespace;
2697
2879
  if (pretenderElement.inheritAttrs) {
2698
2880
  attributes.push(...this._astToken.attributes);
2699
2881
  }
@@ -2967,11 +3149,16 @@ export class MLElement extends MLParentNode {
2967
3149
  throw new UnexpectedCallError('Not supported "showPopover" method');
2968
3150
  }
2969
3151
  /**
3152
+ * Returns a normalized string representation of this element including its tag name,
3153
+ * attributes, and child content. Whitespace-only text nodes are excluded.
3154
+ * The result is cached for repeated calls.
3155
+ *
2970
3156
  * @implements `@markuplint/ml-core` API: `MLElement`
3157
+ * @returns The normalized HTML string of this element
2971
3158
  */
2972
3159
  toNormalizeString() {
2973
- if (__classPrivateFieldGet(this, _MLElement_normalizedString, "f")) {
2974
- return __classPrivateFieldGet(this, _MLElement_normalizedString, "f");
3160
+ if (this.#normalizedString) {
3161
+ return this.#normalizedString;
2975
3162
  }
2976
3163
  const children = this.getChildElementsAndTextNodeWithoutWhitespaces();
2977
3164
  const attrs = this.attributes.map(attr => attr.toNormalizeString());
@@ -2985,11 +3172,17 @@ export class MLElement extends MLParentNode {
2985
3172
  });
2986
3173
  const endTag = `</${this.nodeName}>`;
2987
3174
  const normalizedString = `${startTag}${childNodes.join('')}${endTag}`;
2988
- __classPrivateFieldSet(this, _MLElement_normalizedString, normalizedString, "f");
3175
+ this.#normalizedString = normalizedString;
2989
3176
  return normalizedString;
2990
3177
  }
2991
3178
  /**
3179
+ * Returns a string representation of this element. When `fixed` is true,
3180
+ * returns the element with any lint fixes applied to the tag name,
3181
+ * attributes, and embedded comment nodes.
3182
+ *
2992
3183
  * @implements `@markuplint/ml-core` API: `MLElement`
3184
+ * @param fixed - When true, returns the fixed content; otherwise returns the original raw content
3185
+ * @returns The string content of this element
2993
3186
  */
2994
3187
  toString(fixed = false) {
2995
3188
  if (!fixed) {
@@ -3061,4 +3254,3 @@ export class MLElement extends MLParentNode {
3061
3254
  throw new UnexpectedCallError('Not supported "webkitMatchesSelector" method');
3062
3255
  }
3063
3256
  }
3064
- _MLElement_attributes = new WeakMap(), _MLElement_fixedNodeName = new WeakMap(), _MLElement_getChildElementsAndTextNodeWithoutWhitespacesCache = new WeakMap(), _MLElement_localName = new WeakMap(), _MLElement_normalizedAttrs = new WeakMap(), _MLElement_normalizedString = new WeakMap();
@@ -1,29 +1,21 @@
1
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
- 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");
4
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
- };
6
- var _NodeStore_store;
7
1
  import { TargetParserError } from '@markuplint/parser-utils';
8
2
  import { log } from '../../debug.js';
9
3
  const nodeStoreLog = log.extend('node-store');
10
4
  const nodeStoreError = nodeStoreLog.extend('error');
11
5
  class NodeStore {
12
- constructor() {
13
- _NodeStore_store.set(this, new Map());
14
- }
6
+ #store = new Map();
15
7
  getNode(astNode) {
16
8
  // console.log(`Get: ${astNode.uuid} -> ${astNode.raw.trim()}(${astNode.type})`);
17
- const node = __classPrivateFieldGet(this, _NodeStore_store, "f").get(astNode.uuid);
9
+ const node = this.#store.get(astNode.uuid);
18
10
  if (!node) {
19
11
  nodeStoreError('Ref ID: %s (%s: "%s")', astNode.uuid, astNode.nodeName, astNode.raw);
20
- nodeStoreError('Map: %O', [...__classPrivateFieldGet(this, _NodeStore_store, "f").entries()].map(([id, node]) => ({
12
+ nodeStoreError('Map: %O', [...this.#store.entries()].map(([id, node]) => ({
21
13
  id,
22
14
  name: node.nodeName,
23
15
  })));
24
16
  throw new TargetParserError('Broke mapping nodes.', {
25
- line: astNode.startLine,
26
- col: astNode.startCol,
17
+ line: astNode.line,
18
+ col: astNode.col,
27
19
  raw: astNode.raw,
28
20
  nodeName: astNode.nodeName,
29
21
  });
@@ -41,10 +33,9 @@ class NodeStore {
41
33
  nodeStoreError('Invalid node: %O', node);
42
34
  }
43
35
  nodeStoreLog('Mapped: %s (%s: "%s")', astNode.uuid, astNode.nodeName, astNode.raw.replaceAll('\n', '⏎').replaceAll('\t', '→'));
44
- __classPrivateFieldGet(this, _NodeStore_store, "f").set(astNode.uuid, node);
36
+ this.#store.set(astNode.uuid, node);
45
37
  }
46
38
  }
47
- _NodeStore_store = new WeakMap();
48
39
  /**
49
40
  * `NodeStore` Singleton
50
41
  */
@@ -8,6 +8,15 @@ import type { RuleInfo } from '../../index.js';
8
8
  import type { MLASTNode } from '@markuplint/ml-ast';
9
9
  import type { AnyRule, PlainData, RuleConfigValue } from '@markuplint/ml-config';
10
10
  import { MLToken } from '../token/token.js';
11
+ /**
12
+ * Abstract base class for all markuplint DOM node wrappers.
13
+ * Extends `MLToken` with DOM `Node` interface compliance, tree traversal,
14
+ * rule configuration access, and child node management.
15
+ *
16
+ * @template T - The rule configuration value type
17
+ * @template O - The rule options type
18
+ * @template A - The underlying AST node type
19
+ */
11
20
  export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainData = undefined, A extends MLASTNode = MLASTNode> extends MLToken<A> implements Node {
12
21
  #private;
13
22
  /**
@@ -127,6 +136,8 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
127
136
  get baseURI(): string;
128
137
  /**
129
138
  * The list of child nodes that contains `Element`, `Text`, and `Comment`.
139
+ * Fragment nodes and preprocessor blocks with `each` or `end` block behavior
140
+ * are transparent — their child nodes are flattened into this list.
130
141
  *
131
142
  * @readonly
132
143
  * @implements DOM API: `Node`
@@ -201,6 +212,13 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
201
212
  * @see https://dom.spec.whatwg.org/#ref-for-dom-node-ownerdocument%E2%91%A0
202
213
  */
203
214
  get ownerDocument(): any;
215
+ /**
216
+ * The owning `MLDocument` instance with proper generic types,
217
+ * providing type-safe access to the document without the `any` type
218
+ * used by the DOM-compatible `ownerDocument`.
219
+ *
220
+ * @implements `@markuplint/ml-core` API: `MLNode`
221
+ */
204
222
  get ownerMLDocument(): MLDocument<T, O>;
205
223
  /**
206
224
  * The parent element.
@@ -360,7 +378,7 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
360
378
  /**
361
379
  * Returns an array of NodeLists representing the conditional child nodes of the current node.
362
380
  * Conditional child nodes are nodes that are nested within
363
- * **preprocessor blocks** such as `if`, `each`, or `switch`.
381
+ * **preprocessor blocks** such as `if` or `switch`.
364
382
  * Each NodeList represents a branch of conditional child nodes.
365
383
  *
366
384
  * Note: NodeList doesn't include whitespace nodes.