Skip to content
10 changes: 7 additions & 3 deletions crates/codegraph-core/src/edge_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,13 +339,17 @@ pub fn build_call_edges(
for cls in &file_input.classes {
let source_row = nodes_by_name_and_file
.get(&(cls.name.as_str(), rel_path.as_str()))
.and_then(|v| v.iter().find(|n| n.kind == "class"));
.and_then(|v| v.iter().find(|n| {
n.kind == "class" || n.kind == "struct" || n.kind == "record" || n.kind == "enum"
}));

if let Some(source) = source_row {
if let Some(ref extends_name) = cls.extends {
let targets = nodes_by_name
.get(extends_name.as_str())
.map(|v| v.iter().filter(|n| n.kind == "class").collect::<Vec<_>>())
.map(|v| v.iter().filter(|n| {
n.kind == "class" || n.kind == "struct" || n.kind == "trait" || n.kind == "record"
}).collect::<Vec<_>>())
.unwrap_or_default();
for t in targets {
edges.push(ComputedEdge {
Expand All @@ -362,7 +366,7 @@ pub fn build_call_edges(
.get(implements_name.as_str())
.map(|v| {
v.iter()
.filter(|n| n.kind == "interface" || n.kind == "class")
.filter(|n| n.kind == "interface" || n.kind == "class" || n.kind == "trait")
.collect::<Vec<_>>()
})
.unwrap_or_default();
Expand Down
81 changes: 59 additions & 22 deletions src/domain/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -395,12 +395,23 @@ async function backfillTypeMap(filePath, source) {
}
const parsers = await createParsers();
const extracted = wasmExtractSymbols(parsers, filePath, code);
if (!extracted?.symbols?.typeMap) return { typeMap: [], backfilled: false };
const tm = extracted.symbols.typeMap;
return {
typeMap: tm instanceof Map ? tm : new Map(tm.map((e) => [e.name, e.typeName])),
backfilled: true,
};
try {
if (!extracted?.symbols?.typeMap) {
return { typeMap: [], backfilled: false };
}
const tm = extracted.symbols.typeMap;
return {
typeMap: tm instanceof Map ? tm : new Map(tm.map((e) => [e.name, e.typeName])),
backfilled: true,
};
} finally {
// Free the WASM tree to prevent memory accumulation across repeated builds
if (extracted?.tree && typeof extracted.tree.delete === 'function') {
try {
extracted.tree.delete();
} catch {}
}
}
}

/**
Expand Down Expand Up @@ -441,7 +452,13 @@ export async function parseFileAuto(filePath, source, opts = {}) {
const result = native.parseFile(filePath, source, !!opts.dataflow, opts.ast !== false);
if (!result) return null;
const patched = patchNativeResult(result);
if (!patched.typeMap || patched.typeMap.length === 0) {
// Only backfill typeMap for TS/TSX — JS files have no type annotations,
// and the native engine already handles `new Expr()` patterns.
const TS_BACKFILL_EXTS = new Set(['.ts', '.tsx']);
if (
(!patched.typeMap || patched.typeMap.length === 0) &&
TS_BACKFILL_EXTS.has(path.extname(filePath))
) {
const { typeMap, backfilled } = await backfillTypeMap(filePath, source);
patched.typeMap = typeMap;
if (backfilled) patched._typeMapBackfilled = true;
Expand Down Expand Up @@ -486,21 +503,35 @@ export async function parseFilesAuto(filePaths, rootDir, opts = {}) {
}
// Backfill typeMap via WASM for native binaries that predate the type-map feature
if (needsTypeMap.length > 0) {
const parsers = await createParsers();
for (const { filePath, relPath } of needsTypeMap) {
try {
const code = fs.readFileSync(filePath, 'utf-8');
const extracted = wasmExtractSymbols(parsers, filePath, code);
if (extracted?.symbols?.typeMap) {
const symbols = result.get(relPath);
symbols.typeMap =
extracted.symbols.typeMap instanceof Map
? extracted.symbols.typeMap
: new Map(extracted.symbols.typeMap.map((e) => [e.name, e.typeName]));
symbols._typeMapBackfilled = true;
// Only backfill for languages where WASM extraction can produce typeMap
// (TS/TSX have type annotations; JS only has `new Expr()` which native already handles)
const TS_EXTS = new Set(['.ts', '.tsx']);
const tsFiles = needsTypeMap.filter(({ filePath }) => TS_EXTS.has(path.extname(filePath)));
if (tsFiles.length > 0) {
const parsers = await createParsers();
for (const { filePath, relPath } of tsFiles) {
let extracted;
try {
const code = fs.readFileSync(filePath, 'utf-8');
extracted = wasmExtractSymbols(parsers, filePath, code);
if (extracted?.symbols?.typeMap) {
const symbols = result.get(relPath);
symbols.typeMap =
extracted.symbols.typeMap instanceof Map
? extracted.symbols.typeMap
: new Map(extracted.symbols.typeMap.map((e) => [e.name, e.typeName]));
symbols._typeMapBackfilled = true;
}
} catch {
/* skip — typeMap is a best-effort backfill */
} finally {
// Free the WASM tree to prevent memory accumulation across repeated builds
if (extracted?.tree && typeof extracted.tree.delete === 'function') {
try {
extracted.tree.delete();
} catch {}
}
}
} catch {
/* skip — typeMap is a best-effort backfill */
}
}
}
Expand Down Expand Up @@ -578,7 +609,13 @@ export async function parseFileIncremental(cache, filePath, source, opts = {}) {
const result = cache.parseFile(filePath, source);
if (!result) return null;
const patched = patchNativeResult(result);
if (!patched.typeMap || patched.typeMap.length === 0) {
// Only backfill typeMap for TS/TSX — JS files have no type annotations,
// and the native engine already handles `new Expr()` patterns.
const TS_BACKFILL_EXTS = new Set(['.ts', '.tsx']);
if (
(!patched.typeMap || patched.typeMap.length === 0) &&
TS_BACKFILL_EXTS.has(path.extname(filePath))
) {
const { typeMap, backfilled } = await backfillTypeMap(filePath, source);
patched.typeMap = typeMap;
if (backfilled) patched._typeMapBackfilled = true;
Expand Down
Loading