From 9ddc74540aa2af35d412b676559dd4e5d56fa107 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Tue, 11 Nov 2025 13:03:29 +0000 Subject: [PATCH] Fix module resolution: '.' in subdirectory resolves to index.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, importing '.' or './' from within a subdirectory would incorrectly resolve to a file with the same name in the parent directory, rather than the index file in the current directory. For example, importing from 'lib/run.ts': ```ts import { foo } from "."; ``` Would incorrectly resolve to the root 'lib.ts' instead of 'lib/index.ts'. This fix adds a check in loadAsFile() to detect when the path being resolved is actually a directory. If the basename has no extension (indicating it's likely a directory reference), we check if it exists as a directory before attempting file resolution. This ensures directory imports correctly fall through to index file resolution. Fixes #24449 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/resolver/resolver.zig | 14 ++++++-- test/regression/issue/24449.test.ts | 54 +++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 test/regression/issue/24449.test.ts diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index eeb483a3e3..f4e444a5c3 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -3779,6 +3779,18 @@ pub const Resolver = struct { } } + // If the path itself is an existing directory, don't try to load it as a file. + // This prevents "import '.' from 'lib/foo.ts'" incorrectly resolving to a file + // named "lib.ts" in the parent directory instead of "lib/index.ts". + if (r.dirInfoCached(path) catch null) |_| { + if (r.debug_logs) |*debug| { + debug.addNoteFmt("\"{s}\" is a directory, not a file", .{path}); + } + return null; + } + + const base = std.fs.path.basename(path); + const dir_path = bun.strings.withoutTrailingSlashWindowsPath(Dirname.dirname(path)); const dir_entry: *Fs.FileSystem.RealFS.EntriesOption = rfs.readDirectory( @@ -3811,8 +3823,6 @@ pub const Resolver = struct { const entries = dir_entry.entries; - const base = std.fs.path.basename(path); - // Try the plain path without any extensions if (r.debug_logs) |*debug| { debug.addNoteFmt("Checking for file \"{s}\" ", .{base}); diff --git a/test/regression/issue/24449.test.ts b/test/regression/issue/24449.test.ts new file mode 100644 index 0000000000..1c946d3915 --- /dev/null +++ b/test/regression/issue/24449.test.ts @@ -0,0 +1,54 @@ +import { expect, test } from "bun:test"; +import { bunEnv, bunExe, normalizeBunSnapshot, tempDir } from "harness"; + +test("issue #24449 - '.' in subdirectory should resolve to index.ts, not root file with same name", async () => { + // Create temp directory with test files + using dir = tempDir("test-issue-24449", { + "lib.ts": `export const eulerNumber = 2.71828;`, + "lib/index.ts": `export const piNumber = 3.14159;`, + "lib/run.ts": ` +import { piNumber } from "."; +console.log("piNumber:", piNumber); +`, + }); + + // Spawn Bun process + await using proc = Bun.spawn({ + cmd: [bunExe(), "lib/run.ts"], + env: bunEnv, + cwd: String(dir), + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + // Should import from lib/index.ts, not lib.ts + expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`"piNumber: 3.14159"`); + expect(exitCode).toBe(0); +}); + +test("issue #24449 - './' in subdirectory should resolve to index.ts, not root file with same name", async () => { + // Create temp directory with test files + using dir = tempDir("test-issue-24449-slash", { + "lib.ts": `export const eulerNumber = 2.71828;`, + "lib/index.ts": `export const piNumber = 3.14159;`, + "lib/run.ts": ` +import { piNumber } from "./"; +console.log("piNumber:", piNumber); +`, + }); + + // Spawn Bun process + await using proc = Bun.spawn({ + cmd: [bunExe(), "lib/run.ts"], + env: bunEnv, + cwd: String(dir), + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + // Should import from lib/index.ts, not lib.ts + expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`"piNumber: 3.14159"`); + expect(exitCode).toBe(0); +});