Version: reproduced on 0.9.40 (and 0.9.17)
A function declaration nested inside another function body produces no node. Only top-level declarations are emitted. Any call made from inside such a function then has a source id that no node carries, so the edge is discarded at build time and counts as dangling.
Repro
Panel.tsx
function doThing() {}
export function Panel() {
function handleClick() {
doThing()
}
return <button onClick={handleClick} />
}
from pathlib import Path
from graphify.extract import extract
r = extract([Path('Panel.tsx')], cache_root=Path('.'))
print(sorted(n.get('label','') for n in r['nodes']))
Actual
['Panel()', 'Panel.tsx', 'doThing()']
Expected — a node for handleClick(), contained by Panel(), with a calls edge to doThing().
Why it matters
React components keep their event handlers as nested function declarations, so this removes the entire intra-component call graph. In one real component (ProductOutputPanel.tsx, 724 lines) none of its five handlers — handleRegenDe, handleRegenEn, handleRegen, handleContentSlotApply, handleSave — is emitted; the file yields 16 nodes and the only function-like ones are two top-level hooks.
The consequence is not just missing nodes. The extraction still emits the edge
…productoutputpanel_handlecontentslotapply --calls--> …field_helpers_splicecontentslotintohtml
whose source id matches no node, so it is silently dropped and reported as a dangling endpoint. The relationship survives only at file granularity, and "which handler calls this helper" is unanswerable.
Checked for duplicates
#1740 covers calls inside anonymous arrow callbacks (addEventListener, forEach); #2575 covers await import() inside a nested function; #2040 covers nested class containment; #1322 covers function symbols assigned via this.X = / exports.X = / class arrow fields. This one is a plain named function declaration nested in a function body, where the symbol itself is never created.
Version: reproduced on 0.9.40 (and 0.9.17)
A
functiondeclaration nested inside another function body produces no node. Only top-level declarations are emitted. Any call made from inside such a function then has a source id that no node carries, so the edge is discarded at build time and counts as dangling.Repro
Panel.tsxActual
Expected — a node for
handleClick(), contained byPanel(), with acallsedge todoThing().Why it matters
React components keep their event handlers as nested function declarations, so this removes the entire intra-component call graph. In one real component (
ProductOutputPanel.tsx, 724 lines) none of its five handlers —handleRegenDe,handleRegenEn,handleRegen,handleContentSlotApply,handleSave— is emitted; the file yields 16 nodes and the only function-like ones are two top-level hooks.The consequence is not just missing nodes. The extraction still emits the edge
whose source id matches no node, so it is silently dropped and reported as a dangling endpoint. The relationship survives only at file granularity, and "which handler calls this helper" is unanswerable.
Checked for duplicates
#1740 covers calls inside anonymous arrow callbacks (
addEventListener,forEach); #2575 coversawait import()inside a nested function; #2040 covers nested class containment; #1322 covers function symbols assigned viathis.X =/exports.X =/ class arrow fields. This one is a plain namedfunctiondeclaration nested in a function body, where the symbol itself is never created.