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
8 changed files with 246 additions and 172 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
@@ -2,7 +2,6 @@ import fs from 'fs/promises';
import path from 'path';
import { KnowledgeGraph } from '../graph/types.js';
import { ASTCache } from './ast-cache.js';
import { SymbolTable } from './symbol-table.js';
import Parser from 'tree-sitter';
import { loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
import { LANGUAGE_QUERIES } from './tree-sitter-queries.js';
@@ -729,7 +728,6 @@ export const processImports = async (
onProgress?: (current: number, total: number) => void,
repoRoot?: string,
allPaths?: string[],
symbolTable?: SymbolTable,
) => {
// Use allPaths (full repo) when available for cross-chunk resolution, else fall back to chunk files
const allFileList = allPaths ?? files.map(f => f.path);
@@ -775,57 +773,6 @@ export const processImports = async (
importMap.get(filePath)!.add(resolvedPath);
};
// Helper: add symbol-level IMPORTS edges for named imports
const addSymbolImportEdges = (filePath: string, resolvedPath: string, symbolNames?: string[]) => {
if (!symbolNames || !symbolTable) return;
const sourceId = generateId('File', filePath);
for (const name of symbolNames) {
const targetNodeId = symbolTable.lookupExact(resolvedPath, name);
if (!targetNodeId) continue;
const relId = generateId('IMPORTS', `${filePath}:${name}->${resolvedPath}`);
graph.addRelationship({
id: relId,
sourceId,
targetId: targetNodeId,
type: 'IMPORTS',
confidence: 1.0,
reason: '',
});
}
};
// Helper: extract imported symbol names from AST node (for sequential path)
const extractSymbolNames = (importNode: any, language: string): string[] => {
const names: string[] = [];
if (language === SupportedLanguages.Python) {
for (const child of importNode.namedChildren) {
if (child.type === 'module_name') continue;
if (child.type === 'wildcard_import') continue;
if (child.type === 'dotted_name' || child.type === 'identifier') {
names.push(child.text);
} else if (child.type === 'aliased_import') {
const nameNode = child.childForFieldName?.('name') || child.namedChildren?.[0];
if (nameNode) names.push(nameNode.text);
}
}
return names;
}
if (language === SupportedLanguages.TypeScript || language === SupportedLanguages.JavaScript) {
const importClause = importNode.namedChildren?.find((c: any) => c.type === 'import_clause');
const namedImports = importClause?.namedChildren?.find((c: any) => c.type === 'named_imports');
if (namedImports) {
for (const spec of namedImports.namedChildren) {
if (spec.type === 'import_specifier') {
const nameNode = spec.childForFieldName?.('name');
if (nameNode) names.push(nameNode.text);
}
}
}
return names;
}
return names;
};
for (let i = 0; i < files.length; i++) {
const file = files[i];
onProgress?.(i + 1, files.length);
@@ -897,9 +844,6 @@ export const processImports = async (
: sourceNode.text.replace(/['"<>]/g, '');
totalImportsFound++;
// Extract imported symbol names for symbol-level edges
const symbolNames = extractSymbolNames(captureMap['import'], language);
// ---- JVM languages (Java + Kotlin): handle wildcards and member imports ----
if (language === SupportedLanguages.Java || language === SupportedLanguages.Kotlin) {
const exts = language === SupportedLanguages.Java ? ['.java'] : KOTLIN_EXTENSIONS;
@@ -988,7 +932,6 @@ export const processImports = async (
if (resolvedPath) {
addImportEdge(file.path, resolvedPath);
addSymbolImportEdges(file.path, resolvedPath, symbolNames);
}
}
});
@@ -1013,7 +956,6 @@ export const processImportsFromExtracted = async (
onProgress?: (current: number, total: number) => void,
repoRoot?: string,
prebuiltCtx?: ImportResolutionContext,
symbolTable?: SymbolTable,
) => {
const ctx = prebuiltCtx ?? buildImportResolutionContext(files.map(f => f.path));
const { allFilePaths, allFileList, normalizedFileList, suffixIndex: index, resolveCache } = ctx;
@@ -1049,25 +991,6 @@ export const processImportsFromExtracted = async (
importMap.get(filePath)!.add(resolvedPath);
};
// Helper: add symbol-level IMPORTS edges for named imports
const addSymbolImportEdges = (filePath: string, resolvedPath: string, symbolNames?: string[]) => {
if (!symbolNames || !symbolTable) return;
const sourceId = generateId('File', filePath);
for (const name of symbolNames) {
const targetNodeId = symbolTable.lookupExact(resolvedPath, name);
if (!targetNodeId) continue;
const relId = generateId('IMPORTS', `${filePath}:${name}->${resolvedPath}`);
graph.addRelationship({
id: relId,
sourceId,
targetId: targetNodeId,
type: 'IMPORTS',
confidence: 1.0,
reason: '',
});
}
};
// Group by file for progress reporting (users see file count, not import count)
const importsByFile = new Map<string, ExtractedImport[]>();
for (const imp of extractedImports) {
@@ -1104,7 +1027,7 @@ export const processImportsFromExtracted = async (
await yieldToEventLoop();
}
for (const { rawImportPath, language, symbolNames } of fileImports) {
for (const { rawImportPath, language } of fileImports) {
totalImportsFound++;
// Check resolve cache first
@@ -1197,7 +1120,6 @@ export const processImportsFromExtracted = async (
if (resolvedPath) {
addImportEdge(filePath, resolvedPath);
addSymbolImportEdges(filePath, resolvedPath, symbolNames);
}
}
}
@@ -42,7 +42,6 @@ const DEFINITION_CAPTURE_KEYS = [
'definition.annotation',
'definition.constructor',
'definition.template',
'definition.instance',
] as const;
const getDefinitionNodeFromCaptures = (captureMap: Record<string, any>): any | null => {
@@ -134,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
@@ -160,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':
@@ -298,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;
@@ -366,7 +395,6 @@ const processParsingSequential = async (
else if (captureMap['definition.annotation']) nodeLabel = 'Annotation';
else if (captureMap['definition.constructor']) nodeLabel = 'Constructor';
else if (captureMap['definition.template']) nodeLabel = 'Template';
else if (captureMap['definition.instance']) nodeLabel = 'CodeElement';
const definitionNodeForRange = getDefinitionNodeFromCaptures(captureMap);
const startLine = definitionNodeForRange ? definitionNodeForRange.startPosition.row : (nameNode ? nameNode.startPosition.row : 0);
+2 -2
View File
@@ -213,7 +213,7 @@ export const runPipelineFromRepo = async (
if (chunkWorkerData) {
// Imports
await processImportsFromExtracted(graph, allPathObjects, chunkWorkerData.imports, importMap, undefined, repoPath, importCtx, symbolTable);
await processImportsFromExtracted(graph, allPathObjects, chunkWorkerData.imports, importMap, undefined, repoPath, importCtx);
// Calls — resolve immediately, then free the array
if (chunkWorkerData.calls.length > 0) {
await processCallsFromExtracted(graph, chunkWorkerData.calls, symbolTable, importMap);
@@ -227,7 +227,7 @@ export const runPipelineFromRepo = async (
await processRoutesFromExtracted(graph, chunkWorkerData.routes, symbolTable, importMap);
}
} else {
await processImports(graph, chunkFiles, astCache, importMap, undefined, repoPath, allPaths, symbolTable);
await processImports(graph, chunkFiles, astCache, importMap, undefined, repoPath, allPaths);
sequentialChunkPaths.push(chunkPaths);
}
@@ -141,13 +141,6 @@ export const PYTHON_QUERIES = `
function: (attribute
attribute: (identifier) @call.name)) @call
; Module-level singleton instances: service = ServiceClass()
(module
(expression_statement
(assignment
left: (identifier) @name
right: (call))) @definition.instance)
; Heritage queries - Python class inheritance
(class_definition
name: (identifier) @heritage.class
@@ -235,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
@@ -289,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
@@ -63,7 +63,6 @@ export interface ExtractedImport {
filePath: string;
rawImportPath: string;
language: string;
symbolNames?: string[];
}
export interface ExtractedCall {
@@ -186,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
@@ -226,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
@@ -281,47 +323,6 @@ const FUNCTION_NODE_TYPES = new Set([
'init_declaration', 'deinit_declaration',
]);
/** Extract the specific symbol names from an import AST node.
* Python: `from X import foo, bar` → ['foo', 'bar']
* JS/TS: `import { foo, bar } from 'X'` → ['foo', 'bar']
* Returns empty array for bare module imports or unsupported patterns. */
const extractImportedSymbolNames = (importNode: any, language: string): string[] => {
const names: string[] = [];
if (language === SupportedLanguages.Python) {
// import_from_statement children: module_name (dotted_name) + name fields (dotted_name | aliased_import)
for (const child of importNode.namedChildren) {
if (child.type === 'module_name') continue;
if (child.type === 'wildcard_import') continue;
if (child.type === 'dotted_name' || child.type === 'identifier') {
names.push(child.text);
} else if (child.type === 'aliased_import') {
// from X import foo as bar — use original name 'foo'
const nameNode = child.childForFieldName?.('name') || child.namedChildren?.[0];
if (nameNode) names.push(nameNode.text);
}
}
return names;
}
if (language === SupportedLanguages.TypeScript || language === SupportedLanguages.JavaScript) {
// import_statement > import_clause > named_imports > import_specifier*
const importClause = importNode.namedChildren?.find((c: any) => c.type === 'import_clause');
const namedImports = importClause?.namedChildren?.find((c: any) => c.type === 'named_imports');
if (namedImports) {
for (const spec of namedImports.namedChildren) {
if (spec.type === 'import_specifier') {
const nameNode = spec.childForFieldName?.('name');
if (nameNode) names.push(nameNode.text);
}
}
}
return names;
}
return names;
};
/** Walk up AST to find enclosing function, return its generateId or null for top-level */
const findEnclosingFunctionId = (node: any, filePath: string): string | null => {
let current = node.parent;
@@ -339,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) {
@@ -401,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)
@@ -522,7 +561,6 @@ const getLabelFromCaptures = (captureMap: Record<string, any>): string | null =>
if (captureMap['definition.annotation']) return 'Annotation';
if (captureMap['definition.constructor']) return 'Constructor';
if (captureMap['definition.template']) return 'Template';
if (captureMap['definition.instance']) return 'CodeElement';
return 'CodeElement';
};
@@ -549,7 +587,6 @@ const DEFINITION_CAPTURE_KEYS = [
'definition.annotation',
'definition.constructor',
'definition.template',
'definition.instance',
] as const;
const getDefinitionNodeFromCaptures = (captureMap: Record<string, any>): any | null => {
@@ -1153,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;
}
@@ -1179,15 +1219,10 @@ const processFileGroup = (
const rawImportPath = language === SupportedLanguages.Kotlin
? appendKotlinWildcard(captureMap['import.source'].text.replace(/['"<>]/g, ''), captureMap['import'])
: captureMap['import.source'].text.replace(/['"<>]/g, '');
// Extract imported symbol names from the AST node
const symbolNames = extractImportedSymbolNames(captureMap['import'], language);
result.imports.push({
filePath: file.path,
rawImportPath,
language: language,
symbolNames: symbolNames.length > 0 ? symbolNames : undefined,
});
continue;
}