From e42ace1143ada14d4201f1dc2d50630707c0335b Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Mar 2024 17:38:17 +0100 Subject: [PATCH 1/9] handle `.svelte` file In svelte there is a convention where you can use `
` which means that we extract `class:px-4` as a utility where `class` is a variant. This is obviously incorrect, to solve this we can ignore the `class:` part before parsing the whole file. This is also what we do in v3. Ideally we have completely separate parsers for various programming languages (based on file type) and fallback to the generic one we have now. Implementing that, is a much bigger scope. --- oxide/crates/core/src/lib.rs | 39 +++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/oxide/crates/core/src/lib.rs b/oxide/crates/core/src/lib.rs index 6f04adae0a0c..c85b8ea52f54 100644 --- a/oxide/crates/core/src/lib.rs +++ b/oxide/crates/core/src/lib.rs @@ -1,4 +1,5 @@ use crate::parser::Extractor; +use bstr::ByteSlice; use cache::Cache; use fxhash::FxHashSet; use ignore::DirEntry; @@ -399,6 +400,26 @@ pub fn scan_files(input: Vec, options: u8) -> Vec { } } +fn read_changed_content(c: ChangedContent) -> Option> { + match (c.file, c.content) { + (Some(file), None) => match std::fs::read(&file) { + Ok(content) => match file.extension() { + Some(extension) => match extension.to_str() { + Some("svelte") => Some(content.replace(" class:", " ")), + _ => Some(content), + }, + _ => Some(content), + }, + Err(e) => { + event!(tracing::Level::ERROR, "Failed to read file: {:?}", e); + Default::default() + } + }, + (None, Some(content)) => Some(content.into_bytes()), + _ => Default::default(), + } +} + #[tracing::instrument(skip(changed_content))] fn read_all_files(changed_content: Vec) -> Vec> { event!( @@ -409,17 +430,7 @@ fn read_all_files(changed_content: Vec) -> Vec> { changed_content .into_par_iter() - .map(|c| match (c.file, c.content) { - (Some(file), None) => match std::fs::read(file) { - Ok(content) => content, - Err(e) => { - event!(tracing::Level::ERROR, "Failed to read file: {:?}", e); - Default::default() - } - }, - (None, Some(content)) => content.into_bytes(), - _ => Default::default(), - }) + .filter_map(read_changed_content) .collect() } @@ -433,11 +444,7 @@ fn read_all_files_sync(changed_content: Vec) -> Vec> { changed_content .into_iter() - .filter_map(|c| match (c.file, c.content) { - (Some(file), None) => std::fs::read(file).ok(), - (None, Some(content)) => Some(content.into_bytes()), - _ => Default::default(), - }) + .filter_map(read_changed_content) .collect() } From 6dd0ee40c5f1fb497d2999056313e87e9b783b37 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Mar 2024 18:06:26 +0100 Subject: [PATCH 2/9] flatten match arms --- oxide/crates/core/src/lib.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/oxide/crates/core/src/lib.rs b/oxide/crates/core/src/lib.rs index c85b8ea52f54..fb5cf4054ea5 100644 --- a/oxide/crates/core/src/lib.rs +++ b/oxide/crates/core/src/lib.rs @@ -403,11 +403,8 @@ pub fn scan_files(input: Vec, options: u8) -> Vec { fn read_changed_content(c: ChangedContent) -> Option> { match (c.file, c.content) { (Some(file), None) => match std::fs::read(&file) { - Ok(content) => match file.extension() { - Some(extension) => match extension.to_str() { - Some("svelte") => Some(content.replace(" class:", " ")), - _ => Some(content), - }, + Ok(content) => match file.extension().map(|x| x.to_str()) { + Some(Some("svelte")) => Some(content.replace(" class:", " ")), _ => Some(content), }, Err(e) => { From b7d3a337ca11df960a73471304df21b0a2c49df8 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Mar 2024 18:10:08 +0100 Subject: [PATCH 3/9] add test to verify we can parse `class:px-4="condition"` --- oxide/crates/core/tests/auto_content.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/oxide/crates/core/tests/auto_content.rs b/oxide/crates/core/tests/auto_content.rs index 6c0217079a99..6d9bb8fd359d 100644 --- a/oxide/crates/core/tests/auto_content.rs +++ b/oxide/crates/core/tests/auto_content.rs @@ -302,4 +302,11 @@ mod auto_content { assert_eq!(candidates, vec!["font-bold", "md:flex"]); } + + #[test] + fn it_should_scan_for_utilities_in_svelte_files() { + let candidates = scan(&[("index.svelte", Some("
"))]).1; + + assert_eq!(candidates, vec!["condition", "div", "px-4"]); + } } From 266abd2a77a1f3278a99d9af708dae19e41a23b0 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Mar 2024 18:17:33 +0100 Subject: [PATCH 4/9] explicitly ingore everything but the svelte file --- oxide/crates/core/tests/auto_content.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/oxide/crates/core/tests/auto_content.rs b/oxide/crates/core/tests/auto_content.rs index 6d9bb8fd359d..eeab7829ca72 100644 --- a/oxide/crates/core/tests/auto_content.rs +++ b/oxide/crates/core/tests/auto_content.rs @@ -305,7 +305,15 @@ mod auto_content { #[test] fn it_should_scan_for_utilities_in_svelte_files() { - let candidates = scan(&[("index.svelte", Some("
"))]).1; + let mut ignores = String::new(); + ignores.push_str("*\n"); + ignores.push_str("!*.svelte\n"); + + let candidates = scan(&[ + (".gitignore", Some(&ignores)), + ("index.svelte", Some("
")), + ]) + .1; assert_eq!(candidates, vec!["condition", "div", "px-4"]); } From 77c38a279ff97e1ecbe773a0efee38e2c3102d35 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Mar 2024 18:33:32 +0100 Subject: [PATCH 5/9] merge tests There is some funky stuff happening when running `cargo test` where it sees contents from another test. Maybe a bug in the tmpdir crate. I don't see this problem locally when running `cargo nextest run`, but when using the native `cargo test` command it fails. --- oxide/crates/core/tests/auto_content.rs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/oxide/crates/core/tests/auto_content.rs b/oxide/crates/core/tests/auto_content.rs index eeab7829ca72..030755373953 100644 --- a/oxide/crates/core/tests/auto_content.rs +++ b/oxide/crates/core/tests/auto_content.rs @@ -297,24 +297,14 @@ mod auto_content { ("foo.jpg", Some("xl:font-bold")), // A file that is ignored ("foo.html", Some("lg:font-bold")), - ]) - .1; - - assert_eq!(candidates, vec!["font-bold", "md:flex"]); - } - - #[test] - fn it_should_scan_for_utilities_in_svelte_files() { - let mut ignores = String::new(); - ignores.push_str("*\n"); - ignores.push_str("!*.svelte\n"); - - let candidates = scan(&[ - (".gitignore", Some(&ignores)), + // A svelte file with `class:foo="bar"` syntax ("index.svelte", Some("
")), ]) .1; - assert_eq!(candidates, vec!["condition", "div", "px-4"]); + assert_eq!( + candidates, + vec!["condition", "div", "font-bold", "md:flex", "px-4"] + ); } } From 003320d4fea98e35fe5df93385baf59dde105213 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Mar 2024 18:40:29 +0100 Subject: [PATCH 6/9] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89d86a5f33b5..daea2d5c8e45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Validate bare values ([#13245](https://github.com/tailwindlabs/tailwindcss/pull/13245)) +- Parse candidates in `.svelte` files with `class:abc="condition"` syntax ([#13274](https://github.com/tailwindlabs/tailwindcss/pull/13274)) ### Changed @@ -108,3 +109,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Move the CLI into a separate `@tailwindcss/cli` package ([#13095](https://github.com/tailwindlabs/tailwindcss/pull/13095)) ## [4.0.0-alpha.1] - 2024-03-06 + From 981c48c59bfcd4062e366b0ad0b98a9946dab4ce Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Mar 2024 22:49:23 +0100 Subject: [PATCH 7/9] run prettier --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index daea2d5c8e45..a7b672126d7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,4 +109,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Move the CLI into a separate `@tailwindcss/cli` package ([#13095](https://github.com/tailwindlabs/tailwindcss/pull/13095)) ## [4.0.0-alpha.1] - 2024-03-06 - From 81fd8bb02aba3d7eeb053fdea77bcd49877a2aaa Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Mar 2024 22:49:59 +0100 Subject: [PATCH 8/9] Update oxide/crates/core/src/lib.rs Co-authored-by: Jordan Pittman --- oxide/crates/core/src/lib.rs | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/oxide/crates/core/src/lib.rs b/oxide/crates/core/src/lib.rs index fb5cf4054ea5..ed0025d80a76 100644 --- a/oxide/crates/core/src/lib.rs +++ b/oxide/crates/core/src/lib.rs @@ -401,20 +401,23 @@ pub fn scan_files(input: Vec, options: u8) -> Vec { } fn read_changed_content(c: ChangedContent) -> Option> { - match (c.file, c.content) { - (Some(file), None) => match std::fs::read(&file) { - Ok(content) => match file.extension().map(|x| x.to_str()) { - Some(Some("svelte")) => Some(content.replace(" class:", " ")), - _ => Some(content), - }, - Err(e) => { - event!(tracing::Level::ERROR, "Failed to read file: {:?}", e); - Default::default() - } - }, - (None, Some(content)) => Some(content.into_bytes()), - _ => Default::default(), - } + if let Some(content) = c.content { + return Some(content.into_bytes()) + } + + let Some(file) = c.file else { + return Default::default() + } + + let Ok(content) = std::fs::read(&file) else { + event!(tracing::Level::ERROR, "Failed to read file: {:?}", e); + return Default::default() + } + + match file.extension().map(|x| x.to_str()) { + Some(Some("svelte")) => Some(content.replace(" class:", " ")), + _ => Some(content), + } } #[tracing::instrument(skip(changed_content))] From 882522eeb670db557f51cb016255b24083946c34 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Mar 2024 22:54:11 +0100 Subject: [PATCH 9/9] fixup syntax errors --- oxide/crates/core/src/lib.rs | 40 +++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/oxide/crates/core/src/lib.rs b/oxide/crates/core/src/lib.rs index ed0025d80a76..ae49d0c910a3 100644 --- a/oxide/crates/core/src/lib.rs +++ b/oxide/crates/core/src/lib.rs @@ -401,23 +401,29 @@ pub fn scan_files(input: Vec, options: u8) -> Vec { } fn read_changed_content(c: ChangedContent) -> Option> { - if let Some(content) = c.content { - return Some(content.into_bytes()) - } - - let Some(file) = c.file else { - return Default::default() - } - - let Ok(content) = std::fs::read(&file) else { - event!(tracing::Level::ERROR, "Failed to read file: {:?}", e); - return Default::default() - } - - match file.extension().map(|x| x.to_str()) { - Some(Some("svelte")) => Some(content.replace(" class:", " ")), - _ => Some(content), - } + if let Some(content) = c.content { + return Some(content.into_bytes()); + } + + let Some(file) = c.file else { + return Default::default(); + }; + + let Ok(content) = std::fs::read(&file).map_err(|e| { + event!(tracing::Level::ERROR, "Failed to read file: {:?}", e); + e + }) else { + return Default::default(); + }; + + let Some(extension) = file.extension().map(|x| x.to_str()) else { + return Some(content); + }; + + match extension { + Some("svelte") => Some(content.replace(" class:", " ")), + _ => Some(content), + } } #[tracing::instrument(skip(changed_content))]