Skip to content

fix(core): apply root fallback for source files outside sourceRoot#65

Merged
nirsky merged 2 commits intomainfrom
fix/source-file-outside-sourceroot-fallback
Apr 20, 2026
Merged

fix(core): apply root fallback for source files outside sourceRoot#65
nirsky merged 2 commits intomainfrom
fix/source-file-outside-sourceroot-fallback

Conversation

@nirsky
Copy link
Copy Markdown
Collaborator

@nirsky nirsky commented Apr 20, 2026

Summary

Follow-up to #61. That PR added a root_entries fallback on ProjectIndex so files living at project root but outside sourceRoot (e.g. jest.config.js, webpack.config.ts, project.json) could still be resolved to their owning project. The fallback was wired into the asset path (Step 6b) but not the source path (Step 6a) — meaning source-typed config files (.js/.ts/.jsx/.tsx at project root) were still silently skipped.

Why the existing fix missed it

For a project where sourceRoot ≠ root (e.g. sourceRoot = "apps/my-app/src", root = "apps/my-app"):

  1. utils::is_source_file("jest.config.js") returns true (has .js extension) → goes into source_files, not asset_files
  2. The semantic analyzer only walks sourceRootjest.config.js is never parsed → not in analyzer.files
  3. Step 6a's first check is if !analyzer.files.contains_key(file_path) { continue; } — fires before get_package_names_by_path is ever called
  4. The root_entries lookup table exists but nothing on the source path uses it

Observed on a real Nx monorepo with 1685 projects:

Partitioned files: 1 source, 0 assets
Skipping unanalyzed source file: "apps/browser/desktop/background/jest.config.js"
Affected projects: []

Fix

In Step 6a, when a changed source file isn't in analyzer.files, fall back to project_index.get_package_names_by_path(file_path) and mark owning projects as affected — same treatment the asset path already uses. Record AffectCause::DirectChange when reports are enabled. The existing root_entries index now does its job end-to-end for source-typed files too.

After the fix, same input:

Partitioned files: 1 source, 0 assets
Source file not in analyzer.files, using root fallback: "apps/browser/desktop/background/jest.config.js"
File "..." belongs to package 'browser-desktop-background' (via root fallback)
Affected projects: ["browser-desktop-background", "browser-desktop-extension"]

Scope

  • Triggers only when a source-typed file isn't in analyzer.files — i.e. the exact path that previously did continue;. No hot-path changes.
  • Cost per such file: one get_package_names_by_path call (linear scan over unique roots). In a 1685-project monorepo, that's microseconds — negligible next to Oxc parsing 36k files.
  • Semantic analysis, symbol tracing, and asset handling are unchanged. No regressions in cross-project reference tracking.

Test plan

  • New integration test test_source_file_outside_sourceroot_affects_owning_project — fails without the fix (Got: []), passes with it
  • cargo test --no-default-features --lib — all 189 unit tests pass
  • All 7 TempNxRepo-based integration tests pass, including test_named_inputs_negation_with_root_differs_from_source_root (guards the root != sourceRoot geometry)
  • End-to-end on a real 1685-project monorepo:
    • Normal source file inside sourceRoot → correct owning + dependent projects via semantic analysis (regression check)
    • .ts config at project root (webpack.config.ts) → owning project via new fallback
    • .json asset at project root (project.json) → unchanged asset path
    • Shared utility file → cascades to 21 dependent projects via reference tracking (regression check)
    • jest.config.js in a different project (apps/management) → management + 5 dependents via implicit deps
    • Baseline with no changes → "No affected projects"

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved detection of affected packages for source files outside configured source root directories. These files are now correctly identified as affecting their owning packages.
  • Tests

    • Added integration test verifying affected package detection for files located outside source root.

Source-typed config files at project root (e.g. jest.config.js,
webpack.config.ts when sourceRoot is "<proj>/src") are classified as
source by extension but never walked by the analyzer, which only
descends sourceRoot. Step 6a used to silently `continue` on such files,
so the root fallback added in #61 never got a chance to run for them —
changes to project-level config files went undetected.

When a changed source file isn't in `analyzer.files`, fall back to
`ProjectIndex::get_package_names_by_path` and mark the owning projects
affected, mirroring how Step 6b handles assets. The new `root_entries`
index from #61 now actually covers this case end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

Warning

Rate limit exceeded

@nirsky has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 39 minutes and 46 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 39 minutes and 46 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e250cee8-3bf1-4c95-a39c-cf57db97f9e0

📥 Commits

Reviewing files that changed from the base of the PR and between 523817b and b1cf76f.

📒 Files selected for processing (3)
  • src/core.rs
  • src/utils.rs
  • tests/integration_test.rs
📝 Walkthrough

Walkthrough

The changes add a fallback mechanism for source files that the semantic analyzer couldn't process. When a file is missing from the analyzer, the code now performs a root-based package ownership lookup via ProjectIndex::get_package_names_by_path() to find and attribute the file to its owning project(s), with optional report recording.

Changes

Cohort / File(s) Summary
Core Fallback Logic
src/core.rs
Added fallback handling for source files missing from analyzer: performs root-based package ownership lookup, inserts owning packages into affected set, and records direct-change causes in report (with special handling for empty vs. populated changed lines).
Integration Test
tests/integration_test.rs
Added test_source_file_outside_sourceroot_affects_owning_project to verify files outside a project's sourceRoot but within its root directory are correctly attributed to the owning project.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • EladBezalel

Poem

🐰 A file hops outside the sourceRoot's door,
Yet still within the project's floor—
With fallback magic, we now see,
That ownership belongs to thee!
No more lost files, no more despair,
The index finds them everywhere! 🌿

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically identifies the main change: applying a root-based fallback for source files located outside sourceRoot, which directly matches the core fix described in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/source-file-outside-sourceroot-fallback

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/integration_test.rs (1)

3310-3316: Tighten the regression by asserting unrelated projects stay unaffected.

This test proves the fallback includes lib-a; adding a negative assertion for lib-b would also protect the “owning project only” behavior.

Test assertion enhancement
   assert!(
     affected.contains(&"lib-a".to_string()),
     "lib-a should be affected (jest.config.js at project root, outside sourceRoot). Got: {:?}",
     affected
   );
+  assert!(
+    !affected.contains(&"lib-b".to_string()),
+    "lib-b should NOT be affected by lib-a's root-level config change. Got: {:?}",
+    affected
+  );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration_test.rs` around lines 3310 - 3316, The test currently
asserts that "lib-a" is in the affected set via repo.get_affected() but lacks a
negative assertion; add an assertion that affected does NOT contain "lib-b" to
ensure unrelated projects remain unaffected — use the same pattern as the
existing assert! (checking affected.contains(&"lib-b".to_string()) is false or
using assert!(!affected.contains(...))) and include a helpful failure message
like "lib-b should NOT be affected" to tighten the regression.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/integration_test.rs`:
- Around line 3310-3316: The test currently asserts that "lib-a" is in the
affected set via repo.get_affected() but lacks a negative assertion; add an
assertion that affected does NOT contain "lib-b" to ensure unrelated projects
remain unaffected — use the same pattern as the existing assert! (checking
affected.contains(&"lib-b".to_string()) is false or using
assert!(!affected.contains(...))) and include a helpful failure message like
"lib-b should NOT be affected" to tighten the regression.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ccd57f6a-6f12-4780-ae85-a24fd3c18698

📥 Commits

Reviewing files that changed from the base of the PR and between 8e00e12 and 523817b.

📒 Files selected for processing (2)
  • src/core.rs
  • tests/integration_test.rs

@EladBezalel
Copy link
Copy Markdown
Collaborator

This PR fixes source-typed config files outside sourceRoot — but it doesn't address the workspace regression from #61.

The issue: in Nx monorepos the root workspace project has root: ".". PR #61's root_entries fallback indexes that root, so get_package_names_by_path matches every changed file to workspace (every path starts with "."). This causes workspace to appear in affected output, breaking downstream CI that passes it to nx run-many.

This PR extends the root fallback to source files too (Step 6a), which would make the workspace over-matching worse — it now fires for .js/.ts config files as well, not just assets.

Suggested fix — in ProjectIndex::new(), skip indexing projects whose root is the workspace root:

if project.root != project.source_root
    && !project.root.as_os_str().is_empty()
    && project.root != Path::new(".")
{

This should be addressed before merging, otherwise the workspace regression from #61 gets amplified.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

Copy link
Copy Markdown
Collaborator

@EladBezalel EladBezalel left a comment

Choose a reason for hiding this comment

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

The fix for the intended case (jest.config.js at project root) is correct, but it amplifies the root: "." over-matching regression from #61 — which is currently breaking loco CI.

Before this PR, unanalyzed source files were skipped. Now they go through get_package_names_by_path, which returns the root workspace project for ANY file. Every PR that changes a source-typed file outside sourceRoot will mark workspace as affected.

Must fix before merge: guard against root: "." in ProjectIndex::new() (in src/utils.rs), or in the fallback branch itself. See inline comments.

Comment thread src/core.rs
Comment thread src/core.rs Outdated
Comment thread tests/integration_test.rs Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 20, 2026

📦 Preview Release Available

A preview release has been published for commit b1cf76f.

Installation

npm install https://github.com/frontops-dev/domino/releases/download/pr-65-b1cf76f/front-ops-domino-1.2.4.tgz

Running the preview

npx https://github.com/frontops-dev/domino/releases/download/pr-65-b1cf76f/front-ops-domino-1.2.4.tgz affected

Details

…ct cause helper; tighten tests

Address PR #65 review:

- src/utils.rs: skip projects with `root == ""` or `root == "."` when
  building `root_entries`. The Nx loader produces `root = ""` via
  `strip_prefix(cwd)` for workspace-root projects; an empty path is a
  prefix of every path in the repo and would over-attribute every
  fallback lookup to the workspace project. (`.` is a no-op under
  `Path::starts_with` component semantics but guarded for loaders that
  preserve it literally / paths that arrive with a `./` prefix.) Adds
  two unit tests covering both cases.

- src/core.rs: extract `record_direct_change_causes` helper; reuse it
  from both the new source root-fallback path (Step 6a) and the asset
  path (Step 6b) which had identical inlined blocks.

- tests/integration_test.rs: tighten
  `test_source_file_outside_sourceroot_affects_owning_project` to an
  exact-match assertion (previous `.contains(...)` passed even if every
  project leaked through). Add
  `test_workspace_root_project_not_over_attributed` — a root-level
  project with a nested project next to it; changing the nested
  project's `jest.config.js` must affect only the nested project.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@EladBezalel EladBezalel left a comment

Choose a reason for hiding this comment

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

All review comments addressed. The root: "." / "" guard in ProjectIndex::new() fixes the loco CI regression, the extracted helper deduplicates the report logic, and the test coverage is solid (unit + integration for both root forms + exact-match assertions).

LGTM.

@nirsky nirsky merged commit 7ad7cec into main Apr 20, 2026
25 checks passed
@nirsky nirsky deleted the fix/source-file-outside-sourceroot-fallback branch April 20, 2026 14:59
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