Skip to content

Commit 32f1f52

Browse files
committed
Do not remove whitespace in token lists
Fixes #1005, fixes #1001, fixes #976, closes #1029, fixes #980, closes #1068, fixes #646, fixes #579 In unknown/custom properties and rules, we do not know what the syntax grammar is, so we don't know where whitespace is significant or not. Previously we tried to be too smart and made some assumptions. This broke certain unknown syntax and also affected custom transformers that tried to replace tokens with other values. We now no longer remove whitespace during parsing, and during printing only replace whitespace with a single space character instead of trying to completely remove it. This results in worse compression in a few places that we can potentially later improve, but is more correct. Note that if you write a custom transformer, you are responsible for ensuring that there are whitespace tokens surrounding any values you replace when needed. Lightning CSS does not automatically insert whitespace as it cannot know whether this is correct or not. But at least now Lightning CSS no longer removes whitespace that was already there.
1 parent 4e6492b commit 32f1f52

File tree

4 files changed

+145
-127
lines changed

4 files changed

+145
-127
lines changed

node/test/visitor.test.mjs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,68 @@ test('specific environment variables', () => {
249249
assert.equal(res.code.toString(), '@media (width<=600px){body{padding:20px}}');
250250
});
251251

252+
test('spacing with env substitution', () => {
253+
// Test spacing for different cases when `env()` functions are replaced with actual values.
254+
/** @type {Record<string, string>} */
255+
let tokens = {
256+
'--var1': 'var(--foo)',
257+
'--var2': 'var(--bar)',
258+
'--function': 'scale(1.5)',
259+
'--length1': '10px',
260+
'--length2': '20px',
261+
'--x': '4',
262+
'--y': '12',
263+
'--num1': '5',
264+
'--num2': '10',
265+
'--num3': '15',
266+
'--counter': '2',
267+
'--ident1': 'solid',
268+
'--ident2': 'auto',
269+
'--rotate': '45deg',
270+
'--percentage1': '25%',
271+
'--percentage2': '75%',
272+
'--color': 'red',
273+
'--color1': '#ff1234',
274+
'--string1': '"hello"',
275+
'--string2': '" world"'
276+
};
277+
278+
let res = transform({
279+
filename: 'test.css',
280+
minify: true,
281+
code: Buffer.from(`
282+
.test {
283+
/* Asymmetric spacing - no space after var(). */
284+
background: env(--var1) env(--var2);
285+
border: env(--var1)env(--ident1);
286+
transform: env(--function) env(--function);
287+
/* Normal spacing between values. */
288+
padding: env(--length1) env(--length2);
289+
margin: env(--length1) env(--ident2);
290+
outline: env(--color) env(--ident1);
291+
/* Raw numbers that need spacing. */
292+
cursor: url(cursor.png) env(--x) env(--y), auto;
293+
stroke-dasharray: env(--num1) env(--num2) env(--num3);
294+
counter-increment: myCounter env(--counter);
295+
/* Mixed token types. */
296+
background: linear-gradient(red env(--percentage1), blue env(--percentage2));
297+
content: env(--string1) env(--string2);
298+
/* Inside calc expressions. */
299+
width: calc(env(--length1) - env(--length2));
300+
}
301+
`),
302+
visitor: {
303+
EnvironmentVariable(env) {
304+
if (env.name.type === 'custom' && tokens[env.name.ident]) {
305+
return { raw: tokens[env.name.ident] };
306+
}
307+
}
308+
}
309+
});
310+
311+
assert.equal(res.code.toString(), '.test{background:var(--foo) var(--bar);border:var(--foo)solid;transform:scale(1.5) scale(1.5);padding:10px 20px;margin:10px auto;outline:red solid;cursor:url(cursor.png) 4 12, auto;stroke-dasharray:5 10 15;counter-increment:myCounter 2;background:linear-gradient(red 25%, blue 75%);content:"hello" " world";width:calc(10px - 20px)}');
312+
});
313+
252314
test('url', () => {
253315
// https://www.npmjs.com/package/postcss-url
254316
let res = transform({

src/lib.rs

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8020,7 +8020,22 @@ mod tests {
80208020
);
80218021
minify_test(
80228022
".foo { width: calc(100% - 2 (2 * var(--card-margin))); }",
8023-
".foo{width:calc(100% - 2 (2*var(--card-margin)))}",
8023+
".foo{width:calc(100% - 2 (2 * var(--card-margin)))}",
8024+
);
8025+
8026+
test(
8027+
indoc! {r#"
8028+
.test {
8029+
width: calc(var(--test) + 2px);
8030+
width: calc(var(--test) - 2px);
8031+
}
8032+
"#},
8033+
indoc! {r#"
8034+
.test {
8035+
width: calc(var(--test) + 2px);
8036+
width: calc(var(--test) - 2px);
8037+
}
8038+
"#},
80248039
);
80258040
}
80268041

@@ -8081,7 +8096,7 @@ mod tests {
80818096
minify_test(".foo { rotate: atan2(0, -1)", ".foo{rotate:180deg}");
80828097
minify_test(".foo { rotate: atan2(-1, 1)", ".foo{rotate:-45deg}");
80838098
// incompatible units
8084-
minify_test(".foo { rotate: atan2(1px, -1vw)", ".foo{rotate:atan2(1px,-1vw)}");
8099+
minify_test(".foo { rotate: atan2(1px, -1vw)", ".foo{rotate:atan2(1px, -1vw)}");
80858100
}
80868101

80878102
#[test]
@@ -8113,7 +8128,10 @@ mod tests {
81138128
minify_test(".foo { width: abs(1%)", ".foo{width:abs(1%)}"); // spec says percentages must be against resolved value
81148129

81158130
minify_test(".foo { width: calc(10px * sign(-1vw)", ".foo{width:-10px}");
8116-
minify_test(".foo { width: calc(10px * sign(1%)", ".foo{width:calc(10px*sign(1%))}");
8131+
minify_test(
8132+
".foo { width: calc(10px * sign(1%)",
8133+
".foo{width:calc(10px * sign(1%))}",
8134+
);
81178135
}
81188136

81198137
#[test]
@@ -13947,7 +13965,7 @@ mod tests {
1394713965
// ref: https://github.com/parcel-bundler/lightningcss/pull/255#issuecomment-1219049998
1394813966
minify_test(
1394913967
"@font-face {src: url(\"foo.ttf\") tech(palettes color-colrv0 variations) format(opentype);}",
13950-
"@font-face{src:url(foo.ttf) tech(palettes color-colrv0 variations)format(opentype)}",
13968+
"@font-face{src:url(foo.ttf) tech(palettes color-colrv0 variations) format(opentype)}",
1395113969
);
1395213970
// TODO(CGQAQ): make this test pass when we have strict mode
1395313971
// ref: https://github.com/web-platform-tests/wpt/blob/9f8a6ccc41aa725e8f51f4f096f686313bb88d8d/css/css-fonts/parsing/font-face-src-tech.html#L45
@@ -13969,7 +13987,7 @@ mod tests {
1396913987
// );
1397013988
minify_test(
1397113989
"@font-face {src: local(\"\") url(\"test.woff\");}",
13972-
"@font-face{src:local(\"\")url(test.woff)}",
13990+
"@font-face{src:local(\"\") url(test.woff)}",
1397313991
);
1397413992
minify_test("@font-face {font-weight: 200 400}", "@font-face{font-weight:200 400}");
1397513993
minify_test("@font-face {font-weight: 400 400}", "@font-face{font-weight:400}");
@@ -14067,7 +14085,7 @@ mod tests {
1406714085
font-family: Handover Sans;
1406814086
base-palette: 3;
1406914087
override-colors: 1 rgb(43, 12, 9), 3 var(--highlight);
14070-
}"#, "@font-palette-values --Cooler{font-family:Handover Sans;base-palette:3;override-colors:1 #2b0c09,3 var(--highlight)}");
14088+
}"#, "@font-palette-values --Cooler{font-family:Handover Sans;base-palette:3;override-colors:1 #2b0c09, 3 var(--highlight)}");
1407114089
prefix_test(
1407214090
r#"@font-palette-values --Cooler {
1407314091
font-family: Handover Sans;
@@ -20439,19 +20457,19 @@ mod tests {
2043920457
);
2044020458
minify_test(
2044120459
".foo { color: color-mix(in srgb, currentColor, blue); }",
20442-
".foo{color:color-mix(in srgb,currentColor,blue)}",
20460+
".foo{color:color-mix(in srgb, currentColor, blue)}",
2044320461
);
2044420462
minify_test(
2044520463
".foo { color: color-mix(in srgb, blue, currentColor); }",
20446-
".foo{color:color-mix(in srgb,blue,currentColor)}",
20464+
".foo{color:color-mix(in srgb, blue, currentColor)}",
2044720465
);
2044820466
minify_test(
2044920467
".foo { color: color-mix(in srgb, accentcolor, blue); }",
20450-
".foo{color:color-mix(in srgb,accentcolor,blue)}",
20468+
".foo{color:color-mix(in srgb, accentcolor, blue)}",
2045120469
);
2045220470
minify_test(
2045320471
".foo { color: color-mix(in srgb, blue, accentcolor); }",
20454-
".foo{color:color-mix(in srgb,blue,accentcolor)}",
20472+
".foo{color:color-mix(in srgb, blue, accentcolor)}",
2045520473
);
2045620474

2045720475
// regex for converting web platform tests:
@@ -22645,15 +22663,15 @@ mod tests {
2264522663
minify_test(".foo { --test: var(--foo, 20px); }", ".foo{--test:var(--foo,20px)}");
2264622664
minify_test(
2264722665
".foo { transition: var(--foo, 20px),\nvar(--bar, 40px); }",
22648-
".foo{transition:var(--foo,20px),var(--bar,40px)}",
22666+
".foo{transition:var(--foo,20px), var(--bar,40px)}",
2264922667
);
2265022668
minify_test(
2265122669
".foo { background: var(--color) var(--image); }",
22652-
".foo{background:var(--color)var(--image)}",
22670+
".foo{background:var(--color) var(--image)}",
2265322671
);
2265422672
minify_test(
2265522673
".foo { height: calc(var(--spectrum-global-dimension-size-300) / 2);",
22656-
".foo{height:calc(var(--spectrum-global-dimension-size-300)/2)}",
22674+
".foo{height:calc(var(--spectrum-global-dimension-size-300) / 2)}",
2265722675
);
2265822676
minify_test(
2265922677
".foo { color: var(--color, rgb(255, 255, 0)); }",
@@ -22665,12 +22683,32 @@ mod tests {
2266522683
);
2266622684
minify_test(
2266722685
".foo { color: var(--color, rgb(var(--red), var(--green), 0)); }",
22668-
".foo{color:var(--color,rgb(var(--red),var(--green),0))}",
22686+
".foo{color:var(--color,rgb(var(--red), var(--green), 0))}",
2266922687
);
2267022688
minify_test(".foo { --test: .5s; }", ".foo{--test:.5s}");
2267122689
minify_test(".foo { --theme-sizes-1\\/12: 2 }", ".foo{--theme-sizes-1\\/12:2}");
2267222690
minify_test(".foo { --test: 0px; }", ".foo{--test:0px}");
2267322691

22692+
// Test attr() function with type() syntax - minified
22693+
minify_test(
22694+
".foo { background-color: attr(data-color type(<color>)); }",
22695+
".foo{background-color:attr(data-color type(<color>))}",
22696+
);
22697+
minify_test(
22698+
".foo { width: attr(data-width type(<length>), 100px); }",
22699+
".foo{width:attr(data-width type(<length>), 100px)}",
22700+
);
22701+
22702+
// Test attr() function with type() syntax - non-minified (issue with extra spaces)
22703+
test(
22704+
".foo { background-color: attr(data-color type(<color>)); }",
22705+
".foo {\n background-color: attr(data-color type(<color>));\n}\n",
22706+
);
22707+
test(
22708+
".foo { width: attr(data-width type(<length>), 100px); }",
22709+
".foo {\n width: attr(data-width type(<length>), 100px);\n}\n",
22710+
);
22711+
2267422712
prefix_test(
2267522713
r#"
2267622714
.foo {
@@ -23460,7 +23498,7 @@ mod tests {
2346023498
);
2346123499
attr_test(
2346223500
"text-decoration: var(--foo) lab(40% 56.6 39);",
23463-
"text-decoration:var(--foo)#b32323",
23501+
"text-decoration:var(--foo) #b32323",
2346423502
true,
2346523503
Some(Browsers {
2346623504
chrome: Some(90 << 16),
@@ -24686,7 +24724,9 @@ mod tests {
2468624724
indoc! {r#"
2468724725
div {
2468824726
color: #00f;
24689-
--button: focus { color: red; };
24727+
--button: focus {
24728+
color: red;
24729+
};
2469024730
}
2469124731
"#},
2469224732
);
@@ -29536,11 +29576,12 @@ mod tests {
2953629576
color: red;
2953729577
}
2953829578
}"#,
29539-
indoc! {r#"
29540-
@foo test {
29541-
div { color: red; }
29542-
}
29543-
"#},
29579+
indoc! { r#"@foo test {
29580+
div {
29581+
color: red;
29582+
}
29583+
}
29584+
"#},
2954429585
);
2954529586
minify_test(
2954629587
r#"@foo test {

0 commit comments

Comments
 (0)