a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
183 lines
4.7 KiB
Go
183 lines
4.7 KiB
Go
package semantic
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// SymbolMap provides bidirectional mapping between external symbol identifiers
|
|
// (SCIP URIs, go/types object IDs, LSP URIs) and Gortex node IDs.
|
|
type SymbolMap struct {
|
|
externalToGortex map[string]string
|
|
gortexToExternal map[string]string
|
|
}
|
|
|
|
// NewSymbolMap creates an empty symbol map.
|
|
func NewSymbolMap() *SymbolMap {
|
|
return &SymbolMap{
|
|
externalToGortex: make(map[string]string),
|
|
gortexToExternal: make(map[string]string),
|
|
}
|
|
}
|
|
|
|
// Add registers a mapping between an external symbol ID and a Gortex node ID.
|
|
func (m *SymbolMap) Add(externalID, gortexID string) {
|
|
m.externalToGortex[externalID] = gortexID
|
|
m.gortexToExternal[gortexID] = externalID
|
|
}
|
|
|
|
// GortexID looks up the Gortex node ID for an external symbol.
|
|
func (m *SymbolMap) GortexID(externalID string) (string, bool) {
|
|
id, ok := m.externalToGortex[externalID]
|
|
return id, ok
|
|
}
|
|
|
|
// ExternalID looks up the external symbol ID for a Gortex node.
|
|
func (m *SymbolMap) ExternalID(gortexID string) (string, bool) {
|
|
id, ok := m.gortexToExternal[gortexID]
|
|
return id, ok
|
|
}
|
|
|
|
// Size returns the number of mappings.
|
|
func (m *SymbolMap) Size() int {
|
|
return len(m.externalToGortex)
|
|
}
|
|
|
|
// MatchNodeByFileLine finds a Gortex node by file path and line number.
|
|
// This is the primary matching strategy for SCIP and LSP results.
|
|
// It finds the innermost (smallest range) non-file node containing the line.
|
|
func MatchNodeByFileLine(g graph.Store, filePath string, line int) *graph.Node {
|
|
nodes := g.GetFileNodes(filePath)
|
|
|
|
// First: find the innermost node containing this line (smallest range).
|
|
var best *graph.Node
|
|
bestSize := int(^uint(0) >> 1)
|
|
for _, n := range nodes {
|
|
if n.Kind == graph.KindFile || n.Kind == graph.KindImport {
|
|
continue
|
|
}
|
|
if n.StartLine <= line && line <= n.EndLine {
|
|
size := n.EndLine - n.StartLine
|
|
if size < bestSize {
|
|
best = n
|
|
bestSize = size
|
|
}
|
|
}
|
|
}
|
|
if best != nil {
|
|
return best
|
|
}
|
|
|
|
// Fallback: find the closest node by start line (within tolerance).
|
|
bestDist := int(^uint(0) >> 1)
|
|
for _, n := range nodes {
|
|
if n.Kind == graph.KindFile || n.Kind == graph.KindImport {
|
|
continue
|
|
}
|
|
dist := abs(n.StartLine - line)
|
|
if dist < bestDist {
|
|
best = n
|
|
bestDist = dist
|
|
}
|
|
}
|
|
if bestDist <= 2 {
|
|
return best
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MatchCallableByFileLine finds the innermost function / method /
|
|
// closure node containing the line. Used to match LSP call-hierarchy
|
|
// items (whose selectionRange points at a function's NAME line) back
|
|
// to graph nodes. The generic MatchNodeByFileLine is wrong for this:
|
|
// parameter nodes sit on the declaration line with a zero-height
|
|
// span, so they always win the innermost tie and the hop lands on a
|
|
// `<fn>#param:<name>` endpoint instead of the function itself.
|
|
func MatchCallableByFileLine(g graph.Store, filePath string, line int) *graph.Node {
|
|
nodes := g.GetFileNodes(filePath)
|
|
|
|
callable := func(k graph.NodeKind) bool {
|
|
return k == graph.KindFunction || k == graph.KindMethod || k == graph.KindClosure
|
|
}
|
|
|
|
// First: the innermost callable containing this line (smallest range).
|
|
var best *graph.Node
|
|
bestSize := int(^uint(0) >> 1)
|
|
for _, n := range nodes {
|
|
if !callable(n.Kind) {
|
|
continue
|
|
}
|
|
if n.StartLine <= line && line <= n.EndLine {
|
|
size := n.EndLine - n.StartLine
|
|
if size < bestSize {
|
|
best = n
|
|
bestSize = size
|
|
}
|
|
}
|
|
}
|
|
if best != nil {
|
|
return best
|
|
}
|
|
|
|
// Fallback: the closest callable by start line (within tolerance).
|
|
bestDist := int(^uint(0) >> 1)
|
|
for _, n := range nodes {
|
|
if !callable(n.Kind) {
|
|
continue
|
|
}
|
|
dist := abs(n.StartLine - line)
|
|
if dist < bestDist {
|
|
best = n
|
|
bestDist = dist
|
|
}
|
|
}
|
|
if bestDist <= 2 {
|
|
return best
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MatchNodeByQualName finds a Gortex node by qualified name.
|
|
func MatchNodeByQualName(g graph.Store, qualName string) *graph.Node {
|
|
return g.GetNodeByQualName(qualName)
|
|
}
|
|
|
|
// MatchNodeByNameInFile finds a Gortex node by name within a specific file.
|
|
func MatchNodeByNameInFile(g graph.Store, name, filePath string) *graph.Node {
|
|
nodes := g.GetFileNodes(filePath)
|
|
for _, n := range nodes {
|
|
if n.Name == name {
|
|
return n
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NormalizeFilePath converts an absolute path to a repo-relative path.
|
|
func NormalizeFilePath(absPath, repoRoot string) string {
|
|
rel, err := filepath.Rel(repoRoot, absPath)
|
|
if err != nil {
|
|
return absPath
|
|
}
|
|
return filepath.ToSlash(rel)
|
|
}
|
|
|
|
// ParseGortexID extracts the file path and symbol name from a Gortex node ID.
|
|
// Gortex IDs have the format "path/to/file.go::SymbolName".
|
|
func ParseGortexID(id string) (filePath, symbolName string) {
|
|
idx := strings.LastIndex(id, "::")
|
|
if idx < 0 {
|
|
return id, ""
|
|
}
|
|
return id[:idx], id[idx+2:]
|
|
}
|
|
|
|
func abs(x int) int {
|
|
if x < 0 {
|
|
return -x
|
|
}
|
|
return x
|
|
}
|