import { MultiMap , ObjectDictionary , unionInto } from "@opticss/util" ; import { CompoundSelector , ParsedSelector , parseSelector , postcss , postcssSelectorParser as selectorParser , } from "opticss" ; import { isAttributeNode , isClassNode , isRootNode , toAttrToken } from "../BlockParser" ; import { Root as BlockAST } from "../BlockParser/ast" ; import { BlockPath , CLASS_NAME_IDENT , DEFAULT_EXPORT , ROOT_CLASS } from "../BlockSyntax" ; import { ResolvedConfiguration } from "../configuration" ; import { CssBlockError , InvalidBlockSyntax , MultipleCssBlockErrors } from "../errors" ; import { FileIdentifier } from "../importing" ; import { SourceFile , SourceRange } from "../SourceLocation" ; import { BlockClass } from "./BlockClass" ; import { Inheritable } from "./Inheritable" ; import { Styles } from "./Styles" ; /**
 * In-memory representation of a Block. If you're thinking of CSS Blocks
 * in relation to the BEM architecture for CSS, this is the... well... "Block".
 * Well, with a slight caveat....
 *
 * The Block is always the root node of a BlockTree. The Block may be the
 * parent to any number of BlockClass nodes. Notably, the Block class only
 * stores meta information about the block. Any CSS properties assigned to the
 * `:scope` selector are stored on a special BlockClass node that is a child of
 * the Block. You can access this node directly through the
 * `rootClass` property.
 *
 * Block nodes store all data related to any `@block` imports, the
 * `block-name`, implemented Blocks, the inherited Block, and any other
 * metadata stored in the Block file.
 */ export class Block extends Inheritable < Block , Block , never , BlockClass > { public blockAST ? : BlockAST ; private _blockReferences : ObjectDictionary < Block > = { } ; private _blockReferencesReverseLookup : Map < Block , string > = new Map ( ) ; private _blockExports : ObjectDictionary < Block > = { } ; private _blockExportReverseLookup : Map < Block , string > = new Map ( ) ; private _identifier : FileIdentifier ; private _implements : Block [ ] = [ ] ; private _blockErrors : CssBlockError [ ] = [ ] ; private hasHadNameReset = false ; /**
     * A unique identifier for this Block. Generally created from a hash
     * of the FileIdentifier and other process information.
     *
     * For caching to work properly, this GUID must be unique to the block and
     * shouldn't change between recompiles. You shouldn't use file contents to
     * create this hash.
     */ public readonly guid : string ; /**
     * array of paths that this block depends on and, if changed, would
     * invalidate the compiled css of this block. This is usually only useful in
     * pre-processed blocks.
     */ private _dependencies : Set < string > ; /**
     * A direct reference to the BlockClass that holds style information for the
     * `:scope` selector of this Block. The rootClass is also available through
     * other traversal methods, as you would access any other BlockClass that
     * belongs to this Block.
     */ public readonly rootClass : BlockClass ; /**
     * The PostCSS AST of the stylesheet this Block was built from. Used
     * primarily for error reporting, if present.
     */ public stylesheet : postcss . Root | undefined ; /**
     * The PostCSS AST of the compiled CSS this block was built from.
     * If this is set, the stylesheet property is only the definition file contents.
     */ public precompiledStylesheet : postcss . Root | undefined ; /**
     * The full contents of the compiled CSS this block was built from, if available.
     * This content is copied verbatim from the file system and has not been transformed
     * or processed by PostCSS in any way.
     */ public precompiledStylesheetUnedited : string | undefined ; public blockReferencePaths : Map < string , Block > ; private _resolveImplementedBlocksResult : Set < Block > | undefined ; param name param identifier param guid param stylesheet constructor ( name , identifier , guid , stylesheet ? , precompiledStylesheet ? ) { super ( name ) ; this . _identifier = identifier ; this . _dependencies = new Set < string > ( ) ; this . rootClass = new BlockClass ( ROOT_CLASS , this ) ; this . stylesheet = stylesheet ; this . precompiledStylesheet = precompiledStylesheet ; this . guid = guid ; this . addClass ( this . rootClass ) ; this . blockReferencePaths = new Map ( ) ; } protected get ChildConstructor ( ) : typeof BlockClass { return BlockClass ; } returns public get name ( ) : string { return this . uid ; } prop public setName ( name ) { if ( this . hasHadNameReset ) { throw new CssBlockError ( "Cannot set block name more than once." ) ; } this . _token = name ; this . hasHadNameReset = true ; } prop public setBase ( base ) { this . _base = base ; } param reference returns public lookup ( path , errLoc ? ) { path = new BlockPath ( path ) ; let block = this . getReferencedBlock ( path . block ) ; if ( ! block ) { if ( errLoc ) { throw new InvalidBlockSyntax ( `No Block named "${ path . block }" found in scope.` , errLoc ) ; } return undefined ; } let klass = block . resolveClass ( path . class ) ; let attrInfo = path . attribute ; let attr ; if ( klass && attrInfo ) { attr = klass . resolveAttributeValue ( attrInfo ) ; if ( ! attr ) return undefined ; } if ( ! attr && ! klass && errLoc ) { throw new InvalidBlockSyntax ( `No Style "${ path . path }" found on Block "${ block . name }".` , errLoc ) ; } return attr || klass || undefined ; } param reference returns public externalLookup ( path , errLoc ? ) { path = new BlockPath ( path ) ; let block = this . getExportedBlock ( path . block ) ; if ( ! block ) { if ( errLoc ) { throw new InvalidBlockSyntax ( `No Block named "${ path . block }" found in scope.` , errLoc ) ; } return undefined ; } let klass = block . resolveClass ( path . class ) ; let attrInfo = path . attribute ; let attr ; if ( klass && attrInfo ) { attr = klass . resolveAttributeValue ( attrInfo ) ; if ( ! attr ) return undefined ; } if ( ! attr && ! klass && errLoc ) { throw new InvalidBlockSyntax ( `No Style "${ path . path }" found on Block "${ block . name }".` , errLoc ) ; } return attr || klass || undefined ; } param error addError ( error ) { this . _blockErrors . push ( error ) ; } isValid ( ) { return this . _blockErrors . length === 0 ; } returns assertValid ( ) { if ( this . _blockErrors . length > 1 ) { throw new MultipleCssBlockErrors ( this . _blockErrors ) ; } else if ( this . _blockErrors . length === 1 ) { throw this . _blockErrors [ 0 ] ; } return this ; } /**
     * Add an absolute, normalized path as a compilation dependency. This is used
     * to invalidate caches and trigger watchers when those files change.
     *
     * It is not necessary or helpful to add css-block files.
     */ addDependency ( filepath ) { this . _dependencies . add ( filepath ) ; } get dependencies ( ) : string [ ] { return new Array ( ... this . _dependencies ) ; } get identifier ( ) : FileIdentifier { return this . _identifier ; } getClass ( name ) { return name ? this . getChild ( name ) : this . getChild ( ROOT_CLASS ) ; } resolveClass ( name ) { return name ? this . resolveChild ( name ) : this . resolveChild ( ROOT_CLASS ) ; } get classes ( ) : BlockClass [ ] { return this . children ( ) ; } addClass ( blockClass ) { this . setChild ( blockClass . name , blockClass ) ; } ensureClass ( name ) { return this . ensureChild ( name ) ; } getImplementedBlocks ( ) { return this . _implements . slice ( ) ; } addImplementation ( b ) { return this . _implements . push ( b ) ; } param b returns checkImplementation ( b ) { let missing = [ ] ; for ( let o of b . all ( ) ) { if ( ! this . find ( o . asSource ( ) ) ) { missing . push ( o ) ; } } return missing ; } /**
     * Validate that all foreign blocks this block implements are fully...implemented.
     */ checkImplementations ( ) { for ( let b of this . getImplementedBlocks ( ) ) { let missing = this . checkImplementation ( b ) ; let paths = missing . map ( o => o . asSource ( ) ) . join ( ", " ) ; if ( missing . length > 0 ) { let s = missing . length > 1 ? "s" : "" ; throw new CssBlockError ( `Missing implementation${ s } for: ${ paths } from ${ b . identifier }` ) ; } } } find ( sourceName ) { let blockRefName ; let blockName = sourceName . split ( /\>|\.|\:|\[/ ) [ 0 ] ; let md = blockName . match ( CLASS_NAME_IDENT ) ; if ( md && md . index === 0 ) { blockRefName = md [ 0 ] ; let blockRef ; this . eachBlockReference ( ( name , block ) => { if ( blockRefName === name ) { blockRef = block ; } } ) ; if ( blockRef ) { if ( md [ 0 ] . length === sourceName . length ) { return blockRef . rootClass ; } let rest = sourceName . slice ( md [ 0 ] . length ) ; if ( rest . startsWith ( ">" ) ) { rest = rest . slice ( 1 ) ; } return blockRef . find ( rest ) ; } else { return undefined ; } } return this . all ( ) . find ( e => e . asSource ( ) === sourceName ) ; } eachBlockReference ( callback ) { for ( let name of Object . keys ( this . _blockReferences ) ) { callback ( name , this . _blockReferences [ name ] ) ; } } param localName param block addBlockReference ( localName , block ) { this . _blockReferences [ localName ] = block ; this . _blockReferencesReverseLookup . set ( block , localName ) ; } param localName returns getReferencedBlock ( localName ) { if ( localName === DEFAULT_EXPORT ) { return this ; } return this . _blockReferences [ localName ] || null ; } param block returns getReferencedBlockLocalName ( block ) { return block && this . _blockReferencesReverseLookup . get ( block ) || null ; } param remoteName param block addBlockExport ( remoteName , block ) { this . _blockExports [ remoteName ] = block ; this . _blockExportReverseLookup . set ( block , remoteName ) ; } param callback eachBlockExport ( callback ) { for ( let name of Object . keys ( this . _blockExports ) ) { callback ( name , this . _blockExports [ name ] ) ; } } param remoteName returns getExportedBlock ( remoteName ) { return this . _blockExports [ remoteName ] || null ; } param block returns getExportedBlockRemoteName ( block ) { return block && this . _blockExportReverseLookup . get ( block ) || null ; } transitiveBlockDependencies ( ) { let deps = new Set < Block > ( ) ; this . eachBlockReference ( ( _name , block ) => { deps . add ( block ) ; let moreDeps = block . transitiveBlockDependencies ( ) ; if ( moreDeps . size > 0 ) { deps = new Set ( [ ... deps , ... moreDeps ] ) ; } } ) ; return deps ; } /**
     * Returns a new array of ancestors in order of inheritance
     * with the first one being the immediate super block.
     *
     * If this block doesn't inherit, the array is empty.
     **/ getAncestors ( ) { let inherited = new Array < Block > ( ) ; let base = this . base ; while ( base ) { inherited . push ( base ) ; base = base . base ; } return inherited ; } param shallow returns all ( shallow ? ) { let result = new Array < Styles > ( ) ; for ( let blockClass of this . classes ) { result . push ( ... blockClass . all ( ) ) ; } if ( ! shallow && this . base ) { result . push ( ... this . base . all ( shallow ) ) ; } return result ; } /**
     * Outputs a dictionary of style source string to most specific style in the
     * inheritance hierarchy.
     */ resolvedStyleInterface ( ) { let blocks = [ ... this . resolveInheritance ( ) , this ] ; let styleInterface = { } ; for ( let b of blocks ) { let styles = b . all ( true ) ; for ( let style of styles ) { styleInterface [ style . asSource ( ) ] = style ; } } return styleInterface ; } param shallow returns presetClassesMap ( shallow ? ) { const result = { } ; const all = this . all ( shallow ) ; all . forEach ( el => { const presetCssClass = el . presetCssClass ; if ( presetCssClass ) { result [ presetCssClass ] = el ; } } ) ; return result ; } merged ( ) { let map = new MultiMap < string , Styles > ( false ) ; for ( let obj of this . all ( ) ) { map . set ( obj . asSource ( ) , obj ) ; } return map ; } returns getAllStyleAliases ( ) { let result = new Set < string > ( ) ; for ( let blockClass of this . classes ) { blockClass . getStyleAliases ( ) . forEach ( alias => result . add ( alias ) ) ; blockClass . allAttributeValues ( ) . forEach ( value => value . getStyleAliases ( ) . forEach ( alias => result . add ( alias ) ) ) ; } return result ; } nodeAsStyle ( node ) { let next = node . next ( ) ; if ( isRootNode ( node ) && next && isAttributeNode ( next ) && typeof next . namespace === "string" ) { let otherBlock = this . getReferencedBlock ( next . namespace ) ; if ( otherBlock ) { if ( next && isClassNode ( next ) ) { let klass = otherBlock . getClass ( next . value ) ; if ( klass ) { let another = next . next ( ) ; if ( another && isAttributeNode ( another ) ) { let attr = klass . getAttributeValue ( toAttrToken ( another ) ) ; if ( attr ) { return [ attr , 2 ] ; } else { return null ; } } else { return null ; } } else { return null ; } } else if ( next && isAttributeNode ( next ) ) { let attr = otherBlock . rootClass . getAttributeValue ( toAttrToken ( next ) ) ; if ( attr ) { return [ attr , 1 ] ; } else { return null ; } } else { return null ; } } else { return null ; } } else if ( selectorParser . isClassName ( node ) || isRootNode ( node ) ) { let klass = this . getClass ( node . value ) ; if ( klass === null ) { return null ; } let next = node . next ( ) ; if ( next && isAttributeNode ( next ) ) { let attr = klass . getAttributeValue ( toAttrToken ( next ) ) ; if ( attr === null ) { return null ; } else { return [ attr , 1 ] ; } } else { return [ klass , 0 ] ; } } else if ( isAttributeNode ( node ) ) { let prevNode = node . prev ( ) ; while ( prevNode && ! ( selectorParser . isClassName ( prevNode ) || isRootNode ( prevNode ) ) ) { prevNode = prevNode . prev ( ) ; } if ( ! prevNode ) { throw new Error ( "internal error - illegal selector encountered after validation." ) ; } let klass = this . getClass ( prevNode . value ) ; let attr = klass ?. getAttributeValue ( toAttrToken ( node ) ) ; if ( attr ) { return [ attr , 0 ] ; } else { return null ; } } return null ; } rewriteSelectorNodes ( nodes , config , reservedClassNames ) { let newNodes = [ ] ; for ( let i = 0 ; i < nodes . length ; i ++ ) { let node = nodes [ i ] ; let result = this . nodeAsStyle ( node ) ; if ( result === null ) { newNodes . push ( node ) ; } else { newNodes . push ( selectorParser . className ( { value : result [ 0 ] . cssClass ( config , reservedClassNames ) } ) ) ; i += result [ 1 ] ; } } return newNodes ; } rewriteSelectorToString ( selector , config , reservedClassNames ) { let firstNewSelector = new CompoundSelector ( ) ; let newSelector = firstNewSelector ; let newCurrentSelector = newSelector ; let currentSelector = selector . selector ; do { newCurrentSelector . nodes = this . rewriteSelectorNodes ( currentSelector . nodes , config , reservedClassNames ) ; newCurrentSelector . pseudoelement = currentSelector . pseudoelement ; if ( currentSelector . next !== undefined ) { let tempSel = newCurrentSelector ; newCurrentSelector = new CompoundSelector ( ) ; tempSel . setNext ( currentSelector . next . combinator , newCurrentSelector ) ; currentSelector = currentSelector . next . selector ; } else { currentSelector = undefined ; } } while ( currentSelector !== undefined ) ; return firstNewSelector . toString ( ) ; } rewriteSelector ( selector , config , reservedClassNames ) { let s = this . rewriteSelectorToString ( selector , config , reservedClassNames ) ; return parseSelector ( s ) [ 0 ] ; } debug ( config ) { let result = [ `Source: ${ this . identifier }` ] ; const classes = [ ... this . rootClass . cssClasses ( config , new Set ( ) ) ] . join ( "." ) ; const aliases = this . rootClass . getStyleAliases ( ) ; result . push ( `${ ROOT_CLASS } (.${ classes }${ aliases . size ? `, aliases: .${ [ ... aliases ] . join ( " ." ) }` : "" })` , ... this . rootClass . debug ( config ) ) ; let sourceNames = new Set < string > ( this . resolveChildren ( ) . map ( s => s . asSource ( ) ) ) ; let sortedNames = [ ... sourceNames ] . sort ( ) . filter ( ( n ) => n !== ROOT_CLASS ) ; for ( let n of sortedNames ) { const isLast = sortedNames . indexOf ( n ) === sortedNames . length - 1 ; let o = this . find ( n ) as BlockClass ; result . push ( ` ${ isLast ? "└──" : "├──" } ${ o . asDebug ( config ) }` ) ; const childrenDebugs = o . debug ( config ) . map ( ( s ) => ` ${ isLast ? " " : "|" }   ${ s }` ) ; result . push ( ... childrenDebugs ) ; } return result ; } param other return equal ( other ) { return other && this . identifier === other . identifier ; } isAncestorOf ( other ) { let base = other && other . base ; while ( base ) { if ( this . equal ( base ) ) { return true ; } else { base = base . base ; } } return false ; } /**
     * Gets all the blocks that are implemented by this block or by any ancestor.
     * This includes:
     *  - This block.
     *  - The blocks this block inherits from.
     *  - The blocks the above blocks explicitly declare that they implement.
     *  - The blocks all the above blocks implement transitively.
     * Such that `blockA.resolveImplementedBlocks().has(blockB)` is true iff
     * `blockA` implements the interface of `blockB`.
     */ resolveImplementedBlocks ( ) { if ( this . _resolveImplementedBlocksResult ) { return this . _resolveImplementedBlocksResult ; } let implemented = new Set < Block > ( [ this ] ) ; if ( this . _base ) { unionInto ( implemented , this . _base . resolveImplementedBlocks ( ) ) ; } for ( let impl of this . getImplementedBlocks ( ) ) { unionInto ( implemented , impl . resolveImplementedBlocks ( ) ) ; } this . _resolveImplementedBlocksResult = implemented ; return implemented ; } isImplementationOf ( other ) { return this . resolveImplementedBlocks ( ) . has ( other ) ; } return toJSON ( ) { return this . _token ; } } export function isBlock ( o ? ) { return o instanceof Block ; }