Skip to content

Commit bbd916a

Browse files
Ignore binary extensions, except in folder names (#17595)
We generate a glob to ignore binary extensions that looks something like this: ``` *.{mp4,pages,exe,…} ``` However, if you have a folder that happens to end in `.pages` for example, then this folder will be ignored in its entirety. To solve this, we added a new flag to the `Gitignore` struct so we can register a bunch of ignore rules that _only_ apply to paths and not folders. Fixes: #17569 ## Test plan - Added a unit test --------- Co-authored-by: Philipp Spiess <hello@philippspiess.com>
1 parent d801d8d commit bbd916a

File tree

5 files changed

+62
-4
lines changed

5 files changed

+62
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
- Ensure container query variant names can contain hyphens ([#17628](https://github.com/tailwindlabs/tailwindcss/pull/17628))
2121
- 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))
2222
- Ensure compatibility with array tuples used in `fontSize` JS theme keys ([#17630](https://github.com/tailwindlabs/tailwindcss/pull/17630))
23+
- Ensure folders with binary file extensions in its name are scanned for utilities ([#17595](https://github.com/tailwindlabs/tailwindcss/pull/17595))
2324
- Upgrade: Convert `fontSize` array tuple syntax to CSS theme variables ([#17630](https://github.com/tailwindlabs/tailwindcss/pull/17630))
2425

2526
## [4.1.3] - 2025-04-04

crates/ignore/src/gitignore.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ pub struct Gitignore {
8585
num_ignores: u64,
8686
num_whitelists: u64,
8787
matches: Option<Arc<Pool<Vec<usize>>>>,
88+
// CHANGED: Add a flag to have Gitignore rules that apply only to files.
89+
only_on_files: bool,
8890
}
8991

9092
impl Gitignore {
@@ -140,6 +142,8 @@ impl Gitignore {
140142
num_ignores: 0,
141143
num_whitelists: 0,
142144
matches: None,
145+
// CHANGED: Add a flag to have Gitignore rules that apply only to files.
146+
only_on_files: false,
143147
}
144148
}
145149

@@ -240,6 +244,10 @@ impl Gitignore {
240244
if self.is_empty() {
241245
return Match::None;
242246
}
247+
// CHANGED: Rules marked as only_on_files can not match against directories.
248+
if self.only_on_files && is_dir {
249+
return Match::None;
250+
}
243251
let path = path.as_ref();
244252
let mut matches = self.matches.as_ref().unwrap().get();
245253
let candidate = Candidate::new(path);
@@ -295,6 +303,8 @@ pub struct GitignoreBuilder {
295303
root: PathBuf,
296304
globs: Vec<Glob>,
297305
case_insensitive: bool,
306+
// CHANGED: Add a flag to have Gitignore rules that apply only to files.
307+
only_on_files: bool,
298308
}
299309

300310
impl GitignoreBuilder {
@@ -311,6 +321,8 @@ impl GitignoreBuilder {
311321
root: strip_prefix("./", root).unwrap_or(root).to_path_buf(),
312322
globs: vec![],
313323
case_insensitive: false,
324+
// CHANGED: Add a flag to have Gitignore rules that apply only to files.
325+
only_on_files: false,
314326
}
315327
}
316328

@@ -331,6 +343,8 @@ impl GitignoreBuilder {
331343
num_ignores: nignore as u64,
332344
num_whitelists: nwhite as u64,
333345
matches: Some(Arc::new(Pool::new(|| vec![]))),
346+
// CHANGED: Add a flag to have Gitignore rules that apply only to files.
347+
only_on_files: self.only_on_files,
334348
})
335349
}
336350

@@ -514,6 +528,16 @@ impl GitignoreBuilder {
514528
self.case_insensitive = yes;
515529
Ok(self)
516530
}
531+
532+
/// CHANGED: Add a flag to have Gitignore rules that apply only to files.
533+
///
534+
/// If this is set, then the globs will only be matched against file paths.
535+
/// This will ensure that ignore rules like `*.pages` will _only_ ignore
536+
/// files ending in `.pages` and not folders ending in `.pages`.
537+
pub fn only_on_files(&mut self, yes: bool) -> &mut GitignoreBuilder {
538+
self.only_on_files = yes;
539+
self
540+
}
517541
}
518542

519543
/// Return the file path of the current environment's global gitignore file.

crates/oxide/src/scanner/auto_source_detection.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,21 @@ use std::sync;
1010
/// - Ignoring common binary file extensions like `.png` and `.jpg`
1111
/// - Ignoring common files like `yarn.lock` and `package-lock.json`
1212
///
13-
pub static RULES: sync::LazyLock<Gitignore> = sync::LazyLock::new(|| {
13+
pub static RULES: sync::LazyLock<Vec<Gitignore>> = sync::LazyLock::new(|| {
1414
let mut builder = GitignoreBuilder::new("");
1515

1616
builder.add_line(None, &IGNORED_CONTENT_DIRS_GLOB).unwrap();
1717
builder.add_line(None, &IGNORED_EXTENSIONS_GLOB).unwrap();
18-
builder.add_line(None, &BINARY_EXTENSIONS_GLOB).unwrap();
1918
builder.add_line(None, &IGNORED_FILES_GLOB).unwrap();
2019

21-
builder.build().unwrap()
20+
// Ensure these rules do not match on folder names
21+
let mut file_only_builder = GitignoreBuilder::new("");
22+
file_only_builder
23+
.only_on_files(true)
24+
.add_line(None, &BINARY_EXTENSIONS_GLOB)
25+
.unwrap();
26+
27+
vec![builder.build().unwrap(), file_only_builder.build().unwrap()]
2228
});
2329

2430
pub static IGNORED_CONTENT_DIRS: sync::LazyLock<Vec<&'static str>> = sync::LazyLock::new(|| {

crates/oxide/src/scanner/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,9 @@ fn create_walker(sources: Sources) -> Option<WalkBuilder> {
630630
}
631631

632632
// Setup auto source detection rules
633-
builder.add_gitignore(auto_source_detection::RULES.clone());
633+
for ignore in auto_source_detection::RULES.iter() {
634+
builder.add_gitignore(ignore.clone());
635+
}
634636

635637
// Setup ignores based on `@source` definitions
636638
for (base, patterns) in ignores {

crates/oxide/tests/scanner.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,31 @@ mod scanner {
311311
assert_eq!(normalized_sources, vec!["**/*"]);
312312
}
313313

314+
// https://github.com/tailwindlabs/tailwindcss/issues/17569
315+
#[test]
316+
fn it_should_not_ignore_folders_that_end_with_a_binary_extension() {
317+
let ScanResult {
318+
files,
319+
globs,
320+
normalized_sources,
321+
..
322+
} = scan(&[
323+
// Looks like `.pages` binary extension, but it's a folder
324+
("some.pages/index.html", "content-['some.pages/index.html']"),
325+
// Ignore a specific folder. This is to ensure that this still "wins" from the internal
326+
// solution of dealing with binary extensions for files only.
327+
(".gitignore", "other.pages"),
328+
(
329+
"other.pages/index.html",
330+
"content-['other.pages/index.html']",
331+
),
332+
]);
333+
334+
assert_eq!(files, vec!["some.pages/index.html"]);
335+
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}"]);
336+
assert_eq!(normalized_sources, vec!["**/*"]);
337+
}
338+
314339
#[test]
315340
fn it_should_ignore_known_extensions() {
316341
let ScanResult {

0 commit comments

Comments
 (0)