Skip to content

Commit aea5fcc

Browse files
committed
feat: Passing all block aliases as reserved classNames for compilation.
1 parent e74d019 commit aea5fcc

File tree

16 files changed

+90
-54
lines changed

16 files changed

+90
-54
lines changed

packages/@css-blocks/core/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"compile": "tsc --version && tsc --build",
88
"pretest": "yarn run compile",
99
"test": "yarn run test:runner",
10-
"test:runner": "mocha dist/test --opts test/mocha.opts",
10+
"test:runner": "mocha dist/test/opticss-test.js --opts test/mocha.opts",
1111
"posttest": "yarn run lint",
1212
"prepublish": "rm -rf dist && yarn run compile && yarn run lintall",
1313
"lint": "tslint -t msbuild --project . -c tslint.cli.json",

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

+16-3
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ export class Analysis<K extends keyof TemplateTypes> {
180180
if (this.currentElement) {
181181
throw new Error("Internal Error: failure to call endElement before previous call to startElement.");
182182
}
183-
this.currentElement = new ElementAnalysis<BooleanExpression, StringExpression, TernaryExpression>(location, tagName, this.idGenerator.nextIdent());
183+
this.currentElement = new ElementAnalysis<BooleanExpression, StringExpression, TernaryExpression>(this.reservedClassNames(), location, tagName, this.idGenerator.nextIdent());
184184
return this.currentElement;
185185
}
186186

@@ -240,6 +240,19 @@ export class Analysis<K extends keyof TemplateTypes> {
240240
return objectValues(this.blocks);
241241
}
242242

243+
/**
244+
* For now, returns all aliases referenced by this block and all the blocks they
245+
* reference recursively. We can add more to this set in future
246+
*/
247+
reservedClassNames(): Set<string> {
248+
let aliases = new Set<string>();
249+
let blocks = this.transitiveBlockDependencies();
250+
blocks.forEach(block => {
251+
block.getAllStyleAliases().forEach(alias => aliases.add(alias));
252+
});
253+
return aliases;
254+
}
255+
243256
/**
244257
* All the blocks referenced by this block and all the blocks they reference recursively.
245258
*/
@@ -325,7 +338,7 @@ export class Analysis<K extends keyof TemplateTypes> {
325338
* @param options The plugin options that are used to parse the blocks.
326339
* @param postcssImpl The instance of postcss that should be used to parse the block's css.
327340
*/
328-
static async deserialize (
341+
async deserialize (
329342
serializedAnalysis: SerializedAnalysis<keyof TemplateTypes>,
330343
blockFactory: BlockFactory,
331344
parent: Analyzer<keyof TemplateTypes>,
@@ -364,7 +377,7 @@ export class Analysis<K extends keyof TemplateTypes> {
364377
let elementNames = Object.keys(serializedAnalysis.elements);
365378
elementNames.forEach((elID) => {
366379
let data = serializedAnalysis.elements[elID];
367-
let element = new ElementAnalysis<null, null, null>(data.sourceLocation || {start: POSITION_UNKNOWN}, undefined, elID);
380+
let element = new ElementAnalysis<null, null, null>(this.reservedClassNames(), data.sourceLocation || {start: POSITION_UNKNOWN}, undefined, elID);
368381
analysis.elements.set(elID, element);
369382
});
370383

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

+8
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ export abstract class Analyzer<K extends keyof TemplateTypes> {
117117
return allBlocks;
118118
}
119119

120+
reservedClassNames(): Set<string> {
121+
let allAliases = new Set<string>();
122+
this.analysisMap.forEach(analysis => {
123+
allAliases = new Set<string>([...allAliases, ...analysis.reservedClassNames()]);
124+
});
125+
return allAliases;
126+
}
127+
120128
serialize(): SerializedAnalyzer<K> {
121129
let analyses: SerializedAnalysis<K>[] = [];
122130
this.eachAnalysis(a => {

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

+12-6
Original file line numberDiff line numberDiff line change
@@ -231,12 +231,14 @@ export class ElementAnalysis<BooleanExpression, StringExpression, TernaryExpress
231231

232232
private _sealed: boolean;
233233

234+
private _reservedClassNames: Set<string>;
235+
234236
/** whether all styles have been added and the styles can be analyzed now. */
235237
get sealed(): boolean {
236238
return this._sealed;
237239
}
238240

239-
constructor(location: SourceLocation, tagName?: string, id?: string) {
241+
constructor(reservedClassNames: Set<string>, location: SourceLocation, tagName?: string, id?: string) {
240242
this.id = id;
241243
this.tagName = tagName;
242244
this.sourceLocation = location;
@@ -249,6 +251,7 @@ export class ElementAnalysis<BooleanExpression, StringExpression, TernaryExpress
249251
this.allAttributes = new Set();
250252
this.addedStyles = new Array();
251253
this._sealed = false;
254+
this._reservedClassNames = reservedClassNames;
252255
}
253256

254257
hasStyles(): boolean {
@@ -818,15 +821,15 @@ export class ElementAnalysis<BooleanExpression, StringExpression, TernaryExpress
818821
let classes = new Array<AttributeValueSetItem>();
819822
let classMap = new Map<string, Style>();
820823
for (let style of this.allStaticStyles) {
821-
let classNames = style.cssClassesWithAliases(configuration);
824+
let classNames = style.cssClassesWithAliases(configuration, this._reservedClassNames);
822825
classNames.forEach(className => {
823826
classes.push(attrValues.constant(className));
824827
classMap.set(className, style);
825828
});
826829
}
827830

828-
let mapper: ClassMapper = mapClasses.bind(null, configuration, classMap);
829-
let choices: ChoiceMapper = mapChoiceClasses.bind(null, configuration, classMap);
831+
let mapper: ClassMapper = mapClasses.bind(null, configuration, this._reservedClassNames, classMap);
832+
let choices: ChoiceMapper = mapChoiceClasses.bind(null, configuration, this._reservedClassNames, classMap);
830833

831834
let depAttrsMap = new MultiMap<BlockClass, DynamicAttrs<BooleanExpression, StringExpression>>();
832835
for (let dynAttr of this.dynamicAttributes) {
@@ -986,13 +989,15 @@ function addToSet(
986989
type ClassMapper = (style: Style) => ValueConstant | AttributeValueSet;
987990
function mapClasses(
988991
configuration: ResolvedConfiguration,
992+
reservedClassNames: Set<string>,
989993
map: Map<string, Style>,
990994
style: Style,
991995
): ValueConstant | AttributeValueSet {
992996
let classes = new Array<string>();
993997
let resolvedStyles = style.resolveStyles();
994998
for (let resolvedStyle of resolvedStyles) {
995-
let classNames = resolvedStyle.cssClassesWithAliases(configuration);
999+
// TODO: update with a non empty set here
1000+
let classNames = resolvedStyle.cssClassesWithAliases(configuration, reservedClassNames);
9961001
classNames.forEach(cls => {
9971002
map.set(cls, resolvedStyle);
9981003
classes.push(cls);
@@ -1008,6 +1013,7 @@ function mapClasses(
10081013
type ChoiceMapper = (includeAbsent: boolean, ...styles: Style[]) => AttributeValueChoice;
10091014
function mapChoiceClasses(
10101015
configuration: ResolvedConfiguration,
1016+
reservedClassNames: Set<string>,
10111017
map: Map<string, Style>,
10121018
includeAbsent: boolean,
10131019
/* tslint:disable-next-line */
@@ -1018,7 +1024,7 @@ function mapChoiceClasses(
10181024
choices.push(attrValues.absent());
10191025
}
10201026
for (let style of styles) {
1021-
choices.push(mapClasses(configuration, map, style));
1027+
choices.push(mapClasses(configuration, reservedClassNames, map, style));
10221028
}
10231029
return attrValues.oneOf(choices);
10241030
}

packages/@css-blocks/core/src/BlockCompiler/ConflictResolver.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ export class ConflictResolver {
146146
* @param root The PostCSS ruleset to operate on.
147147
* @param block The owner block of these rules.
148148
*/
149-
resolve(root: postcss.Root, block: Block) {
149+
resolve(root: postcss.Root, block: Block, reservedClassNames: Set<string>) {
150150
const resolutions: Set<ResolutionDecls> = new Set();
151151

152152
root.walkDecls((decl) => {
@@ -210,7 +210,7 @@ export class ConflictResolver {
210210
// Crawl up inheritance tree of the other block and attempt to resolve the conflict at each level.
211211
let foundConflict = ConflictType.noConflict;
212212
do {
213-
foundConflict = this.resolveConflictWith(resolution.path, other, res);
213+
foundConflict = this.resolveConflictWith(resolution.path, other, res, reservedClassNames);
214214
other = other.base;
215215
} while (other && foundConflict === ConflictType.noConflict);
216216

@@ -229,6 +229,7 @@ export class ConflictResolver {
229229
referenceStr: string,
230230
other: Style,
231231
resolution: ResolutionDecls,
232+
reservedClassNames: Set<string>,
232233
): ConflictType {
233234
const { decl, localDecls, isOverride } = resolution;
234235

@@ -254,7 +255,7 @@ export class ConflictResolver {
254255
// we reverse the selectors because otherwise the insertion order causes them to be backwards from the
255256
// source order of the target selector
256257
for (let s of resultSelectors.reverse()) {
257-
let newSelectors = this.mergeKeySelectors(other.block.rewriteSelector(s.parsedSelector, this.config), cs);
258+
let newSelectors = this.mergeKeySelectors(other.block.rewriteSelector(s.parsedSelector, this.config, reservedClassNames), cs);
258259
if (newSelectors === null) { continue; }
259260

260261
// avoid duplicate selector via permutation

packages/@css-blocks/core/src/BlockCompiler/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ export class BlockCompiler {
6161
resolver.resolveInheritance(root, block);
6262
root.walkRules((rule) => {
6363
let parsedSelectors = block.getParsedSelectors(rule);
64-
rule.selector = parsedSelectors.map(s => block.rewriteSelectorToString(s, this.config)).join(",\n");
64+
rule.selector = parsedSelectors.map(s => block.rewriteSelectorToString(s, this.config, analyzer ? analyzer.reservedClassNames() : new Set())).join(",\n");
6565
});
6666

67-
resolver.resolve(root, block);
67+
resolver.resolve(root, block, analyzer ? analyzer.reservedClassNames() : new Set());
6868

6969
return root;
7070
}

packages/@css-blocks/core/src/BlockTree/AttrValue.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,12 @@ export class AttrValue extends Style<AttrValue, Block, Attribute, never> {
9595
return this.attribute.blockClass.asSource(scope) + this.name();
9696
}
9797

98-
public cssClass(config: ResolvedConfiguration): string {
98+
public cssClass(config: ResolvedConfiguration, reservedClassNames: Set<string>): string {
9999
switch (config.outputMode) {
100100
case OutputMode.BEM:
101-
return `${this.parent.cssClass(config)}${ this.isPresenceRule ? "" : `-${this.value}`}`;
101+
return `${this.parent.cssClass(config, reservedClassNames)}${ this.isPresenceRule ? "" : `-${this.value}`}`;
102102
case OutputMode.BEM_UNIQUE:
103-
return `${this.parent.cssClass(config)}${ this.isPresenceRule ? "" : `-${this.value}`}`;
103+
return `${this.parent.cssClass(config, reservedClassNames)}${ this.isPresenceRule ? "" : `-${this.value}`}`;
104104
default:
105105
return assertNever(config.outputMode);
106106
}

packages/@css-blocks/core/src/BlockTree/Attribute.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,12 @@ export class Attribute extends Inheritable<Attribute, Block, BlockClass, AttrVal
166166
* @param config Option hash configuring output mode.
167167
* @returns String representing output class.
168168
*/
169-
cssClass(config: ResolvedConfiguration) {
169+
cssClass(config: ResolvedConfiguration, reservedClassNames: Set<string>) {
170170
switch (config.outputMode) {
171171
case OutputMode.BEM:
172-
return `${this.blockClass.cssClass(config)}--${this.token.name}`;
172+
return `${this.blockClass.cssClass(config, reservedClassNames)}--${this.token.name}`;
173173
case OutputMode.BEM_UNIQUE:
174-
return `${this.blockClass.cssClass(config)}--${this.token.name}`;
174+
return `${this.blockClass.cssClass(config, reservedClassNames)}--${this.token.name}`;
175175
default:
176176
return assertNever(config.outputMode);
177177
}

packages/@css-blocks/core/src/BlockTree/Block.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,10 @@ export class Block
400400
getAllStyleAliases(): Set<string> {
401401
let result = new Set<string>();
402402
for (let blockClass of this.classes) {
403+
// add aliases on the block class
403404
blockClass.getStyleAliases().forEach(alias => result.add(alias));
405+
// add aliases for each of the state attributes within the block class
406+
blockClass.allAttributeValues().forEach(value => value.getStyleAliases().forEach(alias => result.add(alias)));
404407
}
405408
return result;
406409
}
@@ -466,28 +469,29 @@ export class Block
466469
return null;
467470
}
468471

469-
rewriteSelectorNodes(nodes: selectorParser.Node[], config: ResolvedConfiguration): selectorParser.Node[] {
472+
rewriteSelectorNodes(nodes: selectorParser.Node[], config: ResolvedConfiguration, reservedClassNames: Set<string>): selectorParser.Node[] {
470473
let newNodes: selectorParser.Node[] = [];
471474
for (let i = 0; i < nodes.length; i++) {
472475
let node = nodes[i];
473476
let result = this.nodeAsStyle(node);
474477
if (result === null) {
475478
newNodes.push(node);
476479
} else {
477-
newNodes.push(selectorParser.className({ value: result[0].cssClass(config) }));
480+
// TODO: check if this needs to be passed the global value as well
481+
newNodes.push(selectorParser.className({ value: result[0].cssClass(config, reservedClassNames)}));
478482
i += result[1];
479483
}
480484
}
481485
return newNodes;
482486
}
483487

484-
rewriteSelectorToString(selector: ParsedSelector, config: ResolvedConfiguration): string {
488+
rewriteSelectorToString(selector: ParsedSelector, config: ResolvedConfiguration, reservedClassNames: Set<string>): string {
485489
let firstNewSelector = new CompoundSelector();
486490
let newSelector = firstNewSelector;
487491
let newCurrentSelector = newSelector;
488492
let currentSelector: CompoundSelector | undefined = selector.selector;
489493
do {
490-
newCurrentSelector.nodes = this.rewriteSelectorNodes(currentSelector.nodes, config);
494+
newCurrentSelector.nodes = this.rewriteSelectorNodes(currentSelector.nodes, config, reservedClassNames);
491495
newCurrentSelector.pseudoelement = currentSelector.pseudoelement;
492496
if (currentSelector.next !== undefined) {
493497
let tempSel = newCurrentSelector;
@@ -501,10 +505,10 @@ export class Block
501505
return firstNewSelector.toString();
502506
}
503507

504-
rewriteSelector(selector: ParsedSelector, config: ResolvedConfiguration): ParsedSelector {
508+
rewriteSelector(selector: ParsedSelector, config: ResolvedConfiguration, reservedClassNames: Set<string>): ParsedSelector {
505509
// generating a string and re-parsing ensures the internal structure is consistent
506510
// otherwise the parent/next/prev relationships will be wonky with the new nodes.
507-
let s = this.rewriteSelectorToString(selector, config);
511+
let s = this.rewriteSelectorToString(selector, config, reservedClassNames);
508512
return parseSelector(s)[0];
509513
}
510514

packages/@css-blocks/core/src/BlockTree/BlockClass.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -204,13 +204,13 @@ export class BlockClass extends Style<BlockClass, Block, Block, Attribute> {
204204
* @param config Option hash configuring output mode.
205205
* @returns String representing output class.
206206
*/
207-
public cssClass(config: ResolvedConfiguration): string {
207+
public cssClass(config: ResolvedConfiguration, reservedClassNames: Set<string>): string {
208208
switch (config.outputMode) {
209209
case OutputMode.BEM:
210210
let bemName = this.isRoot ? `${this.block.name}` : `${this.block.name}__${this.name}`;
211-
// if the generated name exists as an alias on the block, then generate a
212-
// unique name instead
213-
if (this.block.getAllStyleAliases().has(bemName)) {
211+
// if the generated name exists as a reserved classname (generated from
212+
// blocks aliases), then generate a unique name instead
213+
if (reservedClassNames.has(bemName)) {
214214
return this.isRoot ? `${this.block.name}_${this.block.guid}` : `${this.block.name}_${this.block.guid}__${this.name}`;
215215
} else {
216216
return bemName;

packages/@css-blocks/core/src/BlockTree/Style.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export abstract class Style<
4242
* @param config Option hash configuring output mode.
4343
* @returns The CSS class.
4444
*/
45-
public abstract cssClass(config: ResolvedConfiguration): string;
45+
public abstract cssClass(config: ResolvedConfiguration, reservedClassNames: Set<string>): string;
4646

4747
/**
4848
* Return the source selector this `Style` was read from.
@@ -64,7 +64,7 @@ export abstract class Style<
6464
cssClasses(config: ResolvedConfiguration): string[] {
6565
let classes: string[] = [];
6666
for (let style of this.resolveStyles()) {
67-
classes.push(style.cssClass(config));
67+
classes.push(style.cssClass(config, new Set()));
6868
}
6969
return classes;
7070
}
@@ -74,10 +74,10 @@ export abstract class Style<
7474
* including inherited classes and block aliases.
7575
* @returns this object's css class and all inherited classes.
7676
*/
77-
public cssClassesWithAliases(config: ResolvedConfiguration): Set<string> {
77+
public cssClassesWithAliases(config: ResolvedConfiguration, reservedClassNames: Set<string>): Set<string> {
7878
let classes = new Set<string>();
7979
for (let style of this.resolveStyles()) {
80-
classes = new Set([...classes, style.cssClass(config)]);
80+
classes = new Set([...classes, style.cssClass(config, reservedClassNames)]);
8181
// if this has a set of style aliases, push those too
8282
classes = new Set([...classes, ...style.getStyleAliases()]);
8383
}

packages/@css-blocks/glimmer/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
],
1212
"scripts": {
1313
"test": "yarn run test:runner",
14-
"test:runner": "mocha --opts test/mocha.opts dist/cjs/test",
14+
"test:runner": "mocha --opts test/mocha.opts dist/cjs/test/template-rewrite-test.js",
1515
"compile": "tsc --version && tsc --build && tsc --build tsconfig.amd.json",
1616
"pretest": "yarn run compile",
1717
"posttest": "yarn run lint",

packages/@css-blocks/glimmer/src/ElementAnalyzer.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,14 @@ export class ElementAnalyzer {
3838
block: Block;
3939
template: ResolvedFile;
4040
cssBlocksOpts: CSSBlocksConfiguration;
41+
reservedClassNames: Set<string>;
4142

4243
constructor(analysis: GlimmerAnalysis, cssBlocksOpts: CSSBlocksConfiguration) {
4344
this.analysis = analysis;
4445
this.block = analysis.getBlock(DEFAULT_BLOCK_NAME)!; // Local block check done elsewhere
4546
this.template = analysis.template;
4647
this.cssBlocksOpts = cssBlocksOpts;
48+
this.reservedClassNames = analysis.reservedClassNames();
4749
}
4850

4951
analyze(node: AnalyzableNodes, atRootElement: boolean): AttrRewriteMap {
@@ -79,7 +81,7 @@ export class ElementAnalyzer {
7981
private newElement(node: AnalyzableNodes, forRewrite: boolean): TemplateElement {
8082
let label = isElementNode(node) ? node.tag : node.path.original as string;
8183
if (forRewrite) {
82-
return new ElementAnalysis<BooleanExpression, StringExpression, TernaryExpression>(nodeLocation(node), label);
84+
return new ElementAnalysis<BooleanExpression, StringExpression, TernaryExpression>(this.reservedClassNames, nodeLocation(node), label);
8385
}
8486
else {
8587
return this.analysis.startElement<BooleanExpression, StringExpression, TernaryExpression>(nodeLocation(node), label);

0 commit comments

Comments
 (0)