import { MultiMap , TwoKeyMultiMap , objectValues } from "@opticss/util" ; import * as propParser from "css-property-parser" ; import { postcss } from "opticss" ; import { AttrValue , BlockClass , Ruleset , Style , isBlockClass } from "../../BlockTree" ; import { charInFile } from "../../errors" ; import { ElementAnalysis , hasAttrValue , isAttrGroup , isFalseCondition , isTrueCondition , } from "../ElementAnalysis" ; import { Validator } from "./Validator" ; type Pseudo = string ; type Property = string ; type ConflictMap = MultiMap < Property , Ruleset > ; type PropMap = TwoKeyMultiMap < Pseudo , Property , Ruleset > ; param propToBlocks param obj function add ( propToBlocks , obj ) { for ( let pseudo of obj . rulesets . getPseudos ( ) ) { for ( let prop of obj . rulesets . getProperties ( ) ) { propToBlocks . set ( pseudo , prop , ... obj . rulesets . getRulesets ( prop , pseudo ) ) ; } } } param obj param propToBlocks param conflicts function evaluate ( obj , propToRules , conflicts ) { for ( let pseudo of obj . rulesets . getPseudos ( ) ) { for ( let prop of obj . rulesets . getProperties ( pseudo ) ) { for ( let other of propToRules . get ( pseudo , prop ) ) { for ( let self of obj . rulesets . getRulesets ( prop , pseudo ) ) { if ( other . style . block === self . style . block ) { continue ; } if ( other . style . block . isAncestorOf ( self . style . block ) || self . style . block . isAncestorOf ( other . style . block ) ) { continue ; } if ( isBlockClass ( other . style ) && ! other . style . composes ( self . style , false ) && other . style . composes ( self . style ) || isBlockClass ( self . style ) && ! self . style . composes ( other . style , false ) && self . style . composes ( other . style ) ) { continue ; } let selfDecl = self . declarations . get ( prop ) ; let otherDecl = other . declarations . get ( prop ) ; if ( ! selfDecl || ! otherDecl ) { continue ; } let valuesEqual = selfDecl . length === otherDecl . length ; if ( valuesEqual ) { for ( let i = 0 ; i < Math . min ( selfDecl . length , otherDecl . length ) ; i ++ ) { valuesEqual = valuesEqual && selfDecl [ i ] . value === otherDecl [ i ] . value ; } } if ( valuesEqual || other . hasResolutionFor ( prop , self . style ) || self . hasResolutionFor ( prop , other . style ) ) { continue ; } conflicts . set ( prop , other ) ; conflicts . set ( prop , self ) ; } } } } } param prop param conflicts function recursivelyPruneConflicts ( prop , conflicts ) { if ( propParser . isShorthandProperty ( prop ) ) { let longhands = propParser . expandShorthandProperty ( prop , "inherit" , false , true ) ; for ( let longProp of Object . keys ( longhands ) ) { let rules = recursivelyPruneConflicts ( longProp , conflicts ) ; for ( let rule of rules ) { if ( conflicts . hasValue ( prop , rule ) ) { conflicts . deleteValue ( longProp , rule ) ; } } } } return conflicts . get ( prop ) ; } param prop param rule function formatRulesetConflicts ( prop , rules ) { const out = new Set ( ) ; for ( let rule of rules ) { let decl = rule . declarations . get ( prop ) ; let nodes = decl ? decl . map ( ( d ) => d . node ) : [ rule . node ] ; for ( let node of nodes ) { out . add ( `    ${ rule . style . asSource ( true ) } (${ charInFile ( rule . file , node . source && node . source . start ) })` ) ; } } return [ ... out ] . join ( "\n" ) ; } function inStylesheetComposition ( blockClass , analysis , conflicts , allConditions ) { composed : for ( let composed of blockClass . composedStyles ( ) ) { for ( let condition of composed . conditions ) { if ( ! analysis . hasAttribute ( condition ) ) { break composed ; } } evaluate ( composed . style , allConditions , conflicts ) ; add ( allConditions , composed . style ) ; } } param correlations param err export const propertyConflictValidator = ( elAnalysis , _templateAnalysis , err ) => { let conflicts = new MultiMap ( false ) ; let allConditions = new TwoKeyMultiMap ( false ) ; elAnalysis . static . forEach ( ( obj ) => { evaluate ( obj , allConditions , conflicts ) ; add ( allConditions , obj ) ; if ( isBlockClass ( obj ) ) { inStylesheetComposition ( obj , elAnalysis , conflicts , allConditions ) ; } } ) ; elAnalysis . dynamicClasses . forEach ( ( condition ) => { let truthyConditions = new TwoKeyMultiMap ( false ) ; let falsyConditions = new TwoKeyMultiMap ( false ) ; if ( isTrueCondition ( condition ) ) { condition . whenTrue . forEach ( ( obj ) => { evaluate ( obj , allConditions , conflicts ) ; evaluate ( obj , truthyConditions , conflicts ) ; add ( truthyConditions , obj ) ; } ) ; } if ( isFalseCondition ( condition ) ) { condition . whenFalse . forEach ( ( obj ) => { evaluate ( obj , allConditions , conflicts ) ; evaluate ( obj , falsyConditions , conflicts ) ; add ( falsyConditions , obj ) ; } ) ; } allConditions . setAll ( truthyConditions ) ; allConditions . setAll ( falsyConditions ) ; } ) ; elAnalysis . dynamicAttributes . forEach ( ( condition ) => { if ( isAttrGroup ( condition ) ) { let attrConditions = new TwoKeyMultiMap ( false ) ; for ( let attr of objectValues ( condition . group ) ) { evaluate ( attr , allConditions , conflicts ) ; add ( attrConditions , attr ) ; } allConditions . setAll ( attrConditions ) ; } else if ( hasAttrValue ( condition ) ) { for ( let val of condition . value ) { evaluate ( val as AttrValue , allConditions , conflicts ) ; add ( allConditions , val as AttrValue ) ; } } } ) ; for ( let prop of conflicts . keys ( ) ) { if ( propParser . isShorthandProperty ( prop ) ) { recursivelyPruneConflicts ( prop , conflicts ) ; } } if ( conflicts . size ) { let msg = "The following property conflicts must be resolved for these composed Styles:" ; let details = "\n" ; for ( let [ prop , matches ] of conflicts . entries ( ) ) { if ( ! prop || ! matches . length ) { return ; } details += `  ${ prop }:\n${ formatRulesetConflicts ( prop , matches ) }\n\n` ; } err ( msg , null , details ) ; } } ;