Skip to content

feat(vba): label nodes and handles-error edges #263

Description

@ardelperal

Summary

Make error handlers addressable in the graph: one label node per line label, and a handles-error edge from the procedure to the handler it routes to.

The error-policy work (#259) records whether a procedure has a handler; the handler-region work (#260) marks which edges come from inside one. Neither gives the handler an identity you can point at, search for, or traverse to. This does.

What the corpus contains

Measured across 00_EXPEDIENTES, 00_GESTION_RIESGOS, HPS_SOLICITUDES:

Label definitions3,912
…that are On Error GoTo targets3,776 (96.5%)
…that are pure control flow (siguiente, salir, fin, Teardown)136
On Error GoTo <label> statements3,850
Procedures with more than one On Error statement337
Plain GoTo <label> jumps~450
Dangling targets (referenced, never defined)1

Label names are procedure-scoped in VBA and heavily repeated: errores alone is defined 3,735 times. The qualified name must include the owning procedure or every handler in the codebase collides.

Prerequisite

Land #259 first. Its classifier already computes everything this issue needs — label definitions, On Error GoTo targets, handler-region boundaries, dangling-target resolution. This issue adds no parsing. It is emission only, reading state #259 already carries on ProcInfo.

That is why it is cheap to build and why building it before #259 would mean writing the same scanner twice.

Design

Node

New NodeKind value 'label'.

id generateNodeId(filePath, 'label', name, lineNum)
kind 'label'
name the label as written (e.g. 'errores')
qualifiedName '<ModuleOrClass>.<Procedure>.<label>' // mandatory — see collision note above
filePath the .bas / .cls
startLine the label definition line
endLine handler labels: the procedure's End Sub/Function/Property
control-flow labels: same as startLine
metadata {
isHandler: boolean, // true when an On Error GoTo targets it
handlerBehavior?: 'channel' | 'display' | 'reraise' | 'mixed' | 'unknown',
regionStartLine?: number, // handler labels only
regionEndLine?: number
}

handlerBehavior is the value #260 already derives for errorPolicy.behavior. Copy it; do not re-classify.

Edges

EdgeSourceTargetKindTag
procedure owns its labelfunctionlabelcontains
procedure routes errors herefunctionlabelhandles-error (new)synthesizedBy: 'vba-error-handler'
procedure jumps herefunctionlabelreferencessynthesizedBy: 'vba-goto'

Only one new EdgeKind. Plain GoTo reuses the generic references kind — 450 sites do not justify a second kind, and the synthesizer tag keeps them filterable.

handles-error is not deduplicated per procedure: 337 procedures issue more than one On Error GoTo, and each statement is a distinct routing decision with its own line number. Emit one edge per statement.

Dangling targets

On Error GoTo X where X: is never defined in that procedure emits an UnresolvedReference (referenceKind: 'references', synthesizedBy: 'vba-goto-unresolved'). Never fabricate a node for a label that does not exist — a graph that invents its own targets cannot be used to find this defect, which is the whole point. Exactly 1 site in the corpus.

What this deliberately does NOT do

Calls inside a handler stay attributed to the enclosing procedure. Do not re-parent them onto the label node.

Re-parenting would change callers / callees results for 3,774 procedures and silently break every existing consumer query that expects a call to belong to the procedure that contains it. #260's inErrorHandler: true flag already answers "did this call come from the error path". The label node is addressable, not a container.

Integration points

Verified against the current tree:

  1. src/types.ts — add 'label' to the NODE_KINDS array and 'handles-error' to the EdgeKind union, each with a JSDoc block in the style of the existing VBA entries.
  2. src/context/index.tsHIGH_VALUE_NODE_KINDSdo not add 'label'. That array is the default node filter for context results; adding labels would push ~3,900 of them into every default context response.
  3. src/mcp/tools.tsCONTAINER_NODE_KINDSdo not add 'label'. Container kinds get their body expanded in explore output; a handler label would print the tail of every procedure.
  4. src/search/query-parser.ts — derives KIND_VALUES from NODE_KINDS automatically, so kind:label becomes a valid search filter with no code change. Add a test that pins it.
  5. Databasenodes.kind and edges.kind are plain TEXT with no CHECK constraint (src/db/schema.sql). No migration needed.
  6. docs/index-schema.md — auto-generated; regenerate with npm run schema:dump.
  7. EXTRACTION_VERSION — bump. A new node kind and a new edge kind change extraction output, so existing indexes should be rebuilt.

Node budget

On the three-project corpus:

+3,912 label nodes
+3,912 contains edges
+3,850 handles-error edges
+ 450 references edges (vba-goto)
------
~+3,900 nodes, ~+8,200 edges

Against a post-#245 baseline of roughly 13,000 nodes that is about +30% nodes. Budget it deliberately: this is the largest single node addition in the roadmap, and the reason the HIGH_VALUE_NODE_KINDS and CONTAINER_NODE_KINDS exclusions above are not optional.

What it buys over #259 and #260

  • A stable id per handler, so tooling and other issues can reference one directly
  • kind:label search, and handlers visible in codegraph_explore listings
  • Dangling and duplicate label detection as a graph query rather than a scan
  • The 136 pure control-flow labels become visible for the first time

Ordering

After #259 and #260. Before #262 if the published queries should be able to select handlers by node rather than by procedure metadata.

Acceptance criteria

  • 'label' in NODE_KINDS, 'handles-error' in EdgeKind, both documented in src/types.ts
  • One label node per label definition; qualifiedName includes the owning procedure
  • Two procedures in the same module both defining errores produce two distinct nodes with distinct ids and distinct qualified names
  • A handler label carries isHandler: true plus its region lines; a control-flow label carries isHandler: false and no region
  • contains edge from the owning function node for every label
  • One handles-error edge per On Error GoTo statement — a procedure with two such statements produces two edges
  • Plain GoTo produces a references edge tagged vba-goto
  • On Error GoTo noExiste produces an UnresolvedReference and no node
  • callers / callees output for a sample of 10 procedures with handlers is byte-identical to before this change (no re-parenting)
  • 'label' absent from HIGH_VALUE_NODE_KINDS and from CONTAINER_NODE_KINDS, asserted by test
  • kind:label works as a search filter, asserted by test
  • Corpus counts land within 2% of the budget above; actual figures pasted in the PR body
  • npm run schema:dump re-run and the regenerated doc committed
  • EXTRACTION_VERSION bumped

Context

Task E6 of docs/vba-error-handling-plan.md. That document's §4 argues the cheaper model (#259, #260) covers most of the value at zero node cost — read it for the trade-off, then build this on top of it rather than instead of it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:vbaVBA/Access-specific work (parent codegraph product)status:approvedApproved for implementationtype:featureNew feature

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions