Skip to content

Commit 8eaa1c0

Browse files
committed
fix: guard dot-slash conversion for Go imports, prefer longest module match
Only apply dot-to-slash conversion on imports without slashes (Python dotted absolute form). Go-style imports like github.com/user/repo are left untouched. Also prefer longest matching module for deterministic resolution and normalize resolved paths with filepath.ToSlash for Windows compat.
1 parent 5708dd5 commit 8eaa1c0

2 files changed

Lines changed: 47 additions & 12 deletions

File tree

internal/graph/graph.go

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -292,46 +292,61 @@ func resolveImport(imp, fileDir string, knownModules []string) string {
292292

293293
// Relative imports: ./foo or ../foo
294294
if strings.HasPrefix(imp, "./") || strings.HasPrefix(imp, "../") {
295-
resolved := filepath.Clean(filepath.Join(fileDir, imp))
296-
// Try exact match first, then prefix match.
295+
resolved := filepath.ToSlash(filepath.Clean(filepath.Join(fileDir, imp)))
296+
// Try exact match first, then prefix match (longest wins).
297+
best := ""
297298
for _, mod := range knownModules {
298299
if mod == resolved {
299300
return mod
300301
}
301302
}
302-
// The import may point to a file inside a module directory.
303303
for _, mod := range knownModules {
304304
if strings.HasPrefix(resolved, mod+"/") || strings.HasPrefix(resolved+"/", mod+"/") {
305-
return mod
305+
if len(mod) > len(best) {
306+
best = mod
307+
}
306308
}
307309
}
308-
return ""
310+
return best
309311
}
310312

311313
// Absolute/package-style imports: match suffix against known module names.
312-
// e.g. import "internal/auth" matches module "internal/auth".
313-
// Also convert dotted imports (Python "os.path" → "os/path") for matching.
314-
slashImp := strings.ReplaceAll(imp, ".", "/")
314+
// Convert dotted imports to slash-separated only when the import has no slash
315+
// (Python "os.path" → "os/path"). Imports with slashes (Go "github.com/...")
316+
// are left as-is to avoid false matches.
317+
slashImp := imp
318+
if !strings.Contains(imp, "/") {
319+
slashImp = strings.ReplaceAll(imp, ".", "/")
320+
}
315321

322+
best := ""
316323
for _, mod := range knownModules {
317324
if mod == imp || mod == slashImp {
318325
return mod
319326
}
320327
// Suffix match: "internal/auth" matches module "internal/auth".
321328
if strings.HasSuffix(imp, "/"+mod) || strings.HasSuffix(slashImp, "/"+mod) {
322-
return mod
329+
if len(mod) > len(best) {
330+
best = mod
331+
}
332+
continue
323333
}
324334
// Module is a prefix of the import path (package.submodule → package/).
325335
if strings.HasPrefix(slashImp, mod+"/") || strings.HasPrefix(imp, mod+"/") {
326-
return mod
336+
if len(mod) > len(best) {
337+
best = mod
338+
}
339+
continue
327340
}
328341
// Module is a suffix of the import path (Go-style).
329342
if strings.HasSuffix(imp, mod) || strings.HasSuffix(slashImp, mod) {
330-
return mod
343+
if len(mod) > len(best) {
344+
best = mod
345+
}
331346
}
332347
}
333348

334-
return ""
349+
return best
335350
}
336351

337352
// normalizePythonImport converts Python dot-prefix relative imports to path-style.

internal/graph/graph_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,26 @@ func TestPythonDottedAbsoluteImport(t *testing.T) {
202202
assertStringSliceContains(t, apiMod.DependsOn, "myapp/db")
203203
}
204204

205+
// TestGoImportWithDotsNotMangled verifies that Go-style imports containing dots
206+
// (e.g. "github.com/user/repo") are not incorrectly slash-converted.
207+
func TestGoImportWithDotsNotMangled(t *testing.T) {
208+
files := []*parser.FileInfo{
209+
{Path: "cmd/main.go", Language: "go", Imports: []string{"github.com/lib/pq"}, LineCount: 20},
210+
{Path: "internal/db/conn.go", Language: "go", Exports: []string{"Connect"}, LineCount: 30},
211+
}
212+
213+
g := Build(files, BuildOptions{MaxDepth: 4})
214+
215+
cmdMod := g.Module("cmd")
216+
if cmdMod == nil {
217+
t.Fatal("expected module cmd to exist")
218+
}
219+
// "github.com/lib/pq" is external — should not resolve to internal/db or any module.
220+
if len(cmdMod.DependsOn) != 0 {
221+
t.Errorf("expected no deps for external Go import, got %v", cmdMod.DependsOn)
222+
}
223+
}
224+
205225
// moduleNames is a helper to extract names for error messages.
206226
func moduleNames(mods []*Module) []string {
207227
names := make([]string, len(mods))

0 commit comments

Comments
 (0)