132 lines
3.1 KiB
Go
132 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/token"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Dependency analysis types
|
|
type DependencyInfo struct {
|
|
Package string `json:"package"`
|
|
Dir string `json:"dir"`
|
|
Dependencies []string `json:"dependencies"`
|
|
Dependents []string `json:"dependents,omitempty"`
|
|
Cycles [][]string `json:"cycles,omitempty"`
|
|
}
|
|
|
|
func analyzeDependencies(dir string) ([]DependencyInfo, error) {
|
|
depMap := make(map[string]*DependencyInfo)
|
|
|
|
// First pass: collect all packages and their imports
|
|
err := walkGoFiles(dir, func(path string, src []byte, file *ast.File, fset *token.FileSet) error {
|
|
pkgDir := filepath.Dir(path)
|
|
if _, exists := depMap[pkgDir]; !exists {
|
|
depMap[pkgDir] = &DependencyInfo{
|
|
Package: file.Name.Name,
|
|
Dir: pkgDir,
|
|
Dependencies: []string{},
|
|
}
|
|
}
|
|
|
|
// Add imports
|
|
for _, imp := range file.Imports {
|
|
importPath := strings.Trim(imp.Path.Value, `"`)
|
|
if !contains(depMap[pkgDir].Dependencies, importPath) {
|
|
depMap[pkgDir].Dependencies = append(depMap[pkgDir].Dependencies, importPath)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Build dependency graph and find cycles
|
|
var deps []DependencyInfo
|
|
for _, dep := range depMap {
|
|
// Find internal dependencies
|
|
for _, imp := range dep.Dependencies {
|
|
// Check if this is an internal package
|
|
for otherDir, otherDep := range depMap {
|
|
if strings.HasSuffix(imp, otherDep.Package) && otherDir != dep.Dir {
|
|
otherDep.Dependents = append(otherDep.Dependents, dep.Package)
|
|
}
|
|
}
|
|
}
|
|
deps = append(deps, *dep)
|
|
}
|
|
|
|
// Simple cycle detection (could be enhanced)
|
|
for i := range deps {
|
|
deps[i].Cycles = findCycles(&deps[i], depMap)
|
|
}
|
|
|
|
return deps, nil
|
|
}
|
|
|
|
func contains(slice []string, str string) bool {
|
|
for _, s := range slice {
|
|
if s == str {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func findCycles(dep *DependencyInfo, depMap map[string]*DependencyInfo) [][]string {
|
|
// Simple DFS-based cycle detection
|
|
var cycles [][]string
|
|
visited := make(map[string]bool)
|
|
recStack := make(map[string]bool)
|
|
path := []string{}
|
|
|
|
var dfs func(pkg string) bool
|
|
dfs = func(pkg string) bool {
|
|
visited[pkg] = true
|
|
recStack[pkg] = true
|
|
path = append(path, pkg)
|
|
|
|
// Find dependencies for this package
|
|
for _, d := range depMap {
|
|
if d.Package == pkg {
|
|
for _, imp := range d.Dependencies {
|
|
for _, otherDep := range depMap {
|
|
if strings.HasSuffix(imp, otherDep.Package) {
|
|
if !visited[otherDep.Package] {
|
|
if dfs(otherDep.Package) {
|
|
return true
|
|
}
|
|
} else if recStack[otherDep.Package] {
|
|
// Found a cycle
|
|
cycleStart := -1
|
|
for i, p := range path {
|
|
if p == otherDep.Package {
|
|
cycleStart = i
|
|
break
|
|
}
|
|
}
|
|
if cycleStart >= 0 {
|
|
cycle := append([]string{}, path[cycleStart:]...)
|
|
cycles = append(cycles, cycle)
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
path = path[:len(path)-1]
|
|
recStack[pkg] = false
|
|
return false
|
|
}
|
|
|
|
dfs(dep.Package)
|
|
return cycles
|
|
} |