mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-11-13 08:51:28 +00:00
Merge branch 'pr-64'
This commit is contained in:
commit
e868fd6cf9
20 changed files with 667 additions and 16 deletions
BIN
gitnexus-web/public/wasm/php/tree-sitter-php.wasm
Normal file
BIN
gitnexus-web/public/wasm/php/tree-sitter-php.wasm
Normal file
Binary file not shown.
|
|
@ -8,7 +8,7 @@ export enum SupportedLanguages {
|
|||
CSharp = 'csharp',
|
||||
Go = 'go',
|
||||
Rust = 'rust',
|
||||
// PHP = 'php',
|
||||
PHP = 'php',
|
||||
// Ruby = 'ruby',
|
||||
// Swift = 'swift',
|
||||
}
|
||||
|
|
@ -102,6 +102,27 @@ const ENTRY_POINT_PATTERNS: Record<string, RegExp[]> = {
|
|||
/^Run$/, // Run methods
|
||||
/^Start$/, // Start methods
|
||||
],
|
||||
|
||||
// PHP / Laravel
|
||||
'php': [
|
||||
/Controller$/, // UserController (class name convention)
|
||||
/^handle$/, // Job::handle(), Listener::handle()
|
||||
/^execute$/, // Command::execute()
|
||||
/^boot$/, // ServiceProvider::boot()
|
||||
/^register$/, // ServiceProvider::register()
|
||||
/^__invoke$/, // Invokable controllers/actions
|
||||
/^(index|show|store|update|destroy|create|edit)$/, // RESTful resource methods
|
||||
/^(get|post|put|delete|patch)[A-Z]/, // Explicit HTTP method actions
|
||||
/^run$/, // Command/Job run()
|
||||
/^fire$/, // Event fire()
|
||||
/^dispatch$/, // Dispatchable jobs
|
||||
/Service$/, // UserService (Service layer)
|
||||
/Repository$/, // UserRepository (Repository pattern)
|
||||
/^find$/, // Repository::find()
|
||||
/^findAll$/, // Repository::findAll()
|
||||
/^save$/, // Repository::save()
|
||||
/^delete$/, // Repository::delete()
|
||||
],
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -252,7 +273,12 @@ export function isTestFile(filePath: string): boolean {
|
|||
p.includes('/tests/') ||
|
||||
// C# test patterns
|
||||
p.includes('.tests/') ||
|
||||
p.includes('tests.cs')
|
||||
p.includes('tests.cs') ||
|
||||
// PHP/Laravel test patterns
|
||||
p.endsWith('test.php') ||
|
||||
p.endsWith('spec.php') ||
|
||||
p.includes('/tests/feature/') ||
|
||||
p.includes('/tests/unit/')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -195,8 +195,70 @@ export function detectFrameworkFromPath(filePath: string): FrameworkHint | null
|
|||
return { framework: 'c-cpp', entryPointMultiplier: 2.5, reason: 'c-app' };
|
||||
}
|
||||
|
||||
// ========== PHP / LARAVEL FRAMEWORKS ==========
|
||||
|
||||
// Laravel routes (highest - these ARE the entry point definitions)
|
||||
if (p.includes('/routes/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-routes' };
|
||||
}
|
||||
|
||||
// Laravel controllers (very high - receive HTTP requests)
|
||||
if ((p.includes('/http/controllers/') || p.includes('/controllers/')) && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-controller' };
|
||||
}
|
||||
|
||||
// Laravel controller by file name convention
|
||||
if (p.endsWith('controller.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-controller-file' };
|
||||
}
|
||||
|
||||
// Laravel console commands
|
||||
if ((p.includes('/console/commands/') || p.includes('/commands/')) && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-command' };
|
||||
}
|
||||
|
||||
// Laravel jobs (queue entry points)
|
||||
if (p.includes('/jobs/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-job' };
|
||||
}
|
||||
|
||||
// Laravel listeners (event-driven entry points)
|
||||
if (p.includes('/listeners/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-listener' };
|
||||
}
|
||||
|
||||
// Laravel middleware
|
||||
if (p.includes('/http/middleware/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-middleware' };
|
||||
}
|
||||
|
||||
// Laravel service providers
|
||||
if (p.includes('/providers/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 1.8, reason: 'laravel-provider' };
|
||||
}
|
||||
|
||||
// Laravel policies
|
||||
if (p.includes('/policies/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 2.0, reason: 'laravel-policy' };
|
||||
}
|
||||
|
||||
// Laravel models (important but not entry points per se)
|
||||
if (p.includes('/models/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 1.5, reason: 'laravel-model' };
|
||||
}
|
||||
|
||||
// Laravel services (Service Repository pattern)
|
||||
if (p.includes('/services/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 1.8, reason: 'laravel-service' };
|
||||
}
|
||||
|
||||
// Laravel repositories (Service Repository pattern)
|
||||
if (p.includes('/repositories/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 1.5, reason: 'laravel-repository' };
|
||||
}
|
||||
|
||||
// ========== GENERIC PATTERNS ==========
|
||||
|
||||
|
||||
// Any language: index files in API folders
|
||||
if (p.includes('/api/') && (
|
||||
p.endsWith('/index.ts') || p.endsWith('/index.js') ||
|
||||
|
|
@ -235,7 +297,11 @@ export const FRAMEWORK_AST_PATTERNS = {
|
|||
|
||||
// Go patterns (function signatures)
|
||||
'go-http': ['http.Handler', 'http.HandlerFunc', 'ServeHTTP'],
|
||||
|
||||
|
||||
// PHP/Laravel
|
||||
'laravel': ['Route::get', 'Route::post', 'Route::put', 'Route::delete',
|
||||
'Route::resource', 'Route::apiResource', '#[Route('],
|
||||
|
||||
// Rust macros
|
||||
'actix': ['#[get', '#[post', '#[put', '#[delete'],
|
||||
'axum': ['Router::new'],
|
||||
|
|
|
|||
|
|
@ -317,6 +317,85 @@ export const RUST_QUERIES = `
|
|||
(impl_item trait: (generic_type type: (type_identifier) @heritage.trait) type: (type_identifier) @heritage.class) @heritage
|
||||
`;
|
||||
|
||||
// PHP queries - works with tree-sitter-php (php_only grammar)
|
||||
export const PHP_QUERIES = `
|
||||
; ── Namespace ────────────────────────────────────────────────────────────────
|
||||
(namespace_definition
|
||||
name: (namespace_name) @name) @definition.namespace
|
||||
|
||||
; ── Classes ──────────────────────────────────────────────────────────────────
|
||||
(class_declaration
|
||||
name: (name) @name) @definition.class
|
||||
|
||||
; ── Interfaces ───────────────────────────────────────────────────────────────
|
||||
(interface_declaration
|
||||
name: (name) @name) @definition.interface
|
||||
|
||||
; ── Traits ───────────────────────────────────────────────────────────────────
|
||||
(trait_declaration
|
||||
name: (name) @name) @definition.trait
|
||||
|
||||
; ── Enums (PHP 8.1) ──────────────────────────────────────────────────────────
|
||||
(enum_declaration
|
||||
name: (name) @name) @definition.enum
|
||||
|
||||
; ── Top-level functions ───────────────────────────────────────────────────────
|
||||
(function_definition
|
||||
name: (name) @name) @definition.function
|
||||
|
||||
; ── Methods (including constructors) ─────────────────────────────────────────
|
||||
(method_declaration
|
||||
name: (name) @name) @definition.method
|
||||
|
||||
; ── Class properties (including Eloquent $fillable, $casts, etc.) ────────────
|
||||
(property_declaration
|
||||
(property_element
|
||||
(variable_name
|
||||
(name) @name))) @definition.property
|
||||
|
||||
; ── Imports: use statements ──────────────────────────────────────────────────
|
||||
; Simple: use App\\Models\\User;
|
||||
(namespace_use_declaration
|
||||
(namespace_use_clause
|
||||
(qualified_name) @import.source)) @import
|
||||
|
||||
; ── Function/method calls ────────────────────────────────────────────────────
|
||||
; Regular function call: foo()
|
||||
(function_call_expression
|
||||
function: (name) @call.name) @call
|
||||
|
||||
; Method call: $obj->method()
|
||||
(member_call_expression
|
||||
name: (name) @call.name) @call
|
||||
|
||||
; Nullsafe method call: $obj?->method()
|
||||
(nullsafe_member_call_expression
|
||||
name: (name) @call.name) @call
|
||||
|
||||
; Static call: Foo::bar() (php_only uses scoped_call_expression)
|
||||
(scoped_call_expression
|
||||
name: (name) @call.name) @call
|
||||
|
||||
; ── Heritage: extends ────────────────────────────────────────────────────────
|
||||
(class_declaration
|
||||
name: (name) @heritage.class
|
||||
(base_clause
|
||||
[(name) (qualified_name)] @heritage.extends)) @heritage
|
||||
|
||||
; ── Heritage: implements ─────────────────────────────────────────────────────
|
||||
(class_declaration
|
||||
name: (name) @heritage.class
|
||||
(class_interface_clause
|
||||
[(name) (qualified_name)] @heritage.implements)) @heritage.impl
|
||||
|
||||
; ── Heritage: use trait (must capture enclosing class name) ──────────────────
|
||||
(class_declaration
|
||||
name: (name) @heritage.class
|
||||
body: (declaration_list
|
||||
(use_declaration
|
||||
[(name) (qualified_name)] @heritage.trait))) @heritage
|
||||
`;
|
||||
|
||||
export const LANGUAGE_QUERIES: Record<SupportedLanguages, string> = {
|
||||
[SupportedLanguages.TypeScript]: TYPESCRIPT_QUERIES,
|
||||
[SupportedLanguages.JavaScript]: JAVASCRIPT_QUERIES,
|
||||
|
|
@ -327,5 +406,6 @@ export const LANGUAGE_QUERIES: Record<SupportedLanguages, string> = {
|
|||
[SupportedLanguages.CPlusPlus]: CPP_QUERIES,
|
||||
[SupportedLanguages.CSharp]: CSHARP_QUERIES,
|
||||
[SupportedLanguages.Rust]: RUST_QUERIES,
|
||||
[SupportedLanguages.PHP]: PHP_QUERIES,
|
||||
};
|
||||
|
||||
|
|
@ -25,6 +25,12 @@ export const getLanguageFromFilename = (filename: string): SupportedLanguages |
|
|||
if (filename.endsWith('.go')) return SupportedLanguages.Go;
|
||||
// Rust
|
||||
if (filename.endsWith('.rs')) return SupportedLanguages.Rust;
|
||||
// PHP (all common extensions)
|
||||
if (filename.endsWith('.php') || filename.endsWith('.phtml') ||
|
||||
filename.endsWith('.php3') || filename.endsWith('.php4') ||
|
||||
filename.endsWith('.php5') || filename.endsWith('.php8')) {
|
||||
return SupportedLanguages.PHP;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ const getWasmPath = (language: SupportedLanguages, filePath?: string): string =>
|
|||
[SupportedLanguages.CSharp]: '/wasm/csharp/tree-sitter-csharp.wasm',
|
||||
[SupportedLanguages.Go]: '/wasm/go/tree-sitter-go.wasm',
|
||||
[SupportedLanguages.Rust]: '/wasm/rust/tree-sitter-rust.wasm',
|
||||
[SupportedLanguages.PHP]: '/wasm/php/tree-sitter-php.wasm',
|
||||
};
|
||||
|
||||
return languageFileMap[language];
|
||||
|
|
|
|||
29
gitnexus/package-lock.json
generated
29
gitnexus/package-lock.json
generated
|
|
@ -30,6 +30,7 @@
|
|||
"tree-sitter-go": "^0.21.0",
|
||||
"tree-sitter-java": "^0.21.0",
|
||||
"tree-sitter-javascript": "^0.21.0",
|
||||
"tree-sitter-php": "^0.23.12",
|
||||
"tree-sitter-python": "^0.21.0",
|
||||
"tree-sitter-rust": "^0.21.0",
|
||||
"tree-sitter-typescript": "^0.21.0",
|
||||
|
|
@ -4378,6 +4379,34 @@
|
|||
"node": "^18 || ^20 || >= 21"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-sitter-php": {
|
||||
"version": "0.23.12",
|
||||
"resolved": "https://registry.npmjs.org/tree-sitter-php/-/tree-sitter-php-0.23.12.tgz",
|
||||
"integrity": "sha512-VwkBVOahhC2NYXK/Fuqq30NxuL/6c2hmbxEF4jrB7AyR5rLc7nT27mzF3qoi+pqx9Gy2AbXnGezF7h4MeM6YRA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^8.2.2",
|
||||
"node-gyp-build": "^4.8.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tree-sitter": "^0.21.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"tree-sitter": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tree-sitter-php/node_modules/node-addon-api": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
|
||||
"integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18 || ^20 || >= 21"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-sitter-python": {
|
||||
"version": "0.21.0",
|
||||
"resolved": "https://registry.npmjs.org/tree-sitter-python/-/tree-sitter-python-0.21.0.tgz",
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@
|
|||
"tree-sitter-go": "^0.21.0",
|
||||
"tree-sitter-java": "^0.21.0",
|
||||
"tree-sitter-javascript": "^0.21.0",
|
||||
"tree-sitter-php": "^0.23.12",
|
||||
"tree-sitter-python": "^0.21.0",
|
||||
"tree-sitter-rust": "^0.21.0",
|
||||
"tree-sitter-typescript": "^0.21.0",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export enum SupportedLanguages {
|
|||
CSharp = 'csharp',
|
||||
Go = 'go',
|
||||
Rust = 'rust',
|
||||
// PHP = 'php',
|
||||
PHP = 'php',
|
||||
// Ruby = 'ruby',
|
||||
// Swift = 'swift',
|
||||
}
|
||||
|
|
@ -102,6 +102,27 @@ const ENTRY_POINT_PATTERNS: Record<string, RegExp[]> = {
|
|||
/^Run$/, // Run methods
|
||||
/^Start$/, // Start methods
|
||||
],
|
||||
|
||||
// PHP / Laravel
|
||||
'php': [
|
||||
/Controller$/, // UserController (class name convention)
|
||||
/^handle$/, // Job::handle(), Listener::handle()
|
||||
/^execute$/, // Command::execute()
|
||||
/^boot$/, // ServiceProvider::boot()
|
||||
/^register$/, // ServiceProvider::register()
|
||||
/^__invoke$/, // Invokable controllers/actions
|
||||
/^(index|show|store|update|destroy|create|edit)$/, // RESTful resource methods
|
||||
/^(get|post|put|delete|patch)[A-Z]/, // Explicit HTTP method actions
|
||||
/^run$/, // Command/Job run()
|
||||
/^fire$/, // Event fire()
|
||||
/^dispatch$/, // Dispatchable jobs
|
||||
/Service$/, // UserService (Service layer)
|
||||
/Repository$/, // UserRepository (Repository pattern)
|
||||
/^find$/, // Repository::find()
|
||||
/^findAll$/, // Repository::findAll()
|
||||
/^save$/, // Repository::save()
|
||||
/^delete$/, // Repository::delete()
|
||||
],
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -252,7 +273,12 @@ export function isTestFile(filePath: string): boolean {
|
|||
p.includes('/tests/') ||
|
||||
// C# test patterns
|
||||
p.includes('.tests/') ||
|
||||
p.includes('tests.cs')
|
||||
p.includes('tests.cs') ||
|
||||
// PHP/Laravel test patterns
|
||||
p.endsWith('test.php') ||
|
||||
p.endsWith('spec.php') ||
|
||||
p.includes('/tests/feature/') ||
|
||||
p.includes('/tests/unit/')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -195,8 +195,70 @@ export function detectFrameworkFromPath(filePath: string): FrameworkHint | null
|
|||
return { framework: 'c-cpp', entryPointMultiplier: 2.5, reason: 'c-app' };
|
||||
}
|
||||
|
||||
// ========== PHP / LARAVEL FRAMEWORKS ==========
|
||||
|
||||
// Laravel routes (highest - these ARE the entry point definitions)
|
||||
if (p.includes('/routes/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-routes' };
|
||||
}
|
||||
|
||||
// Laravel controllers (very high - receive HTTP requests)
|
||||
if ((p.includes('/http/controllers/') || p.includes('/controllers/')) && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-controller' };
|
||||
}
|
||||
|
||||
// Laravel controller by file name convention
|
||||
if (p.endsWith('controller.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-controller-file' };
|
||||
}
|
||||
|
||||
// Laravel console commands
|
||||
if ((p.includes('/console/commands/') || p.includes('/commands/')) && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-command' };
|
||||
}
|
||||
|
||||
// Laravel jobs (queue entry points)
|
||||
if (p.includes('/jobs/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-job' };
|
||||
}
|
||||
|
||||
// Laravel listeners (event-driven entry points)
|
||||
if (p.includes('/listeners/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-listener' };
|
||||
}
|
||||
|
||||
// Laravel middleware
|
||||
if (p.includes('/http/middleware/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-middleware' };
|
||||
}
|
||||
|
||||
// Laravel service providers
|
||||
if (p.includes('/providers/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 1.8, reason: 'laravel-provider' };
|
||||
}
|
||||
|
||||
// Laravel policies
|
||||
if (p.includes('/policies/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 2.0, reason: 'laravel-policy' };
|
||||
}
|
||||
|
||||
// Laravel models (important but not entry points per se)
|
||||
if (p.includes('/models/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 1.5, reason: 'laravel-model' };
|
||||
}
|
||||
|
||||
// Laravel services (Service Repository pattern)
|
||||
if (p.includes('/services/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 1.8, reason: 'laravel-service' };
|
||||
}
|
||||
|
||||
// Laravel repositories (Service Repository pattern)
|
||||
if (p.includes('/repositories/') && p.endsWith('.php')) {
|
||||
return { framework: 'laravel', entryPointMultiplier: 1.5, reason: 'laravel-repository' };
|
||||
}
|
||||
|
||||
// ========== GENERIC PATTERNS ==========
|
||||
|
||||
|
||||
// Any language: index files in API folders
|
||||
if (p.includes('/api/') && (
|
||||
p.endsWith('/index.ts') || p.endsWith('/index.js') ||
|
||||
|
|
@ -235,7 +297,11 @@ export const FRAMEWORK_AST_PATTERNS = {
|
|||
|
||||
// Go patterns (function signatures)
|
||||
'go-http': ['http.Handler', 'http.HandlerFunc', 'ServeHTTP'],
|
||||
|
||||
|
||||
// PHP/Laravel
|
||||
'laravel': ['Route::get', 'Route::post', 'Route::put', 'Route::delete',
|
||||
'Route::resource', 'Route::apiResource', '#[Route('],
|
||||
|
||||
// Rust macros
|
||||
'actix': ['#[get', '#[post', '#[put', '#[delete'],
|
||||
'axum': ['Router::new'],
|
||||
|
|
|
|||
|
|
@ -122,6 +122,37 @@ async function loadGoModulePath(repoRoot: string): Promise<GoModuleConfig | null
|
|||
return null;
|
||||
}
|
||||
|
||||
/** PHP Composer PSR-4 autoload config */
|
||||
interface ComposerConfig {
|
||||
/** Map of namespace prefix -> directory (e.g., "App\\" -> "app/") */
|
||||
psr4: Map<string, string>;
|
||||
}
|
||||
|
||||
async function loadComposerConfig(repoRoot: string): Promise<ComposerConfig | null> {
|
||||
try {
|
||||
const composerPath = path.join(repoRoot, 'composer.json');
|
||||
const raw = await fs.readFile(composerPath, 'utf-8');
|
||||
const composer = JSON.parse(raw);
|
||||
const psr4Raw = composer.autoload?.['psr-4'] ?? {};
|
||||
const psr4Dev = composer['autoload-dev']?.['psr-4'] ?? {};
|
||||
const merged = { ...psr4Raw, ...psr4Dev };
|
||||
|
||||
const psr4 = new Map<string, string>();
|
||||
for (const [ns, dir] of Object.entries(merged)) {
|
||||
const nsNorm = (ns as string).replace(/\\+$/, '');
|
||||
const dirNorm = (dir as string).replace(/\\/g, '/').replace(/\/+$/, '');
|
||||
psr4.set(nsNorm, dirNorm);
|
||||
}
|
||||
|
||||
if (isDev) {
|
||||
console.log(`📦 Loaded ${psr4.size} PSR-4 mappings from composer.json`);
|
||||
}
|
||||
return { psr4 };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// IMPORT PATH RESOLUTION
|
||||
// ============================================================================
|
||||
|
|
@ -143,6 +174,8 @@ const EXTENSIONS = [
|
|||
'.go',
|
||||
// Rust
|
||||
'.rs', '/mod.rs',
|
||||
// PHP
|
||||
'.php', '.phtml',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -581,6 +614,48 @@ function resolveGoPackage(
|
|||
return matches;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PHP PSR-4 IMPORT RESOLUTION
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Resolve a PHP use-statement import path using PSR-4 mappings.
|
||||
* e.g. "App\Http\Controllers\UserController" -> "app/Http/Controllers/UserController.php"
|
||||
*/
|
||||
function resolvePhpImport(
|
||||
importPath: string,
|
||||
composerConfig: ComposerConfig | null,
|
||||
allFiles: Set<string>,
|
||||
normalizedFileList: string[],
|
||||
allFileList: string[],
|
||||
index?: SuffixIndex,
|
||||
): string | null {
|
||||
// Normalize: replace backslashes with forward slashes
|
||||
const normalized = importPath.replace(/\\/g, '/');
|
||||
|
||||
// Try PSR-4 resolution if composer.json was found
|
||||
if (composerConfig) {
|
||||
// Sort namespaces by length descending (longest match wins)
|
||||
const sorted = [...composerConfig.psr4.entries()].sort((a, b) => b[0].length - a[0].length);
|
||||
for (const [nsPrefix, dirPrefix] of sorted) {
|
||||
const nsPrefixSlash = nsPrefix.replace(/\\/g, '/');
|
||||
if (normalized.startsWith(nsPrefixSlash + '/') || normalized === nsPrefixSlash) {
|
||||
const remainder = normalized.slice(nsPrefixSlash.length).replace(/^\//, '');
|
||||
const filePath = dirPrefix + (remainder ? '/' + remainder : '') + '.php';
|
||||
if (allFiles.has(filePath)) return filePath;
|
||||
if (index) {
|
||||
const result = index.getInsensitive(filePath);
|
||||
if (result) return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: suffix matching (works without composer.json)
|
||||
const pathParts = normalized.split('/').filter(Boolean);
|
||||
return suffixResolve(pathParts, normalizedFileList, allFileList, index);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAIN IMPORT PROCESSOR
|
||||
// ============================================================================
|
||||
|
|
@ -612,6 +687,7 @@ export const processImports = async (
|
|||
const effectiveRoot = repoRoot || '';
|
||||
const tsconfigPaths = await loadTsconfigPaths(effectiveRoot);
|
||||
const goModule = await loadGoModulePath(effectiveRoot);
|
||||
const composerConfig = await loadComposerConfig(effectiveRoot);
|
||||
|
||||
// Helper: add an IMPORTS edge + update import map
|
||||
const addImportEdge = (filePath: string, resolvedPath: string) => {
|
||||
|
|
@ -736,6 +812,15 @@ export const processImports = async (
|
|||
// Fall through if no files found (package might be external)
|
||||
}
|
||||
|
||||
// ---- PHP: handle namespace-based imports (use statements) ----
|
||||
if (language === SupportedLanguages.PHP) {
|
||||
const resolved = resolvePhpImport(rawImportPath, composerConfig, allFilePaths, normalizedFileList, allFileList, index);
|
||||
if (resolved) {
|
||||
addImportEdge(file.path, resolved);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- Standard single-file resolution ----
|
||||
const resolvedPath = resolveImportPath(
|
||||
file.path,
|
||||
|
|
@ -785,6 +870,7 @@ export const processImportsFromExtracted = async (
|
|||
const effectiveRoot = repoRoot || '';
|
||||
const tsconfigPaths = await loadTsconfigPaths(effectiveRoot);
|
||||
const goModule = await loadGoModulePath(effectiveRoot);
|
||||
const composerConfig = await loadComposerConfig(effectiveRoot);
|
||||
|
||||
const addImportEdge = (filePath: string, resolvedPath: string) => {
|
||||
const sourceId = generateId('File', filePath);
|
||||
|
|
@ -884,6 +970,16 @@ export const processImportsFromExtracted = async (
|
|||
}
|
||||
}
|
||||
|
||||
// PHP: handle namespace-based imports (use statements)
|
||||
if (language === SupportedLanguages.PHP) {
|
||||
const resolved = resolvePhpImport(rawImportPath, composerConfig, allFilePaths, normalizedFileList, allFileList, index);
|
||||
if (resolved) {
|
||||
resolveCache.set(cacheKey, resolved);
|
||||
addImportEdge(filePath, resolved);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Standard resolution (has its own internal cache)
|
||||
const resolvedPath = resolveImportPath(
|
||||
filePath,
|
||||
|
|
|
|||
|
|
@ -317,6 +317,85 @@ export const RUST_QUERIES = `
|
|||
(impl_item trait: (generic_type type: (type_identifier) @heritage.trait) type: (type_identifier) @heritage.class) @heritage
|
||||
`;
|
||||
|
||||
// PHP queries - works with tree-sitter-php (php_only grammar)
|
||||
export const PHP_QUERIES = `
|
||||
; ── Namespace ────────────────────────────────────────────────────────────────
|
||||
(namespace_definition
|
||||
name: (namespace_name) @name) @definition.namespace
|
||||
|
||||
; ── Classes ──────────────────────────────────────────────────────────────────
|
||||
(class_declaration
|
||||
name: (name) @name) @definition.class
|
||||
|
||||
; ── Interfaces ───────────────────────────────────────────────────────────────
|
||||
(interface_declaration
|
||||
name: (name) @name) @definition.interface
|
||||
|
||||
; ── Traits ───────────────────────────────────────────────────────────────────
|
||||
(trait_declaration
|
||||
name: (name) @name) @definition.trait
|
||||
|
||||
; ── Enums (PHP 8.1) ──────────────────────────────────────────────────────────
|
||||
(enum_declaration
|
||||
name: (name) @name) @definition.enum
|
||||
|
||||
; ── Top-level functions ───────────────────────────────────────────────────────
|
||||
(function_definition
|
||||
name: (name) @name) @definition.function
|
||||
|
||||
; ── Methods (including constructors) ─────────────────────────────────────────
|
||||
(method_declaration
|
||||
name: (name) @name) @definition.method
|
||||
|
||||
; ── Class properties (including Eloquent $fillable, $casts, etc.) ────────────
|
||||
(property_declaration
|
||||
(property_element
|
||||
(variable_name
|
||||
(name) @name))) @definition.property
|
||||
|
||||
; ── Imports: use statements ──────────────────────────────────────────────────
|
||||
; Simple: use App\\Models\\User;
|
||||
(namespace_use_declaration
|
||||
(namespace_use_clause
|
||||
(qualified_name) @import.source)) @import
|
||||
|
||||
; ── Function/method calls ────────────────────────────────────────────────────
|
||||
; Regular function call: foo()
|
||||
(function_call_expression
|
||||
function: (name) @call.name) @call
|
||||
|
||||
; Method call: $obj->method()
|
||||
(member_call_expression
|
||||
name: (name) @call.name) @call
|
||||
|
||||
; Nullsafe method call: $obj?->method()
|
||||
(nullsafe_member_call_expression
|
||||
name: (name) @call.name) @call
|
||||
|
||||
; Static call: Foo::bar() (php_only uses scoped_call_expression)
|
||||
(scoped_call_expression
|
||||
name: (name) @call.name) @call
|
||||
|
||||
; ── Heritage: extends ────────────────────────────────────────────────────────
|
||||
(class_declaration
|
||||
name: (name) @heritage.class
|
||||
(base_clause
|
||||
[(name) (qualified_name)] @heritage.extends)) @heritage
|
||||
|
||||
; ── Heritage: implements ─────────────────────────────────────────────────────
|
||||
(class_declaration
|
||||
name: (name) @heritage.class
|
||||
(class_interface_clause
|
||||
[(name) (qualified_name)] @heritage.implements)) @heritage.impl
|
||||
|
||||
; ── Heritage: use trait (must capture enclosing class name) ──────────────────
|
||||
(class_declaration
|
||||
name: (name) @heritage.class
|
||||
body: (declaration_list
|
||||
(use_declaration
|
||||
[(name) (qualified_name)] @heritage.trait))) @heritage
|
||||
`;
|
||||
|
||||
export const LANGUAGE_QUERIES: Record<SupportedLanguages, string> = {
|
||||
[SupportedLanguages.TypeScript]: TYPESCRIPT_QUERIES,
|
||||
[SupportedLanguages.JavaScript]: JAVASCRIPT_QUERIES,
|
||||
|
|
@ -327,5 +406,6 @@ export const LANGUAGE_QUERIES: Record<SupportedLanguages, string> = {
|
|||
[SupportedLanguages.CPlusPlus]: CPP_QUERIES,
|
||||
[SupportedLanguages.CSharp]: CSHARP_QUERIES,
|
||||
[SupportedLanguages.Rust]: RUST_QUERIES,
|
||||
[SupportedLanguages.PHP]: PHP_QUERIES,
|
||||
};
|
||||
|
||||
|
|
@ -31,6 +31,12 @@ export const getLanguageFromFilename = (filename: string): SupportedLanguages |
|
|||
if (filename.endsWith('.go')) return SupportedLanguages.Go;
|
||||
// Rust
|
||||
if (filename.endsWith('.rs')) return SupportedLanguages.Rust;
|
||||
// PHP (all common extensions)
|
||||
if (filename.endsWith('.php') || filename.endsWith('.phtml') ||
|
||||
filename.endsWith('.php3') || filename.endsWith('.php4') ||
|
||||
filename.endsWith('.php5') || filename.endsWith('.php8')) {
|
||||
return SupportedLanguages.PHP;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import CPP from 'tree-sitter-cpp';
|
|||
import CSharp from 'tree-sitter-c-sharp';
|
||||
import Go from 'tree-sitter-go';
|
||||
import Rust from 'tree-sitter-rust';
|
||||
import PHP from 'tree-sitter-php';
|
||||
import { SupportedLanguages } from '../../../config/supported-languages.js';
|
||||
import { LANGUAGE_QUERIES } from '../tree-sitter-queries.js';
|
||||
import { getLanguageFromFilename } from '../utils.js';
|
||||
|
|
@ -28,6 +29,7 @@ interface ParsedNode {
|
|||
endLine: number;
|
||||
language: string;
|
||||
isExported: boolean;
|
||||
description?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +102,7 @@ const languageMap: Record<string, any> = {
|
|||
[SupportedLanguages.CSharp]: CSharp,
|
||||
[SupportedLanguages.Go]: Go,
|
||||
[SupportedLanguages.Rust]: Rust,
|
||||
[SupportedLanguages.PHP]: PHP.php_only,
|
||||
};
|
||||
|
||||
const setLanguage = (language: SupportedLanguages, filePath: string): void => {
|
||||
|
|
@ -185,6 +188,24 @@ const isNodeExported = (node: any, name: string, language: string): boolean => {
|
|||
case 'cpp':
|
||||
return false;
|
||||
|
||||
case 'php':
|
||||
// Top-level classes/interfaces/traits are always accessible
|
||||
// Methods/properties are exported only if they have 'public' modifier
|
||||
while (current) {
|
||||
if (current.type === 'class_declaration' ||
|
||||
current.type === 'interface_declaration' ||
|
||||
current.type === 'trait_declaration' ||
|
||||
current.type === 'enum_declaration') {
|
||||
return true;
|
||||
}
|
||||
if (current.type === 'visibility_modifier') {
|
||||
return current.text === 'public';
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
// Top-level functions (no parent class) are globally accessible
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
|
@ -200,6 +221,7 @@ const FUNCTION_NODE_TYPES = new Set([
|
|||
'function_definition', 'async_function_declaration', 'async_arrow_function',
|
||||
'method_declaration', 'constructor_declaration',
|
||||
'local_function_statement', 'function_item', 'impl_item',
|
||||
'anonymous_function_creation_expression', // PHP anonymous functions
|
||||
]);
|
||||
|
||||
/** Walk up AST to find enclosing function, return its generateId or null for top-level */
|
||||
|
|
@ -297,6 +319,23 @@ const BUILT_INS = new Set([
|
|||
'mutex_lock', 'mutex_unlock', 'mutex_init',
|
||||
'kfree', 'kmalloc', 'kzalloc', 'kcalloc', 'krealloc', 'kvmalloc', 'kvfree',
|
||||
'get', 'put',
|
||||
// PHP built-ins
|
||||
'echo', 'isset', 'empty', 'unset', 'list', 'array', 'compact', 'extract',
|
||||
'count', 'strlen', 'strpos', 'strrpos', 'substr', 'strtolower', 'strtoupper', 'trim',
|
||||
'ltrim', 'rtrim', 'str_replace', 'str_contains', 'str_starts_with', 'str_ends_with',
|
||||
'sprintf', 'vsprintf', 'printf', 'number_format',
|
||||
'array_map', 'array_filter', 'array_reduce', 'array_push', 'array_pop', 'array_shift',
|
||||
'array_unshift', 'array_slice', 'array_splice', 'array_merge', 'array_keys', 'array_values',
|
||||
'array_key_exists', 'in_array', 'array_search', 'array_unique', 'usort', 'rsort',
|
||||
'json_encode', 'json_decode', 'serialize', 'unserialize',
|
||||
'intval', 'floatval', 'strval', 'boolval', 'is_null', 'is_string', 'is_int', 'is_array',
|
||||
'is_object', 'is_numeric', 'is_bool', 'is_float',
|
||||
'var_dump', 'print_r', 'var_export',
|
||||
'date', 'time', 'strtotime', 'mktime', 'microtime',
|
||||
'file_exists', 'file_get_contents', 'file_put_contents', 'is_file', 'is_dir',
|
||||
'preg_match', 'preg_match_all', 'preg_replace', 'preg_split',
|
||||
'header', 'session_start', 'session_destroy', 'ob_start', 'ob_end_clean', 'ob_get_clean',
|
||||
'dd', 'dump',
|
||||
]);
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -409,6 +448,109 @@ const processBatch = (files: ParseWorkerInput[], onProgress?: (filesProcessed: n
|
|||
return result;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// PHP Eloquent metadata extraction
|
||||
// ============================================================================
|
||||
|
||||
/** Eloquent model properties whose array values are worth indexing */
|
||||
const ELOQUENT_ARRAY_PROPS = new Set(['fillable', 'casts', 'hidden', 'guarded', 'with', 'appends']);
|
||||
|
||||
/** Eloquent relationship method names */
|
||||
const ELOQUENT_RELATIONS = new Set([
|
||||
'hasMany', 'hasOne', 'belongsTo', 'belongsToMany',
|
||||
'morphTo', 'morphMany', 'morphOne', 'morphToMany', 'morphedByMany',
|
||||
'hasManyThrough', 'hasOneThrough',
|
||||
]);
|
||||
|
||||
function findDescendant(node: any, type: string): any {
|
||||
if (node.type === type) return node;
|
||||
for (const child of (node.children ?? [])) {
|
||||
const found = findDescendant(child, type);
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractStringContent(node: any): string | null {
|
||||
if (!node) return null;
|
||||
const content = node.children?.find((c: any) => c.type === 'string_content');
|
||||
if (content) return content.text;
|
||||
if (node.type === 'string_content') return node.text;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a PHP property_declaration node, extract array values as a description string.
|
||||
* Returns null if not an Eloquent model property or no array values found.
|
||||
*/
|
||||
function extractPhpPropertyDescription(propName: string, propDeclNode: any): string | null {
|
||||
if (!ELOQUENT_ARRAY_PROPS.has(propName)) return null;
|
||||
|
||||
const arrayNode = findDescendant(propDeclNode, 'array_creation_expression');
|
||||
if (!arrayNode) return null;
|
||||
|
||||
const items: string[] = [];
|
||||
for (const child of (arrayNode.children ?? [])) {
|
||||
if (child.type !== 'array_element_initializer') continue;
|
||||
const children = child.children ?? [];
|
||||
const arrowIdx = children.findIndex((c: any) => c.type === '=>');
|
||||
if (arrowIdx !== -1) {
|
||||
// key => value pair (used in $casts)
|
||||
const key = extractStringContent(children[arrowIdx - 1]);
|
||||
const val = extractStringContent(children[arrowIdx + 1]);
|
||||
if (key && val) items.push(`${key}:${val}`);
|
||||
} else {
|
||||
// Simple value (used in $fillable, $hidden, etc.)
|
||||
const val = extractStringContent(children[0]);
|
||||
if (val) items.push(val);
|
||||
}
|
||||
}
|
||||
|
||||
return items.length > 0 ? items.join(', ') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a PHP method_declaration node, detect if it defines an Eloquent relationship.
|
||||
* Returns description like "hasMany(Post)" or null.
|
||||
*/
|
||||
function extractEloquentRelationDescription(methodNode: any): string | null {
|
||||
function findRelationCall(node: any): any {
|
||||
if (node.type === 'member_call_expression') {
|
||||
const children = node.children ?? [];
|
||||
const objectNode = children.find((c: any) => c.type === 'variable_name' && c.text === '$this');
|
||||
const nameNode = children.find((c: any) => c.type === 'name');
|
||||
if (objectNode && nameNode && ELOQUENT_RELATIONS.has(nameNode.text)) return node;
|
||||
}
|
||||
for (const child of (node.children ?? [])) {
|
||||
const found = findRelationCall(child);
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const callNode = findRelationCall(methodNode);
|
||||
if (!callNode) return null;
|
||||
|
||||
const relType = callNode.children?.find((c: any) => c.type === 'name')?.text;
|
||||
const argsNode = callNode.children?.find((c: any) => c.type === 'arguments');
|
||||
let targetModel: string | null = null;
|
||||
if (argsNode) {
|
||||
const firstArg = argsNode.children?.find((c: any) => c.type === 'argument');
|
||||
if (firstArg) {
|
||||
const classConstant = firstArg.children?.find((c: any) =>
|
||||
c.type === 'class_constant_access_expression'
|
||||
);
|
||||
if (classConstant) {
|
||||
targetModel = classConstant.children?.find((c: any) => c.type === 'name')?.text ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (relType && targetModel) return `${relType}(${targetModel})`;
|
||||
if (relType) return relType;
|
||||
return null;
|
||||
}
|
||||
|
||||
const processFileGroup = (
|
||||
files: ParseWorkerInput[],
|
||||
language: SupportedLanguages,
|
||||
|
|
@ -515,6 +657,15 @@ const processFileGroup = (
|
|||
const nodeName = nameNode.text;
|
||||
const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}`);
|
||||
|
||||
let description: string | undefined;
|
||||
if (language === SupportedLanguages.PHP) {
|
||||
if (nodeLabel === 'Property' && captureMap['definition.property']) {
|
||||
description = extractPhpPropertyDescription(nodeName, captureMap['definition.property']) ?? undefined;
|
||||
} else if (nodeLabel === 'Method' && captureMap['definition.method']) {
|
||||
description = extractEloquentRelationDescription(captureMap['definition.method']) ?? undefined;
|
||||
}
|
||||
}
|
||||
|
||||
result.nodes.push({
|
||||
id: nodeId,
|
||||
label: nodeLabel,
|
||||
|
|
@ -525,6 +676,7 @@ const processFileGroup = (
|
|||
endLine: nameNode.endPosition.row,
|
||||
language: language,
|
||||
isExported: isNodeExported(nameNode, nodeName, language),
|
||||
...(description !== undefined ? { description } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ export const streamAllCSVsToDisk = async (
|
|||
// Create writers for every node type up-front
|
||||
const fileWriter = new BufferedCSVWriter(path.join(csvDir, 'file.csv'), 'id,name,filePath,content');
|
||||
const folderWriter = new BufferedCSVWriter(path.join(csvDir, 'folder.csv'), 'id,name,filePath');
|
||||
const codeElementHeader = 'id,name,filePath,startLine,endLine,isExported,content';
|
||||
const codeElementHeader = 'id,name,filePath,startLine,endLine,isExported,content,description';
|
||||
const functionWriter = new BufferedCSVWriter(path.join(csvDir, 'function.csv'), codeElementHeader);
|
||||
const classWriter = new BufferedCSVWriter(path.join(csvDir, 'class.csv'), codeElementHeader);
|
||||
const interfaceWriter = new BufferedCSVWriter(path.join(csvDir, 'interface.csv'), codeElementHeader);
|
||||
|
|
@ -222,7 +222,7 @@ export const streamAllCSVsToDisk = async (
|
|||
const processWriter = new BufferedCSVWriter(path.join(csvDir, 'process.csv'), 'id,label,heuristicLabel,processType,stepCount,communities,entryPointId,terminalId');
|
||||
|
||||
// Multi-language node types share the same CSV shape (no isExported column)
|
||||
const multiLangHeader = 'id,name,filePath,startLine,endLine,content';
|
||||
const multiLangHeader = 'id,name,filePath,startLine,endLine,content,description';
|
||||
const MULTI_LANG_TYPES = ['Struct', 'Enum', 'Macro', 'Typedef', 'Union', 'Namespace', 'Trait', 'Impl',
|
||||
'TypeAlias', 'Const', 'Static', 'Property', 'Record', 'Delegate', 'Annotation', 'Constructor', 'Template', 'Module'] as const;
|
||||
const multiLangWriters = new Map<string, BufferedCSVWriter>();
|
||||
|
|
@ -305,6 +305,7 @@ export const streamAllCSVsToDisk = async (
|
|||
escapeCSVNumber(node.properties.endLine, -1),
|
||||
node.properties.isExported ? 'true' : 'false',
|
||||
escapeCSVField(content),
|
||||
escapeCSVField((node.properties as any).description || ''),
|
||||
].join(','));
|
||||
} else {
|
||||
// Multi-language node types (Struct, Impl, Trait, Macro, etc.)
|
||||
|
|
@ -318,6 +319,7 @@ export const streamAllCSVsToDisk = async (
|
|||
escapeCSVNumber(node.properties.startLine, -1),
|
||||
escapeCSVNumber(node.properties.endLine, -1),
|
||||
escapeCSVField(content),
|
||||
escapeCSVField((node.properties as any).description || ''),
|
||||
].join(','));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -277,10 +277,10 @@ const getCopyQuery = (table: NodeTableName, filePath: string): string => {
|
|||
}
|
||||
// TypeScript/JS code element tables have isExported; multi-language tables do not
|
||||
if (TABLES_WITH_EXPORTED.has(table)) {
|
||||
return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content) FROM "${filePath}" ${COPY_CSV_OPTS}`;
|
||||
return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content, description) FROM "${filePath}" ${COPY_CSV_OPTS}`;
|
||||
}
|
||||
// Multi-language tables (Struct, Impl, Trait, Macro, etc.)
|
||||
return `COPY ${t}(id, name, filePath, startLine, endLine, content) FROM "${filePath}" ${COPY_CSV_OPTS}`;
|
||||
return `COPY ${t}(id, name, filePath, startLine, endLine, content, description) FROM "${filePath}" ${COPY_CSV_OPTS}`;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -317,10 +317,12 @@ export const insertNodeToKuzu = async (
|
|||
} else if (label === 'Folder') {
|
||||
query = `CREATE (n:Folder {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}})`;
|
||||
} else if (TABLES_WITH_EXPORTED.has(label)) {
|
||||
query = `CREATE (n:${t} {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, isExported: ${!!properties.isExported}, content: ${escapeValue(properties.content || '')}})`;
|
||||
const descPart = properties.description ? `, description: ${escapeValue(properties.description)}` : '';
|
||||
query = `CREATE (n:${t} {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, isExported: ${!!properties.isExported}, content: ${escapeValue(properties.content || '')}${descPart}})`;
|
||||
} else {
|
||||
// Multi-language tables (Struct, Impl, Trait, Macro, etc.) — no isExported
|
||||
query = `CREATE (n:${t} {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, content: ${escapeValue(properties.content || '')}})`;
|
||||
const descPart = properties.description ? `, description: ${escapeValue(properties.description)}` : '';
|
||||
query = `CREATE (n:${t} {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, content: ${escapeValue(properties.content || '')}${descPart}})`;
|
||||
}
|
||||
|
||||
// Use per-query connection if dbPath provided (avoids lock conflicts)
|
||||
|
|
@ -386,9 +388,11 @@ export const batchInsertNodesToKuzu = async (
|
|||
} else if (label === 'Folder') {
|
||||
query = `MERGE (n:Folder {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}`;
|
||||
} else if (TABLES_WITH_EXPORTED.has(label)) {
|
||||
query = `MERGE (n:${t} {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.isExported = ${!!properties.isExported}, n.content = ${escapeValue(properties.content || '')}`;
|
||||
const descPart = properties.description ? `, n.description = ${escapeValue(properties.description)}` : '';
|
||||
query = `MERGE (n:${t} {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.isExported = ${!!properties.isExported}, n.content = ${escapeValue(properties.content || '')}${descPart}`;
|
||||
} else {
|
||||
query = `MERGE (n:${t} {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.content = ${escapeValue(properties.content || '')}`;
|
||||
const descPart = properties.description ? `, n.description = ${escapeValue(properties.description)}` : '';
|
||||
query = `MERGE (n:${t} {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.content = ${escapeValue(properties.content || '')}${descPart}`;
|
||||
}
|
||||
|
||||
await tempConn.query(query);
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ CREATE NODE TABLE Function (
|
|||
endLine INT64,
|
||||
isExported BOOLEAN,
|
||||
content STRING,
|
||||
description STRING,
|
||||
PRIMARY KEY (id)
|
||||
)`;
|
||||
|
||||
|
|
@ -76,6 +77,7 @@ CREATE NODE TABLE Class (
|
|||
endLine INT64,
|
||||
isExported BOOLEAN,
|
||||
content STRING,
|
||||
description STRING,
|
||||
PRIMARY KEY (id)
|
||||
)`;
|
||||
|
||||
|
|
@ -88,6 +90,7 @@ CREATE NODE TABLE Interface (
|
|||
endLine INT64,
|
||||
isExported BOOLEAN,
|
||||
content STRING,
|
||||
description STRING,
|
||||
PRIMARY KEY (id)
|
||||
)`;
|
||||
|
||||
|
|
@ -100,6 +103,7 @@ CREATE NODE TABLE Method (
|
|||
endLine INT64,
|
||||
isExported BOOLEAN,
|
||||
content STRING,
|
||||
description STRING,
|
||||
PRIMARY KEY (id)
|
||||
)`;
|
||||
|
||||
|
|
@ -112,6 +116,7 @@ CREATE NODE TABLE CodeElement (
|
|||
endLine INT64,
|
||||
isExported BOOLEAN,
|
||||
content STRING,
|
||||
description STRING,
|
||||
PRIMARY KEY (id)
|
||||
)`;
|
||||
|
||||
|
|
@ -154,6 +159,7 @@ CREATE NODE TABLE Process (
|
|||
// ============================================================================
|
||||
|
||||
// Generic code element with startLine/endLine for C, C++, Rust, Go, Java, C#
|
||||
// description: optional metadata (e.g. Eloquent $fillable fields, relationship targets)
|
||||
const CODE_ELEMENT_BASE = (name: string) => `
|
||||
CREATE NODE TABLE \`${name}\` (
|
||||
id STRING,
|
||||
|
|
@ -162,6 +168,7 @@ CREATE NODE TABLE \`${name}\` (
|
|||
startLine INT64,
|
||||
endLine INT64,
|
||||
content STRING,
|
||||
description STRING,
|
||||
PRIMARY KEY (id)
|
||||
)`;
|
||||
|
||||
|
|
@ -266,6 +273,7 @@ CREATE REL TABLE ${REL_TABLE_NAME} (
|
|||
FROM Method TO \`Impl\`,
|
||||
FROM Method TO Interface,
|
||||
FROM Method TO \`Constructor\`,
|
||||
FROM Method TO \`Property\`,
|
||||
FROM \`Template\` TO \`Template\`,
|
||||
FROM \`Template\` TO Function,
|
||||
FROM \`Template\` TO Method,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import CPP from 'tree-sitter-cpp';
|
|||
import CSharp from 'tree-sitter-c-sharp';
|
||||
import Go from 'tree-sitter-go';
|
||||
import Rust from 'tree-sitter-rust';
|
||||
import PHP from 'tree-sitter-php';
|
||||
import { SupportedLanguages } from '../../config/supported-languages.js';
|
||||
|
||||
let parser: Parser | null = null;
|
||||
|
|
@ -23,6 +24,7 @@ const languageMap: Record<string, any> = {
|
|||
[SupportedLanguages.CSharp]: CSharp,
|
||||
[SupportedLanguages.Go]: Go,
|
||||
[SupportedLanguages.Rust]: Rust,
|
||||
[SupportedLanguages.PHP]: PHP.php_only,
|
||||
};
|
||||
|
||||
export const loadParser = async (): Promise<Parser> => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue