From 7dd0cb3cd77a40d4ead820ed95d60877b042716c Mon Sep 17 00:00:00 2001 From: Brennan Kinney <5098581+polarathene@users.noreply.github.com> Date: Mon, 4 Mar 2024 17:45:24 +1300 Subject: [PATCH 001/156] docs: Update reference to Stylo (#691) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 03554462..f44d7b3c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ An extremely fast CSS parser, transformer, and minifier written in Rust. Use it - **Extremely fast** – Parsing and minifying large files is completed in milliseconds, often with significantly smaller output than other tools. See [benchmarks](#benchmarks) below. - **Typed property values** – many other CSS parsers treat property values as an untyped series of tokens. This means that each transformer that wants to do something with these values must interpret them itself, leading to duplicate work and inconsistencies. Lightning CSS parses all values using the grammar from the CSS specification, and exposes a specific value type for each property. -- **Browser-grade parser** – Lightning CSS is built on the [cssparser](https://github.com/servo/rust-cssparser) and [selectors](https://github.com/servo/servo/tree/master/components/selectors) crates created by Mozilla and used by Firefox and Servo. These provide a solid general purpose CSS-parsing foundation on top of which Lightning CSS implements support for all specific CSS rules and properties. +- **Browser-grade parser** – Lightning CSS is built on the [cssparser](https://github.com/servo/rust-cssparser) and [selectors](https://github.com/servo/stylo/tree/main/selectors) crates created by Mozilla and used by Firefox and Servo. These provide a solid general purpose CSS-parsing foundation on top of which Lightning CSS implements support for all specific CSS rules and properties. - **Minification** – One of the main purposes of Lightning CSS is to minify CSS to make it smaller. This includes many optimizations including: - Combining longhand properties into shorthands where possible. - Merging adjacent rules with the same selectors or declarations when it is safe to do so. From e37b9ed5e303b3e7372de092345cfe6e86ef9cb8 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 7 Mar 2024 17:13:32 +0100 Subject: [PATCH 002/156] disable transform optimizations (#694) --- src/lib.rs | 96 +++++++++++++++------------- src/properties/transform.rs | 124 ++++++++++++++++++++---------------- 2 files changed, 122 insertions(+), 98 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index efaf28c9..8c70f8ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11552,14 +11552,16 @@ mod tests { ".foo { transform: matrix3d(1, 0, 0, 0, 0, 1, 6, 0, 0, 0, 1, 0, 50, 100, 0, 1.1)", ".foo{transform:matrix3d(1,0,0,0,0,1,6,0,0,0,1,0,50,100,0,1.1)}", ); - minify_test( - ".foo{transform:translate(100px,200px) rotate(45deg) skew(10deg) scale(2)}", - ".foo{transform:matrix(1.41421,1.41421,-1.16485,1.66358,100,200)}", - ); - minify_test( - ".foo{transform:translate(200px,300px) translate(100px,200px) scale(2)}", - ".foo{transform:matrix(2,0,0,2,300,500)}", - ); + // TODO: Re-enable with a better solution + // See: https://github.com/parcel-bundler/lightningcss/issues/288 + // minify_test( + // ".foo{transform:translate(100px,200px) rotate(45deg) skew(10deg) scale(2)}", + // ".foo{transform:matrix(1.41421,1.41421,-1.16485,1.66358,100,200)}", + // ); + // minify_test( + // ".foo{transform:translate(200px,300px) translate(100px,200px) scale(2)}", + // ".foo{transform:matrix(2,0,0,2,300,500)}", + // ); minify_test( ".foo{transform:translate(100px,200px) rotate(45deg)}", ".foo{transform:translate(100px,200px)rotate(45deg)}", @@ -11568,34 +11570,36 @@ mod tests { ".foo{transform:rotate3d(1, 1, 1, 45deg) translate3d(100px, 100px, 10px)}", ".foo{transform:rotate3d(1,1,1,45deg)translate3d(100px,100px,10px)}", ); - minify_test( - ".foo{transform:translate3d(100px, 100px, 10px) skew(10deg) scale3d(2, 3, 4)}", - ".foo{transform:matrix3d(2,0,0,0,.528981,3,0,0,0,0,4,0,100,100,10,1)}", - ); - minify_test( - ".foo{transform:matrix3d(0.804737854124365, 0.5058793634016805, -0.31061721752604554, 0, -0.31061721752604554, 0.804737854124365, 0.5058793634016805, 0, 0.5058793634016805, -0.31061721752604554, 0.804737854124365, 0, 100, 100, 10, 1)}", - ".foo{transform:translate3d(100px,100px,10px)rotate3d(1,1,1,45deg)}" - ); - minify_test( - ".foo{transform:matrix3d(1, 0, 0, 0, 0, 0.7071067811865476, 0.7071067811865475, 0, 0, -0.7071067811865475, 0.7071067811865476, 0, 100, 100, 10, 1)}", - ".foo{transform:translate3d(100px,100px,10px)rotateX(45deg)}" - ); - minify_test( - ".foo{transform:translate3d(100px, 200px, 10px) translate(100px, 100px)}", - ".foo{transform:translate3d(200px,300px,10px)}", - ); - minify_test( - ".foo{transform:rotate(45deg) rotate(45deg)}", - ".foo{transform:rotate(90deg)}", - ); - minify_test( - ".foo{transform:matrix(0.7071067811865476, 0.7071067811865475, -0.7071067811865475, 0.7071067811865476, 100, 100)}", - ".foo{transform:translate(100px,100px)rotate(45deg)}" - ); - minify_test( - ".foo{transform:translateX(2in) translateX(50px)}", - ".foo{transform:translate(242px)}", - ); + // TODO: Re-enable with a better solution + // See: https://github.com/parcel-bundler/lightningcss/issues/288 + // minify_test( + // ".foo{transform:translate3d(100px, 100px, 10px) skew(10deg) scale3d(2, 3, 4)}", + // ".foo{transform:matrix3d(2,0,0,0,.528981,3,0,0,0,0,4,0,100,100,10,1)}", + // ); + // minify_test( + // ".foo{transform:matrix3d(0.804737854124365, 0.5058793634016805, -0.31061721752604554, 0, -0.31061721752604554, 0.804737854124365, 0.5058793634016805, 0, 0.5058793634016805, -0.31061721752604554, 0.804737854124365, 0, 100, 100, 10, 1)}", + // ".foo{transform:translate3d(100px,100px,10px)rotate3d(1,1,1,45deg)}" + // ); + // minify_test( + // ".foo{transform:matrix3d(1, 0, 0, 0, 0, 0.7071067811865476, 0.7071067811865475, 0, 0, -0.7071067811865475, 0.7071067811865476, 0, 100, 100, 10, 1)}", + // ".foo{transform:translate3d(100px,100px,10px)rotateX(45deg)}" + // ); + // minify_test( + // ".foo{transform:translate3d(100px, 200px, 10px) translate(100px, 100px)}", + // ".foo{transform:translate3d(200px,300px,10px)}", + // ); + // minify_test( + // ".foo{transform:rotate(45deg) rotate(45deg)}", + // ".foo{transform:rotate(90deg)}", + // ); + // minify_test( + // ".foo{transform:matrix(0.7071067811865476, 0.7071067811865475, -0.7071067811865475, 0.7071067811865476, 100, 100)}", + // ".foo{transform:translate(100px,100px)rotate(45deg)}" + // ); + // minify_test( + // ".foo{transform:translateX(2in) translateX(50px)}", + // ".foo{transform:translate(242px)}", + // ); minify_test( ".foo{transform:translateX(calc(2in + 50px))}", ".foo{transform:translate(242px)}", @@ -11660,7 +11664,9 @@ mod tests { minify_test(".foo { scale: 1 0 1 }", ".foo{scale:1 0}"); minify_test(".foo { scale: 1 0 0 }", ".foo{scale:1 0 0}"); - minify_test(".foo { transform: scale(3); scale: 0.5 }", ".foo{transform:scale(1.5)}"); + // TODO: Re-enable with a better solution + // See: https://github.com/parcel-bundler/lightningcss/issues/288 + // minify_test(".foo { transform: scale(3); scale: 0.5 }", ".foo{transform:scale(1.5)}"); minify_test(".foo { scale: 0.5; transform: scale(3); }", ".foo{transform:scale(3)}"); prefix_test( @@ -26076,13 +26082,15 @@ mod tests { "@property --property-name{syntax:\"custom|\";inherits:false;initial-value:#ff0}", ); - minify_test(r#" - @property --property-name { - syntax: ''; - inherits: false; - initial-value: translate(200px,300px) translate(100px,200px) scale(2); - } - "#, "@property --property-name{syntax:\"\";inherits:false;initial-value:matrix(2,0,0,2,300,500)}"); + // TODO: Re-enable with a better solution + // See: https://github.com/parcel-bundler/lightningcss/issues/288 + // minify_test(r#" + // @property --property-name { + // syntax: ''; + // inherits: false; + // initial-value: translate(200px,300px) translate(100px,200px) scale(2); + // } + // "#, "@property --property-name{syntax:\"\";inherits:false;initial-value:matrix(2,0,0,2,300,500)}"); minify_test( r#" diff --git a/src/properties/transform.rs b/src/properties/transform.rs index 87699837..75cd909a 100644 --- a/src/properties/transform.rs +++ b/src/properties/transform.rs @@ -57,64 +57,80 @@ impl ToCss for TransformList { return Ok(()); } + // TODO: Re-enable with a better solution + // See: https://github.com/parcel-bundler/lightningcss/issues/288 if dest.minify { - // Combine transforms into a single matrix. - if let Some(matrix) = self.to_matrix() { - // Generate based on the original transforms. - let mut base = String::new(); - self.to_css_base(&mut Printer::new( - &mut base, - PrinterOptions { - minify: true, - ..PrinterOptions::default() - }, - ))?; - - // Decompose the matrix into transform functions if possible. - // If the resulting length is shorter than the original, use it. - if let Some(d) = matrix.decompose() { - let mut decomposed = String::new(); - d.to_css_base(&mut Printer::new( - &mut decomposed, - PrinterOptions { - minify: true, - ..PrinterOptions::default() - }, - ))?; - if decomposed.len() < base.len() { - base = decomposed; - } - } - - // Also generate a matrix() or matrix3d() representation and compare that. - let mut mat = String::new(); - if let Some(matrix) = matrix.to_matrix2d() { - Transform::Matrix(matrix).to_css(&mut Printer::new( - &mut mat, - PrinterOptions { - minify: true, - ..PrinterOptions::default() - }, - ))? - } else { - Transform::Matrix3d(matrix).to_css(&mut Printer::new( - &mut mat, - PrinterOptions { - minify: true, - ..PrinterOptions::default() - }, - ))? - } + let mut base = String::new(); + self.to_css_base(&mut Printer::new( + &mut base, + PrinterOptions { + minify: true, + ..PrinterOptions::default() + }, + ))?; - if mat.len() < base.len() { - dest.write_str(&mat)?; - } else { - dest.write_str(&base)?; - } + dest.write_str(&base)?; - return Ok(()); - } + return Ok(()); } + // if dest.minify { + // // Combine transforms into a single matrix. + // if let Some(matrix) = self.to_matrix() { + // // Generate based on the original transforms. + // let mut base = String::new(); + // self.to_css_base(&mut Printer::new( + // &mut base, + // PrinterOptions { + // minify: true, + // ..PrinterOptions::default() + // }, + // ))?; + // + // // Decompose the matrix into transform functions if possible. + // // If the resulting length is shorter than the original, use it. + // if let Some(d) = matrix.decompose() { + // let mut decomposed = String::new(); + // d.to_css_base(&mut Printer::new( + // &mut decomposed, + // PrinterOptions { + // minify: true, + // ..PrinterOptions::default() + // }, + // ))?; + // if decomposed.len() < base.len() { + // base = decomposed; + // } + // } + // + // // Also generate a matrix() or matrix3d() representation and compare that. + // let mut mat = String::new(); + // if let Some(matrix) = matrix.to_matrix2d() { + // Transform::Matrix(matrix).to_css(&mut Printer::new( + // &mut mat, + // PrinterOptions { + // minify: true, + // ..PrinterOptions::default() + // }, + // ))? + // } else { + // Transform::Matrix3d(matrix).to_css(&mut Printer::new( + // &mut mat, + // PrinterOptions { + // minify: true, + // ..PrinterOptions::default() + // }, + // ))? + // } + // + // if mat.len() < base.len() { + // dest.write_str(&mat)?; + // } else { + // dest.write_str(&base)?; + // } + // + // return Ok(()); + // } + // } self.to_css_base(dest) } From 6bd2761badb9d5434783acffcae35ef6c3311e06 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Fri, 15 Mar 2024 00:02:34 -0400 Subject: [PATCH 003/156] Merge @supports declarations with the same property (minus prefix) and value --- src/lib.rs | 62 +++++++++++++++++++++++++++++++++++++++++++ src/properties/mod.rs | 22 ++++++++++++++- src/rules/supports.rs | 33 +++++++++++++++++++++-- 3 files changed, 114 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8c70f8ad..f13b7c48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13123,6 +13123,68 @@ mod tests { ..Default::default() }, ); + prefix_test( + r#" + @supports ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) { + div { + backdrop-filter: blur(10px); + } + } + "#, + indoc! { r#" + @supports ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) { + div { + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); + } + } + "#}, + Browsers { + safari: Some(14 << 16), + ..Default::default() + }, + ); + prefix_test( + r#" + @supports ((-webkit-backdrop-filter: blur(20px)) or (backdrop-filter: blur(10px))) { + div { + backdrop-filter: blur(10px); + } + } + "#, + indoc! { r#" + @supports ((-webkit-backdrop-filter: blur(20px))) or ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) { + div { + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); + } + } + "#}, + Browsers { + safari: Some(14 << 16), + ..Default::default() + }, + ); + prefix_test( + r#" + @supports ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) { + div { + backdrop-filter: blur(10px); + } + } + "#, + indoc! { r#" + @supports (backdrop-filter: blur(10px)) { + div { + backdrop-filter: blur(10px); + } + } + "#}, + Browsers { + chrome: Some(80 << 16), + ..Default::default() + }, + ); minify_test( r#" @supports (width: calc(10px * 2)) { diff --git a/src/properties/mod.rs b/src/properties/mod.rs index 6e39fc5d..845140f9 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -322,7 +322,7 @@ macro_rules! define_properties { } } - fn with_prefix(&self, prefix: VendorPrefix) -> PropertyId<'i> { + pub(crate) fn with_prefix(&self, prefix: VendorPrefix) -> PropertyId<'i> { use PropertyId::*; match self { $( @@ -344,6 +344,26 @@ macro_rules! define_properties { } } + pub(crate) fn add_prefix(&mut self, prefix: VendorPrefix) { + use PropertyId::*; + match self { + $( + $(#[$meta])* + $property$((vp_name!($vp, p)))? => { + macro_rules! get_prefixed { + ($v: ty) => {{ + *p |= prefix; + }}; + () => {{}}; + } + + get_prefixed!($($vp)?) + }, + )+ + _ => {} + } + } + pub(crate) fn set_prefixes_for_targets(&mut self, targets: Targets) { match self { $( diff --git a/src/rules/supports.rs b/src/rules/supports.rs index 68c13e6a..04f1fdd3 100644 --- a/src/rules/supports.rs +++ b/src/rules/supports.rs @@ -1,5 +1,7 @@ //! The `@supports` rule. +use std::collections::HashMap; + use super::Location; use super::{CssRuleList, MinifyContext}; use crate::error::{MinifyError, ParserError, PrinterError}; @@ -159,6 +161,7 @@ impl<'i> Parse<'i> for SupportsCondition<'i> { let in_parens = Self::parse_in_parens(input)?; let mut expected_type = None; let mut conditions = Vec::new(); + let mut seen_declarations = HashMap::new(); loop { let condition = input.try_parse(|input| { @@ -185,14 +188,40 @@ impl<'i> Parse<'i> for SupportsCondition<'i> { if let Ok(condition) = condition { if conditions.is_empty() { - conditions.push(in_parens.clone()) + conditions.push(in_parens.clone()); + if let SupportsCondition::Declaration { property_id, value } = &in_parens { + seen_declarations.insert((property_id.with_prefix(VendorPrefix::None), value.clone()), 0); + } + } + + if let SupportsCondition::Declaration { property_id, value } = condition { + // Merge multiple declarations with the same property id (minus prefix) and value together. + let property_id = property_id.with_prefix(VendorPrefix::None); + let key = (property_id.clone(), value.clone()); + if let Some(index) = seen_declarations.get(&key) { + if let SupportsCondition::Declaration { + property_id: cur_property, + .. + } = &mut conditions[*index] + { + cur_property.add_prefix(property_id.prefix()); + } + } else { + seen_declarations.insert(key, conditions.len()); + conditions.push(SupportsCondition::Declaration { property_id, value }); + } + } else { + conditions.push(condition); } - conditions.push(condition) } else { break; } } + if conditions.len() == 1 { + return Ok(conditions.pop().unwrap()); + } + match expected_type { Some(1) => Ok(SupportsCondition::And(conditions)), Some(2) => Ok(SupportsCondition::Or(conditions)), From baa1a2b7fa52eeb3827f8edcc2e14de33cd69ad0 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Fri, 15 Mar 2024 00:05:17 -0400 Subject: [PATCH 004/156] v1.24.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4e19974..db6c59ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -760,7 +760,7 @@ dependencies = [ [[package]] name = "lightningcss" -version = "1.0.0-alpha.54" +version = "1.0.0-alpha.55" dependencies = [ "ahash 0.8.7", "assert_cmd", diff --git a/Cargo.toml b/Cargo.toml index 6806ca01..4e27dbf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ members = [ [package] authors = ["Devon Govett "] name = "lightningcss" -version = "1.0.0-alpha.54" +version = "1.0.0-alpha.55" description = "A CSS parser, transformer, and minifier" license = "MPL-2.0" edition = "2021" diff --git a/package.json b/package.json index d8fcb832..c9fe3d72 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lightningcss", - "version": "1.24.0", + "version": "1.24.1", "license": "MPL-2.0", "description": "A CSS parser, transformer, and minifier written in Rust", "main": "node/index.js", From d7aeff3db67ee9d15e0fefce9251cf41c0b8ec44 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 13 May 2024 19:19:35 -0700 Subject: [PATCH 005/156] optimize "all" property --- Cargo.lock | 7 ++-- Cargo.toml | 1 + node/ast.d.ts | 34 +++++++++++++++- node/test/visitor.test.mjs | 4 +- src/declaration.rs | 66 +++++++++++++++++++++++++------- src/lib.rs | 41 ++++++++++++++++---- src/properties/mod.rs | 48 ++++++++++++++++++++++- src/properties/prefix_handler.rs | 6 --- src/properties/text.rs | 28 ++++++++++++++ 9 files changed, 200 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db6c59ae..7358e44c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -153,7 +153,7 @@ dependencies = [ "anyhow", "chrono", "either", - "indexmap 2.2.2", + "indexmap 2.2.6", "itertools 0.12.1", "nom", "once_cell", @@ -658,9 +658,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.2" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.2", @@ -775,6 +775,7 @@ dependencies = [ "dashmap", "data-encoding", "getrandom", + "indexmap 2.2.6", "indoc", "itertools 0.10.5", "jemallocator", diff --git a/Cargo.toml b/Cargo.toml index 4e27dbf5..795b25ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,7 @@ const-str = "0.3.1" pathdiff = "0.2.1" ahash = "0.8.7" paste = "1.0.12" +indexmap = "2.2.6" # CLI deps atty = { version = "0.2", optional = true } clap = { version = "3.0.6", features = ["derive"], optional = true } diff --git a/node/ast.d.ts b/node/ast.d.ts index 25c9dd7f..bd6e0e98 100644 --- a/node/ast.d.ts +++ b/node/ast.d.ts @@ -2066,6 +2066,12 @@ export type PropertyId = property: "text-size-adjust"; vendorPrefix: VendorPrefix; } + | { + property: "direction"; + } + | { + property: "unicode-bidi"; + } | { property: "box-decoration-break"; vendorPrefix: VendorPrefix; @@ -3445,6 +3451,14 @@ export type Declaration = value: TextSizeAdjust; vendorPrefix: VendorPrefix; } + | { + property: "direction"; + value: Direction2; + } + | { + property: "unicode-bidi"; + value: UnicodeBidi; + } | { property: "box-decoration-break"; value: BoxDecorationBreak; @@ -3757,6 +3771,10 @@ export type Declaration = property: "color-scheme"; value: ColorScheme; } + | { + property: "all"; + value: CSSWideKeyword; + } | { property: "unparsed"; value: UnparsedProperty; @@ -5729,6 +5747,14 @@ export type TextSizeAdjust = type: "percentage"; value: number; }; +/** + * A value for the [direction](https://drafts.csswg.org/css-writing-modes-3/#direction) property. + */ +export type Direction2 = "ltr" | "rtl"; +/** + * A value for the [unicode-bidi](https://drafts.csswg.org/css-writing-modes-3/#unicode-bidi) property. + */ +export type UnicodeBidi = "normal" | "embed" | "isolate" | "bidi-override" | "isolate-override" | "plaintext"; /** * A value for the [box-decoration-break](https://www.w3.org/TR/css-break-3/#break-decoration) property. */ @@ -6234,6 +6260,10 @@ export type ContainerNameList = type: "names"; value: String[]; }; +/** + * A [CSS-wide keyword](https://drafts.csswg.org/css-cascade-5/#defaulting-keywords). + */ +export type CSSWideKeyword = "initial" | "inherit" | "unset" | "revert" | "revert-layer"; /** * A CSS custom property name. */ @@ -7040,8 +7070,8 @@ export type ParsedComponent = }; } | { - type: "token"; - value: Token; + type: "token-list"; + value: TokenOrValue[]; }; /** * A [multiplier](https://drafts.css-houdini.org/css-properties-values-api/#multipliers) for a [SyntaxComponent](SyntaxComponent). Indicates whether and how the component may be repeated. diff --git a/node/test/visitor.test.mjs b/node/test/visitor.test.mjs index 3a42a696..87ee208d 100644 --- a/node/test/visitor.test.mjs +++ b/node/test/visitor.test.mjs @@ -116,7 +116,7 @@ test('custom units', () => { } }); - assert.equal(res.code.toString(), '.foo{--step:.25rem;font-size:calc(3*var(--step))}'); + assert.equal(res.code.toString(), '.foo{font-size:calc(3*var(--step));--step:.25rem}'); }); test('design tokens', () => { @@ -822,7 +822,7 @@ test('dashed idents', () => { } }); - assert.equal(res.code.toString(), '.foo{--prefix-foo:#ff0;color:var(--prefix-foo)}'); + assert.equal(res.code.toString(), '.foo{color:var(--prefix-foo);--prefix-foo:#ff0}'); }); test('custom idents', () => { diff --git a/src/declaration.rs b/src/declaration.rs index 556fd152..501e2f8c 100644 --- a/src/declaration.rs +++ b/src/declaration.rs @@ -1,7 +1,6 @@ //! CSS declarations. use std::borrow::Cow; -use std::collections::HashMap; use std::ops::Range; use crate::context::{DeclarationContext, PropertyHandlerContext}; @@ -11,6 +10,7 @@ use crate::printer::Printer; use crate::properties::box_shadow::BoxShadowHandler; use crate::properties::custom::{CustomProperty, CustomPropertyName}; use crate::properties::masking::MaskHandler; +use crate::properties::text::{Direction, UnicodeBidi}; use crate::properties::{ align::AlignHandler, animation::AnimationHandler, @@ -33,13 +33,14 @@ use crate::properties::{ transition::TransitionHandler, ui::ColorSchemeHandler, }; -use crate::properties::{Property, PropertyId}; +use crate::properties::{CSSWideKeyword, Property, PropertyId}; use crate::traits::{PropertyHandler, ToCss}; use crate::values::ident::DashedIdent; use crate::values::string::CowArcStr; #[cfg(feature = "visitor")] use crate::visitor::Visit; use cssparser::*; +use indexmap::IndexMap; /// A CSS declaration block. /// @@ -515,7 +516,10 @@ pub(crate) struct DeclarationHandler<'i> { color_scheme: ColorSchemeHandler, fallback: FallbackHandler, prefix: PrefixHandler, - custom_properties: HashMap<'i>, usize>, + all: Option, + direction: Option, + unicode_bidi: Option, + custom_properties: IndexMap<'i>, CustomProperty<'i>>, decls: DeclarationList<'i>, } @@ -552,6 +556,7 @@ impl<'i> DeclarationHandler<'i> { || self.color_scheme.handle_property(property, &mut self.decls, context) || self.fallback.handle_property(property, &mut self.decls, context) || self.prefix.handle_property(property, &mut self.decls, context) + || self.handle_all(property) || self.handle_custom_property(property, context) } @@ -566,18 +571,13 @@ impl<'i> DeclarationHandler<'i> { } if let CustomPropertyName::Custom(name) = &custom.name { - if let Some(index) = self.custom_properties.get(name) { - if self.decls[*index] == *property { + if let Some(prev) = self.custom_properties.get_mut(name) { + if prev.value == custom.value { return true; } - let mut custom = custom.clone(); - self.add_conditional_fallbacks(&mut custom, context); - self.decls[*index] = Property::Custom(custom); + *prev = custom.clone(); } else { - self.custom_properties.insert(name.clone(), self.decls.len()); - let mut custom = custom.clone(); - self.add_conditional_fallbacks(&mut custom, context); - self.decls.push(Property::Custom(custom)); + self.custom_properties.insert(name.clone(), custom.clone()); } return true; @@ -587,6 +587,32 @@ impl<'i> DeclarationHandler<'i> { false } + fn handle_all(&mut self, property: &Property<'i>) -> bool { + // The `all` property resets all properies except `unicode-bidi`, `direction`, and custom properties. + // https://drafts.csswg.org/css-cascade-5/#all-shorthand + match property { + Property::UnicodeBidi(bidi) => { + self.unicode_bidi = Some(*bidi); + true + } + Property::Direction(direction) => { + self.direction = Some(*direction); + true + } + Property::All(keyword) => { + *self = DeclarationHandler { + custom_properties: std::mem::take(&mut self.custom_properties), + unicode_bidi: self.unicode_bidi.clone(), + direction: self.direction.clone(), + all: Some(keyword.clone()), + ..Default::default() + }; + true + } + _ => false, + } + } + fn add_conditional_fallbacks( &self, custom: &mut CustomProperty<'i>, @@ -607,6 +633,21 @@ impl<'i> DeclarationHandler<'i> { } pub fn finalize(&mut self, context: &mut PropertyHandlerContext<'i, '_>) { + // Always place the `all` property first. Previous properties will have been omitted. + if let Some(all) = std::mem::take(&mut self.all) { + self.decls.push(Property::All(all)); + } + if let Some(direction) = std::mem::take(&mut self.direction) { + self.decls.push(Property::Direction(direction)); + } + if let Some(unicode_bidi) = std::mem::take(&mut self.unicode_bidi) { + self.decls.push(Property::UnicodeBidi(unicode_bidi)); + } + for (_, mut value) in std::mem::take(&mut self.custom_properties) { + self.add_conditional_fallbacks(&mut value, context); + self.decls.push(Property::Custom(value)); + } + self.background.finalize(&mut self.decls, context); self.border.finalize(&mut self.decls, context); self.outline.finalize(&mut self.decls, context); @@ -634,6 +675,5 @@ impl<'i> DeclarationHandler<'i> { self.color_scheme.finalize(&mut self.decls, context); self.fallback.finalize(&mut self.decls, context); self.prefix.finalize(&mut self.decls, context); - self.custom_properties.clear(); } } diff --git a/src/lib.rs b/src/lib.rs index f13b7c48..dd3b0fe8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21435,26 +21435,26 @@ mod tests { indoc! {r#" @keyframes foo { from { - --custom: #ff0; opacity: 0; + --custom: #ff0; } to { - --custom: #ee00be; opacity: 1; + --custom: #ee00be; } } @supports (color: lab(0% 0 0)) { @keyframes foo { from { - --custom: #ff0; opacity: 0; + --custom: #ff0; } to { - --custom: lab(50.998% 125.506 -50.7078); opacity: 1; + --custom: lab(50.998% 125.506 -50.7078); } } } @@ -23457,8 +23457,8 @@ mod tests { } .EgL3uq_foo { - --foo: red; color: var(--foo); + --foo: red; } "#}, map! { @@ -23507,10 +23507,10 @@ mod tests { } .EgL3uq_foo { - --EgL3uq_foo: red; - --EgL3uq_bar: green; color: var(--EgL3uq_foo); font-palette: --EgL3uq_Cooler; + --EgL3uq_foo: red; + --EgL3uq_bar: green; } .EgL3uq_bar { @@ -27244,4 +27244,31 @@ mod tests { }, ); } + + #[test] + fn test_all() { + minify_test(".foo { all: initial; all: initial }", ".foo{all:initial}"); + minify_test(".foo { all: initial; all: revert }", ".foo{all:revert}"); + minify_test(".foo { background: red; all: revert-layer }", ".foo{all:revert-layer}"); + minify_test( + ".foo { background: red; all: revert-layer; background: green }", + ".foo{all:revert-layer;background:green}", + ); + minify_test( + ".foo { --test: red; all: revert-layer }", + ".foo{all:revert-layer;--test:red}", + ); + minify_test( + ".foo { unicode-bidi: embed; all: revert-layer }", + ".foo{all:revert-layer;unicode-bidi:embed}", + ); + minify_test( + ".foo { direction: rtl; all: revert-layer }", + ".foo{all:revert-layer;direction:rtl}", + ); + minify_test( + ".foo { direction: rtl; all: revert-layer; direction: ltr }", + ".foo{all:revert-layer;direction:ltr}", + ); + } } diff --git a/src/properties/mod.rs b/src/properties/mod.rs index 845140f9..98756755 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -123,6 +123,7 @@ pub mod ui; use crate::declaration::DeclarationBlock; use crate::error::{ParserError, PrinterError}; use crate::logical::{LogicalGroup, PropertyCategory}; +use crate::macros::enum_property; use crate::parser::starts_with_ignore_ascii_case; use crate::parser::ParserOptions; use crate::prefixes::Feature; @@ -688,6 +689,8 @@ macro_rules! define_properties { $(#[$meta])* $property($type, $($vp)?), )+ + /// The [all](https://drafts.csswg.org/css-cascade-5/#all-shorthand) shorthand property. + All(CSSWideKeyword), /// An unparsed property. Unparsed(UnparsedProperty<'i>), /// A custom or unknown property. @@ -710,6 +713,7 @@ macro_rules! define_properties { } }, )+ + PropertyId::All => return Ok(Property::All(CSSWideKeyword::parse(input)?)), PropertyId::Custom(name) => return Ok(Property::Custom(CustomProperty::parse(name, input, options)?)), _ => {} }; @@ -731,6 +735,7 @@ macro_rules! define_properties { $(#[$meta])* $property(_, $(vp_name!($vp, p))?) => PropertyId::$property$((*vp_name!($vp, p)))?, )+ + All(_) => PropertyId::All, Unparsed(unparsed) => unparsed.property_id.clone(), Custom(custom) => PropertyId::Custom(custom.name.clone()) } @@ -779,6 +784,7 @@ macro_rules! define_properties { val.to_css(dest) } )+ + All(keyword) => keyword.to_css(dest), Unparsed(unparsed) => { unparsed.value.to_css(dest, false) } @@ -838,6 +844,7 @@ macro_rules! define_properties { ($name, get_prefix!($($vp)?)) }, )+ + All(_) => ("all", VendorPrefix::None), Unparsed(unparsed) => { let mut prefix = unparsed.property_id.prefix(); if prefix.is_empty() { @@ -966,7 +973,10 @@ macro_rules! define_properties { s.serialize_field("value", value)?; } )+ - _ => unreachable!() + All(value) => { + s.serialize_field("value", value)?; + } + Unparsed(_) | Custom(_) => unreachable!() } s.end() @@ -1072,7 +1082,10 @@ macro_rules! define_properties { Ok(Property::Custom(value)) } } - PropertyId::All => unreachable!() + PropertyId::All => { + let value = CSSWideKeyword::deserialize(deserializer)?; + Ok(Property::All(value)) + } } } } @@ -1134,6 +1147,17 @@ macro_rules! define_properties { with_prefix!($($vp)?) }, )+ + { + property!("all"); + #[derive(schemars::JsonSchema)] + struct T { + #[schemars(rename = "property", schema_with = "property")] + _property: u8, + #[schemars(rename = "value")] + _value: CSSWideKeyword + } + T::json_schema(gen) + }, { property!("unparsed"); @@ -1513,6 +1537,10 @@ define_properties! { // https://w3c.github.io/csswg-drafts/css-size-adjust/ "text-size-adjust": TextSizeAdjust(TextSizeAdjust, VendorPrefix) / WebKit / Moz / Ms, + // https://drafts.csswg.org/css-writing-modes-3/ + "direction": Direction(Direction), + "unicode-bidi": UnicodeBidi(UnicodeBidi), + // https://www.w3.org/TR/css-break-3/ "box-decoration-break": BoxDecorationBreak(BoxDecorationBreak, VendorPrefix) / WebKit, @@ -1667,3 +1695,19 @@ impl ToCss for Vec { Ok(()) } } + +enum_property! { + /// A [CSS-wide keyword](https://drafts.csswg.org/css-cascade-5/#defaulting-keywords). + pub enum CSSWideKeyword { + /// The property's initial value. + "initial": Initial, + /// The property's computed value on the parent element. + "inherit": Inherit, + /// Either inherit or initial depending on whether the property is inherited. + "unset": Unset, + /// Rolls back the cascade to the cascaded value of the earlier origin. + "revert": Revert, + /// Rolls back the cascade to the value of the previous cascade layer. + "revert-layer": RevertLayer, + } +} diff --git a/src/properties/prefix_handler.rs b/src/properties/prefix_handler.rs index 78dd7d09..7ad2dfe8 100644 --- a/src/properties/prefix_handler.rs +++ b/src/properties/prefix_handler.rs @@ -141,12 +141,6 @@ macro_rules! define_fallbacks { (val, paste::paste! { &mut self.[<$name:snake>] }) } )+ - PropertyId::All => { - let mut unparsed = val.clone(); - context.add_unparsed_fallbacks(&mut unparsed); - dest.push(Property::Unparsed(unparsed)); - return true - }, _ => return false }; diff --git a/src/properties/text.rs b/src/properties/text.rs index 4c7b6fde..e22f6fab 100644 --- a/src/properties/text.rs +++ b/src/properties/text.rs @@ -1599,3 +1599,31 @@ impl FallbackValues for SmallVec<[TextShadow; 1]> { res } } + +enum_property! { + /// A value for the [direction](https://drafts.csswg.org/css-writing-modes-3/#direction) property. + pub enum Direction { + /// This value sets inline base direction (bidi directionality) to line-left-to-line-right. + Ltr, + /// This value sets inline base direction (bidi directionality) to line-right-to-line-left. + Rtl, + } +} + +enum_property! { + /// A value for the [unicode-bidi](https://drafts.csswg.org/css-writing-modes-3/#unicode-bidi) property. + pub enum UnicodeBidi { + /// The box does not open an additional level of embedding. + "normal": Normal, + /// If the box is inline, this value creates a directional embedding by opening an additional level of embedding. + "embed": Embed, + /// On an inline box, this bidi-isolates its contents. + "isolate": Isolate, + /// This value puts the box’s immediate inline content in a directional override. + "bidi-override": BidiOverride, + /// This combines the isolation behavior of isolate with the directional override behavior of bidi-override. + "isolate-override": IsolateOverride, + /// This value behaves as isolate except that the base directionality is determined using a heuristic rather than the direction property. + "plaintext": Plaintext, + } +} From a4cc0246d28c364c0f4e16552685bd911444bdc5 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 14 May 2024 04:51:41 +0200 Subject: [PATCH 006/156] Prevent simplifying `translate: none;` and `scale: none;` (#703) --- node/ast.d.ts | 78 ++++++++++++----------- src/lib.rs | 4 +- src/properties/transform.rs | 119 +++++++++++++++++++++++------------- 3 files changed, 120 insertions(+), 81 deletions(-) diff --git a/node/ast.d.ts b/node/ast.d.ts index bd6e0e98..11e92001 100644 --- a/node/ast.d.ts +++ b/node/ast.d.ts @@ -1,4 +1,4 @@ -/* tslint:disable */ +/* eslint-disable */ /** * This file was automatically generated by json-schema-to-typescript. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, @@ -5615,6 +5615,48 @@ export type Perspective = type: "length"; value: Length; }; +/** + * A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property. + */ +export type Translate = + | "None" + | { + XYZ: { + /** + * The x translation. + */ + x: DimensionPercentageFor_LengthValue; + /** + * The y translation. + */ + y: DimensionPercentageFor_LengthValue; + /** + * The z translation. + */ + z: Length; + }; + }; +/** + * A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property. + */ +export type Scale = + | "None" + | { + XYZ: { + /** + * Scale on the x axis. + */ + x: NumberOrPercentage; + /** + * Scale on the y axis. + */ + y: NumberOrPercentage; + /** + * Scale on the z axis. + */ + z: NumberOrPercentage; + }; + }; /** * Defines how text case should be transformed in the [text-transform](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-transform-property) property. */ @@ -8519,23 +8561,6 @@ export interface Matrix3DForFloat { m43: number; m44: number; } -/** - * A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property. - */ -export interface Translate { - /** - * The x translation. - */ - x: DimensionPercentageFor_LengthValue; - /** - * The y translation. - */ - y: DimensionPercentageFor_LengthValue; - /** - * The z translation. - */ - z: Length; -} /** * A value for the [rotate](https://drafts.csswg.org/css-transforms-2/#propdef-rotate) property. */ @@ -8557,23 +8582,6 @@ export interface Rotate { */ z: number; } -/** - * A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property. - */ -export interface Scale { - /** - * Scale on the x axis. - */ - x: NumberOrPercentage; - /** - * Scale on the y axis. - */ - y: NumberOrPercentage; - /** - * Scale on the z axis. - */ - z: NumberOrPercentage; -} /** * A value for the [text-transform](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-transform-property) property. */ diff --git a/src/lib.rs b/src/lib.rs index dd3b0fe8..b63e3ec8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11645,7 +11645,7 @@ mod tests { minify_test(".foo { translate: 1px 0px 0px }", ".foo{translate:1px}"); minify_test(".foo { translate: 1px 2px 0px }", ".foo{translate:1px 2px}"); minify_test(".foo { translate: 1px 0px 2px }", ".foo{translate:1px 0 2px}"); - minify_test(".foo { translate: none }", ".foo{translate:0}"); + minify_test(".foo { translate: none }", ".foo{translate:none}"); minify_test(".foo { rotate: 10deg }", ".foo{rotate:10deg}"); minify_test(".foo { rotate: z 10deg }", ".foo{rotate:10deg}"); minify_test(".foo { rotate: 0 0 1 10deg }", ".foo{rotate:10deg}"); @@ -11659,7 +11659,7 @@ mod tests { minify_test(".foo { scale: 1 }", ".foo{scale:1}"); minify_test(".foo { scale: 1 1 }", ".foo{scale:1}"); minify_test(".foo { scale: 1 1 1 }", ".foo{scale:1}"); - minify_test(".foo { scale: none }", ".foo{scale:1}"); + minify_test(".foo { scale: none }", ".foo{scale:none}"); minify_test(".foo { scale: 1 0 }", ".foo{scale:1 0}"); minify_test(".foo { scale: 1 0 1 }", ".foo{scale:1 0}"); minify_test(".foo { scale: 1 0 0 }", ".foo{scale:1 0 0}"); diff --git a/src/properties/transform.rs b/src/properties/transform.rs index 75cd909a..ae390ae3 100644 --- a/src/properties/transform.rs +++ b/src/properties/transform.rs @@ -1457,23 +1457,25 @@ impl ToCss for Perspective { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] -pub struct Translate { - /// The x translation. - pub x: LengthPercentage, - /// The y translation. - pub y: LengthPercentage, - /// The z translation. - pub z: Length, +pub enum Translate { + /// The "none" keyword. + None, + + /// The x, y, and z translations. + XYZ { + /// The x translation. + x: LengthPercentage, + /// The y translation. + y: LengthPercentage, + /// The z translation. + z: Length, + }, } impl<'i> Parse<'i> for Translate { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(Translate { - x: LengthPercentage::zero(), - y: LengthPercentage::zero(), - z: Length::zero(), - }); + return Ok(Translate::None); } let x = LengthPercentage::parse(input)?; @@ -1484,7 +1486,7 @@ impl<'i> Parse<'i> for Translate { None }; - Ok(Translate { + Ok(Translate::XYZ { x, y: y.unwrap_or(LengthPercentage::zero()), z: z.unwrap_or(Length::zero()), @@ -1497,15 +1499,23 @@ impl ToCss for Translate { where W: std::fmt::Write, { - self.x.to_css(dest)?; - if !self.y.is_zero() || !self.z.is_zero() { - dest.write_char(' ')?; - self.y.to_css(dest)?; - if !self.z.is_zero() { - dest.write_char(' ')?; - self.z.to_css(dest)?; + match self { + Translate::None => { + dest.write_str("none")?; } - } + Translate::XYZ { x, y, z } => { + x.to_css(dest)?; + if !y.is_zero() || !z.is_zero() { + dest.write_char(' ')?; + y.to_css(dest)?; + if !z.is_zero() { + dest.write_char(' ')?; + z.to_css(dest)?; + } + } + } + }; + Ok(()) } } @@ -1513,7 +1523,12 @@ impl ToCss for Translate { impl Translate { /// Converts the translation to a transform function. pub fn to_transform(&self) -> Transform { - Transform::Translate3d(self.x.clone(), self.y.clone(), self.z.clone()) + match self { + Translate::None => { + Transform::Translate3d(LengthPercentage::zero(), LengthPercentage::zero(), Length::zero()) + } + Translate::XYZ { x, y, z } => Transform::Translate3d(x.clone(), y.clone(), z.clone()), + } } } @@ -1610,23 +1625,25 @@ impl Rotate { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] -pub struct Scale { - /// Scale on the x axis. - pub x: NumberOrPercentage, - /// Scale on the y axis. - pub y: NumberOrPercentage, - /// Scale on the z axis. - pub z: NumberOrPercentage, +pub enum Scale { + /// The "none" keyword. + None, + + /// Scale on the x, y, and z axis. + XYZ { + /// Scale on the x axis. + x: NumberOrPercentage, + /// Scale on the y axis. + y: NumberOrPercentage, + /// Scale on the z axis. + z: NumberOrPercentage, + }, } impl<'i> Parse<'i> for Scale { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(Scale { - x: NumberOrPercentage::Number(1.0), - y: NumberOrPercentage::Number(1.0), - z: NumberOrPercentage::Number(1.0), - }); + return Ok(Scale::None); } let x = NumberOrPercentage::parse(input)?; @@ -1637,7 +1654,7 @@ impl<'i> Parse<'i> for Scale { None }; - Ok(Scale { + Ok(Scale::XYZ { x: x.clone(), y: y.unwrap_or(x), z: z.unwrap_or(NumberOrPercentage::Number(1.0)), @@ -1650,14 +1667,21 @@ impl ToCss for Scale { where W: std::fmt::Write, { - self.x.to_css(dest)?; - let zv: f32 = (&self.z).into(); - if self.y != self.x || zv != 1.0 { - dest.write_char(' ')?; - self.y.to_css(dest)?; - if zv != 1.0 { - dest.write_char(' ')?; - self.z.to_css(dest)?; + match self { + Scale::None => { + dest.write_str("none")?; + } + Scale::XYZ { x, y, z } => { + x.to_css(dest)?; + let zv: f32 = z.into(); + if y != x || zv != 1.0 { + dest.write_char(' ')?; + y.to_css(dest)?; + if zv != 1.0 { + dest.write_char(' ')?; + z.to_css(dest)?; + } + } } } @@ -1668,7 +1692,14 @@ impl ToCss for Scale { impl Scale { /// Converts the scale to a transform function. pub fn to_transform(&self) -> Transform { - Transform::Scale3d(self.x.clone(), self.y.clone(), self.z.clone()) + match self { + Scale::None => Transform::Scale3d( + NumberOrPercentage::Number(1.0), + NumberOrPercentage::Number(1.0), + NumberOrPercentage::Number(1.0), + ), + Scale::XYZ { x, y, z } => Transform::Scale3d(x.clone(), y.clone(), z.clone()), + } } } From 029c88ee04e1599505488a8e4f4a374d45053236 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 13 May 2024 21:18:59 -0700 Subject: [PATCH 007/156] fixup ast --- Cargo.lock | 56 ++++++++++++++++----------------- Cargo.toml | 4 +-- napi/Cargo.toml | 2 +- node/ast.d.ts | 62 +++++++++++++++++-------------------- scripts/build-ast.js | 12 +++++++ selectors/Cargo.toml | 4 +-- src/properties/transform.rs | 7 +++-- 7 files changed, 79 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7358e44c..1aba540b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -403,7 +403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -413,7 +413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad291aa74992b9b7a7e88c38acbbf6ad7e107f1d90ee8775b7bc1fc3394f485c" dependencies = [ "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -921,7 +921,7 @@ dependencies = [ "napi-derive-backend", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -936,7 +936,7 @@ dependencies = [ "quote", "regex", "semver", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -1122,7 +1122,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -1223,9 +1223,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] @@ -1252,9 +1252,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1420,9 +1420,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.15" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c" +checksum = "fc6e7ed6919cb46507fb01ff1654309219f62b4d603822501b0b80d42f6f21ef" dependencies = [ "dyn-clone", "schemars_derive", @@ -1433,14 +1433,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.15" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c" +checksum = "185f2b7aa7e02d418e453790dde16890256bbd2bcd04b7dc5348811052b53f49" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 1.0.109", + "syn 2.0.63", ] [[package]] @@ -1463,9 +1463,9 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.192" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" dependencies = [ "serde_derive", ] @@ -1491,24 +1491,24 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] name = "serde_derive_internals" -version = "0.26.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.63", ] [[package]] @@ -1614,9 +1614,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" dependencies = [ "proc-macro2", "quote", @@ -1680,7 +1680,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -1793,7 +1793,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", "wasm-bindgen-shared", ] @@ -1815,7 +1815,7 @@ checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1964,5 +1964,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] diff --git a/Cargo.toml b/Cargo.toml index 795b25ac..73ac94fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ into_owned = ["static-self", "static-self/smallvec", "parcel_selectors/into_owne substitute_variables = ["visitor", "into_owned"] [dependencies] -serde = { version = "1.0.123", features = ["derive"], optional = true } +serde = { version = "1.0.201", features = ["derive"], optional = true } cssparser = "0.33.0" cssparser-color = "0.1.0" parcel_selectors = { version = "0.26.4", path = "./selectors" } @@ -71,7 +71,7 @@ rayon = { version = "1.5.1", optional = true } dashmap = { version = "5.0.0", optional = true } serde_json = { version = "1.0.78", optional = true } lightningcss-derive = { version = "=1.0.0-alpha.42", path = "./derive", optional = true } -schemars = { version = "0.8.11", features = ["smallvec"], optional = true } +schemars = { version = "0.8.19", features = ["smallvec"], optional = true } static-self = { version = "0.1.0", path = "static-self", optional = true } [target.'cfg(target_os = "macos")'.dependencies] diff --git a/napi/Cargo.toml b/napi/Cargo.toml index 3360a9cd..b296c3b6 100644 --- a/napi/Cargo.toml +++ b/napi/Cargo.toml @@ -13,7 +13,7 @@ visitor = ["lightningcss/visitor"] bundler = ["dep:crossbeam-channel", "dep:rayon"] [dependencies] -serde = { version = "1.0.123", features = ["derive"] } +serde = { version = "1.0.201", features = ["derive"] } serde_bytes = "0.11.5" cssparser = "0.33.0" lightningcss = { version = "1.0.0-alpha.54", path = "../", features = ["nodejs", "serde"] } diff --git a/node/ast.d.ts b/node/ast.d.ts index 11e92001..6015ff44 100644 --- a/node/ast.d.ts +++ b/node/ast.d.ts @@ -1,4 +1,4 @@ -/* eslint-disable */ +/* tslint:disable */ /** * This file was automatically generated by json-schema-to-typescript. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, @@ -5619,44 +5619,40 @@ export type Perspective = * A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property. */ export type Translate = - | "None" + | "none" | { - XYZ: { - /** - * The x translation. - */ - x: DimensionPercentageFor_LengthValue; - /** - * The y translation. - */ - y: DimensionPercentageFor_LengthValue; - /** - * The z translation. - */ - z: Length; - }; - }; + /** + * The x translation. + */ + x: DimensionPercentageFor_LengthValue; + /** + * The y translation. + */ + y: DimensionPercentageFor_LengthValue; + /** + * The z translation. + */ + z: Length; + }; /** * A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property. */ export type Scale = - | "None" + | "none" | { - XYZ: { - /** - * Scale on the x axis. - */ - x: NumberOrPercentage; - /** - * Scale on the y axis. - */ - y: NumberOrPercentage; - /** - * Scale on the z axis. - */ - z: NumberOrPercentage; - }; - }; + /** + * Scale on the x axis. + */ + x: NumberOrPercentage; + /** + * Scale on the y axis. + */ + y: NumberOrPercentage; + /** + * Scale on the z axis. + */ + z: NumberOrPercentage; + }; /** * Defines how text case should be transformed in the [text-transform](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-transform-property) property. */ diff --git a/scripts/build-ast.js b/scripts/build-ast.js index 4e6bbebf..7b7fc020 100644 --- a/scripts/build-ast.js +++ b/scripts/build-ast.js @@ -55,6 +55,18 @@ compileFromFile('node/ast.json', { if (path.node.name.startsWith('GenericBorderFor_LineStyleAnd_')) { path.node.name = 'GenericBorderFor_LineStyle'; } + }, + TSTypeAliasDeclaration(path) { + // Workaround for schemars not supporting untagged variants. + // https://github.com/GREsau/schemars/issues/222 + if ( + (path.node.id.name === 'Translate' || path.node.id.name === 'Scale') && + path.node.typeAnnotation.type === 'TSUnionType' && + path.node.typeAnnotation.types[1].type === 'TSTypeLiteral' && + path.node.typeAnnotation.types[1].members[0].key.name === 'xyz' + ) { + path.get('typeAnnotation.types.1').replaceWith(path.node.typeAnnotation.types[1].members[0].typeAnnotation.typeAnnotation); + } } }); diff --git a/selectors/Cargo.toml b/selectors/Cargo.toml index c3dd9402..481c468b 100644 --- a/selectors/Cargo.toml +++ b/selectors/Cargo.toml @@ -29,8 +29,8 @@ log = "0.4" phf = "0.10" precomputed-hash = "0.1" smallvec = "1.0" -serde = { version = "1.0.123", features = ["derive"], optional = true } -schemars = { version = "0.8.11", features = ["smallvec"], optional = true } +serde = { version = "1.0.201", features = ["derive"], optional = true } +schemars = { version = "0.8.19", features = ["smallvec"], optional = true } static-self = { version = "0.1.0", path = "../static-self", optional = true } [build-dependencies] diff --git a/src/properties/transform.rs b/src/properties/transform.rs index ae390ae3..cbf6d60b 100644 --- a/src/properties/transform.rs +++ b/src/properties/transform.rs @@ -19,6 +19,7 @@ use crate::vendor_prefix::VendorPrefix; use crate::visitor::Visit; use cssparser::*; use std::f32::consts::PI; +use crate::serialization::ValueWrapper; /// A value for the [transform](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#propdef-transform) property. #[derive(Debug, Clone, PartialEq, Default)] @@ -1454,7 +1455,7 @@ impl ToCss for Perspective { /// A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum Translate { @@ -1462,6 +1463,7 @@ pub enum Translate { None, /// The x, y, and z translations. + #[cfg_attr(feature = "serde", serde(untagged))] XYZ { /// The x translation. x: LengthPercentage, @@ -1622,7 +1624,7 @@ impl Rotate { /// A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum Scale { @@ -1630,6 +1632,7 @@ pub enum Scale { None, /// Scale on the x, y, and z axis. + #[cfg_attr(feature = "serde", serde(untagged))] XYZ { /// Scale on the x axis. x: NumberOrPercentage, From 06ba62f6d1af563cd8ad31dba13844f434345720 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Tue, 14 May 2024 12:19:25 +0800 Subject: [PATCH 008/156] Fix box-shadow combination of `oklch` and `currentColor` (#722) --- src/lib.rs | 20 ++++++++++++++++++++ src/properties/box_shadow.rs | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b63e3ec8..90b4ca33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27182,6 +27182,26 @@ mod tests { ..Browsers::default() }, ); + prefix_test( + r#" + .foo { + box-shadow: + oklch(100% 0 0deg / 50%) 0 0.63rem 0.94rem -0.19rem, + currentColor 0 0.44rem 0.8rem -0.58rem; + } + "#, + indoc! { r#" + .foo { + box-shadow: 0 .63rem .94rem -.19rem #ffffff80, 0 .44rem .8rem -.58rem; + box-shadow: 0 .63rem .94rem -.19rem lab(100% 0 0 / .5), 0 .44rem .8rem -.58rem; + } + "#}, + Browsers { + chrome: Some(95 << 16), + ..Browsers::default() + }, + ); + prefix_test( ".foo { color: light-dark(var(--light), var(--dark)); }", indoc! { r#" diff --git a/src/properties/box_shadow.rs b/src/properties/box_shadow.rs index 0d757caf..de60f4d3 100644 --- a/src/properties/box_shadow.rs +++ b/src/properties/box_shadow.rs @@ -208,7 +208,7 @@ impl BoxShadowHandler { let rgb = box_shadows .iter() .map(|shadow| BoxShadow { - color: shadow.color.to_rgb().unwrap(), + color: shadow.color.to_rgb().unwrap_or_else(|_| shadow.color.clone()), ..shadow.clone() }) .collect(); @@ -236,7 +236,7 @@ impl BoxShadowHandler { let lab = box_shadows .iter() .map(|shadow| BoxShadow { - color: shadow.color.to_lab().unwrap(), + color: shadow.color.to_lab().unwrap_or_else(|_| shadow.color.clone()), ..shadow.clone() }) .collect(); From 4a168e94a671492ba834ed7682dc41ecf66205be Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 13 May 2024 21:18:59 -0700 Subject: [PATCH 009/156] fixup ast --- Cargo.lock | 56 ++++++++++++++++----------------- Cargo.toml | 4 +-- napi/Cargo.toml | 2 +- node/ast.d.ts | 62 +++++++++++++++++-------------------- scripts/build-ast.js | 12 +++++++ selectors/Cargo.toml | 4 +-- src/properties/transform.rs | 6 ++-- 7 files changed, 78 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7358e44c..1aba540b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -403,7 +403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -413,7 +413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad291aa74992b9b7a7e88c38acbbf6ad7e107f1d90ee8775b7bc1fc3394f485c" dependencies = [ "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -921,7 +921,7 @@ dependencies = [ "napi-derive-backend", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -936,7 +936,7 @@ dependencies = [ "quote", "regex", "semver", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -1122,7 +1122,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -1223,9 +1223,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] @@ -1252,9 +1252,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1420,9 +1420,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.15" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c" +checksum = "fc6e7ed6919cb46507fb01ff1654309219f62b4d603822501b0b80d42f6f21ef" dependencies = [ "dyn-clone", "schemars_derive", @@ -1433,14 +1433,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.15" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c" +checksum = "185f2b7aa7e02d418e453790dde16890256bbd2bcd04b7dc5348811052b53f49" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 1.0.109", + "syn 2.0.63", ] [[package]] @@ -1463,9 +1463,9 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.192" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" dependencies = [ "serde_derive", ] @@ -1491,24 +1491,24 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] name = "serde_derive_internals" -version = "0.26.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.63", ] [[package]] @@ -1614,9 +1614,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" dependencies = [ "proc-macro2", "quote", @@ -1680,7 +1680,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -1793,7 +1793,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", "wasm-bindgen-shared", ] @@ -1815,7 +1815,7 @@ checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1964,5 +1964,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] diff --git a/Cargo.toml b/Cargo.toml index 795b25ac..73ac94fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ into_owned = ["static-self", "static-self/smallvec", "parcel_selectors/into_owne substitute_variables = ["visitor", "into_owned"] [dependencies] -serde = { version = "1.0.123", features = ["derive"], optional = true } +serde = { version = "1.0.201", features = ["derive"], optional = true } cssparser = "0.33.0" cssparser-color = "0.1.0" parcel_selectors = { version = "0.26.4", path = "./selectors" } @@ -71,7 +71,7 @@ rayon = { version = "1.5.1", optional = true } dashmap = { version = "5.0.0", optional = true } serde_json = { version = "1.0.78", optional = true } lightningcss-derive = { version = "=1.0.0-alpha.42", path = "./derive", optional = true } -schemars = { version = "0.8.11", features = ["smallvec"], optional = true } +schemars = { version = "0.8.19", features = ["smallvec"], optional = true } static-self = { version = "0.1.0", path = "static-self", optional = true } [target.'cfg(target_os = "macos")'.dependencies] diff --git a/napi/Cargo.toml b/napi/Cargo.toml index 3360a9cd..b296c3b6 100644 --- a/napi/Cargo.toml +++ b/napi/Cargo.toml @@ -13,7 +13,7 @@ visitor = ["lightningcss/visitor"] bundler = ["dep:crossbeam-channel", "dep:rayon"] [dependencies] -serde = { version = "1.0.123", features = ["derive"] } +serde = { version = "1.0.201", features = ["derive"] } serde_bytes = "0.11.5" cssparser = "0.33.0" lightningcss = { version = "1.0.0-alpha.54", path = "../", features = ["nodejs", "serde"] } diff --git a/node/ast.d.ts b/node/ast.d.ts index 11e92001..6015ff44 100644 --- a/node/ast.d.ts +++ b/node/ast.d.ts @@ -1,4 +1,4 @@ -/* eslint-disable */ +/* tslint:disable */ /** * This file was automatically generated by json-schema-to-typescript. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, @@ -5619,44 +5619,40 @@ export type Perspective = * A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property. */ export type Translate = - | "None" + | "none" | { - XYZ: { - /** - * The x translation. - */ - x: DimensionPercentageFor_LengthValue; - /** - * The y translation. - */ - y: DimensionPercentageFor_LengthValue; - /** - * The z translation. - */ - z: Length; - }; - }; + /** + * The x translation. + */ + x: DimensionPercentageFor_LengthValue; + /** + * The y translation. + */ + y: DimensionPercentageFor_LengthValue; + /** + * The z translation. + */ + z: Length; + }; /** * A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property. */ export type Scale = - | "None" + | "none" | { - XYZ: { - /** - * Scale on the x axis. - */ - x: NumberOrPercentage; - /** - * Scale on the y axis. - */ - y: NumberOrPercentage; - /** - * Scale on the z axis. - */ - z: NumberOrPercentage; - }; - }; + /** + * Scale on the x axis. + */ + x: NumberOrPercentage; + /** + * Scale on the y axis. + */ + y: NumberOrPercentage; + /** + * Scale on the z axis. + */ + z: NumberOrPercentage; + }; /** * Defines how text case should be transformed in the [text-transform](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-transform-property) property. */ diff --git a/scripts/build-ast.js b/scripts/build-ast.js index 4e6bbebf..7b7fc020 100644 --- a/scripts/build-ast.js +++ b/scripts/build-ast.js @@ -55,6 +55,18 @@ compileFromFile('node/ast.json', { if (path.node.name.startsWith('GenericBorderFor_LineStyleAnd_')) { path.node.name = 'GenericBorderFor_LineStyle'; } + }, + TSTypeAliasDeclaration(path) { + // Workaround for schemars not supporting untagged variants. + // https://github.com/GREsau/schemars/issues/222 + if ( + (path.node.id.name === 'Translate' || path.node.id.name === 'Scale') && + path.node.typeAnnotation.type === 'TSUnionType' && + path.node.typeAnnotation.types[1].type === 'TSTypeLiteral' && + path.node.typeAnnotation.types[1].members[0].key.name === 'xyz' + ) { + path.get('typeAnnotation.types.1').replaceWith(path.node.typeAnnotation.types[1].members[0].typeAnnotation.typeAnnotation); + } } }); diff --git a/selectors/Cargo.toml b/selectors/Cargo.toml index c3dd9402..481c468b 100644 --- a/selectors/Cargo.toml +++ b/selectors/Cargo.toml @@ -29,8 +29,8 @@ log = "0.4" phf = "0.10" precomputed-hash = "0.1" smallvec = "1.0" -serde = { version = "1.0.123", features = ["derive"], optional = true } -schemars = { version = "0.8.11", features = ["smallvec"], optional = true } +serde = { version = "1.0.201", features = ["derive"], optional = true } +schemars = { version = "0.8.19", features = ["smallvec"], optional = true } static-self = { version = "0.1.0", path = "../static-self", optional = true } [build-dependencies] diff --git a/src/properties/transform.rs b/src/properties/transform.rs index ae390ae3..8ff7b968 100644 --- a/src/properties/transform.rs +++ b/src/properties/transform.rs @@ -1454,7 +1454,7 @@ impl ToCss for Perspective { /// A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum Translate { @@ -1462,6 +1462,7 @@ pub enum Translate { None, /// The x, y, and z translations. + #[cfg_attr(feature = "serde", serde(untagged))] XYZ { /// The x translation. x: LengthPercentage, @@ -1622,7 +1623,7 @@ impl Rotate { /// A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum Scale { @@ -1630,6 +1631,7 @@ pub enum Scale { None, /// Scale on the x, y, and z axis. + #[cfg_attr(feature = "serde", serde(untagged))] XYZ { /// Scale on the x axis. x: NumberOrPercentage, From 445def9a77f89aa612fecb2f776261fc2c9d8f66 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 13 May 2024 22:27:30 -0700 Subject: [PATCH 010/156] fix minifying predefined color spaces fixes #727 --- src/lib.rs | 31 +++++++++++++++++-------------- src/properties/transform.rs | 12 ++++++++++-- src/values/color.rs | 30 +++++++++--------------------- 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 90b4ca33..7d8aff33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16040,11 +16040,11 @@ mod tests { ); minify_test( ".foo { color: color(display-p3 1 0.5 0); }", - ".foo{color:color(display-p3 1 .5)}", + ".foo{color:color(display-p3 1 .5 0)}", ); minify_test( ".foo { color: color(display-p3 100% 50% 0%); }", - ".foo{color:color(display-p3 1 .5)}", + ".foo{color:color(display-p3 1 .5 0)}", ); minify_test( ".foo { color: color(xyz-d50 0.2005 0.14089 0.4472); }", @@ -16070,24 +16070,27 @@ mod tests { ".foo { color: color(xyz 20.05% 14.089% 44.72%); }", ".foo{color:color(xyz .2005 .14089 .4472)}", ); - minify_test(".foo { color: color(xyz 0.2005 0 0); }", ".foo{color:color(xyz .2005)}"); - minify_test(".foo { color: color(xyz 0 0 0); }", ".foo{color:color(xyz)}"); - minify_test(".foo { color: color(xyz 0 1 0); }", ".foo{color:color(xyz 0 1)}"); - minify_test(".foo { color: color(xyz 0 1); }", ".foo{color:color(xyz 0 1)}"); - minify_test(".foo { color: color(xyz 1); }", ".foo{color:color(xyz 1)}"); - minify_test(".foo { color: color(xyz); }", ".foo{color:color(xyz)}"); + minify_test( + ".foo { color: color(xyz 0.2005 0 0); }", + ".foo{color:color(xyz .2005 0 0)}", + ); + minify_test(".foo { color: color(xyz 0 0 0); }", ".foo{color:color(xyz 0 0 0)}"); + minify_test(".foo { color: color(xyz 0 1 0); }", ".foo{color:color(xyz 0 1 0)}"); minify_test( ".foo { color: color(xyz 0 1 0 / 20%); }", - ".foo{color:color(xyz 0 1/.2)}", + ".foo{color:color(xyz 0 1 0/.2)}", + ); + minify_test( + ".foo { color: color(xyz 0 0 0 / 20%); }", + ".foo{color:color(xyz 0 0 0/.2)}", ); - minify_test(".foo { color: color(xyz / 20%); }", ".foo{color:color(xyz/.2)}"); minify_test( ".foo { color: color(display-p3 100% 50% 0 / 20%); }", - ".foo{color:color(display-p3 1 .5/.2)}", + ".foo{color:color(display-p3 1 .5 0/.2)}", ); minify_test( - ".foo { color: color(display-p3 100% / 20%); }", - ".foo{color:color(display-p3 1/.2)}", + ".foo { color: color(display-p3 100% 0 0 / 20%); }", + ".foo{color:color(display-p3 1 0 0/.2)}", ); minify_test(".foo { color: hsl(none none none) }", ".foo{color:#000}"); minify_test(".foo { color: hwb(none none none) }", ".foo{color:red}"); @@ -20034,7 +20037,7 @@ mod tests { ".foo {{ color: color-mix(in {0}, color({0} -2 -3 -4 / -5), color({0} -4 -6 -8 / -10)) }}", color_space ), - &format!(".foo{{color:color({}/0)}}", result_color_space), + &format!(".foo{{color:color({} 0 0 0/0)}}", result_color_space), ); minify_test( diff --git a/src/properties/transform.rs b/src/properties/transform.rs index 8ff7b968..d58a08fe 100644 --- a/src/properties/transform.rs +++ b/src/properties/transform.rs @@ -1454,7 +1454,11 @@ impl ToCss for Perspective { /// A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "lowercase") +)] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum Translate { @@ -1623,7 +1627,11 @@ impl Rotate { /// A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "lowercase") +)] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum Scale { diff --git a/src/values/color.rs b/src/values/color.rs index 524c43c7..efa2bd26 100644 --- a/src/values/color.rs +++ b/src/values/color.rs @@ -1169,15 +1169,9 @@ fn parse_predefined_relative<'i, 't>( // Out of gamut values should not be clamped, i.e. values < 0 or > 1 should be preserved. // The browser will gamut-map the color for the target device that it is rendered on. - let a = input - .try_parse(|input| parse_number_or_percentage(input, parser)) - .unwrap_or(0.0); - let b = input - .try_parse(|input| parse_number_or_percentage(input, parser)) - .unwrap_or(0.0); - let c = input - .try_parse(|input| parse_number_or_percentage(input, parser)) - .unwrap_or(0.0); + let a = input.try_parse(|input| parse_number_or_percentage(input, parser))?; + let b = input.try_parse(|input| parse_number_or_percentage(input, parser))?; + let c = input.try_parse(|input| parse_number_or_percentage(input, parser))?; let alpha = parse_alpha(input, parser)?; let res = match_ignore_ascii_case! { &*&colorspace, @@ -1429,18 +1423,12 @@ where dest.write_str("color(")?; dest.write_str(name)?; - if !dest.minify || a != 0.0 || b != 0.0 || c != 0.0 { - dest.write_char(' ')?; - write_component(a, dest)?; - if !dest.minify || b != 0.0 || c != 0.0 { - dest.write_char(' ')?; - write_component(b, dest)?; - if !dest.minify || c != 0.0 { - dest.write_char(' ')?; - write_component(c, dest)?; - } - } - } + dest.write_char(' ')?; + write_component(a, dest)?; + dest.write_char(' ')?; + write_component(b, dest)?; + dest.write_char(' ')?; + write_component(c, dest)?; if alpha.is_nan() || (alpha - 1.0).abs() > f32::EPSILON { dest.delim('/', true)?; From 83839a98dbe0000acbdf039d968f33e1e8c50277 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Wed, 15 May 2024 07:25:52 +0200 Subject: [PATCH 011/156] Add granular CSS Modules options (#739) --- Cargo.lock | 23 ++++++ Cargo.toml | 1 + napi/src/lib.rs | 9 +++ src/css_modules.rs | 23 +++++- src/lib.rs | 132 +++++++++++++++++++++++++++++++++++ src/printer.rs | 44 ++++++------ src/properties/animation.rs | 19 +++-- src/properties/grid.rs | 27 +++---- src/rules/keyframes.rs | 7 +- src/selector.rs | 6 +- src/values/ident.rs | 21 +++++- website/pages/css-modules.md | 51 +++++++++----- 12 files changed, 299 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1aba540b..eed17d06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -444,6 +444,12 @@ dependencies = [ "matches", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "difflib" version = "0.4.0" @@ -786,6 +792,7 @@ dependencies = [ "paste", "pathdiff", "predicates 2.1.5", + "pretty_assertions", "rayon", "schemars", "serde", @@ -1197,6 +1204,16 @@ dependencies = [ "termtree", ] +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1947,6 +1964,12 @@ dependencies = [ "tap", ] +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "zerocopy" version = "0.7.32" diff --git a/Cargo.toml b/Cargo.toml index 73ac94fe..a298e0b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,7 @@ assert_cmd = "2.0" assert_fs = "1.0" predicates = "2.1" serde_json = "1" +pretty_assertions = "1.4.0" [[test]] name = "cli_integration_tests" diff --git a/napi/src/lib.rs b/napi/src/lib.rs index cc3ce78e..9edfb1ce 100644 --- a/napi/src/lib.rs +++ b/napi/src/lib.rs @@ -605,6 +605,9 @@ enum CssModulesOption { struct CssModulesConfig { pattern: Option, dashed_idents: Option, + animation: Option, + grid: Option, + custom_idents: Option, } #[cfg(feature = "bundler")] @@ -713,6 +716,9 @@ fn compile<'i>( Default::default() }, dashed_idents: c.dashed_idents.unwrap_or_default(), + animation: c.animation.unwrap_or(true), + grid: c.grid.unwrap_or(true), + custom_idents: c.custom_idents.unwrap_or(true), }), } } else { @@ -840,6 +846,9 @@ fn compile_bundle< Default::default() }, dashed_idents: c.dashed_idents.unwrap_or_default(), + animation: c.animation.unwrap_or(true), + grid: c.grid.unwrap_or(true), + custom_idents: c.custom_idents.unwrap_or(true), }), } } else { diff --git a/src/css_modules.rs b/src/css_modules.rs index b794bb43..cfe33d71 100644 --- a/src/css_modules.rs +++ b/src/css_modules.rs @@ -25,13 +25,34 @@ use std::hash::{Hash, Hasher}; use std::path::Path; /// Configuration for CSS modules. -#[derive(Default, Clone, Debug)] +#[derive(Clone, Debug)] pub struct Config<'i> { /// The name pattern to use when renaming class names and other identifiers. /// Default is `[hash]_[local]`. pub pattern: Pattern<'i>, /// Whether to rename dashed identifiers, e.g. custom properties. pub dashed_idents: bool, + /// Whether to scope animation names. + /// Default is `true`. + pub animation: bool, + /// Whether to scope grid names. + /// Default is `true`. + pub grid: bool, + /// Whether to scope custom identifiers + /// Default is `true`. + pub custom_idents: bool, +} + +impl<'i> Default for Config<'i> { + fn default() -> Self { + Config { + pattern: Default::default(), + dashed_idents: Default::default(), + animation: true, + grid: true, + custom_idents: true, + } + } } /// A CSS modules class name pattern. diff --git a/src/lib.rs b/src/lib.rs index 7d8aff33..d0ea1982 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,7 @@ mod tests { use crate::vendor_prefix::VendorPrefix; use cssparser::SourceLocation; use indoc::indoc; + use pretty_assertions::assert_eq; use std::collections::HashMap; fn test(source: &str, expected: &str) { @@ -23053,6 +23054,97 @@ mod tests { Default::default(), ); + css_modules_test( + r#" + .foo { + color: red; + } + + #id { + animation: 2s test; + } + + @keyframes test { + from { color: red } + to { color: yellow } + } + "#, + indoc! {r#" + .EgL3uq_foo { + color: red; + } + + #EgL3uq_id { + animation: 2s test; + } + + @keyframes test { + from { + color: red; + } + + to { + color: #ff0; + } + } + "#}, + map! { + "foo" => "EgL3uq_foo", + "id" => "EgL3uq_id" + }, + HashMap::new(), + crate::css_modules::Config { + animation: false, + // custom_idents: false, + ..Default::default() + }, + ); + + css_modules_test( + r#" + @counter-style circles { + symbols: Ⓐ Ⓑ Ⓒ; + } + + ul { + list-style: circles; + } + + ol { + list-style-type: none; + } + + li { + list-style-type: disc; + } + "#, + indoc! {r#" + @counter-style circles { + symbols: Ⓐ Ⓑ Ⓒ; + } + + ul { + list-style: circles; + } + + ol { + list-style-type: none; + } + + li { + list-style-type: disc; + } + "#}, + map! { + "circles" => "EgL3uq_circles" referenced: true + }, + HashMap::new(), + crate::css_modules::Config { + custom_idents: false, + ..Default::default() + }, + ); + #[cfg(feature = "grid")] css_modules_test( r#" @@ -23135,6 +23227,46 @@ mod tests { Default::default(), ); + #[cfg(feature = "grid")] + css_modules_test( + r#" + .grid { + grid-template-areas: "foo"; + } + + .foo { + grid-area: foo; + } + + .bar { + grid-column-start: foo-start; + } + "#, + indoc! {r#" + .EgL3uq_grid { + grid-template-areas: "foo"; + } + + .EgL3uq_foo { + grid-area: foo; + } + + .EgL3uq_bar { + grid-column-start: foo-start; + } + "#}, + map! { + "foo" => "EgL3uq_foo", + "grid" => "EgL3uq_grid", + "bar" => "EgL3uq_bar" + }, + HashMap::new(), + crate::css_modules::Config { + grid: false, + ..Default::default() + }, + ); + css_modules_test( r#" test { diff --git a/src/printer.rs b/src/printer.rs index 12fe09e3..d8c08e2d 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -267,30 +267,32 @@ impl<'a, 'b, 'c, W: std::fmt::Write + Sized> Printer<'a, 'b, 'c, W> { /// Writes a CSS identifier to the underlying destination, escaping it /// as appropriate. If the `css_modules` option was enabled, then a hash /// is added, and the mapping is added to the CSS module. - pub fn write_ident(&mut self, ident: &str) -> Result<(), PrinterError> { - if let Some(css_module) = &mut self.css_module { - let dest = &mut self.dest; - let mut first = true; - css_module.config.pattern.write( - &css_module.hashes[self.loc.source_index as usize], - &css_module.sources[self.loc.source_index as usize], - ident, - |s| { - self.col += s.len() as u32; - if first { - first = false; - serialize_identifier(s, dest) - } else { - serialize_name(s, dest) - } - }, - )?; + pub fn write_ident(&mut self, ident: &str, handle_css_module: bool) -> Result<(), PrinterError> { + if handle_css_module { + if let Some(css_module) = &mut self.css_module { + let dest = &mut self.dest; + let mut first = true; + css_module.config.pattern.write( + &css_module.hashes[self.loc.source_index as usize], + &css_module.sources[self.loc.source_index as usize], + ident, + |s| { + self.col += s.len() as u32; + if first { + first = false; + serialize_identifier(s, dest) + } else { + serialize_name(s, dest) + } + }, + )?; - css_module.add_local(&ident, &ident, self.loc.source_index); - } else { - serialize_identifier(ident, self)?; + css_module.add_local(&ident, &ident, self.loc.source_index); + return Ok(()); + } } + serialize_identifier(ident, self)?; Ok(()) } diff --git a/src/properties/animation.rs b/src/properties/animation.rs index c995c222..d5d73f83 100644 --- a/src/properties/animation.rs +++ b/src/properties/animation.rs @@ -58,17 +58,24 @@ impl<'i> ToCss for AnimationName<'i> { where W: std::fmt::Write, { + let css_module_animation_enabled = + dest.css_module.as_ref().map_or(false, |css_module| css_module.config.animation); + match self { AnimationName::None => dest.write_str("none"), AnimationName::Ident(s) => { - if let Some(css_module) = &mut dest.css_module { - css_module.reference(&s.0, dest.loc.source_index) + if css_module_animation_enabled { + if let Some(css_module) = &mut dest.css_module { + css_module.reference(&s.0, dest.loc.source_index) + } } - s.to_css(dest) + s.to_css_with_options(dest, css_module_animation_enabled) } AnimationName::String(s) => { - if let Some(css_module) = &mut dest.css_module { - css_module.reference(&s, dest.loc.source_index) + if css_module_animation_enabled { + if let Some(css_module) = &mut dest.css_module { + css_module.reference(&s, dest.loc.source_index) + } } // CSS-wide keywords and `none` cannot remove quotes. @@ -78,7 +85,7 @@ impl<'i> ToCss for AnimationName<'i> { Ok(()) }, _ => { - dest.write_ident(s.as_ref()) + dest.write_ident(s.as_ref(), css_module_animation_enabled) } } } diff --git a/src/properties/grid.rs b/src/properties/grid.rs index fa0e5a64..912f563d 100644 --- a/src/properties/grid.rs +++ b/src/properties/grid.rs @@ -430,21 +430,24 @@ fn write_ident(name: &str, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { - if let Some(css_module) = &mut dest.css_module { - if let Some(last) = css_module.config.pattern.segments.last() { - if !matches!(last, crate::css_modules::Segment::Local) { - return Err(Error { - kind: PrinterErrorKind::InvalidCssModulesPatternInGrid, - loc: Some(ErrorLocation { - filename: dest.filename().into(), - line: dest.loc.line, - column: dest.loc.column, - }), - }); + let css_module_grid_enabled = dest.css_module.as_ref().map_or(false, |css_module| css_module.config.grid); + if css_module_grid_enabled { + if let Some(css_module) = &mut dest.css_module { + if let Some(last) = css_module.config.pattern.segments.last() { + if !matches!(last, crate::css_modules::Segment::Local) { + return Err(Error { + kind: PrinterErrorKind::InvalidCssModulesPatternInGrid, + loc: Some(ErrorLocation { + filename: dest.filename().into(), + line: dest.loc.line, + column: dest.loc.column, + }), + }); + } } } } - dest.write_ident(name)?; + dest.write_ident(name, css_module_grid_enabled)?; Ok(()) } diff --git a/src/rules/keyframes.rs b/src/rules/keyframes.rs index a9489f30..511a3172 100644 --- a/src/rules/keyframes.rs +++ b/src/rules/keyframes.rs @@ -92,9 +92,12 @@ impl<'i> ToCss for KeyframesName<'i> { where W: std::fmt::Write, { + let css_module_animation_enabled = + dest.css_module.as_ref().map_or(false, |css_module| css_module.config.animation); + match self { KeyframesName::Ident(ident) => { - dest.write_ident(ident.0.as_ref())?; + dest.write_ident(ident.0.as_ref(), css_module_animation_enabled)?; } KeyframesName::Custom(s) => { // CSS-wide keywords and `none` cannot remove quotes. @@ -103,7 +106,7 @@ impl<'i> ToCss for KeyframesName<'i> { serialize_string(&s, dest)?; }, _ => { - dest.write_ident(s.as_ref())?; + dest.write_ident(s.as_ref(), css_module_animation_enabled)?; } } } diff --git a/src/selector.rs b/src/selector.rs index 9cc04848..62550198 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -672,7 +672,7 @@ where if let Some(class) = class { dest.write_char('.')?; - dest.write_ident(class) + dest.write_ident(class, true) } else { dest.write_str($s) } @@ -1551,11 +1551,11 @@ where Component::Nesting => serialize_nesting(dest, context, false), Component::Class(ref class) => { dest.write_char('.')?; - dest.write_ident(&class.0) + dest.write_ident(&class.0, true) } Component::ID(ref id) => { dest.write_char('#')?; - dest.write_ident(&id.0) + dest.write_ident(&id.0, true) } Component::Host(selector) => { dest.write_str(":host")?; diff --git a/src/values/ident.rs b/src/values/ident.rs index 9d244965..173d3a08 100644 --- a/src/values/ident.rs +++ b/src/values/ident.rs @@ -49,7 +49,26 @@ impl<'i> ToCss for CustomIdent<'i> { where W: std::fmt::Write, { - dest.write_ident(&self.0) + self.to_css_with_options(dest, true) + } +} + +impl<'i> CustomIdent<'i> { + /// Write the custom ident to CSS. + pub(crate) fn to_css_with_options( + &self, + dest: &mut Printer, + enabled_css_modules: bool, + ) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + let css_module_custom_idents_enabled = enabled_css_modules + && dest + .css_module + .as_mut() + .map_or(false, |css_module| css_module.config.custom_idents); + dest.write_ident(&self.0, css_module_custom_idents_enabled) } } diff --git a/website/pages/css-modules.md b/website/pages/css-modules.md index d66016c3..732f83cf 100644 --- a/website/pages/css-modules.md +++ b/website/pages/css-modules.md @@ -13,16 +13,16 @@ CSS modules treat the classes defined in each file as unique. Each class name or To enable CSS modules, provide the `cssModules` option when calling the Lightning CSS API. When using the CLI, enable the `--css-modules` flag. ```js -import { transform } from 'lightningcss'; +import {transform} from 'lightningcss'; -let { code, map, exports } = transform({ +let {code, map, exports} = transform({ // ... cssModules: true, code: Buffer.from(` .logo { background: skyblue; } - `) + `), }); ``` @@ -89,7 +89,7 @@ You can also reference class names defined in a different CSS file using the `fr ```css .logo { - composes: bg-indigo from "./colors.module.css"; + composes: bg-indigo from './colors.module.css'; } ``` @@ -150,10 +150,10 @@ compiles to: By default, class names, id selectors, and the names of `@keyframes`, `@counter-style`, and CSS grid lines and areas are scoped to the module they are defined in. Scoping for CSS variables and other [``](https://www.w3.org/TR/css-values-4/#dashed-idents) names can also be enabled using the `dashedIdents` option when calling the Lightning CSS API. When using the CLI, enable the `--css-modules-dashed-idents` flag. ```js -let { code, map, exports } = transform({ +let {code, map, exports} = transform({ // ... cssModules: { - dashedIdents: true + dashedIdents: true, }, }); ``` @@ -186,7 +186,7 @@ You can also reference variables defined in other files using the `from` keyword ```css .button { - background: var(--accent-color from "./vars.module.css"); + background: var(--accent-color from './vars.module.css'); } ``` @@ -207,19 +207,19 @@ By default, Lightning CSS prepends the hash of the filename to each class name a A pattern is a string with placeholders that will be filled in by Lightning CSS. This allows you to add custom prefixes or adjust the naming convention for scoped classes. ```js -let { code, map, exports } = transform({ +let {code, map, exports} = transform({ // ... cssModules: { - pattern: 'my-company-[name]-[hash]-[local]' - } + pattern: 'my-company-[name]-[hash]-[local]', + }, }); ``` The following placeholders are currently supported: -* `[name]` - The base name of the file, without the extension. -* `[hash]` - A hash of the full file path. -* `[local]` - The original class name or identifier. +- `[name]` - The base name of the file, without the extension. +- `[hash]` - A hash of the full file path. +- `[local]` - The original class name or identifier.
@@ -231,7 +231,7 @@ The following placeholders are currently supported: let { code, map, exports } = transform({ // ... cssModules: { - // ❌ [local] must be at the end so that + // ❌ [local] must be at the end so that // auto-generated grid line names work pattern: '[local]-[hash]' // ✅ do this instead @@ -242,7 +242,7 @@ let { code, map, exports } = transform({ ```css .grid { - grid-template-areas: "nav main"; + grid-template-areas: 'nav main'; } .nav { @@ -252,10 +252,25 @@ let { code, map, exports } = transform({
+## Turning off feature scoping + +Scoping of grid, animations, and custom identifiers can be turned off. By default all of these are scoped. + +```js +let {code, map, exports} = transform({ + // ... + cssModules: { + animation: true, + grid: true, + customIdents: true, + }, +}); +``` + ## Unsupported features Lightning CSS does not currently implement all CSS modules features available in other implementations. Some of these may be added in the future. -* Non-function syntax for the `:local` and `:global` pseudo classes. -* The `@value` rule – superseded by standard CSS variables. -* The `:import` and `:export` ICSS rules. +- Non-function syntax for the `:local` and `:global` pseudo classes. +- The `@value` rule – superseded by standard CSS variables. +- The `:import` and `:export` ICSS rules. From fb4b33488cbf54ba063de8babb42aec016a96ce8 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Tue, 14 May 2024 23:14:24 -0700 Subject: [PATCH 012/156] fix CSS module scoping with variables fixes #194 --- src/lib.rs | 93 +++++++++++++++++++++++++++++++++++++ src/properties/animation.rs | 52 +++++++++++++++------ src/properties/custom.rs | 7 +++ 3 files changed, 139 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d0ea1982..559f3197 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23672,6 +23672,99 @@ mod tests { }, ); + css_modules_test( + r#" + .test { + animation: rotate var(--duration) linear infinite; + } + "#, + indoc! {r#" + .EgL3uq_test { + animation: EgL3uq_rotate var(--duration) linear infinite; + } + "#}, + map! { + "test" => "EgL3uq_test", + "rotate" => "EgL3uq_rotate" referenced: true + }, + HashMap::new(), + Default::default(), + ); + css_modules_test( + r#" + .test { + animation: none var(--duration); + } + "#, + indoc! {r#" + .EgL3uq_test { + animation: none var(--duration); + } + "#}, + map! { + "test" => "EgL3uq_test" + }, + HashMap::new(), + Default::default(), + ); + css_modules_test( + r#" + .test { + animation: var(--animation); + } + "#, + indoc! {r#" + .EgL3uq_test { + animation: var(--animation); + } + "#}, + map! { + "test" => "EgL3uq_test" + }, + HashMap::new(), + Default::default(), + ); + css_modules_test( + r#" + .test { + animation: rotate var(--duration); + } + "#, + indoc! {r#" + .EgL3uq_test { + animation: rotate var(--duration); + } + "#}, + map! { + "test" => "EgL3uq_test" + }, + HashMap::new(), + crate::css_modules::Config { + animation: false, + ..Default::default() + } + ); + css_modules_test( + r#" + .test { + animation: "rotate" var(--duration); + } + "#, + indoc! {r#" + .EgL3uq_test { + animation: EgL3uq_rotate var(--duration); + } + "#}, + map! { + "test" => "EgL3uq_test", + "rotate" => "EgL3uq_rotate" referenced: true + }, + HashMap::new(), + crate::css_modules::Config { + ..Default::default() + } + ); + // Stable hashes between project roots. fn test_project_root(project_root: &str, filename: &str, hash: &str) { let stylesheet = StyleSheet::parse( diff --git a/src/properties/animation.rs b/src/properties/animation.rs index d5d73f83..2b2277d7 100644 --- a/src/properties/animation.rs +++ b/src/properties/animation.rs @@ -1,12 +1,14 @@ //! CSS properties related to keyframe animations. +use std::borrow::Cow; + use crate::context::PropertyHandlerContext; use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; use crate::macros::*; use crate::prefixes::Feature; use crate::printer::Printer; -use crate::properties::{Property, PropertyId, VendorPrefix}; +use crate::properties::{Property, PropertyId, TokenOrValue, VendorPrefix}; use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero}; use crate::values::number::CSSNumber; use crate::values::string::CowArcStr; @@ -346,8 +348,6 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> { dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { - use Property::*; - macro_rules! maybe_flush { ($prop: ident, $val: expr, $vp: ident) => {{ // If two vendor prefixes for the same property have different @@ -376,15 +376,15 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> { } match property { - AnimationName(val, vp) => property!(names, val, vp), - AnimationDuration(val, vp) => property!(durations, val, vp), - AnimationTimingFunction(val, vp) => property!(timing_functions, val, vp), - AnimationIterationCount(val, vp) => property!(iteration_counts, val, vp), - AnimationDirection(val, vp) => property!(directions, val, vp), - AnimationPlayState(val, vp) => property!(play_states, val, vp), - AnimationDelay(val, vp) => property!(delays, val, vp), - AnimationFillMode(val, vp) => property!(fill_modes, val, vp), - Animation(val, vp) => { + Property::AnimationName(val, vp) => property!(names, val, vp), + Property::AnimationDuration(val, vp) => property!(durations, val, vp), + Property::AnimationTimingFunction(val, vp) => property!(timing_functions, val, vp), + Property::AnimationIterationCount(val, vp) => property!(iteration_counts, val, vp), + Property::AnimationDirection(val, vp) => property!(directions, val, vp), + Property::AnimationPlayState(val, vp) => property!(play_states, val, vp), + Property::AnimationDelay(val, vp) => property!(delays, val, vp), + Property::AnimationFillMode(val, vp) => property!(fill_modes, val, vp), + Property::Animation(val, vp) => { let names = val.iter().map(|b| b.name.clone()).collect(); maybe_flush!(names, &names, vp); @@ -418,7 +418,33 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> { property!(delays, &delays, vp); property!(fill_modes, &fill_modes, vp); } - Unparsed(val) if is_animation_property(&val.property_id) => { + Property::Unparsed(val) if is_animation_property(&val.property_id) => { + let mut val = Cow::Borrowed(val); + if matches!(val.property_id, PropertyId::Animation(_)) { + use crate::properties::custom::Token; + + // Find an identifier that isn't a keyword and replace it with an + // AnimationName token so it is scoped in CSS modules. + for token in &mut val.to_mut().value.0 { + match token { + TokenOrValue::Token(Token::Ident(id)) => { + if AnimationDirection::parse_string(&id).is_err() + && AnimationPlayState::parse_string(&id).is_err() + && AnimationFillMode::parse_string(&id).is_err() + && !EasingFunction::is_ident(&id) + && id.as_ref() != "infinite" + { + *token = TokenOrValue::AnimationName(AnimationName::Ident(CustomIdent(id.clone()))); + } + } + TokenOrValue::Token(Token::String(s)) => { + *token = TokenOrValue::AnimationName(AnimationName::String(s.clone())); + } + _ => {} + } + } + } + self.flush(dest, context); dest.push(Property::Unparsed( val.get_prefixed(context.targets, Feature::Animation), diff --git a/src/properties/custom.rs b/src/properties/custom.rs index f93b4005..c3621af6 100644 --- a/src/properties/custom.rs +++ b/src/properties/custom.rs @@ -29,6 +29,7 @@ use cssparser::*; #[cfg(feature = "serde")] use crate::serialization::ValueWrapper; +use super::AnimationName; /// A CSS custom property, representing any unknown property. #[derive(Debug, Clone, PartialEq)] @@ -241,6 +242,8 @@ pub enum TokenOrValue<'i> { Resolution(Resolution), /// A dashed ident. DashedIdent(DashedIdent<'i>), + /// An animation name. + AnimationName(AnimationName<'i>), } impl<'i> From<'i>> for TokenOrValue<'i> { @@ -590,6 +593,10 @@ impl<'i> TokenList<'i> { v.to_css(dest)?; false } + TokenOrValue::AnimationName(v) => { + v.to_css(dest)?; + false + } TokenOrValue::Token(token) => match token { Token::Delim(d) => { if *d == '+' || *d == '-' { From ec9da43eeb1a236f5fd8dc472b3fe060c3db9a79 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Thu, 16 May 2024 19:29:01 -0700 Subject: [PATCH 013/156] update browser compat data --- package.json | 6 +- scripts/build-prefixes.js | 2 +- src/compat.rs | 287 +++++++++++++++++++++++++++++++------- src/prefixes.rs | 14 +- yarn.lock | 48 +++---- 5 files changed, 274 insertions(+), 83 deletions(-) diff --git a/package.json b/package.json index c9fe3d72..740531f3 100644 --- a/package.json +++ b/package.json @@ -48,10 +48,10 @@ "@codemirror/lang-javascript": "^6.1.2", "@codemirror/lint": "^6.1.0", "@codemirror/theme-one-dark": "^6.1.0", - "@mdn/browser-compat-data": "~5.5.0", + "@mdn/browser-compat-data": "~5.5.28", "@napi-rs/cli": "^2.14.0", - "autoprefixer": "^10.4.17", - "caniuse-lite": "^1.0.30001585", + "autoprefixer": "^10.4.19", + "caniuse-lite": "^1.0.30001620", "codemirror": "^6.0.1", "cssnano": "^5.0.8", "esbuild": "^0.19.8", diff --git a/scripts/build-prefixes.js b/scripts/build-prefixes.js index 597e1dbd..1533c8c9 100644 --- a/scripts/build-prefixes.js +++ b/scripts/build-prefixes.js @@ -282,7 +282,7 @@ let mdnFeatures = { logicalPaddingShorthand: mdn.css.properties['padding-inline'].__compat.support, logicalInset: mdn.css.properties['inset-inline-start'].__compat.support, logicalSize: mdn.css.properties['inline-size'].__compat.support, - logicalTextAlign: mdn.css.properties['text-align']['flow_relative_values_start_and_end'].__compat.support, + logicalTextAlign: mdn.css.properties['text-align'].start.__compat.support, labColors: mdn.css.types.color.lab.__compat.support, oklabColors: mdn.css.types.color.oklab.__compat.support, colorFunction: mdn.css.types.color.color.__compat.support, diff --git a/src/compat.rs b/src/compat.rs index 1a4bec65..1d283cb3 100644 --- a/src/compat.rs +++ b/src/compat.rs @@ -10,11 +10,13 @@ pub enum Feature { AfarListStyleType, AmharicAbegedeListStyleType, AmharicListStyleType, + AnchorSizeSize, AnyLink, AnyPseudo, ArabicIndicListStyleType, ArmenianListStyleType, AsterisksListStyleType, + AutoSize, Autofill, BengaliListStyleType, BinaryListStyleType, @@ -142,6 +144,7 @@ pub enum Feature { MyanmarListStyleType, Namespaces, Nesting, + NoneListStyleType, NotSelectorList, NthChildOf, OctalListStyleType, @@ -441,7 +444,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -533,7 +536,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -578,7 +581,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -623,7 +626,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -663,7 +666,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -708,7 +711,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -753,7 +756,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -798,7 +801,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -890,7 +893,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -935,7 +938,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -1015,7 +1018,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -1060,7 +1063,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -1150,7 +1153,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -1195,7 +1198,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -1245,7 +1248,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -1332,7 +1335,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -1377,7 +1380,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -1422,7 +1425,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -1462,7 +1465,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -1507,7 +1510,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -1552,7 +1555,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -1619,7 +1622,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 7929856 { + if version < 8126464 { return false; } } @@ -2868,12 +2871,47 @@ impl Feature { return false; } } - Feature::RoundFunction - | Feature::RemFunction - | Feature::ModFunction - | Feature::AbsFunction - | Feature::SignFunction - | Feature::HypotFunction => { + Feature::RoundFunction | Feature::RemFunction | Feature::ModFunction => { + if let Some(version) = browsers.chrome { + if version < 8192000 { + return false; + } + } + if let Some(version) = browsers.edge { + if version < 8192000 { + return false; + } + } + if let Some(version) = browsers.firefox { + if version < 7733248 { + return false; + } + } + if let Some(version) = browsers.opera { + if version < 7274496 { + return false; + } + } + if let Some(version) = browsers.safari { + if version < 984064 { + return false; + } + } + if let Some(version) = browsers.ios_saf { + if version < 984064 { + return false; + } + } + if let Some(version) = browsers.android { + if version < 8192000 { + return false; + } + } + if browsers.ie.is_some() || browsers.samsung.is_some() { + return false; + } + } + Feature::AbsFunction | Feature::SignFunction => { if let Some(version) = browsers.firefox { if version < 7733248 { return false; @@ -2899,6 +2937,51 @@ impl Feature { return false; } } + Feature::HypotFunction => { + if let Some(version) = browsers.chrome { + if version < 7864320 { + return false; + } + } + if let Some(version) = browsers.edge { + if version < 7864320 { + return false; + } + } + if let Some(version) = browsers.firefox { + if version < 7733248 { + return false; + } + } + if let Some(version) = browsers.opera { + if version < 5242880 { + return false; + } + } + if let Some(version) = browsers.safari { + if version < 984064 { + return false; + } + } + if let Some(version) = browsers.ios_saf { + if version < 984064 { + return false; + } + } + if let Some(version) = browsers.samsung { + if version < 1638400 { + return false; + } + } + if let Some(version) = browsers.android { + if version < 7864320 { + return false; + } + } + if browsers.ie.is_some() { + return false; + } + } Feature::GradientInterpolationHints => { if let Some(version) = browsers.chrome { if version < 2621440 { @@ -2986,7 +3069,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 2424832 { + if version < 263168 { return false; } } @@ -3080,7 +3163,7 @@ impl Feature { } } if let Some(version) = browsers.android { - if version < 262144 { + if version < 2752512 { return false; } } @@ -3271,19 +3354,37 @@ impl Feature { return false; } } + if let Some(version) = browsers.edge { + if version < 8060928 { + return false; + } + } if let Some(version) = browsers.firefox { if version < 7864320 { return false; } } - if browsers.android.is_some() - || browsers.edge.is_some() - || browsers.ie.is_some() - || browsers.ios_saf.is_some() - || browsers.opera.is_some() - || browsers.safari.is_some() - || browsers.samsung.is_some() - { + if let Some(version) = browsers.opera { + if version < 5373952 { + return false; + } + } + if let Some(version) = browsers.safari { + if version < 1115392 { + return false; + } + } + if let Some(version) = browsers.ios_saf { + if version < 1115392 { + return false; + } + } + if let Some(version) = browsers.android { + if version < 8060928 { + return false; + } + } + if browsers.ie.is_some() || browsers.samsung.is_some() { return false; } } @@ -3389,12 +3490,17 @@ impl Feature { return false; } } + if let Some(version) = browsers.samsung { + if version < 1572864 { + return false; + } + } if let Some(version) = browsers.android { if version < 7667712 { return false; } } - if browsers.ie.is_some() || browsers.samsung.is_some() { + if browsers.ie.is_some() { return false; } } @@ -3704,12 +3810,17 @@ impl Feature { return false; } } + if let Some(version) = browsers.samsung { + if version < 1572864 { + return false; + } + } if let Some(version) = browsers.android { if version < 7667712 { return false; } } - if browsers.firefox.is_some() || browsers.ie.is_some() || browsers.samsung.is_some() { + if browsers.firefox.is_some() || browsers.ie.is_some() { return false; } } @@ -4670,14 +4781,15 @@ impl Feature { | Feature::HiraganaListStyleType | Feature::HiraganaIrohaListStyleType | Feature::KatakanaListStyleType - | Feature::KatakanaIrohaListStyleType => { + | Feature::KatakanaIrohaListStyleType + | Feature::AutoSize => { if let Some(version) = browsers.chrome { if version < 1179648 { return false; } } if let Some(version) = browsers.edge { - if version < 5177344 { + if version < 786432 { return false; } } @@ -4686,6 +4798,11 @@ impl Feature { return false; } } + if let Some(version) = browsers.ie { + if version < 720896 { + return false; + } + } if let Some(version) = browsers.opera { if version < 917504 { return false; @@ -4711,9 +4828,6 @@ impl Feature { return false; } } - if browsers.ie.is_some() { - return false; - } } Feature::KoreanHangulFormalListStyleType | Feature::KoreanHanjaFormalListStyleType @@ -4854,6 +4968,53 @@ impl Feature { } } } + Feature::NoneListStyleType => { + if let Some(version) = browsers.chrome { + if version < 1179648 { + return false; + } + } + if let Some(version) = browsers.edge { + if version < 786432 { + return false; + } + } + if let Some(version) = browsers.firefox { + if version < 5177344 { + return false; + } + } + if let Some(version) = browsers.ie { + if version < 720896 { + return false; + } + } + if let Some(version) = browsers.opera { + if version < 917504 { + return false; + } + } + if let Some(version) = browsers.safari { + if version < 65536 { + return false; + } + } + if let Some(version) = browsers.ios_saf { + if version < 65536 { + return false; + } + } + if let Some(version) = browsers.samsung { + if version < 65536 { + return false; + } + } + if let Some(version) = browsers.android { + if version < 263168 { + return false; + } + } + } Feature::SimpChineseFormalListStyleType | Feature::SimpChineseInformalListStyleType | Feature::TradChineseFormalListStyleType @@ -4965,6 +5126,36 @@ impl Feature { return false; } } + Feature::AnchorSizeSize => { + if let Some(version) = browsers.chrome { + if version < 8192000 { + return false; + } + } + if let Some(version) = browsers.edge { + if version < 8192000 { + return false; + } + } + if let Some(version) = browsers.opera { + if version < 7274496 { + return false; + } + } + if let Some(version) = browsers.android { + if version < 8192000 { + return false; + } + } + if browsers.firefox.is_some() + || browsers.ie.is_some() + || browsers.ios_saf.is_some() + || browsers.safari.is_some() + || browsers.samsung.is_some() + { + return false; + } + } Feature::FitContentSize => { if let Some(version) = browsers.chrome { if version < 1638400 { @@ -5059,7 +5250,7 @@ impl Feature { } Feature::MaxContentSize => { if let Some(version) = browsers.chrome { - if version < 3014656 { + if version < 1638400 { return false; } } @@ -5089,12 +5280,12 @@ impl Feature { } } if let Some(version) = browsers.samsung { - if version < 327680 { + if version < 66816 { return false; } } if let Some(version) = browsers.android { - if version < 3014656 { + if version < 263168 { return false; } } diff --git a/src/prefixes.rs b/src/prefixes.rs index fe06123d..56697404 100644 --- a/src/prefixes.rs +++ b/src/prefixes.rs @@ -806,7 +806,7 @@ impl Feature { } } if let Some(version) = browsers.opera { - if version >= 983040 { + if version >= 983040 && version <= 6881280 { prefixes |= VendorPrefix::WebKit; } } @@ -1124,7 +1124,7 @@ impl Feature { } } if let Some(version) = browsers.opera { - if version >= 983040 { + if version >= 983040 && version <= 6225920 { prefixes |= VendorPrefix::WebKit; } } @@ -1468,7 +1468,7 @@ impl Feature { } } if let Some(version) = browsers.opera { - if version >= 983040 { + if version >= 983040 && version <= 6881280 { prefixes |= VendorPrefix::WebKit; } } @@ -1510,7 +1510,7 @@ impl Feature { } } if let Some(version) = browsers.samsung { - if version >= 262144 { + if version >= 262144 && version <= 327680 { prefixes |= VendorPrefix::WebKit; } } @@ -1785,7 +1785,7 @@ impl Feature { } } if let Some(version) = browsers.samsung { - if version >= 262144 { + if version >= 262144 && version <= 851968 { prefixes |= VendorPrefix::WebKit; } } @@ -1865,7 +1865,7 @@ impl Feature { } } if let Some(version) = browsers.opera { - if version >= 983040 { + if version >= 983040 && version <= 6422528 { prefixes |= VendorPrefix::WebKit; } } @@ -1875,7 +1875,7 @@ impl Feature { } } if let Some(version) = browsers.samsung { - if version >= 262144 { + if version >= 262144 && version <= 1441792 { prefixes |= VendorPrefix::WebKit; } } diff --git a/yarn.lock b/yarn.lock index d2d191f4..af6b910d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -454,10 +454,10 @@ resolved "https://registry.yarnpkg.com/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.5.2.tgz#28f643fbc0bec30b07fbe95b137879b6b4d1c9c5" integrity sha512-zrBczSbXKxEyK2ijtbRdICDygRqWSRPpZMN5dD1T8VMEW5RIhIbwFWw2phDRXuBQdVDpSjalCIUMWMV2h3JaZA== -"@mdn/browser-compat-data@~5.5.0": - version "5.5.10" - resolved "https://registry.yarnpkg.com/@mdn/browser-compat-data/-/browser-compat-data-5.5.10.tgz#b7c0454a617b268b7a9ab9ca6c886abd1ec69473" - integrity sha512-s2GGND9oLhEuksOFtICYOBZdMWPANBXTMqAXh89q6g1Mi3+OuWEmp9WFzw2v/nmS175vqeewpC1kDJA7taaxyA== +"@mdn/browser-compat-data@~5.5.28": + version "5.5.28" + resolved "https://registry.yarnpkg.com/@mdn/browser-compat-data/-/browser-compat-data-5.5.28.tgz#76cd0651ebf9725916da10b686ac98288403d89f" + integrity sha512-yKS9hfVRsYx/3usEAk+86rq2KnHHyoafyvw2Xob5dXPSOZKIY56Nkas/JK3cmav8nZjjHWiYS/asv9TQy1YLbg== "@mischnic/json-sourcemap@^0.1.0": version "0.1.0" @@ -1228,13 +1228,13 @@ ast-types@0.15.2: dependencies: tslib "^2.0.1" -autoprefixer@^10.4.17: - version "10.4.17" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.17.tgz#35cd5695cbbe82f536a50fa025d561b01fdec8be" - integrity sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg== +autoprefixer@^10.4.19: + version "10.4.19" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.19.tgz#ad25a856e82ee9d7898c59583c1afeb3fa65f89f" + integrity sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew== dependencies: - browserslist "^4.22.2" - caniuse-lite "^1.0.30001578" + browserslist "^4.23.0" + caniuse-lite "^1.0.30001599" fraction.js "^4.3.7" normalize-range "^0.1.2" picocolors "^1.0.0" @@ -1291,13 +1291,13 @@ braces@^3.0.2: dependencies: fill-range "^7.0.1" -browserslist@^4.0.0, browserslist@^4.16.0, browserslist@^4.16.6, browserslist@^4.22.2, browserslist@^4.6.6: - version "4.22.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.3.tgz#299d11b7e947a6b843981392721169e27d60c5a6" - integrity sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A== +browserslist@^4.0.0, browserslist@^4.16.0, browserslist@^4.16.6, browserslist@^4.23.0, browserslist@^4.6.6: + version "4.23.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" + integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== dependencies: - caniuse-lite "^1.0.30001580" - electron-to-chromium "^1.4.648" + caniuse-lite "^1.0.30001587" + electron-to-chromium "^1.4.668" node-releases "^2.0.14" update-browserslist-db "^1.0.13" @@ -1347,10 +1347,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001578, caniuse-lite@^1.0.30001580, caniuse-lite@^1.0.30001585: - version "1.0.30001585" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001585.tgz#0b4e848d84919c783b2a41c13f7de8ce96744401" - integrity sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599, caniuse-lite@^1.0.30001620: + version "1.0.30001620" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz#78bb6f35b8fe315b96b8590597094145d0b146b4" + integrity sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew== chalk@^2.0.0: version "2.4.2" @@ -1727,10 +1727,10 @@ dotenv@^7.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c" integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g== -electron-to-chromium@^1.4.648: - version "1.4.664" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.664.tgz#b00fc67d5d4f124e429b0dcce5a02ae18ef33ede" - integrity sha512-k9VKKSkOSNPvSckZgDDl/IQx45E1quMjX8QfLzUsAs/zve8AyFDK+ByRynSP/OfEfryiKHpQeMf00z0leLCc3A== +electron-to-chromium@^1.4.668: + version "1.4.773" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.773.tgz#49741af9bb4e712ad899e35d8344d8d59cdb7e12" + integrity sha512-87eHF+h3PlCRwbxVEAw9KtK3v7lWfc/sUDr0W76955AdYTG4bV/k0zrl585Qnj/skRMH2qOSiE+kqMeOQ+LOpw== end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" From f4408c7bdbbfa2c4cbdf5731b351811a0323fa8c Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Fri, 17 May 2024 09:58:42 -0700 Subject: [PATCH 014/156] Implement animation-timeline property and add to animation shorthand #572 --- node/ast.d.ts | 121 ++++++++++++-- scripts/build-prefixes.js | 1 + src/compat.rs | 35 ++++ src/lib.rs | 150 ++++++++++++++++- src/properties/animation.rs | 324 +++++++++++++++++++++++++++++++++++- src/properties/custom.rs | 2 +- src/properties/mod.rs | 2 + 7 files changed, 613 insertions(+), 22 deletions(-) diff --git a/node/ast.d.ts b/node/ast.d.ts index 6015ff44..8bc02772 100644 --- a/node/ast.d.ts +++ b/node/ast.d.ts @@ -504,6 +504,10 @@ export type TokenOrValue = | { type: "dashed-ident"; value: String; + } + | { + type: "animation-name"; + value: AnimationName; }; /** * A raw CSS token. @@ -1114,6 +1118,21 @@ export type Time = type: "milliseconds"; value: number; }; +/** + * A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property. + */ +export type AnimationName = + | { + type: "none"; + } + | { + type: "ident"; + value: String; + } + | { + type: "string"; + value: String; + }; /** * A CSS environment variable name. */ @@ -1935,6 +1954,12 @@ export type PropertyId = property: "animation-fill-mode"; vendorPrefix: VendorPrefix; } + | { + property: "animation-composition"; + } + | { + property: "animation-timeline"; + } | { property: "animation"; vendorPrefix: VendorPrefix; @@ -3283,6 +3308,14 @@ export type Declaration = value: AnimationFillMode[]; vendorPrefix: VendorPrefix; } + | { + property: "animation-composition"; + value: AnimationComposition[]; + } + | { + property: "animation-timeline"; + value: AnimationTimeline[]; + } | { property: "animation"; value: Animation[]; @@ -5442,21 +5475,6 @@ export type StepPosition = | { type: "jump-both"; }; -/** - * A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property. - */ -export type AnimationName = - | { - type: "none"; - } - | { - type: "ident"; - value: String; - } - | { - type: "string"; - value: String; - }; /** * A value for the [animation-iteration-count](https://drafts.csswg.org/css-animations/#animation-iteration-count) property. */ @@ -5480,6 +5498,49 @@ export type AnimationPlayState = "running" | "paused"; * A value for the [animation-fill-mode](https://drafts.csswg.org/css-animations/#animation-fill-mode) property. */ export type AnimationFillMode = "none" | "forwards" | "backwards" | "both"; +/** + * A value for the [animation-composition](https://drafts.csswg.org/css-animations-2/#animation-composition) property. + */ +export type AnimationComposition = "replace" | "add" | "accumulate"; +/** + * A value for the [animation-timeline](https://drafts.csswg.org/css-animations-2/#animation-timeline) property. + */ +export type AnimationTimeline = + | { + type: "auto"; + } + | { + type: "none"; + } + | { + type: "dashed-ident"; + value: String; + } + | { + type: "scroll"; + value: ScrollTimeline; + } + | { + type: "view"; + value: ViewTimeline; + }; +/** + * A scroll axis, used in the `scroll()` function. + */ +export type ScrollAxis = "block" | "inline" | "x" | "y"; +/** + * A scroller, used in the `scroll()` function. + */ +export type Scroller = "root" | "nearest" | "self"; +/** + * A generic value that represents a value with two components, e.g. a border radius. + * + * When serialized, only a single component will be written if both are equal. + * + * @minItems 2 + * @maxItems 2 + */ +export type Size2DFor_LengthPercentageOrAuto = [LengthPercentageOrAuto, LengthPercentageOrAuto]; /** * An individual [transform function](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#two-d-transform-functions). */ @@ -8488,6 +8549,32 @@ export interface Transition { */ timingFunction: EasingFunction; } +/** + * The [scroll()](https://drafts.csswg.org/scroll-animations-1/#scroll-notation) function. + */ +export interface ScrollTimeline { + /** + * Specifies which axis of the scroll container to use as the progress for the timeline. + */ + axis: ScrollAxis; + /** + * Specifies which element to use as the scroll container. + */ + scroller: Scroller; +} +/** + * The [view()](https://drafts.csswg.org/scroll-animations-1/#view-notation) function. + */ +export interface ViewTimeline { + /** + * Specifies which axis of the scroll container to use as the progress for the timeline. + */ + axis: ScrollAxis; + /** + * Provides an adjustment of the view progress visibility range. + */ + inset: Size2DFor_LengthPercentageOrAuto; +} /** * A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property. */ @@ -8520,6 +8607,10 @@ export interface Animation { * The current play state of the animation. */ playState: AnimationPlayState; + /** + * The animation timeline. + */ + timeline: AnimationTimeline; /** * The easing function for the animation. */ diff --git a/scripts/build-prefixes.js b/scripts/build-prefixes.js index 1533c8c9..8fc83ca2 100644 --- a/scripts/build-prefixes.js +++ b/scripts/build-prefixes.js @@ -329,6 +329,7 @@ let mdnFeatures = { fontStretchPercentage: mdn.css.properties['font-stretch'].percentage.__compat.support, lightDark: mdn.css.types.color['light-dark'].__compat.support, accentSystemColor: mdn.css.types.color['system-color'].accentcolor_accentcolortext.__compat.support, + animationTimelineShorthand: mdn.css.properties.animation['animation-timeline_included'].__compat.support, }; for (let key in mdn.css.types.length) { diff --git a/src/compat.rs b/src/compat.rs index 1d283cb3..b1846f34 100644 --- a/src/compat.rs +++ b/src/compat.rs @@ -11,6 +11,7 @@ pub enum Feature { AmharicAbegedeListStyleType, AmharicListStyleType, AnchorSizeSize, + AnimationTimelineShorthand, AnyLink, AnyPseudo, ArabicIndicListStyleType, @@ -3414,6 +3415,40 @@ impl Feature { return false; } } + Feature::AnimationTimelineShorthand => { + if let Some(version) = browsers.chrome { + if version < 7536640 { + return false; + } + } + if let Some(version) = browsers.edge { + if version < 7536640 { + return false; + } + } + if let Some(version) = browsers.opera { + if version < 5046272 { + return false; + } + } + if let Some(version) = browsers.samsung { + if version < 1507328 { + return false; + } + } + if let Some(version) = browsers.android { + if version < 7536640 { + return false; + } + } + if browsers.firefox.is_some() + || browsers.ie.is_some() + || browsers.ios_saf.is_some() + || browsers.safari.is_some() + { + return false; + } + } Feature::QUnit => { if let Some(version) = browsers.chrome { if version < 4128768 { diff --git a/src/lib.rs b/src/lib.rs index 559f3197..d39c6ac4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11243,6 +11243,46 @@ mod tests { ".foo { animation: foo 0s 3s infinite }", ".foo{animation:0s 3s infinite foo}", ); + minify_test(".foo { animation: foo 3s --test }", ".foo{animation:3s foo --test}"); + minify_test(".foo { animation: foo 3s scroll() }", ".foo{animation:3s foo scroll()}"); + minify_test( + ".foo { animation: foo 3s scroll(block) }", + ".foo{animation:3s foo scroll()}", + ); + minify_test( + ".foo { animation: foo 3s scroll(root inline) }", + ".foo{animation:3s foo scroll(root inline)}", + ); + minify_test( + ".foo { animation: foo 3s scroll(inline root) }", + ".foo{animation:3s foo scroll(root inline)}", + ); + minify_test( + ".foo { animation: foo 3s scroll(inline nearest) }", + ".foo{animation:3s foo scroll(inline)}", + ); + minify_test( + ".foo { animation: foo 3s view(block) }", + ".foo{animation:3s foo view()}", + ); + minify_test( + ".foo { animation: foo 3s view(inline) }", + ".foo{animation:3s foo view(inline)}", + ); + minify_test( + ".foo { animation: foo 3s view(inline 10px 10px) }", + ".foo{animation:3s foo view(inline 10px)}", + ); + minify_test( + ".foo { animation: foo 3s view(inline 10px 12px) }", + ".foo{animation:3s foo view(inline 10px 12px)}", + ); + minify_test( + ".foo { animation: foo 3s view(inline auto auto) }", + ".foo{animation:3s foo view(inline)}", + ); + minify_test(".foo { animation: foo 3s auto }", ".foo{animation:3s foo}"); + minify_test(".foo { animation-composition: add }", ".foo{animation-composition:add}"); test( r#" .foo { @@ -11254,6 +11294,7 @@ mod tests { animation-play-state: running; animation-delay: 100ms; animation-fill-mode: forwards; + animation-timeline: auto; } "#, indoc! {r#" @@ -11273,6 +11314,7 @@ mod tests { animation-play-state: running, paused; animation-delay: 100ms, 0s; animation-fill-mode: forwards, none; + animation-timeline: auto, auto; } "#, indoc! {r#" @@ -11319,6 +11361,7 @@ mod tests { animation-play-state: running; animation-delay: 100ms; animation-fill-mode: forwards; + animation-timeline: auto; } "#, indoc! {r#" @@ -11331,6 +11374,55 @@ mod tests { animation-play-state: running; animation-delay: .1s; animation-fill-mode: forwards; + animation-timeline: auto; + } + "#}, + ); + test( + r#" + .foo { + animation-name: foo; + animation-duration: 0.09s; + animation-timing-function: ease-in-out; + animation-iteration-count: 2; + animation-direction: alternate; + animation-play-state: running; + animation-delay: 100ms; + animation-fill-mode: forwards; + animation-timeline: scroll(); + } + "#, + indoc! {r#" + .foo { + animation: 90ms ease-in-out .1s 2 alternate forwards foo scroll(); + } + "#}, + ); + test( + r#" + .foo { + animation-name: foo; + animation-duration: 0.09s; + animation-timing-function: ease-in-out; + animation-iteration-count: 2; + animation-direction: alternate; + animation-play-state: running; + animation-delay: 100ms; + animation-fill-mode: forwards; + animation-timeline: scroll(), view(); + } + "#, + indoc! {r#" + .foo { + animation-name: foo; + animation-duration: 90ms; + animation-timing-function: ease-in-out; + animation-iteration-count: 2; + animation-direction: alternate; + animation-play-state: running; + animation-delay: .1s; + animation-fill-mode: forwards; + animation-timeline: scroll(), view(); } "#}, ); @@ -11441,6 +11533,58 @@ mod tests { ..Browsers::default() }, ); + + prefix_test( + r#" + .foo { + animation: .2s ease-in-out bar scroll(); + } + "#, + indoc! {r#" + .foo { + animation: .2s ease-in-out bar; + animation-timeline: scroll(); + } + "#}, + Browsers { + safari: Some(16 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" + .foo { + animation: .2s ease-in-out bar scroll(); + } + "#, + indoc! {r#" + .foo { + animation: .2s ease-in-out bar scroll(); + } + "#}, + Browsers { + chrome: Some(120 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" + .foo { + animation: .2s ease-in-out bar scroll(); + } + "#, + indoc! {r#" + .foo { + -webkit-animation: .2s ease-in-out bar; + animation: .2s ease-in-out bar; + animation-timeline: scroll(); + } + "#}, + Browsers { + safari: Some(6 << 16), + ..Browsers::default() + }, + ); } #[test] @@ -23742,7 +23886,7 @@ mod tests { crate::css_modules::Config { animation: false, ..Default::default() - } + }, ); css_modules_test( r#" @@ -23760,9 +23904,7 @@ mod tests { "rotate" => "EgL3uq_rotate" referenced: true }, HashMap::new(), - crate::css_modules::Config { - ..Default::default() - } + crate::css_modules::Config { ..Default::default() }, ); // Stable hashes between project roots. diff --git a/src/properties/animation.rs b/src/properties/animation.rs index 2b2277d7..03b76166 100644 --- a/src/properties/animation.rs +++ b/src/properties/animation.rs @@ -10,7 +10,9 @@ use crate::prefixes::Feature; use crate::printer::Printer; use crate::properties::{Property, PropertyId, TokenOrValue, VendorPrefix}; use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero}; +use crate::values::ident::DashedIdent; use crate::values::number::CSSNumber; +use crate::values::size::Size2D; use crate::values::string::CowArcStr; use crate::values::{easing::EasingFunction, ident::CustomIdent, time::Time}; #[cfg(feature = "visitor")] @@ -19,6 +21,8 @@ use cssparser::*; use itertools::izip; use smallvec::SmallVec; +use super::LengthPercentageOrAuto; + /// A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] @@ -200,6 +204,255 @@ impl Default for AnimationFillMode { } } +enum_property! { + /// A value for the [animation-composition](https://drafts.csswg.org/css-animations-2/#animation-composition) property. + pub enum AnimationComposition { + /// The result of compositing the effect value with the underlying value is simply the effect value. + Replace, + /// The effect value is added to the underlying value. + Add, + /// The effect value is accumulated onto the underlying value. + Accumulate, + } +} + +/// A value for the [animation-timeline](https://drafts.csswg.org/css-animations-2/#animation-timeline) property. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "visitor", derive(Visit))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", content = "value", rename_all = "kebab-case") +)] +#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] +pub enum AnimationTimeline<'i> { + /// The animation’s timeline is a DocumentTimeline, more specifically the default document timeline. + Auto, + /// The animation is not associated with a timeline. + None, + /// A timeline referenced by name. + #[cfg_attr(feature = "serde", serde(borrow))] + DashedIdent(DashedIdent<'i>), + /// The scroll() function. + Scroll(ScrollTimeline), + /// The view() function. + View(ViewTimeline), +} + +impl<'i> Default for AnimationTimeline<'i> { + fn default() -> Self { + AnimationTimeline::Auto + } +} + +/// The [scroll()](https://drafts.csswg.org/scroll-animations-1/#scroll-notation) function. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "visitor", derive(Visit))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] +pub struct ScrollTimeline { + /// Specifies which element to use as the scroll container. + pub scroller: Scroller, + /// Specifies which axis of the scroll container to use as the progress for the timeline. + pub axis: ScrollAxis, +} + +impl<'i> Parse<'i> for ScrollTimeline { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { + let mut scroller = None; + let mut axis = None; + loop { + if scroller.is_none() { + scroller = input.try_parse(Scroller::parse).ok(); + } + + if axis.is_none() { + axis = input.try_parse(ScrollAxis::parse).ok(); + if axis.is_some() { + continue; + } + } + break; + } + + Ok(ScrollTimeline { + scroller: scroller.unwrap_or_default(), + axis: axis.unwrap_or_default(), + }) + } +} + +impl ToCss for ScrollTimeline { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + let mut needs_space = false; + if self.scroller != Scroller::default() { + self.scroller.to_css(dest)?; + needs_space = true; + } + + if self.axis != ScrollAxis::default() { + if needs_space { + dest.write_char(' ')?; + } + self.axis.to_css(dest)?; + } + + Ok(()) + } +} + +enum_property! { + /// A scroller, used in the `scroll()` function. + pub enum Scroller { + /// Specifies to use the document viewport as the scroll container. + "root": Root, + /// Specifies to use the nearest ancestor scroll container. + "nearest": Nearest, + /// Specifies to use the element’s own principal box as the scroll container. + "self": SelfElement, + } +} + +impl Default for Scroller { + fn default() -> Self { + Scroller::Nearest + } +} + +enum_property! { + /// A scroll axis, used in the `scroll()` function. + pub enum ScrollAxis { + /// Specifies to use the measure of progress along the block axis of the scroll container. + Block, + /// Specifies to use the measure of progress along the inline axis of the scroll container. + Inline, + /// Specifies to use the measure of progress along the horizontal axis of the scroll container. + X, + /// Specifies to use the measure of progress along the vertical axis of the scroll container. + Y, + } +} + +impl Default for ScrollAxis { + fn default() -> Self { + ScrollAxis::Block + } +} + +/// The [view()](https://drafts.csswg.org/scroll-animations-1/#view-notation) function. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "visitor", derive(Visit))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] +pub struct ViewTimeline { + /// Specifies which axis of the scroll container to use as the progress for the timeline. + pub axis: ScrollAxis, + /// Provides an adjustment of the view progress visibility range. + pub inset: Size2D, +} + +impl<'i> Parse<'i> for ViewTimeline { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { + let mut axis = None; + let mut inset = None; + loop { + if axis.is_none() { + axis = input.try_parse(ScrollAxis::parse).ok(); + } + + if inset.is_none() { + inset = input.try_parse(Size2D::parse).ok(); + if inset.is_some() { + continue; + } + } + break; + } + + Ok(ViewTimeline { + axis: axis.unwrap_or_default(), + inset: inset.unwrap_or(Size2D(LengthPercentageOrAuto::Auto, LengthPercentageOrAuto::Auto)), + }) + } +} + +impl ToCss for ViewTimeline { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + let mut needs_space = false; + if self.axis != ScrollAxis::default() { + self.axis.to_css(dest)?; + needs_space = true; + } + + if self.inset.0 != LengthPercentageOrAuto::Auto || self.inset.1 != LengthPercentageOrAuto::Auto { + if needs_space { + dest.write_char(' ')?; + } + self.inset.to_css(dest)?; + } + + Ok(()) + } +} + +impl<'i> Parse<'i> for AnimationTimeline<'i> { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { + if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { + return Ok(AnimationTimeline::Auto); + } + + if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { + return Ok(AnimationTimeline::None); + } + + if let Ok(name) = input.try_parse(DashedIdent::parse) { + return Ok(AnimationTimeline::DashedIdent(name)); + } + + let location = input.current_source_location(); + let f = input.expect_function()?.clone(); + input.parse_nested_block(move |input| { + match_ignore_ascii_case! { &f, + "scroll" => ScrollTimeline::parse(input).map(AnimationTimeline::Scroll), + "view" => ViewTimeline::parse(input).map(AnimationTimeline::View), + _ => Err(location.new_custom_error(ParserError::UnexpectedToken(crate::properties::custom::Token::Function(f.into())))) + } + }) + } +} + +impl<'i> ToCss for AnimationTimeline<'i> { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + match self { + AnimationTimeline::Auto => dest.write_str("auto"), + AnimationTimeline::None => dest.write_str("none"), + AnimationTimeline::DashedIdent(name) => name.to_css(dest), + AnimationTimeline::Scroll(scroll) => { + dest.write_str("scroll(")?; + scroll.to_css(dest)?; + dest.write_char(')') + } + AnimationTimeline::View(view) => { + dest.write_str("view(")?; + view.to_css(dest)?; + dest.write_char(')') + } + } + } +} + define_list_shorthand! { /// A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property. pub struct Animation<'i>(VendorPrefix) { @@ -220,6 +473,8 @@ define_list_shorthand! { delay: AnimationDelay(Time, VendorPrefix), /// The animation fill mode. fill_mode: AnimationFillMode(AnimationFillMode, VendorPrefix), + /// The animation timeline. + timeline: AnimationTimeline(AnimationTimeline<'i>), } } @@ -233,6 +488,7 @@ impl<'i> Parse<'i> for Animation<'i> { let mut play_state = None; let mut delay = None; let mut fill_mode = None; + let mut timeline = None; macro_rules! parse_prop { ($var: ident, $type: ident) => { @@ -254,6 +510,7 @@ impl<'i> Parse<'i> for Animation<'i> { parse_prop!(fill_mode, AnimationFillMode); parse_prop!(play_state, AnimationPlayState); parse_prop!(name, AnimationName); + parse_prop!(timeline, AnimationTimeline); break; } @@ -266,6 +523,7 @@ impl<'i> Parse<'i> for Animation<'i> { play_state: play_state.unwrap_or(AnimationPlayState::Running), delay: delay.unwrap_or(Time::Seconds(0.0)), fill_mode: fill_mode.unwrap_or(AnimationFillMode::None), + timeline: timeline.unwrap_or(AnimationTimeline::Auto), }) } } @@ -321,6 +579,11 @@ impl<'i> ToCss for Animation<'i> { // Chrome does not yet support strings, however. self.name.to_css(dest)?; + if self.name != AnimationName::None && self.timeline != AnimationTimeline::default() { + dest.write_char(' ')?; + self.timeline.to_css(dest)?; + } + Ok(()) } } @@ -338,6 +601,7 @@ pub(crate) struct AnimationHandler<'i> { play_states: Option<(SmallVec<[AnimationPlayState; 1]>, VendorPrefix)>, delays: Option<(SmallVec<[Time; 1]>, VendorPrefix)>, fill_modes: Option<(SmallVec<[AnimationFillMode; 1]>, VendorPrefix)>, + timelines: Option<[AnimationTimeline<'i>; 1]>>, has_any: bool, } @@ -384,6 +648,10 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> { Property::AnimationPlayState(val, vp) => property!(play_states, val, vp), Property::AnimationDelay(val, vp) => property!(delays, val, vp), Property::AnimationFillMode(val, vp) => property!(fill_modes, val, vp), + Property::AnimationTimeline(val) => { + self.timelines = Some(val.clone()); + self.has_any = true; + } Property::Animation(val, vp) => { let names = val.iter().map(|b| b.name.clone()).collect(); maybe_flush!(names, &names, vp); @@ -409,6 +677,8 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> { let fill_modes = val.iter().map(|b| b.fill_mode.clone()).collect(); maybe_flush!(fill_modes, &fill_modes, vp); + self.timelines = Some(val.iter().map(|b| b.timeline.clone()).collect()); + property!(names, &names, vp); property!(durations, &durations, vp); property!(timing_functions, &timing_functions, vp); @@ -433,6 +703,7 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> { && AnimationFillMode::parse_string(&id).is_err() && !EasingFunction::is_ident(&id) && id.as_ref() != "infinite" + && id.as_ref() != "auto" { *token = TokenOrValue::AnimationName(AnimationName::Ident(CustomIdent(id.clone()))); } @@ -477,6 +748,7 @@ impl<'i> AnimationHandler<'i> { let mut play_states = std::mem::take(&mut self.play_states); let mut delays = std::mem::take(&mut self.delays); let mut fill_modes = std::mem::take(&mut self.fill_modes); + let mut timelines_value = std::mem::take(&mut self.timelines); if let ( Some((names, names_vp)), @@ -507,6 +779,15 @@ impl<'i> AnimationHandler<'i> { & *play_states_vp & *delays_vp & *fill_modes_vp; + let mut timelines = if let Some(timelines) = &mut timelines_value { + Cow::Borrowed(timelines) + } else if !intersection.contains(VendorPrefix::None) { + // Prefixed animation shorthand does not support animation-timeline + Cow::Owned(std::iter::repeat(AnimationTimeline::Auto).take(len).collect()) + } else { + Cow::Owned(SmallVec::new()) + }; + if !intersection.is_empty() && durations.len() == len && timing_functions.len() == len @@ -515,7 +796,19 @@ impl<'i> AnimationHandler<'i> { && play_states.len() == len && delays.len() == len && fill_modes.len() == len + && timelines.len() == len { + let timeline_property = if timelines.iter().any(|t| *t != AnimationTimeline::Auto) + && (intersection != VendorPrefix::None + || !context + .targets + .is_compatible(crate::compat::Feature::AnimationTimelineShorthand)) + { + Some(Property::AnimationTimeline(timelines.clone().into_owned())) + } else { + None + }; + let animations = izip!( names.drain(..), durations.drain(..), @@ -524,10 +817,21 @@ impl<'i> AnimationHandler<'i> { directions.drain(..), play_states.drain(..), delays.drain(..), - fill_modes.drain(..) + fill_modes.drain(..), + timelines.to_mut().drain(..) ) .map( - |(name, duration, timing_function, iteration_count, direction, play_state, delay, fill_mode)| { + |( + name, + duration, + timing_function, + iteration_count, + direction, + play_state, + delay, + fill_mode, + timeline, + )| { Animation { name, duration, @@ -537,6 +841,11 @@ impl<'i> AnimationHandler<'i> { play_state, delay, fill_mode, + timeline: if timeline_property.is_some() { + AnimationTimeline::Auto + } else { + timeline + }, } }, ) @@ -551,6 +860,11 @@ impl<'i> AnimationHandler<'i> { play_states_vp.remove(intersection); delays_vp.remove(intersection); fill_modes_vp.remove(intersection); + + if let Some(p) = timeline_property { + dest.push(p); + } + timelines_value = None; } } @@ -573,6 +887,10 @@ impl<'i> AnimationHandler<'i> { prop!(play_states, AnimationPlayState); prop!(delays, AnimationDelay); prop!(fill_modes, AnimationFillMode); + + if let Some(val) = timelines_value { + dest.push(Property::AnimationTimeline(val)); + } } } @@ -587,6 +905,8 @@ fn is_animation_property(property_id: &PropertyId) -> bool { | PropertyId::AnimationPlayState(_) | PropertyId::AnimationDelay(_) | PropertyId::AnimationFillMode(_) + | PropertyId::AnimationComposition + | PropertyId::AnimationTimeline | PropertyId::Animation(_) => true, _ => false, } diff --git a/src/properties/custom.rs b/src/properties/custom.rs index c3621af6..3198422b 100644 --- a/src/properties/custom.rs +++ b/src/properties/custom.rs @@ -27,9 +27,9 @@ use crate::visitor::Visit; use cssparser::color::parse_hash_color; use cssparser::*; +use super::AnimationName; #[cfg(feature = "serde")] use crate::serialization::ValueWrapper; -use super::AnimationName; /// A CSS custom property, representing any unknown property. #[derive(Debug, Clone, PartialEq)] diff --git a/src/properties/mod.rs b/src/properties/mod.rs index 98756755..38ff8622 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -1491,6 +1491,8 @@ define_properties! { "animation-play-state": AnimationPlayState(SmallVec<[AnimationPlayState; 1]>, VendorPrefix) / WebKit / Moz / O, "animation-delay": AnimationDelay(SmallVec<[Time; 1]>, VendorPrefix) / WebKit / Moz / O, "animation-fill-mode": AnimationFillMode(SmallVec<[AnimationFillMode; 1]>, VendorPrefix) / WebKit / Moz / O, + "animation-composition": AnimationComposition(SmallVec<[AnimationComposition; 1]>), + "animation-timeline": AnimationTimeline(SmallVec<[AnimationTimeline<'i>; 1]>), "animation": Animation(AnimationList<'i>, VendorPrefix) / WebKit / Moz / O shorthand: true, // https://drafts.csswg.org/css-transforms-2/ From 81d21b9f5201c6eb8365fbb4fa81cbd947c3474f Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Fri, 17 May 2024 11:57:10 -0700 Subject: [PATCH 015/156] v1.25.0 --- Cargo.lock | 6 +++--- Cargo.toml | 4 ++-- napi/Cargo.toml | 4 ++-- node/Cargo.toml | 2 +- package.json | 2 +- selectors/Cargo.toml | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eed17d06..b1b97132 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -766,7 +766,7 @@ dependencies = [ [[package]] name = "lightningcss" -version = "1.0.0-alpha.55" +version = "1.0.0-alpha.56" dependencies = [ "ahash 0.8.7", "assert_cmd", @@ -812,7 +812,7 @@ dependencies = [ [[package]] name = "lightningcss-napi" -version = "0.1.0" +version = "0.2.0" dependencies = [ "crossbeam-channel", "cssparser", @@ -1006,7 +1006,7 @@ checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" [[package]] name = "parcel_selectors" -version = "0.26.4" +version = "0.26.5" dependencies = [ "bitflags 2.4.1", "cssparser", diff --git a/Cargo.toml b/Cargo.toml index a298e0b1..e77fa5a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ members = [ [package] authors = ["Devon Govett "] name = "lightningcss" -version = "1.0.0-alpha.55" +version = "1.0.0-alpha.56" description = "A CSS parser, transformer, and minifier" license = "MPL-2.0" edition = "2021" @@ -51,7 +51,7 @@ substitute_variables = ["visitor", "into_owned"] serde = { version = "1.0.201", features = ["derive"], optional = true } cssparser = "0.33.0" cssparser-color = "0.1.0" -parcel_selectors = { version = "0.26.4", path = "./selectors" } +parcel_selectors = { version = "0.26.5", path = "./selectors" } itertools = "0.10.1" smallvec = { version = "1.7.0", features = ["union"] } bitflags = "2.2.1" diff --git a/napi/Cargo.toml b/napi/Cargo.toml index b296c3b6..d8415acd 100644 --- a/napi/Cargo.toml +++ b/napi/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["Devon Govett "] name = "lightningcss-napi" -version = "0.1.0" +version = "0.2.0" description = "Node-API bindings for Lightning CSS" license = "MPL-2.0" repository = "https://github.com/parcel-bundler/lightningcss" @@ -16,7 +16,7 @@ bundler = ["dep:crossbeam-channel", "dep:rayon"] serde = { version = "1.0.201", features = ["derive"] } serde_bytes = "0.11.5" cssparser = "0.33.0" -lightningcss = { version = "1.0.0-alpha.54", path = "../", features = ["nodejs", "serde"] } +lightningcss = { version = "1.0.0-alpha.55", path = "../", features = ["nodejs", "serde"] } parcel_sourcemap = { version = "2.1.1", features = ["json"] } serde-detach = "0.0.1" smallvec = { version = "1.7.0", features = ["union"] } diff --git a/node/Cargo.toml b/node/Cargo.toml index 4f9e8b75..bb472910 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -9,7 +9,7 @@ publish = false crate-type = ["cdylib"] [dependencies] -lightningcss-napi = { version = "0.1.0", path = "../napi", features = ["bundler", "visitor"] } +lightningcss-napi = { version = "0.2.0", path = "../napi", features = ["bundler", "visitor"] } napi = {version = "2.15.4", default-features = false, features = ["compat-mode"]} napi-derive = "2" diff --git a/package.json b/package.json index 740531f3..fa6a3b78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lightningcss", - "version": "1.24.1", + "version": "1.25.0", "license": "MPL-2.0", "description": "A CSS parser, transformer, and minifier written in Rust", "main": "node/index.js", diff --git a/selectors/Cargo.toml b/selectors/Cargo.toml index 481c468b..8cad46af 100644 --- a/selectors/Cargo.toml +++ b/selectors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "parcel_selectors" -version = "0.26.4" +version = "0.26.5" authors = ["The Servo Project Developers"] documentation = "https://docs.rs/parcel_selectors/" description = "CSS Selectors matching for Rust - forked for lightningcss" From 60622a1af51c0c6b83582fde60a244bfd9edf963 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Fri, 24 May 2024 22:44:53 -0700 Subject: [PATCH 016/156] fix order of properties with all shorthand fixes #746 --- Cargo.lock | 1 - Cargo.toml | 1 - node/test/visitor.test.mjs | 4 ++-- src/declaration.rs | 39 +++++++++++++++++++------------------- src/lib.rs | 21 ++++++++++++-------- 5 files changed, 35 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b1b97132..9cb99076 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -781,7 +781,6 @@ dependencies = [ "dashmap", "data-encoding", "getrandom", - "indexmap 2.2.6", "indoc", "itertools 0.10.5", "jemallocator", diff --git a/Cargo.toml b/Cargo.toml index e77fa5a3..a71057a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,6 @@ const-str = "0.3.1" pathdiff = "0.2.1" ahash = "0.8.7" paste = "1.0.12" -indexmap = "2.2.6" # CLI deps atty = { version = "0.2", optional = true } clap = { version = "3.0.6", features = ["derive"], optional = true } diff --git a/node/test/visitor.test.mjs b/node/test/visitor.test.mjs index 87ee208d..3a42a696 100644 --- a/node/test/visitor.test.mjs +++ b/node/test/visitor.test.mjs @@ -116,7 +116,7 @@ test('custom units', () => { } }); - assert.equal(res.code.toString(), '.foo{font-size:calc(3*var(--step));--step:.25rem}'); + assert.equal(res.code.toString(), '.foo{--step:.25rem;font-size:calc(3*var(--step))}'); }); test('design tokens', () => { @@ -822,7 +822,7 @@ test('dashed idents', () => { } }); - assert.equal(res.code.toString(), '.foo{color:var(--prefix-foo);--prefix-foo:#ff0}'); + assert.equal(res.code.toString(), '.foo{--prefix-foo:#ff0;color:var(--prefix-foo)}'); }); test('custom idents', () => { diff --git a/src/declaration.rs b/src/declaration.rs index 501e2f8c..da97e37d 100644 --- a/src/declaration.rs +++ b/src/declaration.rs @@ -1,6 +1,7 @@ //! CSS declarations. use std::borrow::Cow; +use std::collections::HashMap; use std::ops::Range; use crate::context::{DeclarationContext, PropertyHandlerContext}; @@ -33,14 +34,13 @@ use crate::properties::{ transition::TransitionHandler, ui::ColorSchemeHandler, }; -use crate::properties::{CSSWideKeyword, Property, PropertyId}; +use crate::properties::{Property, PropertyId}; use crate::traits::{PropertyHandler, ToCss}; use crate::values::ident::DashedIdent; use crate::values::string::CowArcStr; #[cfg(feature = "visitor")] use crate::visitor::Visit; use cssparser::*; -use indexmap::IndexMap; /// A CSS declaration block. /// @@ -516,10 +516,9 @@ pub(crate) struct DeclarationHandler<'i> { color_scheme: ColorSchemeHandler, fallback: FallbackHandler, prefix: PrefixHandler, - all: Option, direction: Option, unicode_bidi: Option, - custom_properties: IndexMap<'i>, CustomProperty<'i>>, + custom_properties: HashMap<'i>, usize>, decls: DeclarationList<'i>, } @@ -571,13 +570,18 @@ impl<'i> DeclarationHandler<'i> { } if let CustomPropertyName::Custom(name) = &custom.name { - if let Some(prev) = self.custom_properties.get_mut(name) { - if prev.value == custom.value { + if let Some(index) = self.custom_properties.get(name) { + if self.decls[*index] == *property { return true; } - *prev = custom.clone(); + let mut custom = custom.clone(); + self.add_conditional_fallbacks(&mut custom, context); + self.decls[*index] = Property::Custom(custom); } else { - self.custom_properties.insert(name.clone(), custom.clone()); + self.custom_properties.insert(name.clone(), self.decls.len()); + let mut custom = custom.clone(); + self.add_conditional_fallbacks(&mut custom, context); + self.decls.push(Property::Custom(custom)); } return true; @@ -600,13 +604,17 @@ impl<'i> DeclarationHandler<'i> { true } Property::All(keyword) => { - *self = DeclarationHandler { - custom_properties: std::mem::take(&mut self.custom_properties), + let mut handler = DeclarationHandler { unicode_bidi: self.unicode_bidi.clone(), direction: self.direction.clone(), - all: Some(keyword.clone()), ..Default::default() }; + for (key, index) in self.custom_properties.drain() { + handler.custom_properties.insert(key, handler.decls.len()); + handler.decls.push(self.decls[index].clone()); + } + handler.decls.push(Property::All(keyword.clone())); + *self = handler; true } _ => false, @@ -633,20 +641,12 @@ impl<'i> DeclarationHandler<'i> { } pub fn finalize(&mut self, context: &mut PropertyHandlerContext<'i, '_>) { - // Always place the `all` property first. Previous properties will have been omitted. - if let Some(all) = std::mem::take(&mut self.all) { - self.decls.push(Property::All(all)); - } if let Some(direction) = std::mem::take(&mut self.direction) { self.decls.push(Property::Direction(direction)); } if let Some(unicode_bidi) = std::mem::take(&mut self.unicode_bidi) { self.decls.push(Property::UnicodeBidi(unicode_bidi)); } - for (_, mut value) in std::mem::take(&mut self.custom_properties) { - self.add_conditional_fallbacks(&mut value, context); - self.decls.push(Property::Custom(value)); - } self.background.finalize(&mut self.decls, context); self.border.finalize(&mut self.decls, context); @@ -675,5 +675,6 @@ impl<'i> DeclarationHandler<'i> { self.color_scheme.finalize(&mut self.decls, context); self.fallback.finalize(&mut self.decls, context); self.prefix.finalize(&mut self.decls, context); + self.custom_properties.clear(); } } diff --git a/src/lib.rs b/src/lib.rs index d39c6ac4..27a7df16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21583,26 +21583,26 @@ mod tests { indoc! {r#" @keyframes foo { from { - opacity: 0; --custom: #ff0; + opacity: 0; } to { - opacity: 1; --custom: #ee00be; + opacity: 1; } } @supports (color: lab(0% 0 0)) { @keyframes foo { from { - opacity: 0; --custom: #ff0; + opacity: 0; } to { - opacity: 1; --custom: lab(50.998% 125.506 -50.7078); + opacity: 1; } } } @@ -23736,8 +23736,8 @@ mod tests { } .EgL3uq_foo { - color: var(--foo); --foo: red; + color: var(--foo); } "#}, map! { @@ -23786,10 +23786,10 @@ mod tests { } .EgL3uq_foo { - color: var(--EgL3uq_foo); - font-palette: --EgL3uq_Cooler; --EgL3uq_foo: red; --EgL3uq_bar: green; + color: var(--EgL3uq_foo); + font-palette: --EgL3uq_Cooler; } .EgL3uq_bar { @@ -27646,7 +27646,7 @@ mod tests { ); minify_test( ".foo { --test: red; all: revert-layer }", - ".foo{all:revert-layer;--test:red}", + ".foo{--test:red;all:revert-layer}", ); minify_test( ".foo { unicode-bidi: embed; all: revert-layer }", @@ -27660,5 +27660,10 @@ mod tests { ".foo { direction: rtl; all: revert-layer; direction: ltr }", ".foo{all:revert-layer;direction:ltr}", ); + minify_test(".foo { background: var(--foo); all: unset; }", ".foo{all:unset}"); + minify_test( + ".foo { all: unset; background: var(--foo); }", + ".foo{all:unset;background:var(--foo)}", + ); } } From fa6c015317e5cca29fa8a09b12d09ac53dcfbc77 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Fri, 24 May 2024 22:48:31 -0700 Subject: [PATCH 017/156] v1.25.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9cb99076..a5d8b853 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -766,7 +766,7 @@ dependencies = [ [[package]] name = "lightningcss" -version = "1.0.0-alpha.56" +version = "1.0.0-alpha.57" dependencies = [ "ahash 0.8.7", "assert_cmd", diff --git a/Cargo.toml b/Cargo.toml index a71057a7..7036f761 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ members = [ [package] authors = ["Devon Govett "] name = "lightningcss" -version = "1.0.0-alpha.56" +version = "1.0.0-alpha.57" description = "A CSS parser, transformer, and minifier" license = "MPL-2.0" edition = "2021" diff --git a/package.json b/package.json index fa6a3b78..91ffd35f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lightningcss", - "version": "1.25.0", + "version": "1.25.1", "license": "MPL-2.0", "description": "A CSS parser, transformer, and minifier written in Rust", "main": "node/index.js", From 76e31cf963cc0ef6ace0415664cc987c6977b07f Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 2 Jun 2024 21:17:43 -0700 Subject: [PATCH 018/156] Use macros to derive implementations of Parse and ToCss for many enums --- Cargo.lock | 1 + Cargo.toml | 4 +- derive/Cargo.toml | 1 + derive/src/lib.rs | 295 ++------------------------------- derive/src/parse.rs | 213 ++++++++++++++++++++++++ derive/src/to_css.rs | 156 +++++++++++++++++ derive/src/visit.rs | 292 ++++++++++++++++++++++++++++++++ src/macros.rs | 57 +------ src/properties/align.rs | 203 +++-------------------- src/properties/animation.rs | 182 ++++++-------------- src/properties/background.rs | 24 +-- src/properties/border.rs | 33 +--- src/properties/border_image.rs | 34 +--- src/properties/contain.rs | 6 +- src/properties/custom.rs | 20 +-- src/properties/display.rs | 59 ++----- src/properties/flex.rs | 16 +- src/properties/font.rs | 182 +++----------------- src/properties/grid.rs | 58 +------ src/properties/list.rs | 171 +++++++------------ src/properties/masking.rs | 77 +++------ src/properties/outline.rs | 25 +-- src/properties/position.rs | 25 +-- src/properties/size.rs | 4 +- src/properties/svg.rs | 111 ++----------- src/properties/text.rs | 161 +++++------------- src/properties/transform.rs | 38 +---- src/properties/ui.rs | 97 +++++------ src/rules/keyframes.rs | 20 +-- src/rules/page.rs | 32 ++-- src/traits.rs | 37 +++++ src/values/calc.rs | 8 +- src/values/color.rs | 1 + src/values/easing.rs | 16 +- src/values/gradient.rs | 42 +---- src/values/image.rs | 38 +---- src/values/length.rs | 62 +------ src/values/percentage.rs | 32 +--- src/values/shape.rs | 30 +--- 39 files changed, 1100 insertions(+), 1763 deletions(-) create mode 100644 derive/src/parse.rs create mode 100644 derive/src/to_css.rs create mode 100644 derive/src/visit.rs diff --git a/Cargo.lock b/Cargo.lock index a5d8b853..07d5f7e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -804,6 +804,7 @@ dependencies = [ name = "lightningcss-derive" version = "1.0.0-alpha.42" dependencies = [ + "convert_case", "proc-macro2", "quote", "syn 1.0.109", diff --git a/Cargo.toml b/Cargo.toml index 7036f761..75bc397c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ jsonschema = ["schemars", "serde", "parcel_selectors/jsonschema"] nodejs = ["dep:serde"] serde = ["dep:serde", "smallvec/serde", "cssparser/serde", "parcel_selectors/serde", "into_owned"] sourcemap = ["parcel_sourcemap"] -visitor = ["lightningcss-derive"] +visitor = [] into_owned = ["static-self", "static-self/smallvec", "parcel_selectors/into_owned"] substitute_variables = ["visitor", "into_owned"] @@ -69,7 +69,7 @@ browserslist-rs = { version = "0.15.0", optional = true } rayon = { version = "1.5.1", optional = true } dashmap = { version = "5.0.0", optional = true } serde_json = { version = "1.0.78", optional = true } -lightningcss-derive = { version = "=1.0.0-alpha.42", path = "./derive", optional = true } +lightningcss-derive = { version = "=1.0.0-alpha.42", path = "./derive" } schemars = { version = "0.8.19", features = ["smallvec"], optional = true } static-self = { version = "0.1.0", path = "static-self", optional = true } diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 1864748e..e1f82c70 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -14,3 +14,4 @@ proc-macro = true syn = { version = "1.0", features = ["extra-traits"] } quote = "1.0" proc-macro2 = "1.0" +convert_case = "0.6.0" diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 00b77a26..12241491 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -1,293 +1,20 @@ -use std::collections::HashSet; +use proc_macro::TokenStream; -use proc_macro::{self, TokenStream}; -use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::quote; -use syn::{ - parse::Parse, parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Field, Fields, - GenericParam, Generics, Ident, Member, Token, Type, Visibility, -}; +mod parse; +mod to_css; +mod visit; #[proc_macro_derive(Visit, attributes(visit, skip_visit, skip_type, visit_types))] pub fn derive_visit_children(input: TokenStream) -> TokenStream { - let DeriveInput { - ident, - data, - generics, - attrs, - .. - } = parse_macro_input!(input); - - let options: Vec = attrs - .iter() - .filter_map(|attr| { - if attr.path.is_ident("visit") { - let opts: VisitOptions = attr.parse_args().unwrap(); - Some(opts) - } else { - None - } - }) - .collect(); - - let visit_types = if let Some(attr) = attrs.iter().find(|attr| attr.path.is_ident("visit_types")) { - let types: VisitTypes = attr.parse_args().unwrap(); - let types = types.types; - Some(quote! { crate::visit_types!(#(#types)|*) }) - } else { - None - }; - - if options.is_empty() { - derive(&ident, &data, &generics, None, visit_types) - } else { - options - .into_iter() - .map(|options| derive(&ident, &data, &generics, Some(options), visit_types.clone())) - .collect() - } -} - -fn derive( - ident: &Ident, - data: &Data, - generics: &Generics, - options: Option, - visit_types: Option, -) -> TokenStream { - let mut impl_generics = generics.clone(); - let mut type_defs = quote! {}; - let generics = if let Some(VisitOptions { - generic: Some(generic), .. - }) = &options - { - let mappings = generics - .type_params() - .zip(generic.type_params()) - .map(|(a, b)| quote! { type #a = #b; }); - type_defs = quote! { #(#mappings)* }; - impl_generics.params.clear(); - generic - } else { - &generics - }; - - if impl_generics.lifetimes().next().is_none() { - impl_generics.params.insert(0, parse_quote! { 'i }) - } - - let lifetime = impl_generics.lifetimes().next().unwrap().clone(); - let t = impl_generics.type_params().find(|g| &g.ident.to_string() == "R"); - let v = quote! { __V }; - let t = if let Some(t) = t { - GenericParam::Type(t.ident.clone().into()) - } else { - let t: GenericParam = parse_quote! { __T }; - impl_generics - .params - .push(parse_quote! { #t: crate::visitor::Visit<#lifetime, __T, #v> }); - t - }; - - impl_generics - .params - .push(parse_quote! { #v: ?Sized + crate::visitor::Visitor<#lifetime, #t> }); - - for ty in generics.type_params() { - let name = &ty.ident; - impl_generics.make_where_clause().predicates.push(parse_quote! { - #name: Visit<#lifetime, #t, #v> - }) - } - - let mut seen_types = HashSet::new(); - let mut child_types = Vec::new(); - let mut visit = Vec::new(); - match data { - Data::Struct(s) => { - for ( - index, - Field { - vis, ty, ident, attrs, .. - }, - ) in s.fields.iter().enumerate() - { - if attrs.iter().any(|attr| attr.path.is_ident("skip_visit")) { - continue; - } - - if matches!(ty, Type::Reference(_)) || !matches!(vis, Visibility::Public(..)) { - continue; - } - - if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) { - seen_types.insert(ty.clone()); - child_types.push(quote! { - <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits() - }); - } - - let name = ident - .as_ref() - .map_or_else(|| Member::Unnamed(index.into()), |ident| Member::Named(ident.clone())); - visit.push(quote! { self.#name.visit(visitor)?; }) - } - } - Data::Enum(DataEnum { variants, .. }) => { - let variants = variants - .iter() - .map(|variant| { - let name = &variant.ident; - let mut field_names = Vec::new(); - let mut visit_fields = Vec::new(); - for (index, Field { ty, ident, attrs, .. }) in variant.fields.iter().enumerate() { - let name = ident.as_ref().map_or_else( - || Ident::new(&format!("_{}", index), Span::call_site()), - |ident| ident.clone(), - ); - field_names.push(name.clone()); - - if matches!(ty, Type::Reference(_)) { - continue; - } - - if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) && !skip_type(&variant.attrs) - { - seen_types.insert(ty.clone()); - child_types.push(quote! { - <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits() - }); - } - - visit_fields.push(quote! { #name.visit(visitor)?; }) - } - - match variant.fields { - Fields::Unnamed(_) => { - quote! { - Self::#name(#(#field_names),*) => { - #(#visit_fields)* - } - } - } - Fields::Named(_) => { - quote! { - Self::#name { #(#field_names),* } => { - #(#visit_fields)* - } - } - } - Fields::Unit => quote! {}, - } - }) - .collect::(); - - visit.push(quote! { - match self { - #variants - _ => {} - } - }) - } - _ => {} - } - - if visit_types.is_none() && child_types.is_empty() { - child_types.push(quote! { crate::visitor::VisitTypes::empty().bits() }); - } - - let (_, ty_generics, _) = generics.split_for_impl(); - let (impl_generics, _, where_clause) = impl_generics.split_for_impl(); - - let self_visit = if let Some(VisitOptions { - visit: Some(visit), - kind: Some(kind), - .. - }) = &options - { - child_types.push(quote! { crate::visitor::VisitTypes::#kind.bits() }); - - quote! { - fn visit(&mut self, visitor: &mut #v) -> Result<(), #v::Error> { - if visitor.visit_types().contains(crate::visitor::VisitTypes::#kind) { - visitor.#visit(self) - } else { - self.visit_children(visitor) - } - } - } - } else { - quote! {} - }; - - let child_types = visit_types.unwrap_or_else(|| { - quote! { - { - #type_defs - crate::visitor::VisitTypes::from_bits_retain(#(#child_types)|*) - } - } - }); - - let output = quote! { - impl #impl_generics Visit<#lifetime, #t, #v> for #ident #ty_generics #where_clause { - const CHILD_TYPES: crate::visitor::VisitTypes = #child_types; - - #self_visit - - fn visit_children(&mut self, visitor: &mut #v) -> Result<(), #v::Error> { - if !<#lifetime, #t, #v>>::CHILD_TYPES.intersects(visitor.visit_types()) { - return Ok(()) - } - - #(#visit)* - - Ok(()) - } - } - }; - - output.into() -} - -fn skip_type(attrs: &Vec) -> bool { - attrs.iter().any(|attr| attr.path.is_ident("skip_type")) -} - -struct VisitOptions { - visit: Option, - kind: Option, - generic: Option, -} - -impl Parse for VisitOptions { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let (visit, kind, comma) = if input.peek(Ident) { - let visit: Ident = input.parse()?; - let _: Token![,] = input.parse()?; - let kind: Ident = input.parse()?; - let comma: Result = input.parse(); - (Some(visit), Some(kind), comma.is_ok()) - } else { - (None, None, true) - }; - let generic: Option = if comma { Some(input.parse()?) } else { None }; - Ok(Self { visit, kind, generic }) - } + visit::derive_visit_children(input) } -struct VisitTypes { - types: Vec, +#[proc_macro_derive(Parse, attributes(css))] +pub fn derive_parse(input: TokenStream) -> TokenStream { + parse::derive_parse(input) } -impl Parse for VisitTypes { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let first: Ident = input.parse()?; - let mut types = vec![first]; - while input.parse::().is_ok() { - let id: Ident = input.parse()?; - types.push(id); - } - Ok(Self { types }) - } +#[proc_macro_derive(ToCss, attributes(css))] +pub fn derive_to_css(input: TokenStream) -> TokenStream { + to_css::derive_to_css(input) } diff --git a/derive/src/parse.rs b/derive/src/parse.rs new file mode 100644 index 00000000..995b344e --- /dev/null +++ b/derive/src/parse.rs @@ -0,0 +1,213 @@ +use convert_case::Casing; +use proc_macro::{self, TokenStream}; +use proc_macro2::{Literal, Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{ + parse::Parse, parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Fields, Ident, Token, +}; + +pub fn derive_parse(input: TokenStream) -> TokenStream { + let DeriveInput { + ident, + data, + mut generics, + attrs, + .. + } = parse_macro_input!(input); + let opts = CssOptions::parse_attributes(&attrs).unwrap(); + let cloned_generics = generics.clone(); + let (_, ty_generics, _) = cloned_generics.split_for_impl(); + + if generics.lifetimes().next().is_none() { + generics.params.insert(0, parse_quote! { 'i }) + } + + let lifetime = generics.lifetimes().next().unwrap().clone(); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + + let imp = match &data { + Data::Enum(data) => derive_enum(&data, &ident, &opts), + _ => todo!(), + }; + + let output = quote! { + impl #impl_generics Parse<#lifetime> for #ident #ty_generics #where_clause { + fn parse<'t>(input: &mut Parser<#lifetime, 't>) -> Result<#lifetime, ParserError<#lifetime>>> { + #imp + } + } + }; + + output.into() +} + +fn derive_enum(data: &DataEnum, ident: &Ident, opts: &CssOptions) -> TokenStream2 { + let mut idents = Vec::new(); + let mut non_idents = Vec::new(); + for (index, variant) in data.variants.iter().enumerate() { + let name = &variant.ident; + let fields = variant + .fields + .iter() + .enumerate() + .map(|(index, field)| { + field.ident.as_ref().map_or_else( + || Ident::new(&format!("_{}", index), Span::call_site()), + |ident| ident.clone(), + ) + }) + .collect::<_>>(); + + let mut expr = match &variant.fields { + Fields::Unit => { + idents.push(( + Literal::string(&variant.ident.to_string().to_case(opts.case)), + name.clone(), + )); + continue; + } + Fields::Named(_) => { + quote! { + return Ok(#ident::#name { #(#fields),* }) + } + } + Fields::Unnamed(_) => { + quote! { + return Ok(#ident::#name(#(#fields),*)) + } + } + }; + + // Group multiple ident branches together. + if !idents.is_empty() { + if idents.len() == 1 { + let (s, name) = idents.remove(0); + non_idents.push(quote! { + if input.try_parse(|input| input.expect_ident_matching(#s)).is_ok() { + return Ok(#ident::#name) + } + }); + } else { + let matches = idents + .iter() + .map(|(s, name)| { + quote! { + #s => return Ok(#ident::#name), + } + }) + .collect::<_>>(); + non_idents.push(quote! { + { + let state = input.state(); + if let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) { + cssparser::match_ignore_ascii_case! { &*ident, + #(#matches)* + _ => {} + } + input.reset(&state); + } + } + }); + idents.clear(); + } + } + + let is_last = index == data.variants.len() - 1; + + for (index, field) in variant.fields.iter().enumerate().rev() { + let ty = &field.ty; + let field_name = field.ident.as_ref().map_or_else( + || Ident::new(&format!("_{}", index), Span::call_site()), + |ident| ident.clone(), + ); + if is_last { + expr = quote! { + let #field_name = <#ty>::parse(input)?; + #expr + }; + } else { + expr = quote! { + if let Ok(#field_name) = input.try_parse(<#ty>::parse) { + #expr + } + }; + } + } + + non_idents.push(expr); + } + + let idents = if idents.is_empty() { + quote! {} + } else if idents.len() == 1 { + let (s, name) = idents.remove(0); + quote! { + input.expect_ident_matching(#s)?; + Ok(#ident::#name) + } + } else { + let idents = idents + .into_iter() + .map(|(s, name)| { + quote! { + #s => Ok(#ident::#name), + } + }) + .collect::<_>>(); + quote! { + let location = input.current_source_location(); + let ident = input.expect_ident()?; + cssparser::match_ignore_ascii_case! { &*ident, + #(#idents)* + _ => Err(location.new_unexpected_token_error( + cssparser::Token::Ident(ident.clone()) + )) + } + } + }; + + let output = quote! { + #(#non_idents)* + #idents + }; + + output.into() +} + +pub struct CssOptions { + pub case: convert_case::Case, +} + +impl CssOptions { + pub fn parse_attributes(attrs: &Vec) -> syn::Result { + for attr in attrs { + if attr.path.is_ident("css") { + let opts: CssOptions = attr.parse_args()?; + return Ok(opts); + } + } + + Ok(CssOptions { + case: convert_case::Case::Kebab, + }) + } +} + +impl Parse for CssOptions { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut case = convert_case::Case::Kebab; + while !input.is_empty() { + let k: Ident = input.parse()?; + let _: Token![=] = input.parse()?; + let v: Ident = input.parse()?; + + if k == "case" { + if v == "lower" { + case = convert_case::Case::Flat; + } + } + } + + Ok(Self { case }) + } +} diff --git a/derive/src/to_css.rs b/derive/src/to_css.rs new file mode 100644 index 00000000..739a16d4 --- /dev/null +++ b/derive/src/to_css.rs @@ -0,0 +1,156 @@ +use convert_case::Casing; +use proc_macro::{self, TokenStream}; +use proc_macro2::{Literal, Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields, Ident, Type}; + +use crate::parse::CssOptions; + +pub fn derive_to_css(input: TokenStream) -> TokenStream { + let DeriveInput { + ident, + data, + generics, + attrs, + .. + } = parse_macro_input!(input); + + let opts = CssOptions::parse_attributes(&attrs).unwrap(); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let imp = match &data { + Data::Enum(data) => derive_enum(&data, &opts), + _ => todo!(), + }; + + let output = quote! { + impl #impl_generics ToCss for #ident #ty_generics #where_clause { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + #imp + } + } + }; + + output.into() +} + +fn derive_enum(data: &DataEnum, opts: &CssOptions) -> TokenStream2 { + let variants = data + .variants + .iter() + .map(|variant| { + let name = &variant.ident; + let fields = variant + .fields + .iter() + .enumerate() + .map(|(index, field)| { + field.ident.as_ref().map_or_else( + || Ident::new(&format!("_{}", index), Span::call_site()), + |ident| ident.clone(), + ) + }) + .collect::<_>>(); + + #[derive(PartialEq)] + enum NeedsSpace { + Yes, + No, + Maybe, + } + + let mut needs_space = NeedsSpace::No; + let mut fields_iter = variant.fields.iter().zip(fields.iter()).peekable(); + let mut writes = Vec::new(); + let mut has_needs_space = false; + while let Some((field, name)) = fields_iter.next() { + writes.push(if fields.len() > 1 { + let space = match needs_space { + NeedsSpace::Yes => quote! { dest.write_char(' ')?; }, + NeedsSpace::No => quote! {}, + NeedsSpace::Maybe => { + has_needs_space = true; + quote! { + if needs_space { + dest.write_char(' ')?; + } + } + } + }; + + if is_option(&field.ty) { + needs_space = NeedsSpace::Maybe; + let after_space = if matches!(fields_iter.peek(), Some((field, _)) if !is_option(&field.ty)) { + // If the next field is non-optional, just insert the space here. + needs_space = NeedsSpace::No; + quote! { dest.write_char(' ')?; } + } else { + quote! {} + }; + quote! { + if let Some(v) = #name { + #space + v.to_css(dest)?; + #after_space + } + } + } else { + needs_space = NeedsSpace::Yes; + quote! { + #space + #name.to_css(dest)?; + } + } + } else { + quote! { #name.to_css(dest) } + }); + } + + if writes.len() > 1 { + writes.push(quote! { Ok(()) }); + } + + if has_needs_space { + writes.insert(0, quote! { let mut needs_space = false }); + } + + match variant.fields { + Fields::Unit => { + let s = Literal::string(&variant.ident.to_string().to_case(opts.case)); + quote! { + Self::#name => dest.write_str(#s) + } + } + Fields::Named(_) => { + quote! { + Self::#name { #(#fields),* } => { + #(#writes)* + } + } + } + Fields::Unnamed(_) => { + quote! { + Self::#name(#(#fields),*) => { + #(#writes)* + } + } + } + } + }) + .collect::<_>>(); + + let output = quote! { + match self { + #(#variants),* + } + }; + + output.into() +} + +fn is_option(ty: &Type) -> bool { + matches!(&ty, Type::Path(p) if p.qself.is_none() && p.path.segments.iter().next().unwrap().ident == "Option") +} diff --git a/derive/src/visit.rs b/derive/src/visit.rs new file mode 100644 index 00000000..02093364 --- /dev/null +++ b/derive/src/visit.rs @@ -0,0 +1,292 @@ +use std::collections::HashSet; + +use proc_macro::{self, TokenStream}; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{ + parse::Parse, parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Field, Fields, + GenericParam, Generics, Ident, Member, Token, Type, Visibility, +}; + +pub fn derive_visit_children(input: TokenStream) -> TokenStream { + let DeriveInput { + ident, + data, + generics, + attrs, + .. + } = parse_macro_input!(input); + + let options: Vec = attrs + .iter() + .filter_map(|attr| { + if attr.path.is_ident("visit") { + let opts: VisitOptions = attr.parse_args().unwrap(); + Some(opts) + } else { + None + } + }) + .collect(); + + let visit_types = if let Some(attr) = attrs.iter().find(|attr| attr.path.is_ident("visit_types")) { + let types: VisitTypes = attr.parse_args().unwrap(); + let types = types.types; + Some(quote! { crate::visit_types!(#(#types)|*) }) + } else { + None + }; + + if options.is_empty() { + derive(&ident, &data, &generics, None, visit_types) + } else { + options + .into_iter() + .map(|options| derive(&ident, &data, &generics, Some(options), visit_types.clone())) + .collect() + } +} + +fn derive( + ident: &Ident, + data: &Data, + generics: &Generics, + options: Option, + visit_types: Option, +) -> TokenStream { + let mut impl_generics = generics.clone(); + let mut type_defs = quote! {}; + let generics = if let Some(VisitOptions { + generic: Some(generic), .. + }) = &options + { + let mappings = generics + .type_params() + .zip(generic.type_params()) + .map(|(a, b)| quote! { type #a = #b; }); + type_defs = quote! { #(#mappings)* }; + impl_generics.params.clear(); + generic + } else { + &generics + }; + + if impl_generics.lifetimes().next().is_none() { + impl_generics.params.insert(0, parse_quote! { 'i }) + } + + let lifetime = impl_generics.lifetimes().next().unwrap().clone(); + let t = impl_generics.type_params().find(|g| &g.ident.to_string() == "R"); + let v = quote! { __V }; + let t = if let Some(t) = t { + GenericParam::Type(t.ident.clone().into()) + } else { + let t: GenericParam = parse_quote! { __T }; + impl_generics + .params + .push(parse_quote! { #t: crate::visitor::Visit<#lifetime, __T, #v> }); + t + }; + + impl_generics + .params + .push(parse_quote! { #v: ?Sized + crate::visitor::Visitor<#lifetime, #t> }); + + for ty in generics.type_params() { + let name = &ty.ident; + impl_generics.make_where_clause().predicates.push(parse_quote! { + #name: Visit<#lifetime, #t, #v> + }) + } + + let mut seen_types = HashSet::new(); + let mut child_types = Vec::new(); + let mut visit = Vec::new(); + match data { + Data::Struct(s) => { + for ( + index, + Field { + vis, ty, ident, attrs, .. + }, + ) in s.fields.iter().enumerate() + { + if attrs.iter().any(|attr| attr.path.is_ident("skip_visit")) { + continue; + } + + if matches!(ty, Type::Reference(_)) || !matches!(vis, Visibility::Public(..)) { + continue; + } + + if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) { + seen_types.insert(ty.clone()); + child_types.push(quote! { + <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits() + }); + } + + let name = ident + .as_ref() + .map_or_else(|| Member::Unnamed(index.into()), |ident| Member::Named(ident.clone())); + visit.push(quote! { self.#name.visit(visitor)?; }) + } + } + Data::Enum(DataEnum { variants, .. }) => { + let variants = variants + .iter() + .map(|variant| { + let name = &variant.ident; + let mut field_names = Vec::new(); + let mut visit_fields = Vec::new(); + for (index, Field { ty, ident, attrs, .. }) in variant.fields.iter().enumerate() { + let name = ident.as_ref().map_or_else( + || Ident::new(&format!("_{}", index), Span::call_site()), + |ident| ident.clone(), + ); + field_names.push(name.clone()); + + if matches!(ty, Type::Reference(_)) { + continue; + } + + if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) && !skip_type(&variant.attrs) + { + seen_types.insert(ty.clone()); + child_types.push(quote! { + <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits() + }); + } + + visit_fields.push(quote! { #name.visit(visitor)?; }) + } + + match variant.fields { + Fields::Unnamed(_) => { + quote! { + Self::#name(#(#field_names),*) => { + #(#visit_fields)* + } + } + } + Fields::Named(_) => { + quote! { + Self::#name { #(#field_names),* } => { + #(#visit_fields)* + } + } + } + Fields::Unit => quote! {}, + } + }) + .collect::(); + + visit.push(quote! { + match self { + #variants + _ => {} + } + }) + } + _ => {} + } + + if visit_types.is_none() && child_types.is_empty() { + child_types.push(quote! { crate::visitor::VisitTypes::empty().bits() }); + } + + let (_, ty_generics, _) = generics.split_for_impl(); + let (impl_generics, _, where_clause) = impl_generics.split_for_impl(); + + let self_visit = if let Some(VisitOptions { + visit: Some(visit), + kind: Some(kind), + .. + }) = &options + { + child_types.push(quote! { crate::visitor::VisitTypes::#kind.bits() }); + + quote! { + fn visit(&mut self, visitor: &mut #v) -> Result<(), #v::Error> { + if visitor.visit_types().contains(crate::visitor::VisitTypes::#kind) { + visitor.#visit(self) + } else { + self.visit_children(visitor) + } + } + } + } else { + quote! {} + }; + + let child_types = visit_types.unwrap_or_else(|| { + quote! { + { + #type_defs + crate::visitor::VisitTypes::from_bits_retain(#(#child_types)|*) + } + } + }); + + let output = quote! { + impl #impl_generics Visit<#lifetime, #t, #v> for #ident #ty_generics #where_clause { + const CHILD_TYPES: crate::visitor::VisitTypes = #child_types; + + #self_visit + + fn visit_children(&mut self, visitor: &mut #v) -> Result<(), #v::Error> { + if !<#lifetime, #t, #v>>::CHILD_TYPES.intersects(visitor.visit_types()) { + return Ok(()) + } + + #(#visit)* + + Ok(()) + } + } + }; + + output.into() +} + +fn skip_type(attrs: &Vec) -> bool { + attrs.iter().any(|attr| attr.path.is_ident("skip_type")) +} + +struct VisitOptions { + visit: Option, + kind: Option, + generic: Option, +} + +impl Parse for VisitOptions { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let (visit, kind, comma) = if input.peek(Ident) { + let visit: Ident = input.parse()?; + let _: Token![,] = input.parse()?; + let kind: Ident = input.parse()?; + let comma: Result = input.parse(); + (Some(visit), Some(kind), comma.is_ok()) + } else { + (None, None, true) + }; + let generic: Option = if comma { Some(input.parse()?) } else { None }; + Ok(Self { visit, kind, generic }) + } +} + +struct VisitTypes { + types: Vec, +} + +impl Parse for VisitTypes { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let first: Ident = input.parse()?; + let mut types = vec![first]; + while input.parse::().is_ok() { + let id: Ident = input.parse()?; + types.push(id); + } + Ok(Self { types }) + } +} diff --git a/src/macros.rs b/src/macros.rs index e102676f..7e573816 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -8,12 +8,12 @@ macro_rules! enum_property { )+ } ) => { - $(#[$outer])* - #[derive(Debug, Clone, Copy, PartialEq)] + #[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] + $(#[$outer])* $vis enum $name { $( $(#[$meta])* @@ -21,50 +21,17 @@ macro_rules! enum_property { )+ } - impl<'i> Parse<'i> for $name { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - let location = input.current_source_location(); - let ident = input.expect_ident()?; - match &ident[..] { - $( - s if s.eq_ignore_ascii_case(stringify!($x)) => Ok($name::$x), - )+ - _ => Err(location.new_unexpected_token_error( - cssparser::Token::Ident(ident.clone()) - )) - } - } - - fn parse_string(input: &'i str) -> Result<'i, ParserError<'i>>> { - match input { - $( - s if s.eq_ignore_ascii_case(stringify!($x)) => Ok($name::$x), - )+ - _ => return Err(ParseError { - kind: ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(cssparser::Token::Ident(input.into()))), - location: cssparser::SourceLocation { line: 0, column: 1 } - }) - } - } - } - impl $name { /// Returns a string representation of the value. pub fn as_str(&self) -> &str { use $name::*; match self { $( - $x => const_str::convert_ascii_case!(lower, stringify!($x)), + $x => const_str::convert_ascii_case!(kebab, stringify!($x)), )+ } } } - - impl ToCss for $name { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write { - dest.write_str(self.as_str()) - } - } }; ( $(#[$outer:meta])* @@ -92,25 +59,13 @@ macro_rules! enum_property { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { let location = input.current_source_location(); let ident = input.expect_ident()?; - match &ident[..] { + cssparser::match_ignore_ascii_case! { &*ident, $( - s if s.eq_ignore_ascii_case($str) => Ok($name::$id), + $str => Ok($name::$id), )+ _ => Err(location.new_unexpected_token_error( cssparser::Token::Ident(ident.clone()) - )) - } - } - - fn parse_string(input: &'i str) -> Result<'i, ParserError<'i>>> { - match input { - $( - s if s.eq_ignore_ascii_case($str) => Ok($name::$id), - )+ - _ => return Err(ParseError { - kind: ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(cssparser::Token::Ident(input.into()))), - location: cssparser::SourceLocation { line: 0, column: 1 } - }) + )), } } } diff --git a/src/properties/align.rs b/src/properties/align.rs index 12917c18..819e9ac4 100644 --- a/src/properties/align.rs +++ b/src/properties/align.rs @@ -73,13 +73,13 @@ enum_property! { /// A [``](https://www.w3.org/TR/css-align-3/#typedef-content-distribution) value. pub enum ContentDistribution { /// Items are spaced evenly, with the first and last items against the edge of the container. - "space-between": SpaceBetween, + SpaceBetween, /// Items are spaced evenly, with half-size spaces at the start and end. - "space-around": SpaceAround, + SpaceAround, /// Items are spaced evenly, with full-size spaces at the start and end. - "space-evenly": SpaceEvenly, + SpaceEvenly, /// Items are stretched evenly to fill free space. - "stretch": Stretch, + Stretch, } } @@ -99,20 +99,20 @@ enum_property! { /// A [``](https://www.w3.org/TR/css-align-3/#typedef-content-position) value. pub enum ContentPosition { /// Content is centered within the container. - "center": Center, + Center, /// Content is aligned to the start of the container. - "start": Start, + Start, /// Content is aligned to the end of the container. - "end": End, + End, /// Same as `start` when within a flexbox container. - "flex-start": FlexStart, + FlexStart, /// Same as `end` when within a flexbox container. - "flex-end": FlexEnd, + FlexEnd, } } /// A value for the [align-content](https://www.w3.org/TR/css-align-3/#propdef-align-content) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -132,54 +132,13 @@ pub enum AlignContent { ContentDistribution(ContentDistribution), /// A content position keyword. ContentPosition { - /// A content position keyword. - value: ContentPosition, /// An overflow alignment mode. overflow: Option, + /// A content position keyword. + value: ContentPosition, }, } -impl<'i> Parse<'i> for AlignContent { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { - return Ok(AlignContent::Normal); - } - - if let Ok(val) = input.try_parse(BaselinePosition::parse) { - return Ok(AlignContent::BaselinePosition(val)); - } - - if let Ok(val) = input.try_parse(ContentDistribution::parse) { - return Ok(AlignContent::ContentDistribution(val)); - } - - let overflow = input.try_parse(OverflowPosition::parse).ok(); - let value = ContentPosition::parse(input)?; - Ok(AlignContent::ContentPosition { overflow, value }) - } -} - -impl ToCss for AlignContent { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - AlignContent::Normal => dest.write_str("normal"), - AlignContent::BaselinePosition(val) => val.to_css(dest), - AlignContent::ContentDistribution(val) => val.to_css(dest), - AlignContent::ContentPosition { overflow, value } => { - if let Some(overflow) = overflow { - overflow.to_css(dest)?; - dest.write_str(" ")?; - } - - value.to_css(dest) - } - } - } -} - /// A value for the [justify-content](https://www.w3.org/TR/css-align-3/#propdef-justify-content) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] @@ -349,24 +308,24 @@ enum_property! { /// A [``](https://www.w3.org/TR/css-align-3/#typedef-self-position) value. pub enum SelfPosition { /// Item is centered within the container. - "center": Center, + Center, /// Item is aligned to the start of the container. - "start": Start, + Start, /// Item is aligned to the end of the container. - "end": End, + End, /// Item is aligned to the edge of the container corresponding to the start side of the item. - "self-start": SelfStart, + SelfStart, /// Item is aligned to the edge of the container corresponding to the end side of the item. - "self-end": SelfEnd, + SelfEnd, /// Item is aligned to the start of the container, within flexbox layouts. - "flex-start": FlexStart, + FlexStart, /// Item is aligned to the end of the container, within flexbox layouts. - "flex-end": FlexEnd, + FlexEnd, } } /// A value for the [align-self](https://www.w3.org/TR/css-align-3/#align-self-property) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -387,59 +346,13 @@ pub enum AlignSelf { BaselinePosition(BaselinePosition), /// A self position keyword. SelfPosition { - /// A self position keyword. - value: SelfPosition, /// An overflow alignment mode. overflow: Option, + /// A self position keyword. + value: SelfPosition, }, } -impl<'i> Parse<'i> for AlignSelf { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() { - return Ok(AlignSelf::Auto); - } - - if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { - return Ok(AlignSelf::Normal); - } - - if input.try_parse(|input| input.expect_ident_matching("stretch")).is_ok() { - return Ok(AlignSelf::Stretch); - } - - if let Ok(val) = input.try_parse(BaselinePosition::parse) { - return Ok(AlignSelf::BaselinePosition(val)); - } - - let overflow = input.try_parse(OverflowPosition::parse).ok(); - let value = SelfPosition::parse(input)?; - Ok(AlignSelf::SelfPosition { overflow, value }) - } -} - -impl ToCss for AlignSelf { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - AlignSelf::Auto => dest.write_str("auto"), - AlignSelf::Normal => dest.write_str("normal"), - AlignSelf::Stretch => dest.write_str("stretch"), - AlignSelf::BaselinePosition(val) => val.to_css(dest), - AlignSelf::SelfPosition { overflow, value } => { - if let Some(overflow) = overflow { - overflow.to_css(dest)?; - dest.write_str(" ")?; - } - - value.to_css(dest) - } - } - } -} - /// A value for the [justify-self](https://www.w3.org/TR/css-align-3/#justify-self-property) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] @@ -615,7 +528,7 @@ impl ToCss for PlaceSelf { } /// A value for the [align-items](https://www.w3.org/TR/css-align-3/#align-items-property) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -634,54 +547,13 @@ pub enum AlignItems { BaselinePosition(BaselinePosition), /// A self position keyword. SelfPosition { - /// A self position keyword. - value: SelfPosition, /// An overflow alignment mode. overflow: Option, + /// A self position keyword. + value: SelfPosition, }, } -impl<'i> Parse<'i> for AlignItems { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { - return Ok(AlignItems::Normal); - } - - if input.try_parse(|input| input.expect_ident_matching("stretch")).is_ok() { - return Ok(AlignItems::Stretch); - } - - if let Ok(val) = input.try_parse(BaselinePosition::parse) { - return Ok(AlignItems::BaselinePosition(val)); - } - - let overflow = input.try_parse(OverflowPosition::parse).ok(); - let value = SelfPosition::parse(input)?; - Ok(AlignItems::SelfPosition { overflow, value }) - } -} - -impl ToCss for AlignItems { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - AlignItems::Normal => dest.write_str("normal"), - AlignItems::Stretch => dest.write_str("stretch"), - AlignItems::BaselinePosition(val) => val.to_css(dest), - AlignItems::SelfPosition { overflow, value } => { - if let Some(overflow) = overflow { - overflow.to_css(dest)?; - dest.write_str(" ")?; - } - - value.to_css(dest) - } - } - } -} - /// A legacy justification keyword, as used in the `justify-items` property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] @@ -925,7 +797,7 @@ impl ToCss for PlaceItems { /// A [gap](https://www.w3.org/TR/css-align-3/#column-row-gap) value, as used in the /// `column-gap` and `row-gap` properties. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -941,29 +813,6 @@ pub enum GapValue { LengthPercentage(LengthPercentage), } -impl<'i> Parse<'i> for GapValue { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { - return Ok(GapValue::Normal); - } - - let val = LengthPercentage::parse(input)?; - Ok(GapValue::LengthPercentage(val)) - } -} - -impl ToCss for GapValue { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - GapValue::Normal => dest.write_str("normal"), - GapValue::LengthPercentage(lp) => lp.to_css(dest), - } - } -} - define_shorthand! { /// A value for the [gap](https://www.w3.org/TR/css-align-3/#gap-shorthand) shorthand property. pub struct Gap { diff --git a/src/properties/animation.rs b/src/properties/animation.rs index 03b76166..de83b9fa 100644 --- a/src/properties/animation.rs +++ b/src/properties/animation.rs @@ -13,7 +13,7 @@ use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero}; use crate::values::ident::DashedIdent; use crate::values::number::CSSNumber; use crate::values::size::Size2D; -use crate::values::string::CowArcStr; +use crate::values::string::CSSString; use crate::values::{easing::EasingFunction, ident::CustomIdent, time::Time}; #[cfg(feature = "visitor")] use crate::visitor::Visit; @@ -24,7 +24,7 @@ use smallvec::SmallVec; use super::LengthPercentageOrAuto; /// A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( @@ -41,22 +41,7 @@ pub enum AnimationName<'i> { Ident(CustomIdent<'i>), /// A `` name of a `@keyframes` rule. #[cfg_attr(feature = "serde", serde(borrow))] - String(CowArcStr<'i>), -} - -impl<'i> Parse<'i> for AnimationName<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(AnimationName::None); - } - - if let Ok(s) = input.try_parse(|input| input.expect_string_cloned()) { - return Ok(AnimationName::String(s.into())); - } - - let ident = CustomIdent::parse(input)?; - Ok(AnimationName::Ident(ident)) - } + String(CSSString<'i>), } impl<'i> ToCss for AnimationName<'i> { @@ -103,7 +88,7 @@ impl<'i> ToCss for AnimationName<'i> { pub type AnimationNameList<'i> = SmallVec<[AnimationName<'i>; 1]>; /// A value for the [animation-iteration-count](https://drafts.csswg.org/css-animations/#animation-iteration-count) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -125,40 +110,17 @@ impl Default for AnimationIterationCount { } } -impl<'i> Parse<'i> for AnimationIterationCount { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("infinite")).is_ok() { - return Ok(AnimationIterationCount::Infinite); - } - - let number = CSSNumber::parse(input)?; - return Ok(AnimationIterationCount::Number(number)); - } -} - -impl ToCss for AnimationIterationCount { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - AnimationIterationCount::Number(val) => val.to_css(dest), - AnimationIterationCount::Infinite => dest.write_str("infinite"), - } - } -} - enum_property! { /// A value for the [animation-direction](https://drafts.csswg.org/css-animations/#animation-direction) property. pub enum AnimationDirection { /// The animation is played as specified - "normal": Normal, + Normal, /// The animation is played in reverse. - "reverse": Reverse, + Reverse, /// The animation iterations alternate between forward and reverse. - "alternate": Alternate, + Alternate, /// The animation iterations alternate between forward and reverse, with reverse occurring first. - "alternate-reverse": AlternateReverse, + AlternateReverse, } } @@ -217,7 +179,7 @@ enum_property! { } /// A value for the [animation-timeline](https://drafts.csswg.org/css-animations-2/#animation-timeline) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -261,25 +223,28 @@ pub struct ScrollTimeline { impl<'i> Parse<'i> for ScrollTimeline { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - let mut scroller = None; - let mut axis = None; - loop { - if scroller.is_none() { - scroller = input.try_parse(Scroller::parse).ok(); - } + input.expect_function_matching("scroll")?; + input.parse_nested_block(|input| { + let mut scroller = None; + let mut axis = None; + loop { + if scroller.is_none() { + scroller = input.try_parse(Scroller::parse).ok(); + } - if axis.is_none() { - axis = input.try_parse(ScrollAxis::parse).ok(); - if axis.is_some() { - continue; + if axis.is_none() { + axis = input.try_parse(ScrollAxis::parse).ok(); + if axis.is_some() { + continue; + } } + break; } - break; - } - Ok(ScrollTimeline { - scroller: scroller.unwrap_or_default(), - axis: axis.unwrap_or_default(), + Ok(ScrollTimeline { + scroller: scroller.unwrap_or_default(), + axis: axis.unwrap_or_default(), + }) }) } } @@ -289,6 +254,8 @@ impl ToCss for ScrollTimeline { where W: std::fmt::Write, { + dest.write_str("scroll(")?; + let mut needs_space = false; if self.scroller != Scroller::default() { self.scroller.to_css(dest)?; @@ -302,7 +269,7 @@ impl ToCss for ScrollTimeline { self.axis.to_css(dest)?; } - Ok(()) + dest.write_char(')') } } @@ -359,25 +326,28 @@ pub struct ViewTimeline { impl<'i> Parse<'i> for ViewTimeline { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - let mut axis = None; - let mut inset = None; - loop { - if axis.is_none() { - axis = input.try_parse(ScrollAxis::parse).ok(); - } + input.expect_function_matching("view")?; + input.parse_nested_block(|input| { + let mut axis = None; + let mut inset = None; + loop { + if axis.is_none() { + axis = input.try_parse(ScrollAxis::parse).ok(); + } - if inset.is_none() { - inset = input.try_parse(Size2D::parse).ok(); - if inset.is_some() { - continue; + if inset.is_none() { + inset = input.try_parse(Size2D::parse).ok(); + if inset.is_some() { + continue; + } } + break; } - break; - } - Ok(ViewTimeline { - axis: axis.unwrap_or_default(), - inset: inset.unwrap_or(Size2D(LengthPercentageOrAuto::Auto, LengthPercentageOrAuto::Auto)), + Ok(ViewTimeline { + axis: axis.unwrap_or_default(), + inset: inset.unwrap_or(Size2D(LengthPercentageOrAuto::Auto, LengthPercentageOrAuto::Auto)), + }) }) } } @@ -387,6 +357,7 @@ impl ToCss for ViewTimeline { where W: std::fmt::Write, { + dest.write_str("view(")?; let mut needs_space = false; if self.axis != ScrollAxis::default() { self.axis.to_css(dest)?; @@ -400,56 +371,7 @@ impl ToCss for ViewTimeline { self.inset.to_css(dest)?; } - Ok(()) - } -} - -impl<'i> Parse<'i> for AnimationTimeline<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { - return Ok(AnimationTimeline::Auto); - } - - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(AnimationTimeline::None); - } - - if let Ok(name) = input.try_parse(DashedIdent::parse) { - return Ok(AnimationTimeline::DashedIdent(name)); - } - - let location = input.current_source_location(); - let f = input.expect_function()?.clone(); - input.parse_nested_block(move |input| { - match_ignore_ascii_case! { &f, - "scroll" => ScrollTimeline::parse(input).map(AnimationTimeline::Scroll), - "view" => ViewTimeline::parse(input).map(AnimationTimeline::View), - _ => Err(location.new_custom_error(ParserError::UnexpectedToken(crate::properties::custom::Token::Function(f.into())))) - } - }) - } -} - -impl<'i> ToCss for AnimationTimeline<'i> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - AnimationTimeline::Auto => dest.write_str("auto"), - AnimationTimeline::None => dest.write_str("none"), - AnimationTimeline::DashedIdent(name) => name.to_css(dest), - AnimationTimeline::Scroll(scroll) => { - dest.write_str("scroll(")?; - scroll.to_css(dest)?; - dest.write_char(')') - } - AnimationTimeline::View(view) => { - dest.write_str("view(")?; - view.to_css(dest)?; - dest.write_char(')') - } - } + dest.write_char(')') } } @@ -535,7 +457,7 @@ impl<'i> ToCss for Animation<'i> { { match &self.name { AnimationName::None => {} - AnimationName::Ident(CustomIdent(name)) | AnimationName::String(name) => { + AnimationName::Ident(CustomIdent(name)) | AnimationName::String(CSSString(name)) => { if !self.duration.is_zero() || !self.delay.is_zero() { self.duration.to_css(dest)?; dest.write_char(' ')?; @@ -709,7 +631,7 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> { } } TokenOrValue::Token(Token::String(s)) => { - *token = TokenOrValue::AnimationName(AnimationName::String(s.clone())); + *token = TokenOrValue::AnimationName(AnimationName::String(CSSString(s.clone()))); } _ => {} } diff --git a/src/properties/background.rs b/src/properties/background.rs index 1dc1aeb3..f47821db 100644 --- a/src/properties/background.rs +++ b/src/properties/background.rs @@ -113,13 +113,13 @@ enum_property! { /// See [BackgroundRepeat](BackgroundRepeat). pub enum BackgroundRepeatKeyword { /// The image is repeated in this direction. - "repeat": Repeat, + Repeat, /// The image is repeated so that it fits, and then spaced apart evenly. - "space": Space, + Space, /// The image is scaled so that it repeats an even number of times. - "round": Round, + Round, /// The image is placed once and not repeated in this direction. - "no-repeat": NoRepeat, + NoRepeat, } } @@ -214,11 +214,11 @@ enum_property! { /// A value for the [background-origin](https://www.w3.org/TR/css-backgrounds-3/#background-origin) property. pub enum BackgroundOrigin { /// The position is relative to the border box. - "border-box": BorderBox, + BorderBox, /// The position is relative to the padding box. - "padding-box": PaddingBox, + PaddingBox, /// The position is relative to the content box. - "content-box": ContentBox, + ContentBox, } } @@ -226,15 +226,15 @@ enum_property! { /// A value for the [background-clip](https://drafts.csswg.org/css-backgrounds-4/#background-clip) property. pub enum BackgroundClip { /// The background is clipped to the border box. - "border-box": BorderBox, + BorderBox, /// The background is clipped to the padding box. - "padding-box": PaddingBox, + PaddingBox, /// The background is clipped to the content box. - "content-box": ContentBox, + ContentBox, /// The background is clipped to the area painted by the border. - "border": Border, + Border, /// The background is clipped to the text content of the element. - "text": Text, + Text, } } diff --git a/src/properties/border.rs b/src/properties/border.rs index ee2c4102..9308eebb 100644 --- a/src/properties/border.rs +++ b/src/properties/border.rs @@ -23,7 +23,7 @@ use crate::visitor::Visit; use cssparser::*; /// A value for the [border-width](https://www.w3.org/TR/css-backgrounds-3/#border-width) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -58,37 +58,6 @@ impl IsCompatible for BorderSideWidth { } } -impl<'i> Parse<'i> for BorderSideWidth { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(length) = input.try_parse(|i| Length::parse(i)) { - return Ok(BorderSideWidth::Length(length)); - } - let location = input.current_source_location(); - let ident = input.expect_ident()?; - match_ignore_ascii_case! { &ident, - "thin" => Ok(BorderSideWidth::Thin), - "medium" => Ok(BorderSideWidth::Medium), - "thick" => Ok(BorderSideWidth::Thick), - _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))) - } - } -} - -impl ToCss for BorderSideWidth { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - use BorderSideWidth::*; - match self { - Thin => dest.write_str("thin"), - Medium => dest.write_str("medium"), - Thick => dest.write_str("thick"), - Length(length) => length.to_css(dest), - } - } -} - enum_property! { /// A [``](https://drafts.csswg.org/css-backgrounds/#typedef-line-style) value, used in the `border-style` property. pub enum LineStyle { diff --git a/src/properties/border_image.rs b/src/properties/border_image.rs index 8f444efe..300d458e 100644 --- a/src/properties/border_image.rs +++ b/src/properties/border_image.rs @@ -102,7 +102,7 @@ impl IsCompatible for BorderImageRepeat { } /// A value for the [border-image-width](https://www.w3.org/TR/css-backgrounds-3/#border-image-width) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -126,38 +126,6 @@ impl Default for BorderImageSideWidth { } } -impl<'i> Parse<'i> for BorderImageSideWidth { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { - return Ok(BorderImageSideWidth::Auto); - } - - if let Ok(number) = input.try_parse(CSSNumber::parse) { - return Ok(BorderImageSideWidth::Number(number)); - } - - if let Ok(percent) = input.try_parse(|input| LengthPercentage::parse(input)) { - return Ok(BorderImageSideWidth::LengthPercentage(percent)); - } - - Err(input.new_error_for_next_token()) - } -} - -impl ToCss for BorderImageSideWidth { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - use BorderImageSideWidth::*; - match self { - Auto => dest.write_str("auto"), - LengthPercentage(l) => l.to_css(dest), - Number(n) => n.to_css(dest), - } - } -} - impl IsCompatible for BorderImageSideWidth { fn is_compatible(&self, browsers: Browsers) -> bool { match self { diff --git a/src/properties/contain.rs b/src/properties/contain.rs index 761e2d92..1c57bab4 100644 --- a/src/properties/contain.rs +++ b/src/properties/contain.rs @@ -25,11 +25,11 @@ enum_property! { pub enum ContainerType { /// The element is not a query container for any container size queries, /// but remains a query container for container style queries. - "normal": Normal, + Normal, /// Establishes a query container for container size queries on the container’s own inline axis. - "inline-size": InlineSize, + InlineSize, /// Establishes a query container for container size queries on both the inline and block axis. - "size": Size, + Size, } } diff --git a/src/properties/custom.rs b/src/properties/custom.rs index 3198422b..40b522e8 100644 --- a/src/properties/custom.rs +++ b/src/properties/custom.rs @@ -1329,25 +1329,25 @@ enum_property! { /// A UA-defined environment variable name. pub enum UAEnvironmentVariable { /// The safe area inset from the top of the viewport. - "safe-area-inset-top": SafeAreaInsetTop, + SafeAreaInsetTop, /// The safe area inset from the right of the viewport. - "safe-area-inset-right": SafeAreaInsetRight, + SafeAreaInsetRight, /// The safe area inset from the bottom of the viewport. - "safe-area-inset-bottom": SafeAreaInsetBottom, + SafeAreaInsetBottom, /// The safe area inset from the left of the viewport. - "safe-area-inset-left": SafeAreaInsetLeft, + SafeAreaInsetLeft, /// The viewport segment width. - "viewport-segment-width": ViewportSegmentWidth, + ViewportSegmentWidth, /// The viewport segment height. - "viewport-segment-height": ViewportSegmentHeight, + ViewportSegmentHeight, /// The viewport segment top position. - "viewport-segment-top": ViewportSegmentTop, + ViewportSegmentTop, /// The viewport segment left position. - "viewport-segment-left": ViewportSegmentLeft, + ViewportSegmentLeft, /// The viewport segment bottom position. - "viewport-segment-bottom": ViewportSegmentBottom, + ViewportSegmentBottom, /// The viewport segment right position. - "viewport-segment-right": ViewportSegmentRight, + ViewportSegmentRight, } } diff --git a/src/properties/display.rs b/src/properties/display.rs index d5a53d13..3df7c819 100644 --- a/src/properties/display.rs +++ b/src/properties/display.rs @@ -18,9 +18,9 @@ enum_property! { /// A [``](https://drafts.csswg.org/css-display-3/#typedef-display-outside) value. #[allow(missing_docs)] pub enum DisplayOutside { - "block": Block, - "inline": Inline, - "run-in": RunIn, + Block, + Inline, + RunIn, } } @@ -309,25 +309,25 @@ enum_property! { /// See [Display](Display). #[allow(missing_docs)] pub enum DisplayKeyword { - "none": None, - "contents": Contents, - "table-row-group": TableRowGroup, - "table-header-group": TableHeaderGroup, - "table-footer-group": TableFooterGroup, - "table-row": TableRow, - "table-cell": TableCell, - "table-column-group": TableColumnGroup, - "table-column": TableColumn, - "table-caption": TableCaption, - "ruby-base": RubyBase, - "ruby-text": RubyText, - "ruby-base-container": RubyBaseContainer, - "ruby-text-container": RubyTextContainer, + None, + Contents, + TableRowGroup, + TableHeaderGroup, + TableFooterGroup, + TableRow, + TableCell, + TableColumnGroup, + TableColumn, + TableCaption, + RubyBase, + RubyText, + RubyBaseContainer, + RubyTextContainer, } } /// A value for the [display](https://drafts.csswg.org/css-display-3/#the-display-properties) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -347,29 +347,6 @@ pub enum Display { Pair(DisplayPair), } -impl<'i> Parse<'i> for Display { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(pair) = input.try_parse(DisplayPair::parse) { - return Ok(Display::Pair(pair)); - } - - let keyword = DisplayKeyword::parse(input)?; - Ok(Display::Keyword(keyword)) - } -} - -impl ToCss for Display { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - Display::Keyword(keyword) => keyword.to_css(dest), - Display::Pair(pair) => pair.to_css(dest), - } - } -} - enum_property! { /// A value for the [visibility](https://drafts.csswg.org/css-display-3/#visibility) property. pub enum Visibility { diff --git a/src/properties/flex.rs b/src/properties/flex.rs index cf631993..e885c597 100644 --- a/src/properties/flex.rs +++ b/src/properties/flex.rs @@ -25,13 +25,13 @@ enum_property! { /// A value for the [flex-direction](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#propdef-flex-direction) property. pub enum FlexDirection { /// Flex items are laid out in a row. - "row": Row, + Row, /// Flex items are laid out in a row, and reversed. - "row-reverse": RowReverse, + RowReverse, /// Flex items are laid out in a column. - "column": Column, + Column, /// Flex items are laid out in a column, and reversed. - "column-reverse": ColumnReverse, + ColumnReverse, } } @@ -233,13 +233,13 @@ enum_property! { /// Partially equivalent to `flex-direction` in the standard syntax. pub enum BoxOrient { /// Items are laid out horizontally. - "horizontal": Horizontal, + Horizontal, /// Items are laid out vertically. - "vertical": Vertical, + Vertical, /// Items are laid out along the inline axis, according to the writing direction. - "inline-axis": InlineAxis, + InlineAxis, /// Items are laid out along the block axis, according to the writing direction. - "block-axis": BlockAxis, + BlockAxis, } } diff --git a/src/properties/font.rs b/src/properties/font.rs index 84396cdb..377ea77c 100644 --- a/src/properties/font.rs +++ b/src/properties/font.rs @@ -20,7 +20,7 @@ use crate::visitor::Visit; use cssparser::*; /// A value for the [font-weight](https://www.w3.org/TR/css-fonts-4/#font-weight-prop) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -44,38 +44,6 @@ impl Default for FontWeight { } } -impl<'i> Parse<'i> for FontWeight { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(val) = input.try_parse(AbsoluteFontWeight::parse) { - return Ok(FontWeight::Absolute(val)); - } - - let location = input.current_source_location(); - let ident = input.expect_ident()?; - match_ignore_ascii_case! { &*ident, - "bolder" => Ok(FontWeight::Bolder), - "lighter" => Ok(FontWeight::Lighter), - _ => Err(location.new_unexpected_token_error( - cssparser::Token::Ident(ident.clone()) - )) - } - } -} - -impl ToCss for FontWeight { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - use FontWeight::*; - match self { - Absolute(val) => val.to_css(dest), - Bolder => dest.write_str("bolder"), - Lighter => dest.write_str("lighter"), - } - } -} - impl IsCompatible for FontWeight { fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool { match self { @@ -89,7 +57,7 @@ impl IsCompatible for FontWeight { /// as used in the `font-weight` property. /// /// See [FontWeight](FontWeight). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -112,24 +80,6 @@ impl Default for AbsoluteFontWeight { } } -impl<'i> Parse<'i> for AbsoluteFontWeight { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(val) = input.try_parse(CSSNumber::parse) { - return Ok(AbsoluteFontWeight::Weight(val)); - } - - let location = input.current_source_location(); - let ident = input.expect_ident()?; - match_ignore_ascii_case! { &*ident, - "normal" => Ok(AbsoluteFontWeight::Normal), - "bold" => Ok(AbsoluteFontWeight::Bold), - _ => Err(location.new_unexpected_token_error( - cssparser::Token::Ident(ident.clone()) - )) - } - } -} - impl ToCss for AbsoluteFontWeight { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where @@ -197,7 +147,7 @@ enum_property! { } /// A value for the [font-size](https://www.w3.org/TR/css-fonts-4/#font-size-prop) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -215,35 +165,6 @@ pub enum FontSize { Relative(RelativeFontSize), } -impl<'i> Parse<'i> for FontSize { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(val) = input.try_parse(LengthPercentage::parse) { - return Ok(FontSize::Length(val)); - } - - if let Ok(val) = input.try_parse(AbsoluteFontSize::parse) { - return Ok(FontSize::Absolute(val)); - } - - let val = RelativeFontSize::parse(input)?; - Ok(FontSize::Relative(val)) - } -} - -impl ToCss for FontSize { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - use FontSize::*; - match self { - Absolute(val) => val.to_css(dest), - Length(val) => val.to_css(dest), - Relative(val) => val.to_css(dest), - } - } -} - impl IsCompatible for FontSize { fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool { match self { @@ -309,7 +230,7 @@ impl Into for &FontStretchKeyword { } /// A value for the [font-stretch](https://www.w3.org/TR/css-fonts-4/#font-stretch-prop) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -331,17 +252,6 @@ impl Default for FontStretch { } } -impl<'i> Parse<'i> for FontStretch { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(val) = input.try_parse(Percentage::parse) { - return Ok(FontStretch::Percentage(val)); - } - - let keyword = FontStretchKeyword::parse(input)?; - Ok(FontStretch::Keyword(keyword)) - } -} - impl Into for &FontStretch { fn into(self) -> Percentage { match self { @@ -601,19 +511,19 @@ enum_property! { /// A value for the [font-variant-caps](https://www.w3.org/TR/css-fonts-4/#font-variant-caps-prop) property. pub enum FontVariantCaps { /// No special capitalization features are applied. - "normal": Normal, + Normal, /// The small capitals feature is used for lower case letters. - "small-caps": SmallCaps, + SmallCaps, /// Small capitals are used for both upper and lower case letters. - "all-small-caps": AllSmallCaps, + AllSmallCaps, /// Petite capitals are used. - "petite-caps": PetiteCaps, + PetiteCaps, /// Petite capitals are used for both upper and lower case letters. - "all-petite-caps": AllPetiteCaps, + AllPetiteCaps, /// Enables display of mixture of small capitals for uppercase letters with normal lowercase letters. - "unicase": Unicase, + Unicase, /// Uses titling capitals. - "titling-caps": TitlingCaps, + TitlingCaps, } } @@ -644,7 +554,7 @@ impl IsCompatible for FontVariantCaps { } /// A value for the [line-height](https://www.w3.org/TR/2020/WD-css-inline-3-20200827/#propdef-line-height) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -668,33 +578,6 @@ impl Default for LineHeight { } } -impl<'i> Parse<'i> for LineHeight { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { - return Ok(LineHeight::Normal); - } - - if let Ok(val) = input.try_parse(CSSNumber::parse) { - return Ok(LineHeight::Number(val)); - } - - Ok(LineHeight::Length(LengthPercentage::parse(input)?)) - } -} - -impl ToCss for LineHeight { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - LineHeight::Normal => dest.write_str("normal"), - LineHeight::Number(val) => val.to_css(dest), - LineHeight::Length(val) => val.to_css(dest), - } - } -} - impl IsCompatible for LineHeight { fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool { match self { @@ -708,27 +591,27 @@ enum_property! { /// A keyword for the [vertical align](https://drafts.csswg.org/css2/#propdef-vertical-align) property. pub enum VerticalAlignKeyword { /// Align the baseline of the box with the baseline of the parent box. - "baseline": Baseline, + Baseline, /// Lower the baseline of the box to the proper position for subscripts of the parent’s box. - "sub": Sub, + Sub, /// Raise the baseline of the box to the proper position for superscripts of the parent’s box. - "super": Super, + Super, /// Align the top of the aligned subtree with the top of the line box. - "top": Top, + Top, /// Align the top of the box with the top of the parent’s content area. - "text-top": TextTop, + TextTop, /// Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent. - "middle": Middle, + Middle, /// Align the bottom of the aligned subtree with the bottom of the line box. - "bottom": Bottom, + Bottom, /// Align the bottom of the box with the bottom of the parent’s content area. - "text-bottom": TextBottom, + TextBottom, } } /// A value for the [vertical align](https://drafts.csswg.org/css2/#propdef-vertical-align) property. // TODO: there is a more extensive spec in CSS3 but it doesn't seem any browser implements it? https://www.w3.org/TR/css-inline-3/#transverse-alignment -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -744,29 +627,6 @@ pub enum VerticalAlign { Length(LengthPercentage), } -impl<'i> Parse<'i> for VerticalAlign { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(len) = input.try_parse(LengthPercentage::parse) { - return Ok(VerticalAlign::Length(len)); - } - - let kw = VerticalAlignKeyword::parse(input)?; - Ok(VerticalAlign::Keyword(kw)) - } -} - -impl ToCss for VerticalAlign { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - VerticalAlign::Keyword(kw) => kw.to_css(dest), - VerticalAlign::Length(len) => len.to_css(dest), - } - } -} - define_shorthand! { /// A value for the [font](https://www.w3.org/TR/css-fonts-4/#font-prop) shorthand property. pub struct Font<'i> { diff --git a/src/properties/grid.rs b/src/properties/grid.rs index 912f563d..6e706319 100644 --- a/src/properties/grid.rs +++ b/src/properties/grid.rs @@ -24,7 +24,7 @@ use crate::serialization::ValueWrapper; /// A [track sizing](https://drafts.csswg.org/css-grid-2/#track-sizing) value /// for the `grid-template-rows` and `grid-template-columns` properties. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( @@ -178,7 +178,7 @@ pub struct TrackRepeat<'i> { /// used in the `repeat()` function. /// /// See [TrackRepeat](TrackRepeat). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -365,37 +365,6 @@ impl<'i> ToCss for TrackRepeat<'i> { } } -impl<'i> Parse<'i> for RepeatCount { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(num) = input.try_parse(CSSInteger::parse) { - return Ok(RepeatCount::Number(num)); - } - - let location = input.current_source_location(); - let ident = input.expect_ident()?; - match_ignore_ascii_case! { &*ident, - "auto-fill" => Ok(RepeatCount::AutoFill), - "auto-fit" => Ok(RepeatCount::AutoFit), - _ => Err(location.new_unexpected_token_error( - cssparser::Token::Ident(ident.clone()) - )) - } - } -} - -impl ToCss for RepeatCount { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - RepeatCount::AutoFill => dest.write_str("auto-fill"), - RepeatCount::AutoFit => dest.write_str("auto-fit"), - RepeatCount::Number(num) => num.to_css(dest), - } - } -} - fn parse_line_names<'i, 't>( input: &mut Parser<'i, 't>, ) -> Result<'i>, ParseError<'i, ParserError<'i>>> { @@ -519,29 +488,6 @@ impl<'i> TrackList<'i> { } } -impl<'i> Parse<'i> for TrackSizing<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(TrackSizing::None); - } - - let track_list = TrackList::parse(input)?; - Ok(TrackSizing::TrackList(track_list)) - } -} - -impl<'i> ToCss for TrackSizing<'i> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - TrackSizing::None => dest.write_str("none"), - TrackSizing::TrackList(list) => list.to_css(dest), - } - } -} - impl<'i> TrackSizing<'i> { fn is_explicit(&self) -> bool { match self { diff --git a/src/properties/list.rs b/src/properties/list.rs index 4112ddfe..54a0c052 100644 --- a/src/properties/list.rs +++ b/src/properties/list.rs @@ -15,7 +15,7 @@ use crate::visitor::Visit; use cssparser::*; /// A value for the [list-style-type](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#text-markers) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( @@ -40,34 +40,6 @@ impl Default for ListStyleType<'_> { } } -impl<'i> Parse<'i> for ListStyleType<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(ListStyleType::None); - } - - if let Ok(val) = input.try_parse(CounterStyle::parse) { - return Ok(ListStyleType::CounterStyle(val)); - } - - let s = CSSString::parse(input)?; - Ok(ListStyleType::String(s)) - } -} - -impl ToCss for ListStyleType<'_> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - ListStyleType::None => dest.write_str("none"), - ListStyleType::CounterStyle(style) => style.to_css(dest), - ListStyleType::String(s) => s.to_css(dest), - } - } -} - impl IsCompatible for ListStyleType<'_> { fn is_compatible(&self, browsers: Browsers) -> bool { match self { @@ -117,7 +89,7 @@ macro_rules! counter_styles { $vis:vis enum $name:ident { $( $(#[$meta: meta])* - $str: literal: $id: ident, + $id: ident, )+ } ) => { @@ -127,7 +99,7 @@ macro_rules! counter_styles { pub enum PredefinedCounterStyle { $( $(#[$meta])* - $str: $id, + $id, )+ } } @@ -151,68 +123,68 @@ counter_styles! { #[allow(missing_docs)] pub enum PredefinedCounterStyle { // https://www.w3.org/TR/css-counter-styles-3/#simple-numeric - "decimal": Decimal, - "decimal-leading-zero": DecimalLeadingZero, - "arabic-indic": ArabicIndic, - "armenian": Armenian, - "upper-armenian": UpperArmenian, - "lower-armenian": LowerArmenian, - "bengali": Bengali, - "cambodian": Cambodian, - "khmer": Khmer, - "cjk-decimal": CjkDecimal, - "devanagari": Devanagari, - "georgian": Georgian, - "gujarati": Gujarati, - "gurmukhi": Gurmukhi, - "hebrew": Hebrew, - "kannada": Kannada, - "lao": Lao, - "malayalam": Malayalam, - "mongolian": Mongolian, - "myanmar": Myanmar, - "oriya": Oriya, - "persian": Persian, - "lower-roman": LowerRoman, - "upper-roman": UpperRoman, - "tamil": Tamil, - "telugu": Telugu, - "thai": Thai, - "tibetan": Tibetan, + Decimal, + DecimalLeadingZero, + ArabicIndic, + Armenian, + UpperArmenian, + LowerArmenian, + Bengali, + Cambodian, + Khmer, + CjkDecimal, + Devanagari, + Georgian, + Gujarati, + Gurmukhi, + Hebrew, + Kannada, + Lao, + Malayalam, + Mongolian, + Myanmar, + Oriya, + Persian, + LowerRoman, + UpperRoman, + Tamil, + Telugu, + Thai, + Tibetan, // https://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic - "lower-alpha": LowerAlpha, - "lower-latin": LowerLatin, - "upper-alpha": UpperAlpha, - "upper-latin": UpperLatin, - "lower-greek": LowerGreek, - "hiragana": Hiragana, - "hiragana-iroha": HiraganaIroha, - "katakana": Katakana, - "katakana-iroha": KatakanaIroha, + LowerAlpha, + LowerLatin, + UpperAlpha, + UpperLatin, + LowerGreek, + Hiragana, + HiraganaIroha, + Katakana, + KatakanaIroha, // https://www.w3.org/TR/css-counter-styles-3/#simple-symbolic - "disc": Disc, - "circle": Circle, - "square": Square, - "disclosure-open": DisclosureOpen, - "disclosure-closed": DisclosureClosed, + Disc, + Circle, + Square, + DisclosureOpen, + DisclosureClosed, // https://www.w3.org/TR/css-counter-styles-3/#simple-fixed - "cjk-earthly-branch": CjkEarthlyBranch, - "cjk-heavenly-stem": CjkHeavenlyStem, + CjkEarthlyBranch, + CjkHeavenlyStem, // https://www.w3.org/TR/css-counter-styles-3/#complex-cjk - "japanese-informal": JapaneseInformal, - "japanese-formal": JapaneseFormal, - "korean-hangul-formal": KoreanHangulFormal, - "korean-hanja-informal": KoreanHanjaInformal, - "korean-hanja-formal": KoreanHanjaFormal, - "simp-chinese-informal": SimpChineseInformal, - "simp-chinese-formal": SimpChineseFormal, - "trad-chinese-informal": TradChineseInformal, - "trad-chinese-formal": TradChineseFormal, - "ethiopic-numeric": EthiopicNumeric, + JapaneseInformal, + JapaneseFormal, + KoreanHangulFormal, + KoreanHanjaInformal, + KoreanHanjaFormal, + SimpChineseInformal, + SimpChineseFormal, + TradChineseInformal, + TradChineseFormal, + EthiopicNumeric, } } @@ -309,7 +281,7 @@ impl Default for SymbolsType { /// `symbols()` function. /// /// See [CounterStyle](CounterStyle). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( @@ -326,29 +298,6 @@ pub enum Symbol<'i> { Image(Image<'i>), } -impl<'i> Parse<'i> for Symbol<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(img) = input.try_parse(Image::parse) { - return Ok(Symbol::Image(img)); - } - - let s = CSSString::parse(input)?; - Ok(Symbol::String(s.into())) - } -} - -impl<'i> ToCss for Symbol<'i> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - Symbol::String(s) => s.to_css(dest), - Symbol::Image(img) => img.to_css(dest), - } - } -} - enum_property! { /// A value for the [list-style-position](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#list-style-position-property) property. pub enum ListStylePosition { @@ -375,8 +324,8 @@ enum_property! { /// A value for the [marker-side](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#marker-side) property. #[allow(missing_docs)] pub enum MarkerSide { - "match-self": MatchSelf, - "match-parent": MatchParent, + MatchSelf, + MatchParent, } } diff --git a/src/properties/masking.rs b/src/properties/masking.rs index 35560c6e..ff05e1f6 100644 --- a/src/properties/masking.rs +++ b/src/properties/masking.rs @@ -37,11 +37,11 @@ enum_property! { /// A value for the [mask-mode](https://www.w3.org/TR/css-masking-1/#the-mask-mode) property. pub enum MaskMode { /// The luminance values of the mask image is used. - "luminance": Luminance, + Luminance, /// The alpha values of the mask image is used. - "alpha": Alpha, + Alpha, /// If an SVG source is used, the value matches the `mask-type` property. Otherwise, the alpha values are used. - "match-source": MatchSource, + MatchSource, } } @@ -58,11 +58,11 @@ enum_property! { /// See also [MaskMode](MaskMode). pub enum WebKitMaskSourceType { /// Equivalent to `match-source` in the standard `mask-mode` syntax. - "auto": Auto, + Auto, /// The luminance values of the mask image is used. - "luminance": Luminance, + Luminance, /// The alpha values of the mask image is used. - "alpha": Alpha, + Alpha, } } @@ -81,19 +81,19 @@ enum_property! { /// as used in the `mask-clip` and `clip-path` properties. pub enum GeometryBox { /// The painted content is clipped to the content box. - "border-box": BorderBox, + BorderBox, /// The painted content is clipped to the padding box. - "padding-box": PaddingBox, + PaddingBox, /// The painted content is clipped to the border box. - "content-box": ContentBox, + ContentBox, /// The painted content is clipped to the margin box. - "margin-box": MarginBox, + MarginBox, /// The painted content is clipped to the object bounding box. - "fill-box": FillBox, + FillBox, /// The painted content is clipped to the stroke bounding box. - "stroke-box": StrokeBox, + StrokeBox, /// Uses the nearest SVG viewport as reference box. - "view-box": ViewBox, + ViewBox, } } @@ -104,7 +104,7 @@ impl Default for GeometryBox { } /// A value for the [mask-clip](https://www.w3.org/TR/css-masking-1/#the-mask-clip) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -120,29 +120,6 @@ pub enum MaskClip { NoClip, } -impl<'i> Parse<'i> for MaskClip { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(b) = input.try_parse(GeometryBox::parse) { - return Ok(MaskClip::GeometryBox(b)); - } - - input.expect_ident_matching("no-clip")?; - Ok(MaskClip::NoClip) - } -} - -impl ToCss for MaskClip { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - MaskClip::GeometryBox(b) => b.to_css(dest), - MaskClip::NoClip => dest.write_str("no-clip"), - } - } -} - impl IsCompatible for MaskClip { fn is_compatible(&self, browsers: Browsers) -> bool { match self { @@ -191,21 +168,21 @@ enum_property! { /// See also [MaskComposite](MaskComposite). #[allow(missing_docs)] pub enum WebKitMaskComposite { - "clear": Clear, - "copy": Copy, + Clear, + Copy, /// Equivalent to `add` in the standard `mask-composite` syntax. - "source-over": SourceOver, + SourceOver, /// Equivalent to `intersect` in the standard `mask-composite` syntax. - "source-in": SourceIn, + SourceIn, /// Equivalent to `subtract` in the standard `mask-composite` syntax. - "source-out": SourceOut, - "source-atop": SourceAtop, - "destination-over": DestinationOver, - "destination-in": DestinationIn, - "destination-out": DestinationOut, - "destination-atop": DestinationAtop, + SourceOut, + SourceAtop, + DestinationOver, + DestinationIn, + DestinationOut, + DestinationAtop, /// Equivalent to `exclude` in the standard `mask-composite` syntax. - "xor": Xor, + Xor, } } @@ -477,9 +454,9 @@ enum_property! { /// A value for the [mask-border-mode](https://www.w3.org/TR/css-masking-1/#the-mask-border-mode) property. pub enum MaskBorderMode { /// The luminance values of the mask image is used. - "luminance": Luminance, + Luminance, /// The alpha values of the mask image is used. - "alpha": Alpha, + Alpha, } } diff --git a/src/properties/outline.rs b/src/properties/outline.rs index e0a710e2..2ecdb202 100644 --- a/src/properties/outline.rs +++ b/src/properties/outline.rs @@ -15,7 +15,7 @@ use crate::visitor::Visit; use cssparser::*; /// A value for the [outline-style](https://drafts.csswg.org/css-ui/#outline-style) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -31,29 +31,6 @@ pub enum OutlineStyle { LineStyle(LineStyle), } -impl<'i> Parse<'i> for OutlineStyle { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(border_style) = input.try_parse(LineStyle::parse) { - return Ok(OutlineStyle::LineStyle(border_style)); - } - - input.expect_ident_matching("auto")?; - Ok(OutlineStyle::Auto) - } -} - -impl ToCss for OutlineStyle { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - OutlineStyle::Auto => dest.write_str("auto"), - OutlineStyle::LineStyle(border_style) => border_style.to_css(dest), - } - } -} - impl Default for OutlineStyle { fn default() -> OutlineStyle { OutlineStyle::LineStyle(LineStyle::None) diff --git a/src/properties/position.rs b/src/properties/position.rs index ace9a40d..34a0cf3b 100644 --- a/src/properties/position.rs +++ b/src/properties/position.rs @@ -73,7 +73,7 @@ impl ToCss for Position { } /// A value for the [z-index](https://drafts.csswg.org/css2/#z-index) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -89,29 +89,6 @@ pub enum ZIndex { Integer(CSSInteger), } -impl<'i> Parse<'i> for ZIndex { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(value) = input.expect_integer() { - return Ok(ZIndex::Integer(value)); - } - - input.expect_ident_matching("auto")?; - Ok(ZIndex::Auto) - } -} - -impl ToCss for ZIndex { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - ZIndex::Auto => dest.write_str("auto"), - ZIndex::Integer(value) => value.to_css(dest), - } - } -} - #[derive(Default)] pub(crate) struct PositionHandler { position: Option, diff --git a/src/properties/size.rs b/src/properties/size.rs index 54cc134d..2475773b 100644 --- a/src/properties/size.rs +++ b/src/properties/size.rs @@ -300,9 +300,9 @@ enum_property! { /// A value for the [box-sizing](https://drafts.csswg.org/css-sizing-3/#box-sizing) property. pub enum BoxSizing { /// Exclude the margin/border/padding from the width and height. - "content-box": ContentBox, + ContentBox, /// Include the padding and border (but not the margin) in the width and height. - "border-box": BorderBox, + BorderBox, } } diff --git a/src/properties/svg.rs b/src/properties/svg.rs index 26109941..150e5a25 100644 --- a/src/properties/svg.rs +++ b/src/properties/svg.rs @@ -13,7 +13,7 @@ use cssparser::*; /// An SVG [``](https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint) value /// used in the `fill` and `stroke` properties. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( @@ -23,8 +23,6 @@ use cssparser::*; )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub enum SVGPaint<'i> { - /// No paint. - None, /// A URL reference to a paint server element, e.g. `linearGradient`, `radialGradient`, and `pattern`. Url { #[cfg_attr(feature = "serde", serde(borrow))] @@ -40,12 +38,14 @@ pub enum SVGPaint<'i> { ContextFill, /// Use the paint value of stroke from a context element. ContextStroke, + /// No paint. + None, } /// A fallback for an SVG paint in case a paint server `url()` cannot be resolved. /// /// See [SVGPaint](SVGPaint). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -61,74 +61,6 @@ pub enum SVGPaintFallback { Color(CssColor), } -impl<'i> Parse<'i> for SVGPaint<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(url) = input.try_parse(Url::parse) { - let fallback = input.try_parse(SVGPaintFallback::parse).ok(); - return Ok(SVGPaint::Url { url, fallback }); - } - - if let Ok(color) = input.try_parse(CssColor::parse) { - return Ok(SVGPaint::Color(color)); - } - - let location = input.current_source_location(); - let keyword = input.expect_ident()?; - match_ignore_ascii_case! { &keyword, - "none" => Ok(SVGPaint::None), - "context-fill" => Ok(SVGPaint::ContextFill), - "context-stroke" => Ok(SVGPaint::ContextStroke), - _ => Err(location.new_unexpected_token_error( - cssparser::Token::Ident(keyword.clone()) - )) - } - } -} - -impl<'i> ToCss for SVGPaint<'i> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - SVGPaint::None => dest.write_str("none"), - SVGPaint::Url { url, fallback } => { - url.to_css(dest)?; - if let Some(fallback) = fallback { - dest.write_char(' ')?; - fallback.to_css(dest)?; - } - Ok(()) - } - SVGPaint::Color(color) => color.to_css(dest), - SVGPaint::ContextFill => dest.write_str("context-fill"), - SVGPaint::ContextStroke => dest.write_str("context-stroke"), - } - } -} - -impl<'i> Parse<'i> for SVGPaintFallback { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(SVGPaintFallback::None); - } - - Ok(SVGPaintFallback::Color(CssColor::parse(input)?)) - } -} - -impl ToCss for SVGPaintFallback { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - SVGPaintFallback::None => dest.write_str("none"), - SVGPaintFallback::Color(color) => color.to_css(dest), - } - } -} - impl<'i> FallbackValues for SVGPaint<'i> { fn get_fallbacks(&mut self, targets: Targets) -> Vec { match self { @@ -182,15 +114,15 @@ enum_property! { /// A value for the [stroke-linejoin](https://www.w3.org/TR/SVG2/painting.html#LineJoin) property. pub enum StrokeLinejoin { /// A sharp corner is to be used to join path segments. - "miter": Miter, + Miter, /// Same as `miter` but clipped beyond `stroke-miterlimit`. - "miter-clip": MiterClip, + MiterClip, /// A round corner is to be used to join path segments. - "round": Round, + Round, /// A bevelled corner is to be used to join path segments. - "bevel": Bevel, + Bevel, /// An arcs corner is to be used to join path segments. - "arcs": Arcs, + Arcs, } } @@ -260,7 +192,7 @@ impl ToCss for StrokeDasharray { } /// A value for the [marker](https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties) properties. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( @@ -277,29 +209,6 @@ pub enum Marker<'i> { Url(Url<'i>), } -impl<'i> Parse<'i> for Marker<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(url) = input.try_parse(Url::parse) { - return Ok(Marker::Url(url)); - } - - input.expect_ident_matching("none")?; - Ok(Marker::None) - } -} - -impl<'i> ToCss for Marker<'i> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - Marker::None => dest.write_str("none"), - Marker::Url(url) => url.to_css(dest), - } - } -} - enum_property! { /// A value for the [color-interpolation](https://www.w3.org/TR/SVG2/painting.html#ColorInterpolation) property. pub enum ColorInterpolation { diff --git a/src/properties/text.rs b/src/properties/text.rs index e22f6fab..622b870f 100644 --- a/src/properties/text.rs +++ b/src/properties/text.rs @@ -238,13 +238,13 @@ enum_property! { /// A value for the [word-break](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#word-break-property) property. pub enum WordBreak { /// Words break according to their customary rules. - "normal": Normal, + Normal, /// Breaking is forbidden within “words”. - "keep-all": KeepAll, + KeepAll, /// Breaking is allowed within “words”. - "break-all": BreakAll, + BreakAll, /// Breaking is allowed if there is no otherwise acceptable break points in a line. - "break-word": BreakWord, + BreakWord, } } @@ -279,12 +279,12 @@ enum_property! { /// A value for the [overflow-wrap](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#overflow-wrap-property) property. pub enum OverflowWrap { /// Lines may break only at allowed break points. - "normal": Normal, + Normal, /// Breaking is allowed if there is no otherwise acceptable break points in a line. - "anywhere": Anywhere, + Anywhere, /// As for anywhere except that soft wrap opportunities introduced by break-word are /// not considered when calculating min-content intrinsic sizes. - "break-word": BreakWord, + BreakWord, } } @@ -292,21 +292,21 @@ enum_property! { /// A value for the [text-align](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-align-property) property. pub enum TextAlign { /// Inline-level content is aligned to the start edge of the line box. - "start": Start, + Start, /// Inline-level content is aligned to the end edge of the line box. - "end": End, + End, /// Inline-level content is aligned to the line-left edge of the line box. - "left": Left, + Left, /// Inline-level content is aligned to the line-right edge of the line box. - "right": Right, + Right, /// Inline-level content is centered within the line box. - "center": Center, + Center, /// Text is justified according to the method specified by the text-justify property. - "justify": Justify, + Justify, /// Matches the parent element. - "match-parent": MatchParent, + MatchParent, /// Same as justify, but also justifies the last line. - "justify-all": JustifyAll, + JustifyAll, } } @@ -314,21 +314,21 @@ enum_property! { /// A value for the [text-align-last](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-align-last-property) property. pub enum TextAlignLast { /// Content on the affected line is aligned per `text-align-all` unless set to `justify`, in which case it is start-aligned. - "auto": Auto, + Auto, /// Inline-level content is aligned to the start edge of the line box. - "start": Start, + Start, /// Inline-level content is aligned to the end edge of the line box. - "end": End, + End, /// Inline-level content is aligned to the line-left edge of the line box. - "left": Left, + Left, /// Inline-level content is aligned to the line-right edge of the line box. - "right": Right, + Right, /// Inline-level content is centered within the line box. - "center": Center, + Center, /// Text is justified according to the method specified by the text-justify property. - "justify": Justify, + Justify, /// Matches the parent element. - "match-parent": MatchParent, + MatchParent, } } @@ -336,19 +336,19 @@ enum_property! { /// A value for the [text-justify](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-justify-property) property. pub enum TextJustify { /// The UA determines the justification algorithm to follow. - "auto": Auto, + Auto, /// Justification is disabled. - "none": None, + None, /// Justification adjusts spacing at word separators only. - "inter-word": InterWord, + InterWord, /// Justification adjusts spacing between each character. - "inter-character": InterCharacter, + InterCharacter, } } /// A value for the [word-spacing](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#word-spacing-property) /// and [letter-spacing](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#letter-spacing-property) properties. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -364,29 +364,6 @@ pub enum Spacing { Length(Length), } -impl<'i> Parse<'i> for Spacing { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { - return Ok(Spacing::Normal); - } - - let length = Length::parse(input)?; - Ok(Spacing::Length(length)) - } -} - -impl ToCss for Spacing { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - Spacing::Normal => dest.write_str("normal"), - Spacing::Length(len) => len.to_css(dest), - } - } -} - /// A value for the [text-indent](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-indent-property) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] @@ -466,7 +443,7 @@ impl ToCss for TextIndent { } /// A value for the [text-size-adjust](https://w3c.github.io/csswg-drafts/css-size-adjust/#adjustment-control) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -484,34 +461,6 @@ pub enum TextSizeAdjust { Percentage(Percentage), } -impl<'i> Parse<'i> for TextSizeAdjust { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(p) = input.try_parse(Percentage::parse) { - return Ok(TextSizeAdjust::Percentage(p)); - } - - let ident = input.expect_ident_cloned()?; - match_ignore_ascii_case! {&*ident, - "auto" => Ok(TextSizeAdjust::Auto), - "none" => Ok(TextSizeAdjust::None), - _ => Err(input.new_unexpected_token_error(Token::Ident(ident.clone()))) - } - } -} - -impl ToCss for TextSizeAdjust { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - TextSizeAdjust::Auto => dest.write_str("auto"), - TextSizeAdjust::None => dest.write_str("none"), - TextSizeAdjust::Percentage(p) => p.to_css(dest), - } - } -} - bitflags! { /// A value for the [text-decoration-line](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-line-property) property. /// @@ -749,7 +698,7 @@ impl Default for TextDecorationStyle { } /// A value for the [text-decoration-thickness](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-width-property) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -773,34 +722,6 @@ impl Default for TextDecorationThickness { } } -impl<'i> Parse<'i> for TextDecorationThickness { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() { - return Ok(TextDecorationThickness::Auto); - } - - if input.try_parse(|input| input.expect_ident_matching("from-font")).is_ok() { - return Ok(TextDecorationThickness::FromFont); - } - - let lp = LengthPercentage::parse(input)?; - Ok(TextDecorationThickness::LengthPercentage(lp)) - } -} - -impl ToCss for TextDecorationThickness { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - TextDecorationThickness::Auto => dest.write_str("auto"), - TextDecorationThickness::FromFont => dest.write_str("from-font"), - TextDecorationThickness::LengthPercentage(lp) => lp.to_css(dest), - } - } -} - define_shorthand! { /// A value for the [text-decoration](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-property) shorthand property. pub struct TextDecoration(VendorPrefix) { @@ -927,15 +848,15 @@ enum_property! { /// See [TextEmphasisStyle](TextEmphasisStyle). pub enum TextEmphasisShape { /// Display small circles as marks. - "dot": Dot, + Dot, /// Display large circles as marks. - "circle": Circle, + Circle, /// Display double circles as marks. - "double-circle": DoubleCircle, + DoubleCircle, /// Display triangles as marks. - "triangle": Triangle, + Triangle, /// Display sesames as marks. - "sesame": Sesame, + Sesame, } } @@ -1614,16 +1535,16 @@ enum_property! { /// A value for the [unicode-bidi](https://drafts.csswg.org/css-writing-modes-3/#unicode-bidi) property. pub enum UnicodeBidi { /// The box does not open an additional level of embedding. - "normal": Normal, + Normal, /// If the box is inline, this value creates a directional embedding by opening an additional level of embedding. - "embed": Embed, + Embed, /// On an inline box, this bidi-isolates its contents. - "isolate": Isolate, + Isolate, /// This value puts the box’s immediate inline content in a directional override. - "bidi-override": BidiOverride, + BidiOverride, /// This combines the isolation behavior of isolate with the directional override behavior of bidi-override. - "isolate-override": IsolateOverride, + IsolateOverride, /// This value behaves as isolate except that the base directionality is determined using a heuristic rather than the direction property. - "plaintext": Plaintext, + Plaintext, } } diff --git a/src/properties/transform.rs b/src/properties/transform.rs index d58a08fe..c8f5a7f7 100644 --- a/src/properties/transform.rs +++ b/src/properties/transform.rs @@ -1382,8 +1382,8 @@ enum_property! { /// A value for the [transform-style](https://drafts.csswg.org/css-transforms-2/#transform-style-property) property. #[allow(missing_docs)] pub enum TransformStyle { - "flat": Flat, - "preserve-3d": Preserve3d, + Flat, + Preserve3d, } } @@ -1391,15 +1391,15 @@ enum_property! { /// A value for the [transform-box](https://drafts.csswg.org/css-transforms-1/#transform-box) property. pub enum TransformBox { /// Uses the content box as reference box. - "content-box": ContentBox, + ContentBox, /// Uses the border box as reference box. - "border-box": BorderBox, + BorderBox, /// Uses the object bounding box as reference box. - "fill-box": FillBox, + FillBox, /// Uses the stroke bounding box as reference box. - "stroke-box": StrokeBox, + StrokeBox, /// Uses the nearest SVG viewport as reference box. - "view-box": ViewBox, + ViewBox, } } @@ -1413,7 +1413,7 @@ enum_property! { } /// A value for the [perspective](https://drafts.csswg.org/css-transforms-2/#perspective-property) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -1429,28 +1429,6 @@ pub enum Perspective { Length(Length), } -impl<'i> Parse<'i> for Perspective { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(Perspective::None); - } - - Ok(Perspective::Length(Length::parse(input)?)) - } -} - -impl ToCss for Perspective { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - Perspective::None => dest.write_str("none"), - Perspective::Length(len) => len.to_css(dest), - } - } -} - /// A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] diff --git a/src/properties/ui.rs b/src/properties/ui.rs index 4952082e..11e1eade 100644 --- a/src/properties/ui.rs +++ b/src/properties/ui.rs @@ -94,42 +94,42 @@ enum_property! { /// See [Cursor](Cursor). #[allow(missing_docs)] pub enum CursorKeyword { - "auto": Auto, - "default": Default, - "none": None, - "context-menu": ContextMenu, - "help": Help, - "pointer": Pointer, - "progress": Progress, - "wait": Wait, - "cell": Cell, - "crosshair": Crosshair, - "text": Text, - "vertical-text": VerticalText, - "alias": Alias, - "copy": Copy, - "move": Move, - "no-drop": NoDrop, - "not-allowed": NotAllowed, - "grab": Grab, - "grabbing": Grabbing, - "e-resize": EResize, - "n-resize": NResize, - "ne-resize": NeResize, - "nw-resize": NwResize, - "s-resize": SResize, - "se-resize": SeResize, - "sw-resize": SwResize, - "w-resize": WResize, - "ew-resize": EwResize, - "ns-resize": NsResize, - "nesw-resize": NeswResize, - "nwse-resize": NwseResize, - "col-resize": ColResize, - "row-resize": RowResize, - "all-scroll": AllScroll, - "zoom-in": ZoomIn, - "zoom-out": ZoomOut, + Auto, + Default, + None, + ContextMenu, + Help, + Pointer, + Progress, + Wait, + Cell, + Crosshair, + Text, + VerticalText, + Alias, + Copy, + Move, + NoDrop, + NotAllowed, + Grab, + Grabbing, + EResize, + NResize, + NeResize, + NwResize, + SResize, + SeResize, + SwResize, + WResize, + EwResize, + NsResize, + NeswResize, + NwseResize, + ColResize, + RowResize, + AllScroll, + ZoomIn, + ZoomOut, } } @@ -179,7 +179,7 @@ impl<'i> ToCss for Cursor<'i> { } /// A value for the [caret-color](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#caret-color) property. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -201,29 +201,6 @@ impl Default for ColorOrAuto { } } -impl<'i> Parse<'i> for ColorOrAuto { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() { - return Ok(ColorOrAuto::Auto); - } - - let color = CssColor::parse(input)?; - Ok(ColorOrAuto::Color(color)) - } -} - -impl ToCss for ColorOrAuto { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - ColorOrAuto::Auto => dest.write_str("auto"), - ColorOrAuto::Color(color) => color.to_css(dest), - } - } -} - impl FallbackValues for ColorOrAuto { fn get_fallbacks(&mut self, targets: Targets) -> Vec { match self { diff --git a/src/rules/keyframes.rs b/src/rules/keyframes.rs index 511a3172..adf2fb5b 100644 --- a/src/rules/keyframes.rs +++ b/src/rules/keyframes.rs @@ -267,7 +267,7 @@ impl<'i> ToCss for KeyframesRule<'i> { /// A [keyframe selector](https://drafts.csswg.org/css-animations/#typedef-keyframe-selector) /// within an `@keyframes` rule. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Parse)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -285,24 +285,6 @@ pub enum KeyframeSelector { To, } -impl<'i> Parse<'i> for KeyframeSelector { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(val) = input.try_parse(Percentage::parse) { - return Ok(KeyframeSelector::Percentage(val)); - } - - let location = input.current_source_location(); - let ident = input.expect_ident()?; - match_ignore_ascii_case! { &*ident, - "from" => Ok(KeyframeSelector::From), - "to" => Ok(KeyframeSelector::To), - _ => Err(location.new_unexpected_token_error( - cssparser::Token::Ident(ident.clone()) - )) - } - } -} - impl ToCss for KeyframeSelector { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where diff --git a/src/rules/page.rs b/src/rules/page.rs index e4897cd3..d95c94c2 100644 --- a/src/rules/page.rs +++ b/src/rules/page.rs @@ -81,41 +81,41 @@ enum_property! { /// A [page margin box](https://www.w3.org/TR/css-page-3/#margin-boxes). pub enum PageMarginBox { /// A fixed-size box defined by the intersection of the top and left margins of the page box. - "top-left-corner": TopLeftCorner, + TopLeftCorner, /// A variable-width box filling the top page margin between the top-left-corner and top-center page-margin boxes. - "top-left": TopLeft, + TopLeft, /// A variable-width box centered horizontally between the page’s left and right border edges and filling the /// page top margin between the top-left and top-right page-margin boxes. - "top-center": TopCenter, + TopCenter, /// A variable-width box filling the top page margin between the top-center and top-right-corner page-margin boxes. - "top-right": TopRight, + TopRight, /// A fixed-size box defined by the intersection of the top and right margins of the page box. - "top-right-corner": TopRightCorner, + TopRightCorner, /// A variable-height box filling the left page margin between the top-left-corner and left-middle page-margin boxes. - "left-top": LeftTop, + LeftTop, /// A variable-height box centered vertically between the page’s top and bottom border edges and filling the /// left page margin between the left-top and left-bottom page-margin boxes. - "left-middle": LeftMiddle, + LeftMiddle, /// A variable-height box filling the left page margin between the left-middle and bottom-left-corner page-margin boxes. - "left-bottom": LeftBottom, + LeftBottom, /// A variable-height box filling the right page margin between the top-right-corner and right-middle page-margin boxes. - "right-top": RightTop, + RightTop, /// A variable-height box centered vertically between the page’s top and bottom border edges and filling the right /// page margin between the right-top and right-bottom page-margin boxes. - "right-middle": RightMiddle, + RightMiddle, /// A variable-height box filling the right page margin between the right-middle and bottom-right-corner page-margin boxes. - "right-bottom": RightBottom, + RightBottom, /// A fixed-size box defined by the intersection of the bottom and left margins of the page box. - "bottom-left-corner": BottomLeftCorner, + BottomLeftCorner, /// A variable-width box filling the bottom page margin between the bottom-left-corner and bottom-center page-margin boxes. - "bottom-left": BottomLeft, + BottomLeft, /// A variable-width box centered horizontally between the page’s left and right border edges and filling the bottom /// page margin between the bottom-left and bottom-right page-margin boxes. - "bottom-center": BottomCenter, + BottomCenter, /// A variable-width box filling the bottom page margin between the bottom-center and bottom-right-corner page-margin boxes. - "bottom-right": BottomRight, + BottomRight, /// A fixed-size box defined by the intersection of the bottom and right margins of the page box. - "bottom-right-corner": BottomRightCorner, + BottomRightCorner, } } diff --git a/src/traits.rs b/src/traits.rs index 1d520421..4aecaf7c 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -30,6 +30,20 @@ pub trait Parse<'i>: Sized { } } +pub(crate) use lightningcss_derive::Parse; + +impl<'i, T: Parse<'i>> Parse<'i> for Option { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { + Ok(input.try_parse(T::parse).ok()) + } +} + +impl<'i, T: Parse<'i>> Parse<'i> for Box { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { + Ok(Box::new(T::parse(input)?)) + } +} + /// Trait for things that can be parsed from CSS syntax and require ParserOptions. pub trait ParseWithOptions<'i>: Sized { /// Parse a value of this type with the given options. @@ -78,6 +92,8 @@ pub trait ToCss { } } +pub(crate) use lightningcss_derive::ToCss; + impl<'a, T> ToCss for &'a T where T: ToCss + ?Sized, @@ -90,6 +106,27 @@ where } } +impl ToCss for Box { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + (**self).to_css(dest) + } +} + +impl ToCss for Option { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + if let Some(v) = self { + v.to_css(dest)?; + } + Ok(()) + } +} + pub(crate) trait PropertyHandler<'i>: Sized { fn handle_property( &mut self, diff --git a/src/values/calc.rs b/src/values/calc.rs index 95a0bb59..742c5184 100644 --- a/src/values/calc.rs +++ b/src/values/calc.rs @@ -92,13 +92,13 @@ enum_property! { /// as used in the `round()` function. pub enum RoundingStrategy { /// Round to the nearest integer. - "nearest": Nearest, + Nearest, /// Round up (ceil). - "up": Up, + Up, /// Round down (floor). - "down": Down, + Down, /// Round toward zero (truncate). - "to-zero": ToZero, + ToZero, } } diff --git a/src/values/color.rs b/src/values/color.rs index efa2bd26..b91c1426 100644 --- a/src/values/color.rs +++ b/src/values/color.rs @@ -3581,6 +3581,7 @@ impl<'i, V: ?Sized + Visitor<'i, T>, T: Visit<'i, T, V>> Visit<'i, T, V> for RGB } enum_property! { + #[css(case = lower)] /// A CSS [system color](https://drafts.csswg.org/css-color/#css-system-colors) keyword. pub enum SystemColor { /// Background of accented user interface controls. diff --git a/src/values/easing.rs b/src/values/easing.rs index c1e834a0..4db5c063 100644 --- a/src/values/easing.rs +++ b/src/values/easing.rs @@ -192,7 +192,7 @@ impl EasingFunction { } /// A [step position](https://www.w3.org/TR/css-easing-1/#step-position), used within the `steps()` function. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -233,17 +233,3 @@ impl<'i> Parse<'i> for StepPosition { Ok(keyword) } } - -impl ToCss for StepPosition { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - StepPosition::Start => dest.write_str("start"), - StepPosition::End => dest.write_str("end"), - StepPosition::JumpNone => dest.write_str("jump-none"), - StepPosition::JumpBoth => dest.write_str("jump-both"), - } - } -} diff --git a/src/values/gradient.rs b/src/values/gradient.rs index 01054b44..fec51938 100644 --- a/src/values/gradient.rs +++ b/src/values/gradient.rs @@ -533,7 +533,7 @@ impl LineDirection { /// A `radial-gradient()` [ending shape](https://www.w3.org/TR/css-images-3/#valdef-radial-gradient-ending-shape). /// /// See [RadialGradient](RadialGradient). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -542,10 +542,11 @@ impl LineDirection { )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub enum EndingShape { - /// A circle. - Circle(Circle), + // Note: Ellipse::parse MUST run before Circle::parse for this to be correct. /// An ellipse. Ellipse(Ellipse), + /// A circle. + Circle(Circle), } impl Default for EndingShape { @@ -554,33 +555,6 @@ impl Default for EndingShape { } } -impl<'i> Parse<'i> for EndingShape { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - // Note: Ellipse::parse MUST run before Circle::parse for this to be correct. - if let Ok(ellipse) = input.try_parse(Ellipse::parse) { - return Ok(EndingShape::Ellipse(ellipse)); - } - - if let Ok(circle) = input.try_parse(Circle::parse) { - return Ok(EndingShape::Circle(circle)); - } - - return Err(input.new_error_for_next_token()); - } -} - -impl ToCss for EndingShape { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - EndingShape::Circle(circle) => circle.to_css(dest), - EndingShape::Ellipse(ellipse) => ellipse.to_css(dest), - } - } -} - /// A circle ending shape for a `radial-gradient()`. /// /// See [RadialGradient](RadialGradient). @@ -734,13 +708,13 @@ enum_property! { /// See [RadialGradient](RadialGradient). pub enum ShapeExtent { /// The closest side of the box to the gradient's center. - "closest-side": ClosestSide, + ClosestSide, /// The farthest side of the box from the gradient's center. - "farthest-side": FarthestSide, + FarthestSide, /// The closest cornder of the box to the gradient's center. - "closest-corner": ClosestCorner, + ClosestCorner, /// The farthest corner of the box from the gradient's center. - "farthest-corner": FarthestCorner, + FarthestCorner, } } diff --git a/src/values/image.rs b/src/values/image.rs index fa778cc2..e6ca26c5 100644 --- a/src/values/image.rs +++ b/src/values/image.rs @@ -19,7 +19,7 @@ use cssparser::*; use smallvec::SmallVec; /// A CSS [``](https://www.w3.org/TR/css-images-3/#image-values) value. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr(feature = "visitor", visit(visit_image, IMAGES))] @@ -309,42 +309,6 @@ impl<'i, T: ImageFallback<'i>> FallbackValues for SmallVec<[T; 1]> { } } -impl<'i> Parse<'i> for Image<'i> { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(Image::None); - } - - if let Ok(url) = input.try_parse(Url::parse) { - return Ok(Image::Url(url)); - } - - if let Ok(grad) = input.try_parse(Gradient::parse) { - return Ok(Image::Gradient(Box::new(grad))); - } - - if let Ok(image_set) = input.try_parse(ImageSet::parse) { - return Ok(Image::ImageSet(image_set)); - } - - Err(input.new_error_for_next_token()) - } -} - -impl<'i> ToCss for Image<'i> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - Image::None => dest.write_str("none"), - Image::Url(url) => url.to_css(dest), - Image::Gradient(grad) => grad.to_css(dest), - Image::ImageSet(image_set) => image_set.to_css(dest), - } - } -} - /// A CSS [`image-set()`](https://drafts.csswg.org/css-images-4/#image-set-notation) value. /// /// `image-set()` allows the user agent to choose between multiple versions of an image to diff --git a/src/values/length.rs b/src/values/length.rs index 58967f08..149f1dd1 100644 --- a/src/values/length.rs +++ b/src/values/length.rs @@ -49,7 +49,7 @@ impl IsCompatible for LengthPercentage { } /// Either a [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage), or the `auto` keyword. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -65,33 +65,6 @@ pub enum LengthPercentageOrAuto { LengthPercentage(LengthPercentage), } -impl<'i> Parse<'i> for LengthPercentageOrAuto { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { - return Ok(LengthPercentageOrAuto::Auto); - } - - if let Ok(percent) = input.try_parse(|input| LengthPercentage::parse(input)) { - return Ok(LengthPercentageOrAuto::LengthPercentage(percent)); - } - - Err(input.new_error_for_next_token()) - } -} - -impl ToCss for LengthPercentageOrAuto { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - use LengthPercentageOrAuto::*; - match self { - Auto => dest.write_str("auto"), - LengthPercentage(l) => l.to_css(dest), - } - } -} - impl IsCompatible for LengthPercentageOrAuto { fn is_compatible(&self, browsers: Browsers) -> bool { match self { @@ -830,7 +803,7 @@ impl TrySign for Length { impl_try_from_angle!(Length); /// Either a [``](https://www.w3.org/TR/css-values-4/#lengths) or a [``](https://www.w3.org/TR/css-values-4/#numbers). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -840,10 +813,10 @@ impl_try_from_angle!(Length); #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum LengthOrNumber { - /// A length. - Length(Length), /// A number. Number(CSSNumber), + /// A length. + Length(Length), } impl Default for LengthOrNumber { @@ -865,33 +838,6 @@ impl Zero for LengthOrNumber { } } -impl<'i> Parse<'i> for LengthOrNumber { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - // Parse number first so unitless numbers are not parsed as lengths. - if let Ok(number) = input.try_parse(CSSNumber::parse) { - return Ok(LengthOrNumber::Number(number)); - } - - if let Ok(length) = Length::parse(input) { - return Ok(LengthOrNumber::Length(length)); - } - - Err(input.new_error_for_next_token()) - } -} - -impl ToCss for LengthOrNumber { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - LengthOrNumber::Length(length) => length.to_css(dest), - LengthOrNumber::Number(number) => number.to_css(dest), - } - } -} - impl IsCompatible for LengthOrNumber { fn is_compatible(&self, browsers: Browsers) -> bool { match self { diff --git a/src/values/percentage.rs b/src/values/percentage.rs index c51be75e..54352750 100644 --- a/src/values/percentage.rs +++ b/src/values/percentage.rs @@ -144,7 +144,7 @@ impl_op!(Percentage, std::ops::Add, add); impl_try_from_angle!(Percentage); /// Either a `` or ``. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -154,36 +154,10 @@ impl_try_from_angle!(Percentage); #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum NumberOrPercentage { - /// A percentage. - Percentage(Percentage), /// A number. Number(CSSNumber), -} - -impl<'i> Parse<'i> for NumberOrPercentage { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(number) = input.try_parse(CSSNumber::parse) { - return Ok(NumberOrPercentage::Number(number)); - } - - if let Ok(percent) = input.try_parse(|input| Percentage::parse(input)) { - return Ok(NumberOrPercentage::Percentage(percent)); - } - - Err(input.new_error_for_next_token()) - } -} - -impl ToCss for NumberOrPercentage { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - NumberOrPercentage::Percentage(percent) => percent.to_css(dest), - NumberOrPercentage::Number(number) => number.to_css(dest), - } - } + /// A percentage. + Percentage(Percentage), } impl std::convert::Into for &NumberOrPercentage { diff --git a/src/values/shape.rs b/src/values/shape.rs index c5f4cc31..9cb5f585 100644 --- a/src/values/shape.rs +++ b/src/values/shape.rs @@ -59,7 +59,7 @@ pub struct Circle { /// A [``](https://www.w3.org/TR/css-shapes-1/#typedef-shape-radius) value /// that defines the radius of a `circle()` or `ellipse()` shape. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr( feature = "serde", @@ -182,21 +182,6 @@ impl<'i> Parse<'i> for Circle { } } -impl<'i> Parse<'i> for ShapeRadius { - fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { - if let Ok(len) = input.try_parse(LengthPercentage::parse) { - return Ok(ShapeRadius::LengthPercentage(len)); - } - - if input.try_parse(|input| input.expect_ident_matching("closest-side")).is_ok() { - return Ok(ShapeRadius::ClosestSide); - } - - input.expect_ident_matching("farthest-side")?; - Ok(ShapeRadius::FarthestSide) - } -} - impl Default for ShapeRadius { fn default() -> ShapeRadius { ShapeRadius::ClosestSide @@ -315,19 +300,6 @@ impl ToCss for Circle { } } -impl ToCss for ShapeRadius { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> - where - W: std::fmt::Write, - { - match self { - ShapeRadius::LengthPercentage(len) => len.to_css(dest), - ShapeRadius::ClosestSide => dest.write_str("closest-side"), - ShapeRadius::FarthestSide => dest.write_str("farthest-side"), - } - } -} - impl ToCss for Ellipse { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where From 39964f1d78d237bb90006d1517e05b15c4b10514 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 2 Jun 2024 23:36:22 -0700 Subject: [PATCH 019/156] Implement animation-range properties #572 --- node/ast.d.ts | 66 ++++++++++ src/lib.rs | 243 ++++++++++++++++++++++++++++++++++ src/properties/animation.rs | 252 +++++++++++++++++++++++++++++++++++- src/properties/mod.rs | 3 + 4 files changed, 563 insertions(+), 1 deletion(-) diff --git a/node/ast.d.ts b/node/ast.d.ts index 8bc02772..fba3f69e 100644 --- a/node/ast.d.ts +++ b/node/ast.d.ts @@ -1960,6 +1960,15 @@ export type PropertyId = | { property: "animation-timeline"; } + | { + property: "animation-range-start"; + } + | { + property: "animation-range-end"; + } + | { + property: "animation-range"; + } | { property: "animation"; vendorPrefix: VendorPrefix; @@ -3316,6 +3325,18 @@ export type Declaration = property: "animation-timeline"; value: AnimationTimeline[]; } + | { + property: "animation-range-start"; + value: AnimationRangeStart[]; + } + | { + property: "animation-range-end"; + value: AnimationRangeEnd[]; + } + | { + property: "animation-range"; + value: AnimationRange[]; + } | { property: "animation"; value: Animation[]; @@ -5541,6 +5562,38 @@ export type Scroller = "root" | "nearest" | "self"; * @maxItems 2 */ export type Size2DFor_LengthPercentageOrAuto = [LengthPercentageOrAuto, LengthPercentageOrAuto]; +/** + * A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start) property. + */ +export type AnimationRangeStart = AnimationAttachmentRange; +/** + * A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start) or [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property. + */ +export type AnimationAttachmentRange = + | "Normal" + | { + LengthPercentage: DimensionPercentageFor_LengthValue; + } + | { + TimelineRange: { + /** + * The name of the timeline range. + */ + name: TimelineRangeName; + /** + * The offset from the start of the named timeline range. + */ + offset: DimensionPercentageFor_LengthValue; + }; + }; +/** + * A [view progress timeline range](https://drafts.csswg.org/scroll-animations/#view-timelines-ranges) + */ +export type TimelineRangeName = "cover" | "contain" | "entry" | "exit" | "entry-crossing" | "exit-crossing"; +/** + * A value for the [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property. + */ +export type AnimationRangeEnd = AnimationAttachmentRange; /** * An individual [transform function](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#two-d-transform-functions). */ @@ -8575,6 +8628,19 @@ export interface ViewTimeline { */ inset: Size2DFor_LengthPercentageOrAuto; } +/** + * A value for the [animation-range](https://drafts.csswg.org/scroll-animations/#animation-range) shorthand property. + */ +export interface AnimationRange { + /** + * The end of the animation's attachment range. + */ + end: AnimationRangeEnd; + /** + * The start of the animation's attachment range. + */ + start: AnimationRangeStart; +} /** * A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property. */ diff --git a/src/lib.rs b/src/lib.rs index 27a7df16..8b9535f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11585,6 +11585,249 @@ mod tests { ..Browsers::default() }, ); + + minify_test( + ".foo { animation-range-start: entry 10% }", + ".foo{animation-range-start:entry 10%}", + ); + minify_test( + ".foo { animation-range-start: entry 0% }", + ".foo{animation-range-start:entry}", + ); + minify_test( + ".foo { animation-range-start: entry }", + ".foo{animation-range-start:entry}", + ); + minify_test(".foo { animation-range-start: 50% }", ".foo{animation-range-start:50%}"); + minify_test( + ".foo { animation-range-end: exit 10% }", + ".foo{animation-range-end:exit 10%}", + ); + minify_test( + ".foo { animation-range-end: exit 100% }", + ".foo{animation-range-end:exit}", + ); + minify_test(".foo { animation-range-end: exit }", ".foo{animation-range-end:exit}"); + minify_test(".foo { animation-range-end: 50% }", ".foo{animation-range-end:50%}"); + minify_test( + ".foo { animation-range: entry 10% exit 90% }", + ".foo{animation-range:entry 10% exit 90%}", + ); + minify_test( + ".foo { animation-range: entry 0% exit 100% }", + ".foo{animation-range:entry exit}", + ); + minify_test(".foo { animation-range: entry }", ".foo{animation-range:entry}"); + minify_test( + ".foo { animation-range: entry 0% entry 100% }", + ".foo{animation-range:entry}", + ); + minify_test(".foo { animation-range: 50% normal }", ".foo{animation-range:50%}"); + minify_test( + ".foo { animation-range: normal normal }", + ".foo{animation-range:normal}", + ); + test( + r#" + .foo { + animation-range-start: entry 10%; + animation-range-end: exit 90%; + } + "#, + indoc! {r#" + .foo { + animation-range: entry 10% exit 90%; + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: entry 0%; + animation-range-end: entry 100%; + } + "#, + indoc! {r#" + .foo { + animation-range: entry; + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: entry 0%; + animation-range-end: exit 100%; + } + "#, + indoc! {r#" + .foo { + animation-range: entry exit; + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: 10%; + animation-range-end: normal; + } + "#, + indoc! {r#" + .foo { + animation-range: 10%; + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: 10%; + animation-range-end: 90%; + } + "#, + indoc! {r#" + .foo { + animation-range: 10% 90%; + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: entry 10%; + animation-range-end: exit 100%; + } + "#, + indoc! {r#" + .foo { + animation-range: entry 10% exit; + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: 10%; + animation-range-end: exit 90%; + } + "#, + indoc! {r#" + .foo { + animation-range: 10% exit 90%; + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: entry 10%; + animation-range-end: 90%; + } + "#, + indoc! {r#" + .foo { + animation-range: entry 10% 90%; + } + "#}, + ); + test( + r#" + .foo { + animation-range: entry; + animation-range-end: 90%; + } + "#, + indoc! {r#" + .foo { + animation-range: entry 90%; + } + "#}, + ); + test( + r#" + .foo { + animation-range: entry; + animation-range-end: var(--end); + } + "#, + indoc! {r#" + .foo { + animation-range: entry; + animation-range-end: var(--end); + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: entry 10%, entry 50%; + animation-range-end: exit 90%; + } + "#, + indoc! {r#" + .foo { + animation-range-start: entry 10%, entry 50%; + animation-range-end: exit 90%; + } + "#}, + ); + test( + r#" + .foo { + animation-range-start: entry 10%, entry 50%; + animation-range-end: exit 90%, exit 100%; + } + "#, + indoc! {r#" + .foo { + animation-range: entry 10% exit 90%, entry 50% exit; + } + "#}, + ); + test( + r#" + .foo { + animation-range: entry; + animation-range-end: 90%; + animation: spin 100ms; + } + "#, + indoc! {r#" + .foo { + animation: .1s spin; + } + "#}, + ); + test( + r#" + .foo { + animation: spin 100ms; + animation-range: entry; + animation-range-end: 90%; + } + "#, + indoc! {r#" + .foo { + animation: .1s spin; + animation-range: entry 90%; + } + "#}, + ); + test( + r#" + .foo { + animation-range: entry; + animation-range-end: 90%; + animation: var(--animation) 100ms; + } + "#, + indoc! {r#" + .foo { + animation: var(--animation) .1s; + } + "#}, + ); } #[test] diff --git a/src/properties/animation.rs b/src/properties/animation.rs index de83b9fa..9855127c 100644 --- a/src/properties/animation.rs +++ b/src/properties/animation.rs @@ -12,6 +12,7 @@ use crate::properties::{Property, PropertyId, TokenOrValue, VendorPrefix}; use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero}; use crate::values::ident::DashedIdent; use crate::values::number::CSSNumber; +use crate::values::percentage::Percentage; use crate::values::size::Size2D; use crate::values::string::CSSString; use crate::values::{easing::EasingFunction, ident::CustomIdent, time::Time}; @@ -21,7 +22,7 @@ use cssparser::*; use itertools::izip; use smallvec::SmallVec; -use super::LengthPercentageOrAuto; +use super::{LengthPercentage, LengthPercentageOrAuto}; /// A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property. #[derive(Debug, Clone, PartialEq, Parse)] @@ -375,6 +376,201 @@ impl ToCss for ViewTimeline { } } +/// A [view progress timeline range](https://drafts.csswg.org/scroll-animations/#view-timelines-ranges) +#[derive(Debug, Clone, PartialEq, Parse, ToCss)] +#[cfg_attr(feature = "visitor", derive(Visit))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] +pub enum TimelineRangeName { + /// Represents the full range of the view progress timeline. + Cover, + /// Represents the range during which the principal box is either fully contained by, + /// or fully covers, its view progress visibility range within the scrollport. + Contain, + /// Represents the range during which the principal box is entering the view progress visibility range. + Entry, + /// Represents the range during which the principal box is exiting the view progress visibility range. + Exit, + /// Represents the range during which the principal box crosses the end border edge. + EntryCrossing, + /// Represents the range during which the principal box crosses the start border edge. + ExitCrossing, +} + +/// A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start) +/// or [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "visitor", derive(Visit))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] +pub enum AnimationAttachmentRange { + /// The start of the animation’s attachment range is the start of its associated timeline. + Normal, + /// The animation attachment range starts at the specified point on the timeline measuring from the start of the timeline. + LengthPercentage(LengthPercentage), + /// The animation attachment range starts at the specified point on the timeline measuring from the start of the specified named timeline range. + TimelineRange { + /// The name of the timeline range. + name: TimelineRangeName, + /// The offset from the start of the named timeline range. + offset: LengthPercentage, + }, +} + +impl<'i> AnimationAttachmentRange { + fn parse<'t>(input: &mut Parser<'i, 't>, default: f32) -> Result<'i, ParserError<'i>>> { + if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { + return Ok(AnimationAttachmentRange::Normal); + } + + if let Ok(val) = input.try_parse(LengthPercentage::parse) { + return Ok(AnimationAttachmentRange::LengthPercentage(val)); + } + + let name = TimelineRangeName::parse(input)?; + let offset = input + .try_parse(LengthPercentage::parse) + .unwrap_or(LengthPercentage::Percentage(Percentage(default))); + Ok(AnimationAttachmentRange::TimelineRange { name, offset }) + } + + fn to_css(&self, dest: &mut Printer, default: f32) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + match self { + Self::Normal => dest.write_str("normal"), + Self::LengthPercentage(val) => val.to_css(dest), + Self::TimelineRange { name, offset } => { + name.to_css(dest)?; + if *offset != LengthPercentage::Percentage(Percentage(default)) { + dest.write_char(' ')?; + offset.to_css(dest)?; + } + Ok(()) + } + } + } +} + +impl Default for AnimationAttachmentRange { + fn default() -> Self { + AnimationAttachmentRange::Normal + } +} + +/// A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start) property. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "visitor", derive(Visit))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] +pub struct AnimationRangeStart(pub AnimationAttachmentRange); + +impl<'i> Parse<'i> for AnimationRangeStart { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { + let range = AnimationAttachmentRange::parse(input, 0.0)?; + Ok(Self(range)) + } +} + +impl ToCss for AnimationRangeStart { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + self.0.to_css(dest, 0.0) + } +} + +/// A value for the [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "visitor", derive(Visit))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] +pub struct AnimationRangeEnd(pub AnimationAttachmentRange); + +impl<'i> Parse<'i> for AnimationRangeEnd { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { + let range = AnimationAttachmentRange::parse(input, 1.0)?; + Ok(Self(range)) + } +} + +impl ToCss for AnimationRangeEnd { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + self.0.to_css(dest, 1.0) + } +} + +/// A value for the [animation-range](https://drafts.csswg.org/scroll-animations/#animation-range) shorthand property. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "visitor", derive(Visit))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] +pub struct AnimationRange { + /// The start of the animation's attachment range. + pub start: AnimationRangeStart, + /// The end of the animation's attachment range. + pub end: AnimationRangeEnd, +} + +impl<'i> Parse<'i> for AnimationRange { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> { + let start = AnimationRangeStart::parse(input)?; + let end = input + .try_parse(AnimationRangeStart::parse) + .map(|r| AnimationRangeEnd(r.0)) + .unwrap_or_else(|_| { + // If <'animation-range-end'> is omitted and <'animation-range-start'> includes a component, then + // animation-range-end is set to that same and 100%. Otherwise, any omitted longhand is set to its initial value. + match &start.0 { + AnimationAttachmentRange::TimelineRange { name, .. } => { + AnimationRangeEnd(AnimationAttachmentRange::TimelineRange { + name: name.clone(), + offset: LengthPercentage::Percentage(Percentage(1.0)), + }) + } + _ => AnimationRangeEnd(AnimationAttachmentRange::default()), + } + }); + Ok(AnimationRange { start, end }) + } +} + +impl ToCss for AnimationRange { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + self.start.to_css(dest)?; + + let omit_end = match (&self.start.0, &self.end.0) { + ( + AnimationAttachmentRange::TimelineRange { name: start_name, .. }, + AnimationAttachmentRange::TimelineRange { + name: end_name, + offset: end_offset, + }, + ) => start_name == end_name && *end_offset == LengthPercentage::Percentage(Percentage(1.0)), + (_, end) => *end == AnimationAttachmentRange::default(), + }; + + if !omit_end { + dest.write_char(' ')?; + self.end.to_css(dest)?; + } + Ok(()) + } +} + define_list_shorthand! { /// A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property. pub struct Animation<'i>(VendorPrefix) { @@ -524,6 +720,8 @@ pub(crate) struct AnimationHandler<'i> { delays: Option<(SmallVec<[Time; 1]>, VendorPrefix)>, fill_modes: Option<(SmallVec<[AnimationFillMode; 1]>, VendorPrefix)>, timelines: Option<[AnimationTimeline<'i>; 1]>>, + range_starts: Option<[AnimationRangeStart; 1]>>, + range_ends: Option<[AnimationRangeEnd; 1]>>, has_any: bool, } @@ -574,6 +772,19 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> { self.timelines = Some(val.clone()); self.has_any = true; } + Property::AnimationRangeStart(val) => { + self.range_starts = Some(val.clone()); + self.has_any = true; + } + Property::AnimationRangeEnd(val) => { + self.range_ends = Some(val.clone()); + self.has_any = true; + } + Property::AnimationRange(val) => { + self.range_starts = Some(val.iter().map(|v| v.start.clone()).collect()); + self.range_ends = Some(val.iter().map(|v| v.end.clone()).collect()); + self.has_any = true; + } Property::Animation(val, vp) => { let names = val.iter().map(|b| b.name.clone()).collect(); maybe_flush!(names, &names, vp); @@ -609,6 +820,11 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> { property!(play_states, &play_states, vp); property!(delays, &delays, vp); property!(fill_modes, &fill_modes, vp); + + // The animation shorthand resets animation-range + // https://drafts.csswg.org/scroll-animations/#named-range-animation-declaration + self.range_starts = None; + self.range_ends = None; } Property::Unparsed(val) if is_animation_property(&val.property_id) => { let mut val = Cow::Borrowed(val); @@ -636,6 +852,9 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> { _ => {} } } + + self.range_starts = None; + self.range_ends = None; } self.flush(dest, context); @@ -671,6 +890,8 @@ impl<'i> AnimationHandler<'i> { let mut delays = std::mem::take(&mut self.delays); let mut fill_modes = std::mem::take(&mut self.fill_modes); let mut timelines_value = std::mem::take(&mut self.timelines); + let range_starts = std::mem::take(&mut self.range_starts); + let range_ends = std::mem::take(&mut self.range_ends); if let ( Some((names, names_vp)), @@ -813,6 +1034,32 @@ impl<'i> AnimationHandler<'i> { if let Some(val) = timelines_value { dest.push(Property::AnimationTimeline(val)); } + + match (range_starts, range_ends) { + (Some(range_starts), Some(range_ends)) => { + if range_starts.len() == range_ends.len() { + dest.push(Property::AnimationRange( + range_starts + .into_iter() + .zip(range_ends.into_iter()) + .map(|(start, end)| AnimationRange { start, end }) + .collect(), + )); + } else { + dest.push(Property::AnimationRangeStart(range_starts)); + dest.push(Property::AnimationRangeEnd(range_ends)); + } + } + (range_starts, range_ends) => { + if let Some(range_starts) = range_starts { + dest.push(Property::AnimationRangeStart(range_starts)); + } + + if let Some(range_ends) = range_ends { + dest.push(Property::AnimationRangeEnd(range_ends)); + } + } + } } } @@ -829,6 +1076,9 @@ fn is_animation_property(property_id: &PropertyId) -> bool { | PropertyId::AnimationFillMode(_) | PropertyId::AnimationComposition | PropertyId::AnimationTimeline + | PropertyId::AnimationRange + | PropertyId::AnimationRangeStart + | PropertyId::AnimationRangeEnd | PropertyId::Animation(_) => true, _ => false, } diff --git a/src/properties/mod.rs b/src/properties/mod.rs index 38ff8622..7c5f4071 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -1493,6 +1493,9 @@ define_properties! { "animation-fill-mode": AnimationFillMode(SmallVec<[AnimationFillMode; 1]>, VendorPrefix) / WebKit / Moz / O, "animation-composition": AnimationComposition(SmallVec<[AnimationComposition; 1]>), "animation-timeline": AnimationTimeline(SmallVec<[AnimationTimeline<'i>; 1]>), + "animation-range-start": AnimationRangeStart(SmallVec<[AnimationRangeStart; 1]>), + "animation-range-end": AnimationRangeEnd(SmallVec<[AnimationRangeEnd; 1]>), + "animation-range": AnimationRange(SmallVec<[AnimationRange; 1]>), "animation": Animation(AnimationList<'i>, VendorPrefix) / WebKit / Moz / O shorthand: true, // https://drafts.csswg.org/css-transforms-2/ From f7c883131c3463756ac935496cfe5da710eec601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 2 Aug 2024 00:30:56 +0900 Subject: [PATCH 020/156] fix: Fix codegen of `:is(input:checked)` (#783) --- src/selector.rs | 7 ++++++- tests/cli_integration_tests.rs | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/selector.rs b/src/selector.rs index 62550198..56df04c8 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -1624,8 +1624,13 @@ where #[inline] fn has_type_selector(selector: &Selector) -> bool { - let mut iter = selector.iter_raw_parse_order_from(0); + // For input:checked the component vector is + // [input, :checked] so we have to check it using matching order. + // + // This both happens for input:checked and is(input:checked) + let mut iter = selector.iter_raw_match_order(); let first = iter.next(); + if is_namespace(first) { is_type_selector(iter.next()) } else { diff --git a/tests/cli_integration_tests.rs b/tests/cli_integration_tests.rs index 744ac7ca..c57d26e8 100644 --- a/tests/cli_integration_tests.rs +++ b/tests/cli_integration_tests.rs @@ -759,3 +759,23 @@ fn browserslist_environment_from_browserslist_env() -> Result<(), Box Result<(), Box> { + let infile = assert_fs::NamedTempFile::new("test.css")?; + infile.write_str( + r#" + .cb:is(input:checked) { + margin: 3rem; + } + "#, + )?; + let outfile = assert_fs::NamedTempFile::new("test.out")?; + let mut cmd = Command::cargo_bin("lightningcss")?; + cmd.arg(infile.path()); + cmd.arg("--output-file").arg(outfile.path()); + cmd.assert().success(); + outfile.assert(predicate::str::contains(indoc! {r#".cb:is(input:checked)"#})); + + Ok(()) +} From 3e623473839c7fc76b277f97f88c649c6c94e44e Mon Sep 17 00:00:00 2001 From: neverland Date: Thu, 1 Aug 2024 23:31:42 +0800 Subject: [PATCH 021/156] Bump browserslist-rs 0.16.0 (#772) --- Cargo.lock | 156 ++++++++++++++++++++++++++----------------------- Cargo.toml | 2 +- c/Cargo.toml | 2 +- src/targets.rs | 4 +- 4 files changed, 88 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 07d5f7e1..29c70813 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,12 +57,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" -[[package]] -name = "anyhow" -version = "1.0.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" - [[package]] name = "assert_cmd" version = "2.0.12" @@ -145,23 +139,19 @@ dependencies = [ [[package]] name = "browserslist-rs" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "405bbd46590a441abe5db3e5c8af005aa42e640803fecb51912703e93e4ce8d3" +checksum = "fdf0ca73de70c3da94e4194e4a01fe732378f55d47cf4c0588caab22a0dbfa14" dependencies = [ "ahash 0.8.7", - "anyhow", "chrono", "either", "indexmap 2.2.6", - "itertools 0.12.1", + "itertools 0.13.0", "nom", "once_cell", - "quote", "serde", "serde_json", - "string_cache", - "string_cache_codegen", "thiserror", ] @@ -246,14 +236,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -485,9 +475,9 @@ checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" [[package]] name = "either" -version = "1.9.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "equivalent" @@ -699,9 +689,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -955,12 +945,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "new_debug_unreachable" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" - [[package]] name = "nom" version = "7.1.3" @@ -1035,16 +1019,6 @@ dependencies = [ "vlq", ] -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - [[package]] name = "parking_lot_core" version = "0.9.9" @@ -1055,7 +1029,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1586,32 +1560,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "string_cache" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" -dependencies = [ - "new_debug_unreachable", - "once_cell", - "parking_lot", - "phf_shared 0.10.0", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro2", - "quote", -] - [[package]] name = "strsim" version = "0.10.0" @@ -1880,7 +1828,7 @@ version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1889,7 +1837,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1898,13 +1846,29 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -1913,42 +1877,90 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "wyz" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 75bc397c..80e64af3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,7 @@ paste = "1.0.12" # CLI deps atty = { version = "0.2", optional = true } clap = { version = "3.0.6", features = ["derive"], optional = true } -browserslist-rs = { version = "0.15.0", optional = true } +browserslist-rs = { version = "0.16.0", optional = true } rayon = { version = "1.5.1", optional = true } dashmap = { version = "5.0.0", optional = true } serde_json = { version = "1.0.78", optional = true } diff --git a/c/Cargo.toml b/c/Cargo.toml index 443d34ba..96641dc7 100644 --- a/c/Cargo.toml +++ b/c/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["cdylib"] [dependencies] lightningcss = { path = "../", features = ["browserslist"] } parcel_sourcemap = { version = "2.1.1", features = ["json"] } -browserslist-rs = { version = "0.15.0" } +browserslist-rs = { version = "0.16.0" } [build-dependencies] cbindgen = "0.24.3" diff --git a/src/targets.rs b/src/targets.rs index 3dd40061..4d44ddae 100644 --- a/src/targets.rs +++ b/src/targets.rs @@ -48,7 +48,7 @@ impl Browsers { ) -> Result