Files
git.stella-ops.org/tools/stella-callgraph-go/framework.go
master 8779e9226f feat: add stella-callgraph-node for JavaScript/TypeScript call graph extraction
- Implemented a new tool `stella-callgraph-node` that extracts call graphs from JavaScript/TypeScript projects using Babel AST.
- Added command-line interface with options for JSON output and help.
- Included functionality to analyze project structure, detect functions, and build call graphs.
- Created a package.json file for dependency management.

feat: introduce stella-callgraph-python for Python call graph extraction

- Developed `stella-callgraph-python` to extract call graphs from Python projects using AST analysis.
- Implemented command-line interface with options for JSON output and verbose logging.
- Added framework detection to identify popular web frameworks and their entry points.
- Created an AST analyzer to traverse Python code and extract function definitions and calls.
- Included requirements.txt for project dependencies.

chore: add framework detection for Python projects

- Implemented framework detection logic to identify frameworks like Flask, FastAPI, Django, and others based on project files and import patterns.
- Enhanced the AST analyzer to recognize entry points based on decorators and function definitions.
2025-12-19 18:11:59 +02:00

144 lines
3.6 KiB
Go

// Framework detection for Go projects
package main
import (
"golang.org/x/tools/go/ssa"
"strings"
)
// FrameworkPattern defines detection patterns for a framework
type FrameworkPattern struct {
Name string
Packages []string
EntrypointFns []string
HandlerType string
}
// Known Go web frameworks
var frameworkPatterns = []FrameworkPattern{
{
Name: "net/http",
Packages: []string{"net/http"},
EntrypointFns: []string{"HandleFunc", "Handle", "ListenAndServe"},
HandlerType: "http_handler",
},
{
Name: "gin",
Packages: []string{"github.com/gin-gonic/gin"},
EntrypointFns: []string{"GET", "POST", "PUT", "DELETE", "PATCH", "Run"},
HandlerType: "http_handler",
},
{
Name: "echo",
Packages: []string{"github.com/labstack/echo"},
EntrypointFns: []string{"GET", "POST", "PUT", "DELETE", "PATCH", "Start"},
HandlerType: "http_handler",
},
{
Name: "fiber",
Packages: []string{"github.com/gofiber/fiber"},
EntrypointFns: []string{"Get", "Post", "Put", "Delete", "Patch", "Listen"},
HandlerType: "http_handler",
},
{
Name: "chi",
Packages: []string{"github.com/go-chi/chi"},
EntrypointFns: []string{"Get", "Post", "Put", "Delete", "Patch", "Route"},
HandlerType: "http_handler",
},
{
Name: "mux",
Packages: []string{"github.com/gorilla/mux"},
EntrypointFns: []string{"HandleFunc", "Handle", "NewRouter"},
HandlerType: "http_handler",
},
{
Name: "grpc",
Packages: []string{"google.golang.org/grpc"},
EntrypointFns: []string{"RegisterServer", "NewServer"},
HandlerType: "grpc_method",
},
{
Name: "cobra",
Packages: []string{"github.com/spf13/cobra"},
EntrypointFns: []string{"Execute", "AddCommand", "Run"},
HandlerType: "cli_command",
},
}
// DetectFramework checks if a function is related to a known framework
func DetectFramework(fn *ssa.Function) *FrameworkPattern {
if fn.Pkg == nil {
return nil
}
pkgPath := fn.Pkg.Pkg.Path()
for _, pattern := range frameworkPatterns {
for _, pkg := range pattern.Packages {
if strings.Contains(pkgPath, pkg) {
return &pattern
}
}
}
return nil
}
// DetectFrameworkEntrypoint checks if a call is a framework route registration
func DetectFrameworkEntrypoint(call *ssa.Call) *Entrypoint {
callee := call.Call.StaticCallee()
if callee == nil || callee.Pkg == nil {
return nil
}
pkgPath := callee.Pkg.Pkg.Path()
fnName := callee.Name()
for _, pattern := range frameworkPatterns {
for _, pkg := range pattern.Packages {
if strings.Contains(pkgPath, pkg) {
for _, epFn := range pattern.EntrypointFns {
if fnName == epFn {
nodeID := makeSymbolID(callee)
return &Entrypoint{
ID: nodeID,
Type: pattern.HandlerType,
}
}
}
}
}
}
return nil
}
// IsHTTPHandler checks if a function signature matches http.Handler
func IsHTTPHandler(fn *ssa.Function) bool {
sig := fn.Signature
// Check for (http.ResponseWriter, *http.Request) signature
if sig.Params().Len() == 2 {
p0 := sig.Params().At(0).Type().String()
p1 := sig.Params().At(1).Type().String()
if strings.Contains(p0, "ResponseWriter") && strings.Contains(p1, "Request") {
return true
}
}
// Check for gin.Context, echo.Context, fiber.Ctx, etc.
if sig.Params().Len() >= 1 {
p0 := sig.Params().At(0).Type().String()
if strings.Contains(p0, "gin.Context") ||
strings.Contains(p0, "echo.Context") ||
strings.Contains(p0, "fiber.Ctx") ||
strings.Contains(p0, "chi.") {
return true
}
}
return false
}