Skip to content

Commit 29d2316

Browse files
committed
feat(css-blocks): Track all declarations of the same type and validate that they have the same values, in the same order, before clearing two Styles as not-in-conflict.
1 parent 0c1fb73 commit 29d2316

File tree

3 files changed

+56
-12
lines changed

3 files changed

+56
-12
lines changed

packages/css-blocks/src/Block/RulesetContainer.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export class Ruleset {
4444
node: postcss.Rule;
4545
style: Style;
4646

47-
declarations = new Map<Property, Declaration>();
47+
declarations = new MultiMap<Property, Declaration>();
4848
resolutions = new MultiMap<Property, Style>();
4949

5050
constructor(file: string, node: postcss.Rule, style: Style){

packages/css-blocks/src/TemplateAnalysis/validations/property-conflict-validator.ts

+18-7
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,16 @@ function evaluate(obj: Style, propToRules: PropMap, conflicts: ConflictMap) {
6060
let otherDecl = other.declarations.get(prop);
6161
if ( !selfDecl || !otherDecl ) { continue; }
6262

63-
// If these declarations have the exact same value, of if there
64-
// is a specific resolution, keep going.
65-
if ( selfDecl.value === otherDecl.value ||
63+
// If these declarations have the exact same number of declarations,
64+
// in the exact same order, or if there is an explicit resolution,
65+
// ignore it and move on.
66+
let valuesEqual = selfDecl.length === otherDecl.length;
67+
if (valuesEqual) {
68+
for (let i = 0; i < Math.min(selfDecl.length, otherDecl.length); i++ ) {
69+
valuesEqual = valuesEqual && selfDecl[i].value === otherDecl[i].value;
70+
}
71+
}
72+
if ( valuesEqual ||
6673
other.hasResolutionFor(prop, self.style) ||
6774
self.hasResolutionFor(prop, other.style)
6875
) { continue; }
@@ -105,10 +112,14 @@ function recursivelyPruneConflicts(prop: string, conflicts: ConflictMap): Rulese
105112
*/
106113
function printRulesetConflict(prop: string, rule: Ruleset) {
107114
let decl = rule.declarations.get(prop);
108-
let node: postcss.Rule | postcss.Declaration = decl ? decl.node : rule.node;
109-
let line = node.source.start && `:${node.source.start.line}`;
110-
let column = node.source.start && `:${node.source.start.column}`;
111-
return ` ${rule.style.block.name}${rule.style.asSource()} (${rule.file}${line}${column})`;
115+
let nodes: postcss.Rule[] | postcss.Declaration[] = decl ? decl.map((d) => d.node) : [rule.node];
116+
let out = [];
117+
for (let node of nodes) {
118+
let line = node.source.start && `:${node.source.start.line}`;
119+
let column = node.source.start && `:${node.source.start.column}`;
120+
out.push(` ${rule.style.block.name}${rule.style.asSource()} (${rule.file}${line}${column})`);
121+
}
122+
return out.join('\n');
112123
}
113124

114125
/**

packages/css-blocks/test/validations/property-conflict-validator-test.ts

+37-4
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,18 @@ export class TemplateAnalysisTests {
5454
});
5555
}
5656

57-
@test 'properties of the same value that have been redefined in-ruleset do not throw an error'() {
57+
@test 'all properties of the same type must match, in order, for a conflict to not be thrown'() {
5858
let imports = new MockImportRegistry();
5959
let options: PluginOptions = { importer: imports.importer() };
6060
let reader = new OptionsReader(options);
6161

6262
imports.registerSource("blocks/b.block.css",
63-
`.root { block-name: block-b; color: red; color: blue; background: yellow; }`
63+
`.root { block-name: block-b; color: blue; color: yellow; }`
6464
);
6565

6666
let css = `
6767
@block-reference b from "./b.block.css";
68-
.root { block-name: block-a; color: blue; background-color: yellow; }
68+
.root { block-name: block-a; color: blue; color: yellow; }
6969
`;
7070

7171
return this.parseBlock(css, "blocks/foo.block.css", reader).then(([block, _]) => {
@@ -74,7 +74,39 @@ export class TemplateAnalysisTests {
7474
});
7575
}
7676

77-
@test 'properties of the different values that have been redefined in-ruleset throw an error'() {
77+
@test 'if properties of the same type do not match, in order, a conflict is thrown'() {
78+
let imports = new MockImportRegistry();
79+
let options: PluginOptions = { importer: imports.importer() };
80+
let reader = new OptionsReader(options);
81+
82+
imports.registerSource("blocks/b.block.css",
83+
`.root { block-name: block-b; color: red; color: yellow; }`
84+
);
85+
86+
let css = `
87+
@block-reference b from "./b.block.css";
88+
.root { block-name: block-a; color: blue; color: yellow; }
89+
`;
90+
91+
return assertParseError(
92+
cssBlocks.TemplateAnalysisError,
93+
94+
`The following property conflicts must be resolved for these co-located Styles: (templates/my-template.hbs:10:32)
95+
96+
color:
97+
block-a.root (blocks/foo.block.css:3:36)
98+
block-a.root (blocks/foo.block.css:3:49)
99+
block-b.root (blocks/b.block.css:1:30)
100+
block-b.root (blocks/b.block.css:1:42)`,
101+
102+
this.parseBlock(css, "blocks/foo.block.css", reader).then(([block, _]) => {
103+
constructElement(block, '.root', 'b.root').end();
104+
assert.deepEqual(1, 1);
105+
})
106+
);
107+
}
108+
109+
@test 'properties with different values that have been redefined in-ruleset throw an error'() {
78110
let imports = new MockImportRegistry();
79111
let options: PluginOptions = { importer: imports.importer() };
80112
let reader = new OptionsReader(options);
@@ -95,6 +127,7 @@ export class TemplateAnalysisTests {
95127
96128
color:
97129
block-a.root (blocks/foo.block.css:3:36)
130+
block-b.root (blocks/b.block.css:1:30)
98131
block-b.root (blocks/b.block.css:1:43)`,
99132

100133
this.parseBlock(css, "blocks/foo.block.css", reader).then(([block, _]) => {

0 commit comments

Comments
 (0)