Skip to content

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

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

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 whose base 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 gets scanned and their classes ship. The same directive pointing outside the root narrows correctly since #20263.

The cause is in expand_restricted_patterns: a pattern whose base is inside an unrestricted root skips the * + !<pattern> restriction, assuming everything under that root is walked anyway. That's false when the base hides behind a directory the auto walk prunes (node_modules or a git-ignored one). The pattern's own walk root is then the only thing reaching the file, and its rules are just !button.ts, so every sibling is walked and included. This is the remaining half of #20255.

The fix makes the coverage check ignore-aware: an auto root only covers a pattern base when the path down to it crosses no default-ignored directory and the base isn't git-ignored (reusing the ancestor .gitignore walk that promotes auto sources to external ones). External and ** pattern roots still cover everything, and patterns in ordinary subdirectories keep the previous behavior.

The .gitignore walk now also honors nested re-includes with 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. Since the walk is shared with the auto-to-external promotion, such re-included directory sources now stay auto sources instead of being promoted to external ones.

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

  • Two integration tests (node_modules and a git-ignored directory), both failed before the fix with the sibling's candidates included.
  • Three unit tests for expand_restricted_patterns, including a control for patterns in ordinary covered subdirectories.
  • cargo test in crates/oxide passes (191 tests), fmt 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 ships only that file's classes, whole-directory sources and auto detection 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

Walkthrough

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

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly describes the primary fix: preventing sibling files from being scanned for concrete @source files inside auto source roots.
Description check ✅ Passed The description directly explains the bug, fix, affected paths, tests, and verification results.

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 nested .gitignore precedence mismatch should be fixed before merging because it can suppress classes from sibling files that Auto detection legitimately reaches.

The new coverage helper independently evaluates every ancestor .gitignore, so a farther ignore can override a nearer re-inclusion even though the actual walker preserves the nearer rule; this installs an explicit restriction and drops expected Auto-source files.

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

Reviews (1): Last reviewed commit: "Add PR number to changelog entry" | Re-trigger Greptile

let covered = bypassing_roots.iter().any(|root| base.starts_with(root))
|| (auto_roots
.iter()
.any(|root| base.starts_with(root) && !crosses_ignored_content_dir(root, base))

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.

P1 Nested re-inclusions lose precedence

When a nearer .gitignore re-includes the concrete source directory while a farther ancestor ignores it, is_ignored_by_gitignore still classifies the directory as ignored. This adds an explicit * restriction even though the Auto walker reaches the directory, causing sibling files expected from Auto detection to be omitted from generated CSS.

Knowledge Base Used: Oxide Scanner Engine

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.

1 participant