import * as parse5 from "parse5" ; import * as postcss from "postcss" ; import { inspect } from "util" ; export type RuleScope = Array < postcss . AtRule > ; export type RuleIteratorWithScope = ( rule : postcss . Rule , scope : RuleScope ) => false | undefined | void ; export type BodyElement = parse5 . Element & parse5 . ParentNode & { nodeName : "body" ; tagName : "body" ; } ; export function isRule ( node ) { return node . type === "rule" ; } export function isAtRule ( node ) { return node . type === "atrule" ; } export function isContainer ( node ) { return ( < postcss . Container > node ) . each !== undefined ; } export function walkRules ( container , eachRule ) { _walkRulesWithScope ( container , eachRule , [ ] ) ; } function _walkRulesWithScope ( container , eachRule , scope ) { container . each ( node => { if ( isRule ( node ) ) { eachRule ( node , scope ) ; } else if ( isAtRule ( node ) ) { if ( node . name . includes ( "keyframes" ) ) { } else { _walkRulesWithScope ( node , eachRule , scope . concat ( node ) ) ; } } else if ( isContainer ( node ) ) { console . log ( "warning: container that's not an AtRule encountered: " + inspect ( node ) ) ; _walkRulesWithScope ( node , eachRule , scope ) ; } } ) ; } export function allElements ( parent ) { let els = new Array < parse5 . DefaultTreeElement > ( ) ; walkElements ( parent , ( el ) => { els . push ( el ) ; } ) ; return els ; } export type StartTagCallback = ( node : parse5 . DefaultTreeElement ) => EndTagCallback ; export type EndTagCallback = ( ( node : parse5 . DefaultTreeElement ) => void ) | undefined | void ; /**
 * invokes the callback with each element in the document via a depth first traversal.
 */ export function walkElements ( node , cb ) { let endTagCB = undefined ; if ( isElement ( node ) ) { endTagCB = cb ( node ) ; } if ( isParentNode ( node ) ) { node . childNodes . forEach ( ( node ) => { walkElements ( node , cb ) ; } ) ; } if ( endTagCB && isElement ( node ) ) { endTagCB ( node ) ; } } export function isElement ( node ) { if ( ( < parse5 . DefaultTreeElement > node ) . tagName ) { return true ; } else { return false ; } } export function isParentNode ( node ) { if ( ( < parse5 . DefaultTreeParentNode > node ) . childNodes ) { return true ; } else { return false ; } } export function parseStylesheet ( content ) { return new Promise < postcss . Result > ( ( resolve , reject ) => { postcss ( POSTCSS_NOOP_PLUGIN ) . process ( content , { from : "stylesheet.css" } ) . then ( resolve , reject ) ; } ) ; } export function parseHtml ( html ) { return parse5 . parse ( html ) as parse5 . DefaultTreeDocument ; } function isDocument ( doc ) { return ! ! ( doc as parse5 . DefaultTreeDocument ) . childNodes ; } export function bodyElement ( document ) { if ( ! isDocument ( document ) ) { return ; } let html = document . childNodes . find ( child => isElement ( child ) && child . tagName === "html" ) ; if ( html && isParentNode ( html ) ) { return html . childNodes . find ( child => isElement ( child ) && child . tagName === "body" ) as BodyElement | undefined ; } else { return ; } } /**
 * serializes an element and it's children to a string.
 */ export function serializeElement ( element ) { return parse5 . serialize ( { children : [ element ] } ) ; } /**
 * outputs the element's opening tag only.
 */ export function debugElement ( element ) { if ( ! element || ! element . attrs ) { return inspect ( element ) ; } let tagName = element . tagName ; let attrs = Object . keys ( element . attrs ) . reduce ( ( s , a , i ) => { if ( i > 0 ) { s += " " ; } s += `${ a }="${ element . attrs [ a ] }"` ; return s ; } , "" ) ; return `<${ tagName } ${ attrs }>` ; } export function documentToString ( document ) { return parse5 . serialize ( bodyElement ( document ) ! ) ; } export const POSTCSS_NOOP_PLUGIN = postcss . plugin ( "resolve-cascade" , function ( _opts ) { return function ( _root , _result ) { return Promise . resolve ( ) ; } ; } ) ;