Skip to content

feat(vba): synthesis pass — RaiseEvent sites materialize direct edges to WithEvents handlers #150

Description

@ardelperal

Problem

vba-event-tracer (skill shipped in #33) traces an event end-to-end at query time by walking the graph: declaration site → raises-event edges → subscribes-event edges → handler Subs named by the WithEvents variable convention (<varName>_<EventName>). Every codegraph_explore call repeats this work because the GRAPH doesn't pre-compute the connection.

The graph has all the ingredients:

  • RaiseEvent Fooraises-event edge (caller → event node) — already emitted by scanRaiseEvents
  • WithEvents m_X As ClassNamereferences + subscribes-event edges (subscriber → class, with metadata.variableName: "m_X") — already emitted
  • <varName>_<EventName> Sub → exists as a function node when declared

What's missing: a direct edge from the RaiseEvent site to the handler Sub.

Goal

Materialize one event-handler edge (reusing the existing kind) from every RaiseEvent <EventName> site to its m_X_<EventName> handler Sub, when resolvable by the same WithEvents naming convention that vba-event-tracer uses at query time. Edge provenance: heuristic, metadata.synthesizedBy: 'vba-event-handler'.

Why now

  • The graph data exists, the trace is deterministic, and pre-computing it turns a 4-step query (skill) into a 1-step codegraph_explore call — measurable token + tool-call savings on every event-trace request.
  • Same structural pattern as the existing callback-synthesizer.ts Phase 1 (field-backed observer): registrar + dispatcher paired by field convention. The pair here is the WithEvents field convention rather than property name.
  • Issue epic(vba): complete Access forms coverage — FORMS-1..7 #141 (FORMS-7) just shipped Me.<Control> references; the event wiring is the last big gap in the form code-behind story.

Approach

Two-phase post-extraction pass (mirrors resolveVbaCallStubs in src/resolution/index.ts):

Phase 1 — collect the pairs (per file):
For every WithEvents m_X As ClassName in file F:

  1. Resolve ClassName to the real class node (or skip if shadow)
  2. For every Public Event <EventName> in ClassName: locate the handler Sub in F named m_X_<EventName>
  3. If found, register a pending pair (eventNode, handlerNode, variableName: "m_X")

Phase 2 — connect at RaiseEvent sites (whole project, after extraction):
For every raises-event edge in the DB:

  1. Locate the event node, find its declaring class
  2. Look up the handler Sub from the pairs registered for that class
  3. Emit an event-handler edge from the raiser Sub to the handler Sub
  4. Stamp metadata.synthesizedBy: 'vba-event-handler' and metadata.eventName

Failure modes (silent, no error):

  • WithEvents variable not found in any subscriber (the subscribes-event graph edge is the source of truth — Phase 1 reads it, not source text)
  • Handler Sub m_X_<EventName> not declared (typical for unimplemented events; vba-event-tracer would warn NO_HANDLER — the synthesis just emits no edge)
  • Event declared in a class that has no WithEvents (skip — there's no one listening)

Acceptance criteria

  • On a fixture with: 1 class declaring 2 events, 1 form with WithEvents+handlers for both events, 1 raise site per event → 2 event-handler edges materialized
  • No new edge when the handler Sub m_X_<EventName> is not declared
  • No new edge when the WithEvents variable name doesn't match any Sub in the same file
  • codegraph_explore query for the event returns the handler Sub in ONE call (no skill walk needed) — verified by hand on a real Dysflow-exported project
  • vba-event-tracer skill still works (backward compatible — the skill walks the graph, the new edge is a faster path, not the only path)
  • No new false positives on the FORMS-* real fixture suite (__tests__/extraction-vba-form.test.ts, __tests__/extraction-vba-control-modeling.test.ts)
  • New tests in __tests__/extraction-vba-event-synth.test.ts

Out of scope

  • Cyclic event chains (ModuleA.EventAModuleB.EventBModuleA.EventA) — vba-event-tracer already handles cycle_detected at query time; the synthesis just emits the FIRST hop, the query layer decides whether to follow further
  • Set x = New ClassName late-instantiation subscriptions (these don't produce a subscribes-event edge today; tracked separately)
  • Modifying vba-event-tracer itself (the skill stays; the new edge is an optimization, not a replacement)

Related

  • Builds on the query-time work in [VBA Skill] Ship vba-event-tracer skill #33 vba-event-tracer skill (doesn't replace it, just makes the common case a single explore call)
  • Mirrors resolveVbaCallStubs lifecycle at src/index.ts:535
  • Pattern reference: src/resolution/callback-synthesizer.ts Phase 1 (field-backed observer)

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:vbaVBA/Access-specific work (parent codegraph product)enhancementNew feature or requesttype:featureNew feature

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions