diff --git a/__tests__/graph.test.ts b/__tests__/graph.test.ts index 7c771af0..c518078b 100644 --- a/__tests__/graph.test.ts +++ b/__tests__/graph.test.ts @@ -113,6 +113,17 @@ export { main }; ` ); + fs.writeFileSync( + path.join(srcDir, 'utils.test.ts'), + ` +import { processValue } from './utils'; + +export function testProcessValue(): boolean { + return processValue(2) === 2; +} +` + ); + // Initialize and index cg = CodeGraph.initSync(testDir, { config: { @@ -379,12 +390,23 @@ export { main }; const deps = cg.getFileDependencies('src/main.ts'); expect(Array.isArray(deps)).toBe(true); + expect(deps).toContain('src/derived.ts'); + expect(deps).toContain('src/utils.ts'); }); it('should get file dependents', () => { const dependents = cg.getFileDependents('src/utils.ts'); expect(Array.isArray(dependents)).toBe(true); + expect(dependents).toContain('src/main.ts'); + expect(dependents).toContain('src/utils.test.ts'); + }); + + it('should normalize Windows-style file paths for dependents', () => { + const dependents = cg.getFileDependents('src\\utils.ts'); + + expect(dependents).toContain('src/main.ts'); + expect(dependents).toContain('src/utils.test.ts'); }); }); diff --git a/src/bin/codegraph.ts b/src/bin/codegraph.ts index 6bc63b3f..fd3d92a2 100644 --- a/src/bin/codegraph.ts +++ b/src/bin/codegraph.ts @@ -28,6 +28,7 @@ import * as fs from 'fs'; import { getCodeGraphDir, isInitialized } from '../directory'; import { createShimmerProgress } from '../ui/shimmer-progress'; import { getGlyphs } from '../ui/glyphs'; +import { normalizePath } from '../utils'; import { buildNode25BlockBanner, buildNodeTooOldBanner, MIN_NODE_MAJOR } from './node-version-check'; import { relaunchWithWasmRuntimeFlagsIfNeeded } from '../extraction/wasm-runtime-flags'; @@ -1505,6 +1506,8 @@ program changedFiles.push(...stdinFiles); } + changedFiles = changedFiles.map(normalizePath); + if (changedFiles.length === 0) { if (!options.quiet) info('No files provided. Use file arguments or --stdin.'); process.exit(0); diff --git a/src/graph/queries.ts b/src/graph/queries.ts index c39e2e32..da553f3a 100644 --- a/src/graph/queries.ts +++ b/src/graph/queries.ts @@ -7,6 +7,18 @@ import { Node, Edge, Context, Subgraph, EdgeKind } from '../types'; import { QueryBuilder } from '../db/queries'; import { GraphTraverser } from './traversal'; +import { normalizePath } from '../utils'; + +const DEPENDENCY_EDGE_KINDS: EdgeKind[] = [ + 'imports', + 'calls', + 'references', + 'extends', + 'implements', + 'type_of', + 'returns', + 'instantiates', +]; /** * Graph query manager for complex queries @@ -110,26 +122,39 @@ export class GraphQueryManager { /** * Get dependencies of a file * - * Returns all files that this file imports from. + * Returns all files that this file imports from or references via resolved symbols. * * @param filePath - Path to the file * @returns Array of file paths this file depends on */ getFileDependencies(filePath: string): string[] { - const nodes = this.queries.getNodesByFile(filePath); + const normalizedFilePath = normalizePath(filePath); + const nodes = this.queries.getNodesByFile(normalizedFilePath); const fileNode = nodes.find((n) => n.kind === 'file'); - if (!fileNode) { + if (!fileNode && nodes.length === 0) { return []; } const dependencies = new Set(); - const importEdges = this.queries.getOutgoingEdges(fileNode.id, ['imports']); - for (const edge of importEdges) { - const targetNode = this.queries.getNodeById(edge.target); - if (targetNode && targetNode.filePath !== filePath) { - dependencies.add(targetNode.filePath); + if (fileNode) { + const importEdges = this.queries.getOutgoingEdges(fileNode.id, ['imports']); + for (const edge of importEdges) { + const targetNode = this.queries.getNodeById(edge.target); + if (targetNode && targetNode.filePath !== normalizedFilePath) { + dependencies.add(targetNode.filePath); + } + } + } + + for (const node of nodes) { + const outgoingEdges = this.queries.getOutgoingEdges(node.id, DEPENDENCY_EDGE_KINDS); + for (const edge of outgoingEdges) { + const targetNode = this.queries.getNodeById(edge.target); + if (targetNode && targetNode.filePath !== normalizedFilePath) { + dependencies.add(targetNode.filePath); + } } } @@ -139,13 +164,14 @@ export class GraphQueryManager { /** * Get dependents of a file * - * Returns all files that import from this file. + * Returns all files that import from this file or reference its resolved symbols. * * @param filePath - Path to the file * @returns Array of file paths that depend on this file */ getFileDependents(filePath: string): string[] { - const nodes = this.queries.getNodesByFile(filePath); + const normalizedFilePath = normalizePath(filePath); + const nodes = this.queries.getNodesByFile(normalizedFilePath); const dependents = new Set(); // Check file-level incoming import edges (file:X imports file:Y) @@ -154,21 +180,20 @@ export class GraphQueryManager { const incomingFileEdges = this.queries.getIncomingEdges(fileNode.id, ['imports']); for (const edge of incomingFileEdges) { const sourceNode = this.queries.getNodeById(edge.source); - if (sourceNode && sourceNode.filePath !== filePath) { + if (sourceNode && sourceNode.filePath !== normalizedFilePath) { dependents.add(sourceNode.filePath); } } } - // Also check node-level imports of exported symbols + // Also check node-level dependents of symbols in this file. Some language + // extractors resolve imports as calls/references rather than file imports. for (const node of nodes) { - if (node.isExported) { - const incomingEdges = this.queries.getIncomingEdges(node.id, ['imports']); - for (const edge of incomingEdges) { - const sourceNode = this.queries.getNodeById(edge.source); - if (sourceNode && sourceNode.filePath !== filePath) { - dependents.add(sourceNode.filePath); - } + const incomingEdges = this.queries.getIncomingEdges(node.id, DEPENDENCY_EDGE_KINDS); + for (const edge of incomingEdges) { + const sourceNode = this.queries.getNodeById(edge.source); + if (sourceNode && sourceNode.filePath !== normalizedFilePath) { + dependents.add(sourceNode.filePath); } } }