import type { AST } from './types' ; export function matchPattern ( pattern , text ) { if ( pattern . startsWith ( '/' ) && ( pattern . endsWith ( '/' ) || pattern . endsWith ( '/i' ) ) ) { let caseSensitive = true ; pattern = pattern . slice ( 1 ) ; if ( pattern . endsWith ( '/' ) ) { pattern = pattern . slice ( 0 , - 1 ) ; } else { pattern = pattern . slice ( 0 , - 2 ) ; caseSensitive = false ; } return new RegExp ( pattern , caseSensitive === false ? 'i' : undefined ) . test ( text ) ; } return text . includes ( pattern ) ; } export function matches ( element , selector ) { if ( selector . type === 'id' || selector . type === 'class' || selector . type === 'type' || selector . type === 'attribute' ) { return element . matches ( selector . content ) ; } else if ( selector . type === 'list' ) { return selector . list . some ( ( s ) => matches ( element , s ) ) ; } else if ( selector . type === 'compound' ) { return selector . compound . every ( ( s ) => matches ( element , s ) ) ; } else if ( selector . type === 'pseudo-class' ) { if ( selector . name === 'has' || selector . name === 'if' ) { return ( selector . subtree !== undefined && querySelectorAll ( element , selector . subtree ) . length !== 0 ) ; } else if ( selector . name === 'not' ) { return selector . subtree !== undefined && matches ( element , selector . subtree ) === false ; } else if ( selector . name === 'has-text' ) { const { argument } = selector ; if ( argument === undefined ) { return false ; } const text = element . textContent ; if ( text === null ) { return false ; } return matchPattern ( argument , text ) ; } else if ( selector . name === 'min-text-length' ) { const minLength = Number ( selector . argument ) ; if ( Number . isNaN ( minLength ) || minLength < 0 ) { return false ; } const text = element . textContent ; if ( text === null ) { return false ; } return text . length >= minLength ; } } return false ; } export function querySelectorAll ( element , selector ) { const elements = [ ] ; if ( selector . type === 'id' || selector . type === 'class' || selector . type === 'type' || selector . type === 'attribute' ) { elements . push ( ... element . querySelectorAll ( selector . content ) ) ; } else if ( selector . type === 'list' ) { for ( const subSelector of selector . list ) { elements . push ( ... querySelectorAll ( element , subSelector ) ) ; } } else if ( selector . type === 'compound' ) { if ( selector . compound . length !== 0 ) { elements . push ( ... querySelectorAll ( element , selector . compound [ 0 ] ) . filter ( ( e ) => selector . compound . slice ( 1 ) . every ( ( s ) => matches ( e , s ) ) ) ) ; } } else if ( selector . type === 'complex' ) { const elements2 = selector . left === undefined ? [ element ] : querySelectorAll ( element , selector . left ) ; if ( selector . combinator === ' ' ) { for ( const element2 of elements2 ) { elements . push ( ... querySelectorAll ( element2 , selector . right ) ) ; } } else if ( selector . combinator === '>' ) { for ( const element2 of elements2 ) { for ( const child of element2 . children ) { if ( matches ( child , selector . right ) === true ) { elements . push ( child ) ; } } } } else if ( selector . combinator === '~' ) { for ( const element2 of elements2 ) { let sibling = element2 ; while ( ( sibling = sibling . nextElementSibling ) !== null ) { if ( matches ( sibling , selector . right ) === true ) { elements . push ( sibling ) ; } } } } else if ( selector . combinator === '+' ) { for ( const element2 of elements2 ) { const nextElementSibling = element2 . nextElementSibling ; if ( nextElementSibling !== null && matches ( nextElementSibling , selector . right ) === true ) { elements . push ( nextElementSibling ) ; } } } } else if ( selector . type === 'pseudo-class' ) { for ( const subElement of element . querySelectorAll ( '*' ) ) { if ( matches ( subElement , selector ) === true ) { elements . push ( subElement ) ; } } } return elements ; }