Ensure earlier @source is not accidentally ignored by later @source (#20335)
This PR fixes an issue where an `@source` pointing to a file in a nested
folder was not scanned when a later `@source` pointed to a file in a
parent folder.
E.g.:
```css
@source "./nested/index.html";
@source "./index.html";
```
When using `@source` pointing to a specific file, then we want to make
sure that we ignore _other_ files since they are not listed explicitly.
To ensure that these patterns don't read other files, we inject a `*`
ignore pattern before it. You can think of the above being expanded to:
```rs
Ignored { base: "/project/src/nested", pattern: "*" }
Pattern { base: "/project/src/nested", pattern: "/index.html" }
Ignored { base: "/project/src", pattern: "*" }
Pattern { base: "/project/src", pattern: "/index.html" }
```
The problem with this is that the `Ignored { base: "/project/src",
pattern: "*" }` pattern results in ignoring the `nested` folder as well.
This means that we never even walk into the `nested` folder, so the
earlier `@source "./nested/index.html"` never matches anything.
We could switch the order in user land, but that's going to be hard to
maintain (and order matters for undoing/redoing earlier rules, so we
can't re-order internally either). Instead, we can scope the ignore
pattern to the _current_ path only, and not deeply nested. In other
words, the pattern should become:
```diff
- *
+ /*
```
It's a very subtle difference, but the pattern from above will now
become:
```diff
Ignored { base: "/project/src/nested", pattern: "*" }
Pattern { base: "/project/src/nested", pattern: "/index.html" }
- Ignored { base: "/project/src", pattern: "*" }
+ Ignored { base: "/project/src", pattern: "/*" }
Pattern { base: "/project/src", pattern: "/index.html" }
```
We already do this when an unrestricted root (e.g. `@source "./nested"`)
lives inside the base of a restricted pattern. This works because every
source base is also its own walk root, and a `/*` pattern only matches
direct children so it can't ignore anything when walking from the nested
root itself.
This PR extends that same check to restricted pattern bases: if another
`@source` pattern has its base nested inside the current base, we emit
`/*` instead of `*`.
Note that we only relax the pattern to `/*` when such a nested root
actually exists. Sibling folders that no `@source` points into (e.g. an
`ignore-me` folder next to `nested`) are still direct children, so they
still match `/*` and are never walked.
Fixes: #20333
## Test plan
1. Added a regression test with the reproduction setup
2. Added a similar test with another sibling folder that should still be
ignored
3. Tested it against the actual reproduction:
Before:
<img width="797" height="463" alt="image"
src="https://github.com/user-attachments/assets/893bb872-f5cf-4c5a-bfb5-2dc1043b5a39"
/>
After:
<img width="794" height="460" alt="image"
src="https://github.com/user-attachments/assets/a59aae45-68ad-4fe3-b21f-b568f535d24f"
/>
Notice that the `text-green-500` now appears as expected.
This commit is contained in:
parent
f861d5c5e6
commit
7811d74f37
3 changed files with 82 additions and 7 deletions
|
|
@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Lazily load `@parcel/watcher` when using the `--watch` flag in `@tailwindcss/cli`, so one-off builds and `--watch --poll` work when `@parcel/watcher` can't be loaded ([#20325](https://github.com/tailwindlabs/tailwindcss/issues/20325))
|
||||
- Use explicit platform fonts instead of `system-ui` and `ui-sans-serif` so CJK text respects the page's `lang` attribute on Windows ([#19767](https://github.com/tailwindlabs/tailwindcss/issues/19767), [#19768](https://github.com/tailwindlabs/tailwindcss/issues/19768))
|
||||
- Prevent `@tailwindcss/upgrade` from rewriting ignored files when run from a subdirectory ([#20328](https://github.com/tailwindlabs/tailwindcss/issues/20328))
|
||||
- Ensure earlier `@source` rules pointing to nested files are scanned when later `@source` rules point to files in parent folders ([#20335](https://github.com/tailwindlabs/tailwindcss/pull/20335))
|
||||
|
||||
## [4.3.2] - 2026-06-26
|
||||
|
||||
|
|
|
|||
|
|
@ -148,6 +148,16 @@ fn expand_restricted_patterns(sources: Vec<SourceEntry>) -> Vec<SourceEntry> {
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Bases of restricted patterns. Each of these becomes its own walk root with its own
|
||||
// `*` + `!<pattern>` rules, so an ancestor base must not ignore them recursively.
|
||||
let pattern_roots = sources
|
||||
.iter()
|
||||
.filter_map(|source| match source {
|
||||
SourceEntry::Pattern { base, .. } => Some(base.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut restricted_roots: FxHashSet<PathBuf> = FxHashSet::default();
|
||||
let mut expanded = vec![];
|
||||
|
||||
|
|
@ -167,14 +177,17 @@ fn expand_restricted_patterns(sources: Vec<SourceEntry>) -> Vec<SourceEntry> {
|
|||
}
|
||||
|
||||
// Ignore everything in the directory. We will later add the specific patterns we are
|
||||
// interested in. When another source root is nested in this base, only ignore direct
|
||||
// children so the nested source can still be walked from its own root.
|
||||
// interested in.
|
||||
if restricted_roots.insert(base.clone()) {
|
||||
let pattern = if unrestricted_roots.iter().any(|root| root.starts_with(base)) {
|
||||
"/*"
|
||||
} else {
|
||||
"*"
|
||||
};
|
||||
// When another source root is nested inside this base — an unrestricted root, or the
|
||||
// base of another restricted pattern (which is walked from its own root with its own
|
||||
// rules) — only ignore direct children so the nested root can still be walked.
|
||||
let has_nested_root = unrestricted_roots.iter().any(|root| root.starts_with(base))
|
||||
|| pattern_roots
|
||||
.iter()
|
||||
.any(|root| root != base && root.starts_with(base));
|
||||
|
||||
let pattern = if has_nested_root { "/*" } else { "*" };
|
||||
|
||||
expanded.push(SourceEntry::Ignored {
|
||||
base: base.clone(),
|
||||
|
|
|
|||
|
|
@ -1297,6 +1297,67 @@ mod scanner {
|
|||
assert_eq!(files, vec!["src/bar.html", "src/foo.html"]);
|
||||
}
|
||||
|
||||
// https://github.com/tailwindlabs/tailwindcss/issues/20333
|
||||
#[test]
|
||||
fn it_should_combine_nested_and_root_restricted_sources() {
|
||||
// The restriction (`*`) added for the root-level file must not prevent walking into the
|
||||
// `nested` folder that another explicit source points into. At the same time, relaxing
|
||||
// that restriction to `/*` must not accidentally open up sibling folders (`ignore-me`)
|
||||
// or files that no explicit source points at.
|
||||
let paths_with_content = &[
|
||||
("nested/component.html", "content-['nested/component.html']"),
|
||||
("nested/ignore-me.html", "content-['nested/ignore-me.html']"),
|
||||
("ignore-me/component.html", "content-['ignore-me']"),
|
||||
("component-sources.classes.txt", "content-['classes.txt']"),
|
||||
("ignore-me.txt", "content-['ignore-me.txt']"),
|
||||
];
|
||||
|
||||
let ScanResult {
|
||||
candidates, files, ..
|
||||
} = scan_with_globs(
|
||||
paths_with_content,
|
||||
vec![
|
||||
"@source './nested/component.html'",
|
||||
"@source './component-sources.classes.txt'",
|
||||
],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
candidates,
|
||||
vec![
|
||||
"content-['classes.txt']",
|
||||
"content-['nested/component.html']"
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
files,
|
||||
vec!["component-sources.classes.txt", "nested/component.html"]
|
||||
);
|
||||
|
||||
// Same setup, but with the root-level source declared first
|
||||
let ScanResult {
|
||||
candidates, files, ..
|
||||
} = scan_with_globs(
|
||||
paths_with_content,
|
||||
vec![
|
||||
"@source './component-sources.classes.txt'",
|
||||
"@source './nested/component.html'",
|
||||
],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
candidates,
|
||||
vec![
|
||||
"content-['classes.txt']",
|
||||
"content-['nested/component.html']"
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
files,
|
||||
vec!["component-sources.classes.txt", "nested/component.html"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_should_allow_later_ignores_to_override_restricted_sources() {
|
||||
let ScanResult {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue