Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions __tests__/graph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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');
});
});

Expand Down
3 changes: 3 additions & 0 deletions src/bin/codegraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down
63 changes: 44 additions & 19 deletions src/graph/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<string>();
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);
}
}
}

Expand All @@ -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<string>();

// Check file-level incoming import edges (file:X imports file:Y)
Expand All @@ -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);
}
}
}
Expand Down