Skip to content

Don't scan sibling files of a concrete @source inside an auto source root - #20406

Open
benjamincanac wants to merge 3 commits into
tailwindlabs:mainfrom
benjamincanac:fix/concrete-source-siblings-inside-auto-root
Open

Don't scan sibling files of a concrete @source inside an auto source root#20406
benjamincanac wants to merge 3 commits into
tailwindlabs:mainfrom
benjamincanac:fix/concrete-source-siblings-inside-auto-root

Conversation

@benjamincanac

@benjamincanac benjamincanac commented Aug 10, 2026

Copy link
Copy Markdown

Summary

A concrete file @source that sits inside an auto detection root scans the file's entire parent directory:

@source "./node_modules/.generated/ui/button.ts";

With the project root as an auto source, every sibling of button.ts is scanned and their classes end up in the output. The same directive works correctly when it points outside the root (since #20263).

This is the remaining half of #20255. expand_restricted_patterns skips the * + !<pattern> restriction when the pattern's base is inside an unrestricted root, because everything under that root is walked anyway. But that's not true when the base hides behind a directory the auto walk prunes, like node_modules or a git-ignored folder. In that case the pattern's own walk root is the only thing reaching the file, and its ignore rules are just:

!button.ts

so all the siblings are walked and included too.

To fix this, an auto root only counts as covering a pattern base when its walk actually reaches it: the path down to the base doesn't cross a default-ignored directory and the base isn't git-ignored. The git-ignored check reuses the ancestor .gitignore walk (and cache) that already promotes auto sources to external sources. External roots and ** patterns still cover everything under them, and patterns in ordinary subdirectories behave like before, restricting those would hide siblings the auto source should pick up.

The .gitignore walk now follows git's precedence: the nearest .gitignore with a definitive answer wins, so a directory re-included by a deeper !dir pattern is not treated as ignored. Because that walk is shared, re-included directory sources also stay auto sources instead of being promoted to external ones.

For context, I ran into this in Nuxt UI: we emit per-component @source lines into node_modules/.nuxt-ui, and the narrowed list produced byte-identical CSS to sourcing the whole directory (nuxt/ui#6731).

Test plan

  • Three new integration tests: concrete file sources behind node_modules and behind a git-ignored directory (both failed before with the sibling's candidates included), and a re-included directory where the siblings must stay.
  • Three new unit tests for expand_restricted_patterns, including a control for patterns in ordinary covered subdirectories.
  • cargo test --workspace passes, cargo fmt --check clean, no new clippy warnings.
  • Verified end to end with @tailwindcss/vite on Vite 8 by patching the built oxide binary into a reproduction project: a file @source inside the root now only ships that file's classes; whole-directory sources and auto detection are unchanged.

🤖 Generated with Claude Code

@benjamincanac
benjamincanac requested a review from a team as a code owner August 10, 2026 16:47
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cb76495d-c51e-4b58-ace0-4e93ef8c78ed

📥 Commits

Reviewing files that changed from the base of the PR and between a248cfb and 6276406.

📒 Files selected for processing (2)
  • crates/oxide/src/scanner/sources.rs
  • crates/oxide/tests/scanner.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/oxide/tests/scanner.rs
  • crates/oxide/src/scanner/sources.rs

Walkthrough

The scanner now checks whether automatic directory walks can reach restricted pattern bases. It accounts for default-ignored directories and ancestor .gitignore rules during pattern expansion. Gitignore handling is centralized and reused during source conversion. Tests cover reachable subdirectories, ignored paths, and concrete source files that exclude sibling files. The changelog documents the fix.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main fix for sibling files scanned by concrete @source directives inside auto source roots.
Description check ✅ Passed The description directly explains the bug, fix, affected behavior, tests, and validation results.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/oxide/src/scanner/sources.rs (1)

756-793: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider adding a unit test for the non-repository cwd boundary.

The new tests cover the repository case, where .git stops the ancestor walk. The cwd boundary branch in is_ignored_by_gitignore (lines 340-346) is not covered by a unit test for the restricted-pattern path. A test with no .git marker and a .gitignore above the boundary would pin that behavior.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8a64208f-33ba-4e9e-8a50-2b040de51569

📥 Commits

Reviewing files that changed from the base of the PR and between 16e94cb and a248cfb.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • crates/oxide/src/scanner/sources.rs
  • crates/oxide/tests/scanner.rs

@benjamincanac
benjamincanac marked this pull request as draft August 10, 2026 16:52
@greptile-apps

greptile-apps Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 4/5

The PR is not yet safe to merge because nested re-inclusions can still cause an explicitly sourced file to be omitted.

The new nearest-whitelist return can classify a nested source base as reachable even when an outer ignored parent is pruned before the walker can apply the deeper re-inclusion, leaving the previously reported scanning failure outstanding.

Files Needing Attention: crates/oxide/src/scanner/sources.rs

Reviews (2): Last reviewed commit: "Honor nested `.gitignore` re-includes wh..." | Re-trigger Greptile

Comment thread crates/oxide/src/scanner/sources.rs
@benjamincanac
benjamincanac marked this pull request as ready for review August 11, 2026 09:25
@RobinMalfait RobinMalfait self-assigned this Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants