Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9570d78591 | ||
|
|
473cbeb92f | ||
|
|
355e4b36cf | ||
|
|
e5d3480fa3 |
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user