fix(transforms/module): replace canonicalization with clean to not mess up symlinks#11585
fix(transforms/module): replace canonicalization with clean to not mess up symlinks#11585perbergland wants to merge 11 commits intoswc-project:mainfrom
Conversation
swc-project#11584) Add test that demonstrates the issue where `canonicalize()` in `NodeImportResolver::try_resolve_import` resolves symlinked paths to their real filesystem location, breaking import resolution when `preserve_symlinks` is intended. Also adds the `preserve_symlinks` field to `path::Config` and threads it through `JscConfig` and `build_resolver`, but does not yet skip the canonicalization (test is expected to fail). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…et (swc-project#11584) When `jsc.preserveSymlinks` is true, skip the `canonicalize()` call in `NodeImportResolver::try_resolve_import` so that symlinked paths are preserved during module resolution. Also skip base filename canonicalization in `ModuleConfig::get_resolver`. This fixes the issue where `jsc.baseUrl` causes symlinked source files to have their imports resolved from the real file location instead of the symlink location, breaking bundlers that set `resolve.symlinks: false`. Closes swc-project#11584 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 9e5fc33 The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 09f010577d
ℹ️ 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".
Binary Sizes
Commit: dfbb790 |
…et (swc-project#11584) Replace `std::fs::canonicalize()` with `path_clean::PathClean::clean()` in `NodeImportResolver::try_resolve_import`. This normalizes `.` and `..` path components without resolving symlinks, fixing the issue where symlinked source files had their imports resolved from the real file location instead of the symlink location. `canonicalize()` was originally added for Bazel sandbox support (swc-project#8265), but its symlink resolution is unnecessary — lexical path cleaning achieves the same normalization without breaking symlinked source files. This removes the `preserve_symlinks` config option added in the previous commit since it is no longer needed. Closes swc-project#11584 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
No dependency changes detected. Learn more about Socket for GitHub. 👍 No dependency changes detected in pull request |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5fb410a47d
ℹ️ 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".
…roject#11584) Also normalize the base filename in `get_resolver()` using `path_clean::PathClean::clean()` instead of `std::fs::canonicalize()`. This ensures both base and target paths live in the same "path space" so that `diff_paths` computes correct relative paths in Bazel sandbox and symlinked setups. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 152d8171ae
ℹ️ 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".
kdy1
left a comment
There was a problem hiding this comment.
Not sure if this is what we want. I want to ensure that it works for bazel users before merging/publishing
|
I'm sorry to bother you all, but I want to ensure this doesn't break |
|
Thanks for the ping @kdy1. I think this would be a positive change for rules_swc/js, normally bazel issues come from tools that do resolve or follow symlinks. Have you observed any issues with things like pnpm package stores? I assume |
|
@claude Add tests for the use case described in #11585 (comment) |
|
I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3c0467f370
ℹ️ 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".
crates/swc/src/config/mod.rs
Outdated
| env::current_dir() | ||
| .map(|cwd| cwd.join(v).clean()) | ||
| .unwrap_or_else(|_| v.to_path_buf()) |
There was a problem hiding this comment.
Resolve missing relative base against baseUrl
When base is relative, this new logic always makes it absolute via env::current_dir(). In in-memory/virtual-file compiles (relative filename that does not exist on disk), the previous behavior kept base relative, so NodeImportResolver::try_resolve_import could rebase it through absolute_path(base_dir, ...) and keep rewrites in the jsc.baseUrl path space; with this change, base is pinned to process CWD instead, and diff_paths can emit incorrect long paths (for example ../../project/...) when cwd differs from baseUrl.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 18cf53e24b
ℹ️ 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".
crates/swc/src/config/mod.rs
Outdated
| FileName::Real(v) if !skip_resolver => { | ||
| FileName::Real(v.canonicalize().unwrap_or_else(|_| v.to_path_buf())) | ||
| } | ||
| FileName::Real(v) if !skip_resolver => FileName::Real(v.clean()), |
There was a problem hiding this comment.
Keep Windows base path in UNC form before diffing
Replacing canonicalize() with clean() here leaves base as a drive-letter path on Windows, but build_resolver still canonicalizes jsc.baseUrl to UNC (\\?\...) before creating NodeImportResolver; that means try_resolve_import can compare two absolute paths with different prefixes and skip normalization (it only normalizes when absolute-vs-relative differs), so relative rewriting via diff_paths no longer works and imports can fall back to absolute //?/C:/... specifiers for jsc.baseUrl/paths resolutions.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9e5fc339f7
ℹ️ 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".
| } | ||
| // https://github.com/swc-project/swc/issues/11584 | ||
| if let FileName::Real(resolved) = &mut target.filename { | ||
| *resolved = resolved.clean(); |
There was a problem hiding this comment.
Normalize Windows target paths before import diffing
On Windows, replacing canonicalization with resolved.clean() leaves absolute targets in drive-letter form (for example C:\...), while resolver setup still canonicalizes jsc.baseUrl to UNC (\\?\C:\...) in build_resolver; TsConfigResolver can also return absolute FileName::Real(tp.into()) directly for exact paths mappings (crates/swc_ecma_loader/src/resolvers/tsc.rs, Pattern::Exact). In that case diff_paths receives two absolute paths with different prefixes, fails to compute a relative path, and the rewriter falls back to absolute specifiers, which is a regression for Windows projects using absolute path mappings.
Useful? React with 👍 / 👎.

Summary
std::fs::canonicalize()withpath_clean::PathClean::clean()in bothNodeImportResolver::try_resolve_importandModuleConfig::get_resolver.and..path components without resolving symlinksFixes #11584
Root cause
When
jsc.baseUrlis set, SWC creates aNodeImportResolverwhich calledstd::fs::canonicalize()on resolved paths (path.rs:261).canonicalize()does three things: makes paths absolute, resolves./.., and resolves symlinks. Only the first two are needed — the symlink resolution breaks setups where symlinked source files need imports resolved relative to the symlink location.The
path_cleancrate (already a dependency) provides.clean()which does the same normalization lexically, without touching the filesystem or resolving symlinks.Changes
crates/swc_ecma_transforms_module/src/path.rs: Replacecanonicalize(resolved)withresolved.clean(), remove unusedfs::canonicalizeimportcrates/swc/src/config/mod.rs: Replacev.canonicalize()withv.clean()(+ make-absolute for relative paths) inget_resolver()so both base and target use the same non-symlink-resolving normalization — this ensuresdiff_pathscompares paths in the same "path space"crates/swc/Cargo.toml: Addpath-cleandependencycrates/swc_ecma_transforms_module/tests/path_node.rs: Add test with symlinked directory that verifies import paths are preserved through symlinksTest plan
issue_11584_symlink_not_canonicalizedcreates a symlinked directory structure and verifies../lib/depresolves to../lib/dep.js(not../../real/lib/dep.js)issue_8667_1Bazel sandbox test passes (confirmeddiff_pathsproduces correct relative paths with consistent normalization)swc_ecma_transforms_moduletests passswc_cli_impltests passcargo clippyandcargo fmtclean🤖 Generated with Claude Code