html-validate 10.13.1 → 10.14.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.
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.58.2"
8
+ "packageVersion": "7.58.7"
9
9
  }
10
10
  ]
11
11
  }
package/dist/esm/core.js CHANGED
@@ -2111,6 +2111,26 @@ class Compound {
2111
2111
  }
2112
2112
  }
2113
2113
 
2114
+ const codepoints = {
2115
+ " ": "\\9 ",
2116
+ "\n": "\\a ",
2117
+ "\r": "\\d "
2118
+ };
2119
+ function escapeSelectorComponent(text) {
2120
+ return text.toString().replaceAll(/([\t\n\r]|[^\w-])/gi, (_, ch) => {
2121
+ if (codepoints[ch]) {
2122
+ return codepoints[ch];
2123
+ } else {
2124
+ return `\\${ch}`;
2125
+ }
2126
+ });
2127
+ }
2128
+
2129
+ function generateIdSelector(id) {
2130
+ const escaped = escapeSelectorComponent(id);
2131
+ return /^\d/.test(escaped) ? `[id="${escaped}"]` : `#${escaped}`;
2132
+ }
2133
+
2114
2134
  function* ancestors$1(element) {
2115
2135
  let current = element.parent;
2116
2136
  while (current && !current.isRootElement()) {
@@ -2233,24 +2253,6 @@ function unescapeCodepoint(value) {
2233
2253
  (_, codepoint) => replacement[codepoint]
2234
2254
  );
2235
2255
  }
2236
- function escapeSelectorComponent(text) {
2237
- const codepoints = {
2238
- " ": "\\9 ",
2239
- "\n": "\\a ",
2240
- "\r": "\\d "
2241
- };
2242
- return text.toString().replaceAll(/([\t\n\r]|[^\w-])/gi, (_, ch) => {
2243
- if (codepoints[ch]) {
2244
- return codepoints[ch];
2245
- } else {
2246
- return `\\${ch}`;
2247
- }
2248
- });
2249
- }
2250
- function generateIdSelector(id) {
2251
- const escaped = escapeSelectorComponent(id);
2252
- return /^\d/.test(escaped) ? `[id="${escaped}"]` : `#${escaped}`;
2253
- }
2254
2256
  class Selector {
2255
2257
  pattern;
2256
2258
  constructor(selector) {
@@ -2369,6 +2371,7 @@ class TextNode extends DOMNode {
2369
2371
  }
2370
2372
  }
2371
2373
 
2374
+ const CHILD_ELEMENTS = /* @__PURE__ */ Symbol("childElements");
2372
2375
  const ROLE = /* @__PURE__ */ Symbol("role");
2373
2376
  const TABINDEX = /* @__PURE__ */ Symbol("tabindex");
2374
2377
  var NodeClosed = /* @__PURE__ */ ((NodeClosed2) => {
@@ -2535,7 +2538,11 @@ class HtmlElement extends DOMNode {
2535
2538
  * Similar to childNodes but only elements.
2536
2539
  */
2537
2540
  get childElements() {
2538
- return this.childNodes.filter(isElementNode);
2541
+ const cached = this.cacheGet(CHILD_ELEMENTS);
2542
+ if (cached !== void 0) {
2543
+ return cached;
2544
+ }
2545
+ return this.cacheSet(CHILD_ELEMENTS, this.childNodes.filter(isElementNode));
2539
2546
  }
2540
2547
  /**
2541
2548
  * Find the first ancestor matching a selector.
@@ -2931,12 +2938,24 @@ class HtmlElement extends DOMNode {
2931
2938
  }
2932
2939
  return visit(this);
2933
2940
  }
2941
+ append(node) {
2942
+ super.append(node);
2943
+ this.cacheRemove(CHILD_ELEMENTS);
2944
+ }
2945
+ insertBefore(node, reference) {
2946
+ super.insertBefore(node, reference);
2947
+ this.cacheRemove(CHILD_ELEMENTS);
2948
+ }
2949
+ removeChild(node) {
2950
+ return super.removeChild(node);
2951
+ }
2934
2952
  /**
2935
2953
  * @internal
2936
2954
  */
2937
2955
  _setParent(node) {
2938
2956
  const oldParent = this._parent;
2939
2957
  this._parent = node instanceof HtmlElement ? node : null;
2958
+ oldParent?.cacheRemove(CHILD_ELEMENTS);
2940
2959
  return oldParent;
2941
2960
  }
2942
2961
  }
@@ -12504,7 +12523,7 @@ class EventHandler {
12504
12523
  }
12505
12524
 
12506
12525
  const name = "html-validate";
12507
- const version = "10.13.1";
12526
+ const version = "10.14.0";
12508
12527
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
12509
12528
 
12510
12529
  function freeze(src) {
@@ -12804,32 +12823,92 @@ class Parser {
12804
12823
  * valid). The parser handles this by checking if the element on top of the
12805
12824
  * stack when is allowed to omit.
12806
12825
  */
12826
+ /**
12827
+ * Check whether a given element would be implicitly closed by an incoming
12828
+ * start tag. Used both in `closeOptional` and in the multi-level lookahead.
12829
+ */
12830
+ wouldCloseElement(token, element) {
12831
+ if (!element.meta) {
12832
+ return false;
12833
+ }
12834
+ const implicitClosed = element.meta.implicitClosed;
12835
+ if (!implicitClosed) {
12836
+ return false;
12837
+ }
12838
+ const tagName = token.data[2];
12839
+ const incomingMeta = this.metaTable.getMetaFor(tagName);
12840
+ return implicitClosed.some((entry) => {
12841
+ if (!entry.startsWith("@")) {
12842
+ return entry === tagName;
12843
+ }
12844
+ return incomingMeta ? matchesContentCategory(incomingMeta, entry) : false;
12845
+ });
12846
+ }
12847
+ /**
12848
+ * Walk up the active stack to find the parent element that will remain
12849
+ * after all multi-level implicit closes triggered by the incoming start tag.
12850
+ * For a single-level close this is equivalent to `getActive().parent`.
12851
+ */
12852
+ getParentAfterImplicitClose(token) {
12853
+ let current = this.dom.getActive();
12854
+ while (!current.isRootElement() && this.wouldCloseElement(token, current)) {
12855
+ const { parent } = current;
12856
+ if (!parent) {
12857
+ break;
12858
+ }
12859
+ current = parent;
12860
+ }
12861
+ return current;
12862
+ }
12807
12863
  closeOptional(token) {
12808
12864
  const active = this.dom.getActive();
12809
12865
  if (!active.meta) {
12810
12866
  return false;
12811
12867
  }
12812
- const tagName = token.data[2];
12813
12868
  const open = !token.data[1];
12814
- const implicitClosed = active.meta.implicitClosed;
12815
12869
  if (open) {
12816
- if (!implicitClosed) {
12817
- return false;
12818
- }
12819
- const incomingMeta = this.metaTable.getMetaFor(tagName);
12820
- return implicitClosed.some((entry) => {
12821
- if (!entry.startsWith("@")) {
12822
- return entry === tagName;
12823
- }
12824
- return incomingMeta ? matchesContentCategory(incomingMeta, entry) : false;
12825
- });
12870
+ return this.wouldCloseElement(token, active);
12826
12871
  } else {
12827
- if (active.is(tagName)) {
12872
+ return this.closeOptionalEndTag(token, active);
12873
+ }
12874
+ }
12875
+ /**
12876
+ * Returns `true` if the element’s end tag may be omitted, either because
12877
+ * its `implicitClosed` list includes its own tag name (e.g. `<li>`, `<td>`)
12878
+ * or because `optionalEnd` is set.
12879
+ */
12880
+ canOmitEndTag(element) {
12881
+ if (!element.meta) {
12882
+ return false;
12883
+ }
12884
+ const { implicitClosed, optionalEnd } = element.meta;
12885
+ return Boolean(implicitClosed?.includes(element.tagName)) || Boolean(optionalEnd);
12886
+ }
12887
+ /**
12888
+ * Check whether the active element can be implicitly closed by an incoming
12889
+ * end tag. The end tag may close a direct parent or any ancestor, as long as
12890
+ * every intermediate element can also have its end tag omitted.
12891
+ * This handles cases like `</table>` implicitly closing `<td>`, `<tr>`, `<tbody>`.
12892
+ */
12893
+ closeOptionalEndTag(token, active) {
12894
+ const tagName = token.data[2];
12895
+ if (active.is(tagName)) {
12896
+ return false;
12897
+ }
12898
+ if (!this.canOmitEndTag(active)) {
12899
+ return false;
12900
+ }
12901
+ let ancestor = active.parent;
12902
+ while (ancestor && !ancestor.isRootElement()) {
12903
+ if (ancestor.is(tagName)) {
12904
+ return true;
12905
+ }
12906
+ if (!this.canOmitEndTag(ancestor)) {
12828
12907
  return false;
12829
12908
  }
12830
- const canOmitEnd = Boolean(implicitClosed?.includes(active.tagName)) || Boolean(active.meta.optionalEnd);
12831
- return Boolean(active.parent && active.parent.is(tagName) && canOmitEnd);
12909
+ ancestor = ancestor.parent;
12832
12910
  }
12911
+ return false;
12833
12912
  }
12834
12913
  /**
12835
12914
  * Check whether an intermediary element (e.g. `<head>` or `<body>`) should
@@ -12918,8 +12997,21 @@ class Parser {
12918
12997
  );
12919
12998
  const endToken = tokens.at(-1);
12920
12999
  const isStartTag = !startToken.data[1];
12921
- const closeOptional = this.closeOptional(startToken);
12922
- const baseParent = closeOptional ? this.dom.getActive().parent : this.dom.getActive();
13000
+ let baseParent;
13001
+ if (isStartTag) {
13002
+ baseParent = this.getParentAfterImplicitClose(startToken);
13003
+ } else {
13004
+ const tagName = startToken.data[2];
13005
+ let cur = this.dom.getActive();
13006
+ while (!cur.isRootElement() && !cur.is(tagName)) {
13007
+ const { parent: parent2 } = cur;
13008
+ if (!parent2) {
13009
+ break;
13010
+ }
13011
+ cur = parent2;
13012
+ }
13013
+ baseParent = cur;
13014
+ }
12923
13015
  const implicitParent = isStartTag ? this.peekImplicitOpen(startToken, baseParent) : null;
12924
13016
  const parent = implicitParent ?? baseParent;
12925
13017
  const node = HtmlElement.fromTokens(
@@ -12931,7 +13023,7 @@ class Parser {
12931
13023
  );
12932
13024
  const isClosing = !isStartTag || node.closed !== NodeClosed.Open;
12933
13025
  const isForeign = node.meta?.foreign;
12934
- if (closeOptional) {
13026
+ while (this.closeOptional(startToken)) {
12935
13027
  const active = this.dom.getActive();
12936
13028
  active.closed = NodeClosed.ImplicitClosed;
12937
13029
  this.closeElement(source, node, active, startToken.location);