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
76 changes: 76 additions & 0 deletions internal/golang/golang.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ func init() {

// go.sum - lockfile
core.Register("golang", core.Lockfile, &goSumParser{}, core.ExactMatch("go.sum"))

// go.graph - lockfile (go mod graph output)
core.Register("golang", core.Lockfile, &goGraphParser{}, core.ExactMatch("go.graph"))
}

// goModParser parses go.mod files.
Expand Down Expand Up @@ -145,3 +148,76 @@ func (p *goSumParser) Parse(filename string, content []byte) ([]core.Dependency,

return deps, nil
}

// goGraphParser parses go.graph files (go mod graph output).
type goGraphParser struct{}

func (p *goGraphParser) Parse(filename string, content []byte) ([]core.Dependency, error) {
var deps []core.Dependency
seen := make(map[string]bool)
directDeps := make(map[string]bool)
lines := strings.Split(string(content), "\n")

// First pass: identify direct dependencies (those required by the main module)
// The main module appears without a version in the first column
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}

parts := strings.Fields(line)
if len(parts) != 2 {
continue
}

parent := parts[0]
dep := parts[1]

// If parent has no @version, it's the main module
if !strings.Contains(parent, "@") {
// Extract just the name from dep (before @)
if idx := strings.LastIndex(dep, "@"); idx > 0 {
directDeps[dep[:idx]] = true
}
}
}

// Second pass: collect all dependencies
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}

parts := strings.Fields(line)
if len(parts) != 2 {
continue
}

dep := parts[1]

// Parse name@version
idx := strings.LastIndex(dep, "@")
if idx <= 0 {
continue
}

name := dep[:idx]
version := dep[idx+1:]

if seen[name] {
continue
}
seen[name] = true

deps = append(deps, core.Dependency{
Name: name,
Version: version,
Scope: core.Runtime,
Direct: directDeps[name],
})
}

return deps, nil
}
73 changes: 73 additions & 0 deletions internal/golang/golang_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -574,3 +574,76 @@ func TestGodepsText(t *testing.T) {
}
}
}

func TestGoGraph(t *testing.T) {
content, err := os.ReadFile("../../testdata/golang/go.graph")
if err != nil {
t.Fatalf("failed to read fixture: %v", err)
}

parser := &goGraphParser{}
deps, err := parser.Parse("go.graph", content)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}

// Should have 8 unique dependencies
if len(deps) != 8 {
t.Fatalf("expected 8 dependencies, got %d", len(deps))
}

depMap := make(map[string]core.Dependency)
for _, d := range deps {
depMap[d.Name] = d
}

// Verify direct dependencies (from main module)
directDeps := []struct {
name string
version string
}{
{"golang.org/x/text", "v0.14.0"},
{"github.com/google/uuid", "v1.4.0"},
{"github.com/stretchr/testify", "v1.8.4"},
}

for _, exp := range directDeps {
dep, ok := depMap[exp.name]
if !ok {
t.Errorf("expected %s dependency", exp.name)
continue
}
if dep.Version != exp.version {
t.Errorf("%s version = %q, want %q", exp.name, dep.Version, exp.version)
}
if !dep.Direct {
t.Errorf("%s should be direct dependency", exp.name)
}
}

// Verify transitive dependencies
transitiveDeps := []struct {
name string
version string
}{
{"golang.org/x/tools", "v0.0.0-20180917221912-90fa682c2a6e"},
{"github.com/davecgh/go-spew", "v1.1.1"},
{"github.com/pmezard/go-difflib", "v1.0.0"},
{"github.com/stretchr/objx", "v0.5.0"},
{"gopkg.in/yaml.v3", "v3.0.1"},
}

for _, exp := range transitiveDeps {
dep, ok := depMap[exp.name]
if !ok {
t.Errorf("expected %s dependency", exp.name)
continue
}
if dep.Version != exp.version {
t.Errorf("%s version = %q, want %q", exp.name, dep.Version, exp.version)
}
if dep.Direct {
t.Errorf("%s should be transitive (indirect) dependency", exp.name)
}
}
}
8 changes: 8 additions & 0 deletions testdata/golang/go.graph
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
example.com/myproject golang.org/x/text@v0.14.0
example.com/myproject github.com/google/uuid@v1.4.0
example.com/myproject github.com/stretchr/testify@v1.8.4
golang.org/x/text@v0.14.0 golang.org/x/tools@v0.0.0-20180917221912-90fa682c2a6e
github.com/stretchr/testify@v1.8.4 github.com/davecgh/go-spew@v1.1.1
github.com/stretchr/testify@v1.8.4 github.com/pmezard/go-difflib@v1.0.0
github.com/stretchr/testify@v1.8.4 github.com/stretchr/objx@v0.5.0
github.com/stretchr/testify@v1.8.4 gopkg.in/yaml.v3@v3.0.1