html-validate 8.25.0 → 8.26.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.
package/dist/cjs/core.js CHANGED
@@ -1753,7 +1753,31 @@ class DOMNode {
1753
1753
  return text;
1754
1754
  }
1755
1755
  append(node) {
1756
+ const oldParent = node._setParent(this);
1757
+ if (oldParent && this.isSameNode(oldParent)) {
1758
+ return;
1759
+ }
1756
1760
  this.childNodes.push(node);
1761
+ if (oldParent) {
1762
+ oldParent._removeChild(node);
1763
+ }
1764
+ }
1765
+ /**
1766
+ * Insert a node before a reference node.
1767
+ *
1768
+ * @internal
1769
+ */
1770
+ insertBefore(node, reference) {
1771
+ const index = reference ? this.childNodes.findIndex((it) => it.isSameNode(reference)) : -1;
1772
+ if (index >= 0) {
1773
+ this.childNodes.splice(index, 0, node);
1774
+ } else {
1775
+ this.childNodes.push(node);
1776
+ }
1777
+ const oldParent = node._setParent(this);
1778
+ if (oldParent) {
1779
+ oldParent._removeChild(node);
1780
+ }
1757
1781
  }
1758
1782
  isRootElement() {
1759
1783
  return this.nodeType === NodeType.DOCUMENT_NODE;
@@ -1780,6 +1804,14 @@ class DOMNode {
1780
1804
  get lastChild() {
1781
1805
  return this.childNodes[this.childNodes.length - 1] || null;
1782
1806
  }
1807
+ /**
1808
+ * @internal
1809
+ */
1810
+ removeChild(node) {
1811
+ this._removeChild(node);
1812
+ node._setParent(null);
1813
+ return node;
1814
+ }
1783
1815
  /**
1784
1816
  * Block a rule for this node.
1785
1817
  *
@@ -1854,6 +1886,22 @@ class DOMNode {
1854
1886
  generateSelector() {
1855
1887
  return null;
1856
1888
  }
1889
+ /**
1890
+ * @internal
1891
+ *
1892
+ * @returns Old parent, if set.
1893
+ */
1894
+ _setParent(_node) {
1895
+ return null;
1896
+ }
1897
+ _removeChild(node) {
1898
+ const index = this.childNodes.findIndex((it) => it.isSameNode(node));
1899
+ if (index >= 0) {
1900
+ this.childNodes.splice(index, 1);
1901
+ } else {
1902
+ throw new Error("DOMException: _removeChild(..) could not find child to remove");
1903
+ }
1904
+ }
1857
1905
  }
1858
1906
 
1859
1907
  function parse(text, baseLocation) {
@@ -2373,7 +2421,7 @@ class HtmlElement extends DOMNode {
2373
2421
  throw new Error(`The tag name provided ("${tagName}") is not a valid name`);
2374
2422
  }
2375
2423
  this.tagName = tagName ?? "#document";
2376
- this.parent = parent ?? null;
2424
+ this._parent = null;
2377
2425
  this.attr = {};
2378
2426
  this.metaElement = meta ?? null;
2379
2427
  this.closed = closed;
@@ -2382,7 +2430,7 @@ class HtmlElement extends DOMNode {
2382
2430
  this.annotation = null;
2383
2431
  this._adapter = createAdapter(this);
2384
2432
  if (parent) {
2385
- parent.childNodes.push(this);
2433
+ parent.append(this);
2386
2434
  let cur = parent;
2387
2435
  while (cur.parent) {
2388
2436
  this.depth++;
@@ -2606,6 +2654,9 @@ class HtmlElement extends DOMNode {
2606
2654
  get meta() {
2607
2655
  return this.metaElement;
2608
2656
  }
2657
+ get parent() {
2658
+ return this._parent;
2659
+ }
2609
2660
  /**
2610
2661
  * Get current role for this element (explicit with `role` attribute or mapped
2611
2662
  * with implicit role).
@@ -2866,6 +2917,14 @@ class HtmlElement extends DOMNode {
2866
2917
  }
2867
2918
  return visit(this);
2868
2919
  }
2920
+ /**
2921
+ * @internal
2922
+ */
2923
+ _setParent(node) {
2924
+ const oldParent = this._parent;
2925
+ this._parent = node instanceof HtmlElement ? node : null;
2926
+ return oldParent;
2927
+ }
2869
2928
  }
2870
2929
  function isClosed(endToken, meta) {
2871
2930
  let closed = 0 /* Open */;
@@ -2890,20 +2949,22 @@ function dumpTree(root) {
2890
2949
  }
2891
2950
  return output;
2892
2951
  }
2893
- function writeNode(node, level, sibling) {
2952
+ function writeNode(node, level, indent, sibling) {
2953
+ const numSiblings = node.parent ? node.parent.childElements.length : 0;
2954
+ const lastSibling = sibling === numSiblings - 1;
2894
2955
  if (node.parent) {
2895
- const indent = " ".repeat(level - 1);
2896
- const l = node.childElements.length > 0 ? "\u252C" : "\u2500";
2897
- const b = sibling < node.parent.childElements.length - 1 ? "\u251C" : "\u2514";
2898
- lines.push(`${indent}${b}\u2500${l} ${node.tagName}${decoration(node)}`);
2956
+ const b = lastSibling ? "\u2514" : "\u251C";
2957
+ lines.push(`${indent}${b}\u2500\u2500 ${node.tagName}${decoration(node)}`);
2899
2958
  } else {
2900
2959
  lines.push("(root)");
2901
2960
  }
2902
2961
  node.childElements.forEach((child, index) => {
2903
- writeNode(child, level + 1, index);
2962
+ const s = lastSibling ? " " : "\u2502";
2963
+ const i = level > 0 ? `${indent}${s} ` : "";
2964
+ writeNode(child, level + 1, i, index);
2904
2965
  });
2905
2966
  }
2906
- writeNode(root, 0, 0);
2967
+ writeNode(root, 0, "", 0);
2907
2968
  return lines;
2908
2969
  }
2909
2970
 
@@ -4998,6 +5059,27 @@ class CloseAttr extends Rule {
4998
5059
  }
4999
5060
  }
5000
5061
 
5062
+ function* ancestors(node) {
5063
+ if (!node) {
5064
+ return;
5065
+ }
5066
+ let ancestor = node;
5067
+ while (ancestor && !ancestor.isRootElement()) {
5068
+ yield ancestor;
5069
+ ancestor = ancestor.parent;
5070
+ }
5071
+ if (ancestor) {
5072
+ yield ancestor;
5073
+ }
5074
+ }
5075
+ function findAncestor(node, predicate) {
5076
+ for (const ancestor of ancestors(node)) {
5077
+ if (predicate(ancestor)) {
5078
+ return ancestor;
5079
+ }
5080
+ }
5081
+ return null;
5082
+ }
5001
5083
  class CloseOrder extends Rule {
5002
5084
  documentation() {
5003
5085
  return {
@@ -5006,15 +5088,28 @@ class CloseOrder extends Rule {
5006
5088
  };
5007
5089
  }
5008
5090
  setup() {
5091
+ let reported;
5092
+ this.on("parse:begin", () => {
5093
+ reported = /* @__PURE__ */ new Set();
5094
+ });
5095
+ this.on("tag:end", (event) => {
5096
+ const current = event.target;
5097
+ const active = event.previous;
5098
+ if (current) {
5099
+ return;
5100
+ }
5101
+ for (const ancestor of ancestors(active)) {
5102
+ if (ancestor.isRootElement() || reported.has(ancestor.unique)) {
5103
+ continue;
5104
+ }
5105
+ this.report(ancestor, `Unclosed element '<${ancestor.tagName}>'`, ancestor.location);
5106
+ reported.add(ancestor.unique);
5107
+ }
5108
+ });
5009
5109
  this.on("tag:end", (event) => {
5010
5110
  const current = event.target;
5011
5111
  const active = event.previous;
5012
5112
  if (!current) {
5013
- this.report(
5014
- null,
5015
- `Missing close-tag, expected '</${active.tagName}>' but document ended before it was found.`,
5016
- event.location
5017
- );
5018
5113
  return;
5019
5114
  }
5020
5115
  if (current.voidElement) {
@@ -5031,15 +5126,32 @@ class CloseOrder extends Rule {
5031
5126
  offset: current.location.offset,
5032
5127
  size: current.tagName.length + 1
5033
5128
  };
5034
- this.report(null, "Unexpected close-tag, expected opening tag.", location);
5129
+ this.report(null, `Stray end tag '</${current.tagName}>'`, location);
5130
+ return;
5131
+ }
5132
+ if (current.tagName === active.tagName) {
5035
5133
  return;
5036
5134
  }
5037
- if (current.tagName !== active.tagName) {
5135
+ const ancestor = findAncestor(active.parent, (node) => node.is(current.tagName));
5136
+ if (ancestor && !ancestor.isRootElement()) {
5137
+ for (const element of ancestors(active)) {
5138
+ if (ancestor.isSameNode(element)) {
5139
+ break;
5140
+ }
5141
+ if (reported.has(element.unique)) {
5142
+ continue;
5143
+ }
5144
+ this.report(element, `Unclosed element '<${element.tagName}>'`, element.location);
5145
+ reported.add(element.unique);
5146
+ }
5038
5147
  this.report(
5039
5148
  null,
5040
- `Mismatched close-tag, expected '</${active.tagName}>' but found '</${current.tagName}>'.`,
5149
+ `End tag '</${current.tagName}>' seen but there were open elements`,
5041
5150
  current.location
5042
5151
  );
5152
+ reported.add(ancestor.unique);
5153
+ } else {
5154
+ this.report(null, `Stray end tag '</${current.tagName}>'`, current.location);
5043
5155
  }
5044
5156
  });
5045
5157
  }
@@ -7262,8 +7374,9 @@ Omitted end tags can be ambigious for humans to read and many editors have troub
7262
7374
  if (closed.closed !== NodeClosed.ImplicitClosed) {
7263
7375
  return;
7264
7376
  }
7265
- const closedByParent = closed.parent && closed.parent.tagName === by.tagName;
7266
- const closedByDocument = closedByParent && closed.parent.isRootElement();
7377
+ const parent = closed.parent;
7378
+ const closedByParent = parent && parent.tagName === by.tagName;
7379
+ const closedByDocument = closedByParent && parent.isRootElement();
7267
7380
  const sameTag = closed.tagName === by.tagName;
7268
7381
  if (closedByDocument) {
7269
7382
  this.report(
@@ -11594,8 +11707,9 @@ class Parser {
11594
11707
  node.closed = NodeClosed.EndTag;
11595
11708
  }
11596
11709
  this.closeElement(source, node, active, endToken.location);
11710
+ const mismatched = node.tagName !== active.tagName;
11597
11711
  const voidClosed = !isStartTag && node.voidElement;
11598
- if (!voidClosed) {
11712
+ if (!voidClosed && !mismatched) {
11599
11713
  this.dom.popActive();
11600
11714
  }
11601
11715
  } else if (isForeign) {
@@ -11613,6 +11727,9 @@ class Parser {
11613
11727
  location
11614
11728
  };
11615
11729
  this.trigger("tag:end", event);
11730
+ if (node && node.tagName !== active.tagName && active.closed !== NodeClosed.ImplicitClosed) {
11731
+ return;
11732
+ }
11616
11733
  if (!active.isRootElement()) {
11617
11734
  this.trigger("element:ready", {
11618
11735
  target: active,
@@ -12895,7 +13012,7 @@ class HtmlValidate {
12895
13012
  }
12896
13013
 
12897
13014
  const name = "html-validate";
12898
- const version = "8.25.0";
13015
+ const version = "8.25.1";
12899
13016
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
12900
13017
 
12901
13018
  function definePlugin(plugin) {