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
129 lines
3.6 KiB
Go
129 lines
3.6 KiB
Go
// Package parity computes cross-file dependency-graph coverage metrics used to
|
|
// measure — and lock in — the quality of Gortex's resolved graph per language.
|
|
package parity
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// LanguageCoverage is the per-language share of symbol-bearing source files that
|
|
// have at least one resolved cross-file dependent — something in another file
|
|
// that imports, calls, references, routes to, or otherwise depends on a symbol
|
|
// defined in the file.
|
|
type LanguageCoverage struct {
|
|
Language string `json:"language"`
|
|
SymbolFiles int `json:"symbol_files"`
|
|
CoveredFiles int `json:"covered_files"`
|
|
Coverage float64 `json:"coverage"`
|
|
}
|
|
|
|
// coverageSymbolKinds are the node kinds whose presence makes a file a
|
|
// symbol-bearing source file (the denominator of the coverage metric).
|
|
var coverageSymbolKinds = map[graph.NodeKind]bool{
|
|
graph.KindFunction: true,
|
|
graph.KindMethod: true,
|
|
graph.KindType: true,
|
|
graph.KindInterface: true,
|
|
graph.KindVariable: true,
|
|
graph.KindConstant: true,
|
|
graph.KindField: true,
|
|
graph.KindEnumMember: true,
|
|
}
|
|
|
|
// dependencyEdgeKinds are the edge kinds that count as a cross-file dependent: a
|
|
// resolved edge of one of these kinds crossing a file boundary marks its target
|
|
// file as covered.
|
|
var dependencyEdgeKinds = map[graph.EdgeKind]bool{
|
|
graph.EdgeImports: true,
|
|
graph.EdgeCalls: true,
|
|
graph.EdgeReferences: true,
|
|
graph.EdgeExtends: true,
|
|
graph.EdgeImplements: true,
|
|
graph.EdgeInstantiates: true,
|
|
graph.EdgeTypedAs: true,
|
|
graph.EdgeReturns: true,
|
|
graph.EdgeOverrides: true,
|
|
graph.EdgeHandlesRoute: true,
|
|
}
|
|
|
|
// CoverageOf computes per-language resolved-cross-file-dependent coverage over
|
|
// the whole graph. Languages are returned sorted by name; a language with no
|
|
// symbol-bearing source file is omitted.
|
|
func CoverageOf(g graph.Store) []LanguageCoverage {
|
|
nodes := make(map[string]*graph.Node)
|
|
fileLang := make(map[string]string)
|
|
symbolFiles := make(map[string]bool)
|
|
|
|
for _, n := range g.AllNodes() {
|
|
if n == nil {
|
|
continue
|
|
}
|
|
nodes[n.ID] = n
|
|
switch {
|
|
case n.Kind == graph.KindFile:
|
|
if path := nodeFile(n); path != "" && n.Language != "" {
|
|
fileLang[path] = n.Language
|
|
}
|
|
case coverageSymbolKinds[n.Kind] && n.FilePath != "":
|
|
symbolFiles[n.FilePath] = true
|
|
if fileLang[n.FilePath] == "" && n.Language != "" {
|
|
fileLang[n.FilePath] = n.Language
|
|
}
|
|
}
|
|
}
|
|
|
|
covered := make(map[string]bool)
|
|
for _, e := range g.AllEdges() {
|
|
if e == nil || !dependencyEdgeKinds[e.Kind] || graph.IsUnresolvedTarget(e.To) {
|
|
continue
|
|
}
|
|
src, dst := nodes[e.From], nodes[e.To]
|
|
if src == nil || dst == nil {
|
|
continue
|
|
}
|
|
srcFile, dstFile := nodeFile(src), nodeFile(dst)
|
|
if srcFile == "" || dstFile == "" || srcFile == dstFile {
|
|
continue
|
|
}
|
|
covered[dstFile] = true
|
|
}
|
|
|
|
type acc struct{ sym, cov int }
|
|
byLang := make(map[string]*acc)
|
|
for f := range symbolFiles {
|
|
a := byLang[fileLang[f]]
|
|
if a == nil {
|
|
a = &acc{}
|
|
byLang[fileLang[f]] = a
|
|
}
|
|
a.sym++
|
|
if covered[f] {
|
|
a.cov++
|
|
}
|
|
}
|
|
|
|
out := make([]LanguageCoverage, 0, len(byLang))
|
|
for lang, a := range byLang {
|
|
cov := 0.0
|
|
if a.sym > 0 {
|
|
cov = float64(a.cov) / float64(a.sym)
|
|
}
|
|
out = append(out, LanguageCoverage{Language: lang, SymbolFiles: a.sym, CoveredFiles: a.cov, Coverage: cov})
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].Language < out[j].Language })
|
|
return out
|
|
}
|
|
|
|
// nodeFile returns the source-file path a node belongs to.
|
|
func nodeFile(n *graph.Node) string {
|
|
if n.FilePath != "" {
|
|
return n.FilePath
|
|
}
|
|
if n.Kind == graph.KindFile {
|
|
return n.ID
|
|
}
|
|
return ""
|
|
}
|