import * as postcss from "postcss" ; import * as postcssSelectorParser from "postcss-selector-parser" ; import { isRule } from "./util/cssIntrospection" ; const { isContainer , isPseudo , isPseudoElement , isRoot , isSelector , } = postcssSelectorParser ; export interface CombinatorAndSelector < SelectorType > { combinator : postcssSelectorParser . Combinator ; selector : SelectorType ; } export type CombinatorAndCompoundSelector = CombinatorAndSelector < CompoundSelector > ; export class CombinedSelector < T > { next ? : CombinatorAndSelector < T > ; setNext ( combinator , selector ) { this . next = { combinator : combinator , selector : selector , } ; } } /**
 * Core structure in `CompoundSelector` linked lists. Keeps reference to the next
 * `CompoundSelector` in the chan, and the combinator that connects them.
 */ export class CompoundSelector extends CombinedSelector < CompoundSelector > { nodes : postcssSelectorParser . Node [ ] ; pseudoelement ? : postcssSelectorParser . Pseudo ; next ? : CombinatorAndCompoundSelector ; constructor ( ) { super ( ) ; this . nodes = [ ] ; } return get lastSibling ( ) { let lastSibling = this ; while ( lastSibling . next ) { lastSibling = lastSibling . next . selector ; } return lastSibling ; } param Simple addNode ( node ) { this . nodes . push ( node ) ; } param The setPseudoelement ( pseudo ) { this . pseudoelement = pseudo ; } param other param combinator param reference insertBefore ( other , combinator , reference ) { if ( this . next && this . next . selector === reference ) { let otherEnd = other . lastSibling ; otherEnd . next = { combinator : combinator , selector : this . next . selector , } ; this . next . selector = other ; return true ; } else if ( this . next ) { return this . next . selector . insertBefore ( other , combinator , reference ) ; } else { return false ; } } param combinator param selector return append ( combinator , selector ) { this . lastSibling . next = { combinator : combinator , selector : selector , } ; return this ; } param other return mergeNodes ( other ) { let foundNodes = new Set < string > ( ) ; let pseudos = [ ] ; let nodes = [ ] ; let filterNodes = function ( node ) { let nodeStr = node . toString ( ) ; if ( ! foundNodes . has ( nodeStr ) ) { foundNodes . add ( nodeStr ) ; if ( isPseudo ( node ) ) { pseudos . push ( node ) ; } else { nodes . push ( node ) ; } } } ; this . nodes . forEach ( filterNodes ) ; other . nodes . forEach ( filterNodes ) ; pseudos . sort ( ( a , b ) => a . value . localeCompare ( b . value ) ) ; this . nodes = nodes . concat ( pseudos ) ; if ( this . pseudoelement && other . pseudoelement && this . pseudoelement . value !== other . pseudoelement . value ) { throw new Error ( "Cannot merge two compound selectors with different pseudoelements" ) ; } this . pseudoelement = other . pseudoelement ; return this ; } return removeLast ( ) { let selector = this ; while ( selector . next ) { if ( selector . next . selector . next === undefined ) { let last = selector . next ; selector . next = undefined ; return last ; } else if ( selector . next ) { selector = selector . next . selector ; } } return undefined ; } return clone ( ) { let firstCopy = new CompoundSelector ( ) ; let copy = firstCopy ; let current = this ; do { copy . pseudoelement = current . pseudoelement ; copy . nodes = current . nodes . slice ( ) ; if ( current . next ) { copy . next = { combinator : current . next . combinator , selector : current . next . selector . clone ( ) , } ; } copy = copy . next && copy . next . selector ; current = current . next && current . next . selector ; } while ( copy && current ) ; return firstCopy ; } return toString ( shallow = false ) { let s = this . nodes . map ( n => n . clone ( { spaces : { before : "" , after : "" } } ) ) . join ( "" ) ; if ( this . pseudoelement ) { s += this . pseudoelement . toString ( ) ; } if ( this . next && ! shallow ) { s += this . next . combinator . toString ( ) ; s += this . next . selector . toString ( ) ; } return s ; } } /**
 * `ParsedSelector` serves as a container object for a `CompoundSelector` linked
 * list and provides a number of convenience methods for interacting with it.
 */ export class ParsedSelector { /**
     * The raw string the selector was parsed from, if any.
     */ source : string | undefined ; private _key : CompoundSelector | undefined ; selector : CompoundSelector ; param Root constructor ( selector , source ? ) { this . source = source ; this . selector = selector ; } eachCompoundSelector < EarlyReturnType > ( callback ) { let selector = this . selector ; let earlyReturn = callback ( selector ) ; while ( earlyReturn === undefined && selector . next ) { selector = selector . next . selector ; earlyReturn = callback ( selector ) ; } return earlyReturn ; } eachSelectorNode < EarlyReturnType > ( callback ) { return this . eachCompoundSelector ( ( sel ) => { for ( let node of sel . nodes ) { let earlyReturn = callback ( node ) ; if ( earlyReturn !== undefined ) { return earlyReturn ; } if ( isContainer ( node ) ) { let earlyReturn ; try { node . walk ( ( n ) => { earlyReturn = callback ( n ) ; if ( earlyReturn !== undefined ) { throw earlyReturn ; } } ) ; } catch ( e ) { if ( e === earlyReturn ) { return earlyReturn ; } else { throw e ; } } } } return ; } ) ; } return isContext ( selector ) { let k = this . selector ; while ( k . next !== undefined ) { if ( k === selector ) return true ; k = k . next . selector ; } return false ; } return get key ( ) : CompoundSelector { if ( this . _key === undefined ) { this . _key = this . selector . lastSibling ; } return this . _key ; } return get length ( ) : number { let count = 0 ; let selector = this . selector ; while ( selector ) { count ++ ; selector = selector . next && selector . next . selector ; } return count ; } return clone ( ) { return new ParsedSelector ( this . selector . clone ( ) ) ; } return toString ( ) { return this . selector . toString ( ) ; } toContext ( ... keyNodes ) { let context = this . clone ( ) ; let key = context . key ; key . nodes = key . nodes . filter ( node => isPseudo ( node ) ) . sort ( ( a , b ) => a . value ! . localeCompare ( b . value ! ) ) ; if ( keyNodes . length > 0 ) { key . nodes . unshift ( ... keyNodes ) ; } else { key . nodes . unshift ( postcssSelectorParser . universal ( ) ) ; } return context ; } /**
     * Same as toString() but having omitted the key selector.
     * Includes the final combinator prior to the key selector.
     */ toContextString ( ) { let context = "" ; let selector = this . selector ; if ( selector . next === undefined ) return context ; while ( true ) { context += selector . toString ( true ) ; if ( selector . next ) { let combinator = selector . next . combinator . value ; context += combinator === " " ? combinator : ` ${ combinator } ` ; selector = selector . next . selector ; if ( selector . next === undefined ) { break ; } } else { break ; } } return context ; } } /**
 * All valid selector-like inputs to the `parseSelector` helper methods.
 */ export type Selectorish = string | postcssSelectorParser . Root | postcssSelectorParser . Node [ ] | postcssSelectorParser . Node [ ] [ ] | postcss . Rule ; param root return function coerceRootToNodeList ( root ) { return root . nodes . map ( n => ( < postcssSelectorParser . Container > n ) . nodes ) ; } param selector return function toNodes ( selector ) { if ( Array . isArray ( selector ) ) { if ( Array . isArray ( selector [ 0 ] ) ) { return < postcssSelectorParser . Node [ ] [ ] > selector ; } else { return [ < postcssSelectorParser . Node [ ] > selector ] ; } } if ( isRoot ( selector ) ) { return coerceRootToNodeList ( selector ) ; } if ( isSelector ( selector ) ) { return [ selector . nodes ] ; } let res = postcssSelectorParser ( ) . astSync ( selector , { updateSelector : false } ) ; return coerceRootToNodeList ( res ) ; } param selector return export function parseCompoundSelectors ( selector ) { let compoundSelectors = [ ] ; toNodes ( selector ) . forEach ( ( nodes ) => { let firstCompoundSel = new CompoundSelector ( ) ; let compoundSel = firstCompoundSel ; nodes . forEach ( ( n ) => { if ( n . type === postcssSelectorParser . COMBINATOR ) { let lastCompoundSel = compoundSel ; compoundSel = new CompoundSelector ( ) ; lastCompoundSel . setNext ( n , compoundSel ) ; } else if ( isPseudoElement ( n ) ) { if ( compoundSel . nodes . length === 0 ) { let universal = postcssSelectorParser . universal ( ) ; n . parent ! . insertBefore ( n , universal ) ; compoundSel . addNode ( universal ) ; } compoundSel . pseudoelement = n ; if ( ! compoundSel . pseudoelement . value . startsWith ( "::" ) ) { compoundSel . pseudoelement . value = ":" + compoundSel . pseudoelement . value ; } } else { compoundSel . addNode ( n ) ; } } ) ; if ( firstCompoundSel . nodes . length > 0 ) { compoundSelectors . push ( firstCompoundSel ) ; } } ) ; return compoundSelectors ; } param selector return export function parseSelector ( selector ) { let source = undefined ; if ( typeof selector === "string" ) { let parsedSelectors = new Array < ParsedSelector > ( ) ; for ( let source of selector . split ( "," ) ) { let compoundSelector = parseCompoundSelectors ( source ) [ 0 ] ; parsedSelectors . push ( new ParsedSelector ( compoundSelector , source ) ) ; } return parsedSelectors ; } else if ( isRule ( selector ) ) { let parsedSelectors = new Array < ParsedSelector > ( ) ; for ( let source of selector . selectors ) { let compoundSelector = parseCompoundSelectors ( source ) [ 0 ] ; parsedSelectors . push ( new ParsedSelector ( compoundSelector , source ) ) ; } return parsedSelectors ; } else if ( isRoot ( selector ) ) { source = selector . toString ( ) ; } let compoundSelectors = parseCompoundSelectors ( selector ) ; return compoundSelectors . map ( cs => new ParsedSelector ( cs , source ) ) ; }