@@ -11,17 +11,16 @@ import {
11
11
TemplateInfoFactory ,
12
12
TemplateTypes ,
13
13
} from "@opticss/template-api" ;
14
- import { ObjectDictionary , objectValues } from "@opticss/util" ;
14
+ import { ObjectDictionary , assertNever , objectValues } from "@opticss/util" ;
15
15
import { IdentGenerator } from "opticss" ;
16
16
17
17
import { BlockFactory } from "../BlockParser" ;
18
- import { DEFAULT_EXPORT } from "../BlockSyntax" ;
19
- import { Block , Style } from "../BlockTree" ;
18
+ import { AttrValue , Attribute , Block , BlockClass , Style } from "../BlockTree" ;
20
19
import { ResolvedConfiguration } from "../configuration" ;
21
20
import { allDone } from "../util" ;
22
21
23
22
import { Analyzer } from "./Analyzer" ;
24
- import { ElementAnalysis , SerializedElementAnalysis } from "./ElementAnalysis" ;
23
+ import { DynamicClasses , ElementAnalysis , FalseCondition , SerializedElementAnalysis , SerializedElementSourceAnalysis , TrueCondition , hasAttrValue , hasDependency , isAttrGroup , isConditional , isFalseCondition , isStaticClass , isSwitch , isTrueCondition } from "./ElementAnalysis" ;
25
24
import { TemplateValidator , TemplateValidatorOptions } from "./validations" ;
26
25
27
26
/**
@@ -36,6 +35,18 @@ export interface SerializedAnalysis<K extends keyof TemplateTypes> {
36
35
elements : ObjectDictionary < SerializedElementAnalysis > ;
37
36
}
38
37
38
+ /**
39
+ * This interface defines a JSON friendly serialization
40
+ * of an {Analysis}.
41
+ */
42
+ export interface SerializedSourceAnalysis < K extends keyof TemplateTypes > {
43
+ template : SerializedTemplateInfo < K > ;
44
+ blocks : ObjectDictionary < string > ;
45
+ stylesFound : string [ ] ;
46
+ // The numbers stored in each element are an index into a stylesFound;
47
+ elements : ObjectDictionary < SerializedElementSourceAnalysis > ;
48
+ }
49
+
39
50
// tslint:disable-next-line:prefer-unknown-to-any
40
51
type ElementAnalyzedCallback < BooleanExpression , StringExpression , TernaryExpression > = ( element : ElementAnalysis < BooleanExpression , StringExpression , TernaryExpression > ) => void ;
41
52
@@ -161,19 +172,35 @@ export class Analysis<K extends keyof TemplateTypes> {
161
172
* @return The local name of the given block.
162
173
*/
163
174
getBlockName ( block : Block ) : string | null {
164
- let names = Object . keys ( this . blocks ) ;
165
- for ( let name of names ) {
166
- if ( this . blocks [ name ] === block ) {
167
- return name === DEFAULT_EXPORT ? "" : name ;
175
+ for ( let name of Object . keys ( this . blocks ) ) {
176
+ let searchBlock = this . blocks [ name ] ;
177
+ let blockName = this . _searchForBlock ( block , searchBlock , name ) ;
178
+ if ( blockName !== null ) {
179
+ return blockName ;
168
180
}
169
181
}
170
- for ( let name of names ) {
171
- let superBlock = this . blocks [ name ] . base ;
172
- while ( superBlock ) {
173
- if ( superBlock === block ) return name === DEFAULT_EXPORT ? "" : name ;
174
- superBlock = superBlock . base ;
182
+ return null ;
183
+ }
184
+
185
+ _searchForBlock ( blockToFind : Block , block : Block , parentPath : string ) : string | null {
186
+ if ( block === blockToFind || block . isAncestorOf ( blockToFind ) ) {
187
+ return parentPath ;
188
+ }
189
+
190
+ // we collect these name/block pairs first, so we can early exit the next loop.
191
+ let blockRefs = new Array < [ string , Block ] > ( ) ;
192
+ block . eachBlockReference ( ( name , refBlock ) => {
193
+ blockRefs . push ( [ name , refBlock ] ) ;
194
+ } ) ;
195
+
196
+ for ( let [ name , refBlock ] of blockRefs ) {
197
+ let currentSearchPath = `${ parentPath } >${ name } ` ;
198
+ let rv = this . _searchForBlock ( blockToFind , refBlock , currentSearchPath ) ;
199
+ if ( rv !== null ) {
200
+ return rv ;
175
201
}
176
202
}
203
+
177
204
return null ;
178
205
}
179
206
@@ -236,7 +263,11 @@ export class Analysis<K extends keyof TemplateTypes> {
236
263
* @return The local name for the block object using the local prefix for the block.
237
264
*/
238
265
serializedName ( o : Style ) : string {
239
- return `${ this . getBlockName ( o . block ) || "" } ${ o . asSource ( ) } ` ;
266
+ let blockName = this . getBlockName ( o . block ) ;
267
+ if ( blockName === null ) {
268
+ throw new Error ( `Block ${ o . block . identifier } is not registered in the dependency graph for this analysis.` ) ;
269
+ }
270
+ return `${ blockName } ${ o . asSource ( ) } ` ;
240
271
}
241
272
242
273
/**
@@ -297,13 +328,38 @@ export class Analysis<K extends keyof TemplateTypes> {
297
328
}
298
329
}
299
330
331
+ serializeSource ( blockPaths ?: Map < Block , string > ) : SerializedSourceAnalysis < K > {
332
+ let elements : ObjectDictionary < SerializedElementSourceAnalysis > = { } ;
333
+ let { template, blocks, stylesFound, styleIndexes } = this . _serializeSetup ( blockPaths ) ;
334
+
335
+ // Serialize all discovered Elements.
336
+ this . elements . forEach ( ( el , key ) => {
337
+ elements [ key ] = el . serializeSourceAnalysis ( styleIndexes ) ;
338
+ } ) ;
339
+
340
+ // Return serialized Analysis object.
341
+ return { template, blocks, stylesFound, elements } ;
342
+ }
343
+
300
344
/**
301
345
* Generates a [[SerializedTemplateAnalysis]] for this analysis.
302
346
*/
303
347
serialize ( blockPaths ?: Map < Block , string > ) : SerializedAnalysis < K > {
348
+ let elements : ObjectDictionary < SerializedElementAnalysis > = { } ;
349
+ let { template, blocks, stylesFound, styleIndexes } = this . _serializeSetup ( blockPaths ) ;
350
+
351
+ // Serialize all discovered Elements.
352
+ this . elements . forEach ( ( el , key ) => {
353
+ elements [ key ] = el . serialize ( styleIndexes ) ;
354
+ } ) ;
355
+
356
+ // Return serialized Analysis object.
357
+ return { template, blocks, stylesFound, elements } ;
358
+ }
359
+
360
+ _serializeSetup ( blockPaths ?: Map < Block , string > ) {
304
361
let blocks = { } ;
305
362
let stylesFound : string [ ] = [ ] ;
306
- let elements : ObjectDictionary < SerializedElementAnalysis > = { } ;
307
363
let template = this . template . serialize ( ) as SerializedTemplateInfo < K > ;
308
364
let styleNameMap = new Map < Style , string > ( ) ;
309
365
let styleIndexes = new Map < Style , number > ( ) ;
@@ -329,14 +385,122 @@ export class Analysis<K extends keyof TemplateTypes> {
329
385
let block = this . blocks [ localName ] ;
330
386
blocks [ localName ] = blockPaths && blockPaths . get ( block ) || block . identifier ;
331
387
} ) ;
388
+ return { template, blocks, stylesFound, styleIndexes } ;
389
+ }
332
390
333
- // Serialize all discovered Elements.
334
- this . elements . forEach ( ( el , key ) => {
335
- elements [ key ] = el . serialize ( styleIndexes ) ;
391
+ /**
392
+ * Creates a TemplateAnalysis from its serialized form.
393
+ * @param serializedAnalysis The analysis to be recreated.
394
+ * @param options The plugin options that are used to parse the blocks.
395
+ * @param postcssImpl The instance of postcss that should be used to parse the block's css.
396
+ */
397
+ static async deserializeSource (
398
+ serializedAnalysis : SerializedSourceAnalysis < keyof TemplateTypes > ,
399
+ blockFactory : BlockFactory ,
400
+ parent : Analyzer < keyof TemplateTypes > ,
401
+ ) : Promise < Analysis < keyof TemplateTypes > > {
402
+ let blockNames = Object . keys ( serializedAnalysis . blocks ) ;
403
+ let info = TemplateInfoFactory . deserialize < keyof TemplateTypes > ( serializedAnalysis . template ) ;
404
+ let analysis = parent . newAnalysis ( info ) ;
405
+ let blockPromises = new Array < Promise < { name : string ; block : Block } > > ( ) ;
406
+ blockNames . forEach ( n => {
407
+ let blockIdentifier = serializedAnalysis . blocks [ n ] ;
408
+ let promise = blockFactory . getBlock ( blockIdentifier ) . then ( block => {
409
+ return { name : n , block : block } ;
410
+ } ) ;
411
+ blockPromises . push ( promise ) ;
336
412
} ) ;
413
+ let values = await allDone ( blockPromises ) ;
337
414
338
- // Return serialized Analysis object.
339
- return { template, blocks, stylesFound, elements } ;
415
+ // Create a temporary block so we can take advantage of `Block.lookup`
416
+ // to easily resolve all BlockPaths referenced in the serialized analysis.
417
+ // TODO: We may want to abstract this so we're not making a temporary block.
418
+ let localScope = new Block ( "analysis-block" , "tmp" , "analysis-block" ) ;
419
+ values . forEach ( o => {
420
+ analysis . blocks [ o . name ] = o . block ;
421
+ localScope . addBlockReference ( o . name , o . block ) ;
422
+ } ) ;
423
+ let objects = new Array < Style > ( ) ;
424
+ serializedAnalysis . stylesFound . forEach ( s => {
425
+ let style = localScope . find ( s ) ;
426
+ if ( style ) {
427
+ objects . push ( style ) ;
428
+ } else {
429
+ throw new Error ( `Cannot resolve ${ s } to a block style.` ) ;
430
+ }
431
+ } ) ;
432
+
433
+ let styleRef = ( index : number ) => {
434
+ let s = objects [ index ] ;
435
+ if ( ! s ) {
436
+ throw new Error ( "[internal error] Style index out of bounds!" ) ;
437
+ }
438
+ return s ;
439
+ } ;
440
+ let classRef = ( index : number ) => {
441
+ let s = styleRef ( index ) ;
442
+ if ( ! ( s instanceof BlockClass ) ) {
443
+ throw new Error ( "[internal error] Block class expected." ) ;
444
+ }
445
+ return s ;
446
+ } ;
447
+ let attrValueRef = ( index : number ) => {
448
+ let s = styleRef ( index ) ;
449
+ if ( ! ( s instanceof AttrValue ) ) {
450
+ throw new Error ( "[internal error] attribute value expected." ) ;
451
+ }
452
+ return s ;
453
+ } ;
454
+
455
+ let elementNames = Object . keys ( serializedAnalysis . elements ) ;
456
+ elementNames . forEach ( ( elID ) => {
457
+ let data = serializedAnalysis . elements [ elID ] ;
458
+ let element = new ElementAnalysis < null , null , null > ( data . sourceLocation || { start : POSITION_UNKNOWN } , parent . reservedClassNames ( ) , data . tagName , elID ) ;
459
+ for ( let analyzedStyle of data . analyzedStyles ) {
460
+ if ( isStaticClass ( analyzedStyle ) ) {
461
+ element . addStaticClass ( < BlockClass > styleRef ( analyzedStyle . klass ) ) ;
462
+ } else if ( isConditional ( analyzedStyle ) && ( isTrueCondition ( analyzedStyle ) || isFalseCondition ( analyzedStyle ) ) ) {
463
+ let dynClasses : Partial < DynamicClasses < null > > = { condition : null } ;
464
+ if ( isTrueCondition ( analyzedStyle ) ) {
465
+ ( < TrueCondition < BlockClass > > dynClasses ) . whenTrue = analyzedStyle . whenTrue . map ( c => classRef ( c ) ) ;
466
+ }
467
+ if ( isFalseCondition ( analyzedStyle ) ) {
468
+ ( < FalseCondition < BlockClass > > dynClasses ) . whenFalse = analyzedStyle . whenFalse . map ( c => classRef ( c ) ) ;
469
+ }
470
+ element . addDynamicClasses ( < Required < DynamicClasses < null > > > dynClasses ) ;
471
+ } else if ( hasDependency ( analyzedStyle ) && hasAttrValue ( analyzedStyle ) ) {
472
+ let value = attrValueRef ( analyzedStyle . value [ 0 ] ) ;
473
+ let container = classRef ( analyzedStyle . container ) ;
474
+ if ( isConditional ( analyzedStyle ) ) {
475
+ element . addDynamicAttr ( container , value , null ) ;
476
+ } else {
477
+ element . addStaticAttr ( container , value ) ;
478
+ }
479
+ } else if ( hasDependency ( analyzedStyle ) && isAttrGroup ( analyzedStyle ) && isSwitch ( analyzedStyle ) ) {
480
+ let container = classRef ( analyzedStyle . container ) ;
481
+ let group : Attribute | undefined ;
482
+ // Because the attribute is resolved into styles for serialization
483
+ // we have to find the attribute that is in the most specific sub-block
484
+ // of this attribute group.
485
+ for ( let attrValueIdx of Object . values ( analyzedStyle . group ) ) {
486
+ let attrValue = attrValueRef ( attrValueIdx ) ;
487
+ if ( ! group ) {
488
+ group = attrValue . attribute ;
489
+ } else if ( group . block . isAncestorOf ( attrValue . block ) ) {
490
+ group = attrValue . attribute ;
491
+ }
492
+ }
493
+ element . addDynamicGroup ( container , group ! , null , analyzedStyle . disallowFalsy ) ;
494
+ } else {
495
+ assertNever ( analyzedStyle ) ;
496
+ }
497
+ }
498
+ element . seal ( ) ;
499
+ analysis . elements . set ( elID , element ) ;
500
+ } ) ;
501
+
502
+ // tslint:disable-next-line:prefer-unknown-to-any
503
+ return analysis ;
340
504
}
341
505
342
506
/**
@@ -373,7 +537,7 @@ export class Analysis<K extends keyof TemplateTypes> {
373
537
} ) ;
374
538
let objects = new Array < Style > ( ) ;
375
539
serializedAnalysis . stylesFound . forEach ( s => {
376
- let style = localScope . lookup ( s ) ;
540
+ let style = localScope . find ( s ) ;
377
541
if ( style ) {
378
542
objects . push ( style ) ;
379
543
} else {
@@ -384,7 +548,8 @@ export class Analysis<K extends keyof TemplateTypes> {
384
548
let elementNames = Object . keys ( serializedAnalysis . elements ) ;
385
549
elementNames . forEach ( ( elID ) => {
386
550
let data = serializedAnalysis . elements [ elID ] ;
387
- let element = new ElementAnalysis < null , null , null > ( data . sourceLocation || { start : POSITION_UNKNOWN } , parent . reservedClassNames ( ) , undefined , elID ) ;
551
+ let element = new ElementAnalysis < null , null , null > ( data . sourceLocation || { start : POSITION_UNKNOWN } , parent . reservedClassNames ( ) , data . tagName , elID ) ;
552
+ element . seal ( ) ;
388
553
analysis . elements . set ( elID , element ) ;
389
554
} ) ;
390
555
0 commit comments