Skip to content

fix(compile): close Ajv CJS source and hoist gaps - #7068

Merged
proggeramlug merged 4 commits into
mainfrom
fix/6585-6586-cjs-ajv
Jul 30, 2026
Merged

fix(compile): close Ajv CJS source and hoist gaps#7068
proggeramlug merged 4 commits into
mainfrom
fix/6585-6586-cjs-ajv

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Validation

  • cargo fmt --all -- --check
  • git diff --check
  • cargo test --profile perry-dev -p perry --bin perry commands::compile::cjs_wrap::tests -- --nocapture (100 passed before test-only module split)
  • cargo test --profile perry-dev -p perry --bin perry commands::compile::cjs_wrap::issue_6585_tests -- --nocapture (3 passed)
  • cargo test --profile perry-dev -p perry --bin perry commands::compile::resolve::declaration_map_source_tests -- --nocapture (10 passed)
  • cargo test --profile perry-dev -p perry --test issue_6585_cjs_class_forward_function -- --nocapture
  • cargo test --profile perry-dev -p perry --test issue_6586_namespace_cjs_default_import -- --nocapture
  • PERRY_REQUIRE_NPM_E2E=1 cargo test --profile perry-dev -p perry --test issue_6559_real_libs_e2e real_fast_json_stringify_serializer -- --exact --nocapture

Closes #6585
Closes #6586
Refs #6559

Summary by CodeRabbit

  • Bug Fixes
    • Improved compilation for packages mixing ESM declarations with top-level CommonJS module.exports.
    • Fixed CommonJS class hoisting/wrapping so class methods safely resolve later helper declarations.
    • Corrected wrapping behavior when helpers are mutated via property/decorator-style assignments.
  • Tests
    • Added regression coverage for hybrid CommonJS/ESM emit resolution and forward-reference function handling.
    • Re-enabled the fast-json-stringify serializer end-to-end test (when available).
    • Updated E2E expectations around ajv test skipping.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 35ce32ba-091d-43ff-b95c-496a69bdcfae

📥 Commits

Reviewing files that changed from the base of the PR and between fcfc7a5 and 2b465f3.

📒 Files selected for processing (1)
  • crates/perry/src/commands/compile/cjs_wrap/hoist_classes.rs

📝 Walkthrough

Walkthrough

The compiler detects hybrid CommonJS/ESM TypeScript inputs, keeps affected packages on published JavaScript entries, and preserves safe later-declared helpers when hoisting CommonJS classes. Resolver, unit, end-to-end, and real-library coverage was updated.

Changes

CJS emit and hoisting

Layer / File(s) Summary
Hybrid CJS source detection and routing
crates/perry/src/commands/compile/cjs_wrap/detect.rs, crates/perry/src/commands/compile/resolve.rs, crates/perry/src/commands/compile/resolve/declaration_map_source_tests.rs
Top-level module.exports detection identifies hybrid sources, and package resolution excludes those sources from TypeScript redirects across mapped, convention-based, and mirror paths.
Safe later-function hoisting
crates/perry/src/commands/compile/cjs_wrap/hoist_classes.rs, crates/perry/src/commands/compile/cjs_wrap/issue_6585_tests.rs, crates/perry/src/commands/compile/cjs_wrap/mod.rs
Class extraction collects top-level functions, duplicates only safe helpers referenced by hoisted classes, and keeps classes with captured or value-used dependencies inside the wrapper.
Regression and library acceptance coverage
crates/perry/tests/issue_6585_cjs_class_forward_function.rs, crates/perry/tests/issue_6559_real_libs_e2e.rs, changelog.d/7068-cjs-ajv-source-routing.md
End-to-end coverage validates later function references, fast-json-stringify is re-enabled, Ajv’s remaining failure is updated, and the changelog records the behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PackageResolver
  participant SourceMapResolver
  participant HybridCjsDetector
  participant CjsClassHoister
  participant PublishedJavaScript
  PackageResolver->>SourceMapResolver: recover package original source
  SourceMapResolver->>HybridCjsDetector: inspect hybrid CJS/ESM syntax
  HybridCjsDetector-->>PackageResolver: identify CJS emit input
  PackageResolver->>PublishedJavaScript: retain published JavaScript graph
  CjsClassHoister->>CjsClassHoister: classify function captures and value usage
  CjsClassHoister-->>PublishedJavaScript: emit safe helpers with hoisted classes
Loading

Suggested labels: bug

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and matches the main compile fixes for Ajv/CommonJS hoisting and hybrid source handling.
Description check ✅ Passed The description includes a clear summary and validation steps, and it covers the main changes despite missing some template headings.
Linked Issues check ✅ Passed The code updates and tests address #6585 hoisting and #6586 module graph handling, matching the linked issue goals.
Out of Scope Changes check ✅ Passed No clearly unrelated code changes stand out; the edits are centered on the CJS source/hoist fixes and their tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/6585-6586-cjs-ajv

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 commented Jul 30, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/perry/src/commands/compile/resolve.rs (1)

752-791: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider memoizing is_hybrid_cjs_emit_input — it re-reads and re-strips the same files on every resolution.

resolve_package_source_entry runs package_entry_requires_cjs_emit on every call (including per-subpath), which reads the package entry, its source map, and the mapped TS source, then allocates a full stripped copy via strip_comments_and_strings. The same paths are then re-checked at Lines 804/815/824/834/848. For a large package graph this multiplies file I/O and O(n) scans over identical inputs.

A small path→bool cache (e.g. OnceLock<Mutex<HashMap<PathBuf, bool>>>) keyed on the canonical path would keep behavior identical while collapsing the repeats.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry/src/commands/compile/resolve.rs` around lines 752 - 791, Memoize
the result of is_hybrid_cjs_emit_input by canonical file path so repeated
package resolution does not reread or restrip identical sources. Add a small
shared path-to-boolean cache and use it inside is_hybrid_cjs_emit_input,
preserving the existing false result when reading or canonicalizing a path fails
and keeping package_entry_requires_cjs_emit behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry/src/commands/compile/cjs_wrap/hoist_classes.rs`:
- Around line 286-304: Extend the unsafe_to_duplicate check in the
function-declaration loop to detect references to decl.name outside
decl.block_text when the identifier is used as a value, including member writes,
aliases, identity comparisons, and Object.defineProperty calls. Mark such
helpers unsafe and preserve the existing reassignment, export, and blocker
checks so only capture-free call references remain eligible for duplication.

---

Nitpick comments:
In `@crates/perry/src/commands/compile/resolve.rs`:
- Around line 752-791: Memoize the result of is_hybrid_cjs_emit_input by
canonical file path so repeated package resolution does not reread or restrip
identical sources. Add a small shared path-to-boolean cache and use it inside
is_hybrid_cjs_emit_input, preserving the existing false result when reading or
canonicalizing a path fails and keeping package_entry_requires_cjs_emit behavior
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4b1eb9a8-9c5d-4557-a9c7-1e7302e33d33

📥 Commits

Reviewing files that changed from the base of the PR and between c0d9862 and 6336808.

📒 Files selected for processing (9)
  • changelog.d/7068-cjs-ajv-source-routing.md
  • crates/perry/src/commands/compile/cjs_wrap/detect.rs
  • crates/perry/src/commands/compile/cjs_wrap/hoist_classes.rs
  • crates/perry/src/commands/compile/cjs_wrap/issue_6585_tests.rs
  • crates/perry/src/commands/compile/cjs_wrap/mod.rs
  • crates/perry/src/commands/compile/resolve.rs
  • crates/perry/src/commands/compile/resolve/declaration_map_source_tests.rs
  • crates/perry/tests/issue_6559_real_libs_e2e.rs
  • crates/perry/tests/issue_6585_cjs_class_forward_function.rs

Comment thread crates/perry/src/commands/compile/cjs_wrap/hoist_classes.rs
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Addressed the CodeRabbit performance nitpick in fcfc7a5 as well: hybrid-source classification is now memoized by canonical path, avoiding repeated reads and full-source stripping for each package subpath resolution.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
crates/perry/src/commands/compile/cjs_wrap/hoist_classes.rs (1)

1031-1046: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Redundant full-source re-stripping per top-level function declaration.

identifier_used_as_value_outside re-runs strip_comments_and_strings(source) (Line 1041) on every call, and it's invoked once per entry in function_decls inside the loop at Lines 286-310. collect_top_level_function_decls already computed an equivalent stripped source once (Line 910) to find these declarations. For N top-level functions this means N (+1) full-source strips, each O(source length) — quadratic-ish overhead on exactly the kind of large CJS bundles (ajv, fast-json-stringify) this PR targets.

Consider computing the stripped source once in extract_top_level_class_decls and threading it through both collect_top_level_function_decls and identifier_used_as_value_outside.

♻️ Sketch: hoist the strip out of the per-decl loop
-fn identifier_used_as_value_outside(
-    source: &str,
+fn identifier_used_as_value_outside(
+    stripped: &str,
     name: &str,
     excluded_start: usize,
     excluded_end: usize,
 ) -> bool {
     fn is_ident_byte(b: u8) -> bool {
         b == b'_' || b == b'$' || b.is_ascii_alphanumeric()
     }
 
-    let stripped = super::detect::strip_comments_and_strings(source);
     let bytes = stripped.as_bytes();

And at the call site, compute stripped once before the loop (or have collect_top_level_function_decls return it alongside the decls) and pass &stripped into each identifier_used_as_value_outside(...) call instead of source.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry/src/commands/compile/cjs_wrap/hoist_classes.rs` around lines
1031 - 1046, Compute the comment/string-stripped source once in
extract_top_level_class_decls and reuse it through
collect_top_level_function_decls and the per-declaration checks. Update
identifier_used_as_value_outside to accept the precomputed stripped source
rather than re-running strip_comments_and_strings on each call, while preserving
its existing exclusion-range matching behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/perry/src/commands/compile/cjs_wrap/hoist_classes.rs`:
- Around line 1031-1046: Compute the comment/string-stripped source once in
extract_top_level_class_decls and reuse it through
collect_top_level_function_decls and the per-declaration checks. Update
identifier_used_as_value_outside to accept the precomputed stripped source
rather than re-running strip_comments_and_strings on each call, while preserving
its existing exclusion-range matching behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2ac17cf5-3c04-46f7-b91f-035e35285e08

📥 Commits

Reviewing files that changed from the base of the PR and between 6336808 and fcfc7a5.

📒 Files selected for processing (3)
  • crates/perry/src/commands/compile/cjs_wrap/hoist_classes.rs
  • crates/perry/src/commands/compile/cjs_wrap/issue_6585_tests.rs
  • crates/perry/src/commands/compile/resolve.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/perry/src/commands/compile/resolve.rs

@proggeramlug
proggeramlug merged commit 511c3ea into main Jul 30, 2026
0 of 7 checks passed
@proggeramlug
proggeramlug deleted the fix/6585-6586-cjs-ajv branch July 30, 2026 12:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant