Skip to content

feat(tsconfig): walk transitive project-reference graph - #1164

Closed
Boshen wants to merge 1 commit into
05-27-refactor_tsconfig_precompute_owned_filesfrom
05-27-feat_tsconfig_transitive_project_references
Closed

Boshen wants to merge 1 commit into
05-27-refactor_tsconfig_precompute_owned_filesfrom
05-27-feat_tsconfig_transitive_project_references

Conversation

@Boshen

@Boshen Boshen commented May 27, 2026

Copy link
Copy Markdown
Member

Stacked on #1162. Diff to review is the single commit on top.

Summary

Port typescript-go's initMapperWorker (internal/compiler/projectreferenceparser.go) so a parent tsconfig sees its references' references, not just direct ones. Closes the divergence called out in #1154's "Transitive references" caveat (and matched in typescript-go's getResolvedProjectReferenceToRedirect / isSourceOfProjectReferenceRedirect flow).

Changes

  • references_resolved is populated recursively at load time. The references loop now calls load_tsconfig instead of cache.get_tsconfig directly, so each reference's own references are wired up.
  • TsconfigResolveContext::loading_references breaks cycles in the reference graph (a → b → a) by skipping a tsconfig already mid-load. Mirrors typescript-go's seen set. Direct self-references (a → a) still surface as TsconfigSelfReference.
  • resolve_tsconfig_solution and claims_ownership_of walk the graph breadth-first and pick the deepest project that owns the file — matching typescript-go's maps.Copy-into-deepest-wins behavior at line 99 of projectreferenceparser.go.
  • Cache::resolve_tsconfig_path extracted from the existing dir/file/extensionless resolution so cycle keys are computed on the canonical tsconfig.json file path.

Test

project-references-transitive/: a 3-level chain (root → parent → child) where neither root nor parent owns the queried file but child does (via paths). Without transitive walking, resolve_tsconfig_solution would stop at parent and the @child/* alias wouldn't apply.

Known limits

Cyclic references (a → b → a) are broken, but the cached tsconfig that was mid-load when the cycle was detected has an incomplete references_resolved. Subsequent direct loads of that tsconfig return the cached (incomplete) version. Matches typescript-go's behavior where the cyclic edge is simply dropped via seen.AddIfAbsent.

Port typescript-go's `initMapperWorker`
(internal/compiler/projectreferenceparser.go) so a parent tsconfig sees
its references' references, not just direct ones. `references_resolved`
is now populated recursively at load time, with cycle detection on
canonical file paths; `resolve_tsconfig_solution` and
`claims_ownership_of` walk the graph breadth-first and pick the deepest
project that owns the file — mirroring `maps.Copy`'s child-wins
behavior in typescript-go.

- New `TsconfigResolveContext::loading_references` breaks cycles by
  skipping a reference already mid-load (matches typescript-go's `seen`
  set). Direct self-reference still errors with `TsconfigSelfReference`.
- `Cache::resolve_tsconfig_path` extracted from the existing dir/file/
  extensionless resolution so cycle keys are canonical.
- Closes the divergence called out in #1154's "Transitive references"
  caveat.
@codecov

codecov Bot commented May 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.97590% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.31%. Comparing base (5006e52) to head (a18b497).

Files with missing lines Patch % Lines
src/tsconfig.rs 89.47% 4 Missing ⚠️
src/tsconfig_resolver.rs 97.05% 1 Missing ⚠️
Additional details and impacted files
@@                                Coverage Diff                                 @@
##           05-27-refactor_tsconfig_precompute_owned_files    #1164      +/-   ##
==================================================================================
- Coverage                                           93.33%   93.31%   -0.03%     
==================================================================================
  Files                                                  22       22              
  Lines                                                4234     4265      +31     
==================================================================================
+ Hits                                                 3952     3980      +28     
- Misses                                                282      285       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a18b4973ae

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tsconfig_resolver.rs
Comment on lines +272 to +278
let referenced_tsconfig = self.load_tsconfig(
/* root */ true,
&reference_input,
references,
ctx,
)?;
tsconfig.references_resolved.push(referenced_tsconfig);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Traverse transitive references during manual directory resolution

When a manual tsconfig is used with the directory-based resolve() API, this recursive load still leaves grandchildren nested under their parent, but resolve_references_then_self_paths only scans the root's direct references_resolved. In a graph like the new fixture (root -> parent -> child), resolver.resolve(child/src, "@child/foo") will never consult the child's paths and will fall back to the root config, even though resolve_file() passes because it selects the child solution first. The transitive graph needs to be traversed or flattened for this path-selection code as well.

Useful? React with 👍 / 👎.

@codspeed

codspeed Bot commented May 27, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 4.09%

❌ 1 regressed benchmark
✅ 12 untouched benchmarks
⏩ 5 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
❌ resolver_real[multi-thread] 387.9 µs 404.4 µs -4.09%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing 05-27-feat_tsconfig_transitive_project_references (a18b497) with 05-27-refactor_tsconfig_precompute_owned_files (5006e52)

Open in CodSpeed

Footnotes

  1. 5 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@Boshen Boshen closed this May 27, 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.

1 participant