diff --git a/CHANGELOG.md b/CHANGELOG.md index 351d4e4837a5..914177cfbb0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure container query variant names can contain hyphens ([#17628](https://github.com/tailwindlabs/tailwindcss/pull/17628)) - Ensure `shadow-inherit`, `inset-shadow-inherit`, `drop-shadow-inherit`, and `text-shadow-inherit` inherits the shadow color ([#17647](https://github.com/tailwindlabs/tailwindcss/pull/17647)) - Ensure compatibility with array tuples used in `fontSize` JS theme keys ([#17630](https://github.com/tailwindlabs/tailwindcss/pull/17630)) +- Ensure folders with binary file extensions in its name are scanned for utilities ([#17595](https://github.com/tailwindlabs/tailwindcss/pull/17595)) - Upgrade: Convert `fontSize` array tuple syntax to CSS theme variables ([#17630](https://github.com/tailwindlabs/tailwindcss/pull/17630)) ## [4.1.3] - 2025-04-04 diff --git a/crates/ignore/src/gitignore.rs b/crates/ignore/src/gitignore.rs index 30f1ccef2d7c..7da86153cf79 100644 --- a/crates/ignore/src/gitignore.rs +++ b/crates/ignore/src/gitignore.rs @@ -85,6 +85,8 @@ pub struct Gitignore { num_ignores: u64, num_whitelists: u64, matches: Option>>>, + // CHANGED: Add a flag to have Gitignore rules that apply only to files. + only_on_files: bool, } impl Gitignore { @@ -140,6 +142,8 @@ impl Gitignore { num_ignores: 0, num_whitelists: 0, matches: None, + // CHANGED: Add a flag to have Gitignore rules that apply only to files. + only_on_files: false, } } @@ -240,6 +244,10 @@ impl Gitignore { if self.is_empty() { return Match::None; } + // CHANGED: Rules marked as only_on_files can not match against directories. + if self.only_on_files && is_dir { + return Match::None; + } let path = path.as_ref(); let mut matches = self.matches.as_ref().unwrap().get(); let candidate = Candidate::new(path); @@ -295,6 +303,8 @@ pub struct GitignoreBuilder { root: PathBuf, globs: Vec, case_insensitive: bool, + // CHANGED: Add a flag to have Gitignore rules that apply only to files. + only_on_files: bool, } impl GitignoreBuilder { @@ -311,6 +321,8 @@ impl GitignoreBuilder { root: strip_prefix("./", root).unwrap_or(root).to_path_buf(), globs: vec![], case_insensitive: false, + // CHANGED: Add a flag to have Gitignore rules that apply only to files. + only_on_files: false, } } @@ -331,6 +343,8 @@ impl GitignoreBuilder { num_ignores: nignore as u64, num_whitelists: nwhite as u64, matches: Some(Arc::new(Pool::new(|| vec![]))), + // CHANGED: Add a flag to have Gitignore rules that apply only to files. + only_on_files: self.only_on_files, }) } @@ -514,6 +528,16 @@ impl GitignoreBuilder { self.case_insensitive = yes; Ok(self) } + + /// CHANGED: Add a flag to have Gitignore rules that apply only to files. + /// + /// If this is set, then the globs will only be matched against file paths. + /// This will ensure that ignore rules like `*.pages` will _only_ ignore + /// files ending in `.pages` and not folders ending in `.pages`. + pub fn only_on_files(&mut self, yes: bool) -> &mut GitignoreBuilder { + self.only_on_files = yes; + self + } } /// Return the file path of the current environment's global gitignore file. diff --git a/crates/oxide/src/scanner/auto_source_detection.rs b/crates/oxide/src/scanner/auto_source_detection.rs index e9b7f64aacbc..0d723a1ab1f5 100644 --- a/crates/oxide/src/scanner/auto_source_detection.rs +++ b/crates/oxide/src/scanner/auto_source_detection.rs @@ -10,15 +10,21 @@ use std::sync; /// - Ignoring common binary file extensions like `.png` and `.jpg` /// - Ignoring common files like `yarn.lock` and `package-lock.json` /// -pub static RULES: sync::LazyLock = sync::LazyLock::new(|| { +pub static RULES: sync::LazyLock> = sync::LazyLock::new(|| { let mut builder = GitignoreBuilder::new(""); builder.add_line(None, &IGNORED_CONTENT_DIRS_GLOB).unwrap(); builder.add_line(None, &IGNORED_EXTENSIONS_GLOB).unwrap(); - builder.add_line(None, &BINARY_EXTENSIONS_GLOB).unwrap(); builder.add_line(None, &IGNORED_FILES_GLOB).unwrap(); - builder.build().unwrap() + // Ensure these rules do not match on folder names + let mut file_only_builder = GitignoreBuilder::new(""); + file_only_builder + .only_on_files(true) + .add_line(None, &BINARY_EXTENSIONS_GLOB) + .unwrap(); + + vec![builder.build().unwrap(), file_only_builder.build().unwrap()] }); pub static IGNORED_CONTENT_DIRS: sync::LazyLock> = sync::LazyLock::new(|| { diff --git a/crates/oxide/src/scanner/mod.rs b/crates/oxide/src/scanner/mod.rs index fad6fa712da4..5512ad000354 100644 --- a/crates/oxide/src/scanner/mod.rs +++ b/crates/oxide/src/scanner/mod.rs @@ -630,7 +630,9 @@ fn create_walker(sources: Sources) -> Option { } // Setup auto source detection rules - builder.add_gitignore(auto_source_detection::RULES.clone()); + for ignore in auto_source_detection::RULES.iter() { + builder.add_gitignore(ignore.clone()); + } // Setup ignores based on `@source` definitions for (base, patterns) in ignores { diff --git a/crates/oxide/tests/scanner.rs b/crates/oxide/tests/scanner.rs index 681d63620f22..4003ff253048 100644 --- a/crates/oxide/tests/scanner.rs +++ b/crates/oxide/tests/scanner.rs @@ -311,6 +311,31 @@ mod scanner { assert_eq!(normalized_sources, vec!["**/*"]); } + // https://github.com/tailwindlabs/tailwindcss/issues/17569 + #[test] + fn it_should_not_ignore_folders_that_end_with_a_binary_extension() { + let ScanResult { + files, + globs, + normalized_sources, + .. + } = scan(&[ + // Looks like `.pages` binary extension, but it's a folder + ("some.pages/index.html", "content-['some.pages/index.html']"), + // Ignore a specific folder. This is to ensure that this still "wins" from the internal + // solution of dealing with binary extensions for files only. + (".gitignore", "other.pages"), + ( + "other.pages/index.html", + "content-['other.pages/index.html']", + ), + ]); + + assert_eq!(files, vec!["some.pages/index.html"]); + assert_eq!(globs, vec!["*", "some.pages/**/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}"]); + assert_eq!(normalized_sources, vec!["**/*"]); + } + #[test] fn it_should_ignore_known_extensions() { let ScanResult {