Skip to content

perf(semantic): index re-exports at build time instead of scanning all exports per lookup - #88

Merged
shaharkazaz merged 8 commits into
mainfrom
perf/reverse-reexport-index
Jul 27, 2026
Merged

perf(semantic): index re-exports at build time instead of scanning all exports per lookup#88
shaharkazaz merged 8 commits into
mainfrom
perf/reverse-reexport-index

Conversation

@shaharkazaz

Copy link
Copy Markdown
Collaborator

Problem

The hottest loop in reference finding: every find_refs_recursive call scanned all exports of all workspace files to find barrel files re-exporting the current file — O(visited_nodes × total_exports_in_workspace). On barrel-heavy monorepos this dominates the run (a 3.7k-file synthetic showed ~26M resolution calls for one changed symbol). Found in the full repo review (perf finding #6).

Solution

A reverse re-export index built once at analyzer construction — reexport_index: FxHashMap<PathBuf, Vec<(PathBuf, Export)>> mapping resolved source file → re-exporters — exactly mirroring how import_index already made the forward direction O(1). The reference-finder loop becomes a single map lookup.

Key changes

  • src/semantic/analyzer.rs: build_reexport_index(cwd) called right after build_import_index (which is byte-for-byte untouched — merges cleanly with the parallel-import-index PR, verified via git merge-tree); shared resolve_workspace_specifier helper replicates the exact query-time resolution chain (is_workspace_specifier guard → resolver.resolvestrip_prefix(cwd)simple_resolve_relative fallback) so build-time and on-demand resolution cannot diverge
  • src/semantic/reference_finder.rs: the workspace scan replaced by the lookup; normalize_path extracted and paths_equal delegates to it, so index keys and comparisons share one normalization by construction
  • src/profiler.rs: the previously-dead record_reexport_check counter is now live and measures the lookup

Performance (measured, release builds)

workspace before after
1,872 files, wildcard barrels 0.90s 0.10s (~9x)
3,732 files, wildcard barrels ~3.4s ~0.2s (~17x)

--profile corroborates the mechanism: query-time resolution calls 25.9M → 0 (reviewer's independent bench: 37,449 → 9), symbol lookups identical (traversal provably unchanged), identical affected output.

Testing

Characterization-first: 4 new integration tests (named barrel re-export, barrel-of-barrels multi-hop, export *, and a negative case asserting a consumer of a different symbol from the same barrel is NOT affected) were run green against the unmodified code first, then shown to genuinely gate the new path (all 4 fail with the index stubbed empty — reviewer-verified). 2 unit tests pin index construction and key shape.

Second-model review did differential testing against main on 1k-file synthetics (identical affected sets, aliasing/wildcard edge cases included) and verified resolution-chain equivalence step by step. Its one required fix (the new test helper missed git branch -M main, breaking on init.defaultBranch != main machines) is in the second commit, verified under defaultBranch=master.

Tallies (--no-default-features, fixture populated): lib 215; integration 65/67 (failures = pre-existing test_three_dot_diff_behavior + the pre-existing test_jsx_to_tsx_extension_resolution flake, which passes 3/3 in isolation; both broken on unmodified main); cli 15; clippy/fmt clean. CLAUDE.md's Critical Data Structures section documents the new index.

Part of the repo-review fix wave (#83, #84).

…l exports per lookup

Problem
-------
`ReferenceFinder::find_refs_recursive` looked for barrel files re-exporting the
current file by iterating the exports of EVERY file in the workspace and
resolving each re-export specifier, on every visited (file, symbol) node. That
made cross-file reference finding O(visited_nodes x total_exports_in_workspace),
which dominates runtime on large monorepos with barrel-file chains.

Solution
--------
`WorkspaceAnalyzer` now builds a reverse re-export index once at construction
time (`build_reexport_index`, called next to `build_import_index`):
`resolved_source_file -> [(reexporting_file, export)]`. The traversal is a
single FxHashMap lookup instead of a full scan.

Key changes
-----------
- analyzer: add `reexport_index` (FxHashMap) + `build_reexport_index`, plus a
  shared `resolve_workspace_specifier` helper so the re-export index resolves
  specifiers exactly like the import index does (same resolver options, same
  `is_workspace_specifier` guard, same `simple_resolve_relative` fallback, same
  strip-to-cwd-relative normalization). `build_import_index` is untouched.
- reference_finder: replace the workspace-wide scan with an index lookup,
  keyed through the extracted `normalize_path` helper that `paths_equal` now
  also uses, so lookup keys keep the exact previous matching semantics
  (cwd-relative, no case folding, no canonicalization).
- profiler: the previously dead `reexport_checks`/`reexport_time_ns` counters
  now measure the (cheap) index lookup, so `--profile` output stays meaningful.

Testing
-------
- New integration tests (per-test TempDir, no shared fixture) written first and
  verified green against the old implementation: named barrel re-export,
  barrel-of-barrels multi-hop chain, `export * from` wildcard re-export, and a
  negative case (consumer of a different symbol from the same barrel is not
  affected).
- New unit tests for index construction: named/aliased/wildcard re-exports,
  barrel-of-barrels, external and import-only files excluded, and keys matching
  the workspace-relative paths used by `exports`/`import_index`.
- Synthetic 3.7k-file workspace with barrel chains: 3.36s -> 0.19s wall
  (~17x), identical affected output; `--profile` shows resolution calls
  dropping from 25,970,412 (~2.0s) to 0 with symbol lookups unchanged (7,212).
The new barrel-test helper omitted 'git branch -M main' (all 13 pre-existing
git-init sites in this file have it), so the 4 new tests failed on any machine
with init.defaultBranch != main. Verified under defaultBranch=master.

Also document the new reexport_index in CLAUDE.md's Critical Data Structures.
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@shaharkazaz, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 52dd3b8e-d148-4ed1-82ad-129889bb7f7e

📥 Commits

Reviewing files that changed from the base of the PR and between 75a2c74 and b2f4c14.

📒 Files selected for processing (5)
  • CLAUDE.md
  • src/profiler.rs
  • src/semantic/analyzer.rs
  • src/semantic/reference_finder.rs
  • tests/integration_test.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/reverse-reexport-index

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.

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

📦 Preview Release Available

A preview release has been published for commit 146f9a6.

Installation

npm install https://github.com/frontops-dev/domino/releases/download/pr-88-146f9a6/front-ops-domino-2.0.0.tgz

Running the preview

npx https://github.com/frontops-dev/domino/releases/download/pr-88-146f9a6/front-ops-domino-2.0.0.tgz affected

Details

@shaharkazaz
shaharkazaz requested a review from EladBezalel July 26, 2026 11:40
The ort merge strategy silently interleaved this branch's test additions
with sibling PRs' tail-appended tests, without leaving conflict markers:

- src/semantic/analyzer.rs: test_build_reexport_index_keys_match_analyzer_paths
  lost its closing braces, merging into the following #90 test.
- tests/integration_test.rs: this branch's scaffold_repo/barrel-test tail
  section was interleaved with #86's Turborepo test-helper tail section.

Restored both sections to their independent, complete bodies, ran cargo
fmt, and dropped the now-removed TrueAffectedConfig.root_ts_config/
include/ignored_paths fields (#83) from the struct literals that still
referenced them.
The automatic merge of origin/main interleaved this branch's appended
barrel/re-export tests with main's appended tests, truncating
test_barrel_consumer_of_other_symbol_not_affected mid-assertion and
leaving unclosed delimiters.

Reconstructed as main's file plus this branch's self-contained barrel
block. Verified nothing was lost: all 4 barrel tests and main's
turbo/mts/relative-roots tests are present and passing (integration 83,
lib 244, cli 16).
@shaharkazaz
shaharkazaz merged commit e824aac into main Jul 27, 2026
25 checks passed
@shaharkazaz
shaharkazaz deleted the perf/reverse-reexport-index branch July 27, 2026 20:35
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