Skip to content

feat(vba): resolve the table or query named by DLookup and friends - #285

Merged
ardelperal merged 1 commit into
mainfrom
feat/issue-255
Sep 2, 2026
Merged

ardelperal merged 1 commit into
mainfrom
feat/issue-255

Conversation

@ardelperal

Copy link
Copy Markdown
Owner

The eight Access domain aggregates — DLookup, DCount, DSum, DMax, DMin, DAvg, DFirst, DLast — take the domain they read as their second argument: a table name or a saved query name. None was modelled, so a procedure whose only data access is a DLookup came through the graph as a procedure that touches no data at all.

What changed

  • src/extraction/vba/sql-wrapper.ts — argument-list walk for the eight domain functions, emitting through the existing feat(vba): resolve saved query names to their query node #253 path.
  • __tests__/extraction-vba-domain-functions.test.ts — 29 tests.
  • CHANGELOG.md — one bullet under [Unreleased] → ### New Features, ends (#255).

Reuse, not a second resolver

The issue said it outright: "reuse that resolver, do not write a second one." The name gate and emit path are #253's (looksLikeSavedQueryName, emitSavedQueryReference), so a domain name is answered by the same "is this a query?" rule, emits the same dao-query reference tagged vba-query-name, and is bound by the same matchVbaQueryName — which still declines on a miss rather than fabricating a table placeholder.

Argument 2 is found by walking, not by splitting

Splitting the argument list on commas is the obvious implementation and it is wrong. Two ordinary spellings break it, and both would have silently shifted the domain to the wrong argument:

  • a comma inside the criteria literal — DLookup("N", "T", "Id In (1,2)")
  • a comma inside a nested call — DLookup("N", "T", "Id=" & Nz(x, 0))

Both are covered by fixtures.

Corpus measurement — the point here is that nothing moved

Run over 00_EXPEDIENTES, 00_GESTION_RIESGOS and HPS_SOLICITUDES, after the change:

synthesizer count
vba-sql-table 860
vba-closes-form 171
dao-query 42
vba-row-source-dynamic 4
distinct SQL tables 93

Byte-identical to the pre-change run. The issue predicted zero uses of these functions in this corpus, and that is confirmed: no new reference appears, and no existing count moved. A change here would have meant the argument walk was matching something it should not — for a feature ranked in the last wave precisely because it is unused locally, "nothing moved" is the result you want.

Behaviour is proven by the 29 fixtures, not by corpus counts.

Verification

  • npx tsc --noEmit — clean.
  • New suite — 29/29.
  • Six adjacent suites together (domain functions, saved query names, VBA extraction, SQL clause coverage, runtime bindings, DoCmd.Close) — 399 passed.

Not verified

The full local suite was not run to completion: npx vitest run on this machine dies with Fatal process out of memory, an environment limit unrelated to this change (it reproduces on file sets this PR does not touch). CI runs the full suite on Ubuntu and Windows.

Closes #255

🤖 Generated with Claude Code

https://claude.ai/code/session_019gmKKUq1ng5ESk6Qhxu77d

The eight Access domain aggregates — DLookup, DCount, DSum, DMax, DMin,
DAvg, DFirst and DLast — take the domain they read as their SECOND
argument: a table name or a saved query name. None of them was modelled,
so a procedure whose only data access is a DLookup came through the graph
as a procedure that touches no data at all.

Decisions taken:

- REUSE, not a second resolver. The name gate and the emit path are the
  ones #253 built for saved-query names (`looksLikeSavedQueryName` and
  `emitSavedQueryReference`), so a domain name is answered by exactly the
  same "is this a query?" rule, emits the same `dao-query` reference
  tagged `vba-query-name`, and is bound by the same `matchVbaQueryName`
  resolver — which still DECLINES on a miss rather than fabricating a
  table placeholder. The issue asked for this explicitly.

- The scan lives in `vba/sql-wrapper.ts` rather than a new file. All it
  adds over #253 is a call shape; a module of its own would have needed a
  rule-table entry to buy nothing.

- Argument 2 is found by WALKING the argument list, not by splitting on
  commas. Two ordinary spellings break a naive split — a comma inside the
  criteria literal (`"Id In (1,2)"`) and a comma inside a nested call
  (`"Id=" & Nz(x, 0)`) — and both would have silently shifted the domain
  to the wrong argument.

- Only a lone string literal is read. A variable or an expression in
  argument 2 names something only the runtime knows, so it is skipped in
  silence. A domain spelled as a full SQL statement is likewise dropped by
  the verb gate: silent beats wrong.

- A file that declares its own procedure by one of these names is left
  alone. The call keeps resolving to that user code through the ordinary
  call-site scan, and no domain reference is invented on top of it. The
  regex anchors on the opening paren, so a helper named `DLookupSeguro`
  was never a candidate in the first place.

Corpus measurement (00_EXPEDIENTES, 00_GESTION_RIESGOS, HPS_SOLICITUDES,
462 files): the probe's JSON output is BYTE-IDENTICAL with and without the
change. These functions have zero uses there, exactly as the issue said,
so node and edge counts are unchanged and nothing new was matched.

Closes #255

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019gmKKUq1ng5ESk6Qhxu77d
@ardelperal
ardelperal merged commit df250a7 into main Sep 2, 2026
5 checks passed
@ardelperal
ardelperal deleted the feat/issue-255 branch September 2, 2026 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(vba): resolve the table or query named by DLookup and friends

1 participant