@reteps/tree-sitter-htmlmustache 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,14 +10,21 @@
10
10
  * Supported user-facing syntax:
11
11
  * - Tag names (`div`), universal (`*`), classes (`.foo`), ids (`#foo`)
12
12
  * - Attributes: `[attr]`, `[attr=v]`, `[attr^=v]`, `[attr*=v]`, `[attr$=v]`, `[attr~=v]`
13
- * - Descendant (space) and child (`>`) combinators
13
+ * - Descendant (space), child (`>`), adjacent-sibling (`+`), and
14
+ * general-sibling (`~`) combinators. Sibling combinators skip over text /
15
+ * whitespace nodes (CSS semantics) and work across HTML and Mustache
16
+ * constructs: e.g. `label + input`, `{{foo}} + p`, `h2 ~ {{#items}}`.
14
17
  * - Mustache variables: `{{path}}` and `{{{path}}}` (raw)
15
18
  * - Mustache sections: `{{#name}}` and `{{^name}}` (inverted)
16
19
  * - Mustache comments: `{{!content}}`
17
20
  * - Mustache partials: `{{>name}}`
18
21
  * - Glob wildcard `*` inside the argument: `{{options.*}}`, `{{*.deprecated}}`, `{{*}}`
19
22
  * - `:has(selector)` — element has a matching descendant
20
- * - `:not(...)` over any attribute/class/id/:has form
23
+ * - `:not(...)` negation. Accepts attributes/class/id/`:has` (folded into
24
+ * the outer compound), plus any other selector (including Mustache
25
+ * literals and type selectors) as a whole-selector check against the
26
+ * node itself. Example: `{{*}}:not({{internal.*}})` matches any
27
+ * interpolation whose path does not start with `internal.`.
21
28
  * - `:root` — the tree-sitter fragment root (the whole document). Unlike
22
29
  * browser CSS where `:root` matches `<html>`, this matches the parse-tree
23
30
  * root so it works on partials/fragments too. Useful as a document-scoped
@@ -26,15 +33,19 @@
26
33
  * `pl-answer-panel` is. Cannot combine with tag/class/id/attribute in the
27
34
  * same compound (only with `:has` / `:not(:has(...))`). Inside `:has(...)`,
28
35
  * `:root` refers to the element being checked, not the document.
36
+ * - `:is(a, b, ...)` — matches if any alternative matches. Expanded at parse
37
+ * time into the Cartesian product of alternatives, so `:is(a, b) :is(c, d)`
38
+ * is equivalent to `a c, a d, b c, b d`. Alternatives inside `:is` that
39
+ * contain combinators are only allowed when the `:is(...)` stands alone in
40
+ * its compound (e.g. `:is(div > span, p)` works; `x:is(div > span, p)`
41
+ * does not, since a combinator can't be merged into another compound).
29
42
  * - Comma-separated alternatives
30
43
  *
31
44
  * Unsupported (parseSelector returns null, rule is skipped):
32
- * - Sibling combinators (`+`, `~`)
33
45
  * - `[attr|=v]`, case-insensitive `i` flag
34
46
  * - Mixed HTML + Mustache kinds in one compound (e.g. `img{{foo}}`)
35
47
  * - `{{/end}}` (end tags aren't standalone nodes)
36
48
  * - `{{=<% %>=}}` (delimiter changes aren't grammar-tracked)
37
- * - Mustache literals inside `:not(...)` (only attribute/class/id/:has)
38
49
  */
39
50
  import type { BalanceNode } from './htmlBalanceChecker.js';
40
51
  export type AttributeOperator = '=' | '^=' | '*=' | '$=' | '~=';
@@ -49,6 +60,7 @@ export interface DescendantCheck {
49
60
  selector: ParsedSelector;
50
61
  negated: boolean;
51
62
  }
63
+ export type Combinator = 'descendant' | 'child' | 'adjacent-sibling' | 'general-sibling';
52
64
  export interface Segment {
53
65
  kind: SegmentKind;
54
66
  rootOnly: boolean;
@@ -56,7 +68,8 @@ export interface Segment {
56
68
  pathRegex?: RegExp;
57
69
  attributes: AttributeConstraint[];
58
70
  descendantChecks: DescendantCheck[];
59
- combinator: 'descendant' | 'child';
71
+ selfNegations: ParsedSelector[];
72
+ combinator: Combinator;
60
73
  }
61
74
  /** A parsed selector is a list of alternatives (from comma-separated parts). */
62
75
  export type ParsedSelector = Segment[][];
@@ -70,5 +83,5 @@ export type ParsedSelector = Segment[][];
70
83
  */
71
84
  export declare function preprocessMustacheLiterals(raw: string): string | null;
72
85
  export declare function parseSelector(raw: string): ParsedSelector | null;
73
- export declare function matchSelector(rootNode: BalanceNode, selector: ParsedSelector): BalanceNode[];
86
+ export declare function matchSelector(rootNode: BalanceNode, selector: ParsedSelector, siblings?: BalanceNode[], indexInSiblings?: number): BalanceNode[];
74
87
  //# sourceMappingURL=selectorMatcher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"selectorMatcher.d.ts","sourceRoot":"","sources":["../../../src/core/selectorMatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAY3D,MAAM,MAAM,iBAAiB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhE,MAAM,MAAM,WAAW,GACnB,MAAM,GACN,SAAS,GACT,UAAU,GACV,UAAU,GACV,KAAK,GACL,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,iBAAiB,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,cAAc,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,UAAU,EAAE,YAAY,GAAG,OAAO,CAAC;CACpC;AAED,gFAAgF;AAChF,MAAM,MAAM,cAAc,GAAG,OAAO,EAAE,EAAE,CAAC;AAQzC;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuFrE;AAID,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAuBhE;AA0eD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,cAAc,GAAG,WAAW,EAAE,CAY5F"}
1
+ {"version":3,"file":"selectorMatcher.d.ts","sourceRoot":"","sources":["../../../src/core/selectorMatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAY3D,MAAM,MAAM,iBAAiB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhE,MAAM,MAAM,WAAW,GACnB,MAAM,GACN,SAAS,GACT,UAAU,GACV,UAAU,GACV,KAAK,GACL,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,iBAAiB,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,cAAc,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,OAAO,GAAG,kBAAkB,GAAG,iBAAiB,CAAC;AAEzF,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,aAAa,EAAE,cAAc,EAAE,CAAC;IAChC,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,gFAAgF;AAChF,MAAM,MAAM,cAAc,GAAG,OAAO,EAAE,EAAE,CAAC;AAQzC;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuFrE;AAID,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CA2BhE;AA2pBD,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,WAAW,EACrB,QAAQ,EAAE,cAAc,EACxB,QAAQ,GAAE,WAAW,EAAO,EAC5B,eAAe,SAAI,GAClB,WAAW,EAAE,CAYf"}
package/cli/out/main.js CHANGED
@@ -1939,13 +1939,85 @@ function parseSelector(raw) {
1939
1939
  const tops = ast.type === "list" ? ast.list : [ast];
1940
1940
  const alts = [];
1941
1941
  for (const top of tops) {
1942
- const segments = [];
1943
- if (!collectSegments(top, "descendant", segments)) return null;
1944
- if (segments.length === 0) return null;
1945
- alts.push(segments);
1942
+ const expanded = expandIs(top);
1943
+ if (expanded === null) return null;
1944
+ for (const exp of expanded) {
1945
+ const segments = [];
1946
+ if (!collectSegments(exp, "descendant", segments)) return null;
1947
+ if (segments.length === 0) return null;
1948
+ alts.push(segments);
1949
+ }
1946
1950
  }
1947
1951
  return alts.length > 0 ? alts : null;
1948
1952
  }
1953
+ function expandIs(ast) {
1954
+ switch (ast.type) {
1955
+ case "list": {
1956
+ const out = [];
1957
+ for (const alt of ast.list) {
1958
+ const expanded = expandIs(alt);
1959
+ if (expanded === null) return null;
1960
+ out.push(...expanded);
1961
+ }
1962
+ return out;
1963
+ }
1964
+ case "complex": {
1965
+ const lefts = expandIs(ast.left);
1966
+ if (lefts === null) return null;
1967
+ const rights = expandIs(ast.right);
1968
+ if (rights === null) return null;
1969
+ const out = [];
1970
+ for (const l2 of lefts) for (const r2 of rights) {
1971
+ out.push({ ...ast, left: l2, right: r2 });
1972
+ }
1973
+ return out;
1974
+ }
1975
+ case "compound": {
1976
+ if (ast.list.length === 1) {
1977
+ const tok = ast.list[0];
1978
+ if (tok.type === "pseudo-class" && tok.name === "is") {
1979
+ if (!tok.subtree) return null;
1980
+ return expandIs(tok.subtree);
1981
+ }
1982
+ }
1983
+ return expandCompoundWithIs(ast.list);
1984
+ }
1985
+ default:
1986
+ if (ast.type === "pseudo-class" && ast.name === "is") {
1987
+ if (!ast.subtree) return null;
1988
+ return expandIs(ast.subtree);
1989
+ }
1990
+ return [ast];
1991
+ }
1992
+ }
1993
+ function expandCompoundWithIs(tokens) {
1994
+ let variants = [[]];
1995
+ for (const tok of tokens) {
1996
+ if (tok.type === "pseudo-class" && tok.name === "is") {
1997
+ if (!tok.subtree) return null;
1998
+ const alts = expandIs(tok.subtree);
1999
+ if (alts === null) return null;
2000
+ const next = [];
2001
+ for (const base of variants) {
2002
+ for (const alt of alts) {
2003
+ if (alt.type === "compound") {
2004
+ next.push([...base, ...alt.list]);
2005
+ } else if (alt.type === "complex" || alt.type === "list" || alt.type === "relative") {
2006
+ return null;
2007
+ } else {
2008
+ next.push([...base, alt]);
2009
+ }
2010
+ }
2011
+ }
2012
+ variants = next;
2013
+ } else {
2014
+ variants = variants.map((v) => [...v, tok]);
2015
+ }
2016
+ }
2017
+ return variants.map(
2018
+ (list) => list.length === 1 ? list[0] : { type: "compound", list }
2019
+ );
2020
+ }
1949
2021
  function collectSegments(ast, combinator, out) {
1950
2022
  if (ast.type === "complex") {
1951
2023
  const mapped = mapCombinator(ast.combinator);
@@ -1963,6 +2035,8 @@ function mapCombinator(c2) {
1963
2035
  const trimmed = c2.trim();
1964
2036
  if (trimmed === "") return "descendant";
1965
2037
  if (trimmed === ">") return "child";
2038
+ if (trimmed === "+") return "adjacent-sibling";
2039
+ if (trimmed === "~") return "general-sibling";
1966
2040
  return null;
1967
2041
  }
1968
2042
  function segmentFromCompound(ast) {
@@ -1973,6 +2047,7 @@ function segmentFromCompound(ast) {
1973
2047
  let rootOnly = false;
1974
2048
  const attributes = [];
1975
2049
  const descendantChecks = [];
2050
+ const selfNegations = [];
1976
2051
  const forbidChange = (requested) => {
1977
2052
  if (kind === void 0) return false;
1978
2053
  if (kind === requested) return false;
@@ -2026,7 +2101,7 @@ function segmentFromCompound(ast) {
2026
2101
  break;
2027
2102
  }
2028
2103
  if (token.name === "not") {
2029
- if (!applyNegatedSubtree(token.subtree, attributes, descendantChecks)) return null;
2104
+ if (!applyNegatedSubtree(token.subtree, attributes, descendantChecks, selfNegations)) return null;
2030
2105
  break;
2031
2106
  }
2032
2107
  if (token.name === "root") {
@@ -2048,7 +2123,7 @@ function segmentFromCompound(ast) {
2048
2123
  }
2049
2124
  const isHtml = kind === "html";
2050
2125
  const finalAttrs = isHtml ? attributes : [];
2051
- return { kind, rootOnly, name, pathRegex, attributes: finalAttrs, descendantChecks, combinator: "descendant" };
2126
+ return { kind, rootOnly, name, pathRegex, attributes: finalAttrs, descendantChecks, selfNegations, combinator: "descendant" };
2052
2127
  }
2053
2128
  function mustacheKindFromMarker(name) {
2054
2129
  switch (name) {
@@ -2113,7 +2188,7 @@ function classConstraint(token, negated) {
2113
2188
  function idConstraint(token, negated) {
2114
2189
  return { name: "id", op: "=", value: token.name, negated };
2115
2190
  }
2116
- function applyNegatedSubtree(subtree, attributes, descendantChecks) {
2191
+ function applyNegatedSubtree(subtree, attributes, descendantChecks, selfNegations) {
2117
2192
  if (!subtree) return false;
2118
2193
  if (subtree.type === "attribute") {
2119
2194
  const c2 = attributeConstraint(subtree, true);
@@ -2130,9 +2205,14 @@ function applyNegatedSubtree(subtree, attributes, descendantChecks) {
2130
2205
  return true;
2131
2206
  }
2132
2207
  if (subtree.type === "pseudo-class" && subtree.name === "has") {
2133
- const sel = subtreeToSelector(subtree.subtree);
2134
- if (!sel) return false;
2135
- descendantChecks.push({ selector: sel, negated: true });
2208
+ const sel2 = subtreeToSelector(subtree.subtree);
2209
+ if (!sel2) return false;
2210
+ descendantChecks.push({ selector: sel2, negated: true });
2211
+ return true;
2212
+ }
2213
+ const sel = subtreeToSelector(subtree);
2214
+ if (sel) {
2215
+ selfNegations.push(sel);
2136
2216
  return true;
2137
2217
  }
2138
2218
  return false;
@@ -2232,11 +2312,20 @@ function checkDescendants(node, checks) {
2232
2312
  return true;
2233
2313
  }
2234
2314
  function hasDescendantMatch(node, selector) {
2235
- for (const child of node.children) {
2236
- if (matchSelector(child, selector).length > 0) return true;
2315
+ for (let i2 = 0; i2 < node.children.length; i2++) {
2316
+ if (matchSelector(node.children[i2], selector, node.children, i2).length > 0) return true;
2237
2317
  }
2238
2318
  return false;
2239
2319
  }
2320
+ function checkSelfNegations(node, negations, rootNode) {
2321
+ for (const sel of negations) {
2322
+ for (const alt of sel) {
2323
+ if (alt.length !== 1) continue;
2324
+ if (nodeMatchesSegment(node, alt[0], rootNode)) return false;
2325
+ }
2326
+ }
2327
+ return true;
2328
+ }
2240
2329
  function matchesName(actual, segment) {
2241
2330
  if (segment.name === null) return true;
2242
2331
  if (actual === null) return false;
@@ -2246,51 +2335,73 @@ function matchesName(actual, segment) {
2246
2335
  function nodeMatchesSegment(node, segment, rootNode) {
2247
2336
  if (segment.rootOnly) {
2248
2337
  if (node !== rootNode) return false;
2249
- return checkDescendants(node, segment.descendantChecks);
2250
- }
2251
- switch (segment.kind) {
2252
- case "html": {
2253
- if (!HTML_ELEMENT_TYPES.has(node.type)) return false;
2254
- if (segment.name !== null) {
2255
- const tagName = getTagName(node)?.toLowerCase();
2256
- if (tagName !== segment.name) return false;
2338
+ return checkDescendants(node, segment.descendantChecks) && checkSelfNegations(node, segment.selfNegations, rootNode);
2339
+ }
2340
+ const baseMatches = (() => {
2341
+ switch (segment.kind) {
2342
+ case "html": {
2343
+ if (!HTML_ELEMENT_TYPES.has(node.type)) return false;
2344
+ if (segment.name !== null) {
2345
+ const tagName = getTagName(node)?.toLowerCase();
2346
+ if (tagName !== segment.name) return false;
2347
+ }
2348
+ return checkAttributes(node, segment.attributes) && checkDescendants(node, segment.descendantChecks);
2257
2349
  }
2258
- return checkAttributes(node, segment.attributes) && checkDescendants(node, segment.descendantChecks);
2259
- }
2260
- case "section":
2261
- if (node.type !== "mustache_section") return false;
2262
- if (!matchesName(getSectionName(node)?.toLowerCase() ?? null, segment)) return false;
2263
- return checkDescendants(node, segment.descendantChecks);
2264
- case "inverted":
2265
- if (node.type !== "mustache_inverted_section") return false;
2266
- if (!matchesName(getSectionName(node)?.toLowerCase() ?? null, segment)) return false;
2267
- return checkDescendants(node, segment.descendantChecks);
2268
- case "variable":
2269
- if (node.type !== "mustache_interpolation") return false;
2270
- if (!matchesName(getInterpolationPath(node)?.toLowerCase() ?? null, segment)) return false;
2271
- return checkDescendants(node, segment.descendantChecks);
2272
- case "raw":
2273
- if (node.type !== "mustache_triple") return false;
2274
- if (!matchesName(getInterpolationPath(node)?.toLowerCase() ?? null, segment)) return false;
2275
- return checkDescendants(node, segment.descendantChecks);
2276
- case "comment":
2277
- if (node.type !== "mustache_comment") return false;
2278
- if (!matchesName(getCommentContent(node)?.toLowerCase() ?? null, segment)) return false;
2279
- return checkDescendants(node, segment.descendantChecks);
2280
- case "partial":
2281
- if (node.type !== "mustache_partial") return false;
2282
- if (!matchesName(getPartialName(node)?.toLowerCase() ?? null, segment)) return false;
2283
- return checkDescendants(node, segment.descendantChecks);
2284
- }
2285
- }
2286
- function checkAncestors(ancestors, segments, segIdx, childCombinator) {
2350
+ case "section":
2351
+ if (node.type !== "mustache_section") return false;
2352
+ if (!matchesName(getSectionName(node)?.toLowerCase() ?? null, segment)) return false;
2353
+ return checkDescendants(node, segment.descendantChecks);
2354
+ case "inverted":
2355
+ if (node.type !== "mustache_inverted_section") return false;
2356
+ if (!matchesName(getSectionName(node)?.toLowerCase() ?? null, segment)) return false;
2357
+ return checkDescendants(node, segment.descendantChecks);
2358
+ case "variable":
2359
+ if (node.type !== "mustache_interpolation") return false;
2360
+ if (!matchesName(getInterpolationPath(node)?.toLowerCase() ?? null, segment)) return false;
2361
+ return checkDescendants(node, segment.descendantChecks);
2362
+ case "raw":
2363
+ if (node.type !== "mustache_triple") return false;
2364
+ if (!matchesName(getInterpolationPath(node)?.toLowerCase() ?? null, segment)) return false;
2365
+ return checkDescendants(node, segment.descendantChecks);
2366
+ case "comment":
2367
+ if (node.type !== "mustache_comment") return false;
2368
+ if (!matchesName(getCommentContent(node)?.toLowerCase() ?? null, segment)) return false;
2369
+ return checkDescendants(node, segment.descendantChecks);
2370
+ case "partial":
2371
+ if (node.type !== "mustache_partial") return false;
2372
+ if (!matchesName(getPartialName(node)?.toLowerCase() ?? null, segment)) return false;
2373
+ return checkDescendants(node, segment.descendantChecks);
2374
+ }
2375
+ })();
2376
+ if (!baseMatches) return false;
2377
+ return checkSelfNegations(node, segment.selfNegations, rootNode);
2378
+ }
2379
+ function checkPrefix(cursor, segments, segIdx, stepCombinator, rootNode) {
2287
2380
  if (segIdx < 0) return true;
2288
2381
  const segment = segments[segIdx];
2382
+ if (stepCombinator === "adjacent-sibling" || stepCombinator === "general-sibling") {
2383
+ for (let i2 = cursor.indexInSiblings - 1; i2 >= 0; i2--) {
2384
+ const sib = cursor.siblings[i2];
2385
+ if (!isMatchableNode(sib)) continue;
2386
+ if (!nodeMatchesSegment(sib, segment, rootNode)) {
2387
+ if (stepCombinator === "adjacent-sibling") return false;
2388
+ continue;
2389
+ }
2390
+ const newCursor = {
2391
+ ancestors: cursor.ancestors,
2392
+ siblings: cursor.siblings,
2393
+ indexInSiblings: i2
2394
+ };
2395
+ if (checkPrefix(newCursor, segments, segIdx - 1, segment.combinator, rootNode)) return true;
2396
+ if (stepCombinator === "adjacent-sibling") return false;
2397
+ }
2398
+ return false;
2399
+ }
2289
2400
  const ancestorKind = ancestorKindForSegment(segment);
2290
2401
  if (ancestorKind === null) return false;
2291
- if (childCombinator === "child") {
2292
- for (let a2 = ancestors.length - 1; a2 >= 0; a2--) {
2293
- const entry = ancestors[a2];
2402
+ if (stepCombinator === "child") {
2403
+ for (let a2 = cursor.ancestors.length - 1; a2 >= 0; a2--) {
2404
+ const entry = cursor.ancestors[a2];
2294
2405
  if (entry.kind !== ancestorKind) {
2295
2406
  if (ancestorKind === "root" && entry.kind === "html") return false;
2296
2407
  continue;
@@ -2298,22 +2409,35 @@ function checkAncestors(ancestors, segments, segIdx, childCombinator) {
2298
2409
  if (!matchesName(entry.name, segment)) return false;
2299
2410
  if (segment.kind === "html" && !checkAttributes(entry.node, segment.attributes)) return false;
2300
2411
  if (!checkDescendants(entry.node, segment.descendantChecks)) return false;
2301
- return checkAncestors(ancestors.slice(0, a2), segments, segIdx - 1, segment.combinator);
2412
+ if (!checkSelfNegations(entry.node, segment.selfNegations, rootNode)) return false;
2413
+ const newCursor = {
2414
+ ancestors: cursor.ancestors.slice(0, a2),
2415
+ siblings: entry.siblings,
2416
+ indexInSiblings: entry.indexInSiblings
2417
+ };
2418
+ return checkPrefix(newCursor, segments, segIdx - 1, segment.combinator, rootNode);
2302
2419
  }
2303
2420
  return false;
2304
2421
  }
2305
- for (let a2 = ancestors.length - 1; a2 >= 0; a2--) {
2306
- const entry = ancestors[a2];
2422
+ for (let a2 = cursor.ancestors.length - 1; a2 >= 0; a2--) {
2423
+ const entry = cursor.ancestors[a2];
2307
2424
  if (entry.kind !== ancestorKind) continue;
2308
2425
  if (!matchesName(entry.name, segment)) continue;
2309
2426
  if (segment.kind === "html" && !checkAttributes(entry.node, segment.attributes)) continue;
2310
2427
  if (!checkDescendants(entry.node, segment.descendantChecks)) continue;
2311
- if (checkAncestors(ancestors.slice(0, a2), segments, segIdx - 1, segment.combinator)) {
2312
- return true;
2313
- }
2428
+ if (!checkSelfNegations(entry.node, segment.selfNegations, rootNode)) continue;
2429
+ const newCursor = {
2430
+ ancestors: cursor.ancestors.slice(0, a2),
2431
+ siblings: entry.siblings,
2432
+ indexInSiblings: entry.indexInSiblings
2433
+ };
2434
+ if (checkPrefix(newCursor, segments, segIdx - 1, segment.combinator, rootNode)) return true;
2314
2435
  }
2315
2436
  return false;
2316
2437
  }
2438
+ function isMatchableNode(node) {
2439
+ return HTML_ELEMENT_TYPES.has(node.type) || node.type === "mustache_section" || node.type === "mustache_inverted_section" || node.type === "mustache_interpolation" || node.type === "mustache_triple" || node.type === "mustache_comment" || node.type === "mustache_partial";
2440
+ }
2317
2441
  function ancestorKindForSegment(segment) {
2318
2442
  if (segment.rootOnly) return "root";
2319
2443
  if (segment.kind === "html") return "html";
@@ -2347,12 +2471,13 @@ function getReportNode(node, rootNode) {
2347
2471
  }
2348
2472
  return node;
2349
2473
  }
2350
- function matchAlternative(rootNode, segments) {
2474
+ function matchAlternative(rootNode, segments, rootSiblings, rootIndexInSiblings) {
2351
2475
  const results = [];
2352
2476
  const lastSegment = segments[segments.length - 1];
2353
- function walk(node, ancestors) {
2477
+ function walk(node, ancestors, siblings, indexInSiblings) {
2354
2478
  if (nodeMatchesSegment(node, lastSegment, rootNode)) {
2355
- if (segments.length === 1 || checkAncestors(ancestors, segments, segments.length - 2, lastSegment.combinator)) {
2479
+ const cursor = { ancestors, siblings, indexInSiblings };
2480
+ if (segments.length === 1 || checkPrefix(cursor, segments, segments.length - 2, lastSegment.combinator, rootNode)) {
2356
2481
  results.push(getReportNode(node, rootNode));
2357
2482
  }
2358
2483
  }
@@ -2361,19 +2486,28 @@ function matchAlternative(rootNode, segments) {
2361
2486
  if (ancestorKind !== null) {
2362
2487
  const name = ancestorKind === "html" ? getTagName(node)?.toLowerCase() : getSectionName(node)?.toLowerCase();
2363
2488
  if (name) {
2364
- newAncestors = [...ancestors, { kind: ancestorKind, name, node }];
2489
+ newAncestors = [...ancestors, { kind: ancestorKind, name, node, siblings, indexInSiblings }];
2365
2490
  }
2366
2491
  }
2367
- for (const child of node.children) walk(child, newAncestors);
2492
+ for (let i2 = 0; i2 < node.children.length; i2++) {
2493
+ walk(node.children[i2], newAncestors, node.children, i2);
2494
+ }
2368
2495
  }
2369
- walk(rootNode, [{ kind: "root", name: "", node: rootNode }]);
2496
+ const rootEntry = {
2497
+ kind: "root",
2498
+ name: "",
2499
+ node: rootNode,
2500
+ siblings: rootSiblings,
2501
+ indexInSiblings: rootIndexInSiblings
2502
+ };
2503
+ walk(rootNode, [rootEntry], rootSiblings, rootIndexInSiblings);
2370
2504
  return results;
2371
2505
  }
2372
- function matchSelector(rootNode, selector) {
2506
+ function matchSelector(rootNode, selector, siblings = [], indexInSiblings = 0) {
2373
2507
  const allResults = [];
2374
2508
  const seen = /* @__PURE__ */ new Set();
2375
2509
  for (const alt of selector) {
2376
- for (const node of matchAlternative(rootNode, alt)) {
2510
+ for (const node of matchAlternative(rootNode, alt, siblings, indexInSiblings)) {
2377
2511
  if (!seen.has(node)) {
2378
2512
  seen.add(node);
2379
2513
  allResults.push(node);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reteps/tree-sitter-htmlmustache",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "HTML with Mustache/Handlebars template syntax grammar for tree-sitter",
5
5
  "repository": {
6
6
  "type": "git",