diff --git a/.cargo/config.toml b/.cargo/config.toml
index a05c05ab5..10746d766 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -1,3 +1,5 @@
+[target.'cfg(target_env = "gnu")']
+rustflags = ["-C", "link-args=-Wl,-z,nodelete"]
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
@@ -10,4 +12,16 @@ linker = "aarch64-linux-musl-gcc"
rustflags = ["-C", "target-feature=-crt-static"]
[target.wasm32-unknown-unknown]
-rustflags = ["-C", "link-arg=--export-table"]
+rustflags = [
+ "-C",
+ "link-arg=--export-table",
+ '--cfg',
+ 'getrandom_backend="custom"',
+]
+
+
+# Statically link Visual Studio redistributables on Windows builds
+[target.x86_64-pc-windows-msvc]
+rustflags = ["-C", "target-feature=+crt-static"]
+[target.aarch64-pc-windows-msvc]
+rustflags = ["-C", "target-feature=+crt-static"]
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0a0a9618a..95d304311 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -48,7 +48,7 @@ jobs:
if: ${{ matrix.strip }}
run: ${{ matrix.strip }} *.node ${{ matrix.binary }}
- name: Upload artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: bindings-${{ matrix.target }}
path: |
@@ -90,7 +90,7 @@ jobs:
- name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034
run: strip -x *.node lightningcss
- name: Upload artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: bindings-aarch64-apple-darwin
path: |
@@ -109,6 +109,9 @@ jobs:
- target: aarch64-unknown-linux-gnu
strip: llvm-strip
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
+ - target: aarch64-linux-android
+ strip: llvm-strip
+ image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
- target: armv7-unknown-linux-gnueabihf
strip: llvm-strip
image: ghcr.io/napi-rs/napi-rs/nodejs-rust@sha256:c22284b2d79092d3e885f64ede00f6afdeb2ccef7e2b6e78be52e7909091cd57
@@ -133,6 +136,14 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
+ - name: Setup Android NDK
+ if: ${{ matrix.target == 'aarch64-linux-android' }}
+ run: |
+ sudo apt update && sudo apt install unzip -y
+ cd /tmp
+ wget -q https://dl.google.com/android/repository/android-ndk-r28-linux.zip -O /tmp/ndk.zip
+ unzip ndk.zip
+
- name: Setup cross compile toolchain
if: ${{ matrix.setup }}
run: ${{ matrix.setup }}
@@ -144,8 +155,11 @@ jobs:
- name: Build release
run: yarn build-release
env:
+ ANDROID_NDK_LATEST_HOME: /tmp/android-ndk-r28
RUST_TARGET: ${{ matrix.target }}
- name: Build CLI
+ env:
+ ANDROID_NDK_LATEST_HOME: /tmp/android-ndk-r28
run: |
yarn napi build --bin lightningcss --release --features cli --target ${{ matrix.target }}
mv target/${{ matrix.target }}/release/lightningcss lightningcss
@@ -153,7 +167,7 @@ jobs:
if: ${{ matrix.strip }}
run: ${{ matrix.strip }} *.node lightningcss
- name: Upload artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: bindings-${{ matrix.target }}
path: |
@@ -201,7 +215,7 @@ jobs:
rm -rf .yarn/cache
- name: Upload artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: bindings-x86_64-unknown-freebsd
path: |
@@ -232,7 +246,7 @@ jobs:
export PATH="$PATH:./binaryen-version_111/bin"
yarn wasm:build-release
- name: Upload artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: wasm
path: wasm/lightningcss_node.wasm
@@ -250,9 +264,11 @@ jobs:
- uses: actions/checkout@v3
- uses: bahmutov/npm-install@v1.8.32
- name: Download artifacts
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
path: artifacts
+ - name: Show artifacts
+ run: ls -R artifacts
- name: Build npm packages
run: |
node scripts/build-npm.js
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e81411540..10868f4a6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -47,6 +47,13 @@ yarn wasm:build
yarn wasm:build-release
```
+Note: If you plan to build the WASM target, ensure that you have the required toolchain and binaries installed.
+
+```sh
+rustup target add wasm32-unknown-unknown
+cargo install wasm-opt
+```
+
## Website
The website is built using [Parcel](https://parceljs.org). You can start the development server by running:
diff --git a/Cargo.lock b/Cargo.lock
index 671f2e017..bb4bb2840 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,26 +1,26 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
[[package]]
name = "ahash"
-version = "0.7.7"
+version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd"
+checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
dependencies = [
- "getrandom",
+ "getrandom 0.2.15",
"once_cell",
"version_check",
]
[[package]]
name = "ahash"
-version = "0.8.7"
+version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
- "getrandom",
+ "getrandom 0.3.3",
"once_cell",
"serde",
"version_check",
@@ -29,9 +29,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
-version = "1.1.2"
+version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
@@ -53,20 +53,21 @@ dependencies = [
[[package]]
name = "anstyle"
-version = "1.0.4"
+version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "assert_cmd"
-version = "2.0.12"
+version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6"
+checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d"
dependencies = [
"anstyle",
"bstr",
"doc-comment",
- "predicates 3.0.4",
+ "libc",
+ "predicates 3.1.3",
"predicates-core",
"predicates-tree",
"wait-timeout",
@@ -74,14 +75,14 @@ dependencies = [
[[package]]
name = "assert_fs"
-version = "1.0.13"
+version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f070617a68e5c2ed5d06ee8dd620ee18fb72b99f6c094bed34cf8ab07c875b48"
+checksum = "7efdb1fdb47602827a342857666feb372712cbc64b414172bd6b167a02927674"
dependencies = [
"anstyle",
"doc-comment",
"globwalk",
- "predicates 3.0.4",
+ "predicates 3.1.3",
"predicates-core",
"predicates-tree",
"tempfile",
@@ -100,9 +101,9 @@ dependencies = [
[[package]]
name = "autocfg"
-version = "1.1.0"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "base64-simd"
@@ -121,9 +122,12 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
-version = "2.4.1"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+dependencies = [
+ "serde",
+]
[[package]]
name = "bitvec"
@@ -137,19 +141,28 @@ dependencies = [
"wyz 0.5.1",
]
+[[package]]
+name = "browserslist-data"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c49471c5ae53cefe3ac4acc4d3c75cb4b68995b70b3bbb864f8e08fae282098c"
+dependencies = [
+ "ahash 0.8.12",
+ "chrono",
+]
+
[[package]]
name = "browserslist-rs"
-version = "0.16.0"
+version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fdf0ca73de70c3da94e4194e4a01fe732378f55d47cf4c0588caab22a0dbfa14"
+checksum = "8dd48a6ca358df4f7000e3fb5f08738b1b91a0e5d5f862e2f77b2b14647547f5"
dependencies = [
- "ahash 0.8.7",
+ "ahash 0.8.12",
+ "browserslist-data",
"chrono",
"either",
- "indexmap 2.2.6",
"itertools 0.13.0",
"nom",
- "once_cell",
"serde",
"serde_json",
"thiserror",
@@ -157,9 +170,9 @@ dependencies = [
[[package]]
name = "bstr"
-version = "1.7.0"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019"
+checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8"
dependencies = [
"memchr",
"regex-automata",
@@ -168,15 +181,15 @@ dependencies = [
[[package]]
name = "bumpalo"
-version = "3.14.0"
+version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "bytecheck"
-version = "0.6.11"
+version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627"
+checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2"
dependencies = [
"bytecheck_derive",
"ptr_meta",
@@ -185,9 +198,9 @@ dependencies = [
[[package]]
name = "bytecheck_derive"
-version = "0.6.11"
+version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61"
+checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659"
dependencies = [
"proc-macro2",
"quote",
@@ -195,10 +208,10 @@ dependencies = [
]
[[package]]
-name = "byteorder"
-version = "1.5.0"
+name = "bytes"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
[[package]]
name = "cbindgen"
@@ -221,11 +234,11 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.0.83"
+version = "1.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e"
dependencies = [
- "libc",
+ "shlex",
]
[[package]]
@@ -236,14 +249,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
-version = "0.4.38"
+version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [
"android-tzdata",
"iana-time-zone",
"num-traits",
- "windows-targets 0.52.6",
+ "windows-targets",
]
[[package]]
@@ -316,52 +329,43 @@ dependencies = [
[[package]]
name = "core-foundation-sys"
-version = "0.8.4"
+version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "crossbeam-channel"
-version = "0.5.8"
+version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
+checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
dependencies = [
- "cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
-version = "0.8.3"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
- "cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
-version = "0.9.15"
+version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
- "autocfg",
- "cfg-if",
"crossbeam-utils",
- "memoffset",
- "scopeguard",
]
[[package]]
name = "crossbeam-utils"
-version = "0.8.16"
+version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
-dependencies = [
- "cfg-if",
-]
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "cssparser"
@@ -372,7 +376,7 @@ dependencies = [
"cssparser-macros",
"dtoa-short",
"itoa",
- "phf 0.11.2",
+ "phf",
"serde",
"smallvec",
]
@@ -393,17 +397,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
- "syn 2.0.63",
+ "syn 2.0.90",
]
[[package]]
name = "ctor"
-version = "0.2.7"
+version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad291aa74992b9b7a7e88c38acbbf6ad7e107f1d90ee8775b7bc1fc3394f485c"
+checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
dependencies = [
"quote",
- "syn 2.0.63",
+ "syn 2.0.90",
]
[[package]]
@@ -413,7 +417,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
- "hashbrown 0.14.2",
+ "hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core",
@@ -421,9 +425,9 @@ dependencies = [
[[package]]
name = "data-encoding"
-version = "2.4.0"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
+checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
[[package]]
name = "data-url"
@@ -460,18 +464,18 @@ checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653"
[[package]]
name = "dtoa-short"
-version = "0.3.4"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbaceec3c6e4211c79e7b1800fb9680527106beb2f9c51904a3210c03a448c74"
+checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87"
dependencies = [
"dtoa",
]
[[package]]
name = "dyn-clone"
-version = "1.0.16"
+version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d"
+checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "either"
@@ -487,9 +491,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
-version = "0.3.6"
+version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
+checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys",
@@ -497,9 +501,9 @@ dependencies = [
[[package]]
name = "fastrand"
-version = "2.0.1"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "float-cmp"
@@ -510,12 +514,6 @@ dependencies = [
"num-traits",
]
-[[package]]
-name = "fnv"
-version = "1.0.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
-
[[package]]
name = "fs_extra"
version = "1.3.0"
@@ -529,45 +527,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
-name = "fxhash"
-version = "0.2.1"
+name = "getrandom"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
- "byteorder",
+ "cfg-if",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
-version = "0.2.10"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"libc",
- "wasi",
+ "r-efi",
+ "wasi 0.14.2+wasi-0.2.4",
]
[[package]]
name = "globset"
-version = "0.4.13"
+version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d"
+checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19"
dependencies = [
"aho-corasick",
"bstr",
- "fnv",
"log",
- "regex",
+ "regex-automata",
+ "regex-syntax",
]
[[package]]
name = "globwalk"
-version = "0.8.1"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
+checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.6.0",
"ignore",
"walkdir",
]
@@ -578,14 +579,20 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
- "ahash 0.7.7",
+ "ahash 0.7.8",
]
[[package]]
name = "hashbrown"
-version = "0.14.2"
+version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "heck"
@@ -604,9 +611,9 @@ dependencies = [
[[package]]
name = "iana-time-zone"
-version = "0.1.58"
+version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
+checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@@ -627,17 +634,16 @@ dependencies = [
[[package]]
name = "ignore"
-version = "0.4.20"
+version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
+checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b"
dependencies = [
+ "crossbeam-deque",
"globset",
- "lazy_static",
"log",
"memchr",
- "regex",
+ "regex-automata",
"same-file",
- "thread_local",
"walkdir",
"winapi-util",
]
@@ -654,12 +660,12 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.2.6"
+version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
+checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [
"equivalent",
- "hashbrown 0.14.2",
+ "hashbrown 0.15.2",
"serde",
]
@@ -678,15 +684,6 @@ dependencies = [
"either",
]
-[[package]]
-name = "itertools"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
-dependencies = [
- "either",
-]
-
[[package]]
name = "itertools"
version = "0.13.0"
@@ -698,9 +695,9 @@ dependencies = [
[[package]]
name = "itoa"
-version = "1.0.9"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "jemalloc-sys"
@@ -725,44 +722,45 @@ dependencies = [
[[package]]
name = "js-sys"
-version = "0.3.65"
+version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
+checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
dependencies = [
+ "once_cell",
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
-version = "1.4.0"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
-version = "0.2.150"
+version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
+checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "libloading"
-version = "0.8.1"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
+checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
- "windows-sys",
+ "windows-targets",
]
[[package]]
name = "lightningcss"
-version = "1.0.0-alpha.58"
+version = "1.0.0-alpha.70"
dependencies = [
- "ahash 0.8.7",
+ "ahash 0.8.12",
"assert_cmd",
"assert_fs",
"atty",
- "bitflags 2.4.1",
+ "bitflags 2.6.0",
"browserslist-rs",
"clap",
"const-str",
@@ -770,7 +768,8 @@ dependencies = [
"cssparser-color",
"dashmap",
"data-encoding",
- "getrandom",
+ "getrandom 0.3.3",
+ "indexmap 2.7.0",
"indoc",
"itertools 0.10.5",
"jemallocator",
@@ -778,13 +777,14 @@ dependencies = [
"lightningcss-derive",
"parcel_selectors",
"parcel_sourcemap",
- "paste",
+ "pastey",
"pathdiff",
"predicates 2.1.5",
"pretty_assertions",
"rayon",
"schemars",
"serde",
+ "serde-content",
"serde_json",
"smallvec",
"static-self",
@@ -802,7 +802,7 @@ dependencies = [
[[package]]
name = "lightningcss-napi"
-version = "0.2.1"
+version = "0.4.7"
dependencies = [
"crossbeam-channel",
"cssparser",
@@ -811,6 +811,7 @@ dependencies = [
"parcel_sourcemap",
"rayon",
"serde",
+ "serde-content",
"serde-detach",
"serde_bytes",
"smallvec",
@@ -839,15 +840,15 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
-version = "0.4.10"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "lock_api"
-version = "0.4.11"
+version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
@@ -855,9 +856,9 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.20"
+version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "matches"
@@ -867,18 +868,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "memchr"
-version = "2.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
-
-[[package]]
-name = "memoffset"
-version = "0.9.0"
+version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
-dependencies = [
- "autocfg",
-]
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minimal-lexical"
@@ -888,11 +880,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "napi"
-version = "2.15.4"
+version = "2.16.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72e0dc78e0524286630914db66e31bad70160e379705a9ce92e0161ce2389d89"
+checksum = "214f07a80874bb96a8433b3cdfc84980d56c7b02e1a0d7ba4ba0db5cef785e2b"
dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.6.0",
"ctor",
"napi-derive",
"napi-sys",
@@ -909,23 +901,23 @@ checksum = "ebd4419172727423cf30351406c54f6cc1b354a2cfb4f1dba3e6cd07f6d5522b"
[[package]]
name = "napi-derive"
-version = "2.15.3"
+version = "2.16.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e56bd9f0bd84c1f138c5cb22bbf394f75d796b24dad689599ca94cf94e61cc21"
+checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c"
dependencies = [
"cfg-if",
"convert_case",
"napi-derive-backend",
"proc-macro2",
"quote",
- "syn 2.0.63",
+ "syn 2.0.90",
]
[[package]]
name = "napi-derive-backend"
-version = "1.0.61"
+version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d03b8f403a37007cad225039fc0323b961bb40d697eea744140920ebb689ff1d"
+checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf"
dependencies = [
"convert_case",
"once_cell",
@@ -933,14 +925,14 @@ dependencies = [
"quote",
"regex",
"semver",
- "syn 2.0.63",
+ "syn 2.0.90",
]
[[package]]
name = "napi-sys"
-version = "2.3.0"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2503fa6af34dc83fb74888df8b22afe933b58d37daf7d80424b1c60c68196b8b"
+checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3"
dependencies = [
"libloading",
]
@@ -963,18 +955,18 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
[[package]]
name = "num-traits"
-version = "0.2.17"
+version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
-version = "1.19.0"
+version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "os_str_bytes"
@@ -990,15 +982,15 @@ checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4"
[[package]]
name = "parcel_selectors"
-version = "0.26.6"
+version = "0.28.2"
dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.6.0",
"cssparser",
- "fxhash",
"log",
- "phf 0.10.1",
+ "phf",
"phf_codegen",
"precomputed-hash",
+ "rustc-hash",
"schemars",
"serde",
"smallvec",
@@ -1021,37 +1013,28 @@ dependencies = [
[[package]]
name = "parking_lot_core"
-version = "0.9.9"
+version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
- "windows-targets 0.48.5",
+ "windows-targets",
]
[[package]]
-name = "paste"
-version = "1.0.14"
+name = "pastey"
+version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+checksum = "b3a8cb46bdc156b1c90460339ae6bfd45ba0394e5effbaa640badb4987fdc261"
[[package]]
name = "pathdiff"
-version = "0.2.1"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
-
-[[package]]
-name = "phf"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
-dependencies = [
- "phf_shared 0.10.0",
-]
+checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]]
name = "phf"
@@ -1060,27 +1043,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_macros",
- "phf_shared 0.11.2",
+ "phf_shared",
]
[[package]]
name = "phf_codegen"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
-dependencies = [
- "phf_generator 0.10.0",
- "phf_shared 0.10.0",
-]
-
-[[package]]
-name = "phf_generator"
-version = "0.10.0"
+version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
+checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
dependencies = [
- "phf_shared 0.10.0",
- "rand",
+ "phf_generator",
+ "phf_shared",
]
[[package]]
@@ -1089,7 +1062,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
dependencies = [
- "phf_shared 0.11.2",
+ "phf_shared",
"rand",
]
@@ -1099,20 +1072,11 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
dependencies = [
- "phf_generator 0.11.2",
- "phf_shared 0.11.2",
+ "phf_generator",
+ "phf_shared",
"proc-macro2",
"quote",
- "syn 2.0.63",
-]
-
-[[package]]
-name = "phf_shared"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
-dependencies = [
- "siphasher",
+ "syn 2.0.90",
]
[[package]]
@@ -1124,12 +1088,6 @@ dependencies = [
"siphasher",
]
-[[package]]
-name = "ppv-lite86"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
-
[[package]]
name = "precomputed-hash"
version = "0.1.1"
@@ -1152,27 +1110,26 @@ dependencies = [
[[package]]
name = "predicates"
-version = "3.0.4"
+version = "3.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0"
+checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573"
dependencies = [
"anstyle",
"difflib",
- "itertools 0.11.0",
"predicates-core",
]
[[package]]
name = "predicates-core"
-version = "1.0.6"
+version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174"
+checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa"
[[package]]
name = "predicates-tree"
-version = "1.0.9"
+version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf"
+checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c"
dependencies = [
"predicates-core",
"termtree",
@@ -1180,9 +1137,9 @@ dependencies = [
[[package]]
name = "pretty_assertions"
-version = "1.4.0"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
+checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
@@ -1214,9 +1171,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.82"
+version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
+checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
@@ -1243,13 +1200,19 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.36"
+version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
[[package]]
name = "radium"
version = "0.7.0"
@@ -1262,18 +1225,6 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
- "libc",
- "rand_chacha",
- "rand_core",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
-dependencies = [
- "ppv-lite86",
"rand_core",
]
@@ -1282,15 +1233,12 @@ name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
-dependencies = [
- "getrandom",
-]
[[package]]
name = "rayon"
-version = "1.8.0"
+version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
+checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
@@ -1298,9 +1246,9 @@ dependencies = [
[[package]]
name = "rayon-core"
-version = "1.12.0"
+version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
@@ -1308,18 +1256,18 @@ dependencies = [
[[package]]
name = "redox_syscall"
-version = "0.4.1"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.6.0",
]
[[package]]
name = "regex"
-version = "1.10.2"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
@@ -1329,9 +1277,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.4.3"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
@@ -1340,27 +1288,28 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.8.2"
+version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rend"
-version = "0.4.1"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd"
+checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
dependencies = [
"bytecheck",
]
[[package]]
name = "rkyv"
-version = "0.7.42"
+version = "0.7.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58"
+checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b"
dependencies = [
"bitvec",
"bytecheck",
+ "bytes",
"hashbrown 0.12.3",
"ptr_meta",
"rend",
@@ -1372,22 +1321,28 @@ dependencies = [
[[package]]
name = "rkyv_derive"
-version = "0.7.42"
+version = "0.7.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d"
+checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
+[[package]]
+name = "rustc-hash"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
+
[[package]]
name = "rustix"
-version = "0.38.21"
+version = "0.38.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
+checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.6.0",
"errno",
"libc",
"linux-raw-sys",
@@ -1396,9 +1351,9 @@ dependencies = [
[[package]]
name = "ryu"
-version = "1.0.15"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "same-file"
@@ -1411,11 +1366,12 @@ dependencies = [
[[package]]
name = "schemars"
-version = "0.8.19"
+version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc6e7ed6919cb46507fb01ff1654309219f62b4d603822501b0b80d42f6f21ef"
+checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92"
dependencies = [
"dyn-clone",
+ "indexmap 2.7.0",
"schemars_derive",
"serde",
"serde_json",
@@ -1424,14 +1380,14 @@ dependencies = [
[[package]]
name = "schemars_derive"
-version = "0.8.19"
+version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "185f2b7aa7e02d418e453790dde16890256bbd2bcd04b7dc5348811052b53f49"
+checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
- "syn 2.0.63",
+ "syn 2.0.90",
]
[[package]]
@@ -1448,19 +1404,29 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "semver"
-version = "1.0.22"
+version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
+checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
[[package]]
name = "serde"
-version = "1.0.201"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
+ "serde_core",
"serde_derive",
]
+[[package]]
+name = "serde-content"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3753ca04f350fa92d00b6146a3555e63c55388c9ef2e11e09bce2ff1c0b509c6"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "serde-detach"
version = "0.0.1"
@@ -1473,46 +1439,62 @@ dependencies = [
[[package]]
name = "serde_bytes"
-version = "0.11.12"
+version = "0.11.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff"
+checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a"
dependencies = [
"serde",
]
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
[[package]]
name = "serde_derive"
-version = "1.0.201"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.63",
+ "syn 2.0.90",
]
[[package]]
name = "serde_derive_internals"
-version = "0.29.0"
+version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
+checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.63",
+ "syn 2.0.90",
]
[[package]]
name = "serde_json"
-version = "1.0.108"
+version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
+checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"itoa",
+ "memchr",
"ryu",
"serde",
]
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
[[package]]
name = "simd-abstraction"
version = "0.7.1"
@@ -1524,9 +1506,9 @@ dependencies = [
[[package]]
name = "simdutf8"
-version = "0.1.4"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "siphasher"
@@ -1536,17 +1518,18 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "smallvec"
-version = "1.11.1"
+version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
dependencies = [
"serde",
]
[[package]]
name = "static-self"
-version = "0.1.1"
+version = "0.1.2"
dependencies = [
+ "indexmap 2.7.0",
"smallvec",
"static-self-derive",
]
@@ -1579,9 +1562,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.63"
+version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704"
+checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
@@ -1596,73 +1579,63 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
-version = "3.8.1"
+version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
+checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
dependencies = [
"cfg-if",
"fastrand",
- "redox_syscall",
+ "once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "termcolor"
-version = "1.3.0"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "termtree"
-version = "0.4.1"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
+checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
[[package]]
name = "textwrap"
-version = "0.16.0"
+version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
+checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
[[package]]
name = "thiserror"
-version = "1.0.50"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.50"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.63",
-]
-
-[[package]]
-name = "thread_local"
-version = "1.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
-dependencies = [
- "cfg-if",
- "once_cell",
+ "syn 2.0.90",
]
[[package]]
name = "tinyvec"
-version = "1.6.0"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8"
dependencies = [
"tinyvec_macros",
]
@@ -1684,27 +1657,27 @@ dependencies = [
[[package]]
name = "unicode-ident"
-version = "1.0.12"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unicode-segmentation"
-version = "1.11.0"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
+checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "uuid"
-version = "1.5.0"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
+checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
[[package]]
name = "version_check"
-version = "0.9.4"
+version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "vlq"
@@ -1723,9 +1696,9 @@ dependencies = [
[[package]]
name = "walkdir"
-version = "2.4.0"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
@@ -1737,36 +1710,45 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+[[package]]
+name = "wasi"
+version = "0.14.2+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
+dependencies = [
+ "wit-bindgen-rt",
+]
+
[[package]]
name = "wasm-bindgen"
-version = "0.2.88"
+version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
+checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
dependencies = [
"cfg-if",
+ "once_cell",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.88"
+version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
+checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
dependencies = [
"bumpalo",
"log",
- "once_cell",
"proc-macro2",
"quote",
- "syn 2.0.63",
+ "syn 2.0.90",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.88"
+version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
+checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1774,22 +1756,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.88"
+version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
+checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.63",
+ "syn 2.0.90",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.88"
+version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
+checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
[[package]]
name = "winapi"
@@ -1809,11 +1791,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
-version = "0.1.6"
+version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
- "winapi",
+ "windows-sys",
]
[[package]]
@@ -1824,35 +1806,20 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
-version = "0.51.1"
+version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
- "windows-targets 0.48.5",
+ "windows-targets",
]
[[package]]
name = "windows-sys"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
-dependencies = [
- "windows-targets 0.48.5",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.48.5"
+version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
- "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",
+ "windows-targets",
]
[[package]]
@@ -1861,46 +1828,28 @@ 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_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
"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",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
]
-[[package]]
-name = "windows_aarch64_gnullvm"
-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"
@@ -1913,36 +1862,18 @@ 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"
@@ -1951,15 +1882,18 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.48.5"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
-name = "windows_x86_64_msvc"
-version = "0.52.6"
+name = "wit-bindgen-rt"
+version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+dependencies = [
+ "bitflags 2.6.0",
+]
[[package]]
name = "wyz"
@@ -1978,26 +1912,26 @@ dependencies = [
[[package]]
name = "yansi"
-version = "0.5.1"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
+checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "zerocopy"
-version = "0.7.32"
+version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
+checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.7.32"
+version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
+checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.63",
+ "syn 2.0.90",
]
diff --git a/Cargo.toml b/Cargo.toml
index dd55452aa..c113a3921 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,17 +6,17 @@ members = [
"c",
"derive",
"static-self",
- "static-self-derive"
+ "static-self-derive",
]
[package]
authors = ["Devon Govett "]
name = "lightningcss"
-version = "1.0.0-alpha.58"
+version = "1.0.0-alpha.70"
description = "A CSS parser, transformer, and minifier"
license = "MPL-2.0"
edition = "2021"
-keywords = [ "CSS", "minifier", "Parcel" ]
+keywords = ["CSS", "minifier", "Parcel"]
repository = "https://github.com/parcel-bundler/lightningcss"
[package.metadata.docs.rs]
@@ -34,24 +34,37 @@ path = "src/lib.rs"
crate-type = ["rlib"]
[features]
-default = ["bundler", "grid", "nodejs", "sourcemap"]
+default = ["bundler", "nodejs", "sourcemap"]
browserslist = ["browserslist-rs"]
bundler = ["dashmap", "sourcemap", "rayon"]
cli = ["atty", "clap", "serde_json", "browserslist", "jemallocator"]
-grid = []
jsonschema = ["schemars", "serde", "parcel_selectors/jsonschema"]
-nodejs = ["dep:serde"]
-serde = ["dep:serde", "smallvec/serde", "cssparser/serde", "parcel_selectors/serde", "into_owned"]
+nodejs = ["dep:serde", "dep:serde-content"]
+serde = [
+ "dep:serde",
+ "dep:serde-content",
+ "bitflags/serde",
+ "smallvec/serde",
+ "cssparser/serde",
+ "parcel_selectors/serde",
+ "into_owned",
+]
sourcemap = ["parcel_sourcemap"]
visitor = []
-into_owned = ["static-self", "static-self/smallvec", "parcel_selectors/into_owned"]
+into_owned = [
+ "static-self",
+ "static-self/smallvec",
+ "static-self/indexmap",
+ "parcel_selectors/into_owned",
+]
substitute_variables = ["visitor", "into_owned"]
[dependencies]
-serde = { version = "1.0.201", features = ["derive"], optional = true }
+serde = { version = "1.0.228", features = ["derive"], optional = true }
+serde-content = { version = "0.1.2", features = ["serde"], optional = true }
cssparser = "0.33.0"
cssparser-color = "0.1.0"
-parcel_selectors = { version = "0.26.6", path = "./selectors" }
+parcel_selectors = { version = "0.28.2", path = "./selectors" }
itertools = "0.10.1"
smallvec = { version = "1.7.0", features = ["union"] }
bitflags = "2.2.1"
@@ -61,23 +74,26 @@ lazy_static = "1.4.0"
const-str = "0.3.1"
pathdiff = "0.2.1"
ahash = "0.8.7"
-paste = "1.0.12"
+pastey = "0.1.0"
+indexmap = { version = "2.2.6", features = ["serde"] }
# CLI deps
atty = { version = "0.2", optional = true }
clap = { version = "3.0.6", features = ["derive"], optional = true }
-browserslist-rs = { version = "0.16.0", optional = true }
+browserslist-rs = { version = "0.19.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.43", path = "./derive" }
-schemars = { version = "0.8.19", features = ["smallvec"], optional = true }
-static-self = { version = "0.1.0", path = "static-self", optional = true }
+schemars = { version = "0.8.19", features = ["smallvec", "indexmap2"], optional = true }
+static-self = { version = "0.1.2", path = "static-self", optional = true }
[target.'cfg(target_os = "macos")'.dependencies]
-jemallocator = { version = "0.3.2", features = ["disable_initial_exec_tls"], optional = true }
+jemallocator = { version = "0.3.2", features = [
+ "disable_initial_exec_tls",
+], optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
-getrandom = { version = "0.2", features = ["custom"], default-features = false }
+getrandom = { version = "0.3", default-features = false }
[dev-dependencies]
indoc = "1.0.3"
diff --git a/c/Cargo.toml b/c/Cargo.toml
index 96641dc75..f9a9d167f 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.16.0" }
+browserslist-rs = { version = "0.19.0" }
[build-dependencies]
cbindgen = "0.24.3"
diff --git a/c/build.rs b/c/build.rs
index 6dac4f91c..02fcb75b1 100644
--- a/c/build.rs
+++ b/c/build.rs
@@ -1,5 +1,3 @@
-extern crate cbindgen;
-
use std::env;
fn main() {
diff --git a/c/src/lib.rs b/c/src/lib.rs
index ba689d967..759a18dbe 100644
--- a/c/src/lib.rs
+++ b/c/src/lib.rs
@@ -281,6 +281,7 @@ pub extern "C" fn lightningcss_stylesheet_parse(
Some(lightningcss::css_modules::Config {
pattern,
dashed_idents: options.css_modules_dashed_idents,
+ ..Default::default()
})
} else {
None
diff --git a/cli/postinstall.js b/cli/postinstall.js
index abf9dc191..19dadc796 100644
--- a/cli/postinstall.js
+++ b/cli/postinstall.js
@@ -3,7 +3,8 @@ let path = require('path');
let parts = [process.platform, process.arch];
if (process.platform === 'linux') {
- const {MUSL, family} = require('detect-libc');
+ const {MUSL, familySync} = require('detect-libc');
+ const family = familySync();
if (family === MUSL) {
parts.push('musl');
} else if (process.arch === 'arm') {
diff --git a/napi/Cargo.toml b/napi/Cargo.toml
index 477c1898c..789062ea6 100644
--- a/napi/Cargo.toml
+++ b/napi/Cargo.toml
@@ -1,7 +1,7 @@
[package]
authors = ["Devon Govett "]
name = "lightningcss-napi"
-version = "0.2.1"
+version = "0.4.7"
description = "Node-API bindings for Lightning CSS"
license = "MPL-2.0"
repository = "https://github.com/parcel-bundler/lightningcss"
@@ -14,12 +14,20 @@ bundler = ["dep:crossbeam-channel", "dep:rayon"]
[dependencies]
serde = { version = "1.0.201", features = ["derive"] }
+serde-content = { version = "0.1.2", features = ["serde"] }
serde_bytes = "0.11.5"
cssparser = "0.33.0"
-lightningcss = { version = "1.0.0-alpha.56", path = "../", features = ["nodejs", "serde"] }
+lightningcss = { version = "1.0.0-alpha.70", path = "../", features = [
+ "nodejs",
+ "serde",
+] }
parcel_sourcemap = { version = "2.1.1", features = ["json"] }
serde-detach = "0.0.1"
smallvec = { version = "1.7.0", features = ["union"] }
-napi = {version = "2", default-features = false, features = ["napi4", "napi5", "serde-json"]}
+napi = { version = "2", default-features = false, features = [
+ "napi4",
+ "napi5",
+ "serde-json",
+] }
crossbeam-channel = { version = "0.5.6", optional = true }
rayon = { version = "1.5.1", optional = true }
diff --git a/napi/src/lib.rs b/napi/src/lib.rs
index 9edfb1ce6..f43a8d46e 100644
--- a/napi/src/lib.rs
+++ b/napi/src/lib.rs
@@ -121,8 +121,8 @@ pub fn transform_style_attribute(ctx: CallContext) -> napi::Result {
mod bundle {
use super::*;
use crossbeam_channel::{self, Receiver, Sender};
- use lightningcss::bundler::FileProvider;
- use napi::{Env, JsFunction, JsString, NapiRaw};
+ use lightningcss::bundler::{FileProvider, ResolveResult};
+ use napi::{Env, JsBoolean, JsFunction, JsString, NapiRaw};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::Mutex;
@@ -169,6 +169,7 @@ mod bundle {
// Allocate a single channel per thread to communicate with the JS thread.
thread_local! {
static CHANNEL: (Sender>, Receiver>) = crossbeam_channel::unbounded();
+ static RESOLVER_CHANNEL: (Sender>, Receiver>) = crossbeam_channel::unbounded();
}
impl SourceProvider for JsSourceProvider {
@@ -203,9 +204,9 @@ mod bundle {
}
}
- fn resolve(&self, specifier: &str, originating_file: &Path) -> Result {
+ fn resolve(&self, specifier: &str, originating_file: &Path) -> Result {
if let Some(resolve) = &self.resolve {
- return CHANNEL.with(|channel| {
+ return RESOLVER_CHANNEL.with(|channel| {
let message = ResolveMessage {
specifier: specifier.to_owned(),
originating_file: originating_file.to_str().unwrap().to_owned(),
@@ -213,22 +214,18 @@ mod bundle {
};
resolve.call(message, ThreadsafeFunctionCallMode::Blocking);
- let result = channel.1.recv().unwrap();
- match result {
- Ok(result) => Ok(PathBuf::from_str(&result).unwrap()),
- Err(e) => Err(e),
- }
+ channel.1.recv().unwrap()
});
}
- Ok(originating_file.with_file_name(specifier))
+ Ok(originating_file.with_file_name(specifier).into())
}
}
struct ResolveMessage {
specifier: String,
originating_file: String,
- tx: Sender>,
+ tx: Sender>,
}
struct ReadMessage {
@@ -241,7 +238,11 @@ mod bundle {
tx: Sender>,
}
- fn await_promise(env: Env, result: JsUnknown, tx: Sender>) -> napi::Result<()> {
+ fn await_promise(env: Env, result: JsUnknown, tx: Sender>, parse: Cb) -> napi::Result<()>
+ where
+ T: 'static,
+ Cb: 'static + Fn(JsUnknown) -> Result,
+ {
// If the result is a promise, wait for it to resolve, and send the result to the channel.
// Otherwise, send the result immediately.
if result.is_promise()? {
@@ -249,9 +250,8 @@ mod bundle {
let then: JsFunction = get_named_property(&result, "then")?;
let tx2 = tx.clone();
let cb = env.create_function_from_closure("callback", move |ctx| {
- let res = ctx.get::(0)?.into_utf8()?;
- let s = res.into_owned()?;
- tx.send(Ok(s)).unwrap();
+ let res = parse(ctx.get::(0)?)?;
+ tx.send(Ok(res)).unwrap();
ctx.env.get_undefined()
})?;
let eb = env.create_function_from_closure("error_callback", move |ctx| {
@@ -261,10 +261,8 @@ mod bundle {
})?;
then.call(Some(&result), &[cb, eb])?;
} else {
- let result: JsString = result.try_into()?;
- let utf8 = result.into_utf8()?;
- let s = utf8.into_owned()?;
- tx.send(Ok(s)).unwrap();
+ let result = parse(result)?;
+ tx.send(Ok(result)).unwrap();
}
Ok(())
@@ -274,10 +272,12 @@ mod bundle {
let specifier = ctx.env.create_string(&ctx.value.specifier)?;
let originating_file = ctx.env.create_string(&ctx.value.originating_file)?;
let result = ctx.callback.unwrap().call(None, &[specifier, originating_file])?;
- await_promise(ctx.env, result, ctx.value.tx)
+ await_promise(ctx.env, result, ctx.value.tx, move |unknown| {
+ ctx.env.from_js_value(unknown)
+ })
}
- fn handle_error(tx: Sender>, res: napi::Result<()>) -> napi::Result<()> {
+ fn handle_error(tx: Sender>, res: napi::Result<()>) -> napi::Result<()> {
match res {
Ok(_) => Ok(()),
Err(e) => {
@@ -295,7 +295,9 @@ mod bundle {
fn read_on_js_thread(ctx: ThreadSafeCallContext) -> napi::Result<()> {
let file = ctx.env.create_string(&ctx.value.file)?;
let result = ctx.callback.unwrap().call(None, &[file])?;
- await_promise(ctx.env, result, ctx.value.tx)
+ await_promise(ctx.env, result, ctx.value.tx, |unknown| {
+ JsString::try_from(unknown)?.into_utf8()?.into_owned()
+ })
}
fn read_on_js_thread_wrapper(ctx: ThreadSafeCallContext) -> napi::Result<()> {
@@ -421,10 +423,10 @@ mod bundle {
#[cfg(target_arch = "wasm32")]
mod bundle {
use super::*;
+ use lightningcss::bundler::ResolveResult;
use napi::{Env, JsFunction, JsString, NapiRaw, NapiValue, Ref};
use std::cell::UnsafeCell;
- use std::path::{Path, PathBuf};
- use std::str::FromStr;
+ use std::path::Path;
pub fn bundle(ctx: CallContext) -> napi::Result {
let opts = ctx.get::(0)?;
@@ -497,7 +499,7 @@ mod bundle {
);
}
- fn get_result(env: Env, mut value: JsUnknown) -> napi::Result {
+ fn get_result(env: Env, mut value: JsUnknown) -> napi::Result {
if value.is_promise()? {
let mut result = std::ptr::null_mut();
let mut error = std::ptr::null_mut();
@@ -513,7 +515,7 @@ mod bundle {
value = unsafe { JsUnknown::from_raw(env.raw(), result)? };
}
- value.try_into()
+ Ok(value)
}
impl SourceProvider for JsSourceProvider {
@@ -523,7 +525,9 @@ mod bundle {
let read: JsFunction = self.env.get_reference_value_unchecked(&self.read)?;
let file = self.env.create_string(file.to_str().unwrap())?;
let source: JsUnknown = read.call(None, &[file])?;
- let source = get_result(self.env, source)?.into_utf8()?.into_owned()?;
+ let source = get_result(self.env, source)?;
+ let source: JsString = source.try_into()?;
+ let source = source.into_utf8()?.into_owned()?;
// cache the result
let ptr = Box::into_raw(Box::new(source));
@@ -535,16 +539,17 @@ mod bundle {
Ok(unsafe { &*ptr })
}
- fn resolve(&self, specifier: &str, originating_file: &Path) -> Result {
+ fn resolve(&self, specifier: &str, originating_file: &Path) -> Result {
if let Some(resolve) = &self.resolve {
let resolve: JsFunction = self.env.get_reference_value_unchecked(resolve)?;
let specifier = self.env.create_string(specifier)?;
let originating_file = self.env.create_string(originating_file.to_str().unwrap())?;
let result: JsUnknown = resolve.call(None, &[specifier, originating_file])?;
- let result = get_result(self.env, result)?.into_utf8()?;
- Ok(PathBuf::from_str(result.as_str()?).unwrap())
+ let result = get_result(self.env, result)?;
+ let result = self.env.from_js_value(result)?;
+ Ok(result)
} else {
- Ok(originating_file.with_file_name(specifier))
+ Ok(ResolveResult::File(originating_file.with_file_name(specifier)))
}
}
}
@@ -606,8 +611,10 @@ struct CssModulesConfig {
pattern: Option,
dashed_idents: Option,
animation: Option,
+ container: Option,
grid: Option,
custom_idents: Option,
+ pure: Option,
}
#[cfg(feature = "bundler")]
@@ -717,8 +724,10 @@ fn compile<'i>(
},
dashed_idents: c.dashed_idents.unwrap_or_default(),
animation: c.animation.unwrap_or(true),
+ container: c.container.unwrap_or(true),
grid: c.grid.unwrap_or(true),
custom_idents: c.custom_idents.unwrap_or(true),
+ pure: c.pure.unwrap_or_default(),
}),
}
} else {
@@ -847,8 +856,10 @@ fn compile_bundle<
},
dashed_idents: c.dashed_idents.unwrap_or_default(),
animation: c.animation.unwrap_or(true),
+ container: c.container.unwrap_or(true),
grid: c.grid.unwrap_or(true),
custom_idents: c.custom_idents.unwrap_or(true),
+ pure: c.pure.unwrap_or_default(),
}),
}
} else {
diff --git a/napi/src/transformer.rs b/napi/src/transformer.rs
index ad20c611d..bab931f2d 100644
--- a/napi/src/transformer.rs
+++ b/napi/src/transformer.rs
@@ -298,6 +298,7 @@ impl<'i> Visitor<'i, AtRule<'i>> for JsVisitor {
CssRule::Keyframes(..) => "keyframes",
CssRule::FontFace(..) => "font-face",
CssRule::FontPaletteValues(..) => "font-palette-values",
+ CssRule::FontFeatureValues(..) => "font-feature-values",
CssRule::Page(..) => "page",
CssRule::Supports(..) => "supports",
CssRule::CounterStyle(..) => "counter-style",
@@ -310,8 +311,10 @@ impl<'i> Visitor<'i, AtRule<'i>> for JsVisitor {
CssRule::Scope(..) => "scope",
CssRule::MozDocument(..) => "moz-document",
CssRule::Nesting(..) => "nesting",
+ CssRule::NestedDeclarations(..) => "nested-declarations",
CssRule::Viewport(..) => "viewport",
CssRule::StartingStyle(..) => "starting-style",
+ CssRule::ViewTransition(..) => "view-transition",
CssRule::Unknown(v) => {
let name = v.name.as_ref();
if let Some(visit) = rule_map.custom(stage, "unknown", name) {
@@ -752,9 +755,8 @@ impl<'de, V: serde::Deserialize<'de>, const IS_VEC: bool> serde::Deserialize<'de
D: serde::Deserializer<'de>,
{
use serde::Deserializer;
- let content = serde::__private::de::Content::deserialize(deserializer)?;
- let de: serde::__private::de::ContentRefDeserializer =
- serde::__private::de::ContentRefDeserializer::new(&content);
+ let content = serde_content::Value::deserialize(deserializer)?;
+ let de = serde_content::Deserializer::new(content.clone()).coerce_numbers();
// Try to deserialize as a sequence first.
let mut was_seq = false;
@@ -766,13 +768,15 @@ impl<'de, V: serde::Deserialize<'de>, const IS_VEC: bool> serde::Deserialize<'de
if was_seq {
// Allow fallback if we know the value is also a list (e.g. selector).
if res.is_ok() || !IS_VEC {
- return res.map(ValueOrVec::Vec);
+ return res.map_err(|e| serde::de::Error::custom(e.to_string())).map(ValueOrVec::Vec);
}
}
// If it wasn't a sequence, try a value.
- let de = serde::__private::de::ContentRefDeserializer::new(&content);
- return V::deserialize(de).map(ValueOrVec::Value);
+ let de = serde_content::Deserializer::new(content).coerce_numbers();
+ return V::deserialize(de)
+ .map_err(|e| serde::de::Error::custom(e.to_string()))
+ .map(ValueOrVec::Value);
struct SeqVisitor<'a, V> {
was_seq: &'a mut bool,
@@ -808,16 +812,14 @@ impl<'i, 'de: 'i> serde::Deserialize<'de> for TokensOrRaw<'i> {
where
D: serde::Deserializer<'de>,
{
- use serde::__private::de::ContentRefDeserializer;
-
#[derive(serde::Deserialize)]
struct Raw<'i> {
#[serde(borrow)]
raw: CowArcStr<'i>,
}
- let content = serde::__private::de::Content::deserialize(deserializer)?;
- let de: ContentRefDeserializer = ContentRefDeserializer::new(&content);
+ let content = serde_content::Value::deserialize(deserializer)?;
+ let de = serde_content::Deserializer::new(content.clone()).coerce_numbers();
if let Ok(res) = Raw::deserialize(de) {
let res = TokenList::parse_string_with_options(res.raw.as_ref(), ParserOptions::default())
@@ -825,8 +827,10 @@ impl<'i, 'de: 'i> serde::Deserialize<'de> for TokensOrRaw<'i> {
return Ok(TokensOrRaw(ValueOrVec::Vec(res.into_owned().0)));
}
- let de = ContentRefDeserializer::new(&content);
- Ok(TokensOrRaw(ValueOrVec::deserialize(de)?))
+ let de = serde_content::Deserializer::new(content).coerce_numbers();
+ Ok(TokensOrRaw(
+ ValueOrVec::deserialize(de).map_err(|e| serde::de::Error::custom(e.to_string()))?,
+ ))
}
}
diff --git a/node/Cargo.toml b/node/Cargo.toml
index 6cf2aa214..b5c7505c3 100644
--- a/node/Cargo.toml
+++ b/node/Cargo.toml
@@ -9,8 +9,13 @@ publish = false
crate-type = ["cdylib"]
[dependencies]
-lightningcss-napi = { version = "0.2.1", path = "../napi", features = ["bundler", "visitor"] }
-napi = {version = "2.15.4", default-features = false, features = ["compat-mode"]}
+lightningcss-napi = { version = "0.4.7", path = "../napi", features = [
+ "bundler",
+ "visitor",
+] }
+napi = { version = "2.15.4", default-features = false, features = [
+ "compat-mode",
+] }
napi-derive = "2"
[target.'cfg(target_os = "macos")'.dependencies]
diff --git a/node/ast.d.ts b/node/ast.d.ts
index 19ab2c1ff..28e9d0930 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,
@@ -33,6 +33,10 @@ export type Rule = | {
type: "font-palette-values";
value: FontPaletteValuesRule;
}
+| {
+ type: "font-feature-values";
+ value: FontFeatureValuesRule;
+ }
| {
type: "page";
value: PageRule;
@@ -57,6 +61,10 @@ export type Rule = | {
type: "nesting";
value: NestingRule;
}
+| {
+ type: "nested-declarations";
+ value: NestedDeclarationsRule;
+ }
| {
type: "viewport";
value: ViewportRule;
@@ -89,6 +97,10 @@ export type Rule = | {
type: "starting-style";
value: StartingStyleRule;
}
+| {
+ type: "view-transition";
+ value: ViewTransitionRule;
+ }
| {
type: "ignored";
}
@@ -122,6 +134,10 @@ export type MediaCondition =
*/
operator: Operator;
type: "operation";
+ }
+ | {
+ type: "unknown";
+ value: TokenOrValue[];
};
/**
* A generic media feature or container feature.
@@ -2342,9 +2358,19 @@ export type PropertyId =
| {
property: "view-transition-name";
}
+ | {
+ property: "view-transition-class";
+ }
+ | {
+ property: "view-transition-group";
+ }
| {
property: "color-scheme";
}
+ | {
+ property: "print-color-adjust";
+ vendorPrefix: VendorPrefix;
+ }
| {
property: "all";
}
@@ -3819,12 +3845,25 @@ export type Declaration =
}
| {
property: "view-transition-name";
- value: String;
+ value: ViewTransitionName;
+ }
+ | {
+ property: "view-transition-class";
+ value: NoneOrCustomIdentList;
+ }
+ | {
+ property: "view-transition-group";
+ value: ViewTransitionGroup;
}
| {
property: "color-scheme";
value: ColorScheme;
}
+ | {
+ property: "print-color-adjust";
+ value: PrintColorAdjust;
+ vendorPrefix: VendorPrefix;
+ }
| {
property: "all";
value: CSSWideKeyword;
@@ -5192,7 +5231,7 @@ export type RepeatCount =
};
export type AutoFlowDirection = "row" | "column";
/**
- * A value for the [grid-template-areas](https://drafts.csswg.org/css-grid-2/#grid-template-areas-property) property.
+ * A value for the [grid-template-areas](https://drafts.csswg.org/css-grid-2/#grid-template-areas-property) property. none | +
*/
export type GridTemplateAreas =
| {
@@ -6394,7 +6433,7 @@ export type ZIndex =
/**
* A value for the [container-type](https://drafts.csswg.org/css-contain-3/#container-type) property. Establishes the element as a query container for the purpose of container queries.
*/
-export type ContainerType = "normal" | "inline-size" | "size";
+export type ContainerType = "normal" | "inline-size" | "size" | "scroll-state";
/**
* A value for the [container-name](https://drafts.csswg.org/css-contain-3/#container-name) property.
*/
@@ -6406,6 +6445,25 @@ export type ContainerNameList =
type: "names";
value: String[];
};
+/**
+ * A value for the [view-transition-name](https://drafts.csswg.org/css-view-transitions-1/#view-transition-name-prop) property.
+ */
+export type ViewTransitionName =
+ "none" | "auto" | String;
+/**
+ * The `none` keyword, or a space-separated list of custom idents.
+ */
+export type NoneOrCustomIdentList =
+ "none" | String[];
+/**
+ * A value for the [view-transition-group](https://drafts.csswg.org/css-view-transitions-2/#view-transition-group-prop) property.
+ */
+export type ViewTransitionGroup =
+ "normal" | "contain" | "nearest" | String;
+/**
+ * A value for the [print-color-adjust](https://drafts.csswg.org/css-color-adjust/#propdef-print-color-adjust) property.
+ */
+export type PrintColorAdjust = "economy" | "exact";
/**
* A [CSS-wide keyword](https://drafts.csswg.org/css-cascade-5/#defaulting-keywords).
*/
@@ -6733,6 +6791,23 @@ export type PseudoClass =
kind: "autofill";
vendorPrefix: VendorPrefix;
}
+ | {
+ kind: "active-view-transition";
+ }
+ | {
+ kind: "active-view-transition-type";
+ /**
+ * A view transition type.
+ */
+ type: String[];
+ }
+ | {
+ kind: "state";
+ /**
+ * The custom state identifier.
+ */
+ state: String;
+ }
| {
kind: "local";
/**
@@ -6813,6 +6888,12 @@ export type PseudoElement =
| {
kind: "first-letter";
}
+ | {
+ kind: "details-content";
+ }
+ | {
+ kind: "target-text";
+ }
| {
kind: "selection";
vendorPrefix: VendorPrefix;
@@ -6864,28 +6945,47 @@ export type PseudoElement =
/**
* A part name selector.
*/
- partName: ViewTransitionPartName;
+ part: ViewTransitionPartSelector;
}
| {
kind: "view-transition-image-pair";
/**
* A part name selector.
*/
- partName: ViewTransitionPartName;
+ part: ViewTransitionPartSelector;
}
| {
kind: "view-transition-old";
/**
* A part name selector.
*/
- partName: ViewTransitionPartName;
+ part: ViewTransitionPartSelector;
}
| {
kind: "view-transition-new";
/**
* A part name selector.
*/
- partName: ViewTransitionPartName;
+ part: ViewTransitionPartSelector;
+ }
+ | {
+ /**
+ * A form control identifier.
+ */
+ identifier: String;
+ kind: "picker-function";
+ }
+ | {
+ kind: "picker-icon";
+ }
+ | {
+ kind: "checkmark";
+ }
+ | {
+ kind: "grammar-error";
+ }
+ | {
+ kind: "spelling-error";
}
| {
kind: "custom";
@@ -7116,6 +7216,17 @@ export type BasePalette =
type: "integer";
value: number;
};
+/**
+ * The name of the `@font-feature-values` sub-rule. font-feature-value-type = <@stylistic> | <@historical-forms> | <@styleset> | <@character-variant> | <@swash> | <@ornaments> | <@annotation>
+ */
+export type FontFeatureSubruleType =
+ | "stylistic"
+ | "historical-forms"
+ | "styleset"
+ | "character-variant"
+ | "swash"
+ | "ornaments"
+ | "annotation";
/**
* A [page margin box](https://www.w3.org/TR/css-page-3/#margin-boxes).
*/
@@ -7162,6 +7273,10 @@ export type ParsedComponent =
type: "length-percentage";
value: DimensionPercentageFor_LengthValue;
}
+ | {
+ type: "string";
+ value: String;
+ }
| {
type: "color";
value: CssColor;
@@ -7263,6 +7378,9 @@ export type SyntaxComponentKind =
| {
type: "length-percentage";
}
+ | {
+ type: "string";
+ }
| {
type: "color";
}
@@ -7322,6 +7440,14 @@ export type ContainerCondition = | {
| {
type: "style";
value: StyleQuery;
+ }
+| {
+ type: "scroll-state";
+ value: ScrollStateQuery;
+ }
+| {
+ type: "unknown";
+ value: TokenOrValue[];
};
/**
* A generic media feature or container feature.
@@ -7395,9 +7521,13 @@ export type ContainerSizeFeatureId = "width" | "height" | "inline-size" | "block
* Represents a style query within a container condition.
*/
export type StyleQuery = | {
- type: "feature";
+ type: "declaration";
value: D;
}
+| {
+ type: "property";
+ value: PropertyId;
+ }
| {
type: "not";
value: StyleQuery;
@@ -7413,6 +7543,119 @@ export type StyleQuery = | {
operator: Operator;
type: "operation";
};
+/**
+ * Represents a scroll state query within a container condition.
+ */
+export type ScrollStateQuery =
+ | {
+ type: "feature";
+ value: QueryFeatureFor_ScrollStateFeatureId;
+ }
+ | {
+ type: "not";
+ value: ScrollStateQuery;
+ }
+ | {
+ /**
+ * The conditions for the operator.
+ */
+ conditions: ScrollStateQuery[];
+ /**
+ * The operator for the conditions.
+ */
+ operator: Operator;
+ type: "operation";
+ };
+/**
+ * A generic media feature or container feature.
+ */
+export type QueryFeatureFor_ScrollStateFeatureId =
+ | {
+ /**
+ * The name of the feature.
+ */
+ name: MediaFeatureNameFor_ScrollStateFeatureId;
+ type: "plain";
+ /**
+ * The feature value.
+ */
+ value: MediaFeatureValue;
+ }
+ | {
+ /**
+ * The name of the feature.
+ */
+ name: MediaFeatureNameFor_ScrollStateFeatureId;
+ type: "boolean";
+ }
+ | {
+ /**
+ * The name of the feature.
+ */
+ name: MediaFeatureNameFor_ScrollStateFeatureId;
+ /**
+ * A comparator.
+ */
+ operator: MediaFeatureComparison;
+ type: "range";
+ /**
+ * The feature value.
+ */
+ value: MediaFeatureValue;
+ }
+ | {
+ /**
+ * The end value.
+ */
+ end: MediaFeatureValue;
+ /**
+ * A comparator for the end value.
+ */
+ endOperator: MediaFeatureComparison;
+ /**
+ * The name of the feature.
+ */
+ name: MediaFeatureNameFor_ScrollStateFeatureId;
+ /**
+ * A start value.
+ */
+ start: MediaFeatureValue;
+ /**
+ * A comparator for the start value.
+ */
+ startOperator: MediaFeatureComparison;
+ type: "interval";
+ };
+/**
+ * A media feature name.
+ */
+export type MediaFeatureNameFor_ScrollStateFeatureId = ScrollStateFeatureId | String | String;
+/**
+ * A container query scroll state feature identifier.
+ */
+export type ScrollStateFeatureId = "stuck" | "snapped" | "scrollable" | "scrolled";
+/**
+ * A property within a `@view-transition` rule.
+ *
+ * See [ViewTransitionRule](ViewTransitionRule).
+ */
+export type ViewTransitionProperty =
+ | {
+ property: "navigation";
+ value: Navigation;
+ }
+ | {
+ property: "types";
+ value: NoneOrCustomIdentList;
+ }
+ | {
+ property: "custom";
+ value: CustomProperty;
+ };
+/**
+ * A value for the [navigation](https://drafts.csswg.org/css-view-transitions-2/#view-transition-navigation-descriptor) property in a `@view-transition` rule.
+ */
+export type Navigation = "none" | "auto";
export type DefaultAtRule = null;
/**
@@ -8264,6 +8507,8 @@ export interface GridAutoFlow {
/**
* A value for the [grid-template](https://drafts.csswg.org/css-grid-2/#explicit-grid-shorthand) shorthand property.
*
+ * none | [ <'grid-template-rows'> / <'grid-template-columns'> ] | [ ? ? ? ]+ [ / ]?
+ *
* If `areas` is not `None`, then `rows` must also not be `None`.
*/
export interface GridTemplate {
@@ -8283,6 +8528,8 @@ export interface GridTemplate {
/**
* A value for the [grid](https://drafts.csswg.org/css-grid-2/#grid-shorthand) shorthand property.
*
+ * <'grid-template'> | <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? | [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
+ *
* Explicit and implicit values may not be combined.
*/
export interface Grid {
@@ -8312,7 +8559,7 @@ export interface Grid {
rows: TrackSizing;
}
/**
- * A value for the [grid-row](https://drafts.csswg.org/css-grid-2/#propdef-grid-row) shorthand property.
+ * A value for the [grid-row](https://drafts.csswg.org/css-grid-2/#propdef-grid-row) shorthand property. [ / ]?
*/
export interface GridRow {
/**
@@ -8325,7 +8572,7 @@ export interface GridRow {
start: GridLine;
}
/**
- * A value for the [grid-row](https://drafts.csswg.org/css-grid-2/#propdef-grid-column) shorthand property.
+ * A value for the [grid-column](https://drafts.csswg.org/css-grid-2/#propdef-grid-column) shorthand property. [ / ]?
*/
export interface GridColumn {
/**
@@ -8338,7 +8585,7 @@ export interface GridColumn {
start: GridLine;
}
/**
- * A value for the [grid-area](https://drafts.csswg.org/css-grid-2/#propdef-grid-area) shorthand property.
+ * A value for the [grid-area](https://drafts.csswg.org/css-grid-2/#propdef-grid-area) shorthand property. [ / ]{0,3}
*/
export interface GridArea {
/**
@@ -9126,6 +9373,19 @@ export interface AttrOperation {
operator: AttrSelectorOperator;
value: string;
}
+/**
+ * A [view transition part selector](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#typedef-pt-name-selector).
+ */
+export interface ViewTransitionPartSelector {
+ /**
+ * A list of view transition classes.
+ */
+ classes: String[];
+ /**
+ * The view transition part name.
+ */
+ name?: ViewTransitionPartName | null;
+}
/**
* A [@keyframes](https://drafts.csswg.org/css-animations/#keyframes) rule.
*/
@@ -9250,6 +9510,44 @@ export interface OverrideColors {
*/
index: number;
}
+/**
+ * A [@font-feature-values](https://drafts.csswg.org/css-fonts/#font-feature-values) rule.
+ */
+export interface FontFeatureValuesRule {
+ /**
+ * The location of the rule in the source file.
+ */
+ loc: Location2;
+ /**
+ * The name of the font feature values.
+ */
+ name: String[];
+ /**
+ * The rules within the `@font-feature-values` rule.
+ */
+ rules: {
+ [k: string]: FontFeatureSubrule;
+ };
+}
+/**
+ * A sub-rule of `@font-feature-values` https://drafts.csswg.org/css-fonts/#font-feature-values-syntax
+ */
+export interface FontFeatureSubrule {
+ /**
+ * The declarations within the `@font-feature-values` sub-rules.
+ */
+ declarations: {
+ [k: string]: number[];
+ };
+ /**
+ * The location of the rule in the source file.
+ */
+ loc: Location2;
+ /**
+ * The name of the `@font-feature-values` sub-rule.
+ */
+ name: FontFeatureSubruleType;
+}
/**
* A [@page](https://www.w3.org/TR/css-page-3/#at-page-rule) rule.
*/
@@ -9382,6 +9680,19 @@ export interface NestingRule {
*/
style: StyleRule;
}
+/**
+ * A [nested declarations](https://drafts.csswg.org/css-nesting/#nested-declarations-rule) rule.
+ */
+export interface NestedDeclarationsRule {
+ /**
+ * The style rule that defines the selector and declarations for the `@nest` rule.
+ */
+ declarations: DeclarationBlock;
+ /**
+ * The location of the rule in the source file.
+ */
+ loc: Location2;
+}
/**
* A [@viewport](https://drafts.csswg.org/css-device-adapt/#atviewport-rule) rule.
*/
@@ -9495,7 +9806,7 @@ export interface ContainerRule {
/**
* The container condition.
*/
- condition: ContainerCondition;
+ condition?: ContainerCondition | null;
/**
* The location of the rule in the source file.
*/
@@ -9545,6 +9856,19 @@ export interface StartingStyleRule {
*/
rules: Rule[];
}
+/**
+ * A [@view-transition](https://drafts.csswg.org/css-view-transitions-2/#view-transition-rule) rule.
+ */
+export interface ViewTransitionRule {
+ /**
+ * The location of the rule in the source file.
+ */
+ loc: Location2;
+ /**
+ * Declarations in the `@view-transition` rule.
+ */
+ properties: ViewTransitionProperty[];
+}
/**
* An unknown at-rule, stored as raw tokens.
*/
diff --git a/node/composeVisitors.js b/node/composeVisitors.js
index 850058d0b..f29934905 100644
--- a/node/composeVisitors.js
+++ b/node/composeVisitors.js
@@ -1,20 +1,30 @@
// @ts-check
/** @typedef {import('./index').Visitor} Visitor */
+/** @typedef {import('./index').VisitorFunction} VisitorFunction */
/**
* Composes multiple visitor objects into a single one.
- * @param {Visitor[]} visitors
- * @return {Visitor}
+ * @param {(Visitor | VisitorFunction)[]} visitors
+ * @return {Visitor | VisitorFunction}
*/
function composeVisitors(visitors) {
if (visitors.length === 1) {
return visitors[0];
}
+
+ if (visitors.some(v => typeof v === 'function')) {
+ return (opts) => {
+ let v = visitors.map(v => typeof v === 'function' ? v(opts) : v);
+ return composeVisitors(v);
+ };
+ }
/** @type Visitor */
let res = {};
- composeObjectVisitors(res, visitors, 'Rule', ruleVisitor, wrapUnknownAtRule);
- composeObjectVisitors(res, visitors, 'RuleExit', ruleVisitor, wrapUnknownAtRule);
+ composeSimpleVisitors(res, visitors, 'StyleSheet');
+ composeSimpleVisitors(res, visitors, 'StyleSheetExit');
+ composeObjectVisitors(res, visitors, 'Rule', ruleVisitor, wrapCustomAndUnknownAtRule);
+ composeObjectVisitors(res, visitors, 'RuleExit', ruleVisitor, wrapCustomAndUnknownAtRule);
composeObjectVisitors(res, visitors, 'Declaration', declarationVisitor, wrapCustomProperty);
composeObjectVisitors(res, visitors, 'DeclarationExit', declarationVisitor, wrapCustomProperty);
composeSimpleVisitors(res, visitors, 'Url');
@@ -45,8 +55,14 @@ function composeVisitors(visitors) {
module.exports = composeVisitors;
-function wrapUnknownAtRule(k, f) {
- return k === 'unknown' ? (value => f({ type: 'unknown', value })) : f;
+function wrapCustomAndUnknownAtRule(k, f) {
+ if (k === 'unknown') {
+ return (value => f({ type: 'unknown', value }));
+ }
+ if (k === 'custom') {
+ return (value => f({ type: 'custom', value }));
+ }
+ return f;
}
function wrapCustomProperty(k, f) {
@@ -66,6 +82,13 @@ function ruleVisitor(f, item) {
}
return v?.(item.value);
}
+ if (item.type === 'custom') {
+ let v = f.custom;
+ if (typeof v === 'object') {
+ v = v[item.value.name];
+ }
+ return v?.(item.value);
+ }
return f[item.type]?.(item);
}
return f?.(item);
@@ -351,7 +374,7 @@ function createArrayVisitor(visitors, apply) {
// For each value, call all visitors. If a visitor returns a new value,
// we start over, but skip the visitor that generated the value or saw
// it before (to avoid cycles). This way, visitors can be composed in any order.
- for (let v = 0; v < visitors.length;) {
+ for (let v = 0; v < visitors.length && i < arr.length;) {
if (seen.get(v)) {
v++;
continue;
diff --git a/node/flags.js b/node/flags.js
index 1759b4d97..a636a2045 100644
--- a/node/flags.js
+++ b/node/flags.js
@@ -21,7 +21,8 @@ exports.Features = {
DoublePositionGradients: 131072,
VendorPrefixes: 262144,
LogicalProperties: 524288,
+ LightDark: 1048576,
Selectors: 31,
MediaQueries: 448,
- Colors: 64512,
+ Colors: 1113088,
};
diff --git a/node/index.d.ts b/node/index.d.ts
index d138359b6..6d727d75a 100644
--- a/node/index.d.ts
+++ b/node/index.d.ts
@@ -1,4 +1,4 @@
-import type { Angle, CssColor, Rule, CustomProperty, EnvironmentVariable, Function, Image, LengthValue, MediaQuery, Declaration, Ratio, Resolution, Selector, SupportsCondition, Time, Token, TokenOrValue, UnknownAtRule, Url, Variable, StyleRule, DeclarationBlock, ParsedComponent, Multiplier, StyleSheet } from './ast';
+import type { Angle, CssColor, Rule, CustomProperty, EnvironmentVariable, Function, Image, LengthValue, MediaQuery, Declaration, Ratio, Resolution, Selector, SupportsCondition, Time, Token, TokenOrValue, UnknownAtRule, Url, Variable, StyleRule, DeclarationBlock, ParsedComponent, Multiplier, StyleSheet, Location2 } from './ast';
import { Targets, Features } from './targets';
export * from './ast';
@@ -63,7 +63,7 @@ export interface TransformOptions {
* For optimal performance, visitors should be as specific as possible about what types of values
* they care about so that JavaScript has to be called as little as possible.
*/
- visitor?: Visitor,
+ visitor?: Visitor | VisitorFunction,
/**
* Defines how to parse custom CSS at-rules. Each at-rule can have a prelude, defined using a CSS
* [syntax string](https://drafts.css-houdini.org/css-properties-values-api/#syntax-strings), and
@@ -138,7 +138,7 @@ interface CustomAtRule {
name: N,
prelude: R['prelude'] extends keyof MappedPrelude ? MappedPrelude[R['prelude']] : ParsedComponent,
body: FindByType>,
- loc: Location
+ loc: Location2
}
type CustomAtRuleBody = {
@@ -213,6 +213,13 @@ export interface Visitor {
EnvironmentVariableExit?: EnvironmentVariableVisitor | EnvironmentVariableVisitors;
}
+export type VisitorDependency = FileDependency | GlobDependency;
+export interface VisitorOptions {
+ addDependency: (dep: VisitorDependency) => void
+}
+
+export type VisitorFunction = (options: VisitorOptions) => Visitor;
+
export interface CustomAtRules {
[name: string]: CustomAtRuleDefinition
}
@@ -304,7 +311,17 @@ export interface CSSModulesConfig {
/** The pattern to use when renaming class names and other identifiers. Default is `[hash]_[local]`. */
pattern?: string,
/** Whether to rename dashed identifiers, e.g. custom properties. */
- dashedIdents?: boolean
+ dashedIdents?: boolean,
+ /** Whether to enable hashing for `@keyframes`. */
+ animation?: boolean,
+ /** Whether to enable hashing for CSS grid identifiers. */
+ grid?: boolean,
+ /** Whether to enable hashing for `@container` names. */
+ container?: boolean,
+ /** Whether to enable hashing for custom identifiers. */
+ customIdents?: boolean,
+ /** Whether to require at least one class or id selector in each rule. */
+ pure?: boolean
}
export type CSSModuleExports = {
@@ -348,7 +365,7 @@ export interface DependencyCSSModuleReference {
specifier: string
}
-export type Dependency = ImportDependency | UrlDependency;
+export type Dependency = ImportDependency | UrlDependency | FileDependency | GlobDependency;
export interface ImportDependency {
type: 'import',
@@ -374,6 +391,16 @@ export interface UrlDependency {
placeholder: string
}
+export interface FileDependency {
+ type: 'file',
+ filePath: string
+}
+
+export interface GlobDependency {
+ type: 'glob',
+ glob: string
+}
+
export interface SourceLocation {
/** The file path in which the dependency exists. */
filePath: string,
@@ -428,7 +455,7 @@ export interface TransformAttributeOptions {
* For optimal performance, visitors should be as specific as possible about what types of values
* they care about so that JavaScript has to be called as little as possible.
*/
- visitor?: Visitor
+ visitor?: Visitor | VisitorFunction
}
export interface TransformAttributeResult {
@@ -464,4 +491,4 @@ export declare function bundleAsync(options: BundleAsyn
/**
* Composes multiple visitor objects into a single one.
*/
-export declare function composeVisitors(visitors: Visitor[]): Visitor;
+export declare function composeVisitors(visitors: (Visitor | VisitorFunction)[]): Visitor | VisitorFunction;
diff --git a/node/index.js b/node/index.js
index a9f2f6d5f..6fe25aef4 100644
--- a/node/index.js
+++ b/node/index.js
@@ -1,6 +1,7 @@
let parts = [process.platform, process.arch];
if (process.platform === 'linux') {
- const { MUSL, family } = require('detect-libc');
+ const { MUSL, familySync } = require('detect-libc');
+ const family = familySync();
if (family === MUSL) {
parts.push('musl');
} else if (process.arch === 'arm') {
@@ -12,16 +13,47 @@ if (process.platform === 'linux') {
parts.push('msvc');
}
-if (process.env.CSS_TRANSFORMER_WASM) {
- module.exports = require(`../pkg`);
-} else {
- try {
- module.exports = require(`lightningcss-${parts.join('-')}`);
- } catch (err) {
- module.exports = require(`../lightningcss.${parts.join('-')}.node`);
- }
+let native;
+try {
+ native = require(`lightningcss-${parts.join('-')}`);
+} catch (err) {
+ native = require(`../lightningcss.${parts.join('-')}.node`);
}
+module.exports.transform = wrap(native.transform);
+module.exports.transformStyleAttribute = wrap(native.transformStyleAttribute);
+module.exports.bundle = wrap(native.bundle);
+module.exports.bundleAsync = wrap(native.bundleAsync);
module.exports.browserslistToTargets = require('./browserslistToTargets');
module.exports.composeVisitors = require('./composeVisitors');
module.exports.Features = require('./flags').Features;
+
+function wrap(call) {
+ return (options) => {
+ if (typeof options.visitor === 'function') {
+ let deps = [];
+ options.visitor = options.visitor({
+ addDependency(dep) {
+ deps.push(dep);
+ }
+ });
+
+ let result = call(options);
+ if (result instanceof Promise) {
+ result = result.then(res => {
+ if (deps.length) {
+ res.dependencies ??= [];
+ res.dependencies.push(...deps);
+ }
+ return res;
+ });
+ } else if (deps.length) {
+ result.dependencies ??= [];
+ result.dependencies.push(...deps);
+ }
+ return result;
+ } else {
+ return call(options);
+ }
+ };
+}
diff --git a/node/src/lib.rs b/node/src/lib.rs
index e429b0f2e..822944124 100644
--- a/node/src/lib.rs
+++ b/node/src/lib.rs
@@ -3,7 +3,7 @@
static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;
use napi::{CallContext, JsObject, JsUnknown};
-use napi_derive::{js_function, module_exports};
+use napi_derive::js_function;
#[js_function(1)]
fn transform(ctx: CallContext) -> napi::Result {
@@ -26,7 +26,7 @@ pub fn bundle_async(ctx: CallContext) -> napi::Result {
lightningcss_napi::bundle_async(ctx)
}
-#[cfg_attr(not(target_arch = "wasm32"), module_exports)]
+#[cfg_attr(not(target_arch = "wasm32"), napi_derive::module_exports)]
fn init(mut exports: JsObject) -> napi::Result<()> {
exports.create_named_method("transform", transform)?;
exports.create_named_method("transformStyleAttribute", transform_style_attribute)?;
@@ -45,7 +45,6 @@ pub fn register_module() {
unsafe fn register(raw_env: napi::sys::napi_env, raw_exports: napi::sys::napi_value) -> napi::Result<()> {
use napi::{Env, JsObject, NapiValue};
- let env = Env::from_raw(raw_env);
let exports = JsObject::from_raw_unchecked(raw_env, raw_exports);
init(exports)
}
diff --git a/node/targets.d.ts b/node/targets.d.ts
index c962f229f..ccc7c95f0 100644
--- a/node/targets.d.ts
+++ b/node/targets.d.ts
@@ -33,7 +33,8 @@ export const Features: {
DoublePositionGradients: 131072,
VendorPrefixes: 262144,
LogicalProperties: 524288,
+ LightDark: 1048576,
Selectors: 31,
MediaQueries: 448,
- Colors: 64512,
+ Colors: 1113088,
};
diff --git a/node/test/bundle.test.mjs b/node/test/bundle.test.mjs
index 50d113b57..4279e51c8 100644
--- a/node/test/bundle.test.mjs
+++ b/node/test/bundle.test.mjs
@@ -365,7 +365,7 @@ test('resolve return non-string', async () => {
}
if (!error) throw new Error(`\`testResolveReturnNonString()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`);
- assert.equal(error.message, 'expect String, got: Number');
+ assert.equal(error.message, 'data did not match any variant of untagged enum ResolveResult');
assert.equal(error.fileName, 'tests/testdata/foo.css');
assert.equal(error.loc, {
line: 1,
@@ -414,4 +414,29 @@ test('should support throwing in visitors', async () => {
assert.equal(error.message, 'Some error');
});
+test('external import', async () => {
+ const { code: buffer } = await bundleAsync(/** @type {import('../index').BundleAsyncOptions} */ ({
+ filename: 'tests/testdata/has_external.css',
+ resolver: {
+ resolve(specifier, originatingFile) {
+ if (specifier === './does_not_exist.css' || specifier.startsWith('https:')) {
+ return {external: specifier};
+ }
+ return path.resolve(path.dirname(originatingFile), specifier);
+ }
+ }
+ }));
+ const code = buffer.toString('utf-8').trim();
+
+ const expected = `
+@import "https://fonts.googleapis.com/css2?family=Roboto&display=swap";
+@import "./does_not_exist.css";
+
+.b {
+ height: calc(100vh - 64px);
+}
+ `.trim();
+ if (code !== expected) throw new Error(`\`testResolver()\` failed. Expected:\n${expected}\n\nGot:\n${code}`);
+});
+
test.run();
diff --git a/node/test/composeVisitors.test.mjs b/node/test/composeVisitors.test.mjs
index bb95d9123..4379cf481 100644
--- a/node/test/composeVisitors.test.mjs
+++ b/node/test/composeVisitors.test.mjs
@@ -513,6 +513,87 @@ test('unknown rules', () => {
assert.equal(res.code.toString(), '.menu_link{background:#056ef0}');
});
+test('custom at rules', () => {
+ let res = transform({
+ filename: 'test.css',
+ minify: true,
+ code: Buffer.from(`
+ @testA;
+ @testB;
+ `),
+ customAtRules: {
+ testA: {},
+ testB: {}
+ },
+ visitor: composeVisitors([
+ {
+ Rule: {
+ custom: {
+ testA(rule) {
+ return {
+ type: 'style',
+ value: {
+ loc: rule.loc,
+ selectors: [
+ [{ type: 'class', name: 'testA' }]
+ ],
+ declarations: {
+ declarations: [
+ {
+ property: 'color',
+ value: {
+ type: 'rgb',
+ r: 0xff,
+ g: 0x00,
+ b: 0x00,
+ alpha: 1,
+ }
+ }
+ ]
+ }
+ }
+ };
+ }
+ }
+ }
+ },
+ {
+ Rule: {
+ custom: {
+ testB(rule) {
+ return {
+ type: 'style',
+ value: {
+ loc: rule.loc,
+ selectors: [
+ [{ type: 'class', name: 'testB' }]
+ ],
+ declarations: {
+ declarations: [
+ {
+ property: 'color',
+ value: {
+ type: 'rgb',
+ r: 0x00,
+ g: 0xff,
+ b: 0x00,
+ alpha: 1,
+ }
+ }
+ ]
+ }
+ }
+ };
+ }
+ }
+ }
+ }
+ ])
+ });
+
+ assert.equal(res.code.toString(), '.testA{color:red}.testB{color:#0f0}');
+});
+
test('known rules', () => {
let declared = new Map();
let res = transform({
@@ -686,4 +767,94 @@ test('variables', () => {
assert.equal(res.code.toString(), 'body{padding:20px;width:600px}');
});
+test('StyleSheet', () => {
+ let styleSheetCalledCount = 0;
+ let styleSheetExitCalledCount = 0;
+ transform({
+ filename: 'test.css',
+ code: Buffer.from(`
+ body {
+ color: blue;
+ }
+ `),
+ visitor: composeVisitors([
+ {
+ StyleSheet() {
+ styleSheetCalledCount++
+ },
+ StyleSheetExit() {
+ styleSheetExitCalledCount++
+ }
+ },
+ {
+ StyleSheet() {
+ styleSheetCalledCount++
+ },
+ StyleSheetExit() {
+ styleSheetExitCalledCount++
+ }
+ }
+ ])
+ });
+ assert.equal(styleSheetCalledCount, 2);
+ assert.equal(styleSheetExitCalledCount, 2);
+});
+
+test('visitor function', () => {
+ let res = transform({
+ filename: 'test.css',
+ minify: true,
+ code: Buffer.from(`
+ @dep "foo.js";
+ @dep2 "bar.js";
+
+ .foo {
+ width: 32px;
+ }
+ `),
+ visitor: composeVisitors([
+ ({addDependency}) => ({
+ Rule: {
+ unknown: {
+ dep(rule) {
+ let file = rule.prelude[0].value.value;
+ addDependency({
+ type: 'file',
+ filePath: file
+ });
+ return [];
+ }
+ }
+ }
+ }),
+ ({addDependency}) => ({
+ Rule: {
+ unknown: {
+ dep2(rule) {
+ let file = rule.prelude[0].value.value;
+ addDependency({
+ type: 'file',
+ filePath: file
+ });
+ return [];
+ }
+ }
+ }
+ })
+ ])
+ });
+
+ assert.equal(res.code.toString(), '.foo{width:32px}');
+ assert.equal(res.dependencies, [
+ {
+ type: 'file',
+ filePath: 'foo.js'
+ },
+ {
+ type: 'file',
+ filePath: 'bar.js'
+ }
+ ]);
+});
+
test.run();
diff --git a/node/test/visitor.test.mjs b/node/test/visitor.test.mjs
index 3a42a696b..149825b7d 100644
--- a/node/test/visitor.test.mjs
+++ b/node/test/visitor.test.mjs
@@ -249,6 +249,68 @@ test('specific environment variables', () => {
assert.equal(res.code.toString(), '@media (width<=600px){body{padding:20px}}');
});
+test('spacing with env substitution', () => {
+ // Test spacing for different cases when `env()` functions are replaced with actual values.
+ /** @type {Record} */
+ let tokens = {
+ '--var1': 'var(--foo)',
+ '--var2': 'var(--bar)',
+ '--function': 'scale(1.5)',
+ '--length1': '10px',
+ '--length2': '20px',
+ '--x': '4',
+ '--y': '12',
+ '--num1': '5',
+ '--num2': '10',
+ '--num3': '15',
+ '--counter': '2',
+ '--ident1': 'solid',
+ '--ident2': 'auto',
+ '--rotate': '45deg',
+ '--percentage1': '25%',
+ '--percentage2': '75%',
+ '--color': 'red',
+ '--color1': '#ff1234',
+ '--string1': '"hello"',
+ '--string2': '" world"'
+ };
+
+ let res = transform({
+ filename: 'test.css',
+ minify: true,
+ code: Buffer.from(`
+ .test {
+ /* Asymmetric spacing - no space after var(). */
+ background: env(--var1) env(--var2);
+ border: env(--var1)env(--ident1);
+ transform: env(--function) env(--function);
+ /* Normal spacing between values. */
+ padding: env(--length1) env(--length2);
+ margin: env(--length1) env(--ident2);
+ outline: env(--color) env(--ident1);
+ /* Raw numbers that need spacing. */
+ cursor: url(cursor.png) env(--x) env(--y), auto;
+ stroke-dasharray: env(--num1) env(--num2) env(--num3);
+ counter-increment: myCounter env(--counter);
+ /* Mixed token types. */
+ background: linear-gradient(red env(--percentage1), blue env(--percentage2));
+ content: env(--string1) env(--string2);
+ /* Inside calc expressions. */
+ width: calc(env(--length1) - env(--length2));
+ }
+ `),
+ visitor: {
+ EnvironmentVariable(env) {
+ if (env.name.type === 'custom' && tokens[env.name.ident]) {
+ return { raw: tokens[env.name.ident] };
+ }
+ }
+ }
+ });
+
+ assert.equal(res.code.toString(), '.test{background:var(--foo) var(--bar);border:var(--foo)solid;transform:scale(1.5) scale(1.5);padding:10px 20px;margin:10px auto;outline:red solid;cursor:url(cursor.png) 4 12, auto;stroke-dasharray:5 10 15;counter-increment:myCounter 2;background:linear-gradient(red 25%, blue 75%);content:"hello" " world";width:calc(10px - 20px)}');
+});
+
test('url', () => {
// https://www.npmjs.com/package/postcss-url
let res = transform({
@@ -1108,4 +1170,119 @@ test('visit stylesheet', () => {
assert.equal(res.code.toString(), '.bar{width:80px}.foo{width:32px}');
});
+test('visitor function', () => {
+ let res = transform({
+ filename: 'test.css',
+ minify: true,
+ code: Buffer.from(`
+ @dep "foo.js";
+
+ .foo {
+ width: 32px;
+ }
+ `),
+ visitor: ({addDependency}) => ({
+ Rule: {
+ unknown: {
+ dep(rule) {
+ let file = rule.prelude[0].value.value;
+ addDependency({
+ type: 'file',
+ filePath: file
+ });
+ return [];
+ }
+ }
+ }
+ })
+ });
+
+ assert.equal(res.code.toString(), '.foo{width:32px}');
+ assert.equal(res.dependencies, [{
+ type: 'file',
+ filePath: 'foo.js'
+ }]);
+});
+
+test('visitor function works with style attributes', () => {
+ let res = transformStyleAttribute({
+ filename: 'test.css',
+ minify: true,
+ code: Buffer.from('height: 12px'),
+ visitor: ({addDependency}) => ({
+ Length() {
+ addDependency({
+ type: 'file',
+ filePath: 'test.json'
+ });
+ }
+ })
+ });
+
+ assert.equal(res.dependencies, [{
+ type: 'file',
+ filePath: 'test.json'
+ }]);
+});
+
+test('visitor function works with bundler', () => {
+ let res = bundle({
+ filename: 'tests/testdata/a.css',
+ minify: true,
+ visitor: ({addDependency}) => ({
+ Length() {
+ addDependency({
+ type: 'file',
+ filePath: 'test.json'
+ });
+ }
+ })
+ });
+
+ assert.equal(res.dependencies, [
+ {
+ type: 'file',
+ filePath: 'test.json'
+ },
+ {
+ type: 'file',
+ filePath: 'test.json'
+ },
+ {
+ type: 'file',
+ filePath: 'test.json'
+ }
+ ]);
+});
+
+test('works with async bundler', async () => {
+ let res = await bundleAsync({
+ filename: 'tests/testdata/a.css',
+ minify: true,
+ visitor: ({addDependency}) => ({
+ Length() {
+ addDependency({
+ type: 'file',
+ filePath: 'test.json'
+ });
+ }
+ })
+ });
+
+ assert.equal(res.dependencies, [
+ {
+ type: 'file',
+ filePath: 'test.json'
+ },
+ {
+ type: 'file',
+ filePath: 'test.json'
+ },
+ {
+ type: 'file',
+ filePath: 'test.json'
+ }
+ ]);
+});
+
test.run();
diff --git a/package.json b/package.json
index 68e6701db..af17c0a58 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "lightningcss",
- "version": "1.26.0",
+ "version": "1.31.1",
"license": "MPL-2.0",
"description": "A CSS parser, transformer, and minifier written in Rust",
"main": "node/index.js",
@@ -39,21 +39,21 @@
"node/*.flow"
],
"dependencies": {
- "detect-libc": "^1.0.3"
+ "detect-libc": "^2.0.3"
},
"devDependencies": {
- "@babel/parser": "^7.21.4",
- "@babel/traverse": "^7.21.4",
+ "@babel/parser": "7.21.4",
+ "@babel/traverse": "7.21.4",
"@codemirror/lang-css": "^6.0.1",
"@codemirror/lang-javascript": "^6.1.2",
"@codemirror/lint": "^6.1.0",
"@codemirror/theme-one-dark": "^6.1.0",
- "@mdn/browser-compat-data": "~5.5.44",
+ "@mdn/browser-compat-data": "~7.2.4",
"@napi-rs/cli": "^2.14.0",
- "autoprefixer": "^10.4.20",
- "caniuse-lite": "^1.0.30001649",
+ "autoprefixer": "^10.4.23",
+ "caniuse-lite": "^1.0.30001765",
"codemirror": "^6.0.1",
- "cssnano": "^5.0.8",
+ "cssnano": "^7.0.6",
"esbuild": "^0.19.8",
"flowgen": "^1.21.0",
"jest-diff": "^27.4.2",
@@ -73,7 +73,8 @@
"process": "^0.11.10",
"puppeteer": "^12.0.1",
"recast": "^0.22.0",
- "sharp": "^0.31.1",
+ "sharp": "^0.33.5",
+ "typescript": "^5.7.2",
"util": "^0.12.4",
"uvu": "^0.5.6"
},
diff --git a/patches/@babel+types+7.21.4.patch b/patches/@babel+types+7.26.3.patch
similarity index 89%
rename from patches/@babel+types+7.21.4.patch
rename to patches/@babel+types+7.26.3.patch
index 45b21d586..e672fb01f 100644
--- a/patches/@babel+types+7.21.4.patch
+++ b/patches/@babel+types+7.26.3.patch
@@ -1,8 +1,8 @@
diff --git a/node_modules/@babel/types/lib/retrievers/getBindingIdentifiers.js b/node_modules/@babel/types/lib/retrievers/getBindingIdentifiers.js
-index 19903eb..6bc04a8 100644
+index 31feb1e..a64b83d 100644
--- a/node_modules/@babel/types/lib/retrievers/getBindingIdentifiers.js
+++ b/node_modules/@babel/types/lib/retrievers/getBindingIdentifiers.js
-@@ -59,6 +59,13 @@ getBindingIdentifiers.keys = {
+@@ -66,6 +66,13 @@ const keys = {
InterfaceDeclaration: ["id"],
TypeAlias: ["id"],
OpaqueType: ["id"],
diff --git a/patches/json-schema-to-typescript+11.0.2.patch b/patches/json-schema-to-typescript+11.0.5.patch
similarity index 99%
rename from patches/json-schema-to-typescript+11.0.2.patch
rename to patches/json-schema-to-typescript+11.0.5.patch
index 37b111733..b1d06ba68 100644
--- a/patches/json-schema-to-typescript+11.0.2.patch
+++ b/patches/json-schema-to-typescript+11.0.5.patch
@@ -1,5 +1,5 @@
diff --git a/node_modules/json-schema-to-typescript/dist/src/parser.js b/node_modules/json-schema-to-typescript/dist/src/parser.js
-index aec32ab..aafd1b5 100644
+index fa9d2e4..3f65449 100644
--- a/node_modules/json-schema-to-typescript/dist/src/parser.js
+++ b/node_modules/json-schema-to-typescript/dist/src/parser.js
@@ -1,6 +1,6 @@
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
index a436857e5..1a2165581 100644
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,3 +1,3 @@
[toolchain]
-channel = "1.76.0"
+channel = "1.92.0"
components = ["rustfmt", "clippy"]
diff --git a/scripts/build-ast.js b/scripts/build-ast.js
index de0e7f20a..883aa442d 100644
--- a/scripts/build-ast.js
+++ b/scripts/build-ast.js
@@ -75,6 +75,27 @@ compileFromFile('node/ast.json', {
if (types[2].type === 'TSTypeLiteral' && types[2].members[0].key.name === 'timelinerange') {
path.get('typeAnnotation.types.2').replaceWith(path.node.typeAnnotation.types[2].members[0].typeAnnotation.typeAnnotation);
}
+ } else if (
+ path.node.id.name === 'NoneOrCustomIdentList' &&
+ path.node.typeAnnotation.type === 'TSUnionType' &&
+ path.node.typeAnnotation.types[1].type === 'TSTypeLiteral' &&
+ path.node.typeAnnotation.types[1].members[0].key.name === 'idents'
+ ) {
+ path.get('typeAnnotation.types.1').replaceWith(path.node.typeAnnotation.types[1].members[0].typeAnnotation.typeAnnotation);
+ } else if (
+ path.node.id.name === 'ViewTransitionGroup' &&
+ path.node.typeAnnotation.type === 'TSUnionType' &&
+ path.node.typeAnnotation.types[3].type === 'TSTypeLiteral' &&
+ path.node.typeAnnotation.types[3].members[0].key.name === 'custom'
+ ) {
+ path.get('typeAnnotation.types.3').replaceWith(path.node.typeAnnotation.types[3].members[0].typeAnnotation.typeAnnotation);
+ } else if (
+ path.node.id.name === 'ViewTransitionName' &&
+ path.node.typeAnnotation.type === 'TSUnionType' &&
+ path.node.typeAnnotation.types[2].type === 'TSTypeLiteral' &&
+ path.node.typeAnnotation.types[2].members[0].key.name === 'custom'
+ ) {
+ path.get('typeAnnotation.types.2').replaceWith(path.node.typeAnnotation.types[2].members[0].typeAnnotation.typeAnnotation);
}
}
});
diff --git a/scripts/build-npm.js b/scripts/build-npm.js
index ca89d669e..ef447a7ea 100644
--- a/scripts/build-npm.js
+++ b/scripts/build-npm.js
@@ -38,6 +38,9 @@ const triples = [
},
{
name: 'x86_64-unknown-freebsd'
+ },
+ {
+ name: 'aarch64-linux-android'
}
];
const cpuToNodeArch = {
@@ -51,6 +54,7 @@ const sysToNodePlatform = {
freebsd: 'freebsd',
darwin: 'darwin',
windows: 'win32',
+ android: 'android'
};
let optionalDependencies = {};
diff --git a/scripts/build-prefixes.js b/scripts/build-prefixes.js
index 8fc83ca23..3a9f7945f 100644
--- a/scripts/build-prefixes.js
+++ b/scripts/build-prefixes.js
@@ -25,6 +25,7 @@ const MDN_BROWSER_MAPPING = {
firefox_android: 'firefox',
opera_android: 'opera',
safari_ios: 'ios_saf',
+ webview_ios: 'ios_saf',
samsunginternet_android: 'samsung',
webview_android: 'android',
oculus: null,
@@ -70,6 +71,10 @@ prefixes['any-pseudo'] = {
})
}
+// Safari 4-13 supports background-clip: text with a prefix.
+prefixes['background-clip'].browsers.push('safari 13');
+prefixes['background-clip'].browsers.push('ios_saf 4', 'ios_saf 13');
+
let flexSpec = {};
let oldGradient = {};
let p = new Map();
@@ -253,7 +258,7 @@ for (let feature of cssFeatures) {
addValue(compat, {}, 'custom-media-queries');
let mdnFeatures = {
- doublePositionGradients: mdn.css.types.image.gradient['radial-gradient'].doubleposition.__compat.support,
+ doublePositionGradients: mdn.css.types.gradient['radial-gradient'].doubleposition.__compat.support,
clampFunction: mdn.css.types.clamp.__compat.support,
placeSelf: mdn.css.properties['place-self'].__compat.support,
placeContent: mdn.css.properties['place-content'].__compat.support,
@@ -319,7 +324,7 @@ let mdnFeatures = {
absFunction: mdn.css.types.abs.__compat.support,
signFunction: mdn.css.types.sign.__compat.support,
hypotFunction: mdn.css.types.hypot.__compat.support,
- gradientInterpolationHints: mdn.css.types.image.gradient['linear-gradient'].interpolation_hints.__compat.support,
+ gradientInterpolationHints: mdn.css.types.gradient['linear-gradient'].interpolation_hints.__compat.support,
borderImageRepeatRound: mdn.css.properties['border-image-repeat'].round.__compat.support,
borderImageRepeatSpace: mdn.css.properties['border-image-repeat'].space.__compat.support,
fontSizeRem: mdn.css.properties['font-size'].rem_values.__compat.support,
@@ -330,6 +335,28 @@ let mdnFeatures = {
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,
+ viewTransition: mdn.css.selectors['view-transition'].__compat.support,
+ detailsContent: mdn.css.selectors['details-content'].__compat.support,
+ targetText: mdn.css.selectors['target-text'].__compat.support,
+ picker: mdn.css.selectors.picker.__compat.support,
+ pickerIcon: mdn.css.selectors['picker-icon'].__compat.support,
+ checkmark: mdn.css.selectors.checkmark.__compat.support,
+ grammarError: mdn.css.selectors['grammar-error'].__compat.support,
+ spellingError: mdn.css.selectors['spelling-error'].__compat.support,
+ statePseudoClass: Object.fromEntries(
+ Object.entries(mdn.css.selectors.state.__compat.support)
+ .map(([browser, value]) => {
+ // Chrome/Edge 90-124 supported old :--foo syntax which was removed.
+ // Only include full :state(foo) support from 125+.
+ if (Array.isArray(value)) {
+ value = value.filter(v => !v.partial_implementation)
+ } else if (value.partial_implementation) {
+ value = undefined;
+ }
+
+ return [browser, value];
+ })
+ ),
};
for (let key in mdn.css.types.length) {
@@ -344,13 +371,13 @@ for (let key in mdn.css.types.length) {
mdnFeatures[feat] = mdn.css.types.length[key].__compat.support;
}
-for (let key in mdn.css.types.image.gradient) {
+for (let key in mdn.css.types.gradient) {
if (key === '__compat') {
continue;
}
let feat = key.replace(/-([a-z])/g, (_, l) => l.toUpperCase());
- mdnFeatures[feat] = mdn.css.types.image.gradient[key].__compat.support;
+ mdnFeatures[feat] = mdn.css.types.gradient[key].__compat.support;
}
const nonStandardListStyleType = new Set([
@@ -377,7 +404,7 @@ for (let key in mdn.css.properties['list-style-type']) {
}
for (let key in mdn.css.properties['width']) {
- if (key === '__compat' || key === 'animatable') {
+ if (key === '__compat' || key === 'is_animatable') {
continue;
}
@@ -464,9 +491,10 @@ let flags = [
'DoublePositionGradients',
'VendorPrefixes',
'LogicalProperties',
+ 'LightDark',
['Selectors', ['Nesting', 'NotSelectorList', 'DirSelector', 'LangSelectorList', 'IsSelector']],
['MediaQueries', ['MediaIntervalSyntax', 'MediaRangeSyntax', 'CustomMediaQueries']],
- ['Colors', ['ColorFunction', 'OklabColors', 'LabColors', 'P3Colors', 'HexAlphaColors', 'SpaceSeparatedColorNotation']],
+ ['Colors', ['ColorFunction', 'OklabColors', 'LabColors', 'P3Colors', 'HexAlphaColors', 'SpaceSeparatedColorNotation', 'LightDark']],
];
let enumify = (f) => f.replace(/^@([a-z])/, (_, x) => 'At' + x.toUpperCase()).replace(/^::([a-z])/, (_, x) => 'PseudoElement' + x.toUpperCase()).replace(/^:([a-z])/, (_, x) => 'PseudoClass' + x.toUpperCase()).replace(/(^|-)([a-z])/g, (_, a, x) => x.toUpperCase())
@@ -637,7 +665,10 @@ impl Feature {
if self.is_compatible(browsers) {
return true
}
- browsers.${browser} = None;
+ #[allow(unused_assignments)]
+ {
+ browsers.${browser} = None;
+ }
}\n`).join(' ')}
false
}
diff --git a/selectors/Cargo.toml b/selectors/Cargo.toml
index 4c2129705..824b2f2bd 100644
--- a/selectors/Cargo.toml
+++ b/selectors/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "parcel_selectors"
-version = "0.26.6"
+version = "0.28.2"
authors = ["The Servo Project Developers"]
documentation = "https://docs.rs/parcel_selectors/"
description = "CSS Selectors matching for Rust - forked for lightningcss"
@@ -9,6 +9,7 @@ readme = "README.md"
keywords = ["css", "selectors"]
license = "MPL-2.0"
build = "build.rs"
+edition = "2021"
[lib]
name = "parcel_selectors"
@@ -24,14 +25,14 @@ serde = ["dep:serde", "smallvec/serde"]
[dependencies]
bitflags = "2.2.1"
cssparser = "0.33.0"
-fxhash = "0.2"
+rustc-hash = "2"
log = "0.4"
-phf = "0.10"
+phf = "0.11.2"
precomputed-hash = "0.1"
smallvec = "1.0"
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 }
+static-self = { version = "0.1.2", path = "../static-self", optional = true }
[build-dependencies]
-phf_codegen = "0.10"
+phf_codegen = "0.11"
diff --git a/selectors/bloom.rs b/selectors/bloom.rs
index bbfbee45b..e9d2f7307 100644
--- a/selectors/bloom.rs
+++ b/selectors/bloom.rs
@@ -283,7 +283,7 @@ fn hash2(hash: u32) -> u32 {
#[test]
fn create_and_insert_some_stuff() {
- use fxhash::FxHasher;
+ use rustc_hash::FxHasher;
use std::hash::{Hash, Hasher};
use std::mem::transmute;
diff --git a/selectors/build.rs b/selectors/build.rs
index 945bb9bb6..787e2d80d 100644
--- a/selectors/build.rs
+++ b/selectors/build.rs
@@ -2,8 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
-extern crate phf_codegen;
-
use std::env;
use std::fs::File;
use std::io::{BufWriter, Write};
diff --git a/selectors/lib.rs b/selectors/lib.rs
index 56217d284..2047b4e61 100644
--- a/selectors/lib.rs
+++ b/selectors/lib.rs
@@ -9,18 +9,8 @@
extern crate bitflags;
#[macro_use]
extern crate cssparser;
-extern crate fxhash;
#[macro_use]
extern crate log;
-extern crate phf;
-extern crate precomputed_hash;
-#[cfg(feature = "jsonschema")]
-extern crate schemars;
-#[cfg(feature = "serde")]
-extern crate serde;
-extern crate smallvec;
-#[cfg(feature = "into_owned")]
-extern crate static_self;
pub mod attr;
pub mod bloom;
diff --git a/selectors/matching.rs b/selectors/matching.rs
index ce3d7a59f..61f74a85c 100644
--- a/selectors/matching.rs
+++ b/selectors/matching.rs
@@ -60,7 +60,7 @@ impl ElementSelectorFlags {
}
/// Holds per-compound-selector data.
-struct LocalMatchingContext<'a, 'b: 'a, 'i, Impl: SelectorImpl<'i>> {
+struct LocalMatchingContext<'a, 'b, 'i, Impl: SelectorImpl<'i>> {
shared: &'a mut MatchingContext<'b, 'i, Impl>,
matches_hover_and_active_quirk: MatchesHoverAndActiveQuirk,
}
diff --git a/selectors/nth_index_cache.rs b/selectors/nth_index_cache.rs
index 2ca33e7bb..c5bb8db0a 100644
--- a/selectors/nth_index_cache.rs
+++ b/selectors/nth_index_cache.rs
@@ -3,7 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::tree::OpaqueElement;
-use fxhash::FxHashMap;
+use rustc_hash::FxHashMap;
/// A cache to speed up matching of nth-index-like selectors.
///
diff --git a/selectors/parser.rs b/selectors/parser.rs
index c0e27a9ab..19563d5f9 100644
--- a/selectors/parser.rs
+++ b/selectors/parser.rs
@@ -197,7 +197,8 @@ pub enum SelectorParseErrorKind<'i> {
MissingNestingPrefix,
UnexpectedTokenInAttributeSelector(Token<'i>),
PseudoElementExpectedIdent(Token<'i>),
- UnsupportedPseudoClassOrElement(CowRcStr<'i>),
+ UnsupportedPseudoElement(CowRcStr<'i>),
+ UnsupportedPseudoClass(CowRcStr<'i>),
AmbiguousCssModuleClass(CowRcStr<'i>),
UnexpectedIdent(CowRcStr<'i>),
ExpectedNamespace(CowRcStr<'i>),
@@ -206,6 +207,7 @@ pub enum SelectorParseErrorKind<'i> {
InvalidQualNameInAttr(Token<'i>),
ExplicitNamespaceUnexpectedToken(Token<'i>),
ClassNeedsIdent(Token<'i>),
+ UnexpectedSelectorAfterPseudoElement(Token<'i>),
}
macro_rules! with_all_bounds {
@@ -311,7 +313,7 @@ pub trait Parser<'i> {
location: SourceLocation,
name: CowRcStr<'i>,
) -> Result<<'i>>::NonTSPseudoClass, ParseError<'i, Self::Error>> {
- Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name)))
+ Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name)))
}
fn parse_non_ts_functional_pseudo_class<'t>(
@@ -319,7 +321,7 @@ pub trait Parser<'i> {
name: CowRcStr<'i>,
arguments: &mut CssParser<'i, 't>,
) -> Result<<'i>>::NonTSPseudoClass, ParseError<'i, Self::Error>> {
- Err(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name)))
+ Err(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name)))
}
fn parse_pseudo_element(
@@ -327,7 +329,7 @@ pub trait Parser<'i> {
location: SourceLocation,
name: CowRcStr<'i>,
) -> Result<<'i>>::PseudoElement, ParseError<'i, Self::Error>> {
- Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name)))
+ Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(name)))
}
fn parse_functional_pseudo_element<'t>(
@@ -335,7 +337,7 @@ pub trait Parser<'i> {
name: CowRcStr<'i>,
arguments: &mut CssParser<'i, 't>,
) -> Result<<'i>>::PseudoElement, ParseError<'i, Self::Error>> {
- Err(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name)))
+ Err(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(name)))
}
fn default_namespace(&self) -> Option<<'i>>::NamespaceUrl> {
@@ -878,12 +880,12 @@ impl<'i, Impl: SelectorImpl<'i>> Selector<'i, Impl> {
/// Returns an iterator over the entire sequence of simple selectors and
/// combinators, in matching order (from right to left).
#[inline]
- pub fn iter_raw_match_order(&self) -> slice::Iter<'i, Impl>> {
+ pub fn iter_raw_match_order(&self) -> slice::Iter<'_, Component<'i, Impl>> {
self.1.iter()
}
#[inline]
- pub fn iter_mut_raw_match_order(&mut self) -> slice::IterMut<'i, Impl>> {
+ pub fn iter_mut_raw_match_order(&mut self) -> slice::IterMut<'_, Component<'i, Impl>> {
self.1.iter_mut()
}
@@ -901,7 +903,7 @@ impl<'i, Impl: SelectorImpl<'i>> Selector<'i, Impl> {
/// combinators, in parse order (from left to right), starting from
/// `offset`.
#[inline]
- pub fn iter_raw_parse_order_from(&self, offset: usize) -> Rev<'i, Impl>>> {
+ pub fn iter_raw_parse_order_from(&self, offset: usize) -> Rev<'_, Component<'i, Impl>>> {
self.1[..self.len() - offset].iter().rev()
}
@@ -1007,7 +1009,7 @@ impl<'i, Impl: SelectorImpl<'i>> From<'i, Impl>>> for Selector<'i,
}
#[derive(Clone)]
-pub struct SelectorIter<'a, 'i, Impl: 'a + SelectorImpl<'i>> {
+pub struct SelectorIter<'a, 'i, Impl: SelectorImpl<'i>> {
iter: slice::Iter<'a, Component<'i, Impl>>,
next_combinator: Option,
}
@@ -1090,7 +1092,7 @@ impl<'a, 'i, Impl: SelectorImpl<'i>> fmt::Debug for SelectorIter<'a, 'i, Impl> {
}
/// An iterator over all simple selectors belonging to ancestors.
-struct AncestorIter<'a, 'i, Impl: 'a + SelectorImpl<'i>>(SelectorIter<'a, 'i, Impl>);
+struct AncestorIter<'a, 'i, Impl: SelectorImpl<'i>>(SelectorIter<'a, 'i, Impl>);
impl<'a, 'i, Impl: 'a + SelectorImpl<'i>> AncestorIter<'a, 'i, Impl> {
/// Creates an AncestorIter. The passed-in iterator is assumed to point to
/// the beginning of the child sequence, which will be skipped.
@@ -1300,7 +1302,7 @@ impl NthSelectorData {
/// Writes the beginning of the selector.
#[inline]
- fn write_start(&self, dest: &mut W, is_function: bool) -> fmt::Result {
+ pub fn write_start(&self, dest: &mut W, is_function: bool) -> fmt::Result {
dest.write_str(match self.ty {
NthType::Child if is_function => ":nth-child(",
NthType::Child => ":first-child",
@@ -1320,7 +1322,7 @@ impl NthSelectorData {
/// Serialize (part of the CSS Syntax spec, but currently only used here).
///
#[inline]
- fn write_affine(&self, dest: &mut W) -> fmt::Result {
+ pub fn write_affine(&self, dest: &mut W) -> fmt::Result {
match (self.a, self.b) {
(0, 0) => dest.write_char('0'),
@@ -2141,6 +2143,14 @@ where
}
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
+ // Input should be exhausted here.
+ let source_location = input.current_source_location();
+ if let Ok(next) = input.next() {
+ let next = next.clone();
+ return Err(
+ source_location.new_custom_error(SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(next)),
+ );
+ }
break;
}
@@ -2956,6 +2966,7 @@ where
Impl: SelectorImpl<'i>,
{
let start = input.state();
+ let token_location = input.current_source_location();
let token = match input.next_including_whitespace().map(|t| t.clone()) {
Ok(t) => t,
Err(..) => {
@@ -2967,14 +2978,18 @@ where
Ok(Some(match token {
Token::IDHash(id) => {
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
- return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
+ return Err(token_location.new_custom_error(
+ SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::IDHash(id)),
+ ));
}
let id = Component::ID(id.into());
SimpleSelectorParseResult::SimpleSelector(id)
}
Token::Delim('.') => {
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
- return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
+ return Err(token_location.new_custom_error(
+ SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::Delim('.')),
+ ));
}
let location = input.current_source_location();
let class = match *input.next_including_whitespace()? {
@@ -2989,7 +3004,9 @@ where
}
Token::SquareBracketBlock => {
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
- return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
+ return Err(token_location.new_custom_error(
+ SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::SquareBracketBlock),
+ ));
}
let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?;
SimpleSelectorParseResult::SimpleSelector(attr)
@@ -3337,7 +3354,7 @@ pub mod tests {
"active" => return Ok(PseudoClass::Active),
_ => {}
}
- Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name)))
+ Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name)))
}
fn parse_non_ts_functional_pseudo_class<'t>(
@@ -3352,7 +3369,7 @@ pub mod tests {
},
_ => {}
}
- Err(parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name)))
+ Err(parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClass(name)))
}
fn parse_pseudo_element(
@@ -3365,7 +3382,7 @@ pub mod tests {
"after" => return Ok(PseudoElement::After),
_ => {}
}
- Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name)))
+ Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoElement(name)))
}
fn default_namespace(&self) -> Option {
@@ -3377,7 +3394,7 @@ pub mod tests {
}
}
- fn parse<'i>(input: &'i str) -> Result, SelectorParseError<'i>> {
+ fn parse<'i>(input: &'i str) -> Result<'i, DummySelectorImpl>, SelectorParseError<'i>> {
parse_ns(input, &DummyParser::default())
}
@@ -3912,6 +3929,20 @@ pub mod tests {
assert!(parse("foo:where()").is_err());
assert!(parse("foo:where(div, foo, .bar baz)").is_ok());
assert!(parse("foo:where(::before)").is_err());
+
+ assert!(parse("foo::details-content").is_ok());
+ assert!(parse("foo::target-text").is_ok());
+
+ assert!(parse("select::picker").is_err());
+ assert!(parse("::picker()").is_err());
+ assert!(parse("::picker(select)").is_ok());
+ assert!(parse("select::picker-icon").is_ok());
+ assert!(parse("option::checkmark").is_ok());
+
+ assert!(parse("::grammar-error").is_ok());
+ assert!(parse("::spelling-error").is_ok());
+ assert!(parse("::part(mypart)::grammar-error").is_ok());
+ assert!(parse("::part(mypart)::spelling-error").is_ok());
}
#[test]
diff --git a/src/bundler.rs b/src/bundler.rs
index d73545b8d..e5009a863 100644
--- a/src/bundler.rs
+++ b/src/bundler.rs
@@ -79,7 +79,7 @@ enum AtRuleParserValue<'a, T> {
struct BundleStyleSheet<'i, 'o, T> {
stylesheet: Option<'i, 'o, T>>,
- dependencies: Vec,
+ dependencies: Vec,
css_modules_deps: Vec,
parent_source_index: u32,
parent_dep_index: u32,
@@ -89,6 +89,33 @@ struct BundleStyleSheet<'i, 'o, T> {
loc: Location,
}
+#[derive(Debug, Clone)]
+enum Dependency {
+ File(u32),
+ External(String),
+}
+
+/// The result of [SourceProvider::resolve].
+#[derive(Debug)]
+#[cfg_attr(
+ any(feature = "serde", feature = "nodejs"),
+ derive(serde::Deserialize),
+ serde(rename_all = "lowercase")
+)]
+pub enum ResolveResult {
+ /// An external URL.
+ External(String),
+ /// A file path.
+ #[serde(untagged)]
+ File(PathBuf),
+}
+
+impl From for ResolveResult {
+ fn from(path: PathBuf) -> Self {
+ ResolveResult::File(path)
+ }
+}
+
/// A trait to provide the contents of files to a Bundler.
///
/// See [FileProvider](FileProvider) for an implementation that uses the
@@ -102,7 +129,7 @@ pub trait SourceProvider: Send + Sync {
/// Resolves the given import specifier to a file path given the file
/// which the import originated from.
- fn resolve(&self, specifier: &str, originating_file: &Path) -> Result;
+ fn resolve(&self, specifier: &str, originating_file: &Path) -> Result;
}
/// Provides an implementation of [SourceProvider](SourceProvider)
@@ -136,9 +163,9 @@ impl SourceProvider for FileProvider {
Ok(unsafe { &*ptr })
}
- fn resolve(&self, specifier: &str, originating_file: &Path) -> Result {
+ fn resolve(&self, specifier: &str, originating_file: &Path) -> Result {
// Assume the specifier is a relative file path and join it with current path.
- Ok(originating_file.with_file_name(specifier))
+ Ok(originating_file.with_file_name(specifier).into())
}
}
@@ -162,6 +189,11 @@ pub enum BundleErrorKind<'i, T: std::error::Error> {
UnsupportedLayerCombination,
/// Unsupported media query boolean logic was encountered.
UnsupportedMediaBooleanLogic,
+ /// An external module was referenced with a CSS module "from" clause.
+ ReferencedExternalModuleWithCssModuleFrom,
+ /// An external `@import` was found after a bundled `@import`.
+ /// This may result in unintended selector order.
+ ExternalImportAfterBundledImport,
/// A custom resolver error.
ResolverError(#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] T),
}
@@ -183,6 +215,13 @@ impl<'i, T: std::error::Error> std::fmt::Display for BundleErrorKind<'i, T> {
UnsupportedImportCondition => write!(f, "Unsupported import condition"),
UnsupportedLayerCombination => write!(f, "Unsupported layer combination in @import"),
UnsupportedMediaBooleanLogic => write!(f, "Unsupported boolean logic in @import media query"),
+ ReferencedExternalModuleWithCssModuleFrom => {
+ write!(f, "Referenced external module with CSS module \"from\" clause")
+ }
+ ExternalImportAfterBundledImport => write!(
+ f,
+ "An external `@import` was found after a bundled `@import`. This may result in unintended selector order."
+ ),
ResolverError(err) => std::fmt::Display::fmt(&err, f),
}
}
@@ -265,7 +304,7 @@ where
// Phase 3: concatenate.
let mut rules: Vec<'a, T::AtRule>> = Vec::new();
- self.inline(&mut rules);
+ self.inline(&mut rules)?;
let sources = self
.stylesheets
@@ -293,6 +332,23 @@ where
.flat_map(|s| s.stylesheet.as_ref().unwrap().license_comments.iter().cloned())
.collect();
+ if let Some(config) = &self.options.css_modules {
+ if config.pattern.has_content_hash() {
+ stylesheet.content_hashes = Some(
+ self
+ .stylesheets
+ .get_mut()
+ .unwrap()
+ .iter()
+ .flat_map(|s| {
+ let s = s.stylesheet.as_ref().unwrap();
+ s.content_hashes.as_ref().unwrap().iter().cloned()
+ })
+ .collect(),
+ );
+ }
+ }
+
Ok(stylesheet)
}
@@ -411,7 +467,7 @@ where
}
// Collect and load dependencies for this stylesheet in parallel.
- let dependencies: Result, _> = stylesheet
+ let dependencies: Result, _> = stylesheet
.rules
.0
.par_iter_mut()
@@ -467,16 +523,19 @@ where
};
let result = match self.fs.resolve(&specifier, file) {
- Ok(path) => self.load_file(
- &path,
- ImportRule {
- layer,
- media,
- supports: combine_supports(rule.supports.clone(), &import.supports),
- url: "".into(),
- loc: import.loc,
- },
- ),
+ Ok(ResolveResult::File(path)) => self
+ .load_file(
+ &path,
+ ImportRule {
+ layer,
+ media,
+ supports: combine_supports(rule.supports.clone(), &import.supports),
+ url: "".into(),
+ loc: import.loc,
+ },
+ )
+ .map(Dependency::File),
+ Ok(ResolveResult::External(url)) => Ok(Dependency::External(url)),
Err(err) => Err(Error {
kind: BundleErrorKind::ResolverError(err),
loc: Some(ErrorLocation::new(
@@ -563,7 +622,7 @@ where
) -> Option<'a, P::Error>>>> {
if let Some(Specifier::File(f)) = specifier {
let result = match self.fs.resolve(&f, file) {
- Ok(path) => {
+ Ok(ResolveResult::File(path)) => {
let res = self.load_file(
&path,
ImportRule {
@@ -585,6 +644,13 @@ where
res
}
+ Ok(ResolveResult::External(_)) => Err(Error {
+ kind: BundleErrorKind::ReferencedExternalModuleWithCssModuleFrom,
+ loc: Some(ErrorLocation::new(
+ style_loc,
+ self.find_filename(style_loc.source_index),
+ )),
+ }),
Err(err) => Err(Error {
kind: BundleErrorKind::ResolverError(err),
loc: Some(ErrorLocation::new(
@@ -629,7 +695,9 @@ where
}
for i in 0..stylesheets[source_index as usize].dependencies.len() {
- let dep_source_index = stylesheets[source_index as usize].dependencies[i];
+ let Dependency::File(dep_source_index) = stylesheets[source_index as usize].dependencies[i] else {
+ continue;
+ };
let resolved = &mut stylesheets[dep_source_index as usize];
// In browsers, every instance of an @import is evaluated, so we preserve the last.
@@ -642,14 +710,16 @@ where
}
}
- fn inline(&mut self, dest: &mut Vec<'a, T::AtRule>>) {
- process(self.stylesheets.get_mut().unwrap(), 0, dest);
-
- fn process<'a, T>(
+ fn inline(
+ &mut self,
+ dest: &mut Vec<'a, T::AtRule>>,
+ ) -> Result<(), Error<'a, P::Error>>> {
+ fn process<'a, T, E: std::error::Error>(
stylesheets: &mut Vec<'a, '_, T>>,
source_index: u32,
dest: &mut Vec<'a, T>>,
- ) {
+ filename: &String,
+ ) -> Result<(), Error<'a, E>>> {
let stylesheet = &mut stylesheets[source_index as usize];
let mut rules = std::mem::take(&mut stylesheet.stylesheet.as_mut().unwrap().rules.0);
@@ -661,26 +731,47 @@ where
// Include the dependency if this is the first instance as computed earlier.
if resolved.parent_source_index == source_index && resolved.parent_dep_index == dep_index as u32 {
- process(stylesheets, dep_source_index, dest);
+ process(stylesheets, dep_source_index, dest, filename)?;
}
dep_index += 1;
}
let mut import_index = 0;
+ let mut has_bundled_import = false;
for rule in &mut rules {
match rule {
- CssRule::Import(_) => {
- let dep_source_index = stylesheets[source_index as usize].dependencies[import_index];
- let resolved = &stylesheets[dep_source_index as usize];
-
- // Include the dependency if this is the last instance as computed earlier.
- if resolved.parent_source_index == source_index && resolved.parent_dep_index == dep_index {
- process(stylesheets, dep_source_index, dest);
+ CssRule::Import(import_rule) => {
+ let dep_source = &stylesheets[source_index as usize].dependencies[import_index];
+ match dep_source {
+ Dependency::File(dep_source_index) => {
+ let resolved = &stylesheets[*dep_source_index as usize];
+
+ // Include the dependency if this is the last instance as computed earlier.
+ if resolved.parent_source_index == source_index && resolved.parent_dep_index == dep_index {
+ has_bundled_import = true;
+ process(stylesheets, *dep_source_index, dest, filename)?;
+ }
+
+ *rule = CssRule::Ignored;
+ dep_index += 1;
+ }
+ Dependency::External(url) => {
+ if has_bundled_import {
+ return Err(Error {
+ kind: BundleErrorKind::ExternalImportAfterBundledImport,
+ loc: Some(ErrorLocation {
+ filename: filename.clone(),
+ line: import_rule.loc.line,
+ column: import_rule.loc.column,
+ }),
+ });
+ }
+ import_rule.url = url.to_owned().into();
+ let imp = std::mem::replace(rule, CssRule::Ignored);
+ dest.push(imp);
+ }
}
-
- *rule = CssRule::Ignored;
- dep_index += 1;
import_index += 1;
}
CssRule::LayerStatement(_) => {
@@ -722,7 +813,10 @@ where
}
dest.extend(rules);
+ Ok(())
}
+
+ process(self.stylesheets.get_mut().unwrap(), 0, dest, &self.options.filename)
}
}
@@ -806,8 +900,12 @@ mod tests {
Ok(self.map.get(file).unwrap())
}
- fn resolve(&self, specifier: &str, originating_file: &Path) -> Result {
- Ok(originating_file.with_file_name(specifier))
+ fn resolve(&self, specifier: &str, originating_file: &Path) -> Result {
+ if specifier.starts_with("https:") {
+ Ok(ResolveResult::External(specifier.to_owned()))
+ } else {
+ Ok(originating_file.with_file_name(specifier).into())
+ }
}
}
@@ -826,9 +924,9 @@ mod tests {
/// Resolve by stripping a `foo:` prefix off any import. Specifiers without
/// this prefix fail with an error.
- fn resolve(&self, specifier: &str, _originating_file: &Path) -> Result {
+ fn resolve(&self, specifier: &str, _originating_file: &Path) -> Result {
if specifier.starts_with("foo:") {
- Ok(Path::new(&specifier["foo:".len()..]).to_path_buf())
+ Ok(Path::new(&specifier["foo:".len()..]).to_path_buf().into())
} else {
let err = std::io::Error::new(
std::io::ErrorKind::NotFound,
@@ -866,6 +964,15 @@ mod tests {
fs: P,
entry: &str,
project_root: Option<&str>,
+ ) -> (String, CssModuleExports) {
+ bundle_css_module_with_pattern(fs, entry, project_root, "[hash]_[local]")
+ }
+
+ fn bundle_css_module_with_pattern(
+ fs: P,
+ entry: &str,
+ project_root: Option<&str>,
+ pattern: &'static str,
) -> (String, CssModuleExports) {
let mut bundler = Bundler::new(
&fs,
@@ -873,6 +980,7 @@ mod tests {
ParserOptions {
css_modules: Some(css_modules::Config {
dashed_idents: true,
+ pattern: css_modules::Pattern::parse(pattern).unwrap(),
..Default::default()
}),
..ParserOptions::default()
@@ -1521,6 +1629,49 @@ mod tests {
"#}
);
+ let res = bundle(
+ TestProvider {
+ map: fs! {
+ "/a.css": r#"
+ @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
+ @import './b.css';
+ "#,
+ "/b.css": r#"
+ .b { color: green }
+ "#
+ },
+ },
+ "/a.css",
+ );
+ assert_eq!(
+ res,
+ indoc! { r#"
+ @import "https://fonts.googleapis.com/css2?family=Roboto&display=swap";
+
+ .b {
+ color: green;
+ }
+ "#}
+ );
+
+ error_test(
+ TestProvider {
+ map: fs! {
+ "/a.css": r#"
+ @import './b.css';
+ @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
+ "#,
+ "/b.css": r#"
+ .b { color: green }
+ "#
+ },
+ },
+ "/a.css",
+ Some(Box::new(|err| {
+ assert!(matches!(err, BundleErrorKind::ExternalImportAfterBundledImport));
+ })),
+ );
+
error_test(
TestProvider {
map: fs! {
@@ -1978,6 +2129,35 @@ mod tests {
Some("/x/y/z"),
);
assert_eq!(code, expected);
+
+ let (code, _) = bundle_css_module_with_pattern(
+ TestProvider {
+ map: fs! {
+ "/a.css": r#"
+ @import "b.css";
+ .a { color: red }
+ "#,
+ "/b.css": r#"
+ .a { color: green }
+ "#
+ },
+ },
+ "/a.css",
+ None,
+ "[content-hash]-[local]",
+ );
+ assert_eq!(
+ code,
+ indoc! { r#"
+ .do5n2W-a {
+ color: green;
+ }
+
+ .pP97eq-a {
+ color: red;
+ }
+ "#}
+ );
}
#[test]
diff --git a/src/compat.rs b/src/compat.rs
index 6c86d3988..f0c828d75 100644
--- a/src/compat.rs
+++ b/src/compat.rs
@@ -28,6 +28,7 @@ pub enum Feature {
CapUnit,
CaseInsensitive,
ChUnit,
+ Checkmark,
CircleListStyleType,
CjkDecimalListStyleType,
CjkEarthlyBranchListStyleType,
@@ -42,6 +43,7 @@ pub enum Feature {
DecimalLeadingZeroListStyleType,
DecimalListStyleType,
DefaultPseudo,
+ DetailsContent,
DevanagariListStyleType,
Dialog,
DirSelector,
@@ -85,6 +87,7 @@ pub enum Feature {
Gencontent,
GeorgianListStyleType,
GradientInterpolationHints,
+ GrammarError,
GujaratiListStyleType,
GurmukhiListStyleType,
HasSelector,
@@ -97,7 +100,6 @@ pub enum Feature {
ImageSet,
InOutOfRange,
IndeterminatePseudo,
- IsAnimatableSize,
IsSelector,
JapaneseFormalListStyleType,
JapaneseInformalListStyleType,
@@ -141,7 +143,6 @@ pub enum Feature {
MinFunction,
ModFunction,
MongolianListStyleType,
- MozAvailableSize,
MyanmarListStyleType,
Namespaces,
Nesting,
@@ -157,6 +158,8 @@ pub enum Feature {
P3Colors,
PartPseudo,
PersianListStyleType,
+ Picker,
+ PickerIcon,
PlaceContent,
PlaceItems,
PlaceSelf,
@@ -186,11 +189,14 @@ pub enum Feature {
SimpChineseInformalListStyleType,
SomaliListStyleType,
SpaceSeparatedColorNotation,
+ SpellingError,
SquareListStyleType,
+ StatePseudoClass,
StretchSize,
StringListStyleType,
SymbolsListStyleType,
TamilListStyleType,
+ TargetText,
TeluguListStyleType,
TextDecorationThicknessPercent,
TextDecorationThicknessShorthand,
@@ -212,6 +218,7 @@ pub enum Feature {
VbUnit,
VhUnit,
ViUnit,
+ ViewTransition,
ViewportPercentageUnitsDynamic,
ViewportPercentageUnitsLarge,
ViewportPercentageUnitsSmall,
@@ -445,7 +452,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -537,7 +544,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -582,7 +589,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -627,7 +634,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -672,7 +679,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -717,7 +724,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -762,7 +769,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -807,7 +814,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -899,7 +906,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -944,7 +951,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -1008,23 +1015,13 @@ impl Feature {
return false;
}
}
- if let Some(version) = browsers.safari {
- if version < 721152 {
- return false;
- }
- }
if let Some(version) = browsers.opera {
if version < 4718592 {
return false;
}
}
- if let Some(version) = browsers.ios_saf {
- if version < 721664 {
- return false;
- }
- }
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -1033,7 +1030,7 @@ impl Feature {
return false;
}
}
- if browsers.ie.is_some() {
+ if browsers.ie.is_some() || browsers.ios_saf.is_some() || browsers.safari.is_some() {
return false;
}
}
@@ -1069,7 +1066,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -1159,7 +1156,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -1204,7 +1201,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -1254,7 +1251,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -1341,7 +1338,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -1386,7 +1383,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -1431,11 +1428,16 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
- if browsers.ie.is_some() || browsers.samsung.is_some() {
+ if let Some(version) = browsers.samsung {
+ if version < 1638400 {
+ return false;
+ }
+ }
+ if browsers.ie.is_some() {
return false;
}
}
@@ -1471,7 +1473,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -1516,7 +1518,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -1561,7 +1563,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -1628,7 +1630,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 8323072 {
+ if version < 9371648 {
return false;
}
}
@@ -1641,7 +1643,7 @@ impl Feature {
return false;
}
}
- Feature::CustomMediaQueries | Feature::FitContentFunctionSize | Feature::StretchSize => return false,
+ Feature::CustomMediaQueries | Feature::FitContentFunctionSize => return false,
Feature::DoublePositionGradients => {
if let Some(version) = browsers.chrome {
if version < 4653056 {
@@ -2214,7 +2216,7 @@ impl Feature {
}
}
if let Some(version) = browsers.samsung {
- if version < 327680 {
+ if version < 458752 {
return false;
}
}
@@ -2518,6 +2520,16 @@ impl Feature {
return false;
}
}
+ if let Some(version) = browsers.safari {
+ if version < 1704448 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.ios_saf {
+ if version < 1704448 {
+ return false;
+ }
+ }
if let Some(version) = browsers.samsung {
if version < 917504 {
return false;
@@ -2528,7 +2540,7 @@ impl Feature {
return false;
}
}
- if browsers.ie.is_some() || browsers.ios_saf.is_some() || browsers.safari.is_some() {
+ if browsers.ie.is_some() {
return false;
}
}
@@ -2654,7 +2666,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 2424832 {
+ if version < 263168 {
return false;
}
}
@@ -2773,6 +2785,16 @@ impl Feature {
return false;
}
}
+ if let Some(version) = browsers.safari {
+ if version < 1048576 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.ios_saf {
+ if version < 1048576 {
+ return false;
+ }
+ }
if let Some(version) = browsers.samsung {
if version < 655360 {
return false;
@@ -2783,7 +2805,7 @@ impl Feature {
return false;
}
}
- if browsers.ie.is_some() || browsers.ios_saf.is_some() || browsers.safari.is_some() {
+ if browsers.ie.is_some() {
return false;
}
}
@@ -2908,21 +2930,41 @@ impl Feature {
return false;
}
}
+ if let Some(version) = browsers.samsung {
+ if version < 1769472 {
+ return false;
+ }
+ }
if let Some(version) = browsers.android {
if version < 8192000 {
return false;
}
}
- if browsers.ie.is_some() || browsers.samsung.is_some() {
+ if browsers.ie.is_some() {
return false;
}
}
Feature::AbsFunction | Feature::SignFunction => {
+ if let Some(version) = browsers.chrome {
+ if version < 9043968 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.edge {
+ if version < 9043968 {
+ return false;
+ }
+ }
if let Some(version) = browsers.firefox {
if version < 7733248 {
return false;
}
}
+ if let Some(version) = browsers.opera {
+ if version < 5963776 {
+ return false;
+ }
+ }
if let Some(version) = browsers.safari {
if version < 984064 {
return false;
@@ -2933,13 +2975,12 @@ impl Feature {
return false;
}
}
- if browsers.android.is_some()
- || browsers.chrome.is_some()
- || browsers.edge.is_some()
- || browsers.ie.is_some()
- || browsers.opera.is_some()
- || browsers.samsung.is_some()
- {
+ if let Some(version) = browsers.android {
+ if version < 9043968 {
+ return false;
+ }
+ }
+ if browsers.ie.is_some() || browsers.samsung.is_some() {
return false;
}
}
@@ -3385,12 +3426,17 @@ impl Feature {
return false;
}
}
+ if let Some(version) = browsers.samsung {
+ if version < 1769472 {
+ return false;
+ }
+ }
if let Some(version) = browsers.android {
if version < 8060928 {
return false;
}
}
- if browsers.ie.is_some() || browsers.samsung.is_some() {
+ if browsers.ie.is_some() {
return false;
}
}
@@ -3454,6 +3500,294 @@ impl Feature {
return false;
}
}
+ Feature::ViewTransition => {
+ if let Some(version) = browsers.chrome {
+ if version < 7143424 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.edge {
+ if version < 7143424 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.firefox {
+ if version < 9437184 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.opera {
+ if version < 4849664 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.safari {
+ if version < 1179648 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.ios_saf {
+ if version < 1179648 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.samsung {
+ if version < 1376256 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 7143424 {
+ return false;
+ }
+ }
+ if browsers.ie.is_some() {
+ return false;
+ }
+ }
+ Feature::DetailsContent => {
+ if let Some(version) = browsers.chrome {
+ if version < 8585216 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.edge {
+ if version < 8585216 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.firefox {
+ if version < 9371648 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.opera {
+ if version < 5701632 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.safari {
+ if version < 1180672 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.ios_saf {
+ if version < 1180672 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.samsung {
+ if version < 1900544 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 8585216 {
+ return false;
+ }
+ }
+ if browsers.ie.is_some() {
+ return false;
+ }
+ }
+ Feature::TargetText => {
+ if let Some(version) = browsers.chrome {
+ if version < 5832704 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.edge {
+ if version < 5832704 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.firefox {
+ if version < 8585216 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.opera {
+ if version < 4128768 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.safari {
+ if version < 1180160 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.ios_saf {
+ if version < 1180160 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.samsung {
+ if version < 983040 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 5832704 {
+ return false;
+ }
+ }
+ if browsers.ie.is_some() {
+ return false;
+ }
+ }
+ Feature::Picker => {
+ if let Some(version) = browsers.chrome {
+ if version < 8847360 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.edge {
+ if version < 8847360 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.opera {
+ if version < 5832704 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.samsung {
+ if version < 1900544 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 8847360 {
+ return false;
+ }
+ }
+ if browsers.firefox.is_some()
+ || browsers.ie.is_some()
+ || browsers.ios_saf.is_some()
+ || browsers.safari.is_some()
+ {
+ return false;
+ }
+ }
+ Feature::PickerIcon | Feature::Checkmark => {
+ if let Some(version) = browsers.chrome {
+ if version < 8716288 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.edge {
+ if version < 8716288 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.opera {
+ if version < 5767168 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.samsung {
+ if version < 1900544 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 8716288 {
+ return false;
+ }
+ }
+ if browsers.firefox.is_some()
+ || browsers.ie.is_some()
+ || browsers.ios_saf.is_some()
+ || browsers.safari.is_some()
+ {
+ return false;
+ }
+ }
+ Feature::GrammarError | Feature::SpellingError => {
+ if let Some(version) = browsers.chrome {
+ if version < 7929856 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.edge {
+ if version < 7929856 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.opera {
+ if version < 5308416 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.safari {
+ if version < 1115136 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.ios_saf {
+ if version < 1115136 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.samsung {
+ if version < 1638400 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 7929856 {
+ return false;
+ }
+ }
+ if browsers.firefox.is_some() || browsers.ie.is_some() {
+ return false;
+ }
+ }
+ Feature::StatePseudoClass => {
+ 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 < 8257536 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.opera {
+ if version < 5439488 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.safari {
+ if version < 1115136 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.ios_saf {
+ if version < 1115136 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.samsung {
+ if version < 1769472 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 8192000 {
+ return false;
+ }
+ }
+ if browsers.ie.is_some() {
+ return false;
+ }
+ }
Feature::QUnit => {
if let Some(version) = browsers.chrome {
if version < 4128768 {
@@ -3835,6 +4169,11 @@ impl Feature {
return false;
}
}
+ if let Some(version) = browsers.firefox {
+ if version < 9633792 {
+ return false;
+ }
+ }
if let Some(version) = browsers.opera {
if version < 5177344 {
return false;
@@ -3860,7 +4199,7 @@ impl Feature {
return false;
}
}
- if browsers.firefox.is_some() || browsers.ie.is_some() {
+ if browsers.ie.is_some() {
return false;
}
}
@@ -3875,6 +4214,11 @@ impl Feature {
return false;
}
}
+ if let Some(version) = browsers.firefox {
+ if version < 9633792 {
+ return false;
+ }
+ }
if let Some(version) = browsers.opera {
if version < 4915200 {
return false;
@@ -3900,7 +4244,7 @@ impl Feature {
return false;
}
}
- if browsers.firefox.is_some() || browsers.ie.is_some() {
+ if browsers.ie.is_some() {
return false;
}
}
@@ -4271,7 +4615,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 2424832 {
+ if version < 263168 {
return false;
}
}
@@ -4318,7 +4662,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 2424832 {
+ if version < 263168 {
return false;
}
}
@@ -4822,6 +5166,7 @@ impl Feature {
| Feature::HiraganaIrohaListStyleType
| Feature::KatakanaListStyleType
| Feature::KatakanaIrohaListStyleType
+ | Feature::NoneListStyleType
| Feature::AutoSize => {
if let Some(version) = browsers.chrome {
if version < 1179648 {
@@ -5008,53 +5353,6 @@ 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
@@ -5177,63 +5475,33 @@ impl Feature {
return false;
}
}
- if let Some(version) = browsers.opera {
- if version < 5439488 {
- 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 {
- return false;
- }
- }
- if let Some(version) = browsers.edge {
- if version < 5177344 {
- return false;
- }
- }
if let Some(version) = browsers.firefox {
- if version < 262144 {
+ if version < 9633792 {
return false;
}
}
if let Some(version) = browsers.opera {
- if version < 917504 {
+ if version < 5439488 {
return false;
}
}
if let Some(version) = browsers.safari {
- if version < 458752 {
+ if version < 1703936 {
return false;
}
}
if let Some(version) = browsers.ios_saf {
- if version < 458752 {
+ if version < 1703936 {
return false;
}
}
if let Some(version) = browsers.samsung {
- if version < 66816 {
+ if version < 1769472 {
return false;
}
}
if let Some(version) = browsers.android {
- if version < 263168 {
+ if version < 8192000 {
return false;
}
}
@@ -5241,24 +5509,19 @@ impl Feature {
return false;
}
}
- Feature::IsAnimatableSize => {
+ Feature::FitContentSize => {
if let Some(version) = browsers.chrome {
- if version < 1703936 {
+ if version < 1638400 {
return false;
}
}
if let Some(version) = browsers.edge {
- if version < 786432 {
+ if version < 5177344 {
return false;
}
}
if let Some(version) = browsers.firefox {
- if version < 1048576 {
- return false;
- }
- }
- if let Some(version) = browsers.ie {
- if version < 720896 {
+ if version < 262144 {
return false;
}
}
@@ -5287,6 +5550,9 @@ impl Feature {
return false;
}
}
+ if browsers.ie.is_some() {
+ return false;
+ }
}
Feature::MaxContentSize => {
if let Some(version) = browsers.chrome {
@@ -5378,49 +5644,54 @@ impl Feature {
return false;
}
}
- Feature::WebkitFillAvailableSize => {
+ Feature::StretchSize => {
if let Some(version) = browsers.chrome {
- if version < 1638400 {
+ if version < 9043968 {
return false;
}
}
if let Some(version) = browsers.edge {
- if version < 5177344 {
+ if version < 9043968 {
return false;
}
}
if let Some(version) = browsers.opera {
- if version < 917504 {
+ if version < 5963776 {
return false;
}
}
- if let Some(version) = browsers.safari {
- if version < 458752 {
+ if let Some(version) = browsers.android {
+ if version < 9043968 {
return false;
}
}
- if let Some(version) = browsers.ios_saf {
- if version < 458752 {
+ 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::WebkitFillAvailableSize => {
+ if let Some(version) = browsers.firefox {
+ if version < 9568256 {
return false;
}
}
- if let Some(version) = browsers.samsung {
- if version < 327680 {
+ if let Some(version) = browsers.safari {
+ if version < 458752 {
return false;
}
}
- if let Some(version) = browsers.android {
- if version < 263168 {
+ if let Some(version) = browsers.ios_saf {
+ if version < 458752 {
return false;
}
}
- if browsers.firefox.is_some() || browsers.ie.is_some() {
- return false;
- }
- }
- Feature::MozAvailableSize => {
- if let Some(version) = browsers.firefox {
- if version < 262144 {
+ if let Some(version) = browsers.samsung {
+ if version < 327680 {
return false;
}
}
@@ -5428,10 +5699,7 @@ impl Feature {
|| browsers.chrome.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()
{
return false;
}
@@ -5469,63 +5737,90 @@ impl Feature {
if self.is_compatible(browsers) {
return true;
}
- browsers.android = None;
+ #[allow(unused_assignments)]
+ {
+ browsers.android = None;
+ }
}
if targets.chrome.is_some() {
browsers.chrome = targets.chrome;
if self.is_compatible(browsers) {
return true;
}
- browsers.chrome = None;
+ #[allow(unused_assignments)]
+ {
+ browsers.chrome = None;
+ }
}
if targets.edge.is_some() {
browsers.edge = targets.edge;
if self.is_compatible(browsers) {
return true;
}
- browsers.edge = None;
+ #[allow(unused_assignments)]
+ {
+ browsers.edge = None;
+ }
}
if targets.firefox.is_some() {
browsers.firefox = targets.firefox;
if self.is_compatible(browsers) {
return true;
}
- browsers.firefox = None;
+ #[allow(unused_assignments)]
+ {
+ browsers.firefox = None;
+ }
}
if targets.ie.is_some() {
browsers.ie = targets.ie;
if self.is_compatible(browsers) {
return true;
}
- browsers.ie = None;
+ #[allow(unused_assignments)]
+ {
+ browsers.ie = None;
+ }
}
if targets.ios_saf.is_some() {
browsers.ios_saf = targets.ios_saf;
if self.is_compatible(browsers) {
return true;
}
- browsers.ios_saf = None;
+ #[allow(unused_assignments)]
+ {
+ browsers.ios_saf = None;
+ }
}
if targets.opera.is_some() {
browsers.opera = targets.opera;
if self.is_compatible(browsers) {
return true;
}
- browsers.opera = None;
+ #[allow(unused_assignments)]
+ {
+ browsers.opera = None;
+ }
}
if targets.safari.is_some() {
browsers.safari = targets.safari;
if self.is_compatible(browsers) {
return true;
}
- browsers.safari = None;
+ #[allow(unused_assignments)]
+ {
+ browsers.safari = None;
+ }
}
if targets.samsung.is_some() {
browsers.samsung = targets.samsung;
if self.is_compatible(browsers) {
return true;
}
- browsers.samsung = None;
+ #[allow(unused_assignments)]
+ {
+ browsers.samsung = None;
+ }
}
false
diff --git a/src/css_modules.rs b/src/css_modules.rs
index cfe33d71a..ce7008df2 100644
--- a/src/css_modules.rs
+++ b/src/css_modules.rs
@@ -41,6 +41,11 @@ pub struct Config<'i> {
/// Whether to scope custom identifiers
/// Default is `true`.
pub custom_idents: bool,
+ /// Whether to scope container names.
+ /// Default is `true`.
+ pub container: bool,
+ /// Whether to check for pure CSS modules.
+ pub pure: bool,
}
impl<'i> Default for Config<'i> {
@@ -50,7 +55,9 @@ impl<'i> Default for Config<'i> {
dashed_idents: Default::default(),
animation: true,
grid: true,
+ container: true,
custom_idents: true,
+ pure: false,
}
}
}
@@ -107,6 +114,7 @@ impl<'i> Pattern<'i> {
"[name]" => Segment::Name,
"[local]" => Segment::Local,
"[hash]" => Segment::Hash,
+ "[content-hash]" => Segment::ContentHash,
s => return Err(PatternParseError::UnknownPlaceholder(s.into(), start_idx)),
};
segments.push(segment);
@@ -126,8 +134,20 @@ impl<'i> Pattern<'i> {
Ok(Pattern { segments })
}
+ /// Whether the pattern contains any `[content-hash]` segments.
+ pub fn has_content_hash(&self) -> bool {
+ self.segments.iter().any(|s| matches!(s, Segment::ContentHash))
+ }
+
/// Write the substituted pattern to a destination.
- pub fn write(&self, hash: &str, path: &Path, local: &str, mut write: W) -> Result<(), E>
+ pub fn write(
+ &self,
+ hash: &str,
+ path: &Path,
+ local: &str,
+ content_hash: &str,
+ mut write: W,
+ ) -> Result<(), E>
where
W: FnMut(&str) -> Result<(), E>,
{
@@ -150,6 +170,9 @@ impl<'i> Pattern<'i> {
Segment::Hash => {
write(hash)?;
}
+ Segment::ContentHash => {
+ write(content_hash)?;
+ }
}
}
Ok(())
@@ -162,8 +185,9 @@ impl<'i> Pattern<'i> {
hash: &str,
path: &Path,
local: &str,
+ content_hash: &str,
) -> Result {
- self.write(hash, path, local, |s| res.write_str(s))?;
+ self.write(hash, path, local, content_hash, |s| res.write_str(s))?;
Ok(res)
}
}
@@ -181,6 +205,8 @@ pub enum Segment<'i> {
Local,
/// A hash of the file name.
Hash,
+ /// A hash of the file contents.
+ ContentHash,
}
/// A referenced name within a CSS module, e.g. via the `composes` property.
@@ -245,6 +271,7 @@ pub(crate) struct CssModule<'a, 'b, 'c> {
pub config: &'a Config<'b>,
pub sources: Vec<&'c Path>,
pub hashes: Vec,
+ pub content_hashes: &'a Option>,
pub exports_by_source_index: Vec,
pub references: &'a mut HashMap,
}
@@ -255,6 +282,7 @@ impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> {
sources: &'c Vec,
project_root: Option<&'c str>,
references: &'a mut HashMap,
+ content_hashes: &'a Option>,
) -> Self {
let project_root = project_root.map(|p| Path::new(p));
let sources: Vec<&Path> = sources.iter().map(|filename| Path::new(filename)).collect();
@@ -279,6 +307,7 @@ impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> {
exports_by_source_index: sources.iter().map(|_| HashMap::new()).collect(),
sources,
hashes,
+ content_hashes,
references,
}
}
@@ -295,6 +324,11 @@ impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> {
&self.hashes[source_index as usize],
&self.sources[source_index as usize],
local,
+ if let Some(content_hashes) = &self.content_hashes {
+ &content_hashes[source_index as usize]
+ } else {
+ ""
+ },
)
.unwrap(),
composes: vec![],
@@ -314,6 +348,11 @@ impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> {
&self.hashes[source_index as usize],
&self.sources[source_index as usize],
&local[2..],
+ if let Some(content_hashes) = &self.content_hashes {
+ &content_hashes[source_index as usize]
+ } else {
+ ""
+ },
)
.unwrap(),
composes: vec![],
@@ -336,6 +375,11 @@ impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> {
&self.hashes[source_index as usize],
&self.sources[source_index as usize],
name,
+ if let Some(content_hashes) = &self.content_hashes {
+ &content_hashes[source_index as usize]
+ } else {
+ ""
+ },
)
.unwrap(),
composes: vec![],
@@ -365,6 +409,11 @@ impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> {
&self.hashes[*source_index as usize],
&self.sources[*source_index as usize],
&name[2..],
+ if let Some(content_hashes) = &self.content_hashes {
+ &content_hashes[*source_index as usize]
+ } else {
+ ""
+ },
)
.unwrap(),
)
@@ -385,6 +434,11 @@ impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> {
&self.hashes[source_index as usize],
&self.sources[source_index as usize],
&name[2..],
+ if let Some(content_hashes) = &self.content_hashes {
+ &content_hashes[source_index as usize]
+ } else {
+ ""
+ },
)
.unwrap(),
composes: vec![],
@@ -427,6 +481,11 @@ impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> {
&self.hashes[source_index as usize],
&self.sources[source_index as usize],
name.0.as_ref(),
+ if let Some(content_hashes) = &self.content_hashes {
+ &content_hashes[source_index as usize]
+ } else {
+ ""
+ },
)
.unwrap(),
},
diff --git a/src/declaration.rs b/src/declaration.rs
index da97e37dc..0dd3da619 100644
--- a/src/declaration.rs
+++ b/src/declaration.rs
@@ -1,11 +1,10 @@
//! CSS declarations.
use std::borrow::Cow;
-use std::collections::HashMap;
use std::ops::Range;
use crate::context::{DeclarationContext, PropertyHandlerContext};
-use crate::error::{ParserError, PrinterError};
+use crate::error::{ParserError, PrinterError, PrinterErrorKind};
use crate::parser::ParserOptions;
use crate::printer::Printer;
use crate::properties::box_shadow::BoxShadowHandler;
@@ -35,12 +34,15 @@ use crate::properties::{
ui::ColorSchemeHandler,
};
use crate::properties::{Property, PropertyId};
+use crate::selector::SelectorList;
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;
+use smallvec::SmallVec;
/// A CSS declaration block.
///
@@ -157,18 +159,70 @@ impl<'i> DeclarationBlock<'i> {
dest.whitespace()?;
dest.write_char('{')?;
dest.indent();
+ dest.newline()?;
+
+ self.to_css_declarations(dest, false, &parcel_selectors::SelectorList(SmallVec::new()), 0)?;
+
+ dest.dedent();
+ dest.newline()?;
+ dest.write_char('}')
+ }
+ pub(crate) fn has_printable_declarations(&self) -> bool {
+ if self.len() > 1 {
+ return true;
+ }
+
+ if self.declarations.len() == 1 {
+ !matches!(self.declarations[0], crate::properties::Property::Composes(_))
+ } else if self.important_declarations.len() == 1 {
+ !matches!(self.important_declarations[0], crate::properties::Property::Composes(_))
+ } else {
+ false
+ }
+ }
+
+ /// Writes the declarations to a CSS declaration block.
+ pub fn to_css_declarations(
+ &self,
+ dest: &mut Printer,
+ has_nested_rules: bool,
+ selectors: &SelectorList,
+ source_index: u32,
+ ) -> Result<(), PrinterError>
+ where
+ W: std::fmt::Write,
+ {
let mut i = 0;
let len = self.len();
macro_rules! write {
($decls: expr, $important: literal) => {
for decl in &$decls {
- dest.newline()?;
+ // The CSS modules `composes` property is handled specially, and omitted during printing.
+ // We need to add the classes it references to the list for the selectors in this rule.
+ if let crate::properties::Property::Composes(composes) = &decl {
+ if dest.is_nested() && dest.css_module.is_some() {
+ return Err(dest.error(PrinterErrorKind::InvalidComposesNesting, composes.loc));
+ }
+
+ if let Some(css_module) = &mut dest.css_module {
+ css_module
+ .handle_composes(&selectors, &composes, source_index)
+ .map_err(|e| dest.error(e, composes.loc))?;
+ continue;
+ }
+ }
+
+ if i > 0 {
+ dest.newline()?;
+ }
+
decl.to_css(dest, $important)?;
- if i != len - 1 || !dest.minify {
+ if i != len - 1 || !dest.minify || has_nested_rules {
dest.write_char(';')?;
}
+
i += 1;
}
};
@@ -176,10 +230,7 @@ impl<'i> DeclarationBlock<'i> {
write!(self.declarations, false);
write!(self.important_declarations, true);
-
- dest.dedent();
- dest.newline()?;
- dest.write_char('}')
+ Ok(())
}
}
@@ -518,7 +569,7 @@ pub(crate) struct DeclarationHandler<'i> {
prefix: PrefixHandler,
direction: Option,
unicode_bidi: Option,
- custom_properties: HashMap<'i>, usize>,
+ custom_properties: IndexMap<'i>, usize>,
decls: DeclarationList<'i>,
}
@@ -609,7 +660,7 @@ impl<'i> DeclarationHandler<'i> {
direction: self.direction.clone(),
..Default::default()
};
- for (key, index) in self.custom_properties.drain() {
+ for (key, index) in self.custom_properties.drain(..) {
handler.custom_properties.insert(key, handler.decls.len());
handler.decls.push(self.decls[index].clone());
}
diff --git a/src/error.rs b/src/error.rs
index c7f62e4f7..b4b1b0ab8 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -84,10 +84,14 @@ pub enum ParserError<'i> {
InvalidDeclaration,
/// A media query was invalid.
InvalidMediaQuery,
+ /// The brackets in a condition cannot be empty.
+ EmptyBracketInCondition,
/// Invalid CSS nesting.
InvalidNesting,
/// The @nest rule is deprecated.
DeprecatedNestRule,
+ /// The @value rule (of CSS modules) is deprecated.
+ DeprecatedCssModulesValueRule,
/// An invalid selector in an `@page` rule.
InvalidPageSelector,
/// An invalid value was encountered.
@@ -116,8 +120,10 @@ impl<'i> fmt::Display for ParserError<'i> {
EndOfInput => write!(f, "Unexpected end of input"),
InvalidDeclaration => write!(f, "Invalid declaration"),
InvalidMediaQuery => write!(f, "Invalid media query"),
+ EmptyBracketInCondition => write!(f, "The brackets cannot be empty"),
InvalidNesting => write!(f, "Invalid nesting"),
DeprecatedNestRule => write!(f, "The @nest rule is deprecated"),
+ DeprecatedCssModulesValueRule => write!(f, "The @value rule is deprecated"),
InvalidPageSelector => write!(f, "Invalid page selector"),
InvalidValue => write!(f, "Invalid value"),
QualifiedRuleInvalid => write!(f, "Invalid qualified rule"),
@@ -230,11 +236,20 @@ pub enum SelectorError<'i> {
UnexpectedTokenInAttributeSelector(
#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>,
),
- /// An unsupported pseudo class or pseudo element was encountered.
- UnsupportedPseudoClassOrElement(CowArcStr<'i>),
+
+ /// An unsupported pseudo class was encountered.
+ UnsupportedPseudoClass(CowArcStr<'i>),
+
+ /// An unsupported pseudo element was encountered.
+ UnsupportedPseudoElement(CowArcStr<'i>),
/// Ambiguous CSS module class.
AmbiguousCssModuleClass(CowArcStr<'i>),
+
+ /// An unexpected token was encountered after a pseudo element.
+ UnexpectedSelectorAfterPseudoElement(
+ #[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>,
+ ),
}
impl<'i> fmt::Display for SelectorError<'i> {
@@ -259,8 +274,15 @@ impl<'i> fmt::Display for SelectorError<'i> {
PseudoElementExpectedIdent(token) => write!(f, "Invalid token in pseudo element: {:?}", token),
UnexpectedIdent(name) => write!(f, "Unexpected identifier: {}", name),
UnexpectedTokenInAttributeSelector(token) => write!(f, "Unexpected token in attribute selector: {:?}", token),
- UnsupportedPseudoClassOrElement(name) => write!(f, "Unsupported pseudo class or element: {}", name),
+ UnsupportedPseudoClass(name) =>write!(f, "'{name}' is not recognized as a valid pseudo-class. Did you mean '::{name}' (pseudo-element) or is this a typo?"),
+ UnsupportedPseudoElement(name) => write!(f, "'{name}' is not recognized as a valid pseudo-element. Did you mean ':{name}' (pseudo-class) or is this a typo?"),
AmbiguousCssModuleClass(_) => write!(f, "Ambiguous CSS module class not supported"),
+ UnexpectedSelectorAfterPseudoElement(token) => {
+ write!(
+ f,
+ "Pseudo-elements like '::before' or '::after' can't be followed by selectors like '{token:?}'"
+ )
+ },
}
}
}
@@ -289,9 +311,8 @@ impl<'i> From<'i>> for SelectorError<'i> {
SelectorError::UnexpectedTokenInAttributeSelector(t.into())
}
SelectorParseErrorKind::PseudoElementExpectedIdent(t) => SelectorError::PseudoElementExpectedIdent(t.into()),
- SelectorParseErrorKind::UnsupportedPseudoClassOrElement(t) => {
- SelectorError::UnsupportedPseudoClassOrElement(t.into())
- }
+ SelectorParseErrorKind::UnsupportedPseudoClass(t) => SelectorError::UnsupportedPseudoClass(t.into()),
+ SelectorParseErrorKind::UnsupportedPseudoElement(t) => SelectorError::UnsupportedPseudoElement(t.into()),
SelectorParseErrorKind::UnexpectedIdent(t) => SelectorError::UnexpectedIdent(t.into()),
SelectorParseErrorKind::ExpectedNamespace(t) => SelectorError::ExpectedNamespace(t.into()),
SelectorParseErrorKind::ExpectedBarInAttr(t) => SelectorError::ExpectedBarInAttr(t.into()),
@@ -302,6 +323,9 @@ impl<'i> From<'i>> for SelectorError<'i> {
}
SelectorParseErrorKind::ClassNeedsIdent(t) => SelectorError::ClassNeedsIdent(t.into()),
SelectorParseErrorKind::AmbiguousCssModuleClass(name) => SelectorError::AmbiguousCssModuleClass(name.into()),
+ SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(t) => {
+ SelectorError::UnexpectedSelectorAfterPseudoElement(t.into())
+ }
}
}
}
@@ -342,6 +366,8 @@ pub enum MinifyErrorKind {
/// The source location of the `@custom-media` rule with unsupported boolean logic.
custom_media_loc: Location,
},
+ /// A CSS module selector did not contain at least one class or id selector.
+ ImpureCSSModuleSelector,
}
impl fmt::Display for MinifyErrorKind {
@@ -354,6 +380,10 @@ impl fmt::Display for MinifyErrorKind {
f,
"Boolean logic with media types in @custom-media rules is not supported by Lightning CSS"
),
+ ImpureCSSModuleSelector => write!(
+ f,
+ "A selector in CSS modules should contain at least one class or ID selector"
+ ),
}
}
}
@@ -383,7 +413,7 @@ pub enum PrinterErrorKind {
FmtError,
/// The CSS modules `composes` property cannot be used within nested rules.
InvalidComposesNesting,
- /// The CSS modules `composes` property cannot be used with a simple class selector.
+ /// The CSS modules `composes` property can only be used with a simple class selector.
InvalidComposesSelector,
/// The CSS modules pattern must end with `[local]` for use in CSS grid.
InvalidCssModulesPatternInGrid,
diff --git a/src/lib.rs b/src/lib.rs
index 02bf9fcf3..2a41655ee 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -66,6 +66,7 @@ mod tests {
use indoc::indoc;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
+ use std::sync::{Arc, RwLock};
fn test(source: &str, expected: &str) {
test_with_options(source, expected, ParserOptions::default())
@@ -78,10 +79,18 @@ mod tests {
assert_eq!(res.code, expected);
}
+ fn test_with_printer_options<'i, 'o>(source: &'i str, expected: &'i str, options: PrinterOptions<'o>) {
+ let mut stylesheet = StyleSheet::parse(&source, ParserOptions::default()).unwrap();
+ stylesheet.minify(MinifyOptions::default()).unwrap();
+ let res = stylesheet.to_css(options).unwrap();
+ assert_eq!(res.code, expected);
+ }
+
fn minify_test(source: &str, expected: &str) {
minify_test_with_options(source, expected, ParserOptions::default())
}
+ #[track_caller]
fn minify_test_with_options<'i, 'o>(source: &'i str, expected: &'i str, options: ParserOptions<'o, 'i>) {
let mut stylesheet = StyleSheet::parse(&source, options.clone()).unwrap();
stylesheet.minify(MinifyOptions::default()).unwrap();
@@ -94,6 +103,18 @@ mod tests {
assert_eq!(res.code, expected);
}
+ fn minify_error_test_with_options<'i, 'o>(
+ source: &'i str,
+ error: MinifyErrorKind,
+ options: ParserOptions<'o, 'i>,
+ ) {
+ let mut stylesheet = StyleSheet::parse(&source, options.clone()).unwrap();
+ match stylesheet.minify(MinifyOptions::default()) {
+ Err(e) => assert_eq!(e.kind, error),
+ _ => unreachable!(),
+ }
+ }
+
fn prefix_test(source: &str, expected: &str, targets: Browsers) {
let mut stylesheet = StyleSheet::parse(&source, ParserOptions::default()).unwrap();
stylesheet
@@ -169,6 +190,7 @@ mod tests {
expected_exports: CssModuleExports,
expected_references: CssModuleReferences,
config: crate::css_modules::Config<'i>,
+ minify: bool,
) {
let mut stylesheet = StyleSheet::parse(
&source,
@@ -180,7 +202,12 @@ mod tests {
)
.unwrap();
stylesheet.minify(MinifyOptions::default()).unwrap();
- let res = stylesheet.to_css(PrinterOptions::default()).unwrap();
+ let res = stylesheet
+ .to_css(PrinterOptions {
+ minify,
+ ..Default::default()
+ })
+ .unwrap();
assert_eq!(res.code, expected);
assert_eq!(res.exports.unwrap(), expected_exports);
assert_eq!(res.references.unwrap(), expected_references);
@@ -217,6 +244,39 @@ mod tests {
}
}
+ fn error_recovery_test(source: &str) -> Vec<'_>>> {
+ let warnings = Arc::new(RwLock::default());
+ {
+ let res = StyleSheet::parse(
+ &source,
+ ParserOptions {
+ error_recovery: true,
+ warnings: Some(warnings.clone()),
+ ..Default::default()
+ },
+ );
+ match res {
+ Ok(..) => {}
+ Err(e) => unreachable!("parser error should be recovered, but got {e:?}"),
+ }
+ }
+ Arc::into_inner(warnings).unwrap().into_inner().unwrap()
+ }
+
+ fn css_modules_error_test(source: &str, error: ParserError) {
+ let res = StyleSheet::parse(
+ &source,
+ ParserOptions {
+ css_modules: Some(Default::default()),
+ ..Default::default()
+ },
+ );
+ match res {
+ Ok(_) => unreachable!(),
+ Err(e) => assert_eq!(e.kind, error),
+ }
+ }
+
macro_rules! map(
{ $($key:expr => $name:literal $(referenced: $referenced: literal)? $($value:literal $(global: $global: literal)? $(from $from:literal)?)*),* } => {
{
@@ -335,6 +395,122 @@ mod tests {
);
}
+ #[test]
+ pub fn test_math_fn() {
+ // max()
+ minify_test(
+ r#"
+ .foo {
+ color: rgb(max(255, 100), 0, 0);
+ }
+ "#,
+ indoc! {".foo{color:red}"
+ },
+ );
+ // min()
+ minify_test(
+ r#"
+ .foo {
+ color: rgb(min(255, 500), 0, 0);
+ }
+ "#,
+ indoc! {".foo{color:red}"
+ },
+ );
+ // abs()
+ minify_test(
+ r#"
+ .foo {
+ color: rgb(abs(-255), 0, 0);
+ }
+ "#,
+ indoc! {".foo{color:red}"
+ },
+ );
+ // clamp()
+ minify_test(
+ r#"
+ .foo {
+ flex: clamp(1, 5.20, 20);
+ color: rgb(clamp(0, 255, 300), 0, 0);
+ }
+ "#,
+ indoc! {".foo{color:red;flex:5.2}"
+ },
+ );
+ // round()
+ minify_test(
+ r#"
+ .round-color {
+ color: rgb(round(down, 255.6, 1), 0, 0);
+ }
+ "#,
+ indoc! {".round-color{color:red}"
+ },
+ );
+ // hypot()
+ minify_test(
+ r#"
+ .hypot-color {
+ color: rgb(hypot(255, 0), 0, 0);
+ }
+ "#,
+ indoc! {".hypot-color{color:red}"
+ },
+ );
+ // sign(), sign(50) = 1
+ minify_test(
+ r#"
+ .sign-color {
+ color: rgb(sign(50), 0, 0);
+ }
+ "#,
+ indoc! {".sign-color{color:#010000}"
+ },
+ );
+ // rem(), rem(21, 2) = 1
+ minify_test(
+ r#"
+ .rem-color {
+ color: rgb(rem(21, 2), 0, 0);
+ }
+ "#,
+ indoc! {".rem-color{color:#010000}"
+ },
+ );
+ // max() in width
+ minify_test(
+ r#"
+ .foo {
+ width: max(200px, 5px);
+ }
+ "#,
+ indoc! {".foo{width:200px}"
+ },
+ );
+ // max() in opacity
+ minify_test(
+ r#"
+ .foo {
+ opacity: max(1, 0.2);
+ filter: invert(min(1, 0.5));
+ }
+ "#,
+ indoc! {".foo{opacity:1;filter:invert(.5)}"
+ },
+ );
+ // TODO: support calc in Integer
+ // minify_test(
+ // r#"
+ // .foo {
+ // z-index: max(100, 20);
+ // }
+ // "#,
+ // indoc! {".foo{z-index:100}"
+ // },
+ // );
+ }
+
#[test]
pub fn test_border() {
test(
@@ -1443,6 +1619,33 @@ mod tests {
..Browsers::default()
},
);
+
+ prefix_test(
+ &format!(
+ r#"
+ @supports (color: lab(0% 0 0)) {{
+ .foo {{
+ {}: var(--border-width) solid lab(40% 56.6 39);
+ }}
+ }}
+ "#,
+ prop
+ ),
+ &format!(
+ indoc! {r#"
+ @supports (color: lab(0% 0 0)) {{
+ .foo {{
+ {}: var(--border-width) solid lab(40% 56.6 39);
+ }}
+ }}
+ "#},
+ prop,
+ ),
+ Browsers {
+ chrome: Some(90 << 16),
+ ..Browsers::default()
+ },
+ );
}
prefix_test(
@@ -2118,7 +2321,7 @@ mod tests {
indoc! {r#"
.foo {
-webkit-border-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff)) 60;
- -webkit-border-image: -webkit-linear-gradient(#ff0f0e, #7773ff) 60;
+ -webkit-border-image: -webkit-linear-gradient(top, #ff0f0e, #7773ff) 60;
border-image: linear-gradient(#ff0f0e, #7773ff) 60;
border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 60;
}
@@ -2139,8 +2342,8 @@ mod tests {
indoc! {r#"
.foo {
-webkit-border-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff)) 60;
- -webkit-border-image: -webkit-linear-gradient(#ff0f0e, #7773ff) 60;
- -moz-border-image: -moz-linear-gradient(#ff0f0e, #7773ff) 60;
+ -webkit-border-image: -webkit-linear-gradient(top, #ff0f0e, #7773ff) 60;
+ -moz-border-image: -moz-linear-gradient(top, #ff0f0e, #7773ff) 60;
border-image: linear-gradient(#ff0f0e, #7773ff) 60;
border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 60;
}
@@ -2161,8 +2364,8 @@ mod tests {
"#,
indoc! {r#"
.foo {
- border-image: -webkit-linear-gradient(#ff0f0e, #7773ff) 60;
- border-image: -moz-linear-gradient(#ff0f0e, #7773ff) 60;
+ border-image: -webkit-linear-gradient(top, #ff0f0e, #7773ff) 60;
+ border-image: -moz-linear-gradient(top, #ff0f0e, #7773ff) 60;
border-image: linear-gradient(#ff0f0e, #7773ff) 60;
border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 60;
}
@@ -2183,7 +2386,7 @@ mod tests {
"#,
indoc! {r#"
.foo {
- border-image-source: -webkit-linear-gradient(#ff0f0e, #7773ff);
+ border-image-source: -webkit-linear-gradient(top, #ff0f0e, #7773ff);
border-image-source: linear-gradient(#ff0f0e, #7773ff);
border-image-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));
}
@@ -3953,6 +4156,15 @@ mod tests {
minify_test(".foo { aspect-ratio: 2 / 3 }", ".foo{aspect-ratio:2/3}");
minify_test(".foo { aspect-ratio: auto 2 / 3 }", ".foo{aspect-ratio:auto 2/3}");
minify_test(".foo { aspect-ratio: 2 / 3 auto }", ".foo{aspect-ratio:auto 2/3}");
+
+ minify_test(
+ ".foo { width: 200px; width: var(--foo); }",
+ ".foo{width:200px;width:var(--foo)}",
+ );
+ minify_test(
+ ".foo { width: var(--foo); width: 200px; }",
+ ".foo{width:var(--foo);width:200px}",
+ );
}
#[test]
@@ -4102,12 +4314,24 @@ mod tests {
);
minify_test(
".foo { background-position: left 10px center }",
- ".foo{background-position:10px 50%}",
+ ".foo{background-position:10px}",
);
minify_test(
".foo { background-position: right 10px center }",
".foo{background-position:right 10px center}",
);
+ minify_test(
+ ".foo { background-position: center top 10px }",
+ ".foo{background-position:50% 10px}",
+ );
+ minify_test(
+ ".foo { background-position: center bottom 10px }",
+ ".foo{background-position:center bottom 10px}",
+ );
+ minify_test(
+ ".foo { background-position: center 10px }",
+ ".foo{background-position:50% 10px}",
+ );
minify_test(
".foo { background-position: right 10px top 20px }",
".foo{background-position:right 10px top 20px}",
@@ -4128,6 +4352,26 @@ mod tests {
".foo { background-position: bottom right }",
".foo{background-position:100% 100%}",
);
+ minify_test(
+ ".foo { background-position: center top }",
+ ".foo{background-position:top}",
+ );
+ minify_test(
+ ".foo { background-position: center bottom }",
+ ".foo{background-position:bottom}",
+ );
+ minify_test(
+ ".foo { background-position: left center }",
+ ".foo{background-position:0}",
+ );
+ minify_test(
+ ".foo { background-position: right center }",
+ ".foo{background-position:100%}",
+ );
+ minify_test(
+ ".foo { background-position: 20px center }",
+ ".foo{background-position:20px}",
+ );
minify_test(
".foo { background: url('img-sprite.png') no-repeat bottom right }",
@@ -6820,6 +7064,19 @@ mod tests {
":root::view-transition {position: fixed}",
":root::view-transition{position:fixed}",
);
+ minify_test(
+ ":root:active-view-transition {position: fixed}",
+ ":root:active-view-transition{position:fixed}",
+ );
+ minify_test(
+ ":root:active-view-transition-type(slide-in) {position: fixed}",
+ ":root:active-view-transition-type(slide-in){position:fixed}",
+ );
+ minify_test(
+ ":root:active-view-transition-type(slide-in, reverse) {position: fixed}",
+ ":root:active-view-transition-type(slide-in,reverse){position:fixed}",
+ );
+
for name in &[
"view-transition-group",
"view-transition-image-pair",
@@ -6830,14 +7087,46 @@ mod tests {
&format!(":root::{}(*) {{position: fixed}}", name),
&format!(":root::{}(*){{position:fixed}}", name),
);
+ minify_test(
+ &format!(":root::{}(*.class) {{position: fixed}}", name),
+ &format!(":root::{}(*.class){{position:fixed}}", name),
+ );
+ minify_test(
+ &format!(":root::{}(*.class.class) {{position: fixed}}", name),
+ &format!(":root::{}(*.class.class){{position:fixed}}", name),
+ );
minify_test(
&format!(":root::{}(foo) {{position: fixed}}", name),
&format!(":root::{}(foo){{position:fixed}}", name),
);
+ minify_test(
+ &format!(":root::{}(foo.class) {{position: fixed}}", name),
+ &format!(":root::{}(foo.class){{position:fixed}}", name),
+ );
+ minify_test(
+ &format!(":root::{}(foo.bar.baz) {{position: fixed}}", name),
+ &format!(":root::{}(foo.bar.baz){{position:fixed}}", name),
+ );
minify_test(
&format!(":root::{}(foo):only-child {{position: fixed}}", name),
&format!(":root::{}(foo):only-child{{position:fixed}}", name),
);
+ minify_test(
+ &format!(":root::{}(foo.bar.baz):only-child {{position: fixed}}", name),
+ &format!(":root::{}(foo.bar.baz):only-child{{position:fixed}}", name),
+ );
+ minify_test(
+ &format!(":root::{}(.foo) {{position: fixed}}", name),
+ &format!(":root::{}(.foo){{position:fixed}}", name),
+ );
+ minify_test(
+ &format!(":root::{}(.foo.bar) {{position: fixed}}", name),
+ &format!(":root::{}(.foo.bar){{position:fixed}}", name),
+ );
+ minify_test(
+ &format!(":root::{}( .foo.bar ) {{position: fixed}}", name),
+ &format!(":root::{}(.foo.bar){{position:fixed}}", name),
+ );
error_test(
&format!(":root::{}(foo):first-child {{position: fixed}}", name),
ParserError::SelectorError(SelectorError::InvalidPseudoClassAfterPseudoElement),
@@ -6846,8 +7135,81 @@ mod tests {
&format!(":root::{}(foo)::before {{position: fixed}}", name),
ParserError::SelectorError(SelectorError::InvalidState),
);
+ error_test(
+ &format!(":root::{}(*.*) {{position: fixed}}", name),
+ ParserError::SelectorError(SelectorError::InvalidState),
+ );
+ error_test(
+ &format!(":root::{}(*. cls) {{position: fixed}}", name),
+ ParserError::SelectorError(SelectorError::InvalidState),
+ );
+ error_test(
+ &format!(":root::{}(foo .bar) {{position: fixed}}", name),
+ ParserError::SelectorError(SelectorError::InvalidState),
+ );
+ error_test(
+ &format!(":root::{}(*.cls. c) {{position: fixed}}", name),
+ ParserError::SelectorError(SelectorError::InvalidState),
+ );
+ error_test(
+ &format!(":root::{}(*.cls>cls) {{position: fixed}}", name),
+ ParserError::SelectorError(SelectorError::InvalidState),
+ );
+ error_test(
+ &format!(":root::{}(*.cls.foo.*) {{position: fixed}}", name),
+ ParserError::SelectorError(SelectorError::InvalidState),
+ );
}
+ minify_test(
+ "wa-checkbox:state(disabled) {color:red}",
+ "wa-checkbox:state(disabled){color:red}",
+ );
+ minify_test(
+ "button:state(checked) {background:blue}",
+ "button:state(checked){background:#00f}",
+ );
+ minify_test(
+ "input:state(custom-state) {border:1px solid}",
+ "input:state(custom-state){border:1px solid}",
+ );
+ minify_test(
+ "button:active:not(:state(disabled))::part(control) {border:1px solid}",
+ "button:active:not(:state(disabled))::part(control){border:1px solid}",
+ );
+ // Test nested CSS with :state() selector
+ nesting_test(
+ r#"
+ custom-element {
+ color: blue;
+ &:state(loading) {
+ opacity: 0.5;
+ & .spinner {
+ display: block;
+ }
+ }
+ &:state(error) {
+ border: 2px solid red;
+ }
+ }
+ "#,
+ indoc! {r#"
+ custom-element {
+ color: #00f;
+ }
+
+ custom-element:state(loading) {
+ opacity: .5;
+ }
+
+ custom-element:state(loading) .spinner {
+ display: block;
+ }
+ custom-element:state(error) {
+ border: 2px solid red;
+ }
+ "#},
+ );
minify_test(".foo ::deep .bar {width: 20px}", ".foo ::deep .bar{width:20px}");
minify_test(".foo::deep .bar {width: 20px}", ".foo::deep .bar{width:20px}");
minify_test(".foo ::deep.bar {width: 20px}", ".foo ::deep.bar{width:20px}");
@@ -6900,32 +7262,185 @@ mod tests {
".foo /deep/ .bar{width:20px}",
deep_options.clone(),
);
- }
- #[test]
- fn test_keyframes() {
- minify_test(
- r#"
- @keyframes "test" {
- 100% {
- background: blue
- }
- }
- "#,
- "@keyframes test{to{background:#00f}}",
+ let pure_css_module_options = ParserOptions {
+ css_modules: Some(crate::css_modules::Config {
+ pure: true,
+ ..Default::default()
+ }),
+ ..ParserOptions::default()
+ };
+
+ minify_error_test_with_options(
+ "div {width: 20px}",
+ MinifyErrorKind::ImpureCSSModuleSelector,
+ pure_css_module_options.clone(),
);
- minify_test(
- r#"
- @keyframes test {
- 100% {
- background: blue
- }
- }
- "#,
- "@keyframes test{to{background:#00f}}",
+ minify_error_test_with_options(
+ ":global(.foo) {width: 20px}",
+ MinifyErrorKind::ImpureCSSModuleSelector,
+ pure_css_module_options.clone(),
);
-
- // named animation range percentages
+ minify_error_test_with_options(
+ "[foo=bar] {width: 20px}",
+ MinifyErrorKind::ImpureCSSModuleSelector,
+ pure_css_module_options.clone(),
+ );
+ minify_error_test_with_options(
+ "div, .foo {width: 20px}",
+ MinifyErrorKind::ImpureCSSModuleSelector,
+ pure_css_module_options.clone(),
+ );
+ minify_test_with_options(
+ ":local(.foo) {width: 20px}",
+ "._8Z4fiW_foo{width:20px}",
+ pure_css_module_options.clone(),
+ );
+ minify_test_with_options(
+ "div.my-class {color: red;}",
+ "div._8Z4fiW_my-class{color:red}",
+ pure_css_module_options.clone(),
+ );
+ minify_test_with_options(
+ "#id {color: red;}",
+ "#_8Z4fiW_id{color:red}",
+ pure_css_module_options.clone(),
+ );
+ minify_test_with_options(
+ "a .my-class{color: red;}",
+ "a ._8Z4fiW_my-class{color:red}",
+ pure_css_module_options.clone(),
+ );
+ minify_test_with_options(
+ ".my-class a {color: red;}",
+ "._8Z4fiW_my-class a{color:red}",
+ pure_css_module_options.clone(),
+ );
+ minify_test_with_options(
+ ".my-class:is(a) {color: red;}",
+ "._8Z4fiW_my-class:is(a){color:red}",
+ pure_css_module_options.clone(),
+ );
+ minify_test_with_options(
+ "div:has(.my-class) {color: red;}",
+ "div:has(._8Z4fiW_my-class){color:red}",
+ pure_css_module_options.clone(),
+ );
+ minify_test_with_options(
+ ".foo { html &:hover { a_value: some-value; } }",
+ "._8Z4fiW_foo{html &:hover{a_value:some-value}}",
+ pure_css_module_options.clone(),
+ );
+ minify_test_with_options(
+ ".foo { span { color: red; } }",
+ "._8Z4fiW_foo{& span{color:red}}",
+ pure_css_module_options.clone(),
+ );
+ minify_error_test_with_options(
+ "html { .foo { span { color: red; } } }",
+ MinifyErrorKind::ImpureCSSModuleSelector,
+ pure_css_module_options.clone(),
+ );
+ minify_test_with_options(
+ ".foo { div { span { color: red; } } }",
+ "._8Z4fiW_foo{& div{& span{color:red}}}",
+ pure_css_module_options.clone(),
+ );
+ minify_error_test_with_options(
+ "@scope (div) { .foo { color: red } }",
+ MinifyErrorKind::ImpureCSSModuleSelector,
+ pure_css_module_options.clone(),
+ );
+ minify_error_test_with_options(
+ "@scope (.a) to (div) { .foo { color: red } }",
+ MinifyErrorKind::ImpureCSSModuleSelector,
+ pure_css_module_options.clone(),
+ );
+ minify_error_test_with_options(
+ "@scope (.a) to (.b) { div { color: red } }",
+ MinifyErrorKind::ImpureCSSModuleSelector,
+ pure_css_module_options.clone(),
+ );
+ minify_test_with_options(
+ "@scope (.a) to (.b) { .foo { color: red } }",
+ "@scope(._8Z4fiW_a) to (._8Z4fiW_b){._8Z4fiW_foo{color:red}}",
+ pure_css_module_options.clone(),
+ );
+ minify_test_with_options(
+ "/* cssmodules-pure-no-check */ :global(.foo) { color: red }",
+ ".foo{color:red}",
+ pure_css_module_options.clone(),
+ );
+ minify_test_with_options(
+ "/*! some license */ /* cssmodules-pure-no-check */ :global(.foo) { color: red }",
+ "/*! some license */\n.foo{color:red}",
+ pure_css_module_options.clone(),
+ );
+
+ error_test(
+ "input.defaultCheckbox::before h1 {width: 20px}",
+ ParserError::SelectorError(SelectorError::UnexpectedSelectorAfterPseudoElement(Token::Ident(
+ "h1".into(),
+ ))),
+ );
+ error_test(
+ "input.defaultCheckbox::before .my-class {width: 20px}",
+ ParserError::SelectorError(SelectorError::UnexpectedSelectorAfterPseudoElement(Token::Delim('.'))),
+ );
+ error_test(
+ "input.defaultCheckbox::before.my-class {width: 20px}",
+ ParserError::SelectorError(SelectorError::UnexpectedSelectorAfterPseudoElement(Token::Delim('.'))),
+ );
+ error_test(
+ "input.defaultCheckbox::before #id {width: 20px}",
+ ParserError::SelectorError(SelectorError::UnexpectedSelectorAfterPseudoElement(Token::IDHash(
+ "id".into(),
+ ))),
+ );
+ error_test(
+ "input.defaultCheckbox::before#id {width: 20px}",
+ ParserError::SelectorError(SelectorError::UnexpectedSelectorAfterPseudoElement(Token::IDHash(
+ "id".into(),
+ ))),
+ );
+ error_test(
+ "input.defaultCheckbox::before [attr] {width: 20px}",
+ ParserError::SelectorError(SelectorError::UnexpectedSelectorAfterPseudoElement(
+ Token::SquareBracketBlock,
+ )),
+ );
+ error_test(
+ "input.defaultCheckbox::before[attr] {width: 20px}",
+ ParserError::SelectorError(SelectorError::UnexpectedSelectorAfterPseudoElement(
+ Token::SquareBracketBlock,
+ )),
+ );
+ }
+
+ #[test]
+ fn test_keyframes() {
+ minify_test(
+ r#"
+ @keyframes "test" {
+ 100% {
+ background: blue
+ }
+ }
+ "#,
+ "@keyframes test{to{background:#00f}}",
+ );
+ minify_test(
+ r#"
+ @keyframes test {
+ 100% {
+ background: blue
+ }
+ }
+ "#,
+ "@keyframes test{to{background:#00f}}",
+ );
+
+ // named animation range percentages
minify_test(
r#"
@keyframes test {
@@ -7539,7 +8054,7 @@ mod tests {
);
minify_test(
".foo { border-width: clamp(1em, 2em, 4vh) }",
- ".foo{border-width:min(2em,4vh)}",
+ ".foo{border-width:clamp(1em,2em,4vh)}",
);
minify_test(
".foo { border-width: clamp(1em, 2vh, 4vh) }",
@@ -7550,6 +8065,10 @@ mod tests {
".foo{border-width:clamp(1px,1px + 2em,4px)}",
);
minify_test(".foo { border-width: clamp(1px, 2pt, 1in) }", ".foo{border-width:2pt}");
+ minify_test(
+ ".foo { width: clamp(-100px, 0px, 50% - 50vw); }",
+ ".foo{width:clamp(-100px,0px,50% - 50vw)}",
+ );
minify_test(
".foo { top: calc(-1 * clamp(1.75rem, 8vw, 4rem)) }",
@@ -7682,6 +8201,46 @@ mod tests {
".foo{transform:rotateX(-40deg)rotateY(50deg)}",
);
minify_test(".foo { width: calc(10px * mod(18, 5)) }", ".foo{width:30px}");
+
+ minify_test(
+ ".foo { width: calc(100% - 30px - 0) }",
+ ".foo{width:calc(100% - 30px - 0)}",
+ );
+ minify_test(
+ ".foo { width: calc(100% - 30px - 1 - 2) }",
+ ".foo{width:calc(100% - 30px - 3)}",
+ );
+ minify_test(
+ ".foo { width: calc(1 - 2 - 100% - 30px) }",
+ ".foo{width:calc(-1 - 100% - 30px)}",
+ );
+ minify_test(
+ ".foo { width: calc(2 * min(1px, 1vmin) - min(1px, 1vmin)); }",
+ ".foo{width:calc(2*min(1px,1vmin) - min(1px,1vmin))}",
+ );
+ minify_test(
+ ".foo { width: calc(100% - clamp(1.125rem, 1.25vw, 1.2375rem) - clamp(1.125rem, 1.25vw, 1.2375rem)); }",
+ ".foo{width:calc(100% - clamp(1.125rem,1.25vw,1.2375rem) - clamp(1.125rem,1.25vw,1.2375rem))}",
+ );
+ minify_test(
+ ".foo { width: calc(100% - 2 (2 * var(--card-margin))); }",
+ ".foo{width:calc(100% - 2 (2 * var(--card-margin)))}",
+ );
+
+ test(
+ indoc! {r#"
+ .test {
+ width: calc(var(--test) + 2px);
+ width: calc(var(--test) - 2px);
+ }
+ "#},
+ indoc! {r#"
+ .test {
+ width: calc(var(--test) + 2px);
+ width: calc(var(--test) - 2px);
+ }
+ "#},
+ );
}
#[test]
@@ -7722,13 +8281,13 @@ mod tests {
minify_test(".foo { rotate: acos(cos(45deg))", ".foo{rotate:45deg}");
minify_test(".foo { rotate: acos(-1)", ".foo{rotate:180deg}");
minify_test(".foo { rotate: acos(0)", ".foo{rotate:90deg}");
- minify_test(".foo { rotate: acos(1)", ".foo{rotate:none}");
+ minify_test(".foo { rotate: acos(1)", ".foo{rotate:0deg}");
minify_test(".foo { rotate: acos(45deg)", ".foo{rotate:acos(45deg)}"); // invalid
minify_test(".foo { rotate: acos(-20)", ".foo{rotate:acos(-20)}"); // evaluates to NaN
minify_test(".foo { rotate: atan(tan(45deg))", ".foo{rotate:45deg}");
minify_test(".foo { rotate: atan(1)", ".foo{rotate:45deg}");
- minify_test(".foo { rotate: atan(0)", ".foo{rotate:none}");
+ minify_test(".foo { rotate: atan(0)", ".foo{rotate:0deg}");
minify_test(".foo { rotate: atan(45deg)", ".foo{rotate:atan(45deg)}"); // invalid
minify_test(".foo { rotate: atan2(1px, -1px)", ".foo{rotate:135deg}");
@@ -7741,7 +8300,10 @@ mod tests {
minify_test(".foo { rotate: atan2(0, -1)", ".foo{rotate:180deg}");
minify_test(".foo { rotate: atan2(-1, 1)", ".foo{rotate:-45deg}");
// incompatible units
- minify_test(".foo { rotate: atan2(1px, -1vw)", ".foo{rotate:atan2(1px,-1vw)}");
+ minify_test(".foo { rotate: atan2(1px, -1vw)", ".foo{rotate:atan2(1px, -1vw)}");
+
+ minify_test(".foo { transform: rotate(acos(1)) }", ".foo{transform:rotate(0)}");
+ minify_test(".foo { transform: rotate(atan(0)) }", ".foo{transform:rotate(0)}");
}
#[test]
@@ -7773,7 +8335,10 @@ mod tests {
minify_test(".foo { width: abs(1%)", ".foo{width:abs(1%)}"); // spec says percentages must be against resolved value
minify_test(".foo { width: calc(10px * sign(-1vw)", ".foo{width:-10px}");
- minify_test(".foo { width: calc(10px * sign(1%)", ".foo{width:calc(10px*sign(1%))}");
+ minify_test(
+ ".foo { width: calc(10px * sign(1%)",
+ ".foo{width:calc(10px * sign(1%))}",
+ );
}
#[test]
@@ -8194,7 +8759,7 @@ mod tests {
}
"#,
indoc! { r#"
- @media (min-color: 3) {
+ @media not (max-color: 2) {
.foo {
color: #7fff00;
}
@@ -8215,7 +8780,7 @@ mod tests {
}
"#,
indoc! { r#"
- @media (max-color: 1) {
+ @media not (min-color: 2) {
.foo {
color: #7fff00;
}
@@ -8236,7 +8801,7 @@ mod tests {
}
"#,
indoc! { r#"
- @media (min-width: 240.001px) {
+ @media not (max-width: 240px) {
.foo {
color: #7fff00;
}
@@ -8299,7 +8864,66 @@ mod tests {
}
"#,
indoc! { r#"
- @media (max-width: 239.999px) {
+ @media not (min-width: 240px) {
+ .foo {
+ color: #7fff00;
+ }
+ }
+ "#},
+ Browsers {
+ firefox: Some(60 << 16),
+ ..Browsers::default()
+ },
+ );
+
+ prefix_test(
+ r#"
+ @media not (width < 240px) {
+ .foo {
+ color: chartreuse;
+ }
+ }
+ "#,
+ indoc! { r#"
+ @media (min-width: 240px) {
+ .foo {
+ color: #7fff00;
+ }
+ }
+ "#},
+ Browsers {
+ firefox: Some(60 << 16),
+ ..Browsers::default()
+ },
+ );
+
+ test(
+ r#"
+ @media not (width < 240px) {
+ .foo {
+ color: chartreuse;
+ }
+ }
+ "#,
+ indoc! { r#"
+ @media (width >= 240px) {
+ .foo {
+ color: #7fff00;
+ }
+ }
+ "#},
+ );
+
+ prefix_test(
+ r#"
+ @media (width < 240px) and (hover) {
+ .foo {
+ color: chartreuse;
+ }
+ }
+ "#,
+ indoc! { r#"
+ @media (not (min-width: 240px)) and (hover) {
.foo {
color: #7fff00;
}
@@ -8404,7 +9028,28 @@ mod tests {
}
"#,
indoc! { r#"
- @media (min-width: 100.001px) and (max-width: 199.999px) {
+ @media (not (max-width: 100px)) and (not (min-width: 200px)) {
+ .foo {
+ color: #7fff00;
+ }
+ }
+ "#},
+ Browsers {
+ firefox: Some(85 << 16),
+ ..Browsers::default()
+ },
+ );
+
+ prefix_test(
+ r#"
+ @media not (100px < width < 200px) {
+ .foo {
+ color: chartreuse;
+ }
+ }
+ "#,
+ indoc! { r#"
+ @media not ((not (max-width: 100px)) and (not (min-width: 200px))) {
.foo {
color: #7fff00;
}
@@ -8455,7 +9100,7 @@ mod tests {
}
"#,
indoc! { r#"
- @media (min-width: calc(1.001px + 1rem)) {
+ @media not (max-width: calc(1px + 1rem)) {
.foo {
color: #ff0;
}
@@ -8473,7 +9118,7 @@ mod tests {
}
"#,
indoc! { r#"
- @media (min-width: calc(max(10px, 1rem) + .001px)) {
+ @media not (max-width: max(10px, 1rem)) {
.foo {
color: #ff0;
}
@@ -8491,7 +9136,7 @@ mod tests {
}
"#,
indoc! { r#"
- @media (min-width: .001px) {
+ @media not (max-width: 0) {
.foo {
color: #ff0;
}
@@ -8545,7 +9190,7 @@ mod tests {
}
"#,
indoc! { r#"
- @media (-webkit-min-device-pixel-ratio: 2.001), (min-resolution: 2.001dppx) {
+ @media not (-webkit-max-device-pixel-ratio: 2), not (max-resolution: 2dppx) {
.foo {
color: #ff0;
}
@@ -8651,6 +9296,31 @@ mod tests {
},
);
+ test_with_printer_options(
+ r#"
+ @media (width < 256px) or (hover: none) {
+ .foo {
+ color: #fff;
+ }
+ }
+ "#,
+ indoc! { r#"
+ @media (not (min-width: 256px)) or (hover: none) {
+ .foo {
+ color: #fff;
+ }
+ }
+ "#},
+ PrinterOptions {
+ targets: Targets {
+ browsers: None,
+ include: Features::MediaRangeSyntax,
+ exclude: Features::empty(),
+ },
+ ..Default::default()
+ },
+ );
+
error_test(
"@media (min-width: hi) { .foo { color: chartreuse }}",
ParserError::InvalidMediaQuery,
@@ -8695,6 +9365,16 @@ mod tests {
"@media (prefers-color-scheme = dark) { .foo { color: chartreuse }}",
ParserError::InvalidMediaQuery,
);
+ error_test(
+ "@media unknown(foo) {}",
+ ParserError::UnexpectedToken(crate::properties::custom::Token::Function("unknown".into())),
+ );
+
+ // empty brackets should return a clearer error message
+ error_test("@media () {}", ParserError::EmptyBracketInCondition);
+ error_test("@media screen and () {}", ParserError::EmptyBracketInCondition);
+
+ error_recovery_test("@media unknown(foo) {}");
}
#[test]
@@ -8896,6 +9576,29 @@ mod tests {
@layer b, c;
"#},
);
+
+ test(
+ r#"
+ @layer a;
+ @import "foo.css";
+
+ @layer a {
+ foo {
+ color: red;
+ }
+ }
+ "#,
+ indoc! {r#"
+ @layer a;
+ @import "foo.css";
+
+ @layer a {
+ foo {
+ color: red;
+ }
+ }
+ "#},
+ );
}
#[test]
@@ -11094,6 +11797,77 @@ mod tests {
..Browsers::default()
},
);
+ prefix_test(
+ r#"
+ .foo {
+ transition-property: -webkit-backdrop-filter, backdrop-filter;
+ }
+ .bar {
+ transition-property: backdrop-filter;
+ }
+ .baz {
+ transition-property: -webkit-backdrop-filter;
+ }
+ "#,
+ indoc! {r#"
+ .foo, .bar {
+ transition-property: -webkit-backdrop-filter, backdrop-filter;
+ }
+
+ .baz {
+ transition-property: -webkit-backdrop-filter;
+ }
+ "#
+ },
+ Browsers {
+ safari: Some(15 << 16),
+ ..Browsers::default()
+ },
+ );
+ prefix_test(
+ r#"
+ .foo {
+ transition-property: -webkit-border-radius, -webkit-border-radius, -moz-border-radius;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ transition-property: -webkit-border-radius, -moz-border-radius;
+ }
+ "#
+ },
+ Browsers {
+ safari: Some(15 << 16),
+ ..Browsers::default()
+ },
+ );
+ prefix_test(
+ r#"
+ .foo {
+ transition: -webkit-backdrop-filter, backdrop-filter;
+ }
+ .bar {
+ transition: backdrop-filter;
+ }
+ .baz {
+ transition: -webkit-backdrop-filter;
+ }
+ "#,
+ indoc! {r#"
+ .foo, .bar {
+ transition: -webkit-backdrop-filter, backdrop-filter;
+ }
+
+ .baz {
+ transition: -webkit-backdrop-filter;
+ }
+ "#
+ },
+ Browsers {
+ safari: Some(15 << 16),
+ ..Browsers::default()
+ },
+ );
}
#[test]
@@ -11859,15 +12633,44 @@ mod tests {
#[test]
fn test_transform() {
- minify_test(
- ".foo { transform: translate(2px, 3px)",
- ".foo{transform:translate(2px,3px)}",
- );
- minify_test(
- ".foo { transform: translate(2px, 0px)",
- ".foo{transform:translate(2px)}",
+ test(
+ ".foo { transform: perspective(500px)translate3d(10px, 0, 20px)rotateY(30deg) }",
+ indoc! {r#"
+ .foo {
+ transform: perspective(500px) translate3d(10px, 0, 20px) rotateY(30deg);
+ }
+ "#},
);
- minify_test(
+ test(
+ ".foo { transform: translate3d(12px,50%,3em)scale(2,.5) }",
+ indoc! {r#"
+ .foo {
+ transform: translate3d(12px, 50%, 3em) scale(2, .5);
+ }
+ "#},
+ );
+ test(
+ ".foo { transform:matrix(1,2,-1,1,80,80) }",
+ indoc! {r#"
+ .foo {
+ transform: matrix(1, 2, -1, 1, 80, 80);
+ }
+ "#},
+ );
+
+ minify_test(
+ ".foo { transform: scale( 0.5 )translateX(10px ) }",
+ ".foo{transform:scale(.5)translate(10px)}",
+ );
+ minify_test(
+ ".foo { transform: translate(2px, 3px)",
+ ".foo{transform:translate(2px,3px)}",
+ );
+ minify_test(
+ ".foo { transform: translate(2px, 0px)",
+ ".foo{transform:translate(2px)}",
+ );
+ minify_test(
".foo { transform: translate(0px, 2px)",
".foo{transform:translateY(2px)}",
);
@@ -12061,16 +12864,31 @@ mod tests {
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:none}");
+ minify_test(".foo { rotate: none }", ".foo{rotate:none}");
+ minify_test(".foo { rotate: 0deg }", ".foo{rotate:0deg}");
+ minify_test(".foo { rotate: -0deg }", ".foo{rotate:0deg}");
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}");
minify_test(".foo { rotate: x 10deg }", ".foo{rotate:x 10deg}");
minify_test(".foo { rotate: 1 0 0 10deg }", ".foo{rotate:x 10deg}");
- minify_test(".foo { rotate: y 10deg }", ".foo{rotate:y 10deg}");
+ minify_test(".foo { rotate: 2 0 0 10deg }", ".foo{rotate:x 10deg}");
+ minify_test(".foo { rotate: 0 2 0 10deg }", ".foo{rotate:y 10deg}");
+ minify_test(".foo { rotate: 0 0 2 10deg }", ".foo{rotate:10deg}");
+ minify_test(".foo { rotate: 0 0 5.3 10deg }", ".foo{rotate:10deg}");
+ minify_test(".foo { rotate: 0 0 1 0deg }", ".foo{rotate:0deg}");
+ minify_test(".foo { rotate: 10deg 0 0 -1 }", ".foo{rotate:-10deg}");
+ minify_test(".foo { rotate: 10deg 0 0 -233 }", ".foo{rotate:-10deg}");
+ minify_test(".foo { rotate: -1 0 0 0deg }", ".foo{rotate:x 0deg}");
+ minify_test(".foo { rotate: 0deg 0 0 1 }", ".foo{rotate:0deg}");
+ minify_test(".foo { rotate: 0deg 0 0 -1 }", ".foo{rotate:0deg}");
minify_test(".foo { rotate: 0 1 0 10deg }", ".foo{rotate:y 10deg}");
+ minify_test(".foo { rotate: x 0rad }", ".foo{rotate:x 0deg}");
+ // TODO: In minify mode, convert units to the shortest form.
+ // minify_test(".foo { rotate: y 0turn }", ".foo{rotate:y 0deg}");
+ minify_test(".foo { rotate: z 0deg }", ".foo{rotate:0deg}");
+ minify_test(".foo { rotate: 10deg y }", ".foo{rotate:y 10deg}");
minify_test(".foo { rotate: 1 1 1 10deg }", ".foo{rotate:1 1 1 10deg}");
- minify_test(".foo { rotate: 0 0 1 0deg }", ".foo{rotate:none}");
- minify_test(".foo { rotate: none }", ".foo{rotate:none}");
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}");
@@ -12494,7 +13312,7 @@ mod tests {
indoc! {r#"
.foo {
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(red), to(#00f));
- background-image: -webkit-linear-gradient(red, #00f);
+ background-image: -webkit-linear-gradient(top, red, #00f);
background-image: linear-gradient(red, #00f);
}
"#},
@@ -12512,7 +13330,7 @@ mod tests {
indoc! {r#"
.foo {
background-image: -webkit-gradient(linear, 0 0, 100% 0, from(red), to(#00f));
- background-image: -webkit-linear-gradient(right, red, #00f);
+ background-image: -webkit-linear-gradient(left, red, #00f);
background-image: linear-gradient(to right, red, #00f);
}
"#},
@@ -12530,7 +13348,7 @@ mod tests {
indoc! {r#"
.foo {
background-image: -webkit-gradient(linear, 0 100%, 0 0, from(red), to(#00f));
- background-image: -webkit-linear-gradient(top, red, #00f);
+ background-image: -webkit-linear-gradient(red, #00f);
background-image: linear-gradient(to top, red, #00f);
}
"#},
@@ -12548,7 +13366,7 @@ mod tests {
indoc! {r#"
.foo {
background-image: -webkit-gradient(linear, 100% 0, 0 0, from(red), to(#00f));
- background-image: -webkit-linear-gradient(left, red, #00f);
+ background-image: -webkit-linear-gradient(right, red, #00f);
background-image: linear-gradient(to left, red, #00f);
}
"#},
@@ -12566,7 +13384,7 @@ mod tests {
indoc! {r#"
.foo {
background-image: -webkit-gradient(linear, 100% 0, 0 100%, from(red), to(#00f));
- background-image: -webkit-linear-gradient(bottom left, red, #00f);
+ background-image: -webkit-linear-gradient(top right, red, #00f);
background-image: linear-gradient(to bottom left, red, #00f);
}
"#},
@@ -12584,7 +13402,7 @@ mod tests {
indoc! {r#"
.foo {
background-image: -webkit-gradient(linear, 0 100%, 100% 0, from(red), to(#00f));
- background-image: -webkit-linear-gradient(top right, red, #00f);
+ background-image: -webkit-linear-gradient(bottom left, red, #00f);
background-image: linear-gradient(to top right, red, #00f);
}
"#},
@@ -12602,7 +13420,7 @@ mod tests {
indoc! {r#"
.foo {
background-image: -webkit-gradient(linear, 0 0, 100% 0, from(red), to(#00f));
- background-image: -webkit-linear-gradient(90deg, red, #00f);
+ background-image: -webkit-linear-gradient(0deg, red, #00f);
background-image: linear-gradient(90deg, red, #00f);
}
"#},
@@ -12636,7 +13454,7 @@ mod tests {
"#,
indoc! {r#"
.foo {
- background-image: -webkit-linear-gradient(red, #00f);
+ background-image: -webkit-linear-gradient(top, red, #00f);
background-image: linear-gradient(red, #00f);
}
"#},
@@ -12760,9 +13578,9 @@ mod tests {
indoc! {r#"
.foo {
background: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0), to(red)), url("bg.jpg");
- background: -webkit-radial-gradient(red, #00f), -webkit-linear-gradient(#ff0, red), url("bg.jpg");
- background: -moz-radial-gradient(red, #00f), -moz-linear-gradient(#ff0, red), url("bg.jpg");
- background: -o-radial-gradient(red, #00f), -o-linear-gradient(#ff0, red), url("bg.jpg");
+ background: -webkit-radial-gradient(red, #00f), -webkit-linear-gradient(top, #ff0, red), url("bg.jpg");
+ background: -moz-radial-gradient(red, #00f), -moz-linear-gradient(top, #ff0, red), url("bg.jpg");
+ background: -o-radial-gradient(red, #00f), -o-linear-gradient(top, #ff0, red), url("bg.jpg");
background: radial-gradient(red, #00f), linear-gradient(#ff0, red), url("bg.jpg");
}
"#},
@@ -12842,7 +13660,7 @@ mod tests {
".foo { background: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }",
indoc! { r#"
.foo {
- background: -webkit-linear-gradient(#ff0f0e, #7773ff);
+ background: -webkit-linear-gradient(top, #ff0f0e, #7773ff);
background: linear-gradient(#ff0f0e, #7773ff);
background: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));
}
@@ -12858,7 +13676,7 @@ mod tests {
indoc! { r#"
.foo {
background: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff));
- background: -webkit-linear-gradient(#ff0f0e, #7773ff);
+ background: -webkit-linear-gradient(top, #ff0f0e, #7773ff);
background: linear-gradient(#ff0f0e, #7773ff);
background: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));
}
@@ -12929,7 +13747,7 @@ mod tests {
".foo { background-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }",
indoc! { r#"
.foo {
- background-image: -webkit-linear-gradient(#ff0f0e, #7773ff);
+ background-image: -webkit-linear-gradient(top, #ff0f0e, #7773ff);
background-image: linear-gradient(#ff0f0e, #7773ff);
background-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));
}
@@ -12945,7 +13763,7 @@ mod tests {
indoc! { r#"
.foo {
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff));
- background-image: -webkit-linear-gradient(#ff0f0e, #7773ff);
+ background-image: -webkit-linear-gradient(top, #ff0f0e, #7773ff);
background-image: linear-gradient(#ff0f0e, #7773ff);
background-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));
}
@@ -12981,6 +13799,357 @@ mod tests {
..Browsers::default()
},
);
+
+ // Test cases from https://github.com/postcss/autoprefixer/blob/541295c0e6dd348db2d3f52772b59cd403c59d29/test/cases/gradient.css
+ prefix_test(
+ r#"
+ a {
+ background: linear-gradient(350.5deg, white, black), linear-gradient(-130deg, black, white), linear-gradient(45deg, black, white);
+ }
+ b {
+ background-image: linear-gradient(rgba(0,0,0,1), white), linear-gradient(white, black);
+ }
+ strong {
+ background: linear-gradient(to top, transparent, rgba(0, 0, 0, 0.8) 20px, #000 30px, #000) no-repeat;
+ }
+ div {
+ background-image: radial-gradient(to left, white, black), repeating-linear-gradient(to bottom right, black, white), repeating-radial-gradient(to top, aqua, red);
+ }
+ .old-radial {
+ background: radial-gradient(0 50%, ellipse farthest-corner, black, white);
+ }
+ .simple1 {
+ background: linear-gradient(black, white);
+ }
+ .simple2 {
+ background: linear-gradient(to left, black 0%, rgba(0, 0, 0, 0.5)50%, white 100%);
+ }
+ .simple3 {
+ background: linear-gradient(to left, black 50%, white 100%);
+ }
+ .simple4 {
+ background: linear-gradient(to right top, black, white);
+ }
+ .direction {
+ background: linear-gradient(top left, black, rgba(0, 0, 0, 0.5), white);
+ }
+ .silent {
+ background: -webkit-linear-gradient(top left, black, white);
+ }
+ .radial {
+ background: radial-gradient(farthest-side at 0 50%, white, black);
+ }
+ .second {
+ background: red linear-gradient(red, blue);
+ background: url('logo.png'), linear-gradient(#fff, #000);
+ }
+ .px {
+ background: linear-gradient(black 0, white 100px);
+ }
+ .list {
+ list-style-image: linear-gradient(white, black);
+ }
+ .mask {
+ mask: linear-gradient(white, black);
+ }
+ .newline {
+ background-image:
+ linear-gradient( white, black ),
+ linear-gradient( black, white );
+ }
+ .convert {
+ background: linear-gradient(0deg, white, black);
+ background: linear-gradient(90deg, white, black);
+ background: linear-gradient(180deg, white, black);
+ background: linear-gradient(270deg, white, black);
+ }
+ .grad {
+ background: linear-gradient(1grad, white, black);
+ }
+ .rad {
+ background: linear-gradient(1rad, white, black);
+ }
+ .turn {
+ background: linear-gradient(0.3turn, white, black);
+ }
+ .norm {
+ background: linear-gradient(-90deg, white, black);
+ }
+ .mask {
+ mask-image: radial-gradient(circle at 86% 86%, transparent 8px, black 8px);
+ }
+ .cover {
+ background: radial-gradient(ellipse cover at center, white, black);
+ }
+ .contain {
+ background: radial-gradient(contain at center, white, black);
+ }
+ .no-div {
+ background: linear-gradient(black);
+ }
+ .background-shorthand {
+ background: radial-gradient(#FFF, transparent) 0 0 / cover no-repeat #F0F;
+ }
+ .background-advanced {
+ background: radial-gradient(ellipse farthest-corner at 5px 15px, rgba(214, 168, 18, 0.7) 0%, rgba(255, 21, 177, 0.7) 50%, rgba(210, 7, 148, 0.7) 95%),
+ radial-gradient(#FFF, transparent),
+ url(path/to/image.jpg) 50%/cover;
+ }
+ .multiradial {
+ mask-image: radial-gradient(circle closest-corner at 100% 50%, #000, transparent);
+ }
+ .broken {
+ mask-image: radial-gradient(white, black);
+ }
+ .loop {
+ background-image: url("https://test.com/lol(test.png"), radial-gradient(yellow, black, yellow);
+ }
+ .unitless-zero {
+ background-image: linear-gradient(0, green, blue);
+ background: repeating-linear-gradient(0, blue, red 33.3%)
+ }
+ .zero-grad {
+ background: linear-gradient(0grad, green, blue);
+ background-image: repeating-linear-gradient(0grad, blue, red 33.3%)
+ }
+ .zero-rad {
+ background: linear-gradient(0rad, green, blue);
+ }
+ .zero-turn {
+ background: linear-gradient(0turn, green, blue);
+ }
+ "#,
+ indoc! { r#"
+ a {
+ background: -webkit-linear-gradient(99.5deg, #fff, #000), -webkit-linear-gradient(220deg, #000, #fff), -webkit-linear-gradient(45deg, #000, #fff);
+ background: -o-linear-gradient(99.5deg, #fff, #000), -o-linear-gradient(220deg, #000, #fff), -o-linear-gradient(45deg, #000, #fff);
+ background: linear-gradient(350.5deg, #fff, #000), linear-gradient(-130deg, #000, #fff), linear-gradient(45deg, #000, #fff);
+ }
+
+ b {
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#000), to(#fff)), -webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#000));
+ background-image: -webkit-linear-gradient(top, #000, #fff), -webkit-linear-gradient(top, #fff, #000);
+ background-image: -o-linear-gradient(top, #000, #fff), -o-linear-gradient(top, #fff, #000);
+ background-image: linear-gradient(#000, #fff), linear-gradient(#fff, #000);
+ }
+
+ strong {
+ background: -webkit-linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, .8) 20px, #000 30px, #000) no-repeat;
+ background: -o-linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, .8) 20px, #000 30px, #000) no-repeat;
+ background: linear-gradient(to top, rgba(0, 0, 0, 0), rgba(0, 0, 0, .8) 20px, #000 30px, #000) no-repeat;
+ }
+
+ div {
+ background-image: radial-gradient(to left, white, black), repeating-linear-gradient(to bottom right, black, white), repeating-radial-gradient(to top, aqua, red);
+ }
+
+ .old-radial {
+ background: radial-gradient(0 50%, ellipse farthest-corner, black, white);
+ }
+
+ .simple1 {
+ background: -webkit-gradient(linear, 0 0, 0 100%, from(#000), to(#fff));
+ background: -webkit-linear-gradient(top, #000, #fff);
+ background: -o-linear-gradient(top, #000, #fff);
+ background: linear-gradient(#000, #fff);
+ }
+
+ .simple2 {
+ background: -webkit-gradient(linear, 100% 0, 0 0, from(#000), color-stop(.5, rgba(0, 0, 0, .5)), to(#fff));
+ background: -webkit-linear-gradient(right, #000 0%, rgba(0, 0, 0, .5) 50%, #fff 100%);
+ background: -o-linear-gradient(right, #000 0%, rgba(0, 0, 0, .5) 50%, #fff 100%);
+ background: linear-gradient(to left, #000 0%, rgba(0, 0, 0, .5) 50%, #fff 100%);
+ }
+
+ .simple3 {
+ background: -webkit-gradient(linear, 100% 0, 0 0, color-stop(.5, #000), to(#fff));
+ background: -webkit-linear-gradient(right, #000 50%, #fff 100%);
+ background: -o-linear-gradient(right, #000 50%, #fff 100%);
+ background: linear-gradient(to left, #000 50%, #fff 100%);
+ }
+
+ .simple4 {
+ background: -webkit-gradient(linear, 0 100%, 100% 0, from(#000), to(#fff));
+ background: -webkit-linear-gradient(bottom left, #000, #fff);
+ background: -o-linear-gradient(bottom left, #000, #fff);
+ background: linear-gradient(to top right, #000, #fff);
+ }
+
+ .direction {
+ background: linear-gradient(top left, black, rgba(0, 0, 0, .5), white);
+ }
+
+ .silent {
+ background: -webkit-gradient(linear, 100% 100%, 0 0, from(#000), to(#fff));
+ background: -webkit-linear-gradient(top left, #000, #fff);
+ }
+
+ .radial {
+ background: -webkit-radial-gradient(farthest-side at 0, #fff, #000);
+ background: -o-radial-gradient(farthest-side at 0, #fff, #000);
+ background: radial-gradient(farthest-side at 0, #fff, #000);
+ }
+
+ .second {
+ background: red -webkit-gradient(linear, 0 0, 0 100%, from(red), to(#00f));
+ background: red -webkit-linear-gradient(top, red, #00f);
+ background: red -o-linear-gradient(top, red, #00f);
+ background: red linear-gradient(red, #00f);
+ background: url("logo.png"), linear-gradient(#fff, #000);
+ }
+
+ .px {
+ background: -webkit-linear-gradient(top, #000 0, #fff 100px);
+ background: -o-linear-gradient(top, #000 0, #fff 100px);
+ background: linear-gradient(#000 0, #fff 100px);
+ }
+
+ .list {
+ list-style-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#000));
+ list-style-image: -webkit-linear-gradient(top, #fff, #000);
+ list-style-image: -o-linear-gradient(top, #fff, #000);
+ list-style-image: linear-gradient(#fff, #000);
+ }
+
+ .mask {
+ -webkit-mask: -webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#000));
+ -webkit-mask: -webkit-linear-gradient(top, #fff, #000);
+ -webkit-mask: -o-linear-gradient(top, #fff, #000);
+ mask: -o-linear-gradient(top, #fff, #000);
+ -webkit-mask: linear-gradient(#fff, #000);
+ mask: linear-gradient(#fff, #000);
+ }
+
+ .newline {
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#000)), -webkit-gradient(linear, 0 0, 0 100%, from(#000), to(#fff));
+ background-image: -webkit-linear-gradient(top, #fff, #000), -webkit-linear-gradient(top, #000, #fff);
+ background-image: -o-linear-gradient(top, #fff, #000), -o-linear-gradient(top, #000, #fff);
+ background-image: linear-gradient(#fff, #000), linear-gradient(#000, #fff);
+ }
+
+ .convert {
+ background: -webkit-gradient(linear, 0 100%, 0 0, from(#fff), to(#000));
+ background: -webkit-linear-gradient(90deg, #fff, #000);
+ background: -o-linear-gradient(90deg, #fff, #000);
+ background: linear-gradient(0deg, #fff, #000);
+ background: linear-gradient(90deg, #fff, #000);
+ background: linear-gradient(#fff, #000);
+ background: linear-gradient(270deg, #fff, #000);
+ }
+
+ .grad {
+ background: -webkit-linear-gradient(89.1deg, #fff, #000);
+ background: -o-linear-gradient(89.1deg, #fff, #000);
+ background: linear-gradient(1grad, #fff, #000);
+ }
+
+ .rad {
+ background: -webkit-linear-gradient(32.704deg, #fff, #000);
+ background: -o-linear-gradient(32.704deg, #fff, #000);
+ background: linear-gradient(57.2958deg, #fff, #000);
+ }
+
+ .turn {
+ background: -webkit-linear-gradient(342deg, #fff, #000);
+ background: -o-linear-gradient(342deg, #fff, #000);
+ background: linear-gradient(.3turn, #fff, #000);
+ }
+
+ .norm {
+ background: -webkit-linear-gradient(#fff, #000);
+ background: -o-linear-gradient(#fff, #000);
+ background: linear-gradient(-90deg, #fff, #000);
+ }
+
+ .mask {
+ -webkit-mask-image: -webkit-radial-gradient(circle at 86% 86%, rgba(0, 0, 0, 0) 8px, #000 8px);
+ -webkit-mask-image: -o-radial-gradient(circle at 86% 86%, rgba(0, 0, 0, 0) 8px, #000 8px);
+ mask-image: -o-radial-gradient(circle at 86% 86%, rgba(0, 0, 0, 0) 8px, #000 8px);
+ -webkit-mask-image: radial-gradient(circle at 86% 86%, rgba(0, 0, 0, 0) 8px, #000 8px);
+ mask-image: radial-gradient(circle at 86% 86%, rgba(0, 0, 0, 0) 8px, #000 8px);
+ }
+
+ .cover {
+ background: radial-gradient(ellipse cover at center, white, black);
+ }
+
+ .contain {
+ background: radial-gradient(contain at center, white, black);
+ }
+
+ .no-div {
+ background: -webkit-gradient(linear, 0 0, 0 100%, from(#000));
+ background: -webkit-linear-gradient(top, #000);
+ background: -o-linear-gradient(top, #000);
+ background: linear-gradient(#000);
+ }
+
+ .background-shorthand {
+ background: #f0f -webkit-radial-gradient(#fff, rgba(0, 0, 0, 0)) 0 0 / cover no-repeat;
+ background: #f0f -o-radial-gradient(#fff, rgba(0, 0, 0, 0)) 0 0 / cover no-repeat;
+ background: #f0f radial-gradient(#fff, rgba(0, 0, 0, 0)) 0 0 / cover no-repeat;
+ }
+
+ .background-advanced {
+ background: url("path/to/image.jpg") 50% / cover;
+ background: -webkit-radial-gradient(at 5px 15px, rgba(214, 168, 18, .7) 0%, rgba(255, 21, 177, .7) 50%, rgba(210, 7, 148, .7) 95%), -webkit-radial-gradient(#fff, rgba(0, 0, 0, 0)), url("path/to/image.jpg") 50% / cover;
+ background: -o-radial-gradient(at 5px 15px, rgba(214, 168, 18, .7) 0%, rgba(255, 21, 177, .7) 50%, rgba(210, 7, 148, .7) 95%), -o-radial-gradient(#fff, rgba(0, 0, 0, 0)), url("path/to/image.jpg") 50% / cover;
+ background: radial-gradient(at 5px 15px, rgba(214, 168, 18, .7) 0%, rgba(255, 21, 177, .7) 50%, rgba(210, 7, 148, .7) 95%), radial-gradient(#fff, rgba(0, 0, 0, 0)), url("path/to/image.jpg") 50% / cover;
+ }
+
+ .multiradial {
+ -webkit-mask-image: -webkit-radial-gradient(circle closest-corner at 100%, #000, rgba(0, 0, 0, 0));
+ -webkit-mask-image: -o-radial-gradient(circle closest-corner at 100%, #000, rgba(0, 0, 0, 0));
+ mask-image: -o-radial-gradient(circle closest-corner at 100%, #000, rgba(0, 0, 0, 0));
+ -webkit-mask-image: radial-gradient(circle closest-corner at 100%, #000, rgba(0, 0, 0, 0));
+ mask-image: radial-gradient(circle closest-corner at 100%, #000, rgba(0, 0, 0, 0));
+ }
+
+ .broken {
+ -webkit-mask-image: -webkit-radial-gradient(#fff, #000);
+ -webkit-mask-image: -o-radial-gradient(#fff, #000);
+ mask-image: -o-radial-gradient(#fff, #000);
+ -webkit-mask-image: radial-gradient(#fff, #000);
+ mask-image: radial-gradient(#fff, #000);
+ }
+
+ .loop {
+ background-image: url("https://test.com/lol(test.png");
+ background-image: url("https://test.com/lol(test.png"), -webkit-radial-gradient(#ff0, #000, #ff0);
+ background-image: url("https://test.com/lol(test.png"), -o-radial-gradient(#ff0, #000, #ff0);
+ background-image: url("https://test.com/lol(test.png"), radial-gradient(#ff0, #000, #ff0);
+ }
+
+ .unitless-zero {
+ background-image: -webkit-gradient(linear, 0 100%, 0 0, from(green), to(#00f));
+ background-image: -webkit-linear-gradient(90deg, green, #00f);
+ background-image: -o-linear-gradient(90deg, green, #00f);
+ background-image: linear-gradient(0deg, green, #00f);
+ background: repeating-linear-gradient(0deg, #00f, red 33.3%);
+ }
+
+ .zero-grad {
+ background: -webkit-gradient(linear, 0 100%, 0 0, from(green), to(#00f));
+ background: -webkit-linear-gradient(90deg, green, #00f);
+ background: -o-linear-gradient(90deg, green, #00f);
+ background: linear-gradient(0grad, green, #00f);
+ background-image: repeating-linear-gradient(0grad, #00f, red 33.3%);
+ }
+
+ .zero-rad, .zero-turn {
+ background: -webkit-gradient(linear, 0 100%, 0 0, from(green), to(#00f));
+ background: -webkit-linear-gradient(90deg, green, #00f);
+ background: -o-linear-gradient(90deg, green, #00f);
+ background: linear-gradient(0deg, green, #00f);
+ }
+ "#},
+ Browsers {
+ chrome: Some(25 << 16),
+ opera: Some(12 << 16),
+ android: Some(2 << 16 | 3 << 8),
+ ..Browsers::default()
+ },
+ );
}
#[test]
@@ -13051,7 +14220,7 @@ mod tests {
// ref: https://github.com/parcel-bundler/lightningcss/pull/255#issuecomment-1219049998
minify_test(
"@font-face {src: url(\"foo.ttf\") tech(palettes color-colrv0 variations) format(opentype);}",
- "@font-face{src:url(foo.ttf) tech(palettes color-colrv0 variations)format(opentype)}",
+ "@font-face{src:url(foo.ttf) tech(palettes color-colrv0 variations) format(opentype)}",
);
// TODO(CGQAQ): make this test pass when we have strict mode
// ref: https://github.com/web-platform-tests/wpt/blob/9f8a6ccc41aa725e8f51f4f096f686313bb88d8d/css/css-fonts/parsing/font-face-src-tech.html#L45
@@ -13073,7 +14242,7 @@ mod tests {
// );
minify_test(
"@font-face {src: local(\"\") url(\"test.woff\");}",
- "@font-face{src:local(\"\")url(test.woff)}",
+ "@font-face{src:local(\"\") url(test.woff)}",
);
minify_test("@font-face {font-weight: 200 400}", "@font-face{font-weight:200 400}");
minify_test("@font-face {font-weight: 400 400}", "@font-face{font-weight:400}");
@@ -13171,7 +14340,7 @@ mod tests {
font-family: Handover Sans;
base-palette: 3;
override-colors: 1 rgb(43, 12, 9), 3 var(--highlight);
- }"#, "@font-palette-values --Cooler{font-family:Handover Sans;base-palette:3;override-colors:1 #2b0c09,3 var(--highlight)}");
+ }"#, "@font-palette-values --Cooler{font-family:Handover Sans;base-palette:3;override-colors:1 #2b0c09, 3 var(--highlight)}");
prefix_test(
r#"@font-palette-values --Cooler {
font-family: Handover Sans;
@@ -13215,9 +14384,149 @@ mod tests {
..Browsers::default()
},
);
+ prefix_test(
+ r#"@supports (color: lab(0% 0 0)) {
+ @font-palette-values --Cooler {
+ font-family: Handover Sans;
+ base-palette: 3;
+ override-colors: 1 var(--foo), 3 lab(50.998% 125.506 -50.7078);
+ }
+ }"#,
+ indoc! {r#"@supports (color: lab(0% 0 0)) {
+ @font-palette-values --Cooler {
+ font-family: Handover Sans;
+ base-palette: 3;
+ override-colors: 1 var(--foo), 3 lab(50.998% 125.506 -50.7078);
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(90 << 16),
+ ..Browsers::default()
+ },
+ );
minify_test(".foo { font-palette: --Custom; }", ".foo{font-palette:--Custom}");
}
+ #[test]
+ fn test_font_feature_values() {
+ // https://github.com/clagnut/TODS/blob/e693d52ad411507b960cf01a9734265e3efab102/tods.css#L116-L142
+ minify_test(
+ r#"
+@font-feature-values "Fancy Font Name" {
+ @styleset { cursive: 1; swoopy: 7 16; }
+ @character-variant { ampersand: 1; capital-q: 2; }
+ @stylistic { two-story-g: 1; straight-y: 2; }
+ @swash { swishy: 1; flowing: 2; }
+ @ornaments { clover: 1; fleuron: 2; }
+ @annotation { circled: 1; boxed: 2; }
+}
+ "#,
+ r#"@font-feature-values Fancy Font Name{@styleset{cursive:1;swoopy:7 16}@character-variant{ampersand:1;capital-q:2}@stylistic{two-story-g:1;straight-y:2}@swash{swishy:1;flowing:2}@ornaments{clover:1;fleuron:2}@annotation{circled:1;boxed:2}}"#,
+ );
+
+ // https://github.com/Sorixelle/srxl.me/blob/4eb4f4a15cb2d21356df24c096d6a819cfdc1a99/public/fonts/inter/inter.css#L201-L222
+ minify_test(
+ r#"
+@font-feature-values "Inter", "Inter var", "Inter var experimental" {
+ @styleset {
+ open-digits: 1;
+ disambiguation: 2;
+ curved-r: 3;
+ disambiguation-without-zero: 4;
+ }
+
+ @character-variant {
+ alt-one: 1;
+ open-four: 2;
+ open-six: 3;
+ open-nine: 4;
+ lower-l-with-tail: 5;
+ curved-lower-r: 6;
+ german-double-s: 7;
+ upper-i-with-serif: 8;
+ flat-top-three: 9;
+ upper-g-with-spur: 10;
+ single-storey-a: 11;
+ }
+}
+ "#,
+ r#"@font-feature-values Inter,Inter var,Inter var experimental{@styleset{open-digits:1;disambiguation:2;curved-r:3;disambiguation-without-zero:4}@character-variant{alt-one:1;open-four:2;open-six:3;open-nine:4;lower-l-with-tail:5;curved-lower-r:6;german-double-s:7;upper-i-with-serif:8;flat-top-three:9;upper-g-with-spur:10;single-storey-a:11}}"#,
+ );
+
+ // https://github.com/MihailJP/Inconsolata-LGC/blob/7c53cf455787096c93d82d9a51018f12ec39a6e9/Inconsolata-LGC.css#L65-L91
+ minify_test(
+ r#"
+@font-feature-values "Inconsolata LGC" {
+ @styleset {
+ alternative-umlaut: 1;
+ }
+ @character-variant {
+ zero-plain: 1 1;
+ zero-dotted: 1 2;
+ zero-longslash: 1 3;
+ r-with-serif: 2 1;
+ eng-descender: 3 1;
+ eng-uppercase: 3 2;
+ dollar-open: 4 1;
+ dollar-oldstyle: 4 2;
+ dollar-cifrao: 4 2;
+ ezh-no-descender: 5 1;
+ ezh-reversed-sigma: 5 2;
+ triangle-text-form: 6 1;
+ el-with-hook-old: 7 1;
+ qa-enlarged-lowercase: 8 1;
+ qa-reversed-p: 8 2;
+ che-with-hook: 9 1;
+ che-with-hook-alt: 9 2;
+ ge-with-hook: 10 1;
+ ge-with-hook-alt: 10 2;
+ ge-with-stroke-and-descender: 11 1;
+ }
+}
+ "#,
+ r#"@font-feature-values Inconsolata LGC{@styleset{alternative-umlaut:1}@character-variant{zero-plain:1 1;zero-dotted:1 2;zero-longslash:1 3;r-with-serif:2 1;eng-descender:3 1;eng-uppercase:3 2;dollar-open:4 1;dollar-oldstyle:4 2;dollar-cifrao:4 2;ezh-no-descender:5 1;ezh-reversed-sigma:5 2;triangle-text-form:6 1;el-with-hook-old:7 1;qa-enlarged-lowercase:8 1;qa-reversed-p:8 2;che-with-hook:9 1;che-with-hook-alt:9 2;ge-with-hook:10 1;ge-with-hook-alt:10 2;ge-with-stroke-and-descender:11 1}}"#,
+ );
+
+ minify_test(
+ r#"
+ @font-feature-values "Fancy Font Name" {
+ @styleset { cursive: 1; swoopy: 7 16; }
+ @character-variant { ampersand: 1; capital-q: 2; }
+ }
+ "#,
+ r#"@font-feature-values Fancy Font Name{@styleset{cursive:1;swoopy:7 16}@character-variant{ampersand:1;capital-q:2}}"#,
+ );
+ minify_test(
+ r#"
+ @font-feature-values foo {
+ @swash { pretty: 0; pretty: 1; cool: 2; }
+ }
+ "#,
+ "@font-feature-values foo{@swash{pretty:1;cool:2}}",
+ );
+ minify_test(
+ r#"
+ @font-feature-values foo {
+ @swash { pretty: 1; }
+ @swash { cool: 2; }
+ }
+ "#,
+ "@font-feature-values foo{@swash{pretty:1;cool:2}}",
+ );
+ minify_test(
+ r#"
+ @font-feature-values foo {
+ @swash { pretty: 1; }
+ }
+ @font-feature-values foo {
+ @swash { cool: 2; }
+ }
+ "#,
+ "@font-feature-values foo{@swash{pretty:1;cool:2}}",
+ );
+ }
+
#[test]
fn test_page_rule() {
minify_test("@page {margin: 0.5cm}", "@page{margin:.5cm}");
@@ -13803,6 +15112,8 @@ mod tests {
"@layer foo; @import url(foo.css); @layer bar; @import url(bar.css)",
ParserError::UnexpectedImportRule,
);
+ let warnings = error_recovery_test("@import './actual-styles.css';");
+ assert_eq!(warnings, vec![]);
}
#[test]
@@ -14905,6 +16216,27 @@ mod tests {
},
);
+ prefix_test(
+ r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ text-decoration: lab(50.998% 125.506 -50.7078) var(--style);
+ }
+ }
+ "#,
+ indoc! {r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ text-decoration: lab(50.998% 125.506 -50.7078) var(--style);
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(90 << 16),
+ ..Browsers::default()
+ },
+ );
+
prefix_test(
r#"
.foo {
@@ -15272,26 +16604,47 @@ mod tests {
..Browsers::default()
},
);
- }
- #[test]
- fn test_text_shadow() {
- minify_test(
- ".foo { text-shadow: 1px 1px 2px yellow; }",
- ".foo{text-shadow:1px 1px 2px #ff0}",
- );
- minify_test(
- ".foo { text-shadow: 1px 1px 2px 3px yellow; }",
- ".foo{text-shadow:1px 1px 2px 3px #ff0}",
- );
- minify_test(
- ".foo { text-shadow: 1px 1px 0 yellow; }",
- ".foo{text-shadow:1px 1px #ff0}",
- );
- minify_test(
- ".foo { text-shadow: 1px 1px yellow; }",
- ".foo{text-shadow:1px 1px #ff0}",
- );
+ prefix_test(
+ r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ text-emphasis: lab(50.998% 125.506 -50.7078) var(--style);
+ }
+ }
+ "#,
+ indoc! {r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ text-emphasis: lab(50.998% 125.506 -50.7078) var(--style);
+ }
+ }
+ "#},
+ Browsers {
+ safari: Some(8 << 16),
+ ..Browsers::default()
+ },
+ );
+ }
+
+ #[test]
+ fn test_text_shadow() {
+ minify_test(
+ ".foo { text-shadow: 1px 1px 2px yellow; }",
+ ".foo{text-shadow:1px 1px 2px #ff0}",
+ );
+ minify_test(
+ ".foo { text-shadow: 1px 1px 2px 3px yellow; }",
+ ".foo{text-shadow:1px 1px 2px 3px #ff0}",
+ );
+ minify_test(
+ ".foo { text-shadow: 1px 1px 0 yellow; }",
+ ".foo{text-shadow:1px 1px #ff0}",
+ );
+ minify_test(
+ ".foo { text-shadow: 1px 1px yellow; }",
+ ".foo{text-shadow:1px 1px #ff0}",
+ );
minify_test(
".foo { text-shadow: 1px 1px yellow, 2px 3px red; }",
".foo{text-shadow:1px 1px #ff0,2px 3px red}",
@@ -15359,6 +16712,27 @@ mod tests {
..Browsers::default()
},
);
+
+ prefix_test(
+ r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ text-shadow: var(--foo) 12px lab(40% 56.6 39);
+ }
+ }
+ "#,
+ indoc! { r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ text-shadow: var(--foo) 12px lab(40% 56.6 39);
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(4 << 16),
+ ..Browsers::default()
+ },
+ );
}
#[test]
@@ -16069,6 +17443,27 @@ mod tests {
..Browsers::default()
},
);
+
+ prefix_test(
+ r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ caret: lab(50.998% 125.506 -50.7078) var(--foo);
+ }
+ }
+ "#,
+ indoc! { r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ caret: lab(50.998% 125.506 -50.7078) var(--foo);
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(90 << 16),
+ ..Browsers::default()
+ },
+ );
}
#[test]
@@ -16101,7 +17496,18 @@ mod tests {
);
minify_test(
".foo { list-style: \"★\" url(ellipse.png) outside; }",
- ".foo{list-style:\"★\" url(ellipse.png)}",
+ ".foo{list-style:url(ellipse.png) \"★\"}",
+ );
+ minify_test(".foo { list-style: none; }", ".foo{list-style:none}");
+ minify_test(".foo { list-style: none none outside; }", ".foo{list-style:none}");
+ minify_test(".foo { list-style: none none inside; }", ".foo{list-style:inside none}");
+ minify_test(".foo { list-style: none inside; }", ".foo{list-style:inside none}");
+ minify_test(".foo { list-style: none disc; }", ".foo{list-style:outside}");
+ minify_test(".foo { list-style: none inside disc; }", ".foo{list-style:inside}");
+ minify_test(".foo { list-style: none \"★\"; }", ".foo{list-style:\"★\"}");
+ minify_test(
+ ".foo { list-style: none url(foo.png); }",
+ ".foo{list-style:url(foo.png) none}",
);
test(
@@ -16142,7 +17548,7 @@ mod tests {
"#,
indoc! {r#"
.foo {
- list-style: \"★\" url("ellipse.png");
+ list-style: url("ellipse.png") \"★\";
list-style-image: var(--img);
}
"#},
@@ -16153,7 +17559,7 @@ mod tests {
indoc! { r#"
.foo {
list-style-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff));
- list-style-image: -webkit-linear-gradient(#ff0f0e, #7773ff);
+ list-style-image: -webkit-linear-gradient(top, #ff0f0e, #7773ff);
list-style-image: linear-gradient(#ff0f0e, #7773ff);
list-style-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));
}
@@ -16168,8 +17574,8 @@ mod tests {
".foo { list-style: \"★\" linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }",
indoc! { r#"
.foo {
- list-style: "★" linear-gradient(#ff0f0e, #7773ff);
- list-style: "★" linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));
+ list-style: linear-gradient(#ff0f0e, #7773ff) "★";
+ list-style: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) "★";
}
"#},
Browsers {
@@ -16196,6 +17602,54 @@ mod tests {
..Browsers::default()
},
);
+
+ prefix_test(
+ r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ list-style: var(--foo) linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586));
+ }
+ }
+ "#,
+ indoc! { r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ list-style: var(--foo) linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586));
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(90 << 16),
+ ..Browsers::default()
+ },
+ );
+
+ test(
+ r#"
+ .foo {
+ list-style: inside;
+ list-style-type: disc;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ list-style: inside;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ list-style: inside;
+ list-style-type: decimal;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ list-style: inside decimal;
+ }
+ "#},
+ );
}
#[test]
@@ -16396,6 +17850,7 @@ mod tests {
minify_test(".foo { color: hsl(100deg, 100%, 50%) }", ".foo{color:#5f0}");
minify_test(".foo { color: hsl(100, 100%, 50%) }", ".foo{color:#5f0}");
minify_test(".foo { color: hsl(100 100% 50%) }", ".foo{color:#5f0}");
+ minify_test(".foo { color: hsl(100 100 50) }", ".foo{color:#5f0}");
minify_test(".foo { color: hsl(100, 100%, 50%, .8) }", ".foo{color:#5f0c}");
minify_test(".foo { color: hsl(100 100% 50% / .8) }", ".foo{color:#5f0c}");
minify_test(".foo { color: hsla(100, 100%, 50%, .8) }", ".foo{color:#5f0c}");
@@ -16407,12 +17862,21 @@ mod tests {
minify_test(".foo { color: hwb(194 0% 0% / 50%) }", ".foo{color:#00c4ff80}");
minify_test(".foo { color: hwb(194 0% 50%) }", ".foo{color:#006280}");
minify_test(".foo { color: hwb(194 50% 0%) }", ".foo{color:#80e1ff}");
+ minify_test(".foo { color: hwb(194 50 0) }", ".foo{color:#80e1ff}");
minify_test(".foo { color: hwb(194 50% 50%) }", ".foo{color:gray}");
// minify_test(".foo { color: ActiveText }", ".foo{color:ActiveTet}");
minify_test(
".foo { color: lab(29.2345% 39.3825 20.0664); }",
".foo{color:lab(29.2345% 39.3825 20.0664)}",
);
+ minify_test(
+ ".foo { color: lab(29.2345 39.3825 20.0664); }",
+ ".foo{color:lab(29.2345% 39.3825 20.0664)}",
+ );
+ minify_test(
+ ".foo { color: lab(29.2345% 39.3825% 20.0664%); }",
+ ".foo{color:lab(29.2345% 49.2281 25.083)}",
+ );
minify_test(
".foo { color: lab(29.2345% 39.3825 20.0664 / 100%); }",
".foo{color:lab(29.2345% 39.3825 20.0664)}",
@@ -16425,6 +17889,14 @@ mod tests {
".foo { color: lch(29.2345% 44.2 27); }",
".foo{color:lch(29.2345% 44.2 27)}",
);
+ minify_test(
+ ".foo { color: lch(29.2345 44.2 27); }",
+ ".foo{color:lch(29.2345% 44.2 27)}",
+ );
+ minify_test(
+ ".foo { color: lch(29.2345% 44.2% 27deg); }",
+ ".foo{color:lch(29.2345% 66.3 27)}",
+ );
minify_test(
".foo { color: lch(29.2345% 44.2 45deg); }",
".foo{color:lch(29.2345% 44.2 45)}",
@@ -16445,10 +17917,26 @@ mod tests {
".foo { color: oklab(40.101% 0.1147 0.0453); }",
".foo{color:oklab(40.101% .1147 .0453)}",
);
+ minify_test(
+ ".foo { color: oklab(.40101 0.1147 0.0453); }",
+ ".foo{color:oklab(40.101% .1147 .0453)}",
+ );
+ minify_test(
+ ".foo { color: oklab(40.101% 0.1147% 0.0453%); }",
+ ".foo{color:oklab(40.101% .0004588 .0001812)}",
+ );
minify_test(
".foo { color: oklch(40.101% 0.12332 21.555); }",
".foo{color:oklch(40.101% .12332 21.555)}",
);
+ minify_test(
+ ".foo { color: oklch(.40101 0.12332 21.555); }",
+ ".foo{color:oklch(40.101% .12332 21.555)}",
+ );
+ minify_test(
+ ".foo { color: oklch(40.101% 0.12332% 21.555); }",
+ ".foo{color:oklch(40.101% .00049328 21.555)}",
+ );
minify_test(
".foo { color: oklch(40.101% 0.12332 .5turn); }",
".foo{color:oklch(40.101% .12332 180)}",
@@ -16991,6 +18479,27 @@ mod tests {
},
);
+ prefix_test(
+ r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ background: var(--image) lab(40% 56.6 39);
+ }
+ }
+ "#,
+ indoc! { r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ background: var(--image) lab(40% 56.6 39);
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(90 << 16),
+ ..Browsers::default()
+ },
+ );
+
prefix_test(
r#"
.foo {
@@ -17093,6 +18602,28 @@ mod tests {
},
);
+ prefix_test(
+ r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ color: var(--foo, lab(40% 56.6 39));
+ }
+ }
+ "#,
+ indoc! {r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ color: var(--foo, lab(40% 56.6 39));
+ }
+ }
+ "#
+ },
+ Browsers {
+ safari: Some(14 << 16),
+ ..Browsers::default()
+ },
+ );
+
prefix_test(
r#"
.foo {
@@ -17270,7 +18801,7 @@ mod tests {
}
test("lab(from indianred calc(l * .8) a b)", "lab(43.1402% 45.7516 23.1557)");
- test("lch(from indianred calc(l + 10%) c h)", "lch(63.9252% 51.2776 26.8448)");
+ test("lch(from indianred calc(l + 10) c h)", "lch(63.9252% 51.2776 26.8448)");
test("lch(from indianred l calc(c - 50) h)", "lch(53.9252% 1.27763 26.8448)");
test(
"lch(from indianred l c calc(h + 180deg))",
@@ -17286,12 +18817,23 @@ mod tests {
"rgba(205, 92, 92, .7)",
);
test(
- "rgb(from rgba(205, 92, 92, .5) r g b / calc(alpha + 20%))",
+ "rgb(from rgba(205, 92, 92, .5) r g b / calc(alpha + .2))",
"rgba(205, 92, 92, .7)",
);
test("lch(from indianred l sin(c) h)", "lch(53.9252% .84797 26.8448)");
test("lch(from indianred l sqrt(c) h)", "lch(53.9252% 7.16084 26.8448)");
- test("lch(from indianred l c sin(h))", "lch(53.9252% 51.2776 .990043)");
+ test("lch(from indianred l c sin(h))", "lch(53.9252% 51.2776 .451575)");
+ test("lch(from indianred calc(10% + 20%) c h)", "lch(30% 51.2776 26.8448)");
+ test("lch(from indianred calc(10 + 20) c h)", "lch(30% 51.2776 26.8448)");
+ test("lch(from indianred l c calc(10 + 20))", "lch(53.9252% 51.2776 30)");
+ test(
+ "lch(from indianred l c calc(10deg + 20deg))",
+ "lch(53.9252% 51.2776 30)",
+ );
+ test(
+ "lch(from indianred l c calc(10deg + 0.35rad))",
+ "lch(53.9252% 51.2776 30.0535)",
+ );
minify_test(
".foo{color:lch(from currentColor l c sin(h))}",
".foo{color:lch(from currentColor l c sin(h))}",
@@ -17405,21 +18947,15 @@ mod tests {
// Testing permutation.
test("rgb(from rebeccapurple g b r)", "rgb(51, 153, 102)");
- test("rgb(from rebeccapurple b alpha r / g)", "rgba(153, 255, 102, 0.2)");
- test("rgb(from rebeccapurple r r r / r)", "rgba(102, 102, 102, 0.4)");
- test(
- "rgb(from rebeccapurple alpha alpha alpha / alpha)",
- "rgb(255, 255, 255)",
- );
+ test("rgb(from rebeccapurple b alpha r / g)", "rgba(153, 1, 102, 1)");
+ test("rgb(from rebeccapurple r r r / r)", "rgba(102, 102, 102, 1)");
+ test("rgb(from rebeccapurple alpha alpha alpha / alpha)", "rgb(1, 1, 1)");
test("rgb(from rgb(20%, 40%, 60%, 80%) g b r)", "rgb(102, 153, 51)");
- test(
- "rgb(from rgb(20%, 40%, 60%, 80%) b alpha r / g)",
- "rgba(153, 204, 51, 0.4)",
- );
- test("rgb(from rgb(20%, 40%, 60%, 80%) r r r / r)", "rgba(51, 51, 51, 0.2)");
+ test("rgb(from rgb(20%, 40%, 60%, 80%) b alpha r / g)", "rgba(153, 1, 51, 1)");
+ test("rgb(from rgb(20%, 40%, 60%, 80%) r r r / r)", "rgba(51, 51, 51, 1)");
test(
"rgb(from rgb(20%, 40%, 60%, 80%) alpha alpha alpha / alpha)",
- "rgba(204, 204, 204, 0.8)",
+ "rgba(1, 1, 1, 0.8)",
);
// Testing mixes of number and percentage. (These would not be allowed in the non-relative syntax).
@@ -17543,17 +19079,29 @@ mod tests {
// Testing valid permutation (types match).
test("hsl(from rebeccapurple h l s)", "rgb(128, 77, 179)");
- test("hsl(from rebeccapurple h alpha l / s)", "rgba(102, 0, 204, 0.5)");
- test("hsl(from rebeccapurple h l l / l)", "rgba(102, 61, 143, 0.4)");
- test("hsl(from rebeccapurple h alpha alpha / alpha)", "rgb(255, 255, 255)");
+ test(
+ "hsl(from rebeccapurple h calc(alpha * 100) l / calc(s / 100))",
+ "rgba(102, 0, 204, 0.5)",
+ );
+ test(
+ "hsl(from rebeccapurple h l l / calc(l / 100))",
+ "rgba(102, 61, 143, 0.4)",
+ );
+ test(
+ "hsl(from rebeccapurple h calc(alpha * 100) calc(alpha * 100) / calc(alpha * 100))",
+ "rgb(255, 255, 255)",
+ );
test("hsl(from rgb(20%, 40%, 60%, 80%) h l s)", "rgb(77, 128, 179)");
test(
- "hsl(from rgb(20%, 40%, 60%, 80%) h alpha l / s)",
+ "hsl(from rgb(20%, 40%, 60%, 80%) h calc(alpha * 100) l / calc(s / 100))",
"rgba(20, 102, 184, 0.5)",
);
- test("hsl(from rgb(20%, 40%, 60%, 80%) h l l / l)", "rgba(61, 102, 143, 0.4)");
test(
- "hsl(from rgb(20%, 40%, 60%, 80%) h alpha alpha / alpha)",
+ "hsl(from rgb(20%, 40%, 60%, 80%) h l l / calc(l / 100))",
+ "rgba(61, 102, 143, 0.4)",
+ );
+ test(
+ "hsl(from rgb(20%, 40%, 60%, 80%) h calc(alpha * 100) calc(alpha * 100) / alpha)",
"rgba(163, 204, 245, 0.8)",
);
@@ -17681,17 +19229,29 @@ mod tests {
// Testing valid permutation (types match).
test("hwb(from rebeccapurple h b w)", "rgb(153, 102, 204)");
- test("hwb(from rebeccapurple h alpha w / b)", "rgba(213, 213, 213, 0.4)");
- test("hwb(from rebeccapurple h w w / w)", "rgba(128, 51, 204, 0.2)");
- test("hwb(from rebeccapurple h alpha alpha / alpha)", "rgb(128, 128, 128)");
+ test(
+ "hwb(from rebeccapurple h calc(alpha * 100) w / calc(b / 100))",
+ "rgba(213, 213, 213, 0.4)",
+ );
+ test(
+ "hwb(from rebeccapurple h w w / calc(w / 100))",
+ "rgba(128, 51, 204, 0.2)",
+ );
+ test(
+ "hwb(from rebeccapurple h calc(alpha * 100) calc(alpha * 100) / alpha)",
+ "rgb(128, 128, 128)",
+ );
test("hwb(from rgb(20%, 40%, 60%, 80%) h b w)", "rgb(102, 153, 204)");
test(
- "hwb(from rgb(20%, 40%, 60%, 80%) h alpha w / b)",
+ "hwb(from rgb(20%, 40%, 60%, 80%) h calc(alpha * 100) w / calc(b / 100))",
"rgba(204, 204, 204, 0.4)",
);
- test("hwb(from rgb(20%, 40%, 60%, 80%) h w w / w)", "rgba(51, 128, 204, 0.2)");
test(
- "hwb(from rgb(20%, 40%, 60%, 80%) h alpha alpha / alpha)",
+ "hwb(from rgb(20%, 40%, 60%, 80%) h w w / calc(w / 100))",
+ "rgba(51, 128, 204, 0.2)",
+ );
+ test(
+ "hwb(from rgb(20%, 40%, 60%, 80%) h calc(alpha * 100) calc(alpha * 100) / alpha)",
"rgba(128, 128, 128, 0.8)",
);
@@ -18144,7 +19704,11 @@ mod tests {
// NOTE: 'c' is a valid hue, as hue is |.
test(
&format!("{}(from {}(70% 45 30) alpha c h / l)", color_space, color_space),
- &format!("{}(100% 45 30 / 0.7)", color_space),
+ &format!(
+ "{}(1 45 30 / {})",
+ color_space,
+ if *color_space == "lch" { "1" } else { ".7" }
+ ),
);
test(
&format!("{}(from {}(70% 45 30) l c c / alpha)", color_space, color_space),
@@ -18152,15 +19716,19 @@ mod tests {
);
test(
&format!("{}(from {}(70% 45 30) alpha c h / alpha)", color_space, color_space),
- &format!("{}(100% 45 30)", color_space),
+ &format!("{}(1 45 30)", color_space),
);
test(
&format!("{}(from {}(70% 45 30) alpha c c / alpha)", color_space, color_space),
- &format!("{}(100% 45 45)", color_space),
+ &format!("{}(1 45 45)", color_space),
);
test(
&format!("{}(from {}(70% 45 30 / 40%) alpha c h / l)", color_space, color_space),
- &format!("{}(40% 45 30 / 0.7)", color_space),
+ &format!(
+ "{}(.4 45 30 / {})",
+ color_space,
+ if *color_space == "lch" { "1" } else { ".7" }
+ ),
);
test(
&format!("{}(from {}(70% 45 30 / 40%) l c c / alpha)", color_space, color_space),
@@ -18171,14 +19739,14 @@ mod tests {
"{}(from {}(70% 45 30 / 40%) alpha c h / alpha)",
color_space, color_space
),
- &format!("{}(40% 45 30 / 0.4)", color_space),
+ &format!("{}(.4 45 30 / 0.4)", color_space),
);
test(
&format!(
"{}(from {}(70% 45 30 / 40%) alpha c c / alpha)",
color_space, color_space
),
- &format!("{}(40% 45 45 / 0.4)", color_space),
+ &format!("{}(.4 45 45 / 0.4)", color_space),
);
// Testing with calc().
@@ -19040,13 +20608,10 @@ mod tests {
".foo{color:hsl(from rebeccapurple s h l)}",
".foo{color:hsl(from rebeccapurple s h l)}",
);
+ minify_test(".foo{color:hsl(from rebeccapurple s s s / s)}", ".foo{color:#bfaa40}");
minify_test(
- ".foo{color:hsl(from rebeccapurple s s s / s)}",
- ".foo{color:hsl(from rebeccapurple s s s/s)}",
- );
- minify_test(
- ".foo{color:hsl(from rebeccapurple alpha alpha alpha / alpha)}",
- ".foo{color:hsl(from rebeccapurple alpha alpha alpha/alpha)}",
+ ".foo{color:hsl(from rebeccapurple calc(alpha * 100) calc(alpha * 100) calc(alpha * 100) / alpha)}",
+ ".foo{color:#fff}",
);
}
}
@@ -19149,11 +20714,19 @@ mod tests {
);
minify_test(
".foo { color: color-mix(in srgb, currentColor, blue); }",
- ".foo{color:color-mix(in srgb,currentColor,blue)}",
+ ".foo{color:color-mix(in srgb, currentColor, blue)}",
);
minify_test(
".foo { color: color-mix(in srgb, blue, currentColor); }",
- ".foo{color:color-mix(in srgb,blue,currentColor)}",
+ ".foo{color:color-mix(in srgb, blue, currentColor)}",
+ );
+ minify_test(
+ ".foo { color: color-mix(in srgb, accentcolor, blue); }",
+ ".foo{color:color-mix(in srgb, accentcolor, blue)}",
+ );
+ minify_test(
+ ".foo { color: color-mix(in srgb, blue, accentcolor); }",
+ ".foo{color:color-mix(in srgb, blue, accentcolor)}",
);
// regex for converting web platform tests:
@@ -20521,7 +22094,6 @@ mod tests {
}
}
- #[cfg(feature = "grid")]
#[test]
fn test_grid() {
minify_test(
@@ -20669,40 +22241,248 @@ mod tests {
".foo{grid-template-areas:\"head head\"\"nav main\"\". .\"}",
);
+ // to grid-* shorthand
+ minify_test(
+ r#"
+ .test-miss-areas {
+ grid-template-columns: 1fr 90px;
+ grid-template-rows: auto 80px;
+ grid-template-areas: "one";
+ }
+ "#,
+ ".test-miss-areas{grid-template:\"one\"\".\"80px/1fr 90px}",
+ );
test(
r#"
- .foo {
- grid-template-areas: "head head" "nav main" "foot ....";
+ .test-miss-areas-2 {
+ grid-template-columns: 1fr 1fr 1fr;
+ grid-template-rows: 30px 60px 100px;
+ grid-template-areas: "a a a" "b c c";
}
"#,
indoc! { r#"
- .foo {
- grid-template-areas: "head head"
- "nav main"
- "foot .";
+ .test-miss-areas-2 {
+ grid-template: "a a a" 30px
+ "b c c" 60px
+ ". . ." 100px
+ / 1fr 1fr 1fr;
}
"#},
);
- minify_test(
+ test(
r#"
- .foo {
- grid-template: [header-top] "a a a" [header-bottom]
- [main-top] "b b b" 1fr [main-bottom];
+ .test-miss-areas-3 {
+ grid-template: 30px 60px 100px / 1fr 1fr 1fr;
+ grid-template-areas: "a a a" "b c c";
}
"#,
- ".foo{grid-template:[header-top]\"a a a\"[header-bottom main-top]\"b b b\"1fr[main-bottom]}",
+ indoc! { r#"
+ .test-miss-areas-3 {
+ grid-template: "a a a" 30px
+ "b c c" 60px
+ ". . ." 100px
+ / 1fr 1fr 1fr;
+ }
+ "#},
);
- minify_test(
+
+ test(
r#"
- .foo {
- grid-template: "head head"
- "nav main" 1fr
- "foot ....";
+ .test-miss-areas-4 {
+ grid: 30px 60px 100px / 1fr 1fr 1fr;
+ grid-template-areas: "a a a" "b c c";
}
"#,
- ".foo{grid-template:\"head head\"\"nav main\"1fr\"foot.\"}",
- );
+ indoc! { r#"
+ .test-miss-areas-4 {
+ grid: "a a a" 30px
+ "b c c" 60px
+ ". . ." 100px
+ / 1fr 1fr 1fr;
+ }
+ "#},
+ );
+
+ // test no unreachable error
+ minify_test(
+ r#"
+ .grid-shorthand-areas {
+ grid: auto / 1fr 3fr;
+ grid-template-areas: ". content .";
+ }
+ "#,
+ ".grid-shorthand-areas{grid:\".content.\"/1fr 3fr}",
+ );
+ minify_test(
+ r#"
+ .grid-shorthand-areas-rows {
+ grid: auto / 1fr 3fr;
+ grid-template-rows: 20px;
+ grid-template-areas: ". content .";
+ }
+ "#,
+ ".grid-shorthand-areas-rows{grid:\".content.\"20px/1fr 3fr}",
+ );
+
+ // test grid-auto-flow: row in grid shorthand
+ test(
+ r#"
+ .test-auto-flow-row-1 {
+ grid: auto-flow / 1fr 2fr 1fr;
+ grid-template-areas: " . one . ";
+ }
+ "#,
+ indoc! { r#"
+ .test-auto-flow-row-1 {
+ grid: auto-flow / 1fr 2fr 1fr;
+ grid-template-areas: ". one .";
+ }
+ "#},
+ );
+ test(
+ r#"
+ .test-auto-flow-row-2 {
+ grid: auto-flow auto / 100px 100px;
+ grid-template-areas: " one two ";
+ }
+ "#,
+ indoc! { r#"
+ .test-auto-flow-row-2 {
+ grid: auto-flow / 100px 100px;
+ grid-template-areas: "one two";
+ }
+ "#},
+ );
+ test(
+ r#"
+ .test-auto-flow-dense {
+ grid: dense auto-flow / 1fr 2fr;
+ grid-template-areas: " . content . ";
+ }
+ "#,
+ indoc! { r#"
+ .test-auto-flow-dense {
+ grid: auto-flow dense / 1fr 2fr;
+ grid-template-areas: ". content .";
+ }
+ "#},
+ );
+ minify_test(
+ r#"
+ .grid-auto-flow-row-auto-rows {
+ grid: auto-flow 40px / 1fr 90px;
+ grid-template-areas: "a";
+ }
+ "#,
+ ".grid-auto-flow-row-auto-rows{grid:auto-flow 40px/1fr 90px;grid-template-areas:\"a\"}",
+ );
+ minify_test(
+ r#"
+ .grid-auto-flow-row-auto-rows-multiple {
+ grid: auto-flow 40px max-content / 1fr;
+ grid-template-areas: ". a";
+ }
+ "#,
+ ".grid-auto-flow-row-auto-rows-multiple{grid:auto-flow 40px max-content/1fr;grid-template-areas:\".a\"}",
+ );
+
+ // test grid-auto-flow: column in grid shorthand
+ test(
+ r#"
+ .test-auto-flow-column-1 {
+ grid: 300px / auto-flow;
+ grid-template-areas: " . one . ";
+ }
+ "#,
+ indoc! { r#"
+ .test-auto-flow-column-1 {
+ grid: 300px / auto-flow;
+ grid-template-areas: ". one .";
+ }
+ "#},
+ );
+ test(
+ r#"
+ .test-auto-flow-column-2 {
+ grid: 200px 1fr / auto-flow auto;
+ grid-template-areas: " . one . ";
+ }
+ "#,
+ indoc! { r#"
+ .test-auto-flow-column-2 {
+ grid: 200px 1fr / auto-flow;
+ grid-template-areas: ". one .";
+ }
+ "#},
+ );
+ test(
+ r#"
+ .test-auto-flow-column-dense {
+ grid: 1fr 2fr / dense auto-flow;
+ grid-template-areas: " . content . ";
+ }
+ "#,
+ indoc! { r#"
+ .test-auto-flow-column-dense {
+ grid: 1fr 2fr / auto-flow dense;
+ grid-template-areas: ". content .";
+ }
+ "#},
+ );
+ minify_test(
+ r#"
+ .grid-auto-flow-column-auto-rows {
+ grid: 1fr 3fr / auto-flow 40px;
+ grid-template-areas: "a";
+ }
+ "#,
+ ".grid-auto-flow-column-auto-rows{grid:1fr 3fr/auto-flow 40px;grid-template-areas:\"a\"}",
+ );
+ minify_test(
+ r#"
+ .grid-auto-flow-column-auto-rows-multiple {
+ grid: 1fr / auto-flow 40px max-content ;
+ grid-template-areas: ". a";
+ }
+ "#,
+ ".grid-auto-flow-column-auto-rows-multiple{grid:1fr/auto-flow 40px max-content;grid-template-areas:\".a\"}",
+ );
+
+ test(
+ r#"
+ .foo {
+ grid-template-areas: "head head" "nav main" "foot ....";
+ }
+ "#,
+ indoc! { r#"
+ .foo {
+ grid-template-areas: "head head"
+ "nav main"
+ "foot .";
+ }
+ "#},
+ );
+
+ minify_test(
+ r#"
+ .foo {
+ grid-template: [header-top] "a a a" [header-bottom]
+ [main-top] "b b b" 1fr [main-bottom];
+ }
+ "#,
+ ".foo{grid-template:[header-top]\"a a a\"[header-bottom main-top]\"b b b\"1fr[main-bottom]}",
+ );
+ minify_test(
+ r#"
+ .foo {
+ grid-template: "head head"
+ "nav main" 1fr
+ "foot ....";
+ }
+ "#,
+ ".foo{grid-template:\"head head\"\"nav main\"1fr\"foot.\"}",
+ );
minify_test(
r#"
.foo {
@@ -21348,15 +23128,15 @@ mod tests {
minify_test(".foo { --test: var(--foo, 20px); }", ".foo{--test:var(--foo,20px)}");
minify_test(
".foo { transition: var(--foo, 20px),\nvar(--bar, 40px); }",
- ".foo{transition:var(--foo,20px),var(--bar,40px)}",
+ ".foo{transition:var(--foo,20px), var(--bar,40px)}",
);
minify_test(
".foo { background: var(--color) var(--image); }",
- ".foo{background:var(--color)var(--image)}",
+ ".foo{background:var(--color) var(--image)}",
);
minify_test(
".foo { height: calc(var(--spectrum-global-dimension-size-300) / 2);",
- ".foo{height:calc(var(--spectrum-global-dimension-size-300)/2)}",
+ ".foo{height:calc(var(--spectrum-global-dimension-size-300) / 2)}",
);
minify_test(
".foo { color: var(--color, rgb(255, 255, 0)); }",
@@ -21368,11 +23148,72 @@ mod tests {
);
minify_test(
".foo { color: var(--color, rgb(var(--red), var(--green), 0)); }",
- ".foo{color:var(--color,rgb(var(--red),var(--green),0))}",
+ ".foo{color:var(--color,rgb(var(--red), var(--green), 0))}",
);
minify_test(".foo { --test: .5s; }", ".foo{--test:.5s}");
minify_test(".foo { --theme-sizes-1\\/12: 2 }", ".foo{--theme-sizes-1\\/12:2}");
minify_test(".foo { --test: 0px; }", ".foo{--test:0px}");
+ test(
+ ".foo { transform: var(--bar, ) }",
+ indoc! {r#"
+ .foo {
+ transform: var(--bar, );
+ }
+ "#},
+ );
+ test(
+ ".foo { transform: env(--bar, ) }",
+ indoc! {r#"
+ .foo {
+ transform: env(--bar, );
+ }
+ "#},
+ );
+
+ // Test attr() function with type() syntax - minified
+ minify_test(
+ ".foo { background-color: attr(data-color type()); }",
+ ".foo{background-color:attr(data-color type())}",
+ );
+ minify_test(
+ ".foo { width: attr(data-width type(), 100px); }",
+ ".foo{width:attr(data-width type(), 100px)}",
+ );
+
+ minify_test(".foo { width: attr( data-foo % ); }", ".foo{width:attr(data-foo %)}");
+
+ // = attr( , ? )
+ // Like var(), a bare comma can be used with nothing following it, indicating that the second was passed, just as an empty sequence.
+ // Spec: https://drafts.csswg.org/css-values-5/#funcdef-attr
+ minify_test(
+ ".foo { width: attr( data-foo %, ); }",
+ ".foo{width:attr(data-foo %,)}",
+ );
+
+ minify_test(
+ ".foo { width: attr( data-foo px ); }",
+ ".foo{width:attr(data-foo px)}",
+ );
+
+ minify_test(
+ ".foo { width: attr(data-foo number ); }",
+ ".foo{width:attr(data-foo number)}",
+ );
+
+ minify_test(
+ ".foo { width: attr(data-foo raw-string); }",
+ ".foo{width:attr(data-foo raw-string)}",
+ );
+
+ // Test attr() function with type() syntax - non-minified (issue with extra spaces)
+ test(
+ ".foo { background-color: attr(data-color type()); }",
+ ".foo {\n background-color: attr(data-color type());\n}\n",
+ );
+ test(
+ ".foo { width: attr(data-width type(), 100px); }",
+ ".foo {\n width: attr(data-width type(), 100px);\n}\n",
+ );
prefix_test(
r#"
@@ -21397,6 +23238,27 @@ mod tests {
},
);
+ prefix_test(
+ r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ --custom: lab(40% 56.6 39);
+ }
+ }
+ "#,
+ indoc! {r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ --custom: lab(40% 56.6 39);
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(90 << 16),
+ ..Browsers::default()
+ },
+ );
+
prefix_test(
r#"
.foo {
@@ -21420,6 +23282,27 @@ mod tests {
},
);
+ prefix_test(
+ r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ --custom: lab(40% 56.6 39) !important;
+ }
+ }
+ "#,
+ indoc! {r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ --custom: lab(40% 56.6 39) !important;
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(90 << 16),
+ ..Browsers::default()
+ },
+ );
+
prefix_test(
r#"
.foo {
@@ -21450,6 +23333,40 @@ mod tests {
},
);
+ prefix_test(
+ r#"
+ @supports (color: color(display-p3 0 0 0)) {
+ .foo {
+ --custom: color(display-p3 .643308 .192455 .167712);
+ }
+ }
+
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ --custom: lab(40% 56.6 39);
+ }
+ }
+ "#,
+ indoc! {r#"
+ @supports (color: color(display-p3 0 0 0)) {
+ .foo {
+ --custom: color(display-p3 .643308 .192455 .167712);
+ }
+ }
+
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ --custom: lab(40% 56.6 39);
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(90 << 16),
+ safari: Some(14 << 16),
+ ..Browsers::default()
+ },
+ );
+
prefix_test(
r#"
.foo {
@@ -21618,6 +23535,28 @@ mod tests {
},
);
+ prefix_test(
+ r#"
+ @supports (color: color(display-p3 0 0 0)) {
+ .foo {
+ --foo: color(display-p3 0 1 0);
+ }
+ }
+ "#,
+ indoc! {r#"
+ @supports (color: color(display-p3 0 0 0)) {
+ .foo {
+ --foo: color(display-p3 0 1 0);
+ }
+ }
+ "#},
+ Browsers {
+ safari: Some(14 << 16),
+ chrome: Some(90 << 16),
+ ..Browsers::default()
+ },
+ );
+
prefix_test(
r#"
.foo {
@@ -21784,11 +23723,44 @@ mod tests {
prefix_test(
r#"
- @keyframes foo {
- from {
- --custom: lab(40% 56.6 39);
- }
-
+ @supports (color: lab(0% 0 0)) {
+ @keyframes foo {
+ from {
+ --custom: lab(40% 56.6 39);
+ }
+
+ to {
+ --custom: lab(50.998% 125.506 -50.7078);
+ }
+ }
+ }
+ "#,
+ indoc! {r#"
+ @supports (color: lab(0% 0 0)) {
+ @keyframes foo {
+ from {
+ --custom: lab(40% 56.6 39);
+ }
+
+ to {
+ --custom: lab(50.998% 125.506 -50.7078);
+ }
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(90 << 16),
+ ..Browsers::default()
+ },
+ );
+
+ prefix_test(
+ r#"
+ @keyframes foo {
+ from {
+ --custom: lab(40% 56.6 39);
+ }
+
to {
--custom: lch(50.998% 135.363 338);
}
@@ -21836,6 +23808,64 @@ mod tests {
},
);
+ prefix_test(
+ r#"
+ @supports (color: color(display-p3 0 0 0)) {
+ @keyframes foo {
+ from {
+ --custom: color(display-p3 .643308 .192455 .167712);
+ }
+
+ to {
+ --custom: color(display-p3 .972962 -.362078 .804206);
+ }
+ }
+ }
+
+ @supports (color: lab(0% 0 0)) {
+ @keyframes foo {
+ from {
+ --custom: lab(40% 56.6 39);
+ }
+
+ to {
+ --custom: lab(50.998% 125.506 -50.7078);
+ }
+ }
+ }
+ "#,
+ indoc! {r#"
+ @supports (color: color(display-p3 0 0 0)) {
+ @keyframes foo {
+ from {
+ --custom: color(display-p3 .643308 .192455 .167712);
+ }
+
+ to {
+ --custom: color(display-p3 .972962 -.362078 .804206);
+ }
+ }
+ }
+
+ @supports (color: lab(0% 0 0)) {
+ @keyframes foo {
+ from {
+ --custom: lab(40% 56.6 39);
+ }
+
+ to {
+ --custom: lab(50.998% 125.506 -50.7078);
+ }
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(90 << 16),
+ safari: Some(14 << 16),
+ ..Browsers::default()
+ },
+ );
+
prefix_test(
r#"
@keyframes foo {
@@ -21974,7 +24004,7 @@ mod tests {
);
attr_test(
"text-decoration: var(--foo) lab(40% 56.6 39);",
- "text-decoration:var(--foo)#b32323",
+ "text-decoration:var(--foo) #b32323",
true,
Some(Browsers {
chrome: Some(90 << 16),
@@ -22195,7 +24225,7 @@ mod tests {
grid-auto-flow: column;
}
- @media (min-width: 1024px) {
+ @media not (max-width: 1024px) {
.foo {
max-inline-size: 1024px;
}
@@ -23062,13 +25092,13 @@ mod tests {
}
"#,
indoc! {r#"
- .foo {
- color: red;
- }
-
.foo .bar {
color: #00f;
}
+
+ .foo {
+ color: red;
+ }
"#},
);
@@ -23082,12 +25112,16 @@ mod tests {
"#,
indoc! {r#"
article {
- color: red;
+ color: green;
}
article {
color: #00f;
}
+
+ article {
+ color: red;
+ }
"#},
);
@@ -23196,8 +25230,33 @@ mod tests {
indoc! {r#"
div {
color: #00f;
- --button: focus { color: red; };
+ --button: focus {
+ color: red;
+ };
+ }
+ "#},
+ );
+ nesting_test(
+ r#"
+ .foo {
+ &::before, &::after {
+ background: blue;
+ @media screen {
+ background: orange;
+ }
+ }
+ }
+ "#,
+ indoc! {r#"
+ .foo:before, .foo:after {
+ background: #00f;
+ }
+
+ @media screen {
+ .foo:before, .foo:after {
+ background: orange;
}
+ }
"#},
);
@@ -23372,6 +25431,56 @@ mod tests {
exclude: Features::empty(),
},
);
+
+ minify_test(
+ r#"
+ .foo {
+ color: red;
+ .bar {
+ color: green;
+ }
+ color: blue;
+ .baz {
+ color: pink;
+ }
+ }"#,
+ ".foo{color:red;& .bar{color:green}color:#00f;& .baz{color:pink}}",
+ );
+ }
+
+ #[test]
+ fn test_nesting_error_recovery() {
+ error_recovery_test(
+ "
+ .container {
+ padding: 3rem;
+ @media (max-width: --styled-jsx-placeholder-0__) {
+ .responsive {
+ color: purple;
+ }
+ }
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn test_css_variable_error_recovery() {
+ error_recovery_test("
+ .container {
+ --local-var: --styled-jsx-placeholder-0__;
+ color: var(--text-color);
+ background: linear-gradient(to right, --styled-jsx-placeholder-1__, --styled-jsx-placeholder-2__);
+
+ .item {
+ transform: translate(calc(var(--x) + --styled-jsx-placeholder-3__px), calc(var(--y) + --styled-jsx-placeholder-4__px));
+ }
+
+ div {
+ margin: calc(10px + --styled-jsx-placeholder-5__px);
+ }
+ }
+ ");
}
#[test]
@@ -23466,6 +25575,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23512,6 +25622,7 @@ mod tests {
// custom_idents: false,
..Default::default()
},
+ false,
);
css_modules_test(
@@ -23557,9 +25668,9 @@ mod tests {
custom_idents: false,
..Default::default()
},
+ false,
);
- #[cfg(feature = "grid")]
css_modules_test(
r#"
body {
@@ -23601,9 +25712,9 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
- #[cfg(feature = "grid")]
css_modules_test(
r#"
.grid {
@@ -23639,9 +25750,9 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
- #[cfg(feature = "grid")]
css_modules_test(
r#"
.grid {
@@ -23679,6 +25790,7 @@ mod tests {
grid: false,
..Default::default()
},
+ false,
);
css_modules_test(
@@ -23695,6 +25807,7 @@ mod tests {
map! {},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23729,6 +25842,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
// :global(:local(.hi)) {
@@ -23761,6 +25875,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23790,6 +25905,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23827,6 +25943,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23846,6 +25963,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23865,6 +25983,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23884,6 +26003,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23903,6 +26023,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23933,6 +26054,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23954,6 +26076,7 @@ mod tests {
pattern: crate::css_modules::Pattern::parse("test-[hash]-[local]").unwrap(),
..Default::default()
},
+ false,
);
let stylesheet = StyleSheet::parse(
@@ -24015,6 +26138,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -24084,6 +26208,7 @@ mod tests {
dashed_idents: true,
..Default::default()
},
+ false,
);
css_modules_test(
@@ -24103,6 +26228,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
r#"
@@ -24120,6 +26246,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
r#"
@@ -24137,6 +26264,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
r#"
@@ -24157,6 +26285,7 @@ mod tests {
animation: false,
..Default::default()
},
+ false,
);
css_modules_test(
r#"
@@ -24175,69 +26304,347 @@ mod tests {
},
HashMap::new(),
crate::css_modules::Config { ..Default::default() },
+ false,
);
- // Stable hashes between project roots.
- fn test_project_root(project_root: &str, filename: &str, hash: &str) {
- let stylesheet = StyleSheet::parse(
- r#"
- .foo {
- background: red;
- }
- "#,
- ParserOptions {
- filename: filename.into(),
- css_modules: Some(Default::default()),
- ..ParserOptions::default()
- },
- )
- .unwrap();
- let res = stylesheet
- .to_css(PrinterOptions {
- project_root: Some(project_root),
- ..PrinterOptions::default()
- })
- .unwrap();
- assert_eq!(
- res.code,
- format!(
- indoc! {r#"
- .{}_foo {{
- background: red;
- }}
- "#},
- hash
- )
- );
- }
-
- test_project_root("/foo/bar", "/foo/bar/test.css", "EgL3uq");
- test_project_root("/foo", "/foo/test.css", "EgL3uq");
- test_project_root("/foo/bar", "/foo/bar/baz/test.css", "xLEkNW");
- test_project_root("/foo", "/foo/baz/test.css", "xLEkNW");
- }
-
- #[test]
- fn test_pseudo_replacement() {
- let source = r#"
- .foo:hover {
- color: red;
- }
-
- .foo:active {
- color: yellow;
+ css_modules_test(
+ r#"
+ .test {
+ composes: foo bar from "foo.css";
+ background: white;
}
-
- .foo:focus-visible {
- color: purple;
+ "#,
+ indoc! {r#"
+ ._5h2kwG-test {
+ background: #fff;
}
- "#;
+ "#},
+ map! {
+ "test" => "_5h2kwG-test" "foo" from "foo.css" "bar" from "foo.css"
+ },
+ HashMap::new(),
+ crate::css_modules::Config {
+ pattern: crate::css_modules::Pattern::parse("[content-hash]-[local]").unwrap(),
+ ..Default::default()
+ },
+ false,
+ );
- let expected = indoc! { r#"
- .foo.is-hovered {
- color: red;
+ css_modules_test(
+ r#"
+ .box2 {
+ @container main (width >= 0) {
+ background-color: #90ee90;
+ }
}
-
+ "#,
+ indoc! {r#"
+ .EgL3uq_box2 {
+ @container EgL3uq_main (width >= 0) {
+ background-color: #90ee90;
+ }
+ }
+ "#},
+ map! {
+ "main" => "EgL3uq_main",
+ "box2" => "EgL3uq_box2"
+ },
+ HashMap::new(),
+ crate::css_modules::Config { ..Default::default() },
+ false,
+ );
+
+ css_modules_test(
+ r#"
+ .box2 {
+ @container main (width >= 0) {
+ background-color: #90ee90;
+ }
+ }
+ "#,
+ indoc! {r#"
+ .EgL3uq_box2 {
+ @container main (width >= 0) {
+ background-color: #90ee90;
+ }
+ }
+ "#},
+ map! {
+ "box2" => "EgL3uq_box2"
+ },
+ HashMap::new(),
+ crate::css_modules::Config {
+ container: false,
+ ..Default::default()
+ },
+ false,
+ );
+
+ css_modules_test(
+ ".foo { view-transition-name: bar }",
+ ".EgL3uq_foo{view-transition-name:EgL3uq_bar}",
+ map! {
+ "foo" => "EgL3uq_foo",
+ "bar" => "EgL3uq_bar"
+ },
+ HashMap::new(),
+ Default::default(),
+ true,
+ );
+ css_modules_test(
+ ".foo { view-transition-name: none }",
+ ".EgL3uq_foo{view-transition-name:none}",
+ map! {
+ "foo" => "EgL3uq_foo"
+ },
+ HashMap::new(),
+ Default::default(),
+ true,
+ );
+ css_modules_test(
+ ".foo { view-transition-name: auto }",
+ ".EgL3uq_foo{view-transition-name:auto}",
+ map! {
+ "foo" => "EgL3uq_foo"
+ },
+ HashMap::new(),
+ Default::default(),
+ true,
+ );
+
+ css_modules_test(
+ ".foo { view-transition-class: bar baz qux }",
+ ".EgL3uq_foo{view-transition-class:EgL3uq_bar EgL3uq_baz EgL3uq_qux}",
+ map! {
+ "foo" => "EgL3uq_foo",
+ "bar" => "EgL3uq_bar",
+ "baz" => "EgL3uq_baz",
+ "qux" => "EgL3uq_qux"
+ },
+ HashMap::new(),
+ Default::default(),
+ true,
+ );
+
+ css_modules_test(
+ ".foo { view-transition-group: contain }",
+ ".EgL3uq_foo{view-transition-group:contain}",
+ map! {
+ "foo" => "EgL3uq_foo"
+ },
+ HashMap::new(),
+ Default::default(),
+ true,
+ );
+ css_modules_test(
+ ".foo { view-transition-group: bar }",
+ ".EgL3uq_foo{view-transition-group:EgL3uq_bar}",
+ map! {
+ "foo" => "EgL3uq_foo",
+ "bar" => "EgL3uq_bar"
+ },
+ HashMap::new(),
+ Default::default(),
+ true,
+ );
+
+ css_modules_test(
+ "@view-transition { types: foo bar baz }",
+ "@view-transition{types:EgL3uq_foo EgL3uq_bar EgL3uq_baz}",
+ map! {
+ "foo" => "EgL3uq_foo",
+ "bar" => "EgL3uq_bar",
+ "baz" => "EgL3uq_baz"
+ },
+ HashMap::new(),
+ Default::default(),
+ true,
+ );
+
+ css_modules_test(
+ ":root:active-view-transition-type(foo, bar) { color: red }",
+ ":root:active-view-transition-type(EgL3uq_foo,EgL3uq_bar){color:red}",
+ map! {
+ "foo" => "EgL3uq_foo",
+ "bar" => "EgL3uq_bar"
+ },
+ HashMap::new(),
+ Default::default(),
+ true,
+ );
+
+ for name in &[
+ "view-transition-group",
+ "view-transition-image-pair",
+ "view-transition-new",
+ "view-transition-old",
+ ] {
+ css_modules_test(
+ &format!(":root::{}(foo) {{position: fixed}}", name),
+ &format!(":root::{}(EgL3uq_foo){{position:fixed}}", name),
+ map! {
+ "foo" => "EgL3uq_foo"
+ },
+ HashMap::new(),
+ Default::default(),
+ true,
+ );
+ css_modules_test(
+ &format!(":root::{}(.bar) {{position: fixed}}", name),
+ &format!(":root::{}(.EgL3uq_bar){{position:fixed}}", name),
+ map! {
+ "bar" => "EgL3uq_bar"
+ },
+ HashMap::new(),
+ Default::default(),
+ true,
+ );
+ css_modules_test(
+ &format!(":root::{}(foo.bar.baz) {{position: fixed}}", name),
+ &format!(":root::{}(EgL3uq_foo.EgL3uq_bar.EgL3uq_baz){{position:fixed}}", name),
+ map! {
+ "foo" => "EgL3uq_foo",
+ "bar" => "EgL3uq_bar",
+ "baz" => "EgL3uq_baz"
+ },
+ HashMap::new(),
+ Default::default(),
+ true,
+ );
+
+ css_modules_test(
+ ":nth-child(1 of .foo) {width: 20px}",
+ ":nth-child(1 of .EgL3uq_foo){width:20px}",
+ map! {
+ "foo" => "EgL3uq_foo"
+ },
+ HashMap::new(),
+ Default::default(),
+ true,
+ );
+ css_modules_test(
+ ":nth-last-child(1 of .foo) {width: 20px}",
+ ":nth-last-child(1 of .EgL3uq_foo){width:20px}",
+ map! {
+ "foo" => "EgL3uq_foo"
+ },
+ HashMap::new(),
+ Default::default(),
+ true,
+ );
+ }
+
+ // Stable hashes between project roots.
+ fn test_project_root(project_root: &str, filename: &str, hash: &str) {
+ let stylesheet = StyleSheet::parse(
+ r#"
+ .foo {
+ background: red;
+ }
+ "#,
+ ParserOptions {
+ filename: filename.into(),
+ css_modules: Some(Default::default()),
+ ..ParserOptions::default()
+ },
+ )
+ .unwrap();
+ let res = stylesheet
+ .to_css(PrinterOptions {
+ project_root: Some(project_root),
+ ..PrinterOptions::default()
+ })
+ .unwrap();
+ assert_eq!(
+ res.code,
+ format!(
+ indoc! {r#"
+ .{}_foo {{
+ background: red;
+ }}
+ "#},
+ hash
+ )
+ );
+ }
+
+ test_project_root("/foo/bar", "/foo/bar/test.css", "EgL3uq");
+ test_project_root("/foo", "/foo/test.css", "EgL3uq");
+ test_project_root("/foo/bar", "/foo/bar/baz/test.css", "xLEkNW");
+ test_project_root("/foo", "/foo/baz/test.css", "xLEkNW");
+
+ let mut stylesheet = StyleSheet::parse(
+ r#"
+ .foo {
+ color: red;
+ .bar {
+ color: green;
+ }
+ composes: test from "foo.css";
+ }
+ "#,
+ ParserOptions {
+ filename: "test.css".into(),
+ css_modules: Some(Default::default()),
+ ..ParserOptions::default()
+ },
+ )
+ .unwrap();
+ stylesheet.minify(MinifyOptions::default()).unwrap();
+ let res = stylesheet
+ .to_css(PrinterOptions {
+ targets: Browsers {
+ chrome: Some(95 << 16),
+ ..Browsers::default()
+ }
+ .into(),
+ ..Default::default()
+ })
+ .unwrap();
+ assert_eq!(
+ res.code,
+ indoc! {r#"
+ .EgL3uq_foo {
+ color: red;
+ }
+
+ .EgL3uq_foo .EgL3uq_bar {
+ color: green;
+ }
+
+
+ "#}
+ );
+ assert_eq!(
+ res.exports.unwrap(),
+ map! {
+ "foo" => "EgL3uq_foo" "test" from "foo.css",
+ "bar" => "EgL3uq_bar"
+ }
+ );
+ }
+
+ #[test]
+ fn test_pseudo_replacement() {
+ let source = r#"
+ .foo:hover {
+ color: red;
+ }
+
+ .foo:active {
+ color: yellow;
+ }
+
+ .foo:focus-visible {
+ color: purple;
+ }
+ "#;
+
+ let expected = indoc! { r#"
+ .foo.is-hovered {
+ color: red;
+ }
+
.foo.is-active {
color: #ff0;
}
@@ -24498,6 +26905,8 @@ mod tests {
#[test]
fn test_svg() {
+ use crate::properties::svg;
+
minify_test(".foo { fill: yellow; }", ".foo{fill:#ff0}");
minify_test(".foo { fill: url(#foo); }", ".foo{fill:url(#foo)}");
minify_test(".foo { fill: url(#foo) none; }", ".foo{fill:url(#foo) none}");
@@ -24783,12 +27192,33 @@ mod tests {
},
);
+ prefix_test(
+ r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ fill: var(--url) lab(50.998% 125.506 -50.7078);
+ }
+ }
+ "#,
+ indoc! { r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ fill: var(--url) lab(50.998% 125.506 -50.7078);
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(90 << 16),
+ ..Browsers::default()
+ },
+ );
+
prefix_test(
".foo { mask-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }",
indoc! { r#"
.foo {
-webkit-mask-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff));
- -webkit-mask-image: -webkit-linear-gradient(#ff0f0e, #7773ff);
+ -webkit-mask-image: -webkit-linear-gradient(top, #ff0f0e, #7773ff);
-webkit-mask-image: linear-gradient(#ff0f0e, #7773ff);
mask-image: linear-gradient(#ff0f0e, #7773ff);
-webkit-mask-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364));
@@ -24850,7 +27280,7 @@ mod tests {
indoc! { r#"
.foo {
-webkit-mask: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff)) 40px 20px;
- -webkit-mask: -webkit-linear-gradient(#ff0f0e, #7773ff) 40px 20px;
+ -webkit-mask: -webkit-linear-gradient(top, #ff0f0e, #7773ff) 40px 20px;
-webkit-mask: linear-gradient(#ff0f0e, #7773ff) 40px 20px;
mask: linear-gradient(#ff0f0e, #7773ff) 40px 20px;
-webkit-mask: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 40px 20px;
@@ -24899,9 +27329,31 @@ mod tests {
);
prefix_test(
- ".foo { mask: url(masks.svg#star) luminance }",
+ r#"
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ mask: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) 40px var(--foo);
+ }
+ }
+ "#,
indoc! { r#"
- .foo {
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ -webkit-mask: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) 40px var(--foo);
+ mask: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) 40px var(--foo);
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(90 << 16),
+ ..Browsers::default()
+ },
+ );
+
+ prefix_test(
+ ".foo { mask: url(masks.svg#star) luminance }",
+ indoc! { r#"
+ .foo {
-webkit-mask: url("masks.svg#star");
-webkit-mask-source-type: luminance;
mask: url("masks.svg#star") luminance;
@@ -25283,6 +27735,21 @@ mod tests {
..Browsers::default()
},
);
+
+ let property =
+ Property::parse_string("text-rendering".into(), "geometricPrecision", ParserOptions::default()).unwrap();
+ assert_eq!(
+ property,
+ Property::TextRendering(svg::TextRendering::GeometricPrecision)
+ );
+ let property =
+ Property::parse_string("shape-rendering".into(), "geometricPrecision", ParserOptions::default()).unwrap();
+ assert_eq!(
+ property,
+ Property::ShapeRendering(svg::ShapeRendering::GeometricPrecision)
+ );
+ let property = Property::parse_string("color-interpolation".into(), "sRGB", ParserOptions::default()).unwrap();
+ assert_eq!(property, Property::ColorInterpolation(svg::ColorInterpolation::SRGB));
}
#[test]
@@ -25454,6 +27921,82 @@ mod tests {
);
}
+ #[test]
+ fn test_mix_blend_mode() {
+ minify_test(
+ ".foo { mix-blend-mode: normal }",
+ ".foo{mix-blend-mode:normal}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: multiply }",
+ ".foo{mix-blend-mode:multiply}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: screen }",
+ ".foo{mix-blend-mode:screen}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: overlay }",
+ ".foo{mix-blend-mode:overlay}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: darken }",
+ ".foo{mix-blend-mode:darken}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: lighten }",
+ ".foo{mix-blend-mode:lighten}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: color-dodge }",
+ ".foo{mix-blend-mode:color-dodge}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: color-burn }",
+ ".foo{mix-blend-mode:color-burn}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: hard-light }",
+ ".foo{mix-blend-mode:hard-light}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: soft-light }",
+ ".foo{mix-blend-mode:soft-light}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: difference }",
+ ".foo{mix-blend-mode:difference}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: exclusion }",
+ ".foo{mix-blend-mode:exclusion}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: hue }",
+ ".foo{mix-blend-mode:hue}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: saturation }",
+ ".foo{mix-blend-mode:saturation}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: color }",
+ ".foo{mix-blend-mode:color}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: luminosity }",
+ ".foo{mix-blend-mode:luminosity}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: plus-darker }",
+ ".foo{mix-blend-mode:plus-darker}",
+ );
+ minify_test(
+ ".foo { mix-blend-mode: plus-lighter }",
+ ".foo{mix-blend-mode:plus-lighter}",
+ );
+ }
+
#[test]
fn test_viewport() {
minify_test(
@@ -25544,7 +28087,7 @@ mod tests {
}
}
"#,
- ".foo{@scope(.bar){&{color:#ff0}}}",
+ ".foo{@scope(.bar){color:#ff0}}",
);
nesting_test(
r#"
@@ -25556,9 +28099,7 @@ mod tests {
"#,
indoc! {r#"
@scope (.bar) {
- :scope {
- color: #ff0;
- }
+ color: #ff0;
}
"#},
);
@@ -25948,7 +28489,7 @@ mod tests {
}
"#,
indoc! {r#"
- @media screen and ((prefers-color-scheme: dark) or (not (width >= 300px))) {
+ @media screen and ((prefers-color-scheme: dark) or ((width < 300px))) {
.foo {
order: 6;
}
@@ -26698,6 +29239,17 @@ mod tests {
"@property --property-name{syntax:\"\";inherits:true;initial-value:25px}",
);
+ minify_test(
+ r#"
+ @property --property-name {
+ syntax: '';
+ inherits: true;
+ initial-value: "hi";
+ }
+ "#,
+ "@property --property-name{syntax:\"\";inherits:true;initial-value:\"hi\"}",
+ );
+
error_test(
r#"
@property --property-name {
@@ -26893,6 +29445,44 @@ mod tests {
"#,
"@property --property-name{syntax:\"\";inherits:true;initial-value:#00f}.foo{color:var(--property-name)}",
);
+
+ test(
+ r#"
+ @media (width < 800px) {
+ @property --property-name {
+ syntax: '*';
+ inherits: false;
+ }
+ }
+ "#,
+ indoc! {r#"
+ @media (width < 800px) {
+ @property --property-name {
+ syntax: "*";
+ inherits: false
+ }
+ }
+ "#},
+ );
+
+ test(
+ r#"
+ @layer foo {
+ @property --property-name {
+ syntax: '*';
+ inherits: false;
+ }
+ }
+ "#,
+ indoc! {r#"
+ @layer foo {
+ @property --property-name {
+ syntax: "*";
+ inherits: false
+ }
+ }
+ "#},
+ );
}
#[test]
@@ -26980,6 +29570,52 @@ mod tests {
);
}
+ #[test]
+ #[cfg(feature = "sourcemap")]
+ fn test_source_maps_with_license_comments() {
+ let source = r#"/*! a single line comment */
+ /*!
+ a comment
+ containing
+ multiple
+ lines
+ */
+ .a {
+ display: flex;
+ }
+
+ .b {
+ display: hidden;
+ }
+ "#;
+
+ let mut sm = parcel_sourcemap::SourceMap::new("/");
+ let source_index = sm.add_source("input.css");
+ sm.set_source_content(source_index as usize, source).unwrap();
+
+ let mut stylesheet = StyleSheet::parse(
+ &source,
+ ParserOptions {
+ source_index,
+ ..Default::default()
+ },
+ )
+ .unwrap();
+ stylesheet.minify(MinifyOptions::default()).unwrap();
+ stylesheet
+ .to_css(PrinterOptions {
+ source_map: Some(&mut sm),
+ minify: true,
+ ..PrinterOptions::default()
+ })
+ .unwrap();
+ let map = sm.to_json(None).unwrap();
+ assert_eq!(
+ map,
+ r#"{"version":3,"sourceRoot":null,"mappings":";;;;;;;AAOI,gBAIA","sources":["input.css"],"sourcesContent":["/*! a single line comment */\n /*!\n a comment\n containing\n multiple\n lines\n */\n .a {\n display: flex;\n }\n\n .b {\n display: hidden;\n }\n "],"names":[]}"#
+ );
+ }
+
#[test]
fn test_error_recovery() {
use std::sync::{Arc, RwLock};
@@ -27008,6 +29644,14 @@ mod tests {
color: red;
}
}
+
+ input:placeholder {
+ color: red;
+ }
+
+ input::hover {
+ color: red;
+ }
"#,
indoc! { r#"
.foo {
@@ -27023,6 +29667,14 @@ mod tests {
color: red;
}
}
+
+ input:placeholder {
+ color: red;
+ }
+
+ input::hover {
+ color: red;
+ }
"#},
ParserOptions {
filename: "test.css".into(),
@@ -27060,6 +29712,22 @@ mod tests {
column: 9
})
},
+ Error {
+ kind: ParserError::SelectorError(SelectorError::UnsupportedPseudoClass("placeholder".into())),
+ loc: Some(ErrorLocation {
+ filename: "test.css".into(),
+ line: 24,
+ column: 13,
+ }),
+ },
+ Error {
+ kind: ParserError::SelectorError(SelectorError::UnsupportedPseudoElement("hover".into())),
+ loc: Some(ErrorLocation {
+ filename: "test.css".into(),
+ line: 28,
+ column: 13,
+ }),
+ },
]
)
}
@@ -27078,6 +29746,18 @@ mod tests {
#[test]
fn test_container_queries() {
+ // name only (no condition) - new syntax
+ minify_test(
+ r#"
+ @container foo {
+ .inner {
+ background: green;
+ }
+ }
+ "#,
+ "@container foo{.inner{background:green}}",
+ );
+
// with name
minify_test(
r#"
@@ -27388,6 +30068,76 @@ mod tests {
"#,
"@container style(--my-prop:foo - bar ()){.foo{color:red}}",
);
+ minify_test(
+ r#"
+ @container style(--test) {
+ .foo {
+ color: red;
+ }
+ }
+ "#,
+ "@container style(--test){.foo{color:red}}",
+ );
+ minify_test(
+ r#"
+ @container style(width) {
+ .foo {
+ color: red;
+ }
+ }
+ "#,
+ "@container style(width){.foo{color:red}}",
+ );
+ minify_test(
+ r#"
+ @container scroll-state(scrollable: top) {
+ .foo {
+ color: red;
+ }
+ }
+ "#,
+ "@container scroll-state(scrollable:top){.foo{color:red}}",
+ );
+ minify_test(
+ r#"
+ @container scroll-state((stuck: top) and (stuck: left)) {
+ .foo {
+ color: red;
+ }
+ }
+ "#,
+ "@container scroll-state((stuck:top) and (stuck:left)){.foo{color:red}}",
+ );
+ minify_test(
+ r#"
+ @container scroll-state(not ((scrollable: bottom) and (scrollable: right))) {
+ .foo {
+ color: red;
+ }
+ }
+ "#,
+ "@container scroll-state(not ((scrollable:bottom) and (scrollable:right))){.foo{color:red}}",
+ );
+ minify_test(
+ r#"
+ @container (scroll-state(scrollable: inline-end)) {
+ .foo {
+ color: red;
+ }
+ }
+ "#,
+ "@container scroll-state(scrollable:inline-end){.foo{color:red}}",
+ );
+ minify_test(
+ r#"
+ @container not scroll-state(scrollable: top) {
+ .foo {
+ color: red;
+ }
+ }
+ "#,
+ "@container not scroll-state(scrollable:top){.foo{color:red}}",
+ );
// Disallow 'none', 'not', 'and', 'or' as a ``
// https://github.com/w3c/csswg-drafts/issues/7203#issuecomment-1144257312
@@ -27428,11 +30178,41 @@ mod tests {
error_test("@container (inline-size <= foo) {}", ParserError::InvalidMediaQuery);
error_test("@container (orientation <= 10px) {}", ParserError::InvalidMediaQuery);
- error_test("@container style(width) {}", ParserError::EndOfInput);
error_test(
"@container style(style(--foo: bar)) {}",
ParserError::UnexpectedToken(crate::properties::custom::Token::Function("style".into())),
);
+ error_test(
+ "@container scroll-state(scroll-state(scrollable: top)) {}",
+ ParserError::InvalidMediaQuery,
+ );
+ error_test(
+ "@container unknown(foo) {}",
+ ParserError::UnexpectedToken(crate::properties::custom::Token::Function("unknown".into())),
+ );
+
+ // empty container (no name and no condition) should error
+ error_test("@container {}", ParserError::EndOfInput);
+
+ // empty brackets should return a clearer error message
+ error_test("@container () {}", ParserError::EmptyBracketInCondition);
+
+ // invalid condition after a name should error
+ error_test("@container foo () {}", ParserError::EmptyBracketInCondition);
+ error_test(
+ "@container foo bar {}",
+ ParserError::UnexpectedToken(crate::properties::custom::Token::Ident("bar".into())),
+ );
+
+ error_recovery_test("@container unknown(foo) {}");
+ }
+
+ #[test]
+ fn test_css_modules_value_rule() {
+ css_modules_error_test(
+ "@value compact: (max-width: 37.4375em);",
+ ParserError::DeprecatedCssModulesValueRule,
+ );
}
#[test]
@@ -27446,11 +30226,12 @@ mod tests {
color: red;
}
}"#,
- indoc! {r#"
- @foo test {
- div { color: red; }
- }
- "#},
+ indoc! { r#"@foo test {
+ div {
+ color: red;
+ }
+ }
+ "#},
);
minify_test(
r#"@foo test {
@@ -27610,6 +30391,28 @@ mod tests {
},
);
+ prefix_test(
+ r#"
+ @supports (color: color(display-p3 0 0 0)) {
+ .foo {
+ color: env(--brand-color, color(display-p3 0 1 0));
+ }
+ }
+ "#,
+ indoc! {r#"
+ @supports (color: color(display-p3 0 0 0)) {
+ .foo {
+ color: env(--brand-color, color(display-p3 0 1 0));
+ }
+ }
+ "#},
+ Browsers {
+ safari: Some(15 << 16),
+ chrome: Some(90 << 16),
+ ..Browsers::default()
+ },
+ );
+
css_modules_test(
r#"
@media (max-width: env(--branding-small)) {
@@ -27635,6 +30438,7 @@ mod tests {
dashed_idents: true,
..Default::default()
},
+ false,
);
}
@@ -27714,14 +30518,20 @@ mod tests {
minify_test(".foo { color-scheme: dark light; }", ".foo{color-scheme:light dark}");
minify_test(".foo { color-scheme: only light; }", ".foo{color-scheme:light only}");
minify_test(".foo { color-scheme: only dark; }", ".foo{color-scheme:dark only}");
+ minify_test(".foo { color-scheme: inherit; }", ".foo{color-scheme:inherit}");
+ minify_test(":root { color-scheme: unset; }", ":root{color-scheme:unset}");
+ minify_test(".foo { color-scheme: unknow; }", ".foo{color-scheme:unknow}");
+ minify_test(".foo { color-scheme: only; }", ".foo{color-scheme:only}");
+ minify_test(".foo { color-scheme: dark foo; }", ".foo{color-scheme:dark foo}");
+ minify_test(".foo { color-scheme: normal dark; }", ".foo{color-scheme:normal dark}");
minify_test(
".foo { color-scheme: dark light only; }",
".foo{color-scheme:light dark only}",
);
- minify_test(".foo { color-scheme: foo bar light; }", ".foo{color-scheme:light}");
+ minify_test(".foo { color-scheme: foo bar light; }", ".foo{color-scheme:foo bar light}");
minify_test(
".foo { color-scheme: only foo dark bar; }",
- ".foo{color-scheme:dark only}",
+ ".foo{color-scheme:only foo dark bar}",
);
prefix_test(
".foo { color-scheme: dark; }",
@@ -27841,6 +30651,25 @@ 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 color(display-p3 1 1 1 / .5), 0 .44rem .8rem -.58rem;
+ box-shadow: 0 .63rem .94rem -.19rem lab(100% 0 0 / .5), 0 .44rem .8rem -.58rem;
+ }
+ "#},
+ Browsers {
+ safari: Some(14 << 16),
+ ..Browsers::default()
+ },
+ );
prefix_test(
".foo { color: light-dark(var(--light), var(--dark)); }",
@@ -27903,6 +30732,58 @@ mod tests {
..Browsers::default()
},
);
+ nesting_test_with_targets(
+ r#"
+ .foo { color-scheme: light; }
+ .bar { color: light-dark(red, green); }
+ "#,
+ indoc! {r#"
+ .foo {
+ color-scheme: light;
+ }
+
+ .bar {
+ color: light-dark(red, green);
+ }
+ "#},
+ Targets {
+ browsers: Some(Browsers {
+ safari: Some(13 << 16),
+ ..Browsers::default()
+ }),
+ include: Features::empty(),
+ exclude: Features::LightDark,
+ },
+ );
+ }
+
+ #[test]
+ fn test_print_color_adjust() {
+ prefix_test(
+ ".foo { print-color-adjust: exact; }",
+ indoc! { r#"
+ .foo {
+ -webkit-print-color-adjust: exact;
+ print-color-adjust: exact;
+ }
+ "#},
+ Browsers {
+ chrome: Some(135 << 16),
+ ..Browsers::default()
+ },
+ );
+ prefix_test(
+ ".foo { print-color-adjust: exact; }",
+ indoc! { r#"
+ .foo {
+ print-color-adjust: exact;
+ }
+ "#},
+ Browsers {
+ chrome: Some(137 << 16),
+ ..Browsers::default()
+ },
+ );
}
#[test]
@@ -27935,5 +30816,187 @@ mod tests {
".foo { all: unset; background: var(--foo); }",
".foo{all:unset;background:var(--foo)}",
);
+ minify_test(
+ ".foo {--bar:currentcolor; --foo:1.1em; all:unset}",
+ ".foo{--bar:currentcolor;--foo:1.1em;all:unset}",
+ );
+ }
+
+ #[test]
+ fn test_view_transition() {
+ minify_test(
+ "@view-transition { navigation: auto }",
+ "@view-transition{navigation:auto}",
+ );
+ minify_test(
+ "@view-transition { navigation: auto; types: none; }",
+ "@view-transition{navigation:auto;types:none}",
+ );
+ minify_test(
+ "@view-transition { navigation: auto; types: foo bar; }",
+ "@view-transition{navigation:auto;types:foo bar}",
+ );
+ minify_test(
+ "@layer { @view-transition { navigation: auto; types: foo bar; } }",
+ "@layer{@view-transition{navigation:auto;types:foo bar}}",
+ );
+ }
+
+ #[test]
+ fn test_skip_generating_unnecessary_fallbacks() {
+ prefix_test(
+ r#"
+ @supports (color: lab(0% 0 0)) and (color: color(display-p3 0 0 0)) {
+ .foo {
+ color: lab(40% 56.6 39);
+ }
+
+ .bar {
+ color: color(display-p3 .643308 .192455 .167712);
+ }
+ }
+ "#,
+ indoc! {r#"
+ @supports (color: lab(0% 0 0)) and (color: color(display-p3 0 0 0)) {
+ .foo {
+ color: lab(40% 56.6 39);
+ }
+
+ .bar {
+ color: color(display-p3 .643308 .192455 .167712);
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(4 << 16),
+ ..Browsers::default()
+ },
+ );
+
+ prefix_test(
+ r#"
+ @supports (color: lab(40% 56.6 39)) {
+ .foo {
+ color: lab(40% 56.6 39);
+ }
+ }
+ "#,
+ indoc! {r#"
+ @supports (color: lab(40% 56.6 39)) {
+ .foo {
+ color: lab(40% 56.6 39);
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(4 << 16),
+ ..Browsers::default()
+ },
+ );
+
+ prefix_test(
+ r#"
+ @supports (background-color: lab(40% 56.6 39)) {
+ .foo {
+ background-color: lab(40% 56.6 39);
+ }
+ }
+ "#,
+ indoc! {r#"
+ @supports (background-color: lab(40% 56.6 39)) {
+ .foo {
+ background-color: lab(40% 56.6 39);
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(4 << 16),
+ ..Browsers::default()
+ },
+ );
+
+ prefix_test(
+ r#"
+ @supports (color: light-dark(#f00, #00f)) {
+ .foo {
+ color: light-dark(#ff0, #0ff);
+ }
+ }
+ "#,
+ indoc! {r#"
+ @supports (color: light-dark(#f00, #00f)) {
+ .foo {
+ color: light-dark(#ff0, #0ff);
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(4 << 16),
+ ..Browsers::default()
+ },
+ );
+
+ // NOTE: fallback for lab is not necessary
+ prefix_test(
+ r#"
+ @supports (color: lab(0% 0 0)) and (not (color: color(display-p3 0 0 0))) {
+ .foo {
+ color: lab(40% 56.6 39);
+ }
+
+ .bar {
+ color: color(display-p3 .643308 .192455 .167712);
+ }
+ }
+ "#,
+ indoc! {r#"
+ @supports (color: lab(0% 0 0)) and (not (color: color(display-p3 0 0 0))) {
+ .foo {
+ color: #b32323;
+ color: lab(40% 56.6 39);
+ }
+
+ .bar {
+ color: #b32323;
+ color: color(display-p3 .643308 .192455 .167712);
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(4 << 16),
+ ..Browsers::default()
+ },
+ );
+
+ prefix_test(
+ r#"
+ @supports (color: lab(0% 0 0)) or (color: color(display-p3 0 0 0)) {
+ .foo {
+ color: lab(40% 56.6 39);
+ }
+
+ .bar {
+ color: color(display-p3 .643308 .192455 .167712);
+ }
+ }
+ "#,
+ indoc! {r#"
+ @supports (color: lab(0% 0 0)) or (color: color(display-p3 0 0 0)) {
+ .foo {
+ color: #b32323;
+ color: lab(40% 56.6 39);
+ }
+
+ .bar {
+ color: #b32323;
+ color: color(display-p3 .643308 .192455 .167712);
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(4 << 16),
+ ..Browsers::default()
+ },
+ );
}
}
diff --git a/src/macros.rs b/src/macros.rs
index 8019c58cb..a50d54f5a 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -189,7 +189,7 @@ macro_rules! shorthand_property_bitflags {
crate::macros::shorthand_property_bitflags!($name, [$($all),*] $($rest),* ; $last_index + 1; $($var = $index)* $cur = $last_index + 1);
};
($name:ident, [$($all:ident),*] $cur:ident; $last_index:expr ; $($var:ident = $index:expr)+) => {
- paste::paste! {
+ pastey::paste! {
crate::macros::property_bitflags! {
#[derive(Default, Debug)]
struct [<$name Property>]: u8 {
@@ -216,7 +216,7 @@ macro_rules! shorthand_handler {
$(
pub $key: Option<$type>,
)*
- flushed_properties: paste::paste!([<$shorthand Property>]),
+ flushed_properties: pastey::paste!([<$shorthand Property>]),
has_any: bool
}
@@ -250,7 +250,7 @@ macro_rules! shorthand_handler {
let mut unparsed = val.clone();
context.add_unparsed_fallbacks(&mut unparsed);
- paste::paste! {
+ pastey::paste! {
self.flushed_properties.insert([<$shorthand Property>]::try_from(&unparsed.property_id).unwrap());
};
dest.push(Property::Unparsed(unparsed));
@@ -263,7 +263,7 @@ macro_rules! shorthand_handler {
fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
self.flush(dest, context);
- self.flushed_properties = paste::paste!([<$shorthand Property>]::empty());
+ self.flushed_properties = pastey::paste!([<$shorthand Property>]::empty());
}
}
@@ -289,7 +289,7 @@ macro_rules! shorthand_handler {
};
$(
- if $shorthand_fallback && !self.flushed_properties.intersects(paste::paste!([<$shorthand Property>]::$shorthand)) {
+ if $shorthand_fallback && !self.flushed_properties.intersects(pastey::paste!([<$shorthand Property>]::$shorthand)) {
let fallbacks = shorthand.get_fallbacks(context.targets);
for fallback in fallbacks {
dest.push(Property::$shorthand(fallback));
@@ -298,7 +298,7 @@ macro_rules! shorthand_handler {
)?
dest.push(Property::$shorthand(shorthand));
- paste::paste! {
+ pastey::paste! {
self.flushed_properties.insert([<$shorthand Property>]::$shorthand);
};
} else {
@@ -306,7 +306,7 @@ macro_rules! shorthand_handler {
#[allow(unused_mut)]
if let Some(mut val) = $key {
$(
- if $fallback && !self.flushed_properties.intersects(paste::paste!([<$shorthand Property>]::$prop)) {
+ if $fallback && !self.flushed_properties.intersects(pastey::paste!([<$shorthand Property>]::$prop)) {
let fallbacks = val.get_fallbacks(context.targets);
for fallback in fallbacks {
dest.push(Property::$prop(fallback));
@@ -315,7 +315,7 @@ macro_rules! shorthand_handler {
)?
dest.push(Property::$prop(val));
- paste::paste! {
+ pastey::paste! {
self.flushed_properties.insert([<$shorthand Property>]::$prop);
};
}
@@ -390,7 +390,7 @@ macro_rules! impl_shorthand {
impl<'i> Shorthand<'i> for $t {
#[allow(unused_variables)]
fn from_longhands(decls: &DeclarationBlock<'i>, vendor_prefix: crate::vendor_prefix::VendorPrefix) -> Option<(Self, bool)> {
- use paste::paste;
+ use pastey::paste;
$(
$(
diff --git a/src/media_query.rs b/src/media_query.rs
index 63c0b2364..2f0ee9de6 100644
--- a/src/media_query.rs
+++ b/src/media_query.rs
@@ -3,14 +3,14 @@ use crate::error::{ErrorWithLocation, MinifyError, MinifyErrorKind, ParserError,
use crate::macros::enum_property;
use crate::parser::starts_with_ignore_ascii_case;
use crate::printer::Printer;
-use crate::properties::custom::EnvironmentVariable;
+use crate::properties::custom::{EnvironmentVariable, TokenList};
#[cfg(feature = "visitor")]
-use crate::rules::container::ContainerSizeFeatureId;
+use crate::rules::container::{ContainerSizeFeatureId, ScrollStateFeatureId};
use crate::rules::custom_media::CustomMediaRule;
use crate::rules::Location;
use crate::stylesheet::ParserOptions;
use crate::targets::{should_compile, Targets};
-use crate::traits::{Parse, ToCss};
+use crate::traits::{Parse, ParseWithOptions, ToCss};
use crate::values::ident::{DashedIdent, Ident};
use crate::values::number::{CSSInteger, CSSNumber};
use crate::values::string::CowArcStr;
@@ -51,10 +51,17 @@ impl<'i> MediaList<'i> {
}
/// Parse a media query list from CSS.
- pub fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
+ pub fn parse<'t>(
+ input: &mut Parser<'i, 't>,
+ options: &ParserOptions<'_, 'i>,
+ ) -> Result<'i, ParserError<'i>>> {
let mut media_queries = vec![];
+ if input.is_exhausted() {
+ return Ok(MediaList { media_queries });
+ }
+
loop {
- match input.parse_until_before(Delimiter::Comma, |i| MediaQuery::parse(i)) {
+ match input.parse_until_before(Delimiter::Comma, |i| MediaQuery::parse_with_options(i, options)) {
Ok(mq) => {
media_queries.push(mq);
}
@@ -269,8 +276,11 @@ pub struct MediaQuery<'i> {
pub condition: Option<'i>>,
}
-impl<'i> Parse<'i> for MediaQuery<'i> {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
+impl<'i> ParseWithOptions<'i> for MediaQuery<'i> {
+ fn parse_with_options<'t>(
+ input: &mut Parser<'i, 't>,
+ options: &ParserOptions<'_, 'i>,
+ ) -> Result<'i, ParserError<'i>>> {
let (qualifier, explicit_media_type) = input
.try_parse(|input| -> Result<_, ParseError<'i, ParserError<'i>>> {
let qualifier = input.try_parse(Qualifier::parse).ok();
@@ -280,9 +290,17 @@ impl<'i> Parse<'i> for MediaQuery<'i> {
.unwrap_or_default();
let condition = if explicit_media_type.is_none() {
- Some(MediaCondition::parse_with_flags(input, QueryConditionFlags::ALLOW_OR)?)
+ Some(MediaCondition::parse_with_flags(
+ input,
+ QueryConditionFlags::ALLOW_OR,
+ options,
+ )?)
} else if input.try_parse(|i| i.expect_ident_matching("and")).is_ok() {
- Some(MediaCondition::parse_with_flags(input, QueryConditionFlags::empty())?)
+ Some(MediaCondition::parse_with_flags(
+ input,
+ QueryConditionFlags::empty(),
+ options,
+ )?)
} else {
None
};
@@ -476,8 +494,8 @@ impl<'i, 'de: 'i> serde::Deserialize<'de> for MediaQuery<'i> {
condition,
}),
MediaQueryOrRaw::Raw { raw } => {
- let res =
- MediaQuery::parse_string(raw.as_ref()).map_err(|_| serde::de::Error::custom("Could not parse value"))?;
+ let res = MediaQuery::parse_string_with_options(raw.as_ref(), ParserOptions::default())
+ .map_err(|_| serde::de::Error::custom("Could not parse value"))?;
Ok(res.into_owned())
}
}
@@ -520,14 +538,30 @@ pub enum MediaCondition<'i> {
/// The conditions for the operator.
conditions: Vec<'i>>,
},
+ /// Unknown tokens.
+ #[cfg_attr(feature = "serde", serde(borrow, with = "ValueWrapper::"))]
+ Unknown(TokenList<'i>),
}
/// A trait for conditions such as media queries and container queries.
pub(crate) trait QueryCondition<'i>: Sized {
- fn parse_feature<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>>;
+ fn parse_feature<'t>(
+ input: &mut Parser<'i, 't>,
+ options: &ParserOptions<'_, 'i>,
+ ) -> Result<'i, ParserError<'i>>>;
fn create_negation(condition: Box) -> Self;
fn create_operation(operator: Operator, conditions: Vec) -> Self;
- fn parse_style_query<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
+ fn parse_style_query<'t>(
+ input: &mut Parser<'i, 't>,
+ _options: &ParserOptions<'_, 'i>,
+ ) -> Result<'i, ParserError<'i>>> {
+ Err(input.new_error_for_next_token())
+ }
+
+ fn parse_scroll_state_query<'t>(
+ input: &mut Parser<'i, 't>,
+ _options: &ParserOptions<'_, 'i>,
+ ) -> Result<'i, ParserError<'i>>> {
Err(input.new_error_for_next_token())
}
@@ -536,8 +570,11 @@ pub(crate) trait QueryCondition<'i>: Sized {
impl<'i> QueryCondition<'i> for MediaCondition<'i> {
#[inline]
- fn parse_feature<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- let feature = MediaFeature::parse(input)?;
+ fn parse_feature<'t>(
+ input: &mut Parser<'i, 't>,
+ options: &ParserOptions<'_, 'i>,
+ ) -> Result<'i, ParserError<'i>>> {
+ let feature = MediaFeature::parse_with_options(input, options)?;
Ok(Self::Feature(feature))
}
@@ -556,6 +593,7 @@ impl<'i> QueryCondition<'i> for MediaCondition<'i> {
MediaCondition::Not(_) => true,
MediaCondition::Operation { operator, .. } => Some(*operator) != parent_operator,
MediaCondition::Feature(f) => f.needs_parens(parent_operator, targets),
+ MediaCondition::Unknown(_) => false,
}
}
}
@@ -568,6 +606,8 @@ bitflags! {
const ALLOW_OR = 1 << 0;
/// Whether to allow style container queries.
const ALLOW_STYLE = 1 << 1;
+ /// Whether to allow scroll state container queries.
+ const ALLOW_SCROLL_STATE = 1 << 2;
}
}
@@ -576,8 +616,18 @@ impl<'i> MediaCondition<'i> {
fn parse_with_flags<'t>(
input: &mut Parser<'i, 't>,
flags: QueryConditionFlags,
+ options: &ParserOptions<'_, 'i>,
) -> Result<'i, ParserError<'i>>> {
- parse_query_condition(input, flags)
+ input
+ .try_parse(|input| parse_query_condition(input, flags, options))
+ .or_else(|e| {
+ if options.error_recovery {
+ options.warn(e);
+ Ok(MediaCondition::Unknown(TokenList::parse(input, options, 0)?))
+ } else {
+ Err(e)
+ }
+ })
}
fn get_necessary_prefixes(&self, targets: Targets) -> VendorPrefix {
@@ -633,9 +683,12 @@ impl<'i> MediaCondition<'i> {
}
}
-impl<'i> Parse<'i> for MediaCondition<'i> {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- Self::parse_with_flags(input, QueryConditionFlags::ALLOW_OR)
+impl<'i> ParseWithOptions<'i> for MediaCondition<'i> {
+ fn parse_with_options<'t>(
+ input: &mut Parser<'i, 't>,
+ options: &ParserOptions<'_, 'i>,
+ ) -> Result<'i, ParserError<'i>>> {
+ Self::parse_with_flags(input, QueryConditionFlags::ALLOW_OR, options)
}
}
@@ -643,30 +696,47 @@ impl<'i> Parse<'i> for MediaCondition<'i> {
pub(crate) fn parse_query_condition<'t, 'i, P: QueryCondition<'i>>(
input: &mut Parser<'i, 't>,
flags: QueryConditionFlags,
+ options: &ParserOptions<'_, 'i>,
) -> Result<'i, ParserError<'i>>> {
let location = input.current_source_location();
- let (is_negation, is_style) = match *input.next()? {
- Token::ParenthesisBlock => (false, false),
- Token::Ident(ref ident) if ident.eq_ignore_ascii_case("not") => (true, false),
+ enum QueryFunction {
+ None,
+ Style,
+ ScrollState,
+ }
+
+ let (is_negation, function) = match *input.next()? {
+ Token::ParenthesisBlock => (false, QueryFunction::None),
+ Token::Ident(ref ident) if ident.eq_ignore_ascii_case("not") => (true, QueryFunction::None),
Token::Function(ref f)
if flags.contains(QueryConditionFlags::ALLOW_STYLE) && f.eq_ignore_ascii_case("style") =>
{
- (false, true)
+ (false, QueryFunction::Style)
+ }
+ Token::Function(ref f)
+ if flags.contains(QueryConditionFlags::ALLOW_SCROLL_STATE) && f.eq_ignore_ascii_case("scroll-state") =>
+ {
+ (false, QueryFunction::ScrollState)
}
ref t => return Err(location.new_unexpected_token_error(t.clone())),
};
- let first_condition = match (is_negation, is_style) {
- (true, false) => {
- let inner_condition = parse_parens_or_function(input, flags)?;
+ let first_condition = match (is_negation, function) {
+ (true, QueryFunction::None) => {
+ let inner_condition = parse_parens_or_function(input, flags, options)?;
return Ok(P::create_negation(Box::new(inner_condition)));
}
- (true, true) => {
- let inner_condition = P::parse_style_query(input)?;
+ (true, QueryFunction::Style) => {
+ let inner_condition = P::parse_style_query(input, options)?;
return Ok(P::create_negation(Box::new(inner_condition)));
}
- (false, false) => parse_paren_block(input, flags)?,
- (false, true) => P::parse_style_query(input)?,
+ (true, QueryFunction::ScrollState) => {
+ let inner_condition = P::parse_scroll_state_query(input, options)?;
+ return Ok(P::create_negation(Box::new(inner_condition)));
+ }
+ (false, QueryFunction::None) => parse_paren_block(input, flags, options)?,
+ (false, QueryFunction::Style) => P::parse_style_query(input, options)?,
+ (false, QueryFunction::ScrollState) => P::parse_scroll_state_query(input, options)?,
};
let operator = match input.try_parse(Operator::parse) {
@@ -680,7 +750,7 @@ pub(crate) fn parse_query_condition<'t, 'i, P: QueryCondition<'i>>(
let mut conditions = vec![];
conditions.push(first_condition);
- conditions.push(parse_parens_or_function(input, flags)?);
+ conditions.push(parse_parens_or_function(input, flags, options)?);
let delim = match operator {
Operator::And => "and",
@@ -692,7 +762,7 @@ pub(crate) fn parse_query_condition<'t, 'i, P: QueryCondition<'i>>(
return Ok(P::create_operation(operator, conditions));
}
- conditions.push(parse_parens_or_function(input, flags)?);
+ conditions.push(parse_parens_or_function(input, flags, options)?);
}
}
@@ -700,14 +770,20 @@ pub(crate) fn parse_query_condition<'t, 'i, P: QueryCondition<'i>>(
fn parse_parens_or_function<'t, 'i, P: QueryCondition<'i>>(
input: &mut Parser<'i, 't>,
flags: QueryConditionFlags,
+ options: &ParserOptions<'_, 'i>,
) -> Result
<'i, ParserError<'i>>> {
let location = input.current_source_location();
match *input.next()? {
- Token::ParenthesisBlock => parse_paren_block(input, flags),
+ Token::ParenthesisBlock => parse_paren_block(input, flags, options),
Token::Function(ref f)
if flags.contains(QueryConditionFlags::ALLOW_STYLE) && f.eq_ignore_ascii_case("style") =>
{
- P::parse_style_query(input)
+ P::parse_style_query(input, options)
+ }
+ Token::Function(ref f)
+ if flags.contains(QueryConditionFlags::ALLOW_SCROLL_STATE) && f.eq_ignore_ascii_case("scroll-state") =>
+ {
+ P::parse_scroll_state_query(input, options)
}
ref t => return Err(location.new_unexpected_token_error(t.clone())),
}
@@ -716,13 +792,21 @@ fn parse_parens_or_function<'t, 'i, P: QueryCondition<'i>>(
fn parse_paren_block<'t, 'i, P: QueryCondition<'i>>(
input: &mut Parser<'i, 't>,
flags: QueryConditionFlags,
+ options: &ParserOptions<'_, 'i>,
) -> Result
<'i, ParserError<'i>>> {
input.parse_nested_block(|input| {
- if let Ok(inner) = input.try_parse(|i| parse_query_condition(i, flags | QueryConditionFlags::ALLOW_OR)) {
+ // Detect empty brackets and provide a clearer error message.
+ if input.is_exhausted() {
+ return Err(input.new_custom_error(ParserError::EmptyBracketInCondition));
+ }
+
+ if let Ok(inner) =
+ input.try_parse(|i| parse_query_condition(i, flags | QueryConditionFlags::ALLOW_OR, options))
+ {
return Ok(inner);
}
- P::parse_feature(input)
+ P::parse_feature(input, options)
})
}
@@ -754,17 +838,27 @@ where
{
let mut iter = conditions.iter();
let first = iter.next().unwrap();
- to_css_with_parens_if_needed(first, dest, first.needs_parens(Some(operator), &dest.targets))?;
+ to_css_with_parens_if_needed(first, dest, first.needs_parens(Some(operator), &dest.targets.current))?;
for item in iter {
dest.write_char(' ')?;
operator.to_css(dest)?;
dest.write_char(' ')?;
- to_css_with_parens_if_needed(item, dest, item.needs_parens(Some(operator), &dest.targets))?;
+ to_css_with_parens_if_needed(item, dest, item.needs_parens(Some(operator), &dest.targets.current))?;
}
Ok(())
}
+impl<'i> MediaCondition<'i> {
+ fn negate(&self) -> Option<'i>> {
+ match self {
+ MediaCondition::Not(not) => Some((**not).clone()),
+ MediaCondition::Feature(f) => f.negate().map(MediaCondition::Feature),
+ _ => None,
+ }
+ }
+}
+
impl<'i> ToCss for MediaCondition<'i> {
fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
where
@@ -773,13 +867,18 @@ impl<'i> ToCss for MediaCondition<'i> {
match *self {
MediaCondition::Feature(ref f) => f.to_css(dest),
MediaCondition::Not(ref c) => {
- dest.write_str("not ")?;
- to_css_with_parens_if_needed(&**c, dest, c.needs_parens(None, &dest.targets))
+ if let Some(negated) = c.negate() {
+ negated.to_css(dest)
+ } else {
+ dest.write_str("not ")?;
+ to_css_with_parens_if_needed(&**c, dest, c.needs_parens(None, &dest.targets.current))
+ }
}
MediaCondition::Operation {
ref conditions,
operator,
} => operation_to_css(operator, conditions, dest),
+ MediaCondition::Unknown(ref tokens) => tokens.to_css(dest, false),
}
}
}
@@ -841,6 +940,16 @@ impl MediaFeatureComparison {
MediaFeatureComparison::Equal => MediaFeatureComparison::Equal,
}
}
+
+ fn negate(&self) -> MediaFeatureComparison {
+ match self {
+ MediaFeatureComparison::GreaterThan => MediaFeatureComparison::LessThanEqual,
+ MediaFeatureComparison::GreaterThanEqual => MediaFeatureComparison::LessThan,
+ MediaFeatureComparison::LessThan => MediaFeatureComparison::GreaterThanEqual,
+ MediaFeatureComparison::LessThanEqual => MediaFeatureComparison::GreaterThan,
+ MediaFeatureComparison::Equal => MediaFeatureComparison::Equal,
+ }
+ }
}
/// A generic media feature or container feature.
@@ -849,7 +958,8 @@ impl MediaFeatureComparison {
feature = "visitor",
derive(Visit),
visit(visit_media_feature, MEDIA_QUERIES, <'i, MediaFeatureId>),
- visit(<'i, ContainerSizeFeatureId>)
+ visit(<'i, ContainerSizeFeatureId>),
+ visit(<'i, ScrollStateFeatureId>)
)]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(
@@ -900,12 +1010,15 @@ pub enum QueryFeature<'i, FeatureId> {
/// A [media feature](https://drafts.csswg.org/mediaqueries/#typedef-media-feature)
pub type MediaFeature<'i> = QueryFeature<'i, MediaFeatureId>;
-impl<'i, FeatureId> Parse<'i> for QueryFeature<'i, FeatureId>
+impl<'i, FeatureId> ParseWithOptions<'i> for QueryFeature<'i, FeatureId>
where
- FeatureId: for<'x> Parse<'x> + std::fmt::Debug + PartialEq + ValueType,
+ FeatureId: for<'x> Parse<'x> + std::fmt::Debug + PartialEq + ValueType + Clone,
{
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- match input.try_parse(Self::parse_name_first) {
+ fn parse_with_options<'t>(
+ input: &mut Parser<'i, 't>,
+ options: &ParserOptions<'_, 'i>,
+ ) -> Result<'i, ParserError<'i>>> {
+ match input.try_parse(|input| Self::parse_name_first(input, options)) {
Ok(res) => Ok(res),
Err(
err @ ParseError {
@@ -920,9 +1033,12 @@ where
impl<'i, FeatureId> QueryFeature<'i, FeatureId>
where
- FeatureId: for<'x> Parse<'x> + std::fmt::Debug + PartialEq + ValueType,
+ FeatureId: for<'x> Parse<'x> + std::fmt::Debug + PartialEq + ValueType + Clone,
{
- fn parse_name_first<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
+ fn parse_name_first<'t>(
+ input: &mut Parser<'i, 't>,
+ options: &ParserOptions<'_, 'i>,
+ ) -> Result<'i, ParserError<'i>>> {
let (name, legacy_op) = MediaFeatureName::parse(input)?;
let operator = input.try_parse(|input| consume_operation_or_colon(input, true));
@@ -937,7 +1053,14 @@ where
let value = MediaFeatureValue::parse(input, name.value_type())?;
if !value.check_type(name.value_type()) {
- return Err(input.new_custom_error(ParserError::InvalidMediaQuery));
+ if options.error_recovery {
+ options.warn(ParseError {
+ kind: ParseErrorKind::Custom(ParserError::InvalidMediaQuery),
+ location: input.current_source_location(),
+ });
+ } else {
+ return Err(input.new_custom_error(ParserError::InvalidMediaQuery));
+ }
}
if let Some(operator) = operator.or(legacy_op) {
@@ -1017,9 +1140,30 @@ where
}
pub(crate) fn needs_parens(&self, parent_operator: Option, targets: &Targets) -> bool {
- parent_operator != Some(Operator::And)
- && matches!(self, QueryFeature::Interval { .. })
- && should_compile!(targets, MediaIntervalSyntax)
+ match self {
+ QueryFeature::Interval { .. } => {
+ should_compile!(targets, MediaIntervalSyntax) && parent_operator != Some(Operator::And)
+ }
+ QueryFeature::Range { operator, .. } => {
+ should_compile!(targets, MediaRangeSyntax)
+ && matches!(
+ operator,
+ MediaFeatureComparison::GreaterThan | MediaFeatureComparison::LessThan
+ )
+ }
+ _ => false,
+ }
+ }
+
+ fn negate(&self) -> Option<'i, FeatureId>> {
+ match self {
+ QueryFeature::Range { name, operator, value } => Some(QueryFeature::Range {
+ name: (*name).clone(),
+ operator: operator.negate(),
+ value: value.clone(),
+ }),
+ _ => None,
+ }
}
}
@@ -1028,23 +1172,24 @@ impl<'i, FeatureId: FeatureToCss> ToCss for QueryFeature<'i, FeatureId> {
where
W: std::fmt::Write,
{
- dest.write_char('(')?;
-
match self {
QueryFeature::Boolean { name } => {
+ dest.write_char('(')?;
name.to_css(dest)?;
}
QueryFeature::Plain { name, value } => {
+ dest.write_char('(')?;
name.to_css(dest)?;
dest.delim(':', false)?;
value.to_css(dest)?;
}
QueryFeature::Range { name, operator, value } => {
// If range syntax is unsupported, use min/max prefix if possible.
- if should_compile!(dest.targets, MediaRangeSyntax) {
- return write_min_max(operator, name, value, dest);
+ if should_compile!(dest.targets.current, MediaRangeSyntax) {
+ return write_min_max(operator, name, value, dest, false);
}
+ dest.write_char('(')?;
name.to_css(dest)?;
operator.to_css(dest)?;
value.to_css(dest)?;
@@ -1056,12 +1201,13 @@ impl<'i, FeatureId: FeatureToCss> ToCss for QueryFeature<'i, FeatureId> {
end,
end_operator,
} => {
- if should_compile!(dest.targets, MediaIntervalSyntax) {
- write_min_max(&start_operator.opposite(), name, start, dest)?;
- dest.write_str(" and (")?;
- return write_min_max(end_operator, name, end, dest);
+ if should_compile!(dest.targets.current, MediaIntervalSyntax) {
+ write_min_max(&start_operator.opposite(), name, start, dest, true)?;
+ dest.write_str(" and ")?;
+ return write_min_max(end_operator, name, end, dest, true);
}
+ dest.write_char('(')?;
start.to_css(dest)?;
start_operator.to_css(dest)?;
name.to_css(dest)?;
@@ -1378,16 +1524,32 @@ fn write_min_max(
name: &MediaFeatureName,
value: &MediaFeatureValue,
dest: &mut Printer,
+ is_range: bool,
) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
let prefix = match operator {
- MediaFeatureComparison::GreaterThan | MediaFeatureComparison::GreaterThanEqual => Some("min-"),
- MediaFeatureComparison::LessThan | MediaFeatureComparison::LessThanEqual => Some("max-"),
+ MediaFeatureComparison::GreaterThan => {
+ if is_range {
+ dest.write_char('(')?;
+ }
+ dest.write_str("not ")?;
+ Some("max-")
+ }
+ MediaFeatureComparison::GreaterThanEqual => Some("min-"),
+ MediaFeatureComparison::LessThan => {
+ if is_range {
+ dest.write_char('(')?;
+ }
+ dest.write_str("not ")?;
+ Some("min-")
+ }
+ MediaFeatureComparison::LessThanEqual => Some("max-"),
MediaFeatureComparison::Equal => None,
};
+ dest.write_char('(')?;
if let Some(prefix) = prefix {
name.to_css_with_prefix(prefix, dest)?;
} else {
@@ -1395,17 +1557,15 @@ where
}
dest.delim(':', false)?;
+ value.to_css(dest)?;
- let adjusted = match operator {
- MediaFeatureComparison::GreaterThan => Some(value.clone() + 0.001),
- MediaFeatureComparison::LessThan => Some(value.clone() + -0.001),
- _ => None,
- };
-
- if let Some(value) = adjusted {
- value.to_css(dest)?;
- } else {
- value.to_css(dest)?;
+ if is_range
+ && matches!(
+ operator,
+ MediaFeatureComparison::GreaterThan | MediaFeatureComparison::LessThan
+ )
+ {
+ dest.write_char(')')?;
}
dest.write_char(')')?;
@@ -1560,25 +1720,6 @@ impl<'i> ToCss for MediaFeatureValue<'i> {
}
}
-impl<'i> std::ops::Add for MediaFeatureValue<'i> {
- type Output = Self;
-
- fn add(self, other: f32) -> Self {
- match self {
- MediaFeatureValue::Length(len) => MediaFeatureValue::Length(len + Length::px(other)),
- MediaFeatureValue::Number(num) => MediaFeatureValue::Number(num + other),
- MediaFeatureValue::Integer(num) => {
- MediaFeatureValue::Integer(num + if other.is_sign_positive() { 1 } else { -1 })
- }
- MediaFeatureValue::Boolean(v) => MediaFeatureValue::Boolean(v),
- MediaFeatureValue::Resolution(res) => MediaFeatureValue::Resolution(res + other),
- MediaFeatureValue::Ratio(ratio) => MediaFeatureValue::Ratio(ratio + other),
- MediaFeatureValue::Ident(id) => MediaFeatureValue::Ident(id),
- MediaFeatureValue::Env(env) => MediaFeatureValue::Env(env), // TODO: calc support
- }
- }
-}
-
/// Consumes an operation or a colon, or returns an error.
fn consume_operation_or_colon<'i, 't>(
input: &mut Parser<'i, 't>,
@@ -1756,10 +1897,10 @@ mod tests {
targets::{Browsers, Targets},
};
- fn parse(s: &str) -> MediaQuery {
+ fn parse(s: &str) -> MediaQuery<'_> {
let mut input = ParserInput::new(&s);
let mut parser = Parser::new(&mut input);
- MediaQuery::parse(&mut parser).unwrap()
+ MediaQuery::parse_with_options(&mut parser, &ParserOptions::default()).unwrap()
}
fn and(a: &str, b: &str) -> String {
@@ -1817,7 +1958,7 @@ mod tests {
};
assert_eq!(
media_query.to_css_string(printer_options).unwrap(),
- "screen and not ((min-width: 200px) and (max-width: 499.999px))"
+ "screen and not ((min-width: 200px) and (not (min-width: 500px)))"
);
}
}
diff --git a/src/parser.rs b/src/parser.rs
index 2d4768444..0dd76041c 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -4,13 +4,17 @@ use crate::media_query::*;
use crate::printer::Printer;
use crate::properties::custom::TokenList;
use crate::rules::container::{ContainerCondition, ContainerName, ContainerRule};
+use crate::rules::font_feature_values::FontFeatureValuesRule;
use crate::rules::font_palette_values::FontPaletteValuesRule;
use crate::rules::layer::{LayerBlockRule, LayerStatementRule};
+use crate::rules::nesting::NestedDeclarationsRule;
use crate::rules::property::PropertyRule;
use crate::rules::scope::ScopeRule;
use crate::rules::starting_style::StartingStyleRule;
+use crate::rules::view_transition::ViewTransitionRule;
use crate::rules::viewport::ViewportRule;
+use crate::properties::font::FamilyName;
use crate::rules::{
counter_style::CounterStyleRule,
custom_media::CustomMediaRule,
@@ -28,8 +32,8 @@ use crate::rules::{
unknown::UnknownAtRule,
CssRule, CssRuleList, Location,
};
-use crate::selector::{Component, SelectorList, SelectorParser};
-use crate::traits::Parse;
+use crate::selector::{SelectorList, SelectorParser};
+use crate::traits::{Parse, ParseWithOptions};
use crate::values::ident::{CustomIdent, DashedIdent};
use crate::values::string::CowArcStr;
use crate::vendor_prefix::VendorPrefix;
@@ -152,7 +156,7 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> TopLevelRuleParser<'a,
}
}
- pub fn nested<'x: 'b>(&'x mut self) -> NestedRuleParser<'_, 'o, 'i, T> {
+ pub fn nested<'x: 'b>(&'x mut self) -> NestedRuleParser<'x, 'o, 'i, T> {
NestedRuleParser {
options: &self.options,
at_rule_parser: self.at_rule_parser,
@@ -172,7 +176,7 @@ pub enum AtRulePrelude<'i, T> {
/// A @font-face rule prelude.
FontFace,
/// A @font-feature-values rule prelude, with its FamilyName list.
- FontFeatureValues, //(Vec),
+ FontFeatureValues(Vec<'i>>),
/// A @font-palette-values rule prelude, with its name.
FontPaletteValues(DashedIdent<'i>),
/// A @counter-style rule prelude, with its counter style name.
@@ -209,11 +213,15 @@ pub enum AtRulePrelude<'i, T> {
/// An @property prelude.
Property(DashedIdent<'i>),
/// A @container prelude.
- Container(Option<'i>>, ContainerCondition<'i>),
+ /// Spec: https://drafts.csswg.org/css-conditional-5/#container-rule
+ /// @container [ ? ? ]!
+ Container(Option<'i>>, Option<'i>>),
/// A @starting-style prelude.
StartingStyle,
/// A @scope rule prelude.
Scope(Option<'i>>, Option<'i>>),
+ /// A @view-transition rule prelude.
+ ViewTransition,
/// An unknown prelude.
Unknown(CowArcStr<'i>, TokenList<'i>),
/// A custom prelude.
@@ -240,7 +248,7 @@ impl<'i, T> AtRulePrelude<'i, T> {
Self::Namespace(..)
| Self::FontFace
- | Self::FontFeatureValues
+ | Self::FontFeatureValues(..)
| Self::FontPaletteValues(..)
| Self::CounterStyle(..)
| Self::Keyframes(..)
@@ -249,7 +257,8 @@ impl<'i, T> AtRulePrelude<'i, T> {
| Self::Import(..)
| Self::CustomMedia(..)
| Self::Viewport(..)
- | Self::Charset => false,
+ | Self::Charset
+ | Self::ViewTransition => false,
}
}
}
@@ -288,7 +297,7 @@ impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for TopLev
} else {
None
};
- let media = MediaList::parse(input)?;
+ let media = MediaList::parse(input, &self.options)?;
return Ok(AtRulePrelude::Import(url_string, media, supports, layer));
},
"namespace" => {
@@ -310,13 +319,9 @@ impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for TopLev
},
"custom-media" if self.options.flags.contains(ParserFlags::CUSTOM_MEDIA) => {
let name = DashedIdent::parse(input)?;
- let media = MediaList::parse(input)?;
+ let media = MediaList::parse(input, &self.options)?;
return Ok(AtRulePrelude::CustomMedia(name, media))
},
- "property" => {
- let name = DashedIdent::parse(input)?;
- return Ok(AtRulePrelude::Property(name))
- },
_ => {}
}
@@ -511,19 +516,13 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> NestedRuleParser<'a, 'o
};
// Declarations can be immediately within @media and @supports blocks that are nested within a parent style rule.
- // These act the same way as if they were nested within a `& { ... }` block.
+ // These are wrapped in an (invisible) NestedDeclarationsRule.
let (declarations, mut rules) = self.parse_nested(input, false)?;
if declarations.len() > 0 {
rules.0.insert(
0,
- CssRule::Style(StyleRule {
- selectors: Component::Nesting.into(),
- declarations,
- vendor_prefix: VendorPrefix::empty(),
- rules: CssRuleList(vec![]),
- loc,
- }),
+ CssRule::NestedDeclarations(NestedDeclarationsRule { declarations, loc }),
)
}
@@ -552,11 +551,11 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne
) -> Result<'i, Self::Error>> {
let result = match_ignore_ascii_case! { &*name,
"media" => {
- let media = MediaList::parse(input)?;
+ let media = MediaList::parse(input, &self.options)?;
AtRulePrelude::Media(media)
},
"supports" => {
- let cond = SupportsCondition::parse(input)?;
+ let cond = SupportsCondition::parse(input, )?;
AtRulePrelude::Supports(cond)
},
"font-face" => {
@@ -570,6 +569,14 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne
// let family_names = parse_family_name_list(self.context, input)?;
// Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::FontFeatureValues(family_names)))
// },
+ "font-feature-values" => {
+ let names = match Vec::::parse(input) {
+ Ok(names) => names,
+ Err(e) => return Err(e)
+ };
+
+ AtRulePrelude::FontFeatureValues(names)
+ },
"font-palette-values" => {
let name = DashedIdent::parse(input)?;
AtRulePrelude::FontPaletteValues(name)
@@ -636,8 +643,18 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne
},
"container" => {
let name = input.try_parse(ContainerName::parse).ok();
- let condition = ContainerCondition::parse(input)?;
- AtRulePrelude::Container(name, condition)
+ match input.try_parse(|input| ContainerCondition::parse_with_options(input, &self.options)) {
+ Ok(condition) => AtRulePrelude::Container(name, Some(condition)),
+ Err(e) => {
+ if name.is_some() && input.is_exhausted() {
+ // name only, no condition - allowed by new syntax
+ AtRulePrelude::Container(name, None)
+ } else {
+ // condition parsing failed (e.g., empty brackets or invalid tokens)
+ return Err(e);
+ }
+ }
+ }
},
"starting-style" => {
AtRulePrelude::StartingStyle
@@ -669,6 +686,9 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne
AtRulePrelude::Scope(scope_start, scope_end)
},
+ "view-transition" => {
+ AtRulePrelude::ViewTransition
+ },
"nest" if self.is_in_style_rule => {
self.options.warn(input.new_custom_error(ParserError::DeprecatedNestRule));
let selector_parser = SelectorParser {
@@ -678,6 +698,16 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne
let selectors = SelectorList::parse(&selector_parser, input, ParseErrorRecovery::DiscardList, NestingRequirement::Contained)?;
AtRulePrelude::Nest(selectors)
},
+
+ "value" if self.options.css_modules.is_some() => {
+ return Err(input.new_custom_error(ParserError::DeprecatedCssModulesValueRule));
+ },
+
+ "property" => {
+ let name = DashedIdent::parse(input)?;
+ return Ok(AtRulePrelude::Property(name))
+ },
+
_ => parse_custom_at_rule_prelude(&name, input, self.options, self.at_rule_parser)?
};
@@ -688,6 +718,45 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne
Ok(result)
}
+ #[inline]
+ fn rule_without_block(
+ &mut self,
+ prelude: AtRulePrelude<'i, T::Prelude>,
+ start: &ParserState,
+ ) -> Result {
+ let loc = self.loc(start);
+ match prelude {
+ AtRulePrelude::Layer(names) => {
+ if self.is_in_style_rule || names.is_empty() {
+ return Err(());
+ }
+
+ self.rules.0.push(CssRule::LayerStatement(LayerStatementRule { names, loc }));
+ Ok(())
+ }
+ AtRulePrelude::Unknown(name, prelude) => {
+ self.rules.0.push(CssRule::Unknown(UnknownAtRule {
+ name,
+ prelude,
+ block: None,
+ loc,
+ }));
+ Ok(())
+ }
+ AtRulePrelude::Custom(prelude) => {
+ self.rules.0.push(parse_custom_at_rule_without_block(
+ prelude,
+ start,
+ self.options,
+ self.at_rule_parser,
+ self.is_in_style_rule,
+ )?);
+ Ok(())
+ }
+ _ => Err(()),
+ }
+ }
+
fn parse_block<'t>(
&mut self,
prelude: Self::Prelude,
@@ -827,6 +896,13 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne
self.rules.0.push(CssRule::StartingStyle(StartingStyleRule { rules, loc }));
Ok(())
}
+ AtRulePrelude::ViewTransition => {
+ self
+ .rules
+ .0
+ .push(CssRule::ViewTransition(ViewTransitionRule::parse(input, loc)?));
+ Ok(())
+ }
AtRulePrelude::Nest(selectors) => {
let (declarations, rules) = self.parse_nested(input, true)?;
self.rules.0.push(CssRule::Nesting(NestingRule {
@@ -841,7 +917,11 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne
}));
Ok(())
}
- AtRulePrelude::FontFeatureValues => unreachable!(),
+ AtRulePrelude::FontFeatureValues(family_names) => {
+ let rule = FontFeatureValuesRule::parse(family_names, input, loc, self.options)?;
+ self.rules.0.push(CssRule::FontFeatureValues(rule));
+ Ok(())
+ }
AtRulePrelude::Unknown(name, prelude) => {
self.rules.0.push(CssRule::Unknown(UnknownAtRule {
name,
@@ -864,45 +944,6 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne
}
}
}
-
- #[inline]
- fn rule_without_block(
- &mut self,
- prelude: AtRulePrelude<'i, T::Prelude>,
- start: &ParserState,
- ) -> Result {
- let loc = self.loc(start);
- match prelude {
- AtRulePrelude::Layer(names) => {
- if self.is_in_style_rule || names.is_empty() {
- return Err(());
- }
-
- self.rules.0.push(CssRule::LayerStatement(LayerStatementRule { names, loc }));
- Ok(())
- }
- AtRulePrelude::Unknown(name, prelude) => {
- self.rules.0.push(CssRule::Unknown(UnknownAtRule {
- name,
- prelude,
- block: None,
- loc,
- }));
- Ok(())
- }
- AtRulePrelude::Custom(prelude) => {
- self.rules.0.push(parse_custom_at_rule_without_block(
- prelude,
- start,
- self.options,
- self.at_rule_parser,
- self.is_in_style_rule,
- )?);
- Ok(())
- }
- _ => Err(()),
- }
- }
}
impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> QualifiedRuleParser<'i>
@@ -968,13 +1009,40 @@ impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> cssparser::DeclarationParse
name: CowRcStr<'i>,
input: &mut cssparser::Parser<'i, 't>,
) -> Result<'i, Self::Error>> {
- parse_declaration(
- name,
- input,
- &mut self.declarations,
- &mut self.important_declarations,
- &self.options,
- )
+ if self.rules.0.is_empty() {
+ parse_declaration(
+ name,
+ input,
+ &mut self.declarations,
+ &mut self.important_declarations,
+ &self.options,
+ )
+ } else if let Some(CssRule::NestedDeclarations(last)) = self.rules.0.last_mut() {
+ parse_declaration(
+ name,
+ input,
+ &mut last.declarations.declarations,
+ &mut last.declarations.important_declarations,
+ &self.options,
+ )
+ } else {
+ let loc = self.loc(&input.state());
+ let mut nested = NestedDeclarationsRule {
+ declarations: DeclarationBlock::new(),
+ loc,
+ };
+
+ parse_declaration(
+ name,
+ input,
+ &mut nested.declarations.declarations,
+ &mut nested.declarations.important_declarations,
+ &self.options,
+ )?;
+
+ self.rules.0.push(CssRule::NestedDeclarations(nested));
+ Ok(())
+ }
}
}
diff --git a/src/prefixes.rs b/src/prefixes.rs
index 5cb213def..63792255e 100644
--- a/src/prefixes.rs
+++ b/src/prefixes.rs
@@ -800,18 +800,13 @@ impl Feature {
prefixes |= VendorPrefix::WebKit;
}
}
- if let Some(version) = browsers.ios_saf {
- if version >= 393216 && version <= 852992 {
- prefixes |= VendorPrefix::WebKit;
- }
- }
if let Some(version) = browsers.opera {
if version >= 983040 && version <= 6881280 {
prefixes |= VendorPrefix::WebKit;
}
}
if let Some(version) = browsers.safari {
- if version >= 262144 && version <= 852224 {
+ if version >= 197120 && version <= 851968 {
prefixes |= VendorPrefix::WebKit;
}
}
@@ -820,6 +815,11 @@ impl Feature {
prefixes |= VendorPrefix::WebKit;
}
}
+ if let Some(version) = browsers.ios_saf {
+ if version >= 262144 && version <= 851968 {
+ prefixes |= VendorPrefix::WebKit;
+ }
+ }
}
Feature::FontFeatureSettings | Feature::FontVariantLigatures | Feature::FontLanguageOverride => {
if let Some(version) = browsers.android {
@@ -1268,23 +1268,23 @@ impl Feature {
}
}
Feature::Stretch => {
- if let Some(version) = browsers.chrome {
- if version >= 1441792 {
- prefixes |= VendorPrefix::WebKit;
- }
- }
if let Some(version) = browsers.firefox {
if version >= 196608 {
prefixes |= VendorPrefix::Moz;
}
}
if let Some(version) = browsers.android {
- if version >= 263168 {
+ if version >= 263168 && version <= 263171 {
+ prefixes |= VendorPrefix::WebKit;
+ }
+ }
+ if let Some(version) = browsers.chrome {
+ if version >= 1441792 && version <= 8978432 {
prefixes |= VendorPrefix::WebKit;
}
}
if let Some(version) = browsers.edge {
- if version >= 5177344 {
+ if version >= 5177344 && version <= 8978432 {
prefixes |= VendorPrefix::WebKit;
}
}
@@ -1299,12 +1299,12 @@ impl Feature {
}
}
if let Some(version) = browsers.safari {
- if version >= 458752 {
+ if version >= 393472 {
prefixes |= VendorPrefix::WebKit;
}
}
if let Some(version) = browsers.samsung {
- if version >= 327680 {
+ if version >= 262144 {
prefixes |= VendorPrefix::WebKit;
}
}
@@ -1386,12 +1386,12 @@ impl Feature {
}
Feature::TextDecoration => {
if let Some(version) = browsers.ios_saf {
- if version >= 524288 {
+ if version >= 524288 && version <= 1704192 {
prefixes |= VendorPrefix::WebKit;
}
}
if let Some(version) = browsers.safari {
- if version >= 524288 {
+ if version >= 524288 && version <= 1704192 {
prefixes |= VendorPrefix::WebKit;
}
}
@@ -1516,18 +1516,18 @@ impl Feature {
}
}
Feature::BoxDecorationBreak => {
- if let Some(version) = browsers.chrome {
- if version >= 1441792 {
+ if let Some(version) = browsers.android {
+ if version >= 263168 && version <= 263171 {
prefixes |= VendorPrefix::WebKit;
}
}
- if let Some(version) = browsers.android {
- if version >= 263168 {
+ if let Some(version) = browsers.chrome {
+ if version >= 1441792 && version <= 8454144 {
prefixes |= VendorPrefix::WebKit;
}
}
if let Some(version) = browsers.edge {
- if version >= 5177344 {
+ if version >= 5177344 && version <= 8454144 {
prefixes |= VendorPrefix::WebKit;
}
}
@@ -2153,18 +2153,18 @@ impl Feature {
}
}
Feature::PrintColorAdjust | Feature::ColorAdjust => {
- if let Some(version) = browsers.chrome {
- if version >= 1114112 {
+ if let Some(version) = browsers.android {
+ if version >= 263168 && version <= 263171 {
prefixes |= VendorPrefix::WebKit;
}
}
- if let Some(version) = browsers.android {
- if version >= 263168 {
+ if let Some(version) = browsers.chrome {
+ if version >= 1114112 && version <= 8847360 {
prefixes |= VendorPrefix::WebKit;
}
}
if let Some(version) = browsers.edge {
- if version >= 5177344 {
+ if version >= 5177344 && version <= 8847360 {
prefixes |= VendorPrefix::WebKit;
}
}
@@ -2189,7 +2189,7 @@ impl Feature {
}
}
if let Some(version) = browsers.samsung {
- if version >= 262144 {
+ if version >= 262144 && version <= 1835008 {
prefixes |= VendorPrefix::WebKit;
}
}
@@ -2231,7 +2231,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version >= 2424832 && version <= 5701632 {
+ if version >= 263168 && version <= 5701632 {
prefixes |= VendorPrefix::WebKit;
}
}
diff --git a/src/printer.rs b/src/printer.rs
index d8c08e2db..b231fbecc 100644
--- a/src/printer.rs
+++ b/src/printer.rs
@@ -5,7 +5,7 @@ use crate::dependencies::{Dependency, DependencyOptions};
use crate::error::{Error, ErrorLocation, PrinterError, PrinterErrorKind};
use crate::rules::{Location, StyleContext};
use crate::selector::SelectorList;
-use crate::targets::Targets;
+use crate::targets::{Targets, TargetsWithSupportsScope};
use crate::vendor_prefix::VendorPrefix;
use cssparser::{serialize_identifier, serialize_name};
#[cfg(feature = "sourcemap")]
@@ -77,7 +77,7 @@ pub struct Printer<'a, 'b, 'c, W> {
line: u32,
col: u32,
pub(crate) minify: bool,
- pub(crate) targets: Targets,
+ pub(crate) targets: TargetsWithSupportsScope,
/// Vendor prefix override. When non-empty, it overrides
/// the vendor prefix of whatever is being printed.
pub(crate) vendor_prefix: VendorPrefix,
@@ -108,7 +108,7 @@ impl<'a, 'b, 'c, W: std::fmt::Write + Sized> Printer<'a, 'b, 'c, W> {
line: 0,
col: 0,
minify: options.minify,
- targets: options.targets,
+ targets: TargetsWithSupportsScope::new(options.targets),
vendor_prefix: VendorPrefix::empty(),
in_calc: false,
css_module: None,
@@ -146,6 +146,25 @@ impl<'a, 'b, 'c, W: std::fmt::Write + Sized> Printer<'a, 'b, 'c, W> {
Ok(())
}
+ /// Writes a raw string which may contain newlines to the underlying destination.
+ pub fn write_str_with_newlines(&mut self, s: &str) -> Result<(), PrinterError> {
+ let mut last_line_start: usize = 0;
+
+ for (idx, n) in s.char_indices() {
+ if n == '\n' {
+ self.line += 1;
+ self.col = 0;
+
+ // Keep track of where the *next* line starts
+ last_line_start = idx + 1;
+ }
+ }
+
+ self.col += (s.len() - last_line_start) as u32;
+ self.dest.write_str(s)?;
+ Ok(())
+ }
+
/// Write a single character to the underlying destination.
pub fn write_char(&mut self, c: char) -> Result<(), PrinterError> {
if c == '\n' {
@@ -241,10 +260,11 @@ impl<'a, 'b, 'c, W: std::fmt::Write + Sized> Printer<'a, 'b, 'c, W> {
if let Some(orig) = mapping.original {
let sources_len = map.get_sources().len();
let source_index = map.add_source(sm.get_source(orig.source).unwrap());
+ let name = orig.name.map(|name| map.add_name(sm.get_name(name).unwrap()));
original.original_line = orig.original_line;
original.original_column = orig.original_column;
original.source = source_index;
- original.name = orig.name;
+ original.name = name;
if map.get_sources().len() > sources_len {
let content = sm.get_source_content(orig.source).unwrap().to_owned();
@@ -276,6 +296,11 @@ impl<'a, 'b, 'c, W: std::fmt::Write + Sized> Printer<'a, 'b, 'c, W> {
&css_module.hashes[self.loc.source_index as usize],
&css_module.sources[self.loc.source_index as usize],
ident,
+ if let Some(content_hashes) = &css_module.content_hashes {
+ &content_hashes[self.loc.source_index as usize]
+ } else {
+ ""
+ },
|s| {
self.col += s.len() as u32;
if first {
@@ -306,6 +331,11 @@ impl<'a, 'b, 'c, W: std::fmt::Write + Sized> Printer<'a, 'b, 'c, W> {
&css_module.hashes[self.loc.source_index as usize],
&css_module.sources[self.loc.source_index as usize],
&ident[2..],
+ if let Some(content_hashes) = &css_module.content_hashes {
+ &content_hashes[self.loc.source_index as usize]
+ } else {
+ ""
+ },
|s| {
self.col += s.len() as u32;
serialize_name(s, dest)
@@ -365,6 +395,19 @@ impl<'a, 'b, 'c, W: std::fmt::Write + Sized> Printer<'a, 'b, 'c, W> {
res
}
+ pub(crate) fn with_parent_context<'a, 'b, 'c, W>) -> Result>(
+ &mut self,
+ f: F,
+ ) -> Result {
+ let parent = std::mem::take(&mut self.context);
+ if let Some(parent) = parent {
+ self.context = parent.parent;
+ }
+ let res = f(self);
+ self.context = parent;
+ res
+ }
+
pub(crate) fn context(&self) -> Option<&'a StyleContext<'a, 'b>> {
self.context.clone()
}
diff --git a/src/properties/animation.rs b/src/properties/animation.rs
index f018f4af4..51bd1ea48 100644
--- a/src/properties/animation.rs
+++ b/src/properties/animation.rs
@@ -406,7 +406,11 @@ pub enum TimelineRangeName {
/// 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), 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 AnimationAttachmentRange {
diff --git a/src/properties/box_shadow.rs b/src/properties/box_shadow.rs
index de60f4d3d..663761654 100644
--- a/src/properties/box_shadow.rs
+++ b/src/properties/box_shadow.rs
@@ -225,7 +225,7 @@ impl BoxShadowHandler {
let p3 = box_shadows
.iter()
.map(|shadow| BoxShadow {
- color: shadow.color.to_p3().unwrap(),
+ color: shadow.color.to_p3().unwrap_or_else(|_| shadow.color.clone()),
..shadow.clone()
})
.collect();
diff --git a/src/properties/contain.rs b/src/properties/contain.rs
index 1c57bab49..27ad2bb95 100644
--- a/src/properties/contain.rs
+++ b/src/properties/contain.rs
@@ -30,6 +30,8 @@ enum_property! {
InlineSize,
/// Establishes a query container for container size queries on both the inline and block axis.
Size,
+ /// Establishes a query container for container scroll-state queries
+ ScrollState,
}
}
diff --git a/src/properties/custom.rs b/src/properties/custom.rs
index 40b522e89..26da05708 100644
--- a/src/properties/custom.rs
+++ b/src/properties/custom.rs
@@ -7,12 +7,12 @@ use crate::printer::Printer;
use crate::properties::PropertyId;
use crate::rules::supports::SupportsCondition;
use crate::stylesheet::ParserOptions;
-use crate::targets::{should_compile, Targets};
+use crate::targets::{should_compile, Features, Targets};
use crate::traits::{Parse, ParseWithOptions, ToCss};
use crate::values::angle::Angle;
use crate::values::color::{
parse_hsl_hwb_components, parse_rgb_components, ColorFallbackKind, ComponentParser, CssColor, LightDarkColor,
- HSL, RGBA, SRGB,
+ HSL, RGB, RGBA,
};
use crate::values::ident::{CustomIdent, DashedIdent, DashedIdentReference, Ident};
use crate::values::length::{serialize_dimension, LengthValue};
@@ -370,59 +370,37 @@ impl<'i> TokenList<'i> {
return Err(input.new_custom_error(ParserError::MaximumNestingDepth));
}
- let mut last_is_delim = false;
- let mut last_is_whitespace = false;
loop {
let state = input.state();
match input.next_including_whitespace_and_comments() {
- Ok(&cssparser::Token::WhiteSpace(..)) | Ok(&cssparser::Token::Comment(..)) => {
- // Skip whitespace if the last token was a delimiter.
- // Otherwise, replace all whitespace and comments with a single space character.
- if !last_is_delim {
- tokens.push(Token::WhiteSpace(" ".into()).into());
- last_is_whitespace = true;
- }
- }
Ok(&cssparser::Token::Function(ref f)) => {
// Attempt to parse embedded color values into hex tokens.
let f = f.into();
if let Some(color) = try_parse_color_token(&f, &state, input) {
tokens.push(TokenOrValue::Color(color));
- last_is_delim = false;
- last_is_whitespace = false;
} else if let Ok(color) = input.try_parse(|input| UnresolvedColor::parse(&f, input, options)) {
tokens.push(TokenOrValue::UnresolvedColor(color));
- last_is_delim = true;
- last_is_whitespace = false;
} else if f == "url" {
input.reset(&state);
tokens.push(TokenOrValue::Url(Url::parse(input)?));
- last_is_delim = false;
- last_is_whitespace = false;
} else if f == "var" {
let var = input.parse_nested_block(|input| {
let var = Variable::parse(input, options, depth + 1)?;
Ok(TokenOrValue::Var(var))
})?;
tokens.push(var);
- last_is_delim = true;
- last_is_whitespace = false;
} else if f == "env" {
let env = input.parse_nested_block(|input| {
let env = EnvironmentVariable::parse_nested(input, options, depth + 1)?;
Ok(TokenOrValue::Env(env))
})?;
tokens.push(env);
- last_is_delim = true;
- last_is_whitespace = false;
} else {
let arguments = input.parse_nested_block(|input| TokenList::parse(input, options, depth + 1))?;
tokens.push(TokenOrValue::Function(Function {
name: Ident(f),
arguments,
}));
- last_is_delim = true; // Whitespace is not required after any of these chars.
- last_is_whitespace = false;
}
}
Ok(&cssparser::Token::Hash(ref h)) | Ok(&cssparser::Token::IDHash(ref h)) => {
@@ -431,19 +409,13 @@ impl<'i> TokenList<'i> {
} else {
tokens.push(Token::Hash(h.into()).into());
}
- last_is_delim = false;
- last_is_whitespace = false;
}
Ok(&cssparser::Token::UnquotedUrl(_)) => {
input.reset(&state);
tokens.push(TokenOrValue::Url(Url::parse(input)?));
- last_is_delim = false;
- last_is_whitespace = false;
}
Ok(&cssparser::Token::Ident(ref name)) if name.starts_with("--") => {
tokens.push(TokenOrValue::DashedIdent(name.into()));
- last_is_delim = false;
- last_is_whitespace = false;
}
Ok(token @ &cssparser::Token::ParenthesisBlock)
| Ok(token @ &cssparser::Token::SquareBracketBlock)
@@ -459,8 +431,6 @@ impl<'i> TokenList<'i> {
input.parse_nested_block(|input| TokenList::parse_into(input, tokens, options, depth + 1))?;
tokens.push(closing_delimiter.into());
- last_is_delim = true; // Whitespace is not required after any of these chars.
- last_is_whitespace = false;
}
Ok(token @ cssparser::Token::Dimension { .. }) => {
let value = if let Ok(length) = LengthValue::try_from(token) {
@@ -475,8 +445,6 @@ impl<'i> TokenList<'i> {
TokenOrValue::Token(token.into())
};
tokens.push(value);
- last_is_delim = false;
- last_is_whitespace = false;
}
Ok(token) if token.is_parse_error() => {
return Err(ParseError {
@@ -485,18 +453,7 @@ impl<'i> TokenList<'i> {
})
}
Ok(token) => {
- last_is_delim = matches!(token, cssparser::Token::Delim(_) | cssparser::Token::Comma);
-
- // If this is a delimiter, and the last token was whitespace,
- // replace the whitespace with the delimiter since both are not required.
- if last_is_delim && last_is_whitespace {
- let last = tokens.last_mut().unwrap();
- *last = Token::from(token).into();
- } else {
- tokens.push(Token::from(token).into());
- }
-
- last_is_whitespace = false;
+ tokens.push(Token::from(token).into());
}
Err(_) => break,
}
@@ -532,20 +489,13 @@ impl<'i> TokenList<'i> {
where
W: std::fmt::Write,
{
- if !dest.minify && self.0.len() == 1 && matches!(self.0.first(), Some(token) if token.is_whitespace()) {
- return Ok(());
- }
-
- let mut has_whitespace = false;
- for (i, token_or_value) in self.0.iter().enumerate() {
- has_whitespace = match token_or_value {
+ for token_or_value in self.0.iter() {
+ match token_or_value {
TokenOrValue::Color(color) => {
color.to_css(dest)?;
- false
}
TokenOrValue::UnresolvedColor(color) => {
color.to_css(dest, is_custom_property)?;
- false
}
TokenOrValue::Url(url) => {
if dest.dependencies.is_some() && is_custom_property && !url.is_absolute() {
@@ -557,77 +507,45 @@ impl<'i> TokenList<'i> {
));
}
url.to_css(dest)?;
- false
}
TokenOrValue::Var(var) => {
var.to_css(dest, is_custom_property)?;
- self.write_whitespace_if_needed(i, dest)?
}
TokenOrValue::Env(env) => {
env.to_css(dest, is_custom_property)?;
- self.write_whitespace_if_needed(i, dest)?
}
TokenOrValue::Function(f) => {
f.to_css(dest, is_custom_property)?;
- self.write_whitespace_if_needed(i, dest)?
}
TokenOrValue::Length(v) => {
// Do not serialize unitless zero lengths in custom properties as it may break calc().
let (value, unit) = v.to_unit_value();
serialize_dimension(value, unit, dest)?;
- false
}
TokenOrValue::Angle(v) => {
v.to_css(dest)?;
- false
}
TokenOrValue::Time(v) => {
v.to_css(dest)?;
- false
}
TokenOrValue::Resolution(v) => {
v.to_css(dest)?;
- false
}
TokenOrValue::DashedIdent(v) => {
v.to_css(dest)?;
- false
}
TokenOrValue::AnimationName(v) => {
v.to_css(dest)?;
- false
}
TokenOrValue::Token(token) => match token {
- Token::Delim(d) => {
- if *d == '+' || *d == '-' {
- dest.write_char(' ')?;
- dest.write_char(*d)?;
- dest.write_char(' ')?;
- } else {
- let ws_before = !has_whitespace && (*d == '/' || *d == '*');
- dest.delim(*d, ws_before)?;
- }
- true
- }
- Token::Comma => {
- dest.delim(',', false)?;
- true
- }
- Token::CloseParenthesis | Token::CloseSquareBracket | Token::CloseCurlyBracket => {
- token.to_css(dest)?;
- self.write_whitespace_if_needed(i, dest)?
- }
Token::Dimension { value, unit, .. } => {
serialize_dimension(*value, unit, dest)?;
- false
}
Token::Number { value, .. } => {
value.to_css(dest)?;
- false
}
_ => {
token.to_css(dest)?;
- matches!(token, Token::WhiteSpace(..))
}
},
};
@@ -657,24 +575,8 @@ impl<'i> TokenList<'i> {
Ok(())
}
- #[inline]
- fn write_whitespace_if_needed(&self, i: usize, dest: &mut Printer) -> Result
- where
- W: std::fmt::Write,
- {
- if !dest.minify
- && i != self.0.len() - 1
- && !matches!(
- self.0[i + 1],
- TokenOrValue::Token(Token::Comma) | TokenOrValue::Token(Token::CloseParenthesis)
- )
- {
- // Whitespace is removed during parsing, so add it back if we aren't minifying.
- dest.write_char(' ')?;
- Ok(true)
- } else {
- Ok(false)
- }
+ pub(crate) fn starts_with_whitespace(&self) -> bool {
+ matches!(self.0.get(0), Some(TokenOrValue::Token(Token::WhiteSpace(_))))
}
}
@@ -986,8 +888,18 @@ impl<'a> ToCss for Token<'a> {
int_value: *int_value,
}
.to_css(dest)?,
- Token::WhiteSpace(w) => cssparser::Token::WhiteSpace(w).to_css(dest)?,
- Token::Comment(c) => cssparser::Token::Comment(c).to_css(dest)?,
+ Token::WhiteSpace(w) => {
+ if dest.minify {
+ dest.write_char(' ')?;
+ } else {
+ dest.write_str(&w)?;
+ }
+ }
+ Token::Comment(c) => {
+ if !dest.minify {
+ cssparser::Token::Comment(c).to_css(dest)?;
+ }
+ }
Token::Colon => cssparser::Token::Colon.to_css(dest)?,
Token::Semicolon => cssparser::Token::Semicolon.to_css(dest)?,
Token::Comma => cssparser::Token::Comma.to_css(dest)?,
@@ -1081,7 +993,7 @@ impl<'a> std::hash::Hash for Token<'a> {
/// Converts a floating point value into its mantissa, exponent,
/// and sign components so that it can be hashed.
fn integer_decode(v: f32) -> (u32, i16, i8) {
- let bits: u32 = unsafe { std::mem::transmute(v) };
+ let bits: u32 = f32::to_bits(v);
let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 };
let mut exponent: i16 = ((bits >> 23) & 0xff) as i16;
let mantissa = if exponent == 0 {
@@ -1176,6 +1088,44 @@ impl<'i> TokenList<'i> {
res
}
+ pub(crate) fn get_features(&self) -> Features {
+ let mut features = Features::empty();
+ for token in &self.0 {
+ match token {
+ TokenOrValue::Color(color) => {
+ features |= color.get_features();
+ }
+ TokenOrValue::UnresolvedColor(unresolved_color) => {
+ features |= Features::SpaceSeparatedColorNotation;
+ match unresolved_color {
+ UnresolvedColor::LightDark { light, dark } => {
+ features |= Features::LightDark;
+ features |= light.get_features();
+ features |= dark.get_features();
+ }
+ _ => {}
+ }
+ }
+ TokenOrValue::Function(f) => {
+ features |= f.arguments.get_features();
+ }
+ TokenOrValue::Var(v) => {
+ if let Some(fallback) = &v.fallback {
+ features |= fallback.get_features();
+ }
+ }
+ TokenOrValue::Env(v) => {
+ if let Some(fallback) = &v.fallback {
+ features |= fallback.get_features();
+ }
+ }
+ _ => {}
+ }
+ }
+
+ features
+ }
+
/// Substitutes variables with the provided values.
#[cfg(feature = "substitute_variables")]
#[cfg_attr(docsrs, doc(cfg(feature = "substitute_variables")))]
@@ -1265,7 +1215,10 @@ impl<'i> Variable<'i> {
dest.write_str("var(")?;
self.name.to_css(dest)?;
if let Some(fallback) = &self.fallback {
- dest.delim(',', false)?;
+ dest.write_char(',')?;
+ if !fallback.starts_with_whitespace() {
+ dest.whitespace()?;
+ }
fallback.to_css(dest, is_custom_property)?;
}
dest.write_char(')')
@@ -1439,7 +1392,10 @@ impl<'i> EnvironmentVariable<'i> {
}
if let Some(fallback) = &self.fallback {
- dest.delim(',', false)?;
+ dest.write_char(',')?;
+ if !fallback.starts_with_whitespace() {
+ dest.whitespace()?;
+ }
fallback.to_css(dest, is_custom_property)?;
}
dest.write_char(')')
@@ -1556,7 +1512,7 @@ impl<'i> UnresolvedColor<'i> {
match_ignore_ascii_case! { &*f,
"rgb" => {
input.parse_nested_block(|input| {
- parser.parse_relative::(input, |input, parser| {
+ parser.parse_relative::