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 ac107a13e..95d304311 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -12,6 +12,9 @@ jobs:
- os: windows-latest
target: x86_64-pc-windows-msvc
binary: lightningcss.exe
+ - os: windows-latest
+ target: aarch64-pc-windows-msvc
+ binary: lightningcss.exe
# Mac OS
- os: macos-latest
target: x86_64-apple-darwin
@@ -45,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: |
@@ -87,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: |
@@ -106,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
@@ -130,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 }}
@@ -141,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
@@ -150,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: |
@@ -162,7 +179,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Build FreeBSD
- uses: cross-platform-actions/action@v0.23.0
+ uses: cross-platform-actions/action@v0.25.0
env:
DEBUG: napi:*
RUSTUP_HOME: /usr/local/rustup
@@ -170,7 +187,7 @@ jobs:
RUSTUP_IO_THREADS: 1
with:
operating_system: freebsd
- version: '13.2'
+ version: '14.0'
memory: 13G
cpu_count: 3
environment_variables: 'DEBUG RUSTUP_IO_THREADS'
@@ -198,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: |
@@ -229,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
@@ -247,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 db6c59aed..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,26 +53,21 @@ dependencies = [
[[package]]
name = "anstyle"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
-
-[[package]]
-name = "anyhow"
-version = "1.0.75"
+version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
+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",
@@ -80,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",
@@ -106,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"
@@ -127,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"
@@ -143,33 +141,38 @@ 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.15.0"
+version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "405bbd46590a441abe5db3e5c8af005aa42e640803fecb51912703e93e4ce8d3"
+checksum = "8dd48a6ca358df4f7000e3fb5f08738b1b91a0e5d5f862e2f77b2b14647547f5"
dependencies = [
- "ahash 0.8.7",
- "anyhow",
+ "ahash 0.8.12",
+ "browserslist-data",
"chrono",
"either",
- "indexmap 2.2.2",
- "itertools 0.12.1",
+ "itertools 0.13.0",
"nom",
- "once_cell",
- "quote",
"serde",
"serde_json",
- "string_cache",
- "string_cache_codegen",
"thiserror",
]
[[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",
@@ -178,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",
@@ -195,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",
@@ -205,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"
@@ -231,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]]
@@ -246,9 +249,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
-version = "0.4.31"
+version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
+checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [
"android-tzdata",
"iana-time-zone",
@@ -326,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"
@@ -382,7 +376,7 @@ dependencies = [
"cssparser-macros",
"dtoa-short",
"itoa",
- "phf 0.11.2",
+ "phf",
"serde",
"smallvec",
]
@@ -403,17 +397,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
- "syn 2.0.39",
+ "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.39",
+ "syn 2.0.90",
]
[[package]]
@@ -423,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",
@@ -431,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"
@@ -444,6 +438,12 @@ dependencies = [
"matches",
]
+[[package]]
+name = "diff"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
+
[[package]]
name = "difflib"
version = "0.4.0"
@@ -464,24 +464,24 @@ 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"
-version = "1.9.0"
+version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "equivalent"
@@ -491,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",
@@ -501,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"
@@ -514,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"
@@ -533,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",
]
@@ -582,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"
@@ -608,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",
@@ -631,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",
]
@@ -658,12 +660,12 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.2.2"
+version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
+checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [
"equivalent",
- "hashbrown 0.14.2",
+ "hashbrown 0.15.2",
"serde",
]
@@ -684,27 +686,18 @@ dependencies = [
[[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.12.1"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[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"
@@ -729,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.55"
+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",
@@ -774,7 +768,8 @@ dependencies = [
"cssparser-color",
"dashmap",
"data-encoding",
- "getrandom",
+ "getrandom 0.3.3",
+ "indexmap 2.7.0",
"indoc",
"itertools 0.10.5",
"jemallocator",
@@ -782,12 +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",
@@ -795,8 +792,9 @@ dependencies = [
[[package]]
name = "lightningcss-derive"
-version = "1.0.0-alpha.42"
+version = "1.0.0-alpha.43"
dependencies = [
+ "convert_case",
"proc-macro2",
"quote",
"syn 1.0.109",
@@ -804,7 +802,7 @@ dependencies = [
[[package]]
name = "lightningcss-napi"
-version = "0.1.0"
+version = "0.4.7"
dependencies = [
"crossbeam-channel",
"cssparser",
@@ -813,6 +811,7 @@ dependencies = [
"parcel_sourcemap",
"rayon",
"serde",
+ "serde-content",
"serde-detach",
"serde_bytes",
"smallvec",
@@ -841,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",
@@ -857,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"
@@ -869,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"
@@ -890,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",
@@ -911,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.39",
+ "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",
@@ -935,24 +925,18 @@ dependencies = [
"quote",
"regex",
"semver",
- "syn 2.0.39",
+ "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",
]
-[[package]]
-name = "new_debug_unreachable"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
-
[[package]]
name = "nom"
version = "7.1.3"
@@ -971,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"
@@ -998,15 +982,15 @@ checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4"
[[package]]
name = "parcel_selectors"
-version = "0.26.4"
+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",
@@ -1027,21 +1011,11 @@ dependencies = [
"vlq",
]
-[[package]]
-name = "parking_lot"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
-dependencies = [
- "lock_api",
- "parking_lot_core",
-]
-
[[package]]
name = "parking_lot_core"
-version = "0.9.9"
+version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
@@ -1051,25 +1025,16 @@ dependencies = [
]
[[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"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
-
-[[package]]
-name = "phf"
-version = "0.10.1"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
-dependencies = [
- "phf_shared 0.10.0",
-]
+checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]]
name = "phf"
@@ -1078,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]]
@@ -1107,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",
]
@@ -1117,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.39",
-]
-
-[[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]]
@@ -1142,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"
@@ -1170,32 +1110,41 @@ 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",
]
+[[package]]
+name = "pretty_assertions"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
+dependencies = [
+ "diff",
+ "yansi",
+]
+
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@@ -1222,9 +1171,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.69"
+version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
@@ -1251,13 +1200,19 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.33"
+version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+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"
@@ -1270,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",
]
@@ -1290,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",
@@ -1306,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",
@@ -1316,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",
@@ -1337,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",
@@ -1348,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",
@@ -1380,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",
@@ -1404,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"
@@ -1419,11 +1366,12 @@ dependencies = [
[[package]]
name = "schemars"
-version = "0.8.15"
+version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c"
+checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92"
dependencies = [
"dyn-clone",
+ "indexmap 2.7.0",
"schemars_derive",
"serde",
"serde_json",
@@ -1432,14 +1380,14 @@ dependencies = [
[[package]]
name = "schemars_derive"
-version = "0.8.15"
+version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
+checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
- "syn 1.0.109",
+ "syn 2.0.90",
]
[[package]]
@@ -1456,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.192"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
+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"
@@ -1481,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.192"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.90",
]
[[package]]
name = "serde_derive_internals"
-version = "0.26.0"
+version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
+checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "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"
@@ -1532,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"
@@ -1544,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",
]
@@ -1568,32 +1543,6 @@ dependencies = [
"syn 1.0.109",
]
-[[package]]
-name = "string_cache"
-version = "0.8.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
-dependencies = [
- "new_debug_unreachable",
- "once_cell",
- "parking_lot",
- "phf_shared 0.10.0",
- "precomputed-hash",
- "serde",
-]
-
-[[package]]
-name = "string_cache_codegen"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
-dependencies = [
- "phf_generator 0.10.0",
- "phf_shared 0.10.0",
- "proc-macro2",
- "quote",
-]
-
[[package]]
name = "strsim"
version = "0.10.0"
@@ -1613,9 +1562,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.39"
+version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
+checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
@@ -1630,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.39",
-]
-
-[[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",
]
@@ -1718,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"
@@ -1757,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",
@@ -1771,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.39",
+ "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",
@@ -1808,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.39",
+ "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"
@@ -1843,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]]
@@ -1858,31 +1806,32 @@ 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",
]
[[package]]
name = "windows-sys"
-version = "0.48.0"
+version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
-version = "0.48.5"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
+ "windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
@@ -1891,45 +1840,60 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.48.5"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.48.5"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
-version = "0.48.5"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
-version = "0.48.5"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.48.5"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.48.5"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+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 = "wit-bindgen-rt"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+dependencies = [
+ "bitflags 2.6.0",
+]
[[package]]
name = "wyz"
@@ -1946,22 +1910,28 @@ dependencies = [
"tap",
]
+[[package]]
+name = "yansi"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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.39",
+ "syn 2.0.90",
]
diff --git a/Cargo.toml b/Cargo.toml
index 4e27dbf59..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.55"
+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 = ["lightningcss-derive"]
-into_owned = ["static-self", "static-self/smallvec", "parcel_selectors/into_owned"]
+visitor = []
+into_owned = [
+ "static-self",
+ "static-self/smallvec",
+ "static-self/indexmap",
+ "parcel_selectors/into_owned",
+]
substitute_variables = ["visitor", "into_owned"]
[dependencies]
-serde = { version = "1.0.123", 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.4", 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.15.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.42", path = "./derive", optional = true }
-schemars = { version = "0.8.11", features = ["smallvec"], optional = true }
-static-self = { version = "0.1.0", path = "static-self", optional = true }
+lightningcss-derive = { version = "=1.0.0-alpha.43", path = "./derive" }
+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"
@@ -85,6 +101,7 @@ assert_cmd = "2.0"
assert_fs = "1.0"
predicates = "2.1"
serde_json = "1"
+pretty_assertions = "1.4.0"
[[test]]
name = "cli_integration_tests"
diff --git a/c/Cargo.toml b/c/Cargo.toml
index 443d34ba8..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.15.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/derive/Cargo.toml b/derive/Cargo.toml
index 1864748e9..ce55e5cca 100644
--- a/derive/Cargo.toml
+++ b/derive/Cargo.toml
@@ -2,7 +2,7 @@
authors = ["Devon Govett "]
name = "lightningcss-derive"
description = "Derive macros for lightningcss"
-version = "1.0.0-alpha.42"
+version = "1.0.0-alpha.43"
license = "MPL-2.0"
edition = "2021"
repository = "https://github.com/parcel-bundler/lightningcss"
@@ -14,3 +14,4 @@ proc-macro = true
syn = { version = "1.0", features = ["extra-traits"] }
quote = "1.0"
proc-macro2 = "1.0"
+convert_case = "0.6.0"
diff --git a/derive/src/lib.rs b/derive/src/lib.rs
index 00b77a266..122414911 100644
--- a/derive/src/lib.rs
+++ b/derive/src/lib.rs
@@ -1,293 +1,20 @@
-use std::collections::HashSet;
+use proc_macro::TokenStream;
-use proc_macro::{self, TokenStream};
-use proc_macro2::{Span, TokenStream as TokenStream2};
-use quote::quote;
-use syn::{
- parse::Parse, parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Field, Fields,
- GenericParam, Generics, Ident, Member, Token, Type, Visibility,
-};
+mod parse;
+mod to_css;
+mod visit;
#[proc_macro_derive(Visit, attributes(visit, skip_visit, skip_type, visit_types))]
pub fn derive_visit_children(input: TokenStream) -> TokenStream {
- let DeriveInput {
- ident,
- data,
- generics,
- attrs,
- ..
- } = parse_macro_input!(input);
-
- let options: Vec = attrs
- .iter()
- .filter_map(|attr| {
- if attr.path.is_ident("visit") {
- let opts: VisitOptions = attr.parse_args().unwrap();
- Some(opts)
- } else {
- None
- }
- })
- .collect();
-
- let visit_types = if let Some(attr) = attrs.iter().find(|attr| attr.path.is_ident("visit_types")) {
- let types: VisitTypes = attr.parse_args().unwrap();
- let types = types.types;
- Some(quote! { crate::visit_types!(#(#types)|*) })
- } else {
- None
- };
-
- if options.is_empty() {
- derive(&ident, &data, &generics, None, visit_types)
- } else {
- options
- .into_iter()
- .map(|options| derive(&ident, &data, &generics, Some(options), visit_types.clone()))
- .collect()
- }
-}
-
-fn derive(
- ident: &Ident,
- data: &Data,
- generics: &Generics,
- options: Option,
- visit_types: Option,
-) -> TokenStream {
- let mut impl_generics = generics.clone();
- let mut type_defs = quote! {};
- let generics = if let Some(VisitOptions {
- generic: Some(generic), ..
- }) = &options
- {
- let mappings = generics
- .type_params()
- .zip(generic.type_params())
- .map(|(a, b)| quote! { type #a = #b; });
- type_defs = quote! { #(#mappings)* };
- impl_generics.params.clear();
- generic
- } else {
- &generics
- };
-
- if impl_generics.lifetimes().next().is_none() {
- impl_generics.params.insert(0, parse_quote! { 'i })
- }
-
- let lifetime = impl_generics.lifetimes().next().unwrap().clone();
- let t = impl_generics.type_params().find(|g| &g.ident.to_string() == "R");
- let v = quote! { __V };
- let t = if let Some(t) = t {
- GenericParam::Type(t.ident.clone().into())
- } else {
- let t: GenericParam = parse_quote! { __T };
- impl_generics
- .params
- .push(parse_quote! { #t: crate::visitor::Visit<#lifetime, __T, #v> });
- t
- };
-
- impl_generics
- .params
- .push(parse_quote! { #v: ?Sized + crate::visitor::Visitor<#lifetime, #t> });
-
- for ty in generics.type_params() {
- let name = &ty.ident;
- impl_generics.make_where_clause().predicates.push(parse_quote! {
- #name: Visit<#lifetime, #t, #v>
- })
- }
-
- let mut seen_types = HashSet::new();
- let mut child_types = Vec::new();
- let mut visit = Vec::new();
- match data {
- Data::Struct(s) => {
- for (
- index,
- Field {
- vis, ty, ident, attrs, ..
- },
- ) in s.fields.iter().enumerate()
- {
- if attrs.iter().any(|attr| attr.path.is_ident("skip_visit")) {
- continue;
- }
-
- if matches!(ty, Type::Reference(_)) || !matches!(vis, Visibility::Public(..)) {
- continue;
- }
-
- if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) {
- seen_types.insert(ty.clone());
- child_types.push(quote! {
- <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits()
- });
- }
-
- let name = ident
- .as_ref()
- .map_or_else(|| Member::Unnamed(index.into()), |ident| Member::Named(ident.clone()));
- visit.push(quote! { self.#name.visit(visitor)?; })
- }
- }
- Data::Enum(DataEnum { variants, .. }) => {
- let variants = variants
- .iter()
- .map(|variant| {
- let name = &variant.ident;
- let mut field_names = Vec::new();
- let mut visit_fields = Vec::new();
- for (index, Field { ty, ident, attrs, .. }) in variant.fields.iter().enumerate() {
- let name = ident.as_ref().map_or_else(
- || Ident::new(&format!("_{}", index), Span::call_site()),
- |ident| ident.clone(),
- );
- field_names.push(name.clone());
-
- if matches!(ty, Type::Reference(_)) {
- continue;
- }
-
- if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) && !skip_type(&variant.attrs)
- {
- seen_types.insert(ty.clone());
- child_types.push(quote! {
- <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits()
- });
- }
-
- visit_fields.push(quote! { #name.visit(visitor)?; })
- }
-
- match variant.fields {
- Fields::Unnamed(_) => {
- quote! {
- Self::#name(#(#field_names),*) => {
- #(#visit_fields)*
- }
- }
- }
- Fields::Named(_) => {
- quote! {
- Self::#name { #(#field_names),* } => {
- #(#visit_fields)*
- }
- }
- }
- Fields::Unit => quote! {},
- }
- })
- .collect::();
-
- visit.push(quote! {
- match self {
- #variants
- _ => {}
- }
- })
- }
- _ => {}
- }
-
- if visit_types.is_none() && child_types.is_empty() {
- child_types.push(quote! { crate::visitor::VisitTypes::empty().bits() });
- }
-
- let (_, ty_generics, _) = generics.split_for_impl();
- let (impl_generics, _, where_clause) = impl_generics.split_for_impl();
-
- let self_visit = if let Some(VisitOptions {
- visit: Some(visit),
- kind: Some(kind),
- ..
- }) = &options
- {
- child_types.push(quote! { crate::visitor::VisitTypes::#kind.bits() });
-
- quote! {
- fn visit(&mut self, visitor: &mut #v) -> Result<(), #v::Error> {
- if visitor.visit_types().contains(crate::visitor::VisitTypes::#kind) {
- visitor.#visit(self)
- } else {
- self.visit_children(visitor)
- }
- }
- }
- } else {
- quote! {}
- };
-
- let child_types = visit_types.unwrap_or_else(|| {
- quote! {
- {
- #type_defs
- crate::visitor::VisitTypes::from_bits_retain(#(#child_types)|*)
- }
- }
- });
-
- let output = quote! {
- impl #impl_generics Visit<#lifetime, #t, #v> for #ident #ty_generics #where_clause {
- const CHILD_TYPES: crate::visitor::VisitTypes = #child_types;
-
- #self_visit
-
- fn visit_children(&mut self, visitor: &mut #v) -> Result<(), #v::Error> {
- if !<#lifetime, #t, #v>>::CHILD_TYPES.intersects(visitor.visit_types()) {
- return Ok(())
- }
-
- #(#visit)*
-
- Ok(())
- }
- }
- };
-
- output.into()
-}
-
-fn skip_type(attrs: &Vec) -> bool {
- attrs.iter().any(|attr| attr.path.is_ident("skip_type"))
-}
-
-struct VisitOptions {
- visit: Option,
- kind: Option,
- generic: Option,
-}
-
-impl Parse for VisitOptions {
- fn parse(input: syn::parse::ParseStream) -> syn::Result {
- let (visit, kind, comma) = if input.peek(Ident) {
- let visit: Ident = input.parse()?;
- let _: Token![,] = input.parse()?;
- let kind: Ident = input.parse()?;
- let comma: Result = input.parse();
- (Some(visit), Some(kind), comma.is_ok())
- } else {
- (None, None, true)
- };
- let generic: Option = if comma { Some(input.parse()?) } else { None };
- Ok(Self { visit, kind, generic })
- }
+ visit::derive_visit_children(input)
}
-struct VisitTypes {
- types: Vec,
+#[proc_macro_derive(Parse, attributes(css))]
+pub fn derive_parse(input: TokenStream) -> TokenStream {
+ parse::derive_parse(input)
}
-impl Parse for VisitTypes {
- fn parse(input: syn::parse::ParseStream) -> syn::Result {
- let first: Ident = input.parse()?;
- let mut types = vec![first];
- while input.parse::().is_ok() {
- let id: Ident = input.parse()?;
- types.push(id);
- }
- Ok(Self { types })
- }
+#[proc_macro_derive(ToCss, attributes(css))]
+pub fn derive_to_css(input: TokenStream) -> TokenStream {
+ to_css::derive_to_css(input)
}
diff --git a/derive/src/parse.rs b/derive/src/parse.rs
new file mode 100644
index 000000000..995b344e3
--- /dev/null
+++ b/derive/src/parse.rs
@@ -0,0 +1,213 @@
+use convert_case::Casing;
+use proc_macro::{self, TokenStream};
+use proc_macro2::{Literal, Span, TokenStream as TokenStream2};
+use quote::quote;
+use syn::{
+ parse::Parse, parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Fields, Ident, Token,
+};
+
+pub fn derive_parse(input: TokenStream) -> TokenStream {
+ let DeriveInput {
+ ident,
+ data,
+ mut generics,
+ attrs,
+ ..
+ } = parse_macro_input!(input);
+ let opts = CssOptions::parse_attributes(&attrs).unwrap();
+ let cloned_generics = generics.clone();
+ let (_, ty_generics, _) = cloned_generics.split_for_impl();
+
+ if generics.lifetimes().next().is_none() {
+ generics.params.insert(0, parse_quote! { 'i })
+ }
+
+ let lifetime = generics.lifetimes().next().unwrap().clone();
+ let (impl_generics, _, where_clause) = generics.split_for_impl();
+
+ let imp = match &data {
+ Data::Enum(data) => derive_enum(&data, &ident, &opts),
+ _ => todo!(),
+ };
+
+ let output = quote! {
+ impl #impl_generics Parse<#lifetime> for #ident #ty_generics #where_clause {
+ fn parse<'t>(input: &mut Parser<#lifetime, 't>) -> Result<#lifetime, ParserError<#lifetime>>> {
+ #imp
+ }
+ }
+ };
+
+ output.into()
+}
+
+fn derive_enum(data: &DataEnum, ident: &Ident, opts: &CssOptions) -> TokenStream2 {
+ let mut idents = Vec::new();
+ let mut non_idents = Vec::new();
+ for (index, variant) in data.variants.iter().enumerate() {
+ let name = &variant.ident;
+ let fields = variant
+ .fields
+ .iter()
+ .enumerate()
+ .map(|(index, field)| {
+ field.ident.as_ref().map_or_else(
+ || Ident::new(&format!("_{}", index), Span::call_site()),
+ |ident| ident.clone(),
+ )
+ })
+ .collect::<_>>();
+
+ let mut expr = match &variant.fields {
+ Fields::Unit => {
+ idents.push((
+ Literal::string(&variant.ident.to_string().to_case(opts.case)),
+ name.clone(),
+ ));
+ continue;
+ }
+ Fields::Named(_) => {
+ quote! {
+ return Ok(#ident::#name { #(#fields),* })
+ }
+ }
+ Fields::Unnamed(_) => {
+ quote! {
+ return Ok(#ident::#name(#(#fields),*))
+ }
+ }
+ };
+
+ // Group multiple ident branches together.
+ if !idents.is_empty() {
+ if idents.len() == 1 {
+ let (s, name) = idents.remove(0);
+ non_idents.push(quote! {
+ if input.try_parse(|input| input.expect_ident_matching(#s)).is_ok() {
+ return Ok(#ident::#name)
+ }
+ });
+ } else {
+ let matches = idents
+ .iter()
+ .map(|(s, name)| {
+ quote! {
+ #s => return Ok(#ident::#name),
+ }
+ })
+ .collect::<_>>();
+ non_idents.push(quote! {
+ {
+ let state = input.state();
+ if let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {
+ cssparser::match_ignore_ascii_case! { &*ident,
+ #(#matches)*
+ _ => {}
+ }
+ input.reset(&state);
+ }
+ }
+ });
+ idents.clear();
+ }
+ }
+
+ let is_last = index == data.variants.len() - 1;
+
+ for (index, field) in variant.fields.iter().enumerate().rev() {
+ let ty = &field.ty;
+ let field_name = field.ident.as_ref().map_or_else(
+ || Ident::new(&format!("_{}", index), Span::call_site()),
+ |ident| ident.clone(),
+ );
+ if is_last {
+ expr = quote! {
+ let #field_name = <#ty>::parse(input)?;
+ #expr
+ };
+ } else {
+ expr = quote! {
+ if let Ok(#field_name) = input.try_parse(<#ty>::parse) {
+ #expr
+ }
+ };
+ }
+ }
+
+ non_idents.push(expr);
+ }
+
+ let idents = if idents.is_empty() {
+ quote! {}
+ } else if idents.len() == 1 {
+ let (s, name) = idents.remove(0);
+ quote! {
+ input.expect_ident_matching(#s)?;
+ Ok(#ident::#name)
+ }
+ } else {
+ let idents = idents
+ .into_iter()
+ .map(|(s, name)| {
+ quote! {
+ #s => Ok(#ident::#name),
+ }
+ })
+ .collect::<_>>();
+ quote! {
+ let location = input.current_source_location();
+ let ident = input.expect_ident()?;
+ cssparser::match_ignore_ascii_case! { &*ident,
+ #(#idents)*
+ _ => Err(location.new_unexpected_token_error(
+ cssparser::Token::Ident(ident.clone())
+ ))
+ }
+ }
+ };
+
+ let output = quote! {
+ #(#non_idents)*
+ #idents
+ };
+
+ output.into()
+}
+
+pub struct CssOptions {
+ pub case: convert_case::Case,
+}
+
+impl CssOptions {
+ pub fn parse_attributes(attrs: &Vec) -> syn::Result {
+ for attr in attrs {
+ if attr.path.is_ident("css") {
+ let opts: CssOptions = attr.parse_args()?;
+ return Ok(opts);
+ }
+ }
+
+ Ok(CssOptions {
+ case: convert_case::Case::Kebab,
+ })
+ }
+}
+
+impl Parse for CssOptions {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result {
+ let mut case = convert_case::Case::Kebab;
+ while !input.is_empty() {
+ let k: Ident = input.parse()?;
+ let _: Token![=] = input.parse()?;
+ let v: Ident = input.parse()?;
+
+ if k == "case" {
+ if v == "lower" {
+ case = convert_case::Case::Flat;
+ }
+ }
+ }
+
+ Ok(Self { case })
+ }
+}
diff --git a/derive/src/to_css.rs b/derive/src/to_css.rs
new file mode 100644
index 000000000..739a16d40
--- /dev/null
+++ b/derive/src/to_css.rs
@@ -0,0 +1,156 @@
+use convert_case::Casing;
+use proc_macro::{self, TokenStream};
+use proc_macro2::{Literal, Span, TokenStream as TokenStream2};
+use quote::quote;
+use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields, Ident, Type};
+
+use crate::parse::CssOptions;
+
+pub fn derive_to_css(input: TokenStream) -> TokenStream {
+ let DeriveInput {
+ ident,
+ data,
+ generics,
+ attrs,
+ ..
+ } = parse_macro_input!(input);
+
+ let opts = CssOptions::parse_attributes(&attrs).unwrap();
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+ let imp = match &data {
+ Data::Enum(data) => derive_enum(&data, &opts),
+ _ => todo!(),
+ };
+
+ let output = quote! {
+ impl #impl_generics ToCss for #ident #ty_generics #where_clause {
+ fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
+ where
+ W: std::fmt::Write,
+ {
+ #imp
+ }
+ }
+ };
+
+ output.into()
+}
+
+fn derive_enum(data: &DataEnum, opts: &CssOptions) -> TokenStream2 {
+ let variants = data
+ .variants
+ .iter()
+ .map(|variant| {
+ let name = &variant.ident;
+ let fields = variant
+ .fields
+ .iter()
+ .enumerate()
+ .map(|(index, field)| {
+ field.ident.as_ref().map_or_else(
+ || Ident::new(&format!("_{}", index), Span::call_site()),
+ |ident| ident.clone(),
+ )
+ })
+ .collect::<_>>();
+
+ #[derive(PartialEq)]
+ enum NeedsSpace {
+ Yes,
+ No,
+ Maybe,
+ }
+
+ let mut needs_space = NeedsSpace::No;
+ let mut fields_iter = variant.fields.iter().zip(fields.iter()).peekable();
+ let mut writes = Vec::new();
+ let mut has_needs_space = false;
+ while let Some((field, name)) = fields_iter.next() {
+ writes.push(if fields.len() > 1 {
+ let space = match needs_space {
+ NeedsSpace::Yes => quote! { dest.write_char(' ')?; },
+ NeedsSpace::No => quote! {},
+ NeedsSpace::Maybe => {
+ has_needs_space = true;
+ quote! {
+ if needs_space {
+ dest.write_char(' ')?;
+ }
+ }
+ }
+ };
+
+ if is_option(&field.ty) {
+ needs_space = NeedsSpace::Maybe;
+ let after_space = if matches!(fields_iter.peek(), Some((field, _)) if !is_option(&field.ty)) {
+ // If the next field is non-optional, just insert the space here.
+ needs_space = NeedsSpace::No;
+ quote! { dest.write_char(' ')?; }
+ } else {
+ quote! {}
+ };
+ quote! {
+ if let Some(v) = #name {
+ #space
+ v.to_css(dest)?;
+ #after_space
+ }
+ }
+ } else {
+ needs_space = NeedsSpace::Yes;
+ quote! {
+ #space
+ #name.to_css(dest)?;
+ }
+ }
+ } else {
+ quote! { #name.to_css(dest) }
+ });
+ }
+
+ if writes.len() > 1 {
+ writes.push(quote! { Ok(()) });
+ }
+
+ if has_needs_space {
+ writes.insert(0, quote! { let mut needs_space = false });
+ }
+
+ match variant.fields {
+ Fields::Unit => {
+ let s = Literal::string(&variant.ident.to_string().to_case(opts.case));
+ quote! {
+ Self::#name => dest.write_str(#s)
+ }
+ }
+ Fields::Named(_) => {
+ quote! {
+ Self::#name { #(#fields),* } => {
+ #(#writes)*
+ }
+ }
+ }
+ Fields::Unnamed(_) => {
+ quote! {
+ Self::#name(#(#fields),*) => {
+ #(#writes)*
+ }
+ }
+ }
+ }
+ })
+ .collect::<_>>();
+
+ let output = quote! {
+ match self {
+ #(#variants),*
+ }
+ };
+
+ output.into()
+}
+
+fn is_option(ty: &Type) -> bool {
+ matches!(&ty, Type::Path(p) if p.qself.is_none() && p.path.segments.iter().next().unwrap().ident == "Option")
+}
diff --git a/derive/src/visit.rs b/derive/src/visit.rs
new file mode 100644
index 000000000..020933644
--- /dev/null
+++ b/derive/src/visit.rs
@@ -0,0 +1,292 @@
+use std::collections::HashSet;
+
+use proc_macro::{self, TokenStream};
+use proc_macro2::{Span, TokenStream as TokenStream2};
+use quote::quote;
+use syn::{
+ parse::Parse, parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Field, Fields,
+ GenericParam, Generics, Ident, Member, Token, Type, Visibility,
+};
+
+pub fn derive_visit_children(input: TokenStream) -> TokenStream {
+ let DeriveInput {
+ ident,
+ data,
+ generics,
+ attrs,
+ ..
+ } = parse_macro_input!(input);
+
+ let options: Vec = attrs
+ .iter()
+ .filter_map(|attr| {
+ if attr.path.is_ident("visit") {
+ let opts: VisitOptions = attr.parse_args().unwrap();
+ Some(opts)
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ let visit_types = if let Some(attr) = attrs.iter().find(|attr| attr.path.is_ident("visit_types")) {
+ let types: VisitTypes = attr.parse_args().unwrap();
+ let types = types.types;
+ Some(quote! { crate::visit_types!(#(#types)|*) })
+ } else {
+ None
+ };
+
+ if options.is_empty() {
+ derive(&ident, &data, &generics, None, visit_types)
+ } else {
+ options
+ .into_iter()
+ .map(|options| derive(&ident, &data, &generics, Some(options), visit_types.clone()))
+ .collect()
+ }
+}
+
+fn derive(
+ ident: &Ident,
+ data: &Data,
+ generics: &Generics,
+ options: Option,
+ visit_types: Option,
+) -> TokenStream {
+ let mut impl_generics = generics.clone();
+ let mut type_defs = quote! {};
+ let generics = if let Some(VisitOptions {
+ generic: Some(generic), ..
+ }) = &options
+ {
+ let mappings = generics
+ .type_params()
+ .zip(generic.type_params())
+ .map(|(a, b)| quote! { type #a = #b; });
+ type_defs = quote! { #(#mappings)* };
+ impl_generics.params.clear();
+ generic
+ } else {
+ &generics
+ };
+
+ if impl_generics.lifetimes().next().is_none() {
+ impl_generics.params.insert(0, parse_quote! { 'i })
+ }
+
+ let lifetime = impl_generics.lifetimes().next().unwrap().clone();
+ let t = impl_generics.type_params().find(|g| &g.ident.to_string() == "R");
+ let v = quote! { __V };
+ let t = if let Some(t) = t {
+ GenericParam::Type(t.ident.clone().into())
+ } else {
+ let t: GenericParam = parse_quote! { __T };
+ impl_generics
+ .params
+ .push(parse_quote! { #t: crate::visitor::Visit<#lifetime, __T, #v> });
+ t
+ };
+
+ impl_generics
+ .params
+ .push(parse_quote! { #v: ?Sized + crate::visitor::Visitor<#lifetime, #t> });
+
+ for ty in generics.type_params() {
+ let name = &ty.ident;
+ impl_generics.make_where_clause().predicates.push(parse_quote! {
+ #name: Visit<#lifetime, #t, #v>
+ })
+ }
+
+ let mut seen_types = HashSet::new();
+ let mut child_types = Vec::new();
+ let mut visit = Vec::new();
+ match data {
+ Data::Struct(s) => {
+ for (
+ index,
+ Field {
+ vis, ty, ident, attrs, ..
+ },
+ ) in s.fields.iter().enumerate()
+ {
+ if attrs.iter().any(|attr| attr.path.is_ident("skip_visit")) {
+ continue;
+ }
+
+ if matches!(ty, Type::Reference(_)) || !matches!(vis, Visibility::Public(..)) {
+ continue;
+ }
+
+ if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) {
+ seen_types.insert(ty.clone());
+ child_types.push(quote! {
+ <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits()
+ });
+ }
+
+ let name = ident
+ .as_ref()
+ .map_or_else(|| Member::Unnamed(index.into()), |ident| Member::Named(ident.clone()));
+ visit.push(quote! { self.#name.visit(visitor)?; })
+ }
+ }
+ Data::Enum(DataEnum { variants, .. }) => {
+ let variants = variants
+ .iter()
+ .map(|variant| {
+ let name = &variant.ident;
+ let mut field_names = Vec::new();
+ let mut visit_fields = Vec::new();
+ for (index, Field { ty, ident, attrs, .. }) in variant.fields.iter().enumerate() {
+ let name = ident.as_ref().map_or_else(
+ || Ident::new(&format!("_{}", index), Span::call_site()),
+ |ident| ident.clone(),
+ );
+ field_names.push(name.clone());
+
+ if matches!(ty, Type::Reference(_)) {
+ continue;
+ }
+
+ if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) && !skip_type(&variant.attrs)
+ {
+ seen_types.insert(ty.clone());
+ child_types.push(quote! {
+ <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits()
+ });
+ }
+
+ visit_fields.push(quote! { #name.visit(visitor)?; })
+ }
+
+ match variant.fields {
+ Fields::Unnamed(_) => {
+ quote! {
+ Self::#name(#(#field_names),*) => {
+ #(#visit_fields)*
+ }
+ }
+ }
+ Fields::Named(_) => {
+ quote! {
+ Self::#name { #(#field_names),* } => {
+ #(#visit_fields)*
+ }
+ }
+ }
+ Fields::Unit => quote! {},
+ }
+ })
+ .collect::();
+
+ visit.push(quote! {
+ match self {
+ #variants
+ _ => {}
+ }
+ })
+ }
+ _ => {}
+ }
+
+ if visit_types.is_none() && child_types.is_empty() {
+ child_types.push(quote! { crate::visitor::VisitTypes::empty().bits() });
+ }
+
+ let (_, ty_generics, _) = generics.split_for_impl();
+ let (impl_generics, _, where_clause) = impl_generics.split_for_impl();
+
+ let self_visit = if let Some(VisitOptions {
+ visit: Some(visit),
+ kind: Some(kind),
+ ..
+ }) = &options
+ {
+ child_types.push(quote! { crate::visitor::VisitTypes::#kind.bits() });
+
+ quote! {
+ fn visit(&mut self, visitor: &mut #v) -> Result<(), #v::Error> {
+ if visitor.visit_types().contains(crate::visitor::VisitTypes::#kind) {
+ visitor.#visit(self)
+ } else {
+ self.visit_children(visitor)
+ }
+ }
+ }
+ } else {
+ quote! {}
+ };
+
+ let child_types = visit_types.unwrap_or_else(|| {
+ quote! {
+ {
+ #type_defs
+ crate::visitor::VisitTypes::from_bits_retain(#(#child_types)|*)
+ }
+ }
+ });
+
+ let output = quote! {
+ impl #impl_generics Visit<#lifetime, #t, #v> for #ident #ty_generics #where_clause {
+ const CHILD_TYPES: crate::visitor::VisitTypes = #child_types;
+
+ #self_visit
+
+ fn visit_children(&mut self, visitor: &mut #v) -> Result<(), #v::Error> {
+ if !<#lifetime, #t, #v>>::CHILD_TYPES.intersects(visitor.visit_types()) {
+ return Ok(())
+ }
+
+ #(#visit)*
+
+ Ok(())
+ }
+ }
+ };
+
+ output.into()
+}
+
+fn skip_type(attrs: &Vec) -> bool {
+ attrs.iter().any(|attr| attr.path.is_ident("skip_type"))
+}
+
+struct VisitOptions {
+ visit: Option,
+ kind: Option,
+ generic: Option,
+}
+
+impl Parse for VisitOptions {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result {
+ let (visit, kind, comma) = if input.peek(Ident) {
+ let visit: Ident = input.parse()?;
+ let _: Token![,] = input.parse()?;
+ let kind: Ident = input.parse()?;
+ let comma: Result = input.parse();
+ (Some(visit), Some(kind), comma.is_ok())
+ } else {
+ (None, None, true)
+ };
+ let generic: Option = if comma { Some(input.parse()?) } else { None };
+ Ok(Self { visit, kind, generic })
+ }
+}
+
+struct VisitTypes {
+ types: Vec,
+}
+
+impl Parse for VisitTypes {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result {
+ let first: Ident = input.parse()?;
+ let mut types = vec![first];
+ while input.parse::().is_ok() {
+ let id: Ident = input.parse()?;
+ types.push(id);
+ }
+ Ok(Self { types })
+ }
+}
diff --git a/napi/Cargo.toml b/napi/Cargo.toml
index 3360a9cdf..789062ea6 100644
--- a/napi/Cargo.toml
+++ b/napi/Cargo.toml
@@ -1,7 +1,7 @@
[package]
authors = ["Devon Govett "]
name = "lightningcss-napi"
-version = "0.1.0"
+version = "0.4.7"
description = "Node-API bindings for Lightning CSS"
license = "MPL-2.0"
repository = "https://github.com/parcel-bundler/lightningcss"
@@ -13,13 +13,21 @@ visitor = ["lightningcss/visitor"]
bundler = ["dep:crossbeam-channel", "dep:rayon"]
[dependencies]
-serde = { version = "1.0.123", features = ["derive"] }
+serde = { version = "1.0.201", features = ["derive"] }
+serde-content = { version = "0.1.2", features = ["serde"] }
serde_bytes = "0.11.5"
cssparser = "0.33.0"
-lightningcss = { version = "1.0.0-alpha.54", path = "../", features = ["nodejs", "serde"] }
+lightningcss = { version = "1.0.0-alpha.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 cc3ce78ed..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)))
}
}
}
@@ -605,6 +610,11 @@ enum CssModulesOption {
struct CssModulesConfig {
pattern: Option,
dashed_idents: Option,
+ animation: Option,
+ container: Option,
+ grid: Option,
+ custom_idents: Option,
+ pure: Option,
}
#[cfg(feature = "bundler")]
@@ -713,6 +723,11 @@ fn compile<'i>(
Default::default()
},
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 {
@@ -840,6 +855,11 @@ fn compile_bundle<
Default::default()
},
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 4f9e8b758..b5c7505c3 100644
--- a/node/Cargo.toml
+++ b/node/Cargo.toml
@@ -9,8 +9,13 @@ publish = false
crate-type = ["cdylib"]
[dependencies]
-lightningcss-napi = { version = "0.1.0", 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 25c9dd7f5..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.
@@ -504,6 +520,10 @@ export type TokenOrValue =
| {
type: "dashed-ident";
value: String;
+ }
+ | {
+ type: "animation-name";
+ value: AnimationName;
};
/**
* A raw CSS token.
@@ -1114,6 +1134,21 @@ export type Time =
type: "milliseconds";
value: number;
};
+/**
+ * A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property.
+ */
+export type AnimationName =
+ | {
+ type: "none";
+ }
+ | {
+ type: "ident";
+ value: String;
+ }
+ | {
+ type: "string";
+ value: String;
+ };
/**
* A CSS environment variable name.
*/
@@ -1935,6 +1970,21 @@ export type PropertyId =
property: "animation-fill-mode";
vendorPrefix: VendorPrefix;
}
+ | {
+ property: "animation-composition";
+ }
+ | {
+ property: "animation-timeline";
+ }
+ | {
+ property: "animation-range-start";
+ }
+ | {
+ property: "animation-range-end";
+ }
+ | {
+ property: "animation-range";
+ }
| {
property: "animation";
vendorPrefix: VendorPrefix;
@@ -2066,6 +2116,12 @@ export type PropertyId =
property: "text-size-adjust";
vendorPrefix: VendorPrefix;
}
+ | {
+ property: "direction";
+ }
+ | {
+ property: "unicode-bidi";
+ }
| {
property: "box-decoration-break";
vendorPrefix: VendorPrefix;
@@ -2302,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";
}
@@ -3277,6 +3343,26 @@ export type Declaration =
value: AnimationFillMode[];
vendorPrefix: VendorPrefix;
}
+ | {
+ property: "animation-composition";
+ value: AnimationComposition[];
+ }
+ | {
+ property: "animation-timeline";
+ value: AnimationTimeline[];
+ }
+ | {
+ property: "animation-range-start";
+ value: AnimationRangeStart[];
+ }
+ | {
+ property: "animation-range-end";
+ value: AnimationRangeEnd[];
+ }
+ | {
+ property: "animation-range";
+ value: AnimationRange[];
+ }
| {
property: "animation";
value: Animation[];
@@ -3445,6 +3531,14 @@ export type Declaration =
value: TextSizeAdjust;
vendorPrefix: VendorPrefix;
}
+ | {
+ property: "direction";
+ value: Direction2;
+ }
+ | {
+ property: "unicode-bidi";
+ value: UnicodeBidi;
+ }
| {
property: "box-decoration-break";
value: BoxDecorationBreak;
@@ -3751,12 +3845,29 @@ 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;
+ }
| {
property: "unparsed";
value: UnparsedProperty;
@@ -4158,23 +4269,30 @@ export type PositionComponentFor_VerticalPositionKeyword =
* See [RadialGradient](RadialGradient).
*/
export type EndingShape =
- | {
- type: "circle";
- value: Circle;
- }
| {
type: "ellipse";
value: Ellipse;
+ }
+ | {
+ type: "circle";
+ value: Circle;
};
/**
- * A circle ending shape for a `radial-gradient()`.
+ * An ellipse ending shape for a `radial-gradient()`.
*
* See [RadialGradient](RadialGradient).
*/
-export type Circle =
+export type Ellipse =
| {
- type: "radius";
- value: Length;
+ type: "size";
+ /**
+ * The x-radius of the ellipse.
+ */
+ x: DimensionPercentageFor_LengthValue;
+ /**
+ * The y-radius of the ellipse.
+ */
+ y: DimensionPercentageFor_LengthValue;
}
| {
type: "extent";
@@ -4187,21 +4305,14 @@ export type Circle =
*/
export type ShapeExtent = "closest-side" | "farthest-side" | "closest-corner" | "farthest-corner";
/**
- * An ellipse ending shape for a `radial-gradient()`.
+ * A circle ending shape for a `radial-gradient()`.
*
* See [RadialGradient](RadialGradient).
*/
-export type Ellipse =
+export type Circle =
| {
- type: "size";
- /**
- * The x-radius of the ellipse.
- */
- x: DimensionPercentageFor_LengthValue;
- /**
- * The y-radius of the ellipse.
- */
- y: DimensionPercentageFor_LengthValue;
+ type: "radius";
+ value: Length;
}
| {
type: "extent";
@@ -4366,11 +4477,11 @@ export type WebKitGradientPointComponentFor_HorizontalPositionKeyword =
*/
export type NumberOrPercentage =
| {
- type: "percentage";
+ type: "number";
value: number;
}
| {
- type: "number";
+ type: "percentage";
value: number;
};
/**
@@ -4681,13 +4792,13 @@ export type RectFor_LengthOrNumber = [LengthOrNumber, LengthOrNumber, LengthOrNu
* Either a [``](https://www.w3.org/TR/css-values-4/#lengths) or a [``](https://www.w3.org/TR/css-values-4/#numbers).
*/
export type LengthOrNumber =
- | {
- type: "length";
- value: Length;
- }
| {
type: "number";
value: number;
+ }
+ | {
+ type: "length";
+ value: Length;
};
/**
* A single [border-image-repeat](https://www.w3.org/TR/css-backgrounds-3/#border-image-repeat) keyword.
@@ -5120,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 =
| {
@@ -5424,21 +5535,6 @@ export type StepPosition =
| {
type: "jump-both";
};
-/**
- * A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property.
- */
-export type AnimationName =
- | {
- type: "none";
- }
- | {
- type: "ident";
- value: String;
- }
- | {
- type: "string";
- value: String;
- };
/**
* A value for the [animation-iteration-count](https://drafts.csswg.org/css-animations/#animation-iteration-count) property.
*/
@@ -5462,6 +5558,75 @@ export type AnimationPlayState = "running" | "paused";
* A value for the [animation-fill-mode](https://drafts.csswg.org/css-animations/#animation-fill-mode) property.
*/
export type AnimationFillMode = "none" | "forwards" | "backwards" | "both";
+/**
+ * A value for the [animation-composition](https://drafts.csswg.org/css-animations-2/#animation-composition) property.
+ */
+export type AnimationComposition = "replace" | "add" | "accumulate";
+/**
+ * A value for the [animation-timeline](https://drafts.csswg.org/css-animations-2/#animation-timeline) property.
+ */
+export type AnimationTimeline =
+ | {
+ type: "auto";
+ }
+ | {
+ type: "none";
+ }
+ | {
+ type: "dashed-ident";
+ value: String;
+ }
+ | {
+ type: "scroll";
+ value: ScrollTimeline;
+ }
+ | {
+ type: "view";
+ value: ViewTimeline;
+ };
+/**
+ * A scroll axis, used in the `scroll()` function.
+ */
+export type ScrollAxis = "block" | "inline" | "x" | "y";
+/**
+ * A scroller, used in the `scroll()` function.
+ */
+export type Scroller = "root" | "nearest" | "self";
+/**
+ * A generic value that represents a value with two components, e.g. a border radius.
+ *
+ * When serialized, only a single component will be written if both are equal.
+ *
+ * @minItems 2
+ * @maxItems 2
+ */
+export type Size2DFor_LengthPercentageOrAuto = [LengthPercentageOrAuto, LengthPercentageOrAuto];
+/**
+ * A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start) property.
+ */
+export type AnimationRangeStart = AnimationAttachmentRange;
+/**
+ * A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start) or [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property.
+ */
+export type AnimationAttachmentRange =
+ "normal" | DimensionPercentageFor_LengthValue | {
+ /**
+ * The name of the timeline range.
+ */
+ name: TimelineRangeName;
+ /**
+ * The offset from the start of the named timeline range.
+ */
+ offset: DimensionPercentageFor_LengthValue;
+ };
+/**
+ * A [view progress timeline range](https://drafts.csswg.org/scroll-animations/#view-timelines-ranges)
+ */
+export type TimelineRangeName = "cover" | "contain" | "entry" | "exit" | "entry-crossing" | "exit-crossing";
+/**
+ * A value for the [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property.
+ */
+export type AnimationRangeEnd = AnimationAttachmentRange;
/**
* An individual [transform function](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#two-d-transform-functions).
*/
@@ -5577,7 +5742,7 @@ export type Transform =
/**
* A value for the [transform-style](https://drafts.csswg.org/css-transforms-2/#transform-style-property) property.
*/
-export type TransformStyle = "flat" | "preserve-3d";
+export type TransformStyle = "flat" | "preserve3d";
/**
* A value for the [transform-box](https://drafts.csswg.org/css-transforms-1/#transform-box) property.
*/
@@ -5597,6 +5762,44 @@ export type Perspective =
type: "length";
value: Length;
};
+/**
+ * A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property.
+ */
+export type Translate =
+ | "none"
+ | {
+ /**
+ * The x translation.
+ */
+ x: DimensionPercentageFor_LengthValue;
+ /**
+ * The y translation.
+ */
+ y: DimensionPercentageFor_LengthValue;
+ /**
+ * The z translation.
+ */
+ z: Length;
+ };
+/**
+ * A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property.
+ */
+export type Scale =
+ | "none"
+ | {
+ /**
+ * Scale on the x axis.
+ */
+ x: NumberOrPercentage;
+ /**
+ * Scale on the y axis.
+ */
+ y: NumberOrPercentage;
+ /**
+ * Scale on the z axis.
+ */
+ z: NumberOrPercentage;
+ };
/**
* Defines how text case should be transformed in the [text-transform](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-transform-property) property.
*/
@@ -5729,6 +5932,14 @@ export type TextSizeAdjust =
type: "percentage";
value: number;
};
+/**
+ * A value for the [direction](https://drafts.csswg.org/css-writing-modes-3/#direction) property.
+ */
+export type Direction2 = "ltr" | "rtl";
+/**
+ * A value for the [unicode-bidi](https://drafts.csswg.org/css-writing-modes-3/#unicode-bidi) property.
+ */
+export type UnicodeBidi = "normal" | "embed" | "isolate" | "bidi-override" | "isolate-override" | "plaintext";
/**
* A value for the [box-decoration-break](https://www.w3.org/TR/css-break-3/#break-decoration) property.
*/
@@ -5927,9 +6138,6 @@ export type MarkerSide = "match-self" | "match-parent";
* An SVG [``](https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint) value used in the `fill` and `stroke` properties.
*/
export type SVGPaint =
- | {
- type: "none";
- }
| {
/**
* A fallback to be used used in case the paint server cannot be resolved.
@@ -5950,6 +6158,9 @@ export type SVGPaint =
}
| {
type: "context-stroke";
+ }
+ | {
+ type: "none";
};
/**
* A fallback for an SVG paint in case a paint server `url()` cannot be resolved.
@@ -6222,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.
*/
@@ -6234,6 +6445,29 @@ 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).
+ */
+export type CSSWideKeyword = "initial" | "inherit" | "unset" | "revert" | "revert-layer";
/**
* A CSS custom property name.
*/
@@ -6557,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";
/**
@@ -6637,6 +6888,12 @@ export type PseudoElement =
| {
kind: "first-letter";
}
+ | {
+ kind: "details-content";
+ }
+ | {
+ kind: "target-text";
+ }
| {
kind: "selection";
vendorPrefix: VendorPrefix;
@@ -6688,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";
@@ -6756,6 +7032,10 @@ export type KeyframeSelector =
}
| {
type: "to";
+ }
+ | {
+ type: "timeline-range-percentage";
+ value: TimelineRangePercentage;
};
/**
* KeyframesName
@@ -6936,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).
*/
@@ -6982,6 +7273,10 @@ export type ParsedComponent =
type: "length-percentage";
value: DimensionPercentageFor_LengthValue;
}
+ | {
+ type: "string";
+ value: String;
+ }
| {
type: "color";
value: CssColor;
@@ -7040,8 +7335,8 @@ export type ParsedComponent =
};
}
| {
- type: "token";
- value: Token;
+ type: "token-list";
+ value: TokenOrValue[];
};
/**
* A [multiplier](https://drafts.css-houdini.org/css-properties-values-api/#multipliers) for a [SyntaxComponent](SyntaxComponent). Indicates whether and how the component may be repeated.
@@ -7083,6 +7378,9 @@ export type SyntaxComponentKind =
| {
type: "length-percentage";
}
+ | {
+ type: "string";
+ }
| {
type: "color";
}
@@ -7142,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.
@@ -7215,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;
@@ -7233,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;
/**
@@ -8084,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 {
@@ -8103,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 {
@@ -8132,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 {
/**
@@ -8145,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 {
/**
@@ -8158,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 {
/**
@@ -8420,6 +8847,45 @@ export interface Transition {
*/
timingFunction: EasingFunction;
}
+/**
+ * The [scroll()](https://drafts.csswg.org/scroll-animations-1/#scroll-notation) function.
+ */
+export interface ScrollTimeline {
+ /**
+ * Specifies which axis of the scroll container to use as the progress for the timeline.
+ */
+ axis: ScrollAxis;
+ /**
+ * Specifies which element to use as the scroll container.
+ */
+ scroller: Scroller;
+}
+/**
+ * The [view()](https://drafts.csswg.org/scroll-animations-1/#view-notation) function.
+ */
+export interface ViewTimeline {
+ /**
+ * Specifies which axis of the scroll container to use as the progress for the timeline.
+ */
+ axis: ScrollAxis;
+ /**
+ * Provides an adjustment of the view progress visibility range.
+ */
+ inset: Size2DFor_LengthPercentageOrAuto;
+}
+/**
+ * A value for the [animation-range](https://drafts.csswg.org/scroll-animations/#animation-range) shorthand property.
+ */
+export interface AnimationRange {
+ /**
+ * The end of the animation's attachment range.
+ */
+ end: AnimationRangeEnd;
+ /**
+ * The start of the animation's attachment range.
+ */
+ start: AnimationRangeStart;
+}
/**
* A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property.
*/
@@ -8452,6 +8918,10 @@ export interface Animation {
* The current play state of the animation.
*/
playState: AnimationPlayState;
+ /**
+ * The animation timeline.
+ */
+ timeline: AnimationTimeline;
/**
* The easing function for the animation.
*/
@@ -8489,23 +8959,6 @@ export interface Matrix3DForFloat {
m43: number;
m44: number;
}
-/**
- * A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property.
- */
-export interface Translate {
- /**
- * The x translation.
- */
- x: DimensionPercentageFor_LengthValue;
- /**
- * The y translation.
- */
- y: DimensionPercentageFor_LengthValue;
- /**
- * The z translation.
- */
- z: Length;
-}
/**
* A value for the [rotate](https://drafts.csswg.org/css-transforms-2/#propdef-rotate) property.
*/
@@ -8527,23 +8980,6 @@ export interface Rotate {
*/
z: number;
}
-/**
- * A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property.
- */
-export interface Scale {
- /**
- * Scale on the x axis.
- */
- x: NumberOrPercentage;
- /**
- * Scale on the y axis.
- */
- y: NumberOrPercentage;
- /**
- * Scale on the z axis.
- */
- z: NumberOrPercentage;
-}
/**
* A value for the [text-transform](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-transform-property) property.
*/
@@ -8937,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.
*/
@@ -8973,6 +9422,19 @@ export interface Keyframe {
*/
selectors: KeyframeSelector[];
}
+/**
+ * A percentage of a given timeline range
+ */
+export interface TimelineRangePercentage {
+ /**
+ * The name of the timeline range.
+ */
+ name: TimelineRangeName;
+ /**
+ * The percentage progress between the start and end of the range.
+ */
+ percentage: number;
+}
/**
* A [@font-face](https://drafts.csswg.org/css-fonts/#font-face-rule) rule.
*/
@@ -9048,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.
*/
@@ -9180,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.
*/
@@ -9293,7 +9806,7 @@ export interface ContainerRule {
/**
* The container condition.
*/
- condition: ContainerCondition;
+ condition?: ContainerCondition | null;
/**
* The location of the rule in the source file.
*/
@@ -9343,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 c9fe3d723..af17c0a58 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "lightningcss",
- "version": "1.24.1",
+ "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.0",
+ "@mdn/browser-compat-data": "~7.2.4",
"@napi-rs/cli": "^2.14.0",
- "autoprefixer": "^10.4.17",
- "caniuse-lite": "^1.0.30001585",
+ "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 4e6bbebf5..883aa442d 100644
--- a/scripts/build-ast.js
+++ b/scripts/build-ast.js
@@ -55,6 +55,48 @@ compileFromFile('node/ast.json', {
if (path.node.name.startsWith('GenericBorderFor_LineStyleAnd_')) {
path.node.name = 'GenericBorderFor_LineStyle';
}
+ },
+ TSTypeAliasDeclaration(path) {
+ // Workaround for schemars not supporting untagged variants.
+ // https://github.com/GREsau/schemars/issues/222
+ if (
+ (path.node.id.name === 'Translate' || path.node.id.name === 'Scale') &&
+ path.node.typeAnnotation.type === 'TSUnionType' &&
+ path.node.typeAnnotation.types[1].type === 'TSTypeLiteral' &&
+ path.node.typeAnnotation.types[1].members[0].key.name === 'xyz'
+ ) {
+ path.get('typeAnnotation.types.1').replaceWith(path.node.typeAnnotation.types[1].members[0].typeAnnotation.typeAnnotation);
+ } else if (path.node.id.name === 'AnimationAttachmentRange' && path.node.typeAnnotation.type === 'TSUnionType') {
+ let types = path.node.typeAnnotation.types;
+ if (types[1].type === 'TSTypeLiteral' && types[1].members[0].key.name === 'lengthpercentage') {
+ path.get('typeAnnotation.types.1').replaceWith(path.node.typeAnnotation.types[1].members[0].typeAnnotation.typeAnnotation);
+ }
+
+ 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 dd4c91bc3..ef447a7ea 100644
--- a/scripts/build-npm.js
+++ b/scripts/build-npm.js
@@ -15,6 +15,9 @@ const triples = [
{
name: 'x86_64-pc-windows-msvc',
},
+ {
+ name: 'aarch64-pc-windows-msvc'
+ },
{
name: 'aarch64-apple-darwin',
},
@@ -35,6 +38,9 @@ const triples = [
},
{
name: 'x86_64-unknown-freebsd'
+ },
+ {
+ name: 'aarch64-linux-android'
}
];
const cpuToNodeArch = {
@@ -48,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 597e1dbdc..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,
@@ -282,7 +287,7 @@ let mdnFeatures = {
logicalPaddingShorthand: mdn.css.properties['padding-inline'].__compat.support,
logicalInset: mdn.css.properties['inset-inline-start'].__compat.support,
logicalSize: mdn.css.properties['inline-size'].__compat.support,
- logicalTextAlign: mdn.css.properties['text-align']['flow_relative_values_start_and_end'].__compat.support,
+ logicalTextAlign: mdn.css.properties['text-align'].start.__compat.support,
labColors: mdn.css.types.color.lab.__compat.support,
oklabColors: mdn.css.types.color.oklab.__compat.support,
colorFunction: mdn.css.types.color.color.__compat.support,
@@ -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,
@@ -329,6 +334,29 @@ let mdnFeatures = {
fontStretchPercentage: mdn.css.properties['font-stretch'].percentage.__compat.support,
lightDark: mdn.css.types.color['light-dark'].__compat.support,
accentSystemColor: mdn.css.types.color['system-color'].accentcolor_accentcolortext.__compat.support,
+ animationTimelineShorthand: mdn.css.properties.animation['animation-timeline_included'].__compat.support,
+ 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) {
@@ -343,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([
@@ -376,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;
}
@@ -463,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())
@@ -636,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/scripts/build-wasm.js b/scripts/build-wasm.js
index 9f69a9af8..718821da9 100644
--- a/scripts/build-wasm.js
+++ b/scripts/build-wasm.js
@@ -54,15 +54,21 @@ wasmPkg.type = 'module';
wasmPkg.main = 'index.mjs';
wasmPkg.module = 'index.mjs';
wasmPkg.exports = {
- types: './index.d.ts',
- node: {
- import: './wasm-node.mjs',
- require: './wasm-node.cjs',
+ '.': {
+ types: './index.d.ts',
+ node: {
+ import: './wasm-node.mjs',
+ require: './wasm-node.cjs'
+ },
+ default: {
+ import: './index.mjs',
+ require: './index.cjs'
+ }
},
- default: {
- import: './index.mjs',
- require: './index.cjs',
- }
+ // Allow esbuild to import the wasm file
+ // without copying it in the src directory.
+ // Simplifies loading it in the browser when used in a library.
+ './lightningcss_node.wasm': './lightningcss_node.wasm'
};
wasmPkg.types = 'index.d.ts';
wasmPkg.sideEffects = false;
diff --git a/selectors/Cargo.toml b/selectors/Cargo.toml
index c3dd94026..824b2f2bd 100644
--- a/selectors/Cargo.toml
+++ b/selectors/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "parcel_selectors"
-version = "0.26.4"
+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.123", features = ["derive"], optional = true }
-schemars = { version = "0.8.11", features = ["smallvec"], optional = true }
-static-self = { version = "0.1.0", path = "../static-self", optional = true }
+serde = { version = "1.0.201", features = ["derive"], optional = true }
+schemars = { version = "0.8.19", features = ["smallvec"], optional = true }
+static-self = { version = "0.1.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 25b1445b5..19563d5f9 100644
--- a/selectors/parser.rs
+++ b/selectors/parser.rs
@@ -197,7 +197,9 @@ 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>),
ExpectedBarInAttr(Token<'i>),
@@ -205,6 +207,7 @@ pub enum SelectorParseErrorKind<'i> {
InvalidQualNameInAttr(Token<'i>),
ExplicitNamespaceUnexpectedToken(Token<'i>),
ClassNeedsIdent(Token<'i>),
+ UnexpectedSelectorAfterPseudoElement(Token<'i>),
}
macro_rules! with_all_bounds {
@@ -310,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>(
@@ -318,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(
@@ -326,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>(
@@ -334,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> {
@@ -877,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()
}
@@ -900,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()
}
@@ -1006,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,
}
@@ -1089,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.
@@ -1299,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",
@@ -1319,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'),
@@ -2140,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;
}
@@ -2955,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(..) => {
@@ -2966,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()? {
@@ -2988,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)
@@ -3336,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>(
@@ -3351,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(
@@ -3364,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 {
@@ -3376,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())
}
@@ -3911,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 1a4bec654..f0c828d75 100644
--- a/src/compat.rs
+++ b/src/compat.rs
@@ -10,11 +10,14 @@ pub enum Feature {
AfarListStyleType,
AmharicAbegedeListStyleType,
AmharicListStyleType,
+ AnchorSizeSize,
+ AnimationTimelineShorthand,
AnyLink,
AnyPseudo,
ArabicIndicListStyleType,
ArmenianListStyleType,
AsterisksListStyleType,
+ AutoSize,
Autofill,
BengaliListStyleType,
BinaryListStyleType,
@@ -25,6 +28,7 @@ pub enum Feature {
CapUnit,
CaseInsensitive,
ChUnit,
+ Checkmark,
CircleListStyleType,
CjkDecimalListStyleType,
CjkEarthlyBranchListStyleType,
@@ -39,6 +43,7 @@ pub enum Feature {
DecimalLeadingZeroListStyleType,
DecimalListStyleType,
DefaultPseudo,
+ DetailsContent,
DevanagariListStyleType,
Dialog,
DirSelector,
@@ -82,6 +87,7 @@ pub enum Feature {
Gencontent,
GeorgianListStyleType,
GradientInterpolationHints,
+ GrammarError,
GujaratiListStyleType,
GurmukhiListStyleType,
HasSelector,
@@ -94,7 +100,6 @@ pub enum Feature {
ImageSet,
InOutOfRange,
IndeterminatePseudo,
- IsAnimatableSize,
IsSelector,
JapaneseFormalListStyleType,
JapaneseInformalListStyleType,
@@ -138,10 +143,10 @@ pub enum Feature {
MinFunction,
ModFunction,
MongolianListStyleType,
- MozAvailableSize,
MyanmarListStyleType,
Namespaces,
Nesting,
+ NoneListStyleType,
NotSelectorList,
NthChildOf,
OctalListStyleType,
@@ -153,6 +158,8 @@ pub enum Feature {
P3Colors,
PartPseudo,
PersianListStyleType,
+ Picker,
+ PickerIcon,
PlaceContent,
PlaceItems,
PlaceSelf,
@@ -182,11 +189,14 @@ pub enum Feature {
SimpChineseInformalListStyleType,
SomaliListStyleType,
SpaceSeparatedColorNotation,
+ SpellingError,
SquareListStyleType,
+ StatePseudoClass,
StretchSize,
StringListStyleType,
SymbolsListStyleType,
TamilListStyleType,
+ TargetText,
TeluguListStyleType,
TextDecorationThicknessPercent,
TextDecorationThicknessShorthand,
@@ -208,6 +218,7 @@ pub enum Feature {
VbUnit,
VhUnit,
ViUnit,
+ ViewTransition,
ViewportPercentageUnitsDynamic,
ViewportPercentageUnitsLarge,
ViewportPercentageUnitsSmall,
@@ -441,7 +452,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -533,7 +544,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -578,7 +589,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -623,11 +634,16 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ 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;
}
}
@@ -663,7 +679,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -708,7 +724,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -753,7 +769,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -798,7 +814,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -890,7 +906,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -935,7 +951,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -999,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 < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -1024,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;
}
}
@@ -1060,7 +1066,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -1150,7 +1156,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -1195,7 +1201,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -1245,7 +1251,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -1332,7 +1338,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -1377,7 +1383,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -1422,11 +1428,16 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ 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;
}
}
@@ -1462,7 +1473,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -1507,7 +1518,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -1552,7 +1563,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -1619,7 +1630,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 9371648 {
return false;
}
}
@@ -1632,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 {
@@ -2205,7 +2216,7 @@ impl Feature {
}
}
if let Some(version) = browsers.samsung {
- if version < 327680 {
+ if version < 458752 {
return false;
}
}
@@ -2509,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;
@@ -2519,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;
}
}
@@ -2645,7 +2666,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 2424832 {
+ if version < 263168 {
return false;
}
}
@@ -2764,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;
@@ -2774,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;
}
}
@@ -2868,17 +2899,27 @@ impl Feature {
return false;
}
}
- Feature::RoundFunction
- | Feature::RemFunction
- | Feature::ModFunction
- | Feature::AbsFunction
- | Feature::SignFunction
- | Feature::HypotFunction => {
+ Feature::RoundFunction | Feature::RemFunction | Feature::ModFunction => {
+ if let Some(version) = browsers.chrome {
+ if version < 8192000 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.edge {
+ if version < 8192000 {
+ return false;
+ }
+ }
if let Some(version) = browsers.firefox {
if version < 7733248 {
return false;
}
}
+ if let Some(version) = browsers.opera {
+ if version < 5439488 {
+ return false;
+ }
+ }
if let Some(version) = browsers.safari {
if version < 984064 {
return false;
@@ -2889,13 +2930,102 @@ 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.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::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;
+ }
+ }
+ if let Some(version) = browsers.ios_saf {
+ if version < 984064 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 9043968 {
+ return false;
+ }
+ }
+ if browsers.ie.is_some() || browsers.samsung.is_some() {
+ return false;
+ }
+ }
+ Feature::HypotFunction => {
+ if let Some(version) = browsers.chrome {
+ if version < 7864320 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.edge {
+ if version < 7864320 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.firefox {
+ if version < 7733248 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.opera {
+ if version < 5242880 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.safari {
+ if version < 984064 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.ios_saf {
+ if version < 984064 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.samsung {
+ if version < 1638400 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 7864320 {
+ return false;
+ }
+ }
+ if browsers.ie.is_some() {
return false;
}
}
@@ -2986,7 +3116,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 2424832 {
+ if version < 263168 {
return false;
}
}
@@ -3080,7 +3210,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 262144 {
+ if version < 2752512 {
return false;
}
}
@@ -3271,19 +3401,42 @@ impl Feature {
return false;
}
}
+ if let Some(version) = browsers.edge {
+ if version < 8060928 {
+ return false;
+ }
+ }
if let Some(version) = browsers.firefox {
if version < 7864320 {
return false;
}
}
- if browsers.android.is_some()
- || browsers.edge.is_some()
- || browsers.ie.is_some()
- || browsers.ios_saf.is_some()
- || browsers.opera.is_some()
- || browsers.safari.is_some()
- || browsers.samsung.is_some()
- {
+ if let Some(version) = browsers.opera {
+ if version < 5373952 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.safari {
+ if version < 1115392 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.ios_saf {
+ if version < 1115392 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.samsung {
+ if version < 1769472 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 8060928 {
+ return false;
+ }
+ }
+ if browsers.ie.is_some() {
return false;
}
}
@@ -3313,69 +3466,391 @@ impl Feature {
return false;
}
}
- Feature::QUnit => {
+ Feature::AnimationTimelineShorthand => {
if let Some(version) = browsers.chrome {
- if version < 4128768 {
+ if version < 7536640 {
return false;
}
}
if let Some(version) = browsers.edge {
- if version < 5177344 {
- return false;
- }
- }
- if let Some(version) = browsers.firefox {
- if version < 3211264 {
+ if version < 7536640 {
return false;
}
}
if let Some(version) = browsers.opera {
- if version < 3014656 {
- return false;
- }
- }
- if let Some(version) = browsers.safari {
- if version < 852224 {
- return false;
- }
- }
- if let Some(version) = browsers.ios_saf {
- if version < 852992 {
+ if version < 5046272 {
return false;
}
}
if let Some(version) = browsers.samsung {
- if version < 524288 {
+ if version < 1507328 {
return false;
}
}
if let Some(version) = browsers.android {
- if version < 4128768 {
+ if version < 7536640 {
return false;
}
}
- if browsers.ie.is_some() {
+ if browsers.firefox.is_some()
+ || browsers.ie.is_some()
+ || browsers.ios_saf.is_some()
+ || browsers.safari.is_some()
+ {
return false;
}
}
- Feature::CapUnit => {
+ Feature::ViewTransition => {
if let Some(version) = browsers.chrome {
- if version < 7667712 {
+ if version < 7143424 {
return false;
}
}
if let Some(version) = browsers.edge {
- if version < 7667712 {
+ if version < 7143424 {
return false;
}
}
if let Some(version) = browsers.firefox {
- if version < 6356992 {
+ if version < 9437184 {
return false;
}
}
if let Some(version) = browsers.opera {
- if version < 5111808 {
+ 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 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.edge {
+ if version < 5177344 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.firefox {
+ if version < 3211264 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.opera {
+ if version < 3014656 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.safari {
+ if version < 852224 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.ios_saf {
+ if version < 852992 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.samsung {
+ if version < 524288 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 4128768 {
+ return false;
+ }
+ }
+ if browsers.ie.is_some() {
+ return false;
+ }
+ }
+ Feature::CapUnit => {
+ if let Some(version) = browsers.chrome {
+ if version < 7733248 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.edge {
+ if version < 7733248 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.firefox {
+ if version < 6356992 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.opera {
+ if version < 5177344 {
return false;
}
}
@@ -3389,12 +3864,17 @@ impl Feature {
return false;
}
}
+ if let Some(version) = browsers.samsung {
+ if version < 1638400 {
+ return false;
+ }
+ }
if let Some(version) = browsers.android {
- if version < 7667712 {
+ if version < 7733248 {
return false;
}
}
- if browsers.ie.is_some() || browsers.samsung.is_some() {
+ if browsers.ie.is_some() {
return false;
}
}
@@ -3680,17 +4160,22 @@ impl Feature {
}
Feature::RcapUnit => {
if let Some(version) = browsers.chrome {
- if version < 7667712 {
+ if version < 7733248 {
return false;
}
}
if let Some(version) = browsers.edge {
- if version < 7667712 {
+ if version < 7733248 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.firefox {
+ if version < 9633792 {
return false;
}
}
if let Some(version) = browsers.opera {
- if version < 5111808 {
+ if version < 5177344 {
return false;
}
}
@@ -3704,12 +4189,17 @@ impl Feature {
return false;
}
}
+ if let Some(version) = browsers.samsung {
+ if version < 1638400 {
+ return false;
+ }
+ }
if let Some(version) = browsers.android {
- if version < 7667712 {
+ if version < 7733248 {
return false;
}
}
- if browsers.firefox.is_some() || browsers.ie.is_some() || browsers.samsung.is_some() {
+ if browsers.ie.is_some() {
return false;
}
}
@@ -3724,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;
@@ -3749,7 +4244,7 @@ impl Feature {
return false;
}
}
- if browsers.firefox.is_some() || browsers.ie.is_some() {
+ if browsers.ie.is_some() {
return false;
}
}
@@ -4120,7 +4615,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 2424832 {
+ if version < 263168 {
return false;
}
}
@@ -4167,7 +4662,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 2424832 {
+ if version < 263168 {
return false;
}
}
@@ -4670,14 +5165,16 @@ impl Feature {
| Feature::HiraganaListStyleType
| Feature::HiraganaIrohaListStyleType
| Feature::KatakanaListStyleType
- | Feature::KatakanaIrohaListStyleType => {
+ | Feature::KatakanaIrohaListStyleType
+ | Feature::NoneListStyleType
+ | Feature::AutoSize => {
if let Some(version) = browsers.chrome {
if version < 1179648 {
return false;
}
}
if let Some(version) = browsers.edge {
- if version < 5177344 {
+ if version < 786432 {
return false;
}
}
@@ -4686,6 +5183,11 @@ impl Feature {
return false;
}
}
+ if let Some(version) = browsers.ie {
+ if version < 720896 {
+ return false;
+ }
+ }
if let Some(version) = browsers.opera {
if version < 917504 {
return false;
@@ -4711,9 +5213,6 @@ impl Feature {
return false;
}
}
- if browsers.ie.is_some() {
- return false;
- }
}
Feature::KoreanHangulFormalListStyleType
| Feature::KoreanHanjaFormalListStyleType
@@ -4965,44 +5464,44 @@ impl Feature {
return false;
}
}
- Feature::FitContentSize => {
+ Feature::AnchorSizeSize => {
if let Some(version) = browsers.chrome {
- if version < 1638400 {
+ if version < 8192000 {
return false;
}
}
if let Some(version) = browsers.edge {
- if version < 5177344 {
+ if version < 8192000 {
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;
}
}
@@ -5010,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;
}
}
@@ -5056,10 +5550,13 @@ impl Feature {
return false;
}
}
+ if browsers.ie.is_some() {
+ return false;
+ }
}
Feature::MaxContentSize => {
if let Some(version) = browsers.chrome {
- if version < 3014656 {
+ if version < 1638400 {
return false;
}
}
@@ -5089,12 +5586,12 @@ impl Feature {
}
}
if let Some(version) = browsers.samsung {
- if version < 327680 {
+ if version < 66816 {
return false;
}
}
if let Some(version) = browsers.android {
- if version < 3014656 {
+ if version < 263168 {
return false;
}
}
@@ -5147,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;
}
}
@@ -5197,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;
}
@@ -5238,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 b794bb431..ce7008df2 100644
--- a/src/css_modules.rs
+++ b/src/css_modules.rs
@@ -25,13 +25,41 @@ use std::hash::{Hash, Hasher};
use std::path::Path;
/// Configuration for CSS modules.
-#[derive(Default, Clone, Debug)]
+#[derive(Clone, Debug)]
pub struct Config<'i> {
/// The name pattern to use when renaming class names and other identifiers.
/// Default is `[hash]_[local]`.
pub pattern: Pattern<'i>,
/// Whether to rename dashed identifiers, e.g. custom properties.
pub dashed_idents: bool,
+ /// Whether to scope animation names.
+ /// Default is `true`.
+ pub animation: bool,
+ /// Whether to scope grid names.
+ /// Default is `true`.
+ pub grid: bool,
+ /// Whether to scope custom identifiers
+ /// Default is `true`.
+ pub custom_idents: bool,
+ /// 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> {
+ fn default() -> Self {
+ Config {
+ pattern: Default::default(),
+ dashed_idents: Default::default(),
+ animation: true,
+ grid: true,
+ container: true,
+ custom_idents: true,
+ pure: false,
+ }
+ }
}
/// A CSS modules class name pattern.
@@ -86,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);
@@ -105,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>,
{
@@ -129,6 +170,9 @@ impl<'i> Pattern<'i> {
Segment::Hash => {
write(hash)?;
}
+ Segment::ContentHash => {
+ write(content_hash)?;
+ }
}
}
Ok(())
@@ -141,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)
}
}
@@ -160,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.
@@ -224,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,
}
@@ -234,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();
@@ -258,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,
}
}
@@ -274,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![],
@@ -293,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![],
@@ -315,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![],
@@ -344,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(),
)
@@ -364,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![],
@@ -406,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 556fd152f..0dd3da619 100644
--- a/src/declaration.rs
+++ b/src/declaration.rs
@@ -1,16 +1,16 @@
//! 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;
use crate::properties::custom::{CustomProperty, CustomPropertyName};
use crate::properties::masking::MaskHandler;
+use crate::properties::text::{Direction, UnicodeBidi};
use crate::properties::{
align::AlignHandler,
animation::AnimationHandler,
@@ -34,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.
///
@@ -156,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;
}
};
@@ -175,10 +230,7 @@ impl<'i> DeclarationBlock<'i> {
write!(self.declarations, false);
write!(self.important_declarations, true);
-
- dest.dedent();
- dest.newline()?;
- dest.write_char('}')
+ Ok(())
}
}
@@ -515,7 +567,9 @@ pub(crate) struct DeclarationHandler<'i> {
color_scheme: ColorSchemeHandler,
fallback: FallbackHandler,
prefix: PrefixHandler,
- custom_properties: HashMap<'i>, usize>,
+ direction: Option,
+ unicode_bidi: Option,
+ custom_properties: IndexMap<'i>, usize>,
decls: DeclarationList<'i>,
}
@@ -552,6 +606,7 @@ impl<'i> DeclarationHandler<'i> {
|| self.color_scheme.handle_property(property, &mut self.decls, context)
|| self.fallback.handle_property(property, &mut self.decls, context)
|| self.prefix.handle_property(property, &mut self.decls, context)
+ || self.handle_all(property)
|| self.handle_custom_property(property, context)
}
@@ -587,6 +642,36 @@ impl<'i> DeclarationHandler<'i> {
false
}
+ fn handle_all(&mut self, property: &Property<'i>) -> bool {
+ // The `all` property resets all properies except `unicode-bidi`, `direction`, and custom properties.
+ // https://drafts.csswg.org/css-cascade-5/#all-shorthand
+ match property {
+ Property::UnicodeBidi(bidi) => {
+ self.unicode_bidi = Some(*bidi);
+ true
+ }
+ Property::Direction(direction) => {
+ self.direction = Some(*direction);
+ true
+ }
+ Property::All(keyword) => {
+ let mut handler = DeclarationHandler {
+ unicode_bidi: self.unicode_bidi.clone(),
+ direction: self.direction.clone(),
+ ..Default::default()
+ };
+ for (key, index) in self.custom_properties.drain(..) {
+ handler.custom_properties.insert(key, handler.decls.len());
+ handler.decls.push(self.decls[index].clone());
+ }
+ handler.decls.push(Property::All(keyword.clone()));
+ *self = handler;
+ true
+ }
+ _ => false,
+ }
+ }
+
fn add_conditional_fallbacks(
&self,
custom: &mut CustomProperty<'i>,
@@ -607,6 +692,13 @@ impl<'i> DeclarationHandler<'i> {
}
pub fn finalize(&mut self, context: &mut PropertyHandlerContext<'i, '_>) {
+ if let Some(direction) = std::mem::take(&mut self.direction) {
+ self.decls.push(Property::Direction(direction));
+ }
+ if let Some(unicode_bidi) = std::mem::take(&mut self.unicode_bidi) {
+ self.decls.push(Property::UnicodeBidi(unicode_bidi));
+ }
+
self.background.finalize(&mut self.decls, context);
self.border.finalize(&mut self.decls, context);
self.outline.finalize(&mut self.decls, context);
diff --git a/src/error.rs b/src/error.rs
index cac0f59c1..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,8 +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> {
@@ -256,7 +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:?}'"
+ )
+ },
}
}
}
@@ -285,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()),
@@ -297,6 +322,10 @@ impl<'i> From<'i>> for SelectorError<'i> {
SelectorError::ExplicitNamespaceUnexpectedToken(t.into())
}
SelectorParseErrorKind::ClassNeedsIdent(t) => SelectorError::ClassNeedsIdent(t.into()),
+ SelectorParseErrorKind::AmbiguousCssModuleClass(name) => SelectorError::AmbiguousCssModuleClass(name.into()),
+ SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(t) => {
+ SelectorError::UnexpectedSelectorAfterPseudoElement(t.into())
+ }
}
}
}
@@ -337,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 {
@@ -349,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"
+ ),
}
}
}
@@ -378,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 f13b7c485..2a41655ee 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -64,7 +64,9 @@ mod tests {
use crate::vendor_prefix::VendorPrefix;
use cssparser::SourceLocation;
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())
@@ -77,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();
@@ -93,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
@@ -168,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,
@@ -179,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);
@@ -216,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)?)*),* } => {
{
@@ -334,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(
@@ -1442,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(
@@ -2117,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;
}
@@ -2138,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;
}
@@ -2160,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;
}
@@ -2182,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));
}
@@ -3952,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]
@@ -4101,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}",
@@ -4127,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 }",
@@ -6819,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",
@@ -6829,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),
@@ -6845,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}");
@@ -6899,32 +7262,200 @@ 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(),
);
-
- // CSS-wide keywords and `none` cannot remove quotes.
+ 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 {
+ entry 0% {
+ background: blue
+ }
+ exit 100% {
+ background: green
+ }
+ }
+ "#,
+ "@keyframes test{entry 0%{background:#00f}exit 100%{background:green}}",
+ );
+
+ // CSS-wide keywords and `none` cannot remove quotes.
minify_test(
r#"
@keyframes "revert" {
@@ -6947,6 +7478,18 @@ mod tests {
"@keyframes \"none\"{0%{background:green}}",
);
+ // named animation ranges cannot be used with to or from
+ minify_test(
+ r#"
+ @keyframes test {
+ entry to {
+ background: blue
+ }
+ }
+ "#,
+ "@keyframes test{}",
+ );
+
// CSS-wide keywords without quotes throws an error.
error_test(
r#"
@@ -7511,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) }",
@@ -7522,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)) }",
@@ -7654,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]
@@ -7694,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}");
@@ -7713,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]
@@ -7745,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]
@@ -8166,7 +8759,7 @@ mod tests {
}
"#,
indoc! { r#"
- @media (min-color: 3) {
+ @media not (max-color: 2) {
.foo {
color: #7fff00;
}
@@ -8187,7 +8780,7 @@ mod tests {
}
"#,
indoc! { r#"
- @media (max-color: 1) {
+ @media not (min-color: 2) {
.foo {
color: #7fff00;
}
@@ -8208,7 +8801,7 @@ mod tests {
}
"#,
indoc! { r#"
- @media (min-width: 240.001px) {
+ @media not (max-width: 240px) {
.foo {
color: #7fff00;
}
@@ -8271,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;
}
@@ -8376,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;
}
@@ -8427,7 +9100,7 @@ mod tests {
}
"#,
indoc! { r#"
- @media (min-width: calc(1.001px + 1rem)) {
+ @media not (max-width: calc(1px + 1rem)) {
.foo {
color: #ff0;
}
@@ -8445,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;
}
@@ -8463,7 +9136,7 @@ mod tests {
}
"#,
indoc! { r#"
- @media (min-width: .001px) {
+ @media not (max-width: 0) {
.foo {
color: #ff0;
}
@@ -8517,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;
}
@@ -8623,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,
@@ -8667,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]
@@ -8868,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]
@@ -11066,23 +11797,94 @@ 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;
+ }
- #[test]
- fn test_animation() {
- minify_test(".foo { animation-name: test }", ".foo{animation-name:test}");
- minify_test(".foo { animation-name: \"test\" }", ".foo{animation-name:test}");
- minify_test(".foo { animation-name: foo, bar }", ".foo{animation-name:foo,bar}");
- minify_test(".foo { animation-name: \"none\" }", ".foo{animation-name:\"none\"}");
- minify_test(
- ".foo { animation-name: \"none\", foo }",
- ".foo{animation-name:\"none\",foo}",
+ .baz {
+ transition-property: -webkit-backdrop-filter;
+ }
+ "#
+ },
+ Browsers {
+ safari: Some(15 << 16),
+ ..Browsers::default()
+ },
);
- let name = crate::properties::animation::AnimationName::parse_string("default");
- assert!(matches!(name, Err(..)));
-
- minify_test(".foo { animation-name: none }", ".foo{animation-name:none}");
- minify_test(".foo { animation-name: none, none }", ".foo{animation-name:none,none}");
+ 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]
+ fn test_animation() {
+ minify_test(".foo { animation-name: test }", ".foo{animation-name:test}");
+ minify_test(".foo { animation-name: \"test\" }", ".foo{animation-name:test}");
+ minify_test(".foo { animation-name: foo, bar }", ".foo{animation-name:foo,bar}");
+ minify_test(".foo { animation-name: \"none\" }", ".foo{animation-name:\"none\"}");
+ minify_test(
+ ".foo { animation-name: \"none\", foo }",
+ ".foo{animation-name:\"none\",foo}",
+ );
+ let name = crate::properties::animation::AnimationName::parse_string("default");
+ assert!(matches!(name, Err(..)));
+
+ minify_test(".foo { animation-name: none }", ".foo{animation-name:none}");
+ minify_test(".foo { animation-name: none, none }", ".foo{animation-name:none,none}");
// Test CSS-wide keywords
minify_test(".foo { animation-name: unset }", ".foo{animation-name:unset}");
@@ -11242,6 +12044,46 @@ mod tests {
".foo { animation: foo 0s 3s infinite }",
".foo{animation:0s 3s infinite foo}",
);
+ minify_test(".foo { animation: foo 3s --test }", ".foo{animation:3s foo --test}");
+ minify_test(".foo { animation: foo 3s scroll() }", ".foo{animation:3s foo scroll()}");
+ minify_test(
+ ".foo { animation: foo 3s scroll(block) }",
+ ".foo{animation:3s foo scroll()}",
+ );
+ minify_test(
+ ".foo { animation: foo 3s scroll(root inline) }",
+ ".foo{animation:3s foo scroll(root inline)}",
+ );
+ minify_test(
+ ".foo { animation: foo 3s scroll(inline root) }",
+ ".foo{animation:3s foo scroll(root inline)}",
+ );
+ minify_test(
+ ".foo { animation: foo 3s scroll(inline nearest) }",
+ ".foo{animation:3s foo scroll(inline)}",
+ );
+ minify_test(
+ ".foo { animation: foo 3s view(block) }",
+ ".foo{animation:3s foo view()}",
+ );
+ minify_test(
+ ".foo { animation: foo 3s view(inline) }",
+ ".foo{animation:3s foo view(inline)}",
+ );
+ minify_test(
+ ".foo { animation: foo 3s view(inline 10px 10px) }",
+ ".foo{animation:3s foo view(inline 10px)}",
+ );
+ minify_test(
+ ".foo { animation: foo 3s view(inline 10px 12px) }",
+ ".foo{animation:3s foo view(inline 10px 12px)}",
+ );
+ minify_test(
+ ".foo { animation: foo 3s view(inline auto auto) }",
+ ".foo{animation:3s foo view(inline)}",
+ );
+ minify_test(".foo { animation: foo 3s auto }", ".foo{animation:3s foo}");
+ minify_test(".foo { animation-composition: add }", ".foo{animation-composition:add}");
test(
r#"
.foo {
@@ -11253,6 +12095,7 @@ mod tests {
animation-play-state: running;
animation-delay: 100ms;
animation-fill-mode: forwards;
+ animation-timeline: auto;
}
"#,
indoc! {r#"
@@ -11272,6 +12115,7 @@ mod tests {
animation-play-state: running, paused;
animation-delay: 100ms, 0s;
animation-fill-mode: forwards, none;
+ animation-timeline: auto, auto;
}
"#,
indoc! {r#"
@@ -11318,6 +12162,7 @@ mod tests {
animation-play-state: running;
animation-delay: 100ms;
animation-fill-mode: forwards;
+ animation-timeline: auto;
}
"#,
indoc! {r#"
@@ -11330,6 +12175,55 @@ mod tests {
animation-play-state: running;
animation-delay: .1s;
animation-fill-mode: forwards;
+ animation-timeline: auto;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-name: foo;
+ animation-duration: 0.09s;
+ animation-timing-function: ease-in-out;
+ animation-iteration-count: 2;
+ animation-direction: alternate;
+ animation-play-state: running;
+ animation-delay: 100ms;
+ animation-fill-mode: forwards;
+ animation-timeline: scroll();
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation: 90ms ease-in-out .1s 2 alternate forwards foo scroll();
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-name: foo;
+ animation-duration: 0.09s;
+ animation-timing-function: ease-in-out;
+ animation-iteration-count: 2;
+ animation-direction: alternate;
+ animation-play-state: running;
+ animation-delay: 100ms;
+ animation-fill-mode: forwards;
+ animation-timeline: scroll(), view();
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-name: foo;
+ animation-duration: 90ms;
+ animation-timing-function: ease-in-out;
+ animation-iteration-count: 2;
+ animation-direction: alternate;
+ animation-play-state: running;
+ animation-delay: .1s;
+ animation-fill-mode: forwards;
+ animation-timeline: scroll(), view();
}
"#},
);
@@ -11440,112 +12334,436 @@ mod tests {
..Browsers::default()
},
);
- }
- #[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)}",
+ prefix_test(
+ r#"
+ .foo {
+ animation: .2s ease-in-out bar scroll();
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation: .2s ease-in-out bar;
+ animation-timeline: scroll();
+ }
+ "#},
+ Browsers {
+ safari: Some(16 << 16),
+ ..Browsers::default()
+ },
);
- minify_test(
- ".foo { transform: translate(0px, 2px)",
- ".foo{transform:translateY(2px)}",
+ prefix_test(
+ r#"
+ .foo {
+ animation: .2s ease-in-out bar scroll();
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation: .2s ease-in-out bar scroll();
+ }
+ "#},
+ Browsers {
+ chrome: Some(120 << 16),
+ ..Browsers::default()
+ },
);
- minify_test(".foo { transform: translateX(2px)", ".foo{transform:translate(2px)}");
- minify_test(".foo { transform: translateY(2px)", ".foo{transform:translateY(2px)}");
- minify_test(".foo { transform: translateZ(2px)", ".foo{transform:translateZ(2px)}");
- minify_test(
- ".foo { transform: translate3d(2px, 3px, 4px)",
- ".foo{transform:translate3d(2px,3px,4px)}",
+ prefix_test(
+ r#"
+ .foo {
+ animation: .2s ease-in-out bar scroll();
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ -webkit-animation: .2s ease-in-out bar;
+ animation: .2s ease-in-out bar;
+ animation-timeline: scroll();
+ }
+ "#},
+ Browsers {
+ safari: Some(6 << 16),
+ ..Browsers::default()
+ },
);
+
minify_test(
- ".foo { transform: translate3d(10%, 20%, 4px)",
- ".foo{transform:translate3d(10%,20%,4px)}",
+ ".foo { animation-range-start: entry 10% }",
+ ".foo{animation-range-start:entry 10%}",
);
minify_test(
- ".foo { transform: translate3d(2px, 0px, 0px)",
- ".foo{transform:translate(2px)}",
+ ".foo { animation-range-start: entry 0% }",
+ ".foo{animation-range-start:entry}",
);
minify_test(
- ".foo { transform: translate3d(0px, 2px, 0px)",
- ".foo{transform:translateY(2px)}",
+ ".foo { animation-range-start: entry }",
+ ".foo{animation-range-start:entry}",
);
+ minify_test(".foo { animation-range-start: 50% }", ".foo{animation-range-start:50%}");
minify_test(
- ".foo { transform: translate3d(0px, 0px, 2px)",
- ".foo{transform:translateZ(2px)}",
+ ".foo { animation-range-end: exit 10% }",
+ ".foo{animation-range-end:exit 10%}",
);
minify_test(
- ".foo { transform: translate3d(2px, 3px, 0px)",
- ".foo{transform:translate(2px,3px)}",
+ ".foo { animation-range-end: exit 100% }",
+ ".foo{animation-range-end:exit}",
);
- minify_test(".foo { transform: scale(2, 3)", ".foo{transform:scale(2,3)}");
- minify_test(".foo { transform: scale(10%, 20%)", ".foo{transform:scale(.1,.2)}");
- minify_test(".foo { transform: scale(2, 2)", ".foo{transform:scale(2)}");
- minify_test(".foo { transform: scale(2, 1)", ".foo{transform:scaleX(2)}");
- minify_test(".foo { transform: scale(1, 2)", ".foo{transform:scaleY(2)}");
- minify_test(".foo { transform: scaleX(2)", ".foo{transform:scaleX(2)}");
- minify_test(".foo { transform: scaleY(2)", ".foo{transform:scaleY(2)}");
- minify_test(".foo { transform: scaleZ(2)", ".foo{transform:scaleZ(2)}");
- minify_test(".foo { transform: scale3d(2, 3, 4)", ".foo{transform:scale3d(2,3,4)}");
- minify_test(".foo { transform: scale3d(2, 1, 1)", ".foo{transform:scaleX(2)}");
- minify_test(".foo { transform: scale3d(1, 2, 1)", ".foo{transform:scaleY(2)}");
- minify_test(".foo { transform: scale3d(1, 1, 2)", ".foo{transform:scaleZ(2)}");
- minify_test(".foo { transform: scale3d(2, 2, 1)", ".foo{transform:scale(2)}");
- minify_test(".foo { transform: rotate(20deg)", ".foo{transform:rotate(20deg)}");
- minify_test(".foo { transform: rotateX(20deg)", ".foo{transform:rotateX(20deg)}");
- minify_test(".foo { transform: rotateY(20deg)", ".foo{transform:rotateY(20deg)}");
- minify_test(".foo { transform: rotateZ(20deg)", ".foo{transform:rotate(20deg)}");
- minify_test(".foo { transform: rotate(360deg)", ".foo{transform:rotate(360deg)}");
+ minify_test(".foo { animation-range-end: exit }", ".foo{animation-range-end:exit}");
+ minify_test(".foo { animation-range-end: 50% }", ".foo{animation-range-end:50%}");
minify_test(
- ".foo { transform: rotate3d(2, 3, 4, 20deg)",
- ".foo{transform:rotate3d(2,3,4,20deg)}",
+ ".foo { animation-range: entry 10% exit 90% }",
+ ".foo{animation-range:entry 10% exit 90%}",
);
minify_test(
- ".foo { transform: rotate3d(1, 0, 0, 20deg)",
- ".foo{transform:rotateX(20deg)}",
+ ".foo { animation-range: entry 0% exit 100% }",
+ ".foo{animation-range:entry exit}",
);
+ minify_test(".foo { animation-range: entry }", ".foo{animation-range:entry}");
minify_test(
- ".foo { transform: rotate3d(0, 1, 0, 20deg)",
- ".foo{transform:rotateY(20deg)}",
+ ".foo { animation-range: entry 0% entry 100% }",
+ ".foo{animation-range:entry}",
);
+ minify_test(".foo { animation-range: 50% normal }", ".foo{animation-range:50%}");
minify_test(
- ".foo { transform: rotate3d(0, 0, 1, 20deg)",
- ".foo{transform:rotate(20deg)}",
+ ".foo { animation-range: normal normal }",
+ ".foo{animation-range:normal}",
);
- minify_test(".foo { transform: rotate(405deg)}", ".foo{transform:rotate(405deg)}");
- minify_test(".foo { transform: rotateX(405deg)}", ".foo{transform:rotateX(405deg)}");
- minify_test(".foo { transform: rotateY(405deg)}", ".foo{transform:rotateY(405deg)}");
- minify_test(".foo { transform: rotate(-200deg)}", ".foo{transform:rotate(-200deg)}");
- minify_test(".foo { transform: rotate(0)", ".foo{transform:rotate(0)}");
- minify_test(".foo { transform: rotate(0deg)", ".foo{transform:rotate(0)}");
- minify_test(
- ".foo { transform: rotateX(-200deg)}",
- ".foo{transform:rotateX(-200deg)}",
+ test(
+ r#"
+ .foo {
+ animation-range-start: entry 10%;
+ animation-range-end: exit 90%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: entry 10% exit 90%;
+ }
+ "#},
);
- minify_test(
- ".foo { transform: rotateY(-200deg)}",
- ".foo{transform:rotateY(-200deg)}",
+ test(
+ r#"
+ .foo {
+ animation-range-start: entry 0%;
+ animation-range-end: entry 100%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: entry;
+ }
+ "#},
);
- minify_test(
- ".foo { transform: rotate3d(1, 1, 0, -200deg)",
- ".foo{transform:rotate3d(1,1,0,-200deg)}",
+ test(
+ r#"
+ .foo {
+ animation-range-start: entry 0%;
+ animation-range-end: exit 100%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: entry exit;
+ }
+ "#},
);
- minify_test(".foo { transform: skew(20deg)", ".foo{transform:skew(20deg)}");
- minify_test(".foo { transform: skew(20deg, 0deg)", ".foo{transform:skew(20deg)}");
- minify_test(".foo { transform: skew(0deg, 20deg)", ".foo{transform:skewY(20deg)}");
- minify_test(".foo { transform: skewX(20deg)", ".foo{transform:skew(20deg)}");
- minify_test(".foo { transform: skewY(20deg)", ".foo{transform:skewY(20deg)}");
- minify_test(
- ".foo { transform: perspective(10px)",
- ".foo{transform:perspective(10px)}",
+ test(
+ r#"
+ .foo {
+ animation-range-start: 10%;
+ animation-range-end: normal;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: 10%;
+ }
+ "#},
);
- minify_test(
- ".foo { transform: matrix(1, 2, -1, 1, 80, 80)",
+ test(
+ r#"
+ .foo {
+ animation-range-start: 10%;
+ animation-range-end: 90%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: 10% 90%;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range-start: entry 10%;
+ animation-range-end: exit 100%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: entry 10% exit;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range-start: 10%;
+ animation-range-end: exit 90%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: 10% exit 90%;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range-start: entry 10%;
+ animation-range-end: 90%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: entry 10% 90%;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range: entry;
+ animation-range-end: 90%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: entry 90%;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range: entry;
+ animation-range-end: var(--end);
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: entry;
+ animation-range-end: var(--end);
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range-start: entry 10%, entry 50%;
+ animation-range-end: exit 90%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range-start: entry 10%, entry 50%;
+ animation-range-end: exit 90%;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range-start: entry 10%, entry 50%;
+ animation-range-end: exit 90%, exit 100%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: entry 10% exit 90%, entry 50% exit;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range: entry;
+ animation-range-end: 90%;
+ animation: spin 100ms;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation: .1s spin;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation: spin 100ms;
+ animation-range: entry;
+ animation-range-end: 90%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation: .1s spin;
+ animation-range: entry 90%;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range: entry;
+ animation-range-end: 90%;
+ animation: var(--animation) 100ms;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation: var(--animation) .1s;
+ }
+ "#},
+ );
+ }
+
+ #[test]
+ fn test_transform() {
+ test(
+ ".foo { transform: perspective(500px)translate3d(10px, 0, 20px)rotateY(30deg) }",
+ indoc! {r#"
+ .foo {
+ transform: perspective(500px) translate3d(10px, 0, 20px) rotateY(30deg);
+ }
+ "#},
+ );
+ 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)}",
+ );
+ minify_test(".foo { transform: translateX(2px)", ".foo{transform:translate(2px)}");
+ minify_test(".foo { transform: translateY(2px)", ".foo{transform:translateY(2px)}");
+ minify_test(".foo { transform: translateZ(2px)", ".foo{transform:translateZ(2px)}");
+ minify_test(
+ ".foo { transform: translate3d(2px, 3px, 4px)",
+ ".foo{transform:translate3d(2px,3px,4px)}",
+ );
+ minify_test(
+ ".foo { transform: translate3d(10%, 20%, 4px)",
+ ".foo{transform:translate3d(10%,20%,4px)}",
+ );
+ minify_test(
+ ".foo { transform: translate3d(2px, 0px, 0px)",
+ ".foo{transform:translate(2px)}",
+ );
+ minify_test(
+ ".foo { transform: translate3d(0px, 2px, 0px)",
+ ".foo{transform:translateY(2px)}",
+ );
+ minify_test(
+ ".foo { transform: translate3d(0px, 0px, 2px)",
+ ".foo{transform:translateZ(2px)}",
+ );
+ minify_test(
+ ".foo { transform: translate3d(2px, 3px, 0px)",
+ ".foo{transform:translate(2px,3px)}",
+ );
+ minify_test(".foo { transform: scale(2, 3)", ".foo{transform:scale(2,3)}");
+ minify_test(".foo { transform: scale(10%, 20%)", ".foo{transform:scale(.1,.2)}");
+ minify_test(".foo { transform: scale(2, 2)", ".foo{transform:scale(2)}");
+ minify_test(".foo { transform: scale(2, 1)", ".foo{transform:scaleX(2)}");
+ minify_test(".foo { transform: scale(1, 2)", ".foo{transform:scaleY(2)}");
+ minify_test(".foo { transform: scaleX(2)", ".foo{transform:scaleX(2)}");
+ minify_test(".foo { transform: scaleY(2)", ".foo{transform:scaleY(2)}");
+ minify_test(".foo { transform: scaleZ(2)", ".foo{transform:scaleZ(2)}");
+ minify_test(".foo { transform: scale3d(2, 3, 4)", ".foo{transform:scale3d(2,3,4)}");
+ minify_test(".foo { transform: scale3d(2, 1, 1)", ".foo{transform:scaleX(2)}");
+ minify_test(".foo { transform: scale3d(1, 2, 1)", ".foo{transform:scaleY(2)}");
+ minify_test(".foo { transform: scale3d(1, 1, 2)", ".foo{transform:scaleZ(2)}");
+ minify_test(".foo { transform: scale3d(2, 2, 1)", ".foo{transform:scale(2)}");
+ minify_test(".foo { transform: rotate(20deg)", ".foo{transform:rotate(20deg)}");
+ minify_test(".foo { transform: rotateX(20deg)", ".foo{transform:rotateX(20deg)}");
+ minify_test(".foo { transform: rotateY(20deg)", ".foo{transform:rotateY(20deg)}");
+ minify_test(".foo { transform: rotateZ(20deg)", ".foo{transform:rotate(20deg)}");
+ minify_test(".foo { transform: rotate(360deg)", ".foo{transform:rotate(360deg)}");
+ minify_test(
+ ".foo { transform: rotate3d(2, 3, 4, 20deg)",
+ ".foo{transform:rotate3d(2,3,4,20deg)}",
+ );
+ minify_test(
+ ".foo { transform: rotate3d(1, 0, 0, 20deg)",
+ ".foo{transform:rotateX(20deg)}",
+ );
+ minify_test(
+ ".foo { transform: rotate3d(0, 1, 0, 20deg)",
+ ".foo{transform:rotateY(20deg)}",
+ );
+ minify_test(
+ ".foo { transform: rotate3d(0, 0, 1, 20deg)",
+ ".foo{transform:rotate(20deg)}",
+ );
+ minify_test(".foo { transform: rotate(405deg)}", ".foo{transform:rotate(405deg)}");
+ minify_test(".foo { transform: rotateX(405deg)}", ".foo{transform:rotateX(405deg)}");
+ minify_test(".foo { transform: rotateY(405deg)}", ".foo{transform:rotateY(405deg)}");
+ minify_test(".foo { transform: rotate(-200deg)}", ".foo{transform:rotate(-200deg)}");
+ minify_test(".foo { transform: rotate(0)", ".foo{transform:rotate(0)}");
+ minify_test(".foo { transform: rotate(0deg)", ".foo{transform:rotate(0)}");
+ minify_test(
+ ".foo { transform: rotateX(-200deg)}",
+ ".foo{transform:rotateX(-200deg)}",
+ );
+ minify_test(
+ ".foo { transform: rotateY(-200deg)}",
+ ".foo{transform:rotateY(-200deg)}",
+ );
+ minify_test(
+ ".foo { transform: rotate3d(1, 1, 0, -200deg)",
+ ".foo{transform:rotate3d(1,1,0,-200deg)}",
+ );
+ minify_test(".foo { transform: skew(20deg)", ".foo{transform:skew(20deg)}");
+ minify_test(".foo { transform: skew(20deg, 0deg)", ".foo{transform:skew(20deg)}");
+ minify_test(".foo { transform: skew(0deg, 20deg)", ".foo{transform:skewY(20deg)}");
+ minify_test(".foo { transform: skewX(20deg)", ".foo{transform:skew(20deg)}");
+ minify_test(".foo { transform: skewY(20deg)", ".foo{transform:skewY(20deg)}");
+ minify_test(
+ ".foo { transform: perspective(10px)",
+ ".foo{transform:perspective(10px)}",
+ );
+ minify_test(
+ ".foo { transform: matrix(1, 2, -1, 1, 80, 80)",
".foo{transform:matrix(1,2,-1,1,80,80)}",
);
minify_test(
@@ -11645,21 +12863,36 @@ mod tests {
minify_test(".foo { translate: 1px 0px 0px }", ".foo{translate:1px}");
minify_test(".foo { translate: 1px 2px 0px }", ".foo{translate:1px 2px}");
minify_test(".foo { translate: 1px 0px 2px }", ".foo{translate:1px 0 2px}");
- minify_test(".foo { translate: none }", ".foo{translate:0}");
+ minify_test(".foo { translate: none }", ".foo{translate:none}");
+ minify_test(".foo { rotate: 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}");
- minify_test(".foo { scale: none }", ".foo{scale:1}");
+ minify_test(".foo { scale: none }", ".foo{scale:none}");
minify_test(".foo { scale: 1 0 }", ".foo{scale:1 0}");
minify_test(".foo { scale: 1 0 1 }", ".foo{scale:1 0}");
minify_test(".foo { scale: 1 0 0 }", ".foo{scale:1 0 0}");
@@ -12079,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);
}
"#},
@@ -12097,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);
}
"#},
@@ -12115,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);
}
"#},
@@ -12133,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);
}
"#},
@@ -12151,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);
}
"#},
@@ -12169,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);
}
"#},
@@ -12187,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);
}
"#},
@@ -12221,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);
}
"#},
@@ -12345,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");
}
"#},
@@ -12427,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));
}
@@ -12443,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));
}
@@ -12514,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));
}
@@ -12530,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));
}
@@ -12566,10 +13799,361 @@ mod tests {
..Browsers::default()
},
);
- }
- #[test]
- fn test_font_face() {
+ // 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]
+ fn test_font_face() {
minify_test(
r#"@font-face {
src: url("test.woff");
@@ -12636,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
@@ -12658,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}");
@@ -12756,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;
@@ -12800,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}");
@@ -13388,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]
@@ -14490,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 {
@@ -14857,6 +16604,27 @@ mod tests {
..Browsers::default()
},
);
+
+ 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]
@@ -14944,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]
@@ -15654,21 +17443,42 @@ mod tests {
..Browsers::default()
},
);
- }
- #[test]
- fn test_list() {
- minify_test(".foo { list-style-type: disc; }", ".foo{list-style-type:disc}");
- minify_test(".foo { list-style-type: \"★\"; }", ".foo{list-style-type:\"★\"}");
- minify_test(
- ".foo { list-style-type: symbols(cyclic '○' '●'); }",
- ".foo{list-style-type:symbols(cyclic \"○\" \"●\")}",
- );
- minify_test(
- ".foo { list-style-type: symbols('○' '●'); }",
- ".foo{list-style-type:symbols(\"○\" \"●\")}",
- );
- minify_test(
+ 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]
+ fn test_list() {
+ minify_test(".foo { list-style-type: disc; }", ".foo{list-style-type:disc}");
+ minify_test(".foo { list-style-type: \"★\"; }", ".foo{list-style-type:\"★\"}");
+ minify_test(
+ ".foo { list-style-type: symbols(cyclic '○' '●'); }",
+ ".foo{list-style-type:symbols(cyclic \"○\" \"●\")}",
+ );
+ minify_test(
+ ".foo { list-style-type: symbols('○' '●'); }",
+ ".foo{list-style-type:symbols(\"○\" \"●\")}",
+ );
+ minify_test(
".foo { list-style-type: symbols(symbolic '○' '●'); }",
".foo{list-style-type:symbols(\"○\" \"●\")}",
);
@@ -15686,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(
@@ -15727,7 +17548,7 @@ mod tests {
"#,
indoc! {r#"
.foo {
- list-style: \"★\" url("ellipse.png");
+ list-style: url("ellipse.png") \"★\";
list-style-image: var(--img);
}
"#},
@@ -15738,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));
}
@@ -15753,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 {
@@ -15781,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]
@@ -15981,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}");
@@ -15992,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)}",
@@ -16010,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)}",
@@ -16030,21 +17917,37 @@ 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)}",
);
minify_test(
".foo { color: color(display-p3 1 0.5 0); }",
- ".foo{color:color(display-p3 1 .5)}",
+ ".foo{color:color(display-p3 1 .5 0)}",
);
minify_test(
".foo { color: color(display-p3 100% 50% 0%); }",
- ".foo{color:color(display-p3 1 .5)}",
+ ".foo{color:color(display-p3 1 .5 0)}",
);
minify_test(
".foo { color: color(xyz-d50 0.2005 0.14089 0.4472); }",
@@ -16070,24 +17973,27 @@ mod tests {
".foo { color: color(xyz 20.05% 14.089% 44.72%); }",
".foo{color:color(xyz .2005 .14089 .4472)}",
);
- minify_test(".foo { color: color(xyz 0.2005 0 0); }", ".foo{color:color(xyz .2005)}");
- minify_test(".foo { color: color(xyz 0 0 0); }", ".foo{color:color(xyz)}");
- minify_test(".foo { color: color(xyz 0 1 0); }", ".foo{color:color(xyz 0 1)}");
- minify_test(".foo { color: color(xyz 0 1); }", ".foo{color:color(xyz 0 1)}");
- minify_test(".foo { color: color(xyz 1); }", ".foo{color:color(xyz 1)}");
- minify_test(".foo { color: color(xyz); }", ".foo{color:color(xyz)}");
+ minify_test(
+ ".foo { color: color(xyz 0.2005 0 0); }",
+ ".foo{color:color(xyz .2005 0 0)}",
+ );
+ minify_test(".foo { color: color(xyz 0 0 0); }", ".foo{color:color(xyz 0 0 0)}");
+ minify_test(".foo { color: color(xyz 0 1 0); }", ".foo{color:color(xyz 0 1 0)}");
minify_test(
".foo { color: color(xyz 0 1 0 / 20%); }",
- ".foo{color:color(xyz 0 1/.2)}",
+ ".foo{color:color(xyz 0 1 0/.2)}",
+ );
+ minify_test(
+ ".foo { color: color(xyz 0 0 0 / 20%); }",
+ ".foo{color:color(xyz 0 0 0/.2)}",
);
- minify_test(".foo { color: color(xyz / 20%); }", ".foo{color:color(xyz/.2)}");
minify_test(
".foo { color: color(display-p3 100% 50% 0 / 20%); }",
- ".foo{color:color(display-p3 1 .5/.2)}",
+ ".foo{color:color(display-p3 1 .5 0/.2)}",
);
minify_test(
- ".foo { color: color(display-p3 100% / 20%); }",
- ".foo{color:color(display-p3 1/.2)}",
+ ".foo { color: color(display-p3 100% 0 0 / 20%); }",
+ ".foo{color:color(display-p3 1 0 0/.2)}",
);
minify_test(".foo { color: hsl(none none none) }", ".foo{color:#000}");
minify_test(".foo { color: hwb(none none none) }", ".foo{color:red}");
@@ -16573,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 {
@@ -16675,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 {
@@ -16852,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))",
@@ -16868,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))}",
@@ -16987,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).
@@ -17125,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)",
);
@@ -17263,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)",
);
@@ -17726,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),
@@ -17734,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),
@@ -17753,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().
@@ -18622,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}",
);
}
}
@@ -18731,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:
@@ -20034,7 +22025,7 @@ mod tests {
".foo {{ color: color-mix(in {0}, color({0} -2 -3 -4 / -5), color({0} -4 -6 -8 / -10)) }}",
color_space
),
- &format!(".foo{{color:color({}/0)}}", result_color_space),
+ &format!(".foo{{color:color({} 0 0 0/0)}}", result_color_space),
);
minify_test(
@@ -20103,7 +22094,6 @@ mod tests {
}
}
- #[cfg(feature = "grid")]
#[test]
fn test_grid() {
minify_test(
@@ -20251,82 +22241,290 @@ 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]}",
- );
- minify_test(
- r#"
- .foo {
- grid-template: "head head"
- "nav main" 1fr
- "foot ....";
+ indoc! { r#"
+ .test-miss-areas-3 {
+ grid-template: "a a a" 30px
+ "b c c" 60px
+ ". . ." 100px
+ / 1fr 1fr 1fr;
}
- "#,
- ".foo{grid-template:\"head head\"\"nav main\"1fr\"foot.\"}",
+ "#},
);
- minify_test(
+
+ test(
r#"
- .foo {
- grid-template: [header-top] "a a a" [header-bottom]
- [main-top] "b b b" 1fr [main-bottom]
- / auto 1fr auto;
+ .test-miss-areas-4 {
+ grid: 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]/auto 1fr auto}",
+ 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(
- ".foo { grid-template: auto 1fr / auto 1fr auto; }",
- ".foo{grid-template:auto 1fr/auto 1fr auto}",
+ r#"
+ .grid-shorthand-areas {
+ grid: auto / 1fr 3fr;
+ grid-template-areas: ". content .";
+ }
+ "#,
+ ".grid-shorthand-areas{grid:\".content.\"/1fr 3fr}",
);
minify_test(
- ".foo { grid-template: [linename1 linename2] 100px repeat(auto-fit, [linename1] 300px) [linename3] / [linename1 linename2] 100px repeat(auto-fit, [linename1] 300px) [linename3]; }",
- ".foo{grid-template:[linename1 linename2]100px repeat(auto-fit,[linename1]300px)[linename3]/[linename1 linename2]100px repeat(auto-fit,[linename1]300px)[linename3]}"
+ 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(
- ".foo{grid-template:[header-top]\"a a a\"[header-bottom main-top]\"b b b\"1fr[main-bottom]/auto 1fr auto}",
- indoc! {r#"
- .foo {
- grid-template: [header-top] "a a a" [header-bottom]
- [main-top] "b b b" 1fr [main-bottom]
- / auto 1fr auto;
- }
- "#},
+ 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(
- ".foo{grid-template:[header-top]\"a a a\"[main-top]\"b b b\"1fr/auto 1fr auto}",
- indoc! {r#"
- .foo {
- grid-template: [header-top] "a a a"
- [main-top] "b b b" 1fr
- / auto 1fr auto;
- }
- "#},
- );
-
- minify_test(".foo { grid-auto-flow: row }", ".foo{grid-auto-flow:row}");
+ 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 {
+ grid-template: [header-top] "a a a" [header-bottom]
+ [main-top] "b b b" 1fr [main-bottom]
+ / auto 1fr auto;
+ }
+ "#,
+ ".foo{grid-template:[header-top]\"a a a\"[header-bottom main-top]\"b b b\"1fr[main-bottom]/auto 1fr auto}",
+ );
+
+ minify_test(
+ ".foo { grid-template: auto 1fr / auto 1fr auto; }",
+ ".foo{grid-template:auto 1fr/auto 1fr auto}",
+ );
+ minify_test(
+ ".foo { grid-template: [linename1 linename2] 100px repeat(auto-fit, [linename1] 300px) [linename3] / [linename1 linename2] 100px repeat(auto-fit, [linename1] 300px) [linename3]; }",
+ ".foo{grid-template:[linename1 linename2]100px repeat(auto-fit,[linename1]300px)[linename3]/[linename1 linename2]100px repeat(auto-fit,[linename1]300px)[linename3]}"
+ );
+
+ test(
+ ".foo{grid-template:[header-top]\"a a a\"[header-bottom main-top]\"b b b\"1fr[main-bottom]/auto 1fr auto}",
+ indoc! {r#"
+ .foo {
+ grid-template: [header-top] "a a a" [header-bottom]
+ [main-top] "b b b" 1fr [main-bottom]
+ / auto 1fr auto;
+ }
+ "#},
+ );
+ test(
+ ".foo{grid-template:[header-top]\"a a a\"[main-top]\"b b b\"1fr/auto 1fr auto}",
+ indoc! {r#"
+ .foo {
+ grid-template: [header-top] "a a a"
+ [main-top] "b b b" 1fr
+ / auto 1fr auto;
+ }
+ "#},
+ );
+
+ minify_test(".foo { grid-auto-flow: row }", ".foo{grid-auto-flow:row}");
minify_test(".foo { grid-auto-flow: column }", ".foo{grid-auto-flow:column}");
minify_test(".foo { grid-auto-flow: row dense }", ".foo{grid-auto-flow:dense}");
minify_test(".foo { grid-auto-flow: dense row }", ".foo{grid-auto-flow:dense}");
@@ -20930,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)); }",
@@ -20950,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#"
@@ -20979,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 {
@@ -21002,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 {
@@ -21032,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 {
@@ -21202,13 +23537,35 @@ mod tests {
prefix_test(
r#"
- .foo {
- --foo: color(display-p3 0 1 0);
+ @supports (color: color(display-p3 0 0 0)) {
+ .foo {
+ --foo: color(display-p3 0 1 0);
+ }
}
"#,
indoc! {r#"
- .foo {
- --foo: color(display-p3 0 1 0);
+ @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 {
+ --foo: color(display-p3 0 1 0);
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ --foo: color(display-p3 0 1 0);
}
"#},
Browsers {
@@ -21364,6 +23721,39 @@ mod tests {
},
);
+ prefix_test(
+ 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);
+ }
+ }
+ }
+ "#,
+ 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 {
@@ -21418,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 {
@@ -21556,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),
@@ -21777,7 +24225,7 @@ mod tests {
grid-auto-flow: column;
}
- @media (min-width: 1024px) {
+ @media not (max-width: 1024px) {
.foo {
max-inline-size: 1024px;
}
@@ -22644,13 +25092,13 @@ mod tests {
}
"#,
indoc! {r#"
- .foo {
- color: red;
- }
-
.foo .bar {
color: #00f;
}
+
+ .foo {
+ color: red;
+ }
"#},
);
@@ -22664,12 +25112,16 @@ mod tests {
"#,
indoc! {r#"
article {
- color: red;
+ color: green;
}
article {
color: #00f;
}
+
+ article {
+ color: red;
+ }
"#},
);
@@ -22778,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;
}
+ }
"#},
);
@@ -22954,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]
@@ -23048,75 +25575,168 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
- #[cfg(feature = "grid")]
css_modules_test(
r#"
- body {
- grid: [header-top] "a a a" [header-bottom]
- [main-top] "b b b" 1fr [main-bottom]
- / auto 1fr auto;
+ .foo {
+ color: red;
}
- header {
- grid-area: a;
+ #id {
+ animation: 2s test;
}
- main {
- grid-row: main-top / main-bottom;
+ @keyframes test {
+ from { color: red }
+ to { color: yellow }
}
"#,
indoc! {r#"
- body {
- grid: [EgL3uq_header-top] "EgL3uq_a EgL3uq_a EgL3uq_a" [EgL3uq_header-bottom]
- [EgL3uq_main-top] "EgL3uq_b EgL3uq_b EgL3uq_b" 1fr [EgL3uq_main-bottom]
- / auto 1fr auto;
+ .EgL3uq_foo {
+ color: red;
}
- header {
- grid-area: EgL3uq_a;
+ #EgL3uq_id {
+ animation: 2s test;
}
- main {
- grid-row: EgL3uq_main-top / EgL3uq_main-bottom;
+ @keyframes test {
+ from {
+ color: red;
+ }
+
+ to {
+ color: #ff0;
+ }
}
"#},
map! {
- "header-top" => "EgL3uq_header-top",
- "header-bottom" => "EgL3uq_header-bottom",
- "main-top" => "EgL3uq_main-top",
- "main-bottom" => "EgL3uq_main-bottom",
- "a" => "EgL3uq_a",
- "b" => "EgL3uq_b"
+ "foo" => "EgL3uq_foo",
+ "id" => "EgL3uq_id"
},
HashMap::new(),
- Default::default(),
+ crate::css_modules::Config {
+ animation: false,
+ // custom_idents: false,
+ ..Default::default()
+ },
+ false,
);
- #[cfg(feature = "grid")]
css_modules_test(
r#"
- .grid {
- grid-template-areas: "foo";
- }
+ @counter-style circles {
+ symbols: Ⓐ Ⓑ Ⓒ;
+ }
- .foo {
- grid-area: foo;
- }
+ ul {
+ list-style: circles;
+ }
- .bar {
- grid-column-start: foo-start;
- }
- "#,
+ ol {
+ list-style-type: none;
+ }
+
+ li {
+ list-style-type: disc;
+ }
+ "#,
indoc! {r#"
- .EgL3uq_grid {
- grid-template-areas: "EgL3uq_foo";
- }
+ @counter-style circles {
+ symbols: Ⓐ Ⓑ Ⓒ;
+ }
- .EgL3uq_foo {
- grid-area: EgL3uq_foo;
- }
+ ul {
+ list-style: circles;
+ }
+
+ ol {
+ list-style-type: none;
+ }
+
+ li {
+ list-style-type: disc;
+ }
+ "#},
+ map! {
+ "circles" => "EgL3uq_circles" referenced: true
+ },
+ HashMap::new(),
+ crate::css_modules::Config {
+ custom_idents: false,
+ ..Default::default()
+ },
+ false,
+ );
+
+ css_modules_test(
+ r#"
+ body {
+ grid: [header-top] "a a a" [header-bottom]
+ [main-top] "b b b" 1fr [main-bottom]
+ / auto 1fr auto;
+ }
+
+ header {
+ grid-area: a;
+ }
+
+ main {
+ grid-row: main-top / main-bottom;
+ }
+ "#,
+ indoc! {r#"
+ body {
+ grid: [EgL3uq_header-top] "EgL3uq_a EgL3uq_a EgL3uq_a" [EgL3uq_header-bottom]
+ [EgL3uq_main-top] "EgL3uq_b EgL3uq_b EgL3uq_b" 1fr [EgL3uq_main-bottom]
+ / auto 1fr auto;
+ }
+
+ header {
+ grid-area: EgL3uq_a;
+ }
+
+ main {
+ grid-row: EgL3uq_main-top / EgL3uq_main-bottom;
+ }
+ "#},
+ map! {
+ "header-top" => "EgL3uq_header-top",
+ "header-bottom" => "EgL3uq_header-bottom",
+ "main-top" => "EgL3uq_main-top",
+ "main-bottom" => "EgL3uq_main-bottom",
+ "a" => "EgL3uq_a",
+ "b" => "EgL3uq_b"
+ },
+ HashMap::new(),
+ Default::default(),
+ false,
+ );
+
+ css_modules_test(
+ r#"
+ .grid {
+ grid-template-areas: "foo";
+ }
+
+ .foo {
+ grid-area: foo;
+ }
+
+ .bar {
+ grid-column-start: foo-start;
+ }
+ "#,
+ indoc! {r#"
+ .EgL3uq_grid {
+ grid-template-areas: "EgL3uq_foo";
+ }
+
+ .EgL3uq_foo {
+ grid-area: EgL3uq_foo;
+ }
.EgL3uq_bar {
grid-column-start: EgL3uq_foo-start;
@@ -23130,6 +25750,47 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
+ );
+
+ css_modules_test(
+ r#"
+ .grid {
+ grid-template-areas: "foo";
+ }
+
+ .foo {
+ grid-area: foo;
+ }
+
+ .bar {
+ grid-column-start: foo-start;
+ }
+ "#,
+ indoc! {r#"
+ .EgL3uq_grid {
+ grid-template-areas: "foo";
+ }
+
+ .EgL3uq_foo {
+ grid-area: foo;
+ }
+
+ .EgL3uq_bar {
+ grid-column-start: foo-start;
+ }
+ "#},
+ map! {
+ "foo" => "EgL3uq_foo",
+ "grid" => "EgL3uq_grid",
+ "bar" => "EgL3uq_bar"
+ },
+ HashMap::new(),
+ crate::css_modules::Config {
+ grid: false,
+ ..Default::default()
+ },
+ false,
);
css_modules_test(
@@ -23146,6 +25807,7 @@ mod tests {
map! {},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23180,6 +25842,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
// :global(:local(.hi)) {
@@ -23212,6 +25875,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23241,6 +25905,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23278,6 +25943,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23297,6 +25963,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23316,6 +25983,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23335,6 +26003,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23354,6 +26023,7 @@ mod tests {
},
HashMap::new(),
Default::default(),
+ false,
);
css_modules_test(
@@ -23379,164 +26049,491 @@ mod tests {
}
"#},
map! {
- "test" => "EgL3uq_test" "EgL3uq_foo" "foo" from "foo.css" "bar" from "bar.css",
- "foo" => "EgL3uq_foo"
+ "test" => "EgL3uq_test" "EgL3uq_foo" "foo" from "foo.css" "bar" from "bar.css",
+ "foo" => "EgL3uq_foo"
+ },
+ HashMap::new(),
+ Default::default(),
+ false,
+ );
+
+ css_modules_test(
+ r#"
+ .foo {
+ color: red;
+ }
+ "#,
+ indoc! {r#"
+ .test-EgL3uq-foo {
+ color: red;
+ }
+ "#},
+ map! {
+ "foo" => "test-EgL3uq-foo"
+ },
+ HashMap::new(),
+ crate::css_modules::Config {
+ pattern: crate::css_modules::Pattern::parse("test-[hash]-[local]").unwrap(),
+ ..Default::default()
+ },
+ false,
+ );
+
+ let stylesheet = StyleSheet::parse(
+ r#"
+ .grid {
+ grid-template-areas: "foo";
+ }
+
+ .foo {
+ grid-area: foo;
+ }
+
+ .bar {
+ grid-column-start: foo-start;
+ }
+ "#,
+ ParserOptions {
+ css_modules: Some(crate::css_modules::Config {
+ pattern: crate::css_modules::Pattern::parse("test-[local]-[hash]").unwrap(),
+ ..Default::default()
+ }),
+ ..ParserOptions::default()
+ },
+ )
+ .unwrap();
+ if let Err(err) = stylesheet.to_css(PrinterOptions::default()) {
+ assert_eq!(err.kind, PrinterErrorKind::InvalidCssModulesPatternInGrid);
+ } else {
+ unreachable!()
+ }
+
+ css_modules_test(
+ r#"
+ @property --foo {
+ syntax: '';
+ inherits: false;
+ initial-value: yellow;
+ }
+
+ .foo {
+ --foo: red;
+ color: var(--foo);
+ }
+ "#,
+ indoc! {r#"
+ @property --foo {
+ syntax: "";
+ inherits: false;
+ initial-value: #ff0;
+ }
+
+ .EgL3uq_foo {
+ --foo: red;
+ color: var(--foo);
+ }
+ "#},
+ map! {
+ "foo" => "EgL3uq_foo"
+ },
+ HashMap::new(),
+ Default::default(),
+ false,
+ );
+
+ css_modules_test(
+ r#"
+ @property --foo {
+ syntax: '';
+ inherits: false;
+ initial-value: yellow;
+ }
+
+ @font-palette-values --Cooler {
+ font-family: Bixa;
+ base-palette: 1;
+ override-colors: 1 #7EB7E4;
+ }
+
+ .foo {
+ --foo: red;
+ --bar: green;
+ color: var(--foo);
+ font-palette: --Cooler;
+ }
+
+ .bar {
+ color: var(--color from "./b.css");
+ }
+ "#,
+ indoc! {r#"
+ @property --EgL3uq_foo {
+ syntax: "";
+ inherits: false;
+ initial-value: #ff0;
+ }
+
+ @font-palette-values --EgL3uq_Cooler {
+ font-family: Bixa;
+ base-palette: 1;
+ override-colors: 1 #7eb7e4;
+ }
+
+ .EgL3uq_foo {
+ --EgL3uq_foo: red;
+ --EgL3uq_bar: green;
+ color: var(--EgL3uq_foo);
+ font-palette: --EgL3uq_Cooler;
+ }
+
+ .EgL3uq_bar {
+ color: var(--ma1CsG);
+ }
+ "#},
+ map! {
+ "foo" => "EgL3uq_foo",
+ "--foo" => "--EgL3uq_foo" referenced: true,
+ "--bar" => "--EgL3uq_bar",
+ "bar" => "EgL3uq_bar",
+ "--Cooler" => "--EgL3uq_Cooler" referenced: true
+ },
+ HashMap::from([(
+ "--ma1CsG".into(),
+ CssModuleReference::Dependency {
+ name: "--color".into(),
+ specifier: "./b.css".into(),
+ },
+ )]),
+ crate::css_modules::Config {
+ dashed_idents: true,
+ ..Default::default()
+ },
+ false,
+ );
+
+ css_modules_test(
+ r#"
+ .test {
+ animation: rotate var(--duration) linear infinite;
+ }
+ "#,
+ indoc! {r#"
+ .EgL3uq_test {
+ animation: EgL3uq_rotate var(--duration) linear infinite;
+ }
+ "#},
+ map! {
+ "test" => "EgL3uq_test",
+ "rotate" => "EgL3uq_rotate" referenced: true
+ },
+ HashMap::new(),
+ Default::default(),
+ false,
+ );
+ css_modules_test(
+ r#"
+ .test {
+ animation: none var(--duration);
+ }
+ "#,
+ indoc! {r#"
+ .EgL3uq_test {
+ animation: none var(--duration);
+ }
+ "#},
+ map! {
+ "test" => "EgL3uq_test"
+ },
+ HashMap::new(),
+ Default::default(),
+ false,
+ );
+ css_modules_test(
+ r#"
+ .test {
+ animation: var(--animation);
+ }
+ "#,
+ indoc! {r#"
+ .EgL3uq_test {
+ animation: var(--animation);
+ }
+ "#},
+ map! {
+ "test" => "EgL3uq_test"
+ },
+ HashMap::new(),
+ Default::default(),
+ false,
+ );
+ css_modules_test(
+ r#"
+ .test {
+ animation: rotate var(--duration);
+ }
+ "#,
+ indoc! {r#"
+ .EgL3uq_test {
+ animation: rotate var(--duration);
+ }
+ "#},
+ map! {
+ "test" => "EgL3uq_test"
+ },
+ HashMap::new(),
+ crate::css_modules::Config {
+ animation: false,
+ ..Default::default()
+ },
+ false,
+ );
+ css_modules_test(
+ r#"
+ .test {
+ animation: "rotate" var(--duration);
+ }
+ "#,
+ indoc! {r#"
+ .EgL3uq_test {
+ animation: EgL3uq_rotate var(--duration);
+ }
+ "#},
+ map! {
+ "test" => "EgL3uq_test",
+ "rotate" => "EgL3uq_rotate" referenced: true
+ },
+ HashMap::new(),
+ crate::css_modules::Config { ..Default::default() },
+ false,
+ );
+
+ css_modules_test(
+ r#"
+ .test {
+ composes: foo bar from "foo.css";
+ background: white;
+ }
+ "#,
+ 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,
+ );
+
+ 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(),
- Default::default(),
+ crate::css_modules::Config { ..Default::default() },
+ false,
);
css_modules_test(
r#"
- .foo {
- color: red;
+ .box2 {
+ @container main (width >= 0) {
+ background-color: #90ee90;
+ }
}
"#,
indoc! {r#"
- .test-EgL3uq-foo {
- color: red;
+ .EgL3uq_box2 {
+ @container main (width >= 0) {
+ background-color: #90ee90;
+ }
}
"#},
map! {
- "foo" => "test-EgL3uq-foo"
+ "box2" => "EgL3uq_box2"
},
HashMap::new(),
crate::css_modules::Config {
- pattern: crate::css_modules::Pattern::parse("test-[hash]-[local]").unwrap(),
+ container: false,
..Default::default()
},
+ false,
);
- let stylesheet = StyleSheet::parse(
- r#"
- .grid {
- grid-template-areas: "foo";
- }
-
- .foo {
- grid-area: foo;
- }
-
- .bar {
- grid-column-start: foo-start;
- }
- "#,
- ParserOptions {
- css_modules: Some(crate::css_modules::Config {
- pattern: crate::css_modules::Pattern::parse("test-[local]-[hash]").unwrap(),
- ..Default::default()
- }),
- ..ParserOptions::default()
+ css_modules_test(
+ ".foo { view-transition-name: bar }",
+ ".EgL3uq_foo{view-transition-name:EgL3uq_bar}",
+ map! {
+ "foo" => "EgL3uq_foo",
+ "bar" => "EgL3uq_bar"
},
- )
- .unwrap();
- if let Err(err) = stylesheet.to_css(PrinterOptions::default()) {
- assert_eq!(err.kind, PrinterErrorKind::InvalidCssModulesPatternInGrid);
- } else {
- unreachable!()
- }
-
+ HashMap::new(),
+ Default::default(),
+ true,
+ );
css_modules_test(
- r#"
- @property --foo {
- syntax: '';
- inherits: false;
- initial-value: yellow;
- }
-
- .foo {
- --foo: red;
- color: var(--foo);
- }
- "#,
- indoc! {r#"
- @property --foo {
- syntax: "";
- inherits: false;
- initial-value: #ff0;
- }
-
- .EgL3uq_foo {
- --foo: red;
- color: var(--foo);
- }
- "#},
+ ".foo { view-transition-name: none }",
+ ".EgL3uq_foo{view-transition-name:none}",
map! {
"foo" => "EgL3uq_foo"
},
HashMap::new(),
Default::default(),
+ true,
);
-
css_modules_test(
- r#"
- @property --foo {
- syntax: '';
- inherits: false;
- initial-value: yellow;
- }
-
- @font-palette-values --Cooler {
- font-family: Bixa;
- base-palette: 1;
- override-colors: 1 #7EB7E4;
- }
-
- .foo {
- --foo: red;
- --bar: green;
- color: var(--foo);
- font-palette: --Cooler;
- }
-
- .bar {
- color: var(--color from "./b.css");
- }
- "#,
- indoc! {r#"
- @property --EgL3uq_foo {
- syntax: "";
- inherits: false;
- initial-value: #ff0;
- }
+ ".foo { view-transition-name: auto }",
+ ".EgL3uq_foo{view-transition-name:auto}",
+ map! {
+ "foo" => "EgL3uq_foo"
+ },
+ HashMap::new(),
+ Default::default(),
+ true,
+ );
- @font-palette-values --EgL3uq_Cooler {
- font-family: Bixa;
- base-palette: 1;
- override-colors: 1 #7eb7e4;
- }
+ 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,
+ );
- .EgL3uq_foo {
- --EgL3uq_foo: red;
- --EgL3uq_bar: green;
- color: var(--EgL3uq_foo);
- font-palette: --EgL3uq_Cooler;
- }
+ 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,
+ );
- .EgL3uq_bar {
- color: var(--ma1CsG);
- }
- "#},
+ css_modules_test(
+ "@view-transition { types: foo bar baz }",
+ "@view-transition{types:EgL3uq_foo EgL3uq_bar EgL3uq_baz}",
map! {
"foo" => "EgL3uq_foo",
- "--foo" => "--EgL3uq_foo" referenced: true,
- "--bar" => "--EgL3uq_bar",
"bar" => "EgL3uq_bar",
- "--Cooler" => "--EgL3uq_Cooler" referenced: true
+ "baz" => "EgL3uq_baz"
},
- HashMap::from([(
- "--ma1CsG".into(),
- CssModuleReference::Dependency {
- name: "--color".into(),
- specifier: "./b.css".into(),
- },
- )]),
- crate::css_modules::Config {
- dashed_idents: true,
- ..Default::default()
+ 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(
@@ -23575,6 +26572,56 @@ mod tests {
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]
@@ -23858,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}");
@@ -24116,21 +27165,42 @@ mod tests {
fill: url("#foo") color(display-p3 .972962 -.362078 .804206);
fill: url("#foo") lch(50.998% 135.363 338);
}
- "##},
+ "##},
+ Browsers {
+ chrome: Some(90 << 16),
+ safari: Some(14 << 16),
+ ..Browsers::default()
+ },
+ );
+
+ prefix_test(
+ ".foo { fill: var(--url) lch(50.998% 135.363 338) }",
+ indoc! { r#"
+ .foo {
+ fill: var(--url) #ee00be;
+ }
+
+ @supports (color: lab(0% 0 0)) {
+ .foo {
+ fill: var(--url) lab(50.998% 125.506 -50.7078);
+ }
+ }
+ "#},
Browsers {
chrome: Some(90 << 16),
- safari: Some(14 << 16),
..Browsers::default()
},
);
prefix_test(
- ".foo { fill: var(--url) lch(50.998% 135.363 338) }",
- indoc! { r#"
- .foo {
- fill: var(--url) #ee00be;
+ 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);
@@ -24148,7 +27218,7 @@ mod tests {
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));
@@ -24210,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;
@@ -24258,6 +27328,28 @@ mod tests {
},
);
+ prefix_test(
+ 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#"
+ @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#"
@@ -24643,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]
@@ -24814,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(
@@ -24904,7 +28087,7 @@ mod tests {
}
}
"#,
- ".foo{@scope(.bar){&{color:#ff0}}}",
+ ".foo{@scope(.bar){color:#ff0}}",
);
nesting_test(
r#"
@@ -24916,9 +28099,7 @@ mod tests {
"#,
indoc! {r#"
@scope (.bar) {
- :scope {
- color: #ff0;
- }
+ color: #ff0;
}
"#},
);
@@ -25308,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;
}
@@ -26058,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 {
@@ -26253,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]
@@ -26340,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};
@@ -26368,6 +29644,14 @@ mod tests {
color: red;
}
}
+
+ input:placeholder {
+ color: red;
+ }
+
+ input::hover {
+ color: red;
+ }
"#,
indoc! { r#"
.foo {
@@ -26383,6 +29667,14 @@ mod tests {
color: red;
}
}
+
+ input:placeholder {
+ color: red;
+ }
+
+ input::hover {
+ color: red;
+ }
"#},
ParserOptions {
filename: "test.css".into(),
@@ -26420,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,
+ }),
+ },
]
)
}
@@ -26438,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#"
@@ -26748,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
@@ -26788,10 +30178,40 @@ 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 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,
);
}
@@ -26806,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 {
@@ -26970,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)) {
@@ -26995,6 +30438,7 @@ mod tests {
dashed_idents: true,
..Default::default()
},
+ false,
);
}
@@ -27074,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; }",
@@ -27182,6 +30632,45 @@ mod tests {
..Browsers::default()
},
);
+ prefix_test(
+ r#"
+ .foo {
+ box-shadow:
+ oklch(100% 0 0deg / 50%) 0 0.63rem 0.94rem -0.19rem,
+ currentColor 0 0.44rem 0.8rem -0.58rem;
+ }
+ "#,
+ indoc! { r#"
+ .foo {
+ box-shadow: 0 .63rem .94rem -.19rem #ffffff80, 0 .44rem .8rem -.58rem;
+ box-shadow: 0 .63rem .94rem -.19rem lab(100% 0 0 / .5), 0 .44rem .8rem -.58rem;
+ }
+ "#},
+ Browsers {
+ chrome: Some(95 << 16),
+ ..Browsers::default()
+ },
+ );
+ prefix_test(
+ 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)); }",
indoc! { r#"
@@ -27243,5 +30732,271 @@ 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]
+ fn test_all() {
+ minify_test(".foo { all: initial; all: initial }", ".foo{all:initial}");
+ minify_test(".foo { all: initial; all: revert }", ".foo{all:revert}");
+ minify_test(".foo { background: red; all: revert-layer }", ".foo{all:revert-layer}");
+ minify_test(
+ ".foo { background: red; all: revert-layer; background: green }",
+ ".foo{all:revert-layer;background:green}",
+ );
+ minify_test(
+ ".foo { --test: red; all: revert-layer }",
+ ".foo{--test:red;all:revert-layer}",
+ );
+ minify_test(
+ ".foo { unicode-bidi: embed; all: revert-layer }",
+ ".foo{all:revert-layer;unicode-bidi:embed}",
+ );
+ minify_test(
+ ".foo { direction: rtl; all: revert-layer }",
+ ".foo{all:revert-layer;direction:rtl}",
+ );
+ minify_test(
+ ".foo { direction: rtl; all: revert-layer; direction: ltr }",
+ ".foo{all:revert-layer;direction:ltr}",
+ );
+ minify_test(".foo { background: var(--foo); all: unset; }", ".foo{all:unset}");
+ minify_test(
+ ".foo { all: unset; background: var(--foo); }",
+ ".foo{all:unset;background:var(--foo)}",
+ );
+ 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 e102676f9..a50d54f5a 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -8,12 +8,12 @@ macro_rules! enum_property {
)+
}
) => {
- $(#[$outer])*
- #[derive(Debug, Clone, Copy, PartialEq)]
+ #[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
- #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))]
+ #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "kebab-case"))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
+ $(#[$outer])*
$vis enum $name {
$(
$(#[$meta])*
@@ -21,50 +21,17 @@ macro_rules! enum_property {
)+
}
- impl<'i> Parse<'i> for $name {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result