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
2 changes: 2 additions & 0 deletions crates/codegraph-extract/src/languages/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub static SPEC: LangSpec = LangSpec {
name_type_fallback: false,

link_impl_methods: false,
anonymous_name_fn: None,
value_func_kinds: &[],
calls: &[CallRule {
kind: "call_expression",
callee_field: "function",
Expand Down
59 changes: 55 additions & 4 deletions crates/codegraph-extract/src/languages/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ pub type TargetFn = fn(&Node, &[u8]) -> (Option<String>, Option<String>);
/// Post-process class symbol: `(class node, src) -> type_name` (VD TS heritage).
pub type ClassTypeFn = fn(&Node, &[u8]) -> Option<String>;

/// Tìm name node cho hàm anonymous theo ngữ cảnh gán (`var a = function(){}`,
/// `f = lambda: ...`) — node trả về làm cả name lẫn line để 2 pass khớp nhau.
pub type ContextNameFn = for<'a> fn(&Node<'a>) -> Option<Node<'a>>;

/// Một call-site rule: node kind nào là call + callee field + cách lấy tên.
#[derive(Clone, Copy)]
pub struct CallRule {
Expand Down Expand Up @@ -73,6 +77,13 @@ pub struct LangSpec {
/// cùng tên, methods scoped vào impl. Bật flag để re-parent methods từ impl
/// về symbol def cùng tên (xem `link_impl_methods_to_def`). Chỉ bật cho Rust.
pub link_impl_methods: bool,
/// Hàm anonymous (lambda/function expression) gán qua biến/property: mượn
/// tên theo ngữ cảnh (JS `var a = function(){}`, Py `f = lambda: ...`,
/// Go `f := func(){}`, PHP `$f = function(){}`).
pub anonymous_name_fn: Option<ContextNameFn>,
/// Node kind của giá trị gán là hàm anonymous — decl Variable/Constant chứa
/// nó bị bỏ để tránh trùng tên với Function sinh từ `anonymous_name_fn`.
pub value_func_kinds: &'static [&'static str],
// ── marker rules ──
pub if_kinds: &'static [&'static str],
pub elif_kinds: &'static [&'static str],
Expand Down Expand Up @@ -224,6 +235,13 @@ fn push_symbol(
kind: SymbolKind,
node_kind: &str,
) -> Option<u64> {
// Declarator gán hàm anonymous (`const a = () => {}`, `var h = func(){}`):
// bỏ symbol Variable — hàm bên trong sẽ được đặt tên qua anonymous_name_fn,
// tránh 2 symbol trùng tên (Variable + Function).
if matches!(kind, SymbolKind::Variable | SymbolKind::Constant) && decl_value_is_func(node, spec)
{
return None;
}
// C/C++: macro attribute trước qualified ctor (`_CUSTOM_ATTRIBUTE
// CustomWidget<T>::CustomWidget(...)`) làm tree-sitter đánh ERROR — field
// `declarator` chỉ vào init_declarator sai; tên ctor nằm trong function_declarator
Expand All @@ -244,8 +262,10 @@ fn push_symbol(
.or_else(|| {
// Anonymous function/class (JS `export default function() {}`,
// C anonymous struct) — first_identifier trong body là nhiễu, bỏ qua.
// Hàm anonymous gán qua biến/property thì mượn tên theo ngữ cảnh
// (JS `var a = function(){}`, Py `f = lambda: ...`).
if spec.func_kinds.contains(&node_kind) || spec.class_kinds.contains(&node_kind) {
None
spec.anonymous_name_fn.and_then(|f| f(node))
} else {
first_identifier(node)
}
Expand Down Expand Up @@ -513,7 +533,7 @@ fn collect_chains(
calls: &mut Vec<CallRecord>,
) {
if spec.func_kinds.contains(&root.kind()) {
if let Some(id) = func_id_of(root, src, func_index) {
if let Some(id) = func_id_of(root, src, spec, func_index) {
let (chain, mut cs) = build_chain(root, src, spec, id);
chains.insert(id, chain);
calls.append(&mut cs);
Expand All @@ -531,10 +551,18 @@ fn collect_chains(
}
}

fn func_id_of(node: &Node, src: &[u8], func_index: &HashMap<(String, u32), u64>) -> Option<u64> {
fn func_id_of<'a>(
node: &Node<'a>,
src: &[u8],
spec: &'static LangSpec,
func_index: &HashMap<(String, u32), u64>,
) -> Option<u64> {
// Phải khớp push_symbol về (name, line): name field → declarator →
// anonymous_name_fn (hàm gán qua biến) → first_identifier.
let name_node = node
.child_by_field_name("name")
.or_else(|| name_from_declarator(node))
.or_else(|| spec.anonymous_name_fn.and_then(|f| f(node)))
.or_else(|| first_identifier(node))?;
let name = text(&name_node, src)?;
let line = name_node.start_position().row as u32 + 1;
Expand Down Expand Up @@ -1155,8 +1183,31 @@ fn is_conversion_declarator(n: &Node) -> bool {
.unwrap_or(false)
}

/// Decl Variable/Constant có giá trị là hàm anonymous? Chỉ đi qua các wrapper
/// trung gian của phép gán (expression_list/assignment_statement/variable_list —
/// Go/Lua bọc value) — không vào object/block để khỏi ăn nhầm hàm lồng sâu.
fn decl_value_is_func(node: &Node, spec: &LangSpec) -> bool {
if spec.value_func_kinds.is_empty() {
return false;
}
decl_value_is_func_at(node, spec, 0)
}

fn decl_value_is_func_at(node: &Node, spec: &LangSpec, depth: u32) -> bool {
if depth > 4 {
return false;
}
named_children(node).into_iter().any(|ch| {
spec.value_func_kinds.contains(&ch.kind())
|| (matches!(
ch.kind(),
"expression_list" | "assignment_statement" | "variable_list"
) && decl_value_is_func_at(&ch, spec, depth + 1))
})
}

/// DFS tìm identifier đầu tiên trong subtree.
fn first_identifier<'a>(n: &Node<'a>) -> Option<Node<'a>> {
pub fn first_identifier<'a>(n: &Node<'a>) -> Option<Node<'a>> {
let mut stack = vec![*n];
while let Some(node) = stack.pop() {
if matches!(
Expand Down
2 changes: 2 additions & 0 deletions crates/codegraph-extract/src/languages/cpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub static SPEC: LangSpec = LangSpec {
name_type_fallback: false,

link_impl_methods: false,
anonymous_name_fn: None,
value_func_kinds: &[],
calls: &[CallRule {
kind: "call_expression",
callee_field: "function",
Expand Down
2 changes: 2 additions & 0 deletions crates/codegraph-extract/src/languages/csharp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ pub static SPEC: LangSpec = LangSpec {
name_type_fallback: false,

link_impl_methods: false,
anonymous_name_fn: None,
value_func_kinds: &[],
calls: &[
CallRule {
kind: "invocation_expression",
Expand Down
28 changes: 26 additions & 2 deletions crates/codegraph-extract/src/languages/go.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
use crate::languages::common::{CallRule, LangSpec};
use crate::languages::common::{first_identifier, CallRule, LangSpec};
use codegraph_core::SymbolKind;
use tree_sitter::Node;

fn ts_language() -> tree_sitter::Language {
tree_sitter_go::LANGUAGE.into()
}

/// `f := func(){}` / `var h = func(){}` — func_literal mượn tên biến.
/// func_literal truyền thẳng (`go func(){ }()`) không được đặt tên.
fn anonymous_name_node<'a>(node: &Node<'a>) -> Option<Node<'a>> {
let el = node.parent()?;
if el.kind() != "expression_list" {
return None;
}
let p = el.parent()?;
match p.kind() {
// `var h = func(){ }` — tên ở field `name` của var_spec.
"var_spec" => p.child_by_field_name("name"),
// `f := func(){ }` — tên là identifier đầu của expression_list left.
"short_var_declaration" => {
let left = p.child_by_field_name("left")?;
first_identifier(&left)
}
_ => None,
}
}

pub static SPEC: LangSpec = LangSpec {
language_name: "go",
extensions: &["go"],
Expand All @@ -16,14 +37,17 @@ pub static SPEC: LangSpec = LangSpec {
("var_spec", SymbolKind::Variable),
("const_spec", SymbolKind::Constant),
("parameter_declaration", SymbolKind::Parameter),
("func_literal", SymbolKind::Function),
],
func_kinds: &["function_declaration", "method_declaration"],
func_kinds: &["function_declaration", "method_declaration", "func_literal"],
class_kinds: &[],
param_kinds: &["parameter_declaration"],
annotation_kinds: &[],
name_type_fallback: false,

link_impl_methods: false,
anonymous_name_fn: Some(anonymous_name_node),
value_func_kinds: &["func_literal"],
calls: &[CallRule {
kind: "call_expression",
callee_field: "function",
Expand Down
2 changes: 2 additions & 0 deletions crates/codegraph-extract/src/languages/java.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ pub static SPEC: LangSpec = LangSpec {
name_type_fallback: false,

link_impl_methods: false,
anonymous_name_fn: None,
value_func_kinds: &[],
calls: &[
CallRule {
kind: "method_invocation",
Expand Down
20 changes: 20 additions & 0 deletions crates/codegraph-extract/src/languages/javascript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@ pub fn class_type_name(node: &Node, src: &[u8]) -> Option<String> {
None
}

/// Hàm anonymous gán qua ngữ cảnh — mượn tên từ nơi gán:
/// `var a = function(){}` / `const f = () => {}` (declarator name),
/// `obj.foo = function(){}` (left), `{ foo: function(){} }` (pair key).
pub fn anonymous_name_node<'a>(node: &Node<'a>) -> Option<Node<'a>> {
let p = node.parent()?;
let (value_field, name_field) = match p.kind() {
"variable_declarator" => ("value", "name"),
"assignment_expression" => ("right", "left"),
"pair" | "property" => ("value", "key"),
_ => return None,
};
let value = p.child_by_field_name(value_field)?;
if value.id() != node.id() {
return None;
}
p.child_by_field_name(name_field)
}

pub static SPEC: LangSpec = LangSpec {
language_name: "javascript",
extensions: &["js", "jsx", "mjs", "cjs"],
Expand Down Expand Up @@ -47,6 +65,8 @@ pub static SPEC: LangSpec = LangSpec {
name_type_fallback: false,

link_impl_methods: false,
anonymous_name_fn: Some(anonymous_name_node),
value_func_kinds: &["function_expression", "arrow_function"],
calls: &[
CallRule {
kind: "call_expression",
Expand Down
22 changes: 21 additions & 1 deletion crates/codegraph-extract/src/languages/lua.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
use crate::languages::common::{CallRule, LangSpec};
use crate::languages::common::{named_children, CallRule, LangSpec};
use codegraph_core::SymbolKind;
use tree_sitter::Node;

fn ts_language() -> tree_sitter::Language {
tree_sitter_lua::LANGUAGE.into()
}

/// `f = function() end` / `local f = function() end` — function_definition
/// anonymous mượn tên từ variable_list của assignment_statement.
fn anonymous_name_node<'a>(node: &Node<'a>) -> Option<Node<'a>> {
let el = node.parent()?;
if el.kind() != "expression_list" {
return None;
}
let stmt = el.parent()?;
if stmt.kind() != "assignment_statement" {
return None;
}
named_children(&stmt)
.into_iter()
.find(|c| c.kind() == "variable_list")
.and_then(|vl| vl.child_by_field_name("name"))
}

pub static SPEC: LangSpec = LangSpec {
language_name: "lua",
extensions: &["lua"],
Expand All @@ -27,6 +45,8 @@ pub static SPEC: LangSpec = LangSpec {
name_type_fallback: false,

link_impl_methods: false,
anonymous_name_fn: Some(anonymous_name_node),
value_func_kinds: &["function_definition"],
calls: &[CallRule {
kind: "function_call",
callee_field: "name",
Expand Down
28 changes: 27 additions & 1 deletion crates/codegraph-extract/src/languages/php.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,23 @@ fn member_call_name(node: &Node, src: &[u8]) -> Option<String> {
Some(name)
}

/// `$f = function() {}` / `$h = fn() => ...` — hàm anonymous mượn tên biến
/// (node `name` bên trong variable_name, bỏ `$` prefix).
fn anonymous_name_node<'a>(node: &Node<'a>) -> Option<Node<'a>> {
let p = node.parent()?;
if p.kind() != "assignment_expression" {
return None;
}
let right = p.child_by_field_name("right")?;
if right.id() != node.id() {
return None;
}
let left = p.child_by_field_name("left")?;
let mut cursor = left.walk();
let name_node = left.children(&mut cursor).find(|c| c.kind() == "name");
name_node.or(Some(left))
}

pub static SPEC: LangSpec = LangSpec {
language_name: "php",
extensions: &["php"],
Expand All @@ -54,8 +71,15 @@ pub static SPEC: LangSpec = LangSpec {
("const_declaration", SymbolKind::Constant),
("simple_parameter", SymbolKind::Parameter),
("property_promotion_parameter", SymbolKind::Parameter),
("anonymous_function", SymbolKind::Function),
("arrow_function", SymbolKind::Function),
],
func_kinds: &[
"function_definition",
"method_declaration",
"anonymous_function",
"arrow_function",
],
func_kinds: &["function_definition", "method_declaration"],
class_kinds: &[
"class_declaration",
"interface_declaration",
Expand All @@ -67,6 +91,8 @@ pub static SPEC: LangSpec = LangSpec {
name_type_fallback: false,

link_impl_methods: false,
anonymous_name_fn: Some(anonymous_name_node),
value_func_kinds: &["anonymous_function", "arrow_function"],
calls: &[
CallRule {
kind: "function_call_expression",
Expand Down
20 changes: 19 additions & 1 deletion crates/codegraph-extract/src/languages/python.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,43 @@
use crate::languages::common::{CallRule, LangSpec};
use codegraph_core::SymbolKind;
use tree_sitter::Node;

fn ts_language() -> tree_sitter::Language {
tree_sitter_python::LANGUAGE.into()
}

/// `f = lambda: ...` — lambda mượn tên biến ở vế trái assignment.
/// Lambda truyền thẳng (vd `map(lambda: 1, ...)`) không được đặt tên.
fn anonymous_name_node<'a>(node: &Node<'a>) -> Option<Node<'a>> {
let p = node.parent()?;
if p.kind() != "assignment" {
return None;
}
let right = p.child_by_field_name("right")?;
if right.id() != node.id() {
return None;
}
p.child_by_field_name("left")
}

pub static SPEC: LangSpec = LangSpec {
language_name: "python",
extensions: &["py", "pyi"],
ts_language,
decls: &[
("function_definition", SymbolKind::Function),
("class_definition", SymbolKind::Class),
("lambda", SymbolKind::Function),
],
func_kinds: &["function_definition"],
func_kinds: &["function_definition", "lambda"],
class_kinds: &["class_definition"],
param_kinds: &[],
annotation_kinds: &[],
name_type_fallback: false,

link_impl_methods: false,
anonymous_name_fn: Some(anonymous_name_node),
value_func_kinds: &[],
calls: &[CallRule {
kind: "call",
callee_field: "function",
Expand Down
2 changes: 2 additions & 0 deletions crates/codegraph-extract/src/languages/ruby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ pub static SPEC: LangSpec = LangSpec {
name_type_fallback: false,

link_impl_methods: false,
anonymous_name_fn: None,
value_func_kinds: &[],
calls: &[
CallRule {
kind: "call",
Expand Down
2 changes: 2 additions & 0 deletions crates/codegraph-extract/src/languages/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ pub static SPEC: LangSpec = LangSpec {
name_type_fallback: true,
// Rust: impl_item cũng là Class → re-parent methods về struct def cùng tên.
link_impl_methods: true,
anonymous_name_fn: None,
value_func_kinds: &[],
calls: &[CallRule {
kind: "call_expression",
callee_field: "function",
Expand Down
2 changes: 2 additions & 0 deletions crates/codegraph-extract/src/languages/scala.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pub static SPEC: LangSpec = LangSpec {
name_type_fallback: false,

link_impl_methods: false,
anonymous_name_fn: None,
value_func_kinds: &[],
calls: &[CallRule {
kind: "call_expression",
callee_field: "function",
Expand Down
Loading
Loading