Skip to content
Open
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
9 changes: 8 additions & 1 deletion ast/src/lang/queries/php.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ impl Stack for Php {
(trait_declaration
name: (name) @{CLASS_NAME}
) @{CLASS_DEFINITION}

(enum_declaration
name: (name) @{CLASS_NAME}
(class_interface_clause
(name) @{INCLUDED_MODULES}
)?
) @{CLASS_DEFINITION}
]"#
)
}
Expand Down Expand Up @@ -438,7 +445,7 @@ impl Stack for Php {
) -> Result<Option<Operand>> {
let mut parent = node.parent();
while let Some(current) = parent {
if current.kind() == "class_declaration" {
if current.kind() == "class_declaration" || current.kind() == "enum_declaration" {
break;
}
parent = current.parent();
Expand Down
33 changes: 33 additions & 0 deletions ast/src/lang/registry/cs_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ fn walk_for_class_fields(node: Node, source: &[u8], out: &mut HashMap<String, Ha
if let Some(name_node) = node.child_by_field_name("name") {
if let Ok(class_name) = name_node.utf8_text(source) {
let mut fields = HashMap::new();
extract_fields_from_primary_ctor(node, source, &mut fields);
if let Some(body) = node.child_by_field_name("body") {
extract_fields_from_body(body, source, &mut fields);
for i in 0..body.named_child_count() {
Expand All @@ -111,6 +112,38 @@ fn walk_for_class_fields(node: Node, source: &[u8], out: &mut HashMap<String, Ha
}
}

/// Collect primary-constructor parameters (C# 12: `class Foo(IBar bar) { ... }`)
/// as implicit fields — `parameter_list` is a positional child of
/// class/record/struct_declaration, not a named field, so it must be found by kind.
fn extract_fields_from_primary_ctor(node: Node, source: &[u8], fields: &mut HashMap<String, String>) {
let Some(params) = (0..node.named_child_count())
.filter_map(|i| node.named_child(i))
.find(|n| n.kind() == "parameter_list")
else {
return;
};
for i in 0..params.named_child_count() {
let Some(param) = params.named_child(i) else {
continue;
};
if param.kind() != "parameter" {
continue;
}
let Some(name_node) = param.child_by_field_name("name") else {
continue;
};
let Ok(name) = name_node.utf8_text(source) else {
continue;
};
let Some(type_node) = param.child_by_field_name("type") else {
continue;
};
if let Some(t) = strip_cs_type(type_node, source) {
fields.insert(name.to_string(), t);
}
}
}

fn extract_fields_from_body(body: Node, source: &[u8], fields: &mut HashMap<String, String>) {
for i in 0..body.named_child_count() {
let Some(child) = body.named_child(i) else {
Expand Down
12 changes: 6 additions & 6 deletions ast/src/testing/coverage/csharp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ async fn test_btreemap_graph_structure() -> Result<()> {
let graph = repo.build_graph_inner::<BTreeMapGraph>().await?;

let endpoints = graph.find_nodes_by_type(NodeType::Endpoint);
assert_eq!(endpoints.len(), 81);
assert_eq!(endpoints.len(), 82);

let functions = graph.find_nodes_by_type(NodeType::Function);
assert_eq!(functions.len(), 362);
assert_eq!(functions.len(), 363);

let unit_tests = graph.find_nodes_by_type(NodeType::UnitTest);
assert_eq!(unit_tests.len(), 31);
Expand All @@ -83,7 +83,7 @@ async fn test_btreemap_graph_structure() -> Result<()> {
assert_eq!(e2e_tests.len(), 0);

let classes = graph.find_nodes_by_type(NodeType::Class);
assert_eq!(classes.len(), 164);
assert_eq!(classes.len(), 165);

let data_models = graph.find_nodes_by_type(NodeType::DataModel);
assert_eq!(data_models.len(), 19);
Expand All @@ -108,13 +108,13 @@ async fn test_btreemap_test_to_function_edges() -> Result<()> {
let graph = repo.build_graph_inner::<BTreeMapGraph>().await?;

let calls_edges = graph.count_edges_of_type(EdgeType::Calls);
assert_eq!(calls_edges, 300);
assert_eq!(calls_edges, 326);

let contains_edges = graph.count_edges_of_type(EdgeType::Contains);
assert_eq!(contains_edges, 1523);
assert_eq!(contains_edges, 1527);

let handler_edges = graph.count_edges_of_type(EdgeType::Handler);
assert_eq!(handler_edges, 81);
assert_eq!(handler_edges, 82);

let implements_edges = graph.count_edges_of_type(EdgeType::Implements);
assert_eq!(implements_edges, 0);
Expand Down
6 changes: 3 additions & 3 deletions ast/src/testing/coverage/php.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async fn test_btreemap_graph_structure() -> Result<()> {
assert_eq!(endpoints.len(), 41);

let functions = graph.find_nodes_by_type(NodeType::Function);
assert_eq!(functions.len(), 22);
assert_eq!(functions.len(), 23);

let unit_tests = graph.find_nodes_by_type(NodeType::UnitTest);
assert_eq!(unit_tests.len(), 5);
Expand All @@ -83,7 +83,7 @@ async fn test_btreemap_graph_structure() -> Result<()> {
assert_eq!(e2e_tests.len(), 0);

let classes = graph.find_nodes_by_type(NodeType::Class);
assert_eq!(classes.len(), 9);
assert_eq!(classes.len(), 10);

let data_models = graph.find_nodes_by_type(NodeType::DataModel);
assert_eq!(data_models.len(), 0);
Expand Down Expand Up @@ -111,7 +111,7 @@ async fn test_btreemap_test_to_function_edges() -> Result<()> {
assert_eq!(calls_edges, 22);

let contains_edges = graph.count_edges_of_type(EdgeType::Contains);
assert_eq!(contains_edges, 106);
assert_eq!(contains_edges, 110);

let handler_edges = graph.count_edges_of_type(EdgeType::Handler);
assert_eq!(handler_edges, 13);
Expand Down
23 changes: 23 additions & 0 deletions ast/src/testing/csharp/Controllers/StatusController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// @ast node: Class "StatusController"
// @ast node: Function "GetStatus"
// @ast edge: Calls -> Function "GetDashboardAsync" "CommonServices.cs"
// @ast node: Endpoint "status"
// @ast edge: Handler -> Function "GetStatus" "StatusController.cs"
// @ast node: Import "import-imports-srctestingcsharpcontrollersstatuscontrollercs-7"

using Microsoft.AspNetCore.Mvc;
using CSharpTestServer.Services;

namespace CSharpTestServer.Controllers;

[ApiController]
[Route("api/[controller]")]
public class StatusController(IAdminService adminService) : ControllerBase
{
[HttpGet("status")]
public async Task<ActionResult<object>> GetStatus()
{
var dashboard = await adminService.GetDashboardAsync();
return Ok(dashboard);
}
}
22 changes: 22 additions & 0 deletions ast/src/testing/php/app/Enums/OrderStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php
// @ast node: Class "OrderStatus"
// @ast edge: Operand -> Function "label" "OrderStatus.php"
// @ast node: Function "label"

namespace App\Enums;

enum OrderStatus: string
{
case Pending = 'pending';
case Shipped = 'shipped';
case Delivered = 'delivered';

public function label(): string
{
return match ($this) {
self::Pending => 'Pending',
self::Shipped => 'Shipped',
self::Delivered => 'Delivered',
};
}
}
6 changes: 3 additions & 3 deletions cli/tests/cli/csharp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ fn parse_stats_csharp_dir() {
let out = run_stakgraph(&["--stats", &dir]);

assert_eq!(out.exit_code, 0, "stderr: {}", out.stderr);
assert!(out.stdout.contains("Endpoint 81"), "stdout: {}", out.stdout);
assert!(out.stdout.contains("Class 164"), "stdout: {}", out.stdout);
assert!(out.stdout.contains("Function 362"), "stdout: {}", out.stdout);
assert!(out.stdout.contains("Endpoint 82"), "stdout: {}", out.stdout);
assert!(out.stdout.contains("Class 165"), "stdout: {}", out.stdout);
assert!(out.stdout.contains("Function 363"), "stdout: {}", out.stdout);
}

// ── search ────────────────────────────────────────────────────────────────────
Expand Down
4 changes: 2 additions & 2 deletions cli/tests/cli/php.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ fn parse_stats_php_dir() {

assert_eq!(out.exit_code, 0, "stderr: {}", out.stderr);
assert!(out.stdout.contains("Endpoint 41"), "stdout: {}", out.stdout);
assert!(out.stdout.contains("Class 9"), "stdout: {}", out.stdout);
assert!(out.stdout.contains("Function 22"), "stdout: {}", out.stdout);
assert!(out.stdout.contains("Class 10"), "stdout: {}", out.stdout);
assert!(out.stdout.contains("Function 23"), "stdout: {}", out.stdout);
}

// ── search ────────────────────────────────────────────────────────────────────
Expand Down
Loading