feat(vba-extractor): support bang operator - Me!Ctl, Forms!Form!Ctl (closes #44) - #68
Merged
Merged
Conversation
…loses #44) Access VBA uses the bang (!) operator as an alias for default-collection access (Me controls) and default-member access (Forms collection, Recordsets). Before this fix only `Me.<Control>` was tracked via `ME_CONTROL_RE`; the bang form was silently invisible, and `Forms!…` was doubly invisible because `Forms` sits in `RUNTIME_RECEIVER_BLACKLIST` so `CALL_RE` skipped it too. The cross-form UI traffic from `Forms!FormX!txtY.Value` never surfaced the form->control binding the resolver needed. Changes: * `ME_CONTROL_RE` regex extended from `/\bMe\.(…)/gu` to `/\bMe[.!](…)/gu`. `Me!txtFoo` now produces a byte-identical UnresolvedReference to `Me.txtFoo` (same `referenceName`, `referenceKind`, `metadata.synthesizedBy='vba-me-control'`); the regression test pins the parity. * New `FORMS_BANG_RE` scanner added next to `ME_CONTROL_RE` (extends the same hole-1 control-modeling references family). Two alternatives: `Forms!<FormName>[!<Ctl>]` (bang form, with negative-lookahead `(?![.\w])` to drop `Forms!FormX.Foo` as a property access) and `Forms("<FormName>")!<Ctl>` (paren form, must have trailing bang control). Bracketed form names (`Forms![Mi Formulario]`) follow the #54 bracket-stripping rule. * New `scanFormsBang()` method — companion to `scanMeControlReferences`; emits ONE UnresolvedReference per match (`referenceKind='references'`, `metadata.synthesizedBy='vba-forms-bang'`). NO synthetic function node for the form (W4 graph-pollution invariant preserved — the form is a real `.cls`/`.form.txt` pair the resolver picks up via `vba-form-binding`). * Wired `scanFormsBang()` into the line scanner right after `scanOpenFormCalls()` (both scan the original unmasked line because the paren form `Forms("X")!Y` has the form name INSIDE a string literal). * 8 regression tests added in `__tests__/extraction-vba.test.ts`: `Me!txtFoo` parity (byte-identical to `Me.txtFoo`), `Forms!FormX!txtY`, `Forms!FormX` alone, `Forms("FormX")!txtY`, `Forms!FormX.Foo` (must NOT emit), `rs!Campo` (STRETCH SCOPE, intentionally silent), and the W4 no-synthetic-fn guard. STRETCH SCOPE: recordset field access (`rs!Campo`, DAO/ADO default-member) is intentionally out of scope and pinned by a test so a future change can be reviewed explicitly against the bang-form scope decision.
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #44.
What
Enables two Access VBA bang-operator idioms that the previous extractor silently dropped:
Me!<Control>— the default-collection shortcut forMe.<Control>. Same emission shape (byte-identicalreferenceName,referenceKind,metadata.synthesizedBy), so the resolver needs no special-casing.Forms!<Form>[!<Ctl>]andForms("<Form>")!<Ctl>— cross-form UI traffic. Previously doubly-invisible:Formsis inRUNTIME_RECEIVER_BLACKLISTsoCALL_REskipped it, and there was no dedicated scanner.Why
In a real Dysflow-managed Access project (codegraph-vba issue #44), every cross-form
Forms!FormPrincipal!txtEstado.Value = …line produced zero graph edges — soForm_FormPrincipal.clsand theForms/*reference trail were unrelated after indexing. Same forMe!txtNombreinside form.clsbodies. The resolver layer alone couldn't recover these because the references didn't exist in the first place.Diff
src/extraction/vba-extractor.ts__tests__/extraction-vba.test.tsInside the 400-line review budget → single PR, no chain required.
Design
ME_CONTROL_REfrom/\bMe\.(…)/guto/\bMe[.!](…)/gu. The +3 column offset inscanMeControlReferencesremains valid — bothMe.andMe!are 3-character prefixes.FORMS_BANG_REwith two alternatives: bang form (Forms!<Form>(?![.\w]), optional!<Ctl>) and paren form (Forms("<Form>")!<Ctl>, required trailing control). Negative lookahead on the bang form is what excludesForms!FormX.Recordsource(property access, not control access).scanFormsBang()method besidescanMeControlReferences; oneUnresolvedReferenceper match (metadata.synthesizedBy = 'vba-forms-bang'). No syntheticfunctionnode — the form is a real.cls/.form.txtpair the resolver already picks up viavba-form-binding(W4 invariant).scanOpenFormCalls()— both need the original unmasked line because the paren formForms("X")!Yhas the form name inside a string literal.Test coverage
8 new atoms in
__tests__/extraction-vba.test.ts:Me!txtFooemitsvba-me-controlreference totxtFooMe!txtFooandMe.txtFooproduce byte-identicalUnresolvedReferences (parity regression)Forms!FormX!txtY.Value = 1emitsvba-forms-bangreference toFormXForms!FormX(no control segment) emitsvba-forms-bangtoFormXForms("FormX")!txtYemits the samevba-forms-bangtoFormXas the bang formForms!FormX.Foo(post-form property access) does NOT emitrs!Campo(recordset field, STRETCH SCOPE) emits zero — pinned for future reviewForms!FormX!txtYdoes NOT synthesize afunctionnode (W4 invariant)Verification
pnpm exec vitest run __tests__/extraction-vba.test.ts -t "Issue #44"→ 8 passed in 569 mspnpm exec vitest run __tests__/extraction-vba.test.ts __tests__/extraction-vba-control-modeling.test.ts __tests__/extraction-vba-form.test.ts __tests__/extraction-vba-enums-consts.test.ts __tests__/extraction-vba-realfixtures.test.ts __tests__/extraction-vba-roadmap-25-26.test.ts→ 227 passed in 5.09 s (zero regressions across the entire VBA test surface)pnpm run build→ exit 0 (tsc + asset copy OK)Out of scope (intentional)
rs!Campo(DAO/ADO recordset default-member field access). The runtime-receiver blacklist plus the absence of anyForms/Meprefix means the existing scanners already skip it. Pinned by a test so a future change can be reviewed explicitly against the bang-form scope decision.<Form>::<Ctl>qualified control emission — the bang scanner emits only the form reference today; the control segment is consumed but not emitted. Control emission is the form's responsibility downstream.Not done
n/a — issue complete in this PR.