fix(vba): classify unambiguous statement-form calls as calls - #270
Merged
Merged
Conversation
An unresolved statement-form Sub call was always surfaced as `unqualified-ident`, the bucket for ambiguous bare-identifier reads. That is right for exactly one of the three statement shapes. Escribir can be a Const read -> unqualified-ident (unchanged) Call Escribir Call is only valid on a procedure -> calls Escribir 1, 2 a constant takes no argument list -> calls `detectStatementCall` now reports whether the shape is syntactically unambiguous, and the unresolved-reference push site picks the kind from it. Nothing else about the row changes, so the two kinds only trade rows. The statement detectors also needed a second string mask: the existing space mask reduces `Escribir "texto"` to the identifier plus blanks, making an argument list indistinguishable from a bare read. They now run on an index-identical `_` mask that keeps the literal visible as an opaque token, while the column-sensitive scanners keep the space mask. Resolved calls are unaffected. Bare identifiers stay in `unqualified-ident` so the const-first disambiguation rule (#108, FR-3.1) and the name-only DAO-enum / intrinsic-constant gates (#188) keep working, and statement-form built-ins stay `declined-runtime` because the stdlib gate (#192/#195) already accepts the `calls` kind by name. Measured on two production Access projects, before -> after: calls 6,194 -> 6,351 rows (693 -> 777 failed), unqualified-ident 166 -> 9 rows (85 -> 1 failed); combined 6,360 rows / 778 failed, both unchanged. docs/vba-reference-kinds.md updated with the split and the re-measured table. Closes #265
…nt-call-kind # Conflicts: # CHANGELOG.md
…nt-call-kind # Conflicts: # CHANGELOG.md
The probe (`npm run probe:vba`) landed on main after this branch started; it is now the supported way to reproduce the extractor-level half of the issue #265 measurement, so quote it alongside the resolver-level numbers.
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 #265
What changed
An unresolved statement-form Sub call was always pushed as
unqualified-ident, the bucket for ambiguous bare-identifier reads. That classification is correct for exactly one of the three statement shapes:Constread?referenceKindEscribirunqualified-ident(unchanged)Call EscribirCallkeyword is only valid on a procedurecallsEscribir 1, 2callsdetectStatementCallnow returns{ name, unambiguous }, and the unresolved-reference push site pickscallsorunqualified-identfrom that flag. Nothing else about the row changes — samefromNodeId, samereferenceName, samevba-statement-call-unresolvedstamp — so the two kinds can only trade rows with each other.Resolved calls are untouched: they emit a
callsedge and never reach this branch.One extra piece the issue did not call out
maskStringContentblanks string literals to spaces before the statement detectors see the line, which reducesEscribir "texto"to the identifier plus trailing whitespace — indistinguishable from a bare read. Implementing "an argument list is present" on the space-masked line would therefore have left the single most common statement-call shape in the wrong bucket.The statement/qualified/
Withdetectors now run on a second, index-identical mask that fills literals with_instead of spaces, so an argument list stays visible while the literal's contents stay unparseable. Column-sensitive scanners (scanCallSites,scanMeControlReferences, TempVars, DoCmd, Forms-bang) keep the original space mask;maskStringContent's default is unchanged.Measurement
Two production Access projects (
00_EXPEDIENTES,00_GESTION_RIESGOS), 246.bas/.clsmodules. Baseline is a detached checkout ofmainata0345ae.Extractor output — the committed probe
referenceKindmaina0345ae)callsunqualified-identreferencesmember-withqualified-callproperty-getproperty-setdiffbetween the two probe reports is 8 lines — those two rows and nothing else. Declared procedures 3,840, stub nodes 1,597, every node kind, every edge kind and synthesizer, the top-30 stub targets, SQL tables (69), forms (132) and errors are all identical.The before column reproduces the figures the orchestrator quoted for current
mainexactly.After the resolver —
failedvsdeclined-runtimeThese are the numbers the noise-ratio table in
docs/vba-reference-kinds.mdpublishes. Same two projects, indexed separately through the real pipeline.reference_kindfailed(before → after)failed/ total (after)callsunqualified-identBoth sums are invariant — rows were reclassified, none created or dropped. Every other kind is identical (
member-with1,401,references826,qualified-call380,property-get215,property-set76).Docs
docs/vba-reference-kinds.md:callsrow keepsCall LimpiaBufferand now also names the argument-list form;unqualified-identrow states explicitly that a bare identifier with noCallkeyword and no arguments stays there because of theConst-read ambiguity, with a short table and the reasons (FR-3.1 const-first disambiguation, and the name-only DAO-enum / intrinsic-constant gates from enh(resolver): DAO enum values + VBA intrinsic constants should be classified as built-ins #188 that key on this kind);00_VBA_TOOLKIT_BENCHcorpus that produced the v1.13.0 numbers is no longer present on disk (its index is empty and it holds no sources), so the re-measurement is on the two Access projects above and says so;src/types.tsand the two extractor files corrected.Regression guards
Conststaysunqualified-ident, including when unambiguous calls sit on neighbouring lines. The existing FR-3.1 test inextraction-vba-reference-kind.test.tsis untouched and still passes.MsgBox "x",Shell "calc"and friends still land indeclined-runtime, verified through a realCodeGraph.indexAll()round-trip. They move to thecallskind, and the stdlib gate already acceptscallsby name, so their status is unchanged. BareDoEventsstaysunqualified-ident.unqualified-ident; those are all bare reads and none of them move.extraction-vba-runtime-constants.test.tspasses unchanged.Call Foonow comes outcalls/failed— the actual point of the issue, since it previously sat behind the const/intrinsic gates meant for bare reads.One existing expectation was updated, not weakened:
extraction-vba-stub-resolver.test.tsTest 9 pinnedMsgBox "hi"andShell "calc.exe"tounqualified-ident. Their kind is nowcalls; the assertion this test exists for — all three aredeclined-runtime— is unchanged.Verification (on
maina0345ae merged in)npx tsc --noEmit— cleanpnpm run build— cleanpnpm exec vitest run vba extraction-sql-query sql-query-discovery— 48 files passed, 747 tests (47 onmain; the new test file makes 48, nothing else moved)Merging
mainproduced one conflict, inCHANGELOG.md, where two### Fixesbullets landed on the same line — both kept.src/extraction/vba/calls.ts(#245) andsrc/extraction/vba/text-utils.ts(#247) auto-merged cleanly; both were re-read after the merge and the measurements above were re-run on the merged tree.New tests
__tests__/extraction-vba-statement-call-kind.test.ts— 13 tests, real files, no mocking: thedetectStatementCallflag itself (including the_-mask case and the trailing-whitespace non-case), the three shapes end-to-end throughVbaExtractor, the resolvedCall Escribiredge, the FR-3.1 guards, and thedeclined-runtimeguards through a real index.