mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-08 03:08:13 +00:00
impact tool test
This commit is contained in:
parent
c2ce19f7a4
commit
32c3544d0d
4 changed files with 182 additions and 17 deletions
|
|
@ -11,16 +11,18 @@ export type ImportMap = Map<string, Set<string>>;
|
|||
|
||||
export const createImportMap = (): ImportMap => new Map();
|
||||
|
||||
// Helper: Resolve relative paths (e.g. "../utils" -> "src/lib/utils.ts")
|
||||
// Helper: Resolve import paths (relative and absolute/package-style)
|
||||
const resolveImportPath = (
|
||||
currentFile: string,
|
||||
importPath: string,
|
||||
allFiles: Set<string>
|
||||
allFiles: Set<string>,
|
||||
allFileList: string[],
|
||||
resolveCache: Map<string, string | null>
|
||||
): string | null => {
|
||||
// 1. Handle non-relative imports (libraries like 'react')
|
||||
if (!importPath.startsWith('.')) return null; // We skip node_modules for now
|
||||
const cacheKey = `${currentFile}::${importPath}`;
|
||||
if (resolveCache.has(cacheKey)) return resolveCache.get(cacheKey) ?? null;
|
||||
|
||||
// 2. Resolve '..' and '.'
|
||||
// 1. Resolve '..' and '.' for relative imports
|
||||
const currentDir = currentFile.split('/').slice(0, -1);
|
||||
const parts = importPath.split('/');
|
||||
|
||||
|
|
@ -35,7 +37,7 @@ const resolveImportPath = (
|
|||
|
||||
const basePath = currentDir.join('/');
|
||||
|
||||
// 3. Try extensions for all supported languages
|
||||
// 2. Try extensions for all supported languages
|
||||
const extensions = [
|
||||
'',
|
||||
// TypeScript/JavaScript
|
||||
|
|
@ -54,11 +56,56 @@ const resolveImportPath = (
|
|||
'.rs', '/mod.rs'
|
||||
];
|
||||
|
||||
for (const ext of extensions) {
|
||||
const candidate = basePath + ext;
|
||||
if (allFiles.has(candidate)) return candidate;
|
||||
if (importPath.startsWith('.')) {
|
||||
for (const ext of extensions) {
|
||||
const candidate = basePath + ext;
|
||||
if (allFiles.has(candidate)) {
|
||||
resolveCache.set(cacheKey, candidate);
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
resolveCache.set(cacheKey, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. Handle absolute/package imports (Java, Go, Python, etc.)
|
||||
if (importPath.endsWith('.*')) {
|
||||
resolveCache.set(cacheKey, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
const pathLike = importPath.includes('/')
|
||||
? importPath
|
||||
: importPath.replace(/\./g, '/');
|
||||
const pathParts = pathLike.split('/').filter(Boolean);
|
||||
|
||||
// Normalize all file paths to forward slashes for matching
|
||||
const normalizedFileList = allFileList.map(p => p.replace(/\\/g, '/'));
|
||||
|
||||
for (let i = 0; i < pathParts.length; i++) {
|
||||
const suffix = pathParts.slice(i).join('/');
|
||||
for (const ext of extensions) {
|
||||
const suffixWithExt = suffix + ext;
|
||||
// Require path separator before match to avoid false positives like "View.java" matching "RootView.java"
|
||||
const suffixPattern = '/' + suffixWithExt;
|
||||
const matchIdx = normalizedFileList.findIndex(filePath =>
|
||||
filePath.endsWith(suffixPattern) || filePath.toLowerCase().endsWith(suffixPattern.toLowerCase())
|
||||
);
|
||||
if (matchIdx !== -1) {
|
||||
const match = allFileList[matchIdx];
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`📦 Import resolved: ${importPath} → ${match}`);
|
||||
}
|
||||
resolveCache.set(cacheKey, match);
|
||||
return match;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.env.DEV && pathParts.length > 0) {
|
||||
console.log(`⚠️ Import unresolved: ${importPath} (tried suffixes from ${pathParts.join('/')})`);
|
||||
}
|
||||
resolveCache.set(cacheKey, null);
|
||||
return null;
|
||||
};
|
||||
|
||||
|
|
@ -72,6 +119,12 @@ export const processImports = async (
|
|||
// Create a Set of all file paths for fast lookup during resolution
|
||||
const allFilePaths = new Set(files.map(f => f.path));
|
||||
const parser = await loadParser();
|
||||
const resolveCache = new Map<string, string | null>();
|
||||
const allFileList = files.map(f => f.path);
|
||||
|
||||
// Track import statistics
|
||||
let totalImportsFound = 0;
|
||||
let totalImportsResolved = 0;
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
|
|
@ -102,6 +155,13 @@ export const processImports = async (
|
|||
try {
|
||||
query = parser.getLanguage().query(queryStr);
|
||||
matches = query.matches(tree.rootNode);
|
||||
|
||||
if (import.meta.env.DEV && language === 'java') {
|
||||
const importMatches = matches.filter(m => m.captures.some(c => c.name === 'import'));
|
||||
if (importMatches.length > 0) {
|
||||
console.log(`📋 Java file ${file.path}: ${importMatches.length} import matches found`);
|
||||
}
|
||||
}
|
||||
} catch (queryError: any) {
|
||||
// Detailed debug logging for query failures
|
||||
console.group(`🔴 Query Error: ${file.path}`);
|
||||
|
|
@ -123,13 +183,29 @@ export const processImports = async (
|
|||
|
||||
if (captureMap['import']) {
|
||||
const sourceNode = captureMap['import.source'];
|
||||
if (!sourceNode) return;
|
||||
if (!sourceNode) {
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`⚠️ Import captured but no source node in ${file.path}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean path (remove quotes)
|
||||
const rawImportPath = sourceNode.text.replace(/['"]/g, '');
|
||||
totalImportsFound++;
|
||||
|
||||
if (import.meta.env.DEV && language === 'java') {
|
||||
console.log(`🔍 Java import found in ${file.path}: ${rawImportPath}`);
|
||||
}
|
||||
|
||||
// Resolve to actual file in the system
|
||||
const resolvedPath = resolveImportPath(file.path, rawImportPath, allFilePaths);
|
||||
const resolvedPath = resolveImportPath(
|
||||
file.path,
|
||||
rawImportPath,
|
||||
allFilePaths,
|
||||
allFileList,
|
||||
resolveCache
|
||||
);
|
||||
|
||||
if (resolvedPath) {
|
||||
// A. Update Graph (File -> IMPORTS -> File)
|
||||
|
|
@ -137,6 +213,8 @@ export const processImports = async (
|
|||
const targetId = generateId('File', resolvedPath);
|
||||
const relId = generateId('IMPORTS', `${file.path}->${resolvedPath}`);
|
||||
|
||||
totalImportsResolved++;
|
||||
|
||||
graph.addRelationship({
|
||||
id: relId,
|
||||
sourceId,
|
||||
|
|
@ -161,6 +239,10 @@ export const processImports = async (
|
|||
tree.delete();
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`📊 Import processing complete: ${totalImportsResolved}/${totalImportsFound} imports resolved to graph edges`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -123,6 +123,16 @@ export const runPipelineFromFiles = async (
|
|||
stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount },
|
||||
});
|
||||
});
|
||||
|
||||
// Debug: Count IMPORTS relationships
|
||||
if (import.meta.env.DEV) {
|
||||
const importsCount = graph.relationships.filter(r => r.type === 'IMPORTS').length;
|
||||
console.log(`📊 Pipeline: After import phase, graph has ${importsCount} IMPORTS relationships (total: ${graph.relationshipCount})`);
|
||||
if (importsCount > 0) {
|
||||
const sample = graph.relationships.filter(r => r.type === 'IMPORTS').slice(0, 3);
|
||||
sample.forEach(r => console.log(` Sample IMPORTS: ${r.sourceId} → ${r.targetId}`));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Phase 5: Calls (82-98%)
|
||||
|
|
|
|||
|
|
@ -160,8 +160,8 @@ export const JAVA_QUERIES = `
|
|||
(method_declaration name: (identifier) @name) @definition.method
|
||||
(constructor_declaration name: (identifier) @name) @definition.constructor
|
||||
|
||||
; Imports
|
||||
(import_declaration (scoped_identifier) @import.source) @import
|
||||
; Imports - capture any import declaration child as source
|
||||
(import_declaration (_) @import.source) @import
|
||||
|
||||
; Calls
|
||||
(method_invocation name: (identifier) @call.name) @call
|
||||
|
|
|
|||
|
|
@ -926,12 +926,40 @@ MATCH (n:Function {id: emb.nodeId}) RETURN n`,
|
|||
return `Could not find "${target}" in the codebase. Try using the search tool first to find the exact name.`;
|
||||
}
|
||||
|
||||
// Use the first match
|
||||
const targetNode = targetResults[0];
|
||||
// Handle multiple matches - require disambiguation
|
||||
const allPaths = targetResults.map((r: any) => Array.isArray(r) ? r[2] : r.filePath).filter(Boolean);
|
||||
|
||||
// If multiple matches and target doesn't look like a specific path, ask for clarification
|
||||
if (targetResults.length > 1 && !target.includes('/')) {
|
||||
return `⚠️ AMBIGUOUS TARGET: Multiple files named "${target}" found:\n\n${allPaths.map((p: string, i: number) => `${i + 1}. ${p}`).join('\n')}\n\nPlease specify which file you mean by using a more specific path, e.g.:\n- impact("${allPaths[0].split('/').slice(-3).join('/')}")\n- impact("${allPaths[1]?.split('/').slice(-3).join('/') || allPaths[0]}")`;
|
||||
}
|
||||
|
||||
// If target contains a path, try to find matching file
|
||||
let targetNode = targetResults[0];
|
||||
if (target.includes('/') && targetResults.length > 1) {
|
||||
const exactMatch = targetResults.find((r: any) => {
|
||||
const path = Array.isArray(r) ? r[2] : r.filePath;
|
||||
return path && path.toLowerCase().includes(target.toLowerCase());
|
||||
});
|
||||
if (exactMatch) {
|
||||
targetNode = exactMatch;
|
||||
} else {
|
||||
// Still ambiguous even with path
|
||||
return `⚠️ AMBIGUOUS TARGET: Could not uniquely match "${target}". Found:\n\n${allPaths.map((p: string, i: number) => `${i + 1}. ${p}`).join('\n')}\n\nPlease use a more specific path.`;
|
||||
}
|
||||
}
|
||||
|
||||
const targetId = Array.isArray(targetNode) ? targetNode[0] : targetNode.id;
|
||||
const targetType = Array.isArray(targetNode) ? targetNode[1] : targetNode.nodeType;
|
||||
const targetFilePath = Array.isArray(targetNode) ? targetNode[2] : targetNode.filePath;
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`🎯 Impact: Found target "${target}" → id=${targetId}, type=${targetType}, filePath=${targetFilePath}`);
|
||||
}
|
||||
|
||||
// No more multipleMatchWarning needed - we either disambiguated or returned early
|
||||
const multipleMatchWarning = '';
|
||||
|
||||
// For File targets, find what calls code INSIDE the file (by filePath)
|
||||
// For code elements (Function, Class, etc.), use the direct id
|
||||
const isFileTarget = targetType === 'File';
|
||||
|
|
@ -1016,7 +1044,18 @@ MATCH (n:Function {id: emb.nodeId}) RETURN n`,
|
|||
r.reason AS reason
|
||||
LIMIT 300
|
||||
`;
|
||||
depthQueries.push(executeQuery(d1Query).catch(err => {
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`🔍 Impact d=1 query:\n${d1Query}`);
|
||||
}
|
||||
depthQueries.push(executeQuery(d1Query).then(results => {
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`📊 Impact d=1 results: ${results.length} rows`);
|
||||
if (results.length > 0) {
|
||||
console.log(' Sample:', results.slice(0, 3));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}).catch(err => {
|
||||
if (import.meta.env.DEV) console.warn('Impact d=1 query failed:', err);
|
||||
return [];
|
||||
}));
|
||||
|
|
@ -1174,7 +1213,38 @@ MATCH (n:Function {id: emb.nodeId}) RETURN n`,
|
|||
const totalAffected = allNodeIds.length;
|
||||
|
||||
if (totalAffected === 0) {
|
||||
return `No ${direction} dependencies found for "${target}" (types: ${activeRelTypes.join(', ')}). This code appears to be ${direction === 'upstream' ? 'unused (not called by anything)' : 'self-contained (no outgoing dependencies)'}.`;
|
||||
if (isFileTarget) {
|
||||
const escapeRegex = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const targetFileName = (targetFilePath || target).split('/').pop() || target;
|
||||
const baseName = targetFileName.replace(/\.[^/.]+$/, '');
|
||||
const refRegex = new RegExp(`\\b${escapeRegex(baseName)}\\b`, 'g');
|
||||
const hints: Array<{ file: string; line: number; content: string }> = [];
|
||||
const hintLimit = 15;
|
||||
|
||||
for (const [filePath, content] of fileContents.entries()) {
|
||||
if (filePath === targetFilePath) continue;
|
||||
const lines = content.split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (refRegex.test(lines[i])) {
|
||||
hints.push({
|
||||
file: filePath,
|
||||
line: i + 1,
|
||||
content: lines[i].trim().slice(0, 150),
|
||||
});
|
||||
if (hints.length >= hintLimit) break;
|
||||
}
|
||||
refRegex.lastIndex = 0;
|
||||
}
|
||||
if (hints.length >= hintLimit) break;
|
||||
}
|
||||
|
||||
if (hints.length > 0) {
|
||||
const formatted = hints.map(h => `${h.file}:${h.line}: ${h.content}`).join('\n');
|
||||
return `No ${direction} dependencies found for "${target}" (types: ${activeRelTypes.join(', ')}), but textual references were detected (graph may be incomplete):\n\n${formatted}${multipleMatchWarning}`;
|
||||
}
|
||||
}
|
||||
|
||||
return `No ${direction} dependencies found for "${target}" (types: ${activeRelTypes.join(', ')}). This code appears to be ${direction === 'upstream' ? 'unused (not called by anything)' : 'self-contained (no outgoing dependencies)'}.${multipleMatchWarning}`;
|
||||
}
|
||||
|
||||
const depth1 = byDepth.get(1) || [];
|
||||
|
|
@ -1361,6 +1431,9 @@ MATCH (n:Function {id: emb.nodeId}) RETURN n`,
|
|||
// Compact footer
|
||||
lines.push(`✅ GRAPH ANALYSIS COMPLETE (trusted)`);
|
||||
lines.push(`⚠️ Optional: grep("${target}") for dynamic patterns`);
|
||||
if (multipleMatchWarning) {
|
||||
lines.push(multipleMatchWarning);
|
||||
}
|
||||
lines.push(``);
|
||||
|
||||
return lines.join('\n');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue