Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion code/semantic_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
tstsx "github.com/smacker/go-tree-sitter/typescript/tsx"
)

const semanticAnalyzerVersion = "codefly.semantic-index/v2"
const semanticAnalyzerVersion = "codefly.semantic-index/v3"

type semanticLanguage struct {
name string
Expand Down
37 changes: 37 additions & 0 deletions code/semantic_index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,43 @@ func TestSemanticIndexReportsDegradedAndNotAttemptedCoverage(t *testing.T) {
})
}

func TestSemanticIndexTraversesLocalDirectorySymlinkRoot(t *testing.T) {
project := t.TempDir()
if err := os.WriteFile(filepath.Join(project, "app.py"), []byte("def run():\n return 1\n"), 0o644); err != nil {
t.Fatal(err)
}
logicalRoot := filepath.Join(t.TempDir(), "code")
if err := os.Symlink(project, logicalRoot); err != nil {
t.Fatal(err)
}
physicalRoot, err := filepath.EvalSymlinks(logicalRoot)
if err != nil {
t.Fatal(err)
}

server := NewDefaultCodeServer(logicalRoot)
if server.SourceDir != physicalRoot {
t.Fatalf("source root = %q, want physical root %q", server.SourceDir, physicalRoot)
}
response, err := server.Execute(t.Context(), &codev0.CodeRequest{Operation: &codev0.CodeRequest_GetSemanticIndex{GetSemanticIndex: &codev0.GetSemanticIndexRequest{}}})
if err != nil {
t.Fatal(err)
}
index := response.GetGetSemanticIndex()
if index.GetState() != basev0.SemanticIndexState_SEMANTIC_INDEX_STATE_COMPLETE || len(index.GetFiles()) != 1 || index.GetFiles()[0].GetPath() != "app.py" {
t.Fatalf("semantic index = %#v, want one root-relative Python file", index)
}
if index.GetAnalyzerVersion() != "codefly.semantic-index/v3" {
t.Fatalf("analyzer version = %q, want the symlink-root contract generation", index.GetAnalyzerVersion())
}

customRoot := filepath.Join(logicalRoot, "opaque")
custom := NewDefaultCodeServer(customRoot, WithVFS(NewMemoryVFS()))
if custom.SourceDir != customRoot {
t.Fatalf("custom VFS root = %q, want opaque identity %q", custom.SourceDir, customRoot)
}
}

func TestSemanticIndexExcludesLocalDataAndQualifiesNestedCallables(t *testing.T) {
root := t.TempDir()
body := `package main
Expand Down
12 changes: 12 additions & 0 deletions code/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@ func NewDefaultCodeServer(sourceDir string, opts ...ServerOption) *DefaultCodeSe
o(s)
}
}
// ARCHITECTURE: arbitrary-source Codefly services expose the project as a
// directory symlink inside an ephemeral service. filepath.WalkDir does not
// traverse a directory symlink used as its root, so retaining that logical
// path makes every scan look like an empty project. Canonicalize only the
// production LocalVFS boundary; custom VFS roots are opaque identities and
// must never be interpreted through the host filesystem.
switch s.FS.(type) {
case LocalVFS, *LocalVFS:
if resolved, err := filepath.EvalSymlinks(s.SourceDir); err == nil {
s.SourceDir = resolved
}
}
// Apply CachedVFS after all options (needs final SourceDir)
if s.wantCachedFS {
if cached, err := NewCachedVFS(s.FS, s.SourceDir); err == nil {
Expand Down
Loading