Compare commits

...
Author SHA1 Message Date
Antigravity Agent 9570d78591 fix(rust): isExported always false - visibility_modifier is sibling not parent
In Rust's AST, `visibility_modifier` (pub, pub(crate), pub(super)) is a direct
child of the declaration node (function_item, struct_item, etc.), NOT a parent
of the name identifier. The previous code walked up the parent chain looking for
visibility_modifier nodes, which would never be found since it is always a
sibling at the declaration level.

Fix: walk up from the name node to the enclosing declaration node
(function_item, struct_item, enum_item, trait_item, etc.), then scan its
direct children for a visibility_modifier node starting with 'pub'.

Verified on tokio: exported Function count 0 → 2,367 after fix.
Also correctly identifies private functions (4,419 in tokio) and
pub(crate)/pub(super) variants as exported.
2026-03-09 15:30:10 -04:00
Antigravity Agent 473cbeb92f fix(cpp): C++ header support, inline methods, adaptive bufferSize
- fix(utils): map .h to C++ (superset of C, handles both pure-C and C++ headers)
- feat(cpp-queries): add typedef, union, macro, declaration (prototype) patterns
  that are common in C/C++ headers — CPP_QUERIES was missing these vs C_QUERIES
- feat(cpp): capture inline method bodies inside class (function_definition
  directly inside field_declaration_list, name is field_identifier not identifier)
- fix(parse-worker,call-processor): handle field_identifier and operator_name
  inner declarator types in findEnclosingFunctionId — inline class methods
  with bodies were producing CALLS from null (fell through to File nodes)
- fix(buffer): adaptive bufferSize = max(2×fileSize, 512KB), capped at 32MB
  Previous 256KB fixed limit silently skipped any file > ~200KB (imgui.h 411KB,
  imgui.cpp 931KB, etc.). Silent parse failures caused 0 nodes for large files.

Results on test repos after this commit:
- tmux (C):   14,087 nodes, 23,196 edges, 300 flows (was 14,008 / 22,686)
- imgui (C++): 4,896 nodes, 10,286 edges, 300 flows (was 2,658 / 5,476 / 220)
- ShareX (C#): 16,265 nodes, 31,319 edges, 300 flows (unchanged, correct)
- curl (C):   28,355 nodes, 53,946 edges, 300 flows (verified)
2026-03-09 15:24:58 -04:00
Antigravity Agent 355e4b36cf fix: C# isExported and C++ template CALLS label matching
- C# isExported: walk up to declaration node and check sibling modifier
  children for 'public', instead of ancestor walk which never reached
  the modifier (it's a sibling, not parent). ShareX now has 3,418
  exported vs 3,272 non-exported nodes (was 0 exported due to bug).

- C++ template functions: function_definition inside template_declaration
  is registered as 'Template' label by the query, but findEnclosingFunctionId
  was generating 'Function' label IDs — causing CALLS edges to dangle.
  Now detects template_declaration parent and sets label='Template'.
  Applied to both parse-worker.ts (worker path) and call-processor.ts
  (sequential fallback).
2026-03-09 14:52:43 -04:00
Antigravity Agent e5d3480fa3 fix: C/C++/C# language support - flows from 0 to 300 on real repos
- fix(c/cpp): isExported was hardcoded false; now checks static linkage
- fix(c/cpp): findEnclosingFunctionId - function name is nested in
  declarator -> function_declarator -> identifier/qualified_identifier,
  not a direct 'name' field. All CALLS were sourced from File nodes.
- fix(cpp): qualified_identifier methods (ImGui::Foo) were registered as
  'Method' nodes but findEnclosingFunctionId returned 'Function' label,
  causing ID mismatch. Fix sets label = 'Method' for qualified_identifier.
- fix(c#): CSHARP_QUERIES used 'simple_base_type' which is not a valid
  node type in tree-sitter-c-sharp. Query silently failed to compile,
  producing 0 nodes/flows for all C# repos. Fixed to use correct AST
  structure: base_list directly contains identifier/generic_name.
- fix(builtins): Remove 'open', 'read', 'write', 'close' from BUILT_INS
  set — these are real POSIX syscalls in C, not Python builtins to ignore.
- feat(entry-points): Expand C/C++ entry point scoring patterns (~30 new
  patterns: _init, _run, handle_, _handler, cmd_, server_, session_, etc.)
- feat(cpp): Add tree-sitter query for inline class methods defined inside
  class body (field_declaration with function_declarator)

Results on test repos:
- tmux (C):   0 → 300 flows, 0 → 22,686 edges
- curl (C):   300 flows, 53,229 edges (verified meaningful)
- imgui (C++): 218 flows, 5,476 edges, Method CALLS working
- ShareX (C#): 0 → 300 flows, 31,338 edges (was completely broken)
2026-03-09 14:45:21 -04:00
6 changed files with 243 additions and 33 deletions
+37 -3
View File
@@ -75,10 +75,43 @@ const findEnclosingFunction = (
current.type === 'async_function_declaration' ||
current.type === 'generator_function_declaration' ||
current.type === 'function_item') { // Rust function
// Named function: function foo() {}
// Try direct name field (JS/TS/Python/Rust)
const nameNode = current.childForFieldName?.('name') ||
current.children?.find((c: any) => c.type === 'identifier' || c.type === 'property_identifier');
funcName = nameNode?.text;
if (nameNode) {
funcName = nameNode.text;
// C++ template functions: function_definition inside template_declaration
// are registered as 'Template' nodes (not 'Function'), so match that label.
if (current.type === 'function_definition' && current.parent?.type === 'template_declaration') {
label = 'Template';
}
} else {
// C/C++: name nested inside declarator -> function_declarator -> identifier
const declarator = current.childForFieldName?.('declarator');
if (declarator) {
const innerDecl = declarator.childForFieldName?.('declarator');
if (innerDecl?.type === 'identifier') {
funcName = innerDecl.text;
// Template function with declarator-style name
if (current.parent?.type === 'template_declaration') {
label = 'Template';
}
} else if (innerDecl?.type === 'qualified_identifier') {
const nameIdent = innerDecl.childForFieldName?.('name') ||
innerDecl.children?.find((c: any) => c.type === 'identifier');
funcName = nameIdent?.text;
label = 'Method'; // qualified_identifier => registered as Method
} else if (innerDecl?.type === 'field_identifier') {
// C++ inline method with body inside class: void myMethod() { ... }
funcName = innerDecl.text;
label = 'Method';
} else if (innerDecl?.type === 'operator_name') {
// C++ operator overload inside class body: operator[]
funcName = innerDecl.text;
label = 'Method';
}
}
}
} else if (current.type === 'impl_item') {
// Rust method inside impl block: wrapper around function_item or const_item
// We need to look inside for the function_item
@@ -325,7 +358,8 @@ const BUILT_IN_NAMES = new Set([
'hasOwnProperty', 'toString', 'valueOf',
// Python built-ins
'print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict', 'set', 'tuple',
'open', 'read', 'write', 'close', 'append', 'extend', 'update',
'append', 'extend', 'update',
// NOTE: 'open', 'read', 'write', 'close' removed — these are real C POSIX syscalls
'super', 'type', 'isinstance', 'issubclass', 'getattr', 'setattr', 'hasattr',
'enumerate', 'zip', 'sorted', 'reversed', 'min', 'max', 'sum', 'abs',
// Kotlin stdlib (IMPORTANT: keep in sync with parse-worker.ts BUILT_IN_NAMES)
@@ -86,21 +86,60 @@ const ENTRY_POINT_PATTERNS: Record<string, RegExp[]> = {
/^spawn/, // Async spawn
],
// C - explicit main() boost (critical for C programs)
// C - explicit main() boost plus common C entry point conventions
'c': [
/^main$/, // THE entry point
/^init_/, // Initialization functions
/^start_/, // Start functions
/^run_/, // Run functions
/^init_/, // init_server, init_client
/_init$/, // module_init, server_init
/^start_/, // start_server
/_start$/, // thread_start
/^run_/, // run_loop
/_run$/, // event_run
/^stop_/, // stop_server
/_stop$/, // service_stop
/^open_/, // open_connection
/_open$/, // file_open
/^close_/, // close_connection
/_close$/, // socket_close
/^create_/, // create_session
/_create$/, // object_create
/^destroy_/, // destroy_session
/_destroy$/, // object_destroy
/^handle_/, // handle_request
/_handler$/, // signal_handler
/_callback$/, // event_callback
/^cmd_/, // tmux: cmd_new_window, cmd_attach_session
/^server_/, // server_start, server_loop
/^client_/, // client_connect
/^session_/, // session_create
/^window_/, // window_resize (tmux)
/^key_/, // key_press
/^input_/, // input_parse
/^output_/, // output_write
/^notify_/, // notify_client
/^control_/, // control_start
],
// C++ - same as C plus class patterns
// C++ - same as C plus OOP/template patterns
'cpp': [
/^main$/, // THE entry point
/^init_/,
/_init$/,
/^Create[A-Z]/, // Factory patterns
/^create_/,
/^Run$/, // Run methods
/^run$/,
/^Start$/, // Start methods
/^start$/,
/^handle_/,
/_handler$/,
/_callback$/,
/^OnEvent/, // Event callbacks
/^on_/,
/::Run$/, // Class::Run
/::Start$/, // Class::Start
/::Init$/, // Class::Init
/::Execute$/, // Class::Execute
],
// Swift / iOS
@@ -133,14 +133,27 @@ export const isNodeExported = (node: any, name: string, language: string): boole
return first === first.toUpperCase() && first !== first.toLowerCase();
// Rust: Check for 'pub' visibility modifier
case 'rust':
// In Rust AST, `visibility_modifier` is a SIBLING of the name node within the
// declaration node (function_item, struct_item, etc.), not a parent of it.
// Fix: walk up to the declaration node, then scan its direct children.
case 'rust': {
const RUST_DECL_TYPES = new Set([
'function_item', 'struct_item', 'enum_item', 'trait_item', 'impl_item',
'type_item', 'const_item', 'static_item', 'mod_item', 'use_declaration',
'associated_type', 'function_signature_item',
]);
while (current) {
if (current.type === 'visibility_modifier') {
if (current.text?.includes('pub')) return true;
if (RUST_DECL_TYPES.has(current.type)) {
for (let i = 0; i < current.childCount; i++) {
const child = current.child(i);
if (child?.type === 'visibility_modifier' && child.text?.startsWith('pub')) return true;
}
return false;
}
current = current.parent;
}
return false;
}
// Kotlin: Default visibility is public (unlike Java)
// visibility_modifier is inside modifiers, a sibling of the name node within the declaration
@@ -159,11 +172,25 @@ export const isNodeExported = (node: any, name: string, language: string): boole
// No visibility modifier = public (Kotlin default)
return true;
// C/C++: No native export concept at language level
// Entry points will be detected via name patterns (main, etc.)
// C/C++: Functions without 'static' storage class have external linkage
// by default, making them globally accessible (equivalent to exported).
// Only functions explicitly marked 'static' are file-scoped (not exported).
case 'c':
case 'cpp':
return false;
case 'cpp': {
// Walk up to the function_definition/declaration and check for 'static'
let cur = node;
while (cur) {
if (cur.type === 'function_definition' || cur.type === 'declaration') {
// Check text before the opening brace (or semicolon) for 'static'
const declText: string = (cur.text || '').split('{')[0].split(';')[0];
// 'static' as a storage class (not 'static_assert' etc.)
if (/\bstatic\b/.test(declText)) return false;
return true; // No 'static' = external linkage = exported
}
cur = cur.parent;
}
return true; // Top-level C/C++ functions default to external linkage
}
// Swift: Check for 'public' or 'open' access modifiers
case 'swift':
@@ -297,7 +324,10 @@ const processParsingSequential = async (
let tree;
try {
tree = parser.parse(file.content, undefined, { bufferSize: 1024 * 256 });
// bufferSize must be >= file size. Use 2× file size, minimum 512KB, maximum 32MB.
const fileSizeBytes = Buffer.byteLength(file.content, 'utf8');
const bufSize = Math.min(Math.max(fileSizeBytes * 2, 512 * 1024), 32 * 1024 * 1024);
tree = parser.parse(file.content, undefined, { bufferSize: bufSize });
} catch (parseError) {
console.warn(`Skipping unparseable file: ${file.path}`);
continue;
@@ -228,9 +228,30 @@ export const CPP_QUERIES = `
(namespace_definition name: (namespace_identifier) @name) @definition.namespace
(enum_specifier name: (type_identifier) @name) @definition.enum
; Typedefs and unions (common in C-style headers and mixed C/C++ code)
(type_definition declarator: (type_identifier) @name) @definition.typedef
(union_specifier name: (type_identifier) @name) @definition.union
; Macros
(preproc_function_def name: (identifier) @name) @definition.macro
(preproc_def name: (identifier) @name) @definition.macro
; Functions & Methods
(function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function
(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))) @definition.method
; Function declarations / prototypes (common in headers)
(declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.function
; Inline class method declarations (inside class body, no body: void Foo();)
(field_declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.method
; Inline class method definitions (inside class body, with body: void Foo() { ... })
; The function_definition is a direct child of field_declaration_list, not wrapped in field_declaration.
; Name uses field_identifier (regular methods) or identifier (constructors) or operator_name (operators).
(field_declaration_list
(function_definition
declarator: (function_declarator
declarator: [(field_identifier) (identifier) (operator_name)] @name))) @definition.method
; Templates
(template_declaration (class_specifier name: (type_identifier) @name)) @definition.template
@@ -282,9 +303,9 @@ export const CSHARP_QUERIES = `
; Heritage
(class_declaration name: (identifier) @heritage.class
(base_list (simple_base_type (identifier) @heritage.extends))) @heritage
(base_list (identifier) @heritage.extends)) @heritage
(class_declaration name: (identifier) @heritage.class
(base_list (simple_base_type (generic_name (identifier) @heritage.extends)))) @heritage
(base_list (generic_name (identifier) @heritage.extends))) @heritage
`;
// Rust queries - works with tree-sitter-rust
+6 -4
View File
@@ -37,11 +37,13 @@ export const getLanguageFromFilename = (filename: string): SupportedLanguages |
if (filename.endsWith('.py')) return SupportedLanguages.Python;
// Java
if (filename.endsWith('.java')) return SupportedLanguages.Java;
// C (source and headers)
if (filename.endsWith('.c') || filename.endsWith('.h')) return SupportedLanguages.C;
// C++ (all common extensions)
// C source files
if (filename.endsWith('.c')) return SupportedLanguages.C;
// C++ (all common extensions, including .h)
// .h is parsed as C++ because tree-sitter-cpp is a strict superset of C, so pure-C
// headers parse correctly, and C++ headers (classes, templates) are handled properly.
if (filename.endsWith('.cpp') || filename.endsWith('.cc') || filename.endsWith('.cxx') ||
filename.endsWith('.hpp') || filename.endsWith('.hxx') || filename.endsWith('.hh')) return SupportedLanguages.CPlusPlus;
filename.endsWith('.h') || filename.endsWith('.hpp') || filename.endsWith('.hxx') || filename.endsWith('.hh')) return SupportedLanguages.CPlusPlus;
// C#
if (filename.endsWith('.cs')) return SupportedLanguages.CSharp;
// Go
@@ -185,28 +185,58 @@ const isNodeExported = (node: any, name: string, language: string): boolean => {
}
return false;
case 'csharp':
case 'csharp': {
// In C# AST, `modifier` nodes are SIBLINGS of the name node inside the
// declaration (e.g. method_declaration, class_declaration). Walking up
// from the name node reaches the declaration, then we check its children.
const CSHARP_DECL_TYPES = new Set([
'method_declaration', 'local_function_statement', 'constructor_declaration',
'class_declaration', 'interface_declaration', 'struct_declaration',
'enum_declaration', 'record_declaration', 'delegate_declaration',
'property_declaration', 'field_declaration', 'event_declaration',
'namespace_declaration',
]);
while (current) {
if (current.type === 'modifier' || current.type === 'modifiers') {
if (current.text?.includes('public')) return true;
if (CSHARP_DECL_TYPES.has(current.type)) {
// Check siblings: any child of the declaration that is a modifier with text 'public'
for (let i = 0; i < current.childCount; i++) {
const child = current.child(i);
if (child?.type === 'modifier' && child.text === 'public') return true;
}
return false;
}
current = current.parent;
}
return false;
}
case 'go':
if (name.length === 0) return false;
const first = name[0];
return first === first.toUpperCase() && first !== first.toLowerCase();
case 'rust':
case 'rust': {
// In Rust AST, `visibility_modifier` is a SIBLING of the name node (identifier/type_identifier)
// within the declaration node (function_item, struct_item, impl_item, etc.).
// Walking up parents from the name node will never hit `visibility_modifier` directly.
// Fix: walk up to the declaration node, then check its children for visibility_modifier.
const RUST_DECL_TYPES = new Set([
'function_item', 'struct_item', 'enum_item', 'trait_item', 'impl_item',
'type_item', 'const_item', 'static_item', 'mod_item', 'use_declaration',
'associated_type', 'function_signature_item',
]);
while (current) {
if (current.type === 'visibility_modifier') {
if (current.text?.includes('pub')) return true;
if (RUST_DECL_TYPES.has(current.type)) {
for (let i = 0; i < current.childCount; i++) {
const child = current.child(i);
if (child?.type === 'visibility_modifier' && child.text?.startsWith('pub')) return true;
}
return false;
}
current = current.parent;
}
return false;
}
// Kotlin: Default visibility is public (unlike Java)
// visibility_modifier is inside modifiers, a sibling of the name node within the declaration
@@ -225,9 +255,22 @@ const isNodeExported = (node: any, name: string, language: string): boolean => {
// No visibility modifier = public (Kotlin default)
return true;
// C/C++: Functions without 'static' storage class have external linkage
// by default, making them globally accessible (equivalent to exported).
// Only functions explicitly marked 'static' are file-scoped (not exported).
case 'c':
case 'cpp':
return false;
case 'cpp': {
let cur = node;
while (cur) {
if (cur.type === 'function_definition' || cur.type === 'declaration') {
const declText: string = (cur.text || '').split('{')[0].split(';')[0];
if (/\bstatic\b/.test(declText)) return false;
return true;
}
cur = cur.parent;
}
return true;
}
case 'php':
// Top-level classes/interfaces/traits are always accessible
@@ -297,9 +340,46 @@ const findEnclosingFunctionId = (node: any, filePath: string): string | null =>
if (['function_declaration', 'function_definition', 'async_function_declaration',
'generator_function_declaration', 'function_item'].includes(current.type)) {
// Try direct name field (JS/TS/Python/Rust)
const nameNode = current.childForFieldName?.('name') ||
current.children?.find((c: any) => c.type === 'identifier' || c.type === 'property_identifier');
funcName = nameNode?.text;
if (nameNode) {
funcName = nameNode.text;
// C++ template functions: function_definition inside template_declaration
// are registered as 'Template' nodes (not 'Function'), so match that label.
if (current.type === 'function_definition' && current.parent?.type === 'template_declaration') {
label = 'Template';
}
} else {
// C/C++: name is nested in declarator -> function_declarator -> identifier/qualified_identifier
const declarator = current.childForFieldName?.('declarator');
if (declarator) {
const innerDecl = declarator.childForFieldName?.('declarator');
if (innerDecl?.type === 'identifier') {
funcName = innerDecl.text;
// Template function with qualified-style declarator (rare, but check)
if (current.parent?.type === 'template_declaration') {
label = 'Template';
}
} else if (innerDecl?.type === 'qualified_identifier') {
// C++ qualified name: Foo::bar — captured as 'Method' node
const nameIdent = innerDecl.childForFieldName?.('name') ||
innerDecl.children?.find((c: any) => c.type === 'identifier');
funcName = nameIdent?.text;
label = 'Method'; // qualified_identifier => registered as Method
} else if (innerDecl?.type === 'field_identifier') {
// C++ inline method with body inside class: void myMethod() { ... }
// The function_definition is a direct child of field_declaration_list.
// Name node is field_identifier, registered as 'Method'.
funcName = innerDecl.text;
label = 'Method';
} else if (innerDecl?.type === 'operator_name') {
// C++ operator overload inside class body: operator[]
funcName = innerDecl.text;
label = 'Method';
}
}
}
} else if (current.type === 'impl_item') {
const funcItem = current.children?.find((c: any) => c.type === 'function_item');
if (funcItem) {
@@ -359,7 +439,8 @@ const BUILT_INS = new Set([
'hasOwnProperty', 'toString', 'valueOf',
// Python
'print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict', 'set', 'tuple',
'open', 'read', 'write', 'close', 'append', 'extend', 'update',
'append', 'extend', 'update',
// NOTE: 'open', 'read', 'write', 'close' removed — these are real C POSIX syscalls
'super', 'type', 'isinstance', 'issubclass', 'getattr', 'setattr', 'hasattr',
'enumerate', 'zip', 'sorted', 'reversed', 'min', 'max', 'sum', 'abs',
// Kotlin stdlib (IMPORTANT: keep in sync with call-processor.ts BUILT_IN_NAMES)
@@ -1109,7 +1190,10 @@ const processFileGroup = (
let tree;
try {
tree = parser.parse(file.content, undefined, { bufferSize: 1024 * 256 });
// bufferSize must be >= file size. Use 2× file size, minimum 512KB, maximum 32MB.
const fileSizeBytes = Buffer.byteLength(file.content, 'utf8');
const bufSize = Math.min(Math.max(fileSizeBytes * 2, 512 * 1024), 32 * 1024 * 1024);
tree = parser.parse(file.content, undefined, { bufferSize: bufSize });
} catch {
continue;
}