2424 * the outer compound), plus any other selector (including Mustache
2525 * literals and type selectors) as a whole-selector check against the
2626 * node itself. Example: `{{*}}:not({{internal.*}})` matches any
27- * interpolation whose path does not start with `internal.`.
27+ * interpolation whose path does not start with `internal.`. Multi-segment
28+ * selectors with combinators are supported and tested against the node's
29+ * ancestor/sibling context, matching `Element.matches` semantics — e.g.
30+ * `img:not(pl-overlay img)` matches any `img` that is not a descendant of
31+ * `pl-overlay`, and `td:not(thead td)` matches `td`s outside a `thead`.
2832 * - `:root` — the tree-sitter fragment root (the whole document). Unlike
2933 * browser CSS where `:root` matches `<html>`, this matches the parse-tree
3034 * root so it works on partials/fragments too. Useful as a document-scoped
@@ -651,14 +655,25 @@ function hasDescendantMatch(node: BalanceNode, selector: ParsedSelector): boolea
651655 return false ;
652656}
653657
654- function checkSelfNegations ( node : BalanceNode , negations : ParsedSelector [ ] , rootNode : BalanceNode ) : boolean {
658+ function checkSelfNegations (
659+ node : BalanceNode ,
660+ negations : ParsedSelector [ ] ,
661+ rootNode : BalanceNode ,
662+ cursor : Cursor ,
663+ ) : boolean {
655664 for ( const sel of negations ) {
656665 for ( const alt of sel ) {
657- // A selfNegation selector is a single-segment check against the node itself.
658- // Multi-segment alternatives (e.g. `:not(a b)`) aren't sensibly testable
659- // against a single node, so they're treated as never matching => pass.
660- if ( alt . length !== 1 ) continue ;
661- if ( nodeMatchesSegment ( node , alt [ 0 ] , rootNode ) ) return false ;
666+ if ( alt . length === 0 ) continue ;
667+ const lastSegment = alt [ alt . length - 1 ] ;
668+ if ( ! nodeMatchesSegment ( node , lastSegment , rootNode , cursor ) ) continue ;
669+ if ( alt . length === 1 ) return false ;
670+ // Multi-segment negation (e.g. `:not(ancestor descendant)`): the last
671+ // segment matched the node itself; walk back through the remaining
672+ // segments against the node's ancestor/sibling cursor. If the chain
673+ // also matches, the negation fires.
674+ if ( checkPrefix ( cursor , alt , alt . length - 2 , lastSegment . combinator , rootNode ) ) {
675+ return false ;
676+ }
662677 }
663678 }
664679 return true ;
@@ -671,11 +686,16 @@ function matchesName(actual: string | null, segment: Segment): boolean {
671686 return actual === segment . name ;
672687}
673688
674- function nodeMatchesSegment ( node : BalanceNode , segment : Segment , rootNode : BalanceNode ) : boolean {
689+ function nodeMatchesSegment (
690+ node : BalanceNode ,
691+ segment : Segment ,
692+ rootNode : BalanceNode ,
693+ cursor : Cursor ,
694+ ) : boolean {
675695 if ( segment . rootOnly ) {
676696 if ( node !== rootNode ) return false ;
677697 return checkDescendants ( node , segment . descendantChecks )
678- && checkSelfNegations ( node , segment . selfNegations , rootNode ) ;
698+ && checkSelfNegations ( node , segment . selfNegations , rootNode , cursor ) ;
679699 }
680700 const baseMatches = ( ( ) => {
681701 switch ( segment . kind ) {
@@ -714,7 +734,7 @@ function nodeMatchesSegment(node: BalanceNode, segment: Segment, rootNode: Balan
714734 }
715735 } ) ( ) ;
716736 if ( ! baseMatches ) return false ;
717- return checkSelfNegations ( node , segment . selfNegations , rootNode ) ;
737+ return checkSelfNegations ( node , segment . selfNegations , rootNode , cursor ) ;
718738}
719739
720740interface Cursor {
@@ -738,16 +758,16 @@ function checkPrefix(
738758 for ( let i = cursor . indexInSiblings - 1 ; i >= 0 ; i -- ) {
739759 const sib = cursor . siblings [ i ] ;
740760 if ( ! isMatchableNode ( sib ) ) continue ;
741- if ( ! nodeMatchesSegment ( sib , segment , rootNode ) ) {
742- if ( stepCombinator === 'adjacent-sibling' ) return false ;
743- continue ;
744- }
745- const newCursor : Cursor = {
761+ const sibCursor : Cursor = {
746762 ancestors : cursor . ancestors ,
747763 siblings : cursor . siblings ,
748764 indexInSiblings : i ,
749765 } ;
750- if ( checkPrefix ( newCursor , segments , segIdx - 1 , segment . combinator , rootNode ) ) return true ;
766+ if ( ! nodeMatchesSegment ( sib , segment , rootNode , sibCursor ) ) {
767+ if ( stepCombinator === 'adjacent-sibling' ) return false ;
768+ continue ;
769+ }
770+ if ( checkPrefix ( sibCursor , segments , segIdx - 1 , segment . combinator , rootNode ) ) return true ;
751771 if ( stepCombinator === 'adjacent-sibling' ) return false ;
752772 }
753773 return false ;
@@ -770,13 +790,13 @@ function checkPrefix(
770790 if ( ! matchesName ( entry . name , segment ) ) return false ;
771791 if ( segment . kind === 'html' && ! checkAttributes ( entry . node , segment . attributes ) ) return false ;
772792 if ( ! checkDescendants ( entry . node , segment . descendantChecks ) ) return false ;
773- if ( ! checkSelfNegations ( entry . node , segment . selfNegations , rootNode ) ) return false ;
774- const newCursor : Cursor = {
793+ const ancestorCursor : Cursor = {
775794 ancestors : cursor . ancestors . slice ( 0 , a ) ,
776795 siblings : entry . siblings ,
777796 indexInSiblings : entry . indexInSiblings ,
778797 } ;
779- return checkPrefix ( newCursor , segments , segIdx - 1 , segment . combinator , rootNode ) ;
798+ if ( ! checkSelfNegations ( entry . node , segment . selfNegations , rootNode , ancestorCursor ) ) return false ;
799+ return checkPrefix ( ancestorCursor , segments , segIdx - 1 , segment . combinator , rootNode ) ;
780800 }
781801 return false ;
782802 }
@@ -787,13 +807,13 @@ function checkPrefix(
787807 if ( ! matchesName ( entry . name , segment ) ) continue ;
788808 if ( segment . kind === 'html' && ! checkAttributes ( entry . node , segment . attributes ) ) continue ;
789809 if ( ! checkDescendants ( entry . node , segment . descendantChecks ) ) continue ;
790- if ( ! checkSelfNegations ( entry . node , segment . selfNegations , rootNode ) ) continue ;
791- const newCursor : Cursor = {
810+ const ancestorCursor : Cursor = {
792811 ancestors : cursor . ancestors . slice ( 0 , a ) ,
793812 siblings : entry . siblings ,
794813 indexInSiblings : entry . indexInSiblings ,
795814 } ;
796- if ( checkPrefix ( newCursor , segments , segIdx - 1 , segment . combinator , rootNode ) ) return true ;
815+ if ( ! checkSelfNegations ( entry . node , segment . selfNegations , rootNode , ancestorCursor ) ) continue ;
816+ if ( checkPrefix ( ancestorCursor , segments , segIdx - 1 , segment . combinator , rootNode ) ) return true ;
797817 }
798818 return false ;
799819}
@@ -862,8 +882,8 @@ function matchAlternative(
862882 siblings : BalanceNode [ ] ,
863883 indexInSiblings : number ,
864884 ) {
865- if ( nodeMatchesSegment ( node , lastSegment , rootNode ) ) {
866- const cursor : Cursor = { ancestors , siblings , indexInSiblings } ;
885+ const cursor : Cursor = { ancestors , siblings , indexInSiblings } ;
886+ if ( nodeMatchesSegment ( node , lastSegment , rootNode , cursor ) ) {
867887 if (
868888 segments . length === 1 ||
869889 checkPrefix ( cursor , segments , segments . length - 2 , lastSegment . combinator , rootNode )
0 commit comments