Skip to content

Commit 201c4db

Browse files
authored
Convert the percentage in the scale property or scale() to a number (parcel-bundler#1174)
- Use shared scale component parsing so individual `scale` and transform scale functions normalize percentages into numbers consistently. - Keep serialization canonical by converting scale values through numeric output paths, including `scaleX/Y/Z`. - Expand transform tests to mirror the individual `scale` percentage cases one-for-one, and add direct AST serialization coverage for both `scale` and `transform`.
1 parent 96c5c6d commit 201c4db

File tree

2 files changed

+154
-15
lines changed

2 files changed

+154
-15
lines changed

src/lib.rs

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12714,6 +12714,57 @@ mod tests {
1271412714
minify_test(".foo { transform: scale3d(1, 2, 1)", ".foo{transform:scaleY(2)}");
1271512715
minify_test(".foo { transform: scale3d(1, 1, 2)", ".foo{transform:scaleZ(2)}");
1271612716
minify_test(".foo { transform: scale3d(2, 2, 1)", ".foo{transform:scale(2)}");
12717+
12718+
// transform: scale(), Convert <percentage> to <number>
12719+
test(
12720+
".foo { transform: scale3d(50%, 1, 200%) }",
12721+
indoc! {r#"
12722+
.foo {
12723+
transform: scale3d(.5, 1, 2);
12724+
}
12725+
"#},
12726+
);
12727+
minify_test(".foo { transform: scale(1%) }", ".foo{transform:scale(.01)}");
12728+
minify_test(".foo { transform: scale(0%) }", ".foo{transform:scale(0)}");
12729+
minify_test(".foo { transform: scale(0.0%) }", ".foo{transform:scale(0)}");
12730+
minify_test(".foo { transform: scale(-0%) }", ".foo{transform:scale(0)}");
12731+
minify_test(".foo { transform: scale(-0) }", ".foo{transform:scale(0)}");
12732+
minify_test(".foo { transform: scale(-0.0) }", ".foo{transform:scale(0)}");
12733+
minify_test(".foo { transform: scale(100%) }", ".foo{transform:scale(1)}");
12734+
minify_test(".foo { transform: scale(-100%) }", ".foo{transform:scale(-1)}");
12735+
minify_test(".foo { transform: scale(68%) }", ".foo{transform:scale(.68)}");
12736+
minify_test(".foo { transform: scale(5.96%) }", ".foo{transform:scale(.0596)}");
12737+
// Match WPT coverage for repeated and multi-value percentages.
12738+
minify_test(".foo { transform: scale(100%, 100%) }", ".foo{transform:scale(1)}");
12739+
minify_test(".foo { transform: scale3d(100%, 100%, 1) }", ".foo{transform:scale(1)}");
12740+
minify_test(".foo { transform: scale(-100%, -100%) }", ".foo{transform:scale(-1)}");
12741+
minify_test(".foo { transform: scale3d(-100%, -100%, 1) }", ".foo{transform:scale(-1)}");
12742+
minify_test(".foo { transform: scale(100%, 200%) }", ".foo{transform:scaleY(2)}");
12743+
minify_test(".foo { transform: scale3d(100%, 200%, 1) }", ".foo{transform:scaleY(2)}");
12744+
minify_test(".foo { transform: scale3d(100%, 100%, 0%) }", ".foo{transform:scaleZ(0)}");
12745+
minify_test(".foo { transform: scale3d(100%, 100%, 100%) }", ".foo{transform:scale(1)}");
12746+
minify_test(".foo { transform: scale3d(-0%, -0%, -0%) }", ".foo{transform:scale3d(0,0,0)}");
12747+
// Additional edge cases: mixed inputs and computed percentages.
12748+
minify_test(".foo { transform: scale(2, 100%) }", ".foo{transform:scaleX(2)}");
12749+
minify_test(".foo { transform: scale(2, -50%) }", ".foo{transform:scale(2,-.5)}");
12750+
minify_test(".foo { transform: scale(-90%, -1) }", ".foo{transform:scale(-.9,-1)}");
12751+
minify_test(".foo { transform: scale(calc(10% + 20%)) }", ".foo{transform:scale(.3)}");
12752+
minify_test(".foo { transform: scale(calc(150% - 50%), 200%) }", ".foo{transform:scaleY(2)}");
12753+
minify_test(".foo { transform: scale(200%, calc(50% - 80%)) }", ".foo{transform:scale(2,-.3)}");
12754+
// TODO: For infinite decimals, please do not attempt to resolve calc
12755+
// Expected: calc(1 / 3)
12756+
// https://github.com/parcel-bundler/lightningcss/issues/12
12757+
minify_test(".foo { transform: scale(calc(100% / 3)) }", ".foo{transform:scale(.333333)}");
12758+
// Transform::ScaleX/Y/Z
12759+
minify_test(".foo { transform: scaleX(10%) }", ".foo{transform:scaleX(.1)}");
12760+
minify_test(".foo { transform: scaleY(20%) }", ".foo{transform:scaleY(.2)}");
12761+
minify_test(".foo { transform: scaleZ(30%) }", ".foo{transform:scaleZ(.3)}");
12762+
minify_test(".foo { transform: scaleX(0%) }", ".foo{transform:scaleX(0)}");
12763+
minify_test(".foo { transform: scaleX(-0%) }", ".foo{transform:scaleX(0)}");
12764+
minify_test(".foo { transform: scaleX(calc(10% + 20%)) }", ".foo{transform:scaleX(.3)}");
12765+
minify_test(".foo { transform: scaleX(calc(180% - 20%)) }", ".foo{transform:scaleX(1.6)}");
12766+
minify_test(".foo { transform: scaleX(calc(50% - 80%)) }", ".foo{transform:scaleX(-.3)}");
12767+
1271712768
minify_test(".foo { transform: rotate(20deg)", ".foo{transform:rotate(20deg)}");
1271812769
minify_test(".foo { transform: rotateX(20deg)", ".foo{transform:rotateX(20deg)}");
1271912770
minify_test(".foo { transform: rotateY(20deg)", ".foo{transform:rotateY(20deg)}");
@@ -12851,7 +12902,6 @@ mod tests {
1285112902
".foo{transform:rotate(calc(10deg + var(--test)))}",
1285212903
".foo{transform:rotate(calc(10deg + var(--test)))}",
1285312904
);
12854-
minify_test(".foo { transform: scale(calc(10% + 20%))", ".foo{transform:scale(.3)}");
1285512905
minify_test(".foo { transform: scale(calc(.1 + .2))", ".foo{transform:scale(.3)}");
1285612906

1285712907
minify_test(
@@ -12897,6 +12947,81 @@ mod tests {
1289712947
minify_test(".foo { scale: 1 0 1 }", ".foo{scale:1 0}");
1289812948
minify_test(".foo { scale: 1 0 0 }", ".foo{scale:1 0 0}");
1289912949

12950+
// scale, Convert <percentage> to <number>
12951+
test(
12952+
".foo { scale: 50% 1 200% }",
12953+
indoc! {r#"
12954+
.foo {
12955+
scale: .5 1 2;
12956+
}
12957+
"#},
12958+
);
12959+
minify_test(".foo { scale: 1% }", ".foo{scale:.01}");
12960+
minify_test(".foo { scale: 0% }", ".foo{scale:0}");
12961+
minify_test(".foo { scale: 0.0% }", ".foo{scale:0}");
12962+
minify_test(".foo { scale: -0% }", ".foo{scale:0}");
12963+
minify_test(".foo { scale: -0 }", ".foo{scale:0}");
12964+
minify_test(".foo { scale: -0.0 }", ".foo{scale:0}");
12965+
minify_test(".foo { scale: 100% }", ".foo{scale:1}");
12966+
minify_test(".foo { scale: -100% }", ".foo{scale:-1}");
12967+
minify_test(".foo { scale: 68% }", ".foo{scale:.68}");
12968+
minify_test(".foo { scale: 5.96% }", ".foo{scale:.0596}");
12969+
// Match WPT coverage for repeated and multi-value percentages.
12970+
minify_test(".foo { scale: 100% 100% }", ".foo{scale:1}");
12971+
minify_test(".foo { scale: 100% 100% 1 }", ".foo{scale:1}");
12972+
minify_test(".foo { scale: -100% -100% }", ".foo{scale:-1}");
12973+
minify_test(".foo { scale: -100% -100% 1 }", ".foo{scale:-1}");
12974+
minify_test(".foo { scale: 100% 200% }", ".foo{scale:1 2}");
12975+
minify_test(".foo { scale: 100% 200% 1 }", ".foo{scale:1 2}");
12976+
minify_test(".foo { scale: 100% 100% 0% }", ".foo{scale:1 1 0}");
12977+
minify_test(".foo { scale: 100% 100% 100% }", ".foo{scale:1}");
12978+
minify_test(".foo { scale: -0% -0% -0% }", ".foo{scale:0 0 0}");
12979+
// Additional edge cases: mixed inputs and computed percentages.
12980+
minify_test(".foo { scale: 2 100% }", ".foo{scale:2 1}");
12981+
minify_test(".foo { scale: 2 -50% }", ".foo{scale:2 -.5}");
12982+
minify_test(".foo { scale: -90% -1 }", ".foo{scale:-.9 -1}");
12983+
minify_test(".foo { scale: calc(10% + 20%) }", ".foo{scale:.3}");
12984+
minify_test(".foo { scale: calc(150% - 50%) 200% }", ".foo{scale:1 2}");
12985+
minify_test(".foo { scale: 200% calc(50% - 80%) }", ".foo{scale:2 -.3}");
12986+
// TODO: For infinite decimals, please do not attempt to resolve calc
12987+
// Expected: calc(1 / 3)
12988+
// https://github.com/parcel-bundler/lightningcss/issues/12
12989+
minify_test(".foo { scale: calc(100% / 3) }", ".foo{scale:.333333}");
12990+
12991+
assert_eq!(
12992+
Property::Scale(crate::properties::transform::Scale::XYZ {
12993+
x: crate::values::percentage::NumberOrPercentage::Percentage(crate::values::percentage::Percentage(0.5)),
12994+
y: crate::values::percentage::NumberOrPercentage::Percentage(crate::values::percentage::Percentage(2.0)),
12995+
z: crate::values::percentage::NumberOrPercentage::Percentage(crate::values::percentage::Percentage(1.0)),
12996+
})
12997+
.to_css_string(
12998+
false,
12999+
PrinterOptions {
13000+
minify: true,
13001+
..PrinterOptions::default()
13002+
},
13003+
)
13004+
.unwrap(),
13005+
"scale:.5 2"
13006+
);
13007+
assert_eq!(
13008+
Property::Transform(
13009+
crate::properties::transform::TransformList(vec![crate::properties::transform::Transform::ScaleX(
13010+
crate::values::percentage::NumberOrPercentage::Percentage(crate::values::percentage::Percentage(0.1)),
13011+
)]),
13012+
VendorPrefix::None,
13013+
)
13014+
.to_css_string(
13015+
false,
13016+
PrinterOptions {
13017+
minify: true,
13018+
..PrinterOptions::default()
13019+
},
13020+
)
13021+
.unwrap(),
13022+
"transform:scaleX(.1)"
13023+
);
13024+
1290013025
// TODO: Re-enable with a better solution
1290113026
// See: https://github.com/parcel-bundler/lightningcss/issues/288
1290213027
// minify_test(".foo { transform: scale(3); scale: 0.5 }", ".foo{transform:scale(1.5)}");

src/properties/transform.rs

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -938,32 +938,32 @@ impl<'i> Parse<'i> for Transform {
938938
Ok(Transform::Translate3d(x, y, z))
939939
},
940940
"scale" => {
941-
let x = NumberOrPercentage::parse(input)?;
941+
let x = convert_percentage_to_number(input)?;
942942
if input.try_parse(|input| input.expect_comma()).is_ok() {
943-
let y = NumberOrPercentage::parse(input)?;
943+
let y = convert_percentage_to_number(input)?;
944944
Ok(Transform::Scale(x, y))
945945
} else {
946946
Ok(Transform::Scale(x.clone(), x))
947947
}
948948
},
949949
"scalex" => {
950-
let x = NumberOrPercentage::parse(input)?;
950+
let x = convert_percentage_to_number(input)?;
951951
Ok(Transform::ScaleX(x))
952952
},
953953
"scaley" => {
954-
let y = NumberOrPercentage::parse(input)?;
954+
let y = convert_percentage_to_number(input)?;
955955
Ok(Transform::ScaleY(y))
956956
},
957957
"scalez" => {
958-
let z = NumberOrPercentage::parse(input)?;
958+
let z = convert_percentage_to_number(input)?;
959959
Ok(Transform::ScaleZ(z))
960960
},
961961
"scale3d" => {
962-
let x = NumberOrPercentage::parse(input)?;
962+
let x = convert_percentage_to_number(input)?;
963963
input.expect_comma()?;
964-
let y = NumberOrPercentage::parse(input)?;
964+
let y = convert_percentage_to_number(input)?;
965965
input.expect_comma()?;
966-
let z = NumberOrPercentage::parse(input)?;
966+
let z = convert_percentage_to_number(input)?;
967967
Ok(Transform::Scale3d(x, y, z))
968968
},
969969
"rotate" => {
@@ -1102,16 +1102,19 @@ impl ToCss for Transform {
11021102
dest.write_char(')')
11031103
}
11041104
ScaleX(x) => {
1105+
let x: f32 = x.into();
11051106
dest.write_str("scaleX(")?;
11061107
x.to_css(dest)?;
11071108
dest.write_char(')')
11081109
}
11091110
ScaleY(y) => {
1111+
let y: f32 = y.into();
11101112
dest.write_str("scaleY(")?;
11111113
y.to_css(dest)?;
11121114
dest.write_char(')')
11131115
}
11141116
ScaleZ(z) => {
1117+
let z: f32 = z.into();
11151118
dest.write_str("scaleZ(")?;
11161119
z.to_css(dest)?;
11171120
dest.write_char(')')
@@ -1643,16 +1646,25 @@ pub enum Scale {
16431646
},
16441647
}
16451648

1649+
fn convert_percentage_to_number<'i, 't>(
1650+
input: &mut Parser<'i, 't>,
1651+
) -> Result<NumberOrPercentage, ParseError<'i, ParserError<'i>>> {
1652+
Ok(match NumberOrPercentage::parse(input)? {
1653+
NumberOrPercentage::Number(number) => NumberOrPercentage::Number(number),
1654+
NumberOrPercentage::Percentage(percent) => NumberOrPercentage::Number(percent.0),
1655+
})
1656+
}
1657+
16461658
impl<'i> Parse<'i> for Scale {
16471659
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
16481660
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
16491661
return Ok(Scale::None);
16501662
}
16511663

1652-
let x = NumberOrPercentage::parse(input)?;
1653-
let y = input.try_parse(NumberOrPercentage::parse);
1664+
let x = convert_percentage_to_number(input)?;
1665+
let y = input.try_parse(convert_percentage_to_number);
16541666
let z = if y.is_ok() {
1655-
input.try_parse(NumberOrPercentage::parse).ok()
1667+
input.try_parse(convert_percentage_to_number).ok()
16561668
} else {
16571669
None
16581670
};
@@ -1675,12 +1687,14 @@ impl ToCss for Scale {
16751687
dest.write_str("none")?;
16761688
}
16771689
Scale::XYZ { x, y, z } => {
1690+
let x: f32 = x.into();
1691+
let y: f32 = y.into();
1692+
let z: f32 = z.into();
16781693
x.to_css(dest)?;
1679-
let zv: f32 = z.into();
1680-
if y != x || zv != 1.0 {
1694+
if y != x || z != 1.0 {
16811695
dest.write_char(' ')?;
16821696
y.to_css(dest)?;
1683-
if zv != 1.0 {
1697+
if z != 1.0 {
16841698
dest.write_char(' ')?;
16851699
z.to_css(dest)?;
16861700
}

0 commit comments

Comments
 (0)