Skip to content

TS/TSX: function declarations nested inside another function emit no node, so their call edges dangle #2653

Description

@larya-dot-eu

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions