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.
This commit is contained in:
master
2025-12-19 18:11:59 +02:00
parent 951a38d561
commit 8779e9226f
130 changed files with 19011 additions and 422 deletions

View File

@@ -0,0 +1,186 @@
// -----------------------------------------------------------------------------
// NodeCallGraphExtractorTests.cs
// Sprint: SPRINT_3610_0003_0001_nodejs_callgraph
// Description: Unit tests for Node.js/JavaScript call graph extraction.
// -----------------------------------------------------------------------------
using StellaOps.Scanner.CallGraph.Node;
using Xunit;
namespace StellaOps.Scanner.CallGraph.Tests;
public class NodeCallGraphExtractorTests
{
[Fact]
public void BabelResultParser_ParsesValidJson()
{
// Arrange
var json = """
{
"module": "my-app",
"version": "1.0.0",
"nodes": [
{
"id": "js:my-app/src/index.handleRequest",
"package": "my-app",
"name": "handleRequest",
"visibility": "public"
}
],
"edges": [
{
"from": "js:my-app/src/index.handleRequest",
"to": "js:external/express.Router",
"kind": "direct"
}
],
"entrypoints": [
{
"id": "js:my-app/src/index.handleRequest",
"type": "http_handler",
"route": "/api/users",
"method": "GET"
}
]
}
""";
// Act
var result = BabelResultParser.Parse(json);
// Assert
Assert.Equal("my-app", result.Module);
Assert.Equal("1.0.0", result.Version);
Assert.Single(result.Nodes);
Assert.Single(result.Edges);
Assert.Single(result.Entrypoints);
}
[Fact]
public void BabelResultParser_ParsesNodeWithPosition()
{
// Arrange
var json = """
{
"module": "test",
"nodes": [
{
"id": "js:test/app.main",
"package": "test",
"name": "main",
"position": {
"file": "app.js",
"line": 10,
"column": 5
}
}
],
"edges": [],
"entrypoints": []
}
""";
// Act
var result = BabelResultParser.Parse(json);
// Assert
Assert.Single(result.Nodes);
var node = result.Nodes[0];
Assert.NotNull(node.Position);
Assert.Equal("app.js", node.Position.File);
Assert.Equal(10, node.Position.Line);
Assert.Equal(5, node.Position.Column);
}
[Fact]
public void BabelResultParser_ParsesEdgeWithSite()
{
// Arrange
var json = """
{
"module": "test",
"nodes": [],
"edges": [
{
"from": "js:test/a.foo",
"to": "js:test/b.bar",
"kind": "callback",
"site": {
"file": "a.js",
"line": 25
}
}
],
"entrypoints": []
}
""";
// Act
var result = BabelResultParser.Parse(json);
// Assert
Assert.Single(result.Edges);
var edge = result.Edges[0];
Assert.Equal("callback", edge.Kind);
Assert.NotNull(edge.Site);
Assert.Equal("a.js", edge.Site.File);
Assert.Equal(25, edge.Site.Line);
}
[Fact]
public void BabelResultParser_ThrowsOnEmptyInput()
{
// Arrange & Act & Assert
Assert.Throws<ArgumentException>(() => BabelResultParser.Parse(""));
Assert.Throws<ArgumentException>(() => BabelResultParser.Parse(null!));
}
[Fact]
public void BabelResultParser_ParsesNdjson()
{
// Arrange
var ndjson = """
{"type": "progress", "percent": 50}
{"type": "progress", "percent": 100}
{"module": "app", "nodes": [], "edges": [], "entrypoints": []}
""";
// Act
var result = BabelResultParser.ParseNdjson(ndjson);
// Assert
Assert.Equal("app", result.Module);
}
[Fact]
public void JsEntrypointInfo_HasCorrectProperties()
{
// Arrange
var json = """
{
"module": "api",
"nodes": [],
"edges": [],
"entrypoints": [
{
"id": "js:api/routes.getUsers",
"type": "http_handler",
"route": "/users/:id",
"method": "GET"
}
]
}
""";
// Act
var result = BabelResultParser.Parse(json);
// Assert
Assert.Single(result.Entrypoints);
var ep = result.Entrypoints[0];
Assert.Equal("js:api/routes.getUsers", ep.Id);
Assert.Equal("http_handler", ep.Type);
Assert.Equal("/users/:id", ep.Route);
Assert.Equal("GET", ep.Method);
}
}