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
4 changes: 2 additions & 2 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:

- name: Run Automated AI Review
id: automated-review
uses: anthropics/claude-code-action@51705da45eecce209d4700538bf8377d5b5fc695
uses: anthropics/claude-code-action@d5726de019ec4498aa667642bc3a80fca83aa102
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
model: claude-sonnet-4-6
Expand Down Expand Up @@ -208,7 +208,7 @@ jobs:

- name: Run Interactive AI Assistant
id: interactive-claude
uses: anthropics/claude-code-action@51705da45eecce209d4700538bf8377d5b5fc695
uses: anthropics/claude-code-action@d5726de019ec4498aa667642bc3a80fca83aa102
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
model: claude-sonnet-4-6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const DEFAULT_IGNORE_DIRS: &[&str] = &[
"venv",
"env",
".env",
// Rust workspace convention — contains only Rust source and NAPI-RS generated
// binding artifacts (index.js / index.d.ts) that produce false complexity readings.
"crates",
];

/// All supported file extensions (mirrors the JS `EXTENSIONS` set).
Expand Down
86 changes: 13 additions & 73 deletions crates/codegraph-core/src/extractors/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,62 +294,15 @@ fn match_c_node(node: &Node, source: &[u8], symbols: &mut FileSymbols, _depth: u

"call_expression" => {
if let Some(fn_node) = node.child_by_field_name("function") {
let call_line = start_line(node);
match fn_node.kind() {
"identifier" => {
let fn_name = node_text(&fn_node, source);
// dlsym(handle, "symbol") — dynamic symbol loading
if fn_name == "dlsym" || fn_name == "dlvsym" {
// Get second arg (index 1) — the symbol name
let args = node.child_by_field_name("arguments")
.or_else(|| find_child(node, "argument_list"));
let mut arg_idx = 0usize;
let mut second_arg: Option<String> = None;
if let Some(args) = args {
for i in 0..args.child_count() {
if let Some(child) = args.child(i) {
match child.kind() {
"(" | ")" | "," => continue,
"string_literal" | "string_content" if arg_idx == 1 => {
second_arg = Some(node_text(&child, source)
.replace(&['"', '\''][..], ""));
break;
}
_ => { arg_idx += 1; }
}
}
}
}
match second_arg {
Some(sym) if !sym.is_empty() => {
symbols.calls.push(Call {
name: sym.clone(),
line: call_line,
dynamic: Some(true),
dynamic_kind: Some("reflection".to_string()),
key_expr: Some(sym),
..Default::default()
});
}
_ => {
symbols.calls.push(Call {
name: "<dynamic:unresolved>".to_string(),
line: call_line,
dynamic: Some(true),
dynamic_kind: Some("unresolved-dynamic".to_string()),
..Default::default()
});
}
}
} else {
symbols.calls.push(Call {
name: fn_name.to_string(),
line: call_line,
dynamic: None,
receiver: None,
..Default::default()
});
}
symbols.calls.push(Call {
name: node_text(&fn_node, source).to_string(),
line: start_line(node),
dynamic: None,
receiver: None,
..Default::default()
});
}
"field_expression" => {
let name = named_child_text(&fn_node, "field", source)
Expand All @@ -359,34 +312,21 @@ fn match_c_node(node: &Node, source: &[u8], symbols: &mut FileSymbols, _depth: u
.map(|s| s.to_string());
symbols.calls.push(Call {
name,
line: call_line,
line: start_line(node),
dynamic: None,
receiver,
..Default::default()
});
}
// (*fp)(args) — function pointer call; unresolvable
"parenthesized_expression" | "pointer_expression" => {
_ => {
symbols.calls.push(Call {
name: "<dynamic:unresolved>".to_string(),
line: call_line,
dynamic: Some(true),
dynamic_kind: Some("unresolved-dynamic".to_string()),
name: node_text(&fn_node, source).to_string(),
line: start_line(node),
dynamic: None,
receiver: None,
..Default::default()
});
}
_ => {
let name = node_text(&fn_node, source);
if !name.is_empty() {
symbols.calls.push(Call {
name: name.to_string(),
line: call_line,
dynamic: None,
receiver: None,
..Default::default()
});
}
}
}
}
}
Expand Down
60 changes: 1 addition & 59 deletions crates/codegraph-core/src/extractors/cpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,57 +323,9 @@ fn handle_cpp_preproc_include(node: &Node, source: &[u8], symbols: &mut FileSymb

fn handle_cpp_call_expression(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
if let Some(fn_node) = node.child_by_field_name("function") {
let call_line = start_line(node);
match fn_node.kind() {
"identifier" | "qualified_identifier" | "scoped_identifier" => {
let fn_name = node_text(&fn_node, source);
// dlsym(handle, "symbol") — dynamic symbol loading via C ABI.
// String-literal second arg: resolves as reflection (C symbols are unmangled, name-match works).
// Variable second arg: flagged as unresolved-dynamic.
if fn_name == "dlsym" || fn_name == "dlvsym" {
let args = node.child_by_field_name("arguments")
.or_else(|| find_child(node, "argument_list"));
let mut arg_idx = 0usize;
let mut second_arg: Option<String> = None;
if let Some(args) = args {
for i in 0..args.child_count() {
if let Some(child) = args.child(i) {
match child.kind() {
"(" | ")" | "," => continue,
"string_literal" | "string_content" if arg_idx == 1 => {
second_arg = Some(node_text(&child, source)
.replace(&['"', '\''][..], ""));
break;
}
_ => { arg_idx += 1; }
}
}
}
}
match second_arg {
Some(sym) if !sym.is_empty() => {
symbols.calls.push(Call {
name: sym.clone(),
line: call_line,
dynamic: Some(true),
dynamic_kind: Some("reflection".to_string()),
key_expr: Some(sym),
..Default::default()
});
}
_ => {
symbols.calls.push(Call {
name: "<dynamic:unresolved>".to_string(),
line: call_line,
dynamic: Some(true),
dynamic_kind: Some("unresolved-dynamic".to_string()),
..Default::default()
});
}
}
} else {
push_simple_call(symbols, node, fn_name.to_string());
}
push_simple_call(symbols, node, node_text(&fn_node, source).to_string());
}
"field_expression" => {
let name = named_child_text(&fn_node, "field", source)
Expand All @@ -383,16 +335,6 @@ fn handle_cpp_call_expression(node: &Node, source: &[u8], symbols: &mut FileSymb
.map(|s| s.to_string());
push_call(symbols, node, name, receiver, None);
}
// (*fp)(args) — function pointer call through dereference; unresolvable statically
"parenthesized_expression" | "pointer_expression" => {
symbols.calls.push(Call {
name: "<dynamic:unresolved>".to_string(),
line: call_line,
dynamic: Some(true),
dynamic_kind: Some("unresolved-dynamic".to_string()),
..Default::default()
});
}
_ => {
push_simple_call(symbols, node, node_text(&fn_node, source).to_string());
}
Expand Down
64 changes: 3 additions & 61 deletions crates/codegraph-core/src/extractors/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,83 +195,25 @@ fn handle_import_decl(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
}
}

/// Get the Nth non-punctuation argument from a Go call_expression.
fn get_go_arg(node: &Node, index: usize, source: &[u8]) -> Option<(String, String)> {
let args = node.child_by_field_name("arguments")?;
let mut count = 0usize;
for i in 0..args.child_count() {
let child = args.child(i)?;
match child.kind() {
"(" | ")" | "," => continue,
kind => {
if count == index {
return Some((node_text(&child, source).to_string(), kind.to_string()));
}
count += 1;
}
}
}
None
}

fn handle_call_expr(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
let Some(fn_node) = node.child_by_field_name("function") else { return };
let call_line = start_line(node);

match fn_node.kind() {
"identifier" => {
symbols.calls.push(Call {
name: node_text(&fn_node, source).to_string(),
line: call_line,
line: start_line(node),
dynamic: None,
receiver: None,
..Default::default()
});
}
"selector_expression" => {
if let Some(field) = fn_node.child_by_field_name("field") {
let field_name = node_text(&field, source);
let receiver = named_child_text(&fn_node, "operand", source)
.map(|s| s.to_string());

// v.MethodByName("name") — reflect-based dynamic dispatch
if field_name == "MethodByName" {
if let Some((arg_text, arg_kind)) = get_go_arg(node, 0, source) {
match arg_kind.as_str() {
"interpreted_string_literal" | "raw_string_literal" => {
let method = arg_text.replace(&['"', '`'][..], "");
if !method.is_empty() {
symbols.calls.push(Call {
name: method,
line: call_line,
dynamic: Some(true),
dynamic_kind: Some("reflection".to_string()),
key_expr: Some(arg_text),
receiver,
..Default::default()
});
return;
}
}
_ => {
symbols.calls.push(Call {
name: "<dynamic:computed-key>".to_string(),
line: call_line,
dynamic: Some(true),
dynamic_kind: Some("computed-key".to_string()),
key_expr: Some(arg_text),
receiver,
..Default::default()
});
return;
}
}
}
}

symbols.calls.push(Call {
name: field_name.to_string(),
line: call_line,
name: node_text(&field, source).to_string(),
line: start_line(node),
dynamic: None,
receiver,
..Default::default()
Expand Down
Loading
Loading