Skip to content

Commit ec338bf

Browse files
committed
feat: Deserializing block definition files & analysis in the ember-app.
1 parent 4599c94 commit ec338bf

36 files changed

+660
-152
lines changed

css-blocks.code-workspace

+3
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@
6060
},
6161
{
6262
"path": "packages/@css-blocks/ember-app"
63+
},
64+
{
65+
"path": "packages/@css-blocks/ember-support"
6366
}
6467
],
6568
"settings": {

packages/@css-blocks/core/src/Analyzer/Analysis.ts

+187-22
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,16 @@ import {
1111
TemplateInfoFactory,
1212
TemplateTypes,
1313
} from "@opticss/template-api";
14-
import { ObjectDictionary, objectValues } from "@opticss/util";
14+
import { ObjectDictionary, assertNever, objectValues } from "@opticss/util";
1515
import { IdentGenerator } from "opticss";
1616

1717
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";
2019
import { ResolvedConfiguration } from "../configuration";
2120
import { allDone } from "../util";
2221

2322
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";
2524
import { TemplateValidator, TemplateValidatorOptions } from "./validations";
2625

2726
/**
@@ -36,6 +35,18 @@ export interface SerializedAnalysis<K extends keyof TemplateTypes> {
3635
elements: ObjectDictionary<SerializedElementAnalysis>;
3736
}
3837

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+
3950
// tslint:disable-next-line:prefer-unknown-to-any
4051
type ElementAnalyzedCallback<BooleanExpression, StringExpression, TernaryExpression> = (element: ElementAnalysis<BooleanExpression, StringExpression, TernaryExpression>) => void;
4152

@@ -161,19 +172,35 @@ export class Analysis<K extends keyof TemplateTypes> {
161172
* @return The local name of the given block.
162173
*/
163174
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;
168180
}
169181
}
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;
175201
}
176202
}
203+
177204
return null;
178205
}
179206

@@ -236,7 +263,11 @@ export class Analysis<K extends keyof TemplateTypes> {
236263
* @return The local name for the block object using the local prefix for the block.
237264
*/
238265
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()}`;
240271
}
241272

242273
/**
@@ -297,13 +328,38 @@ export class Analysis<K extends keyof TemplateTypes> {
297328
}
298329
}
299330

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+
300344
/**
301345
* Generates a [[SerializedTemplateAnalysis]] for this analysis.
302346
*/
303347
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>) {
304361
let blocks = {};
305362
let stylesFound: string[] = [];
306-
let elements: ObjectDictionary<SerializedElementAnalysis> = {};
307363
let template = this.template.serialize() as SerializedTemplateInfo<K>;
308364
let styleNameMap = new Map<Style, string>();
309365
let styleIndexes = new Map<Style, number>();
@@ -329,14 +385,122 @@ export class Analysis<K extends keyof TemplateTypes> {
329385
let block = this.blocks[localName];
330386
blocks[localName] = blockPaths && blockPaths.get(block) || block.identifier;
331387
});
388+
return { template, blocks, stylesFound, styleIndexes };
389+
}
332390

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);
336412
});
413+
let values = await allDone(blockPromises);
337414

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;
340504
}
341505

342506
/**
@@ -373,7 +537,7 @@ export class Analysis<K extends keyof TemplateTypes> {
373537
});
374538
let objects = new Array<Style>();
375539
serializedAnalysis.stylesFound.forEach(s => {
376-
let style = localScope.lookup(s);
540+
let style = localScope.find(s);
377541
if (style) {
378542
objects.push(style);
379543
} else {
@@ -384,7 +548,8 @@ export class Analysis<K extends keyof TemplateTypes> {
384548
let elementNames = Object.keys(serializedAnalysis.elements);
385549
elementNames.forEach((elID) => {
386550
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();
388553
analysis.elements.set(elID, element);
389554
});
390555

0 commit comments

Comments
 (0)