@aroman22/codegraph-vba-darwin-arm64 1.3.5 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js +26 -7
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/extraction/sql-query-extractor.d.ts +11 -3
- package/lib/dist/extraction/sql-query-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/sql-query-extractor.js +19 -5
- package/lib/dist/extraction/sql-query-extractor.js.map +1 -1
- package/lib/dist/extraction/vba-extractor.d.ts +575 -55
- package/lib/dist/extraction/vba-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/vba-extractor.js +1233 -135
- package/lib/dist/extraction/vba-extractor.js.map +1 -1
- package/lib/dist/extraction/vba-form-extractor.d.ts +127 -0
- package/lib/dist/extraction/vba-form-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/vba-form-extractor.js +290 -3
- package/lib/dist/extraction/vba-form-extractor.js.map +1 -1
- package/lib/dist/extraction/vba-preprocess.d.ts +22 -3
- package/lib/dist/extraction/vba-preprocess.d.ts.map +1 -1
- package/lib/dist/extraction/vba-preprocess.js +137 -15
- package/lib/dist/extraction/vba-preprocess.js.map +1 -1
- package/lib/dist/extraction/vba-source.d.ts +58 -0
- package/lib/dist/extraction/vba-source.d.ts.map +1 -0
- package/lib/dist/extraction/vba-source.js +137 -0
- package/lib/dist/extraction/vba-source.js.map +1 -0
- package/lib/dist/types.d.ts +2 -2
- package/lib/dist/types.d.ts.map +1 -1
- package/lib/dist/types.js +6 -0
- package/lib/dist/types.js.map +1 -1
- package/lib/dist/utils/backtrace-helpers.d.ts +35 -0
- package/lib/dist/utils/backtrace-helpers.d.ts.map +1 -0
- package/lib/dist/utils/backtrace-helpers.js +129 -0
- package/lib/dist/utils/backtrace-helpers.js.map +1 -0
- package/lib/dist/utils/sql-impact-helpers.d.ts +38 -0
- package/lib/dist/utils/sql-impact-helpers.d.ts.map +1 -0
- package/lib/dist/utils/sql-impact-helpers.js +329 -0
- package/lib/dist/utils/sql-impact-helpers.js.map +1 -0
- package/lib/node_modules/.modules.yaml +2 -2
- package/lib/node_modules/.pnpm-workspace-state-v1.json +1 -1
- package/lib/package.json +2 -2
- package/package.json +1 -1
|
@@ -94,17 +94,56 @@ export declare class VbaExtractor {
|
|
|
94
94
|
* DIM_UNQUAL_RE pair with a prefix-check + global scan that handles
|
|
95
95
|
* `As New <Type>`, multi-variable `Dim a As Foo, b As Bar`, and all
|
|
96
96
|
* visibility keywords in one pass.
|
|
97
|
+
* Issue #47: now also accepts `Global` (module-level typed instance) and
|
|
98
|
+
* `Static` (procedure-local retention modifier) so they emit the same
|
|
99
|
+
* `references` edge and `localVarTypeMap` registration as their `Dim`
|
|
100
|
+
* siblings today. The negative lookahead is unchanged: `Const` is still
|
|
101
|
+
* routed to `sweepEnumsAndConsts`.
|
|
97
102
|
*/
|
|
98
103
|
private static readonly DIM_DECL_PREFIX_RE;
|
|
99
104
|
/**
|
|
100
105
|
* Globally scan all `identifier As [New] TypePart1[.TypePart2]` on a
|
|
101
106
|
* variable declaration line. Run with /g after confirming DIM_DECL_PREFIX_RE.
|
|
102
107
|
*
|
|
103
|
-
* Groups: (1) variable name, (2)
|
|
108
|
+
* Groups: (1) variable name, (2) bracketed outer type, (3) unbracketed
|
|
109
|
+
* outer type, (4) bracketed inner type (if qualified), (5) unbracketed
|
|
110
|
+
* inner type. The variable name is always bare (`Dim` cannot declare a
|
|
111
|
+
* bracketed variable). The TYPE position accepts BOTH bracketed names
|
|
112
|
+
* with spaces (e.g. `[Clase Con Espacios]`) and bare identifiers — the
|
|
113
|
+
* bracketed capture wins when present. Only one of (2)/(3) and one of
|
|
114
|
+
* (4)/(5) is ever populated per match.
|
|
104
115
|
* `(?:New\s+)?` consumes the VBA auto-instantiation keyword so it is
|
|
105
116
|
* never captured as the type name (Fix 1).
|
|
117
|
+
*
|
|
118
|
+
* Issue #54: extends the type alternative to accept `[Name With Spaces]`
|
|
119
|
+
* so `Dim x As [Clase Con Espacios]` emits a `references` edge to
|
|
120
|
+
* `Clase Con Espacios` (brackets unwrapped). The unwrap is applied in
|
|
121
|
+
* the sweep loop by picking the bracketed capture group when present.
|
|
106
122
|
*/
|
|
107
123
|
private static readonly DIM_ALL_VARS_RE;
|
|
124
|
+
/**
|
|
125
|
+
* Bare-declared variable capture for the `Dim|Private|Public|Global|Static`
|
|
126
|
+
* prefix. Captures (1) the variable name. Used to register bare `Dim x`
|
|
127
|
+
* (no `As` clause) and explicit-primitive `Dim x As Long|String|...`
|
|
128
|
+
* declarations into `localVarTypeMap` so the type tracking is consistent
|
|
129
|
+
* across all three Dim shapes:
|
|
130
|
+
*
|
|
131
|
+
* `Dim x` → outer = 'variant' (VBA default)
|
|
132
|
+
* `Dim x As Variant` → outer = 'variant' (PRIMITIVE_TYPES member)
|
|
133
|
+
* `Dim x As Long` → outer = 'long' (PRIMITIVE_TYPES member)
|
|
134
|
+
* `Dim x As Foo` → outer = 'foo' (project class — non-primitive)
|
|
135
|
+
*
|
|
136
|
+
* Antigravity audit Task 3: the previous `DIM_ALL_VARS_RE` only matched
|
|
137
|
+
* the `... As <Type>` form, so a bare `Dim x` was invisible to
|
|
138
|
+
* `isLocalProjectClassVar` / `scanCallSites` and `x.Method(1)` produced
|
|
139
|
+
* a dead-end `calls` edge to a stub named `x.Method` that no resolver
|
|
140
|
+
* could repoint. Registering bare Dim with `outer = 'variant'` closes
|
|
141
|
+
* the gate, so `scanCallSites` skips ONLY when the receiver is mapped
|
|
142
|
+
* as a primitive — leaving the "undeclared receiver → stub → resolver
|
|
143
|
+
* repoints" path intact for cross-module qualified calls like
|
|
144
|
+
* `modUtils.Foo(1)` (`modUtils` is not in `localVarTypeMap`).
|
|
145
|
+
*/
|
|
146
|
+
private static readonly BARE_DIM_VAR_RE;
|
|
108
147
|
/**
|
|
109
148
|
* VBA primitive type names — skipped when emitted as Dim targets so
|
|
110
149
|
* we don't pollute the graph with `As Long` / `As String` references.
|
|
@@ -114,7 +153,7 @@ export declare class VbaExtractor {
|
|
|
114
153
|
* pattern is ever captured as a type name it is silently skipped.
|
|
115
154
|
*/
|
|
116
155
|
private static readonly PRIMITIVE_TYPES;
|
|
117
|
-
/** `WithEvents m_X As Form_Foo` — Dim/Private/Public prefix is optional. */
|
|
156
|
+
/** `WithEvents m_X As Form_Foo` — Dim/Private/Public/Global/Static prefix is optional. */
|
|
118
157
|
private static readonly WITHEVENTS_RE;
|
|
119
158
|
private sweepDimsAndWithEvents;
|
|
120
159
|
/** `[visibility] Enum <Name>` — opens an enum block. */
|
|
@@ -130,6 +169,15 @@ export declare class VbaExtractor {
|
|
|
130
169
|
private static readonly ENUM_MEMBER_RE;
|
|
131
170
|
/** `[visibility] Const <decls>` — captures visibility (1) and the rest (2). */
|
|
132
171
|
private static readonly CONST_DECL_RE;
|
|
172
|
+
/**
|
|
173
|
+
* Issue #52: shared `End Sub` / `End Function` / `End Property` marker.
|
|
174
|
+
* Promoted from a local regex in `sweepCallsAndSql` so `sweepEnumsAndConsts`
|
|
175
|
+
* can walk the same proc boundaries and decide Const scope per line.
|
|
176
|
+
* The `(?:^|:\s*)` prefix tolerates colon-separated single-line procs
|
|
177
|
+
* (`Public Sub X(): ... : End Sub`) so the proc stack pops on the same
|
|
178
|
+
* physical line.
|
|
179
|
+
*/
|
|
180
|
+
private static readonly PROCEDURE_END_RE;
|
|
133
181
|
/**
|
|
134
182
|
* Fold a VBA visibility keyword to the canonical lowercase enum, matching
|
|
135
183
|
* the procedure convention: `Private` → 'private'; `Public`, `Global`,
|
|
@@ -165,9 +213,18 @@ export declare class VbaExtractor {
|
|
|
165
213
|
* - While inside a procedure, scan the line for call-site patterns and
|
|
166
214
|
* SQL-wrapper patterns.
|
|
167
215
|
*
|
|
168
|
-
* Call-site regex
|
|
169
|
-
*
|
|
170
|
-
*
|
|
216
|
+
* Call-site regex captures either `Name(...)` (same-file candidate) or
|
|
217
|
+
* `Receiver.Member(...)` (qualified — emit a synthetic node + heuristic
|
|
218
|
+
* edge). The receiver AND member alternatives accept BOTH the bare form
|
|
219
|
+
* (`Foo`) and the VBA bracketed form (`[Foo Bar]`) — bracketed captures
|
|
220
|
+
* win when present. Only one of (1)/(2) and one of (3)/(4) is ever
|
|
221
|
+
* populated per match. Brackets are stripped by the regex itself (the
|
|
222
|
+
* capture groups hold the inner content), so callers receive unwrapped
|
|
223
|
+
* identifiers and the `${name}.${proc}` stub shape stays canonical.
|
|
224
|
+
*
|
|
225
|
+
* Issue #54: the bracketed alternative was previously absent, so
|
|
226
|
+
* `[FUNCIONES UTILES].FormatearFecha(fecha)` (a real Dysflow-exported
|
|
227
|
+
* idiom for modules with spaces in their names) was silently dropped.
|
|
171
228
|
*/
|
|
172
229
|
private static readonly CALL_RE;
|
|
173
230
|
/** SQL wrapper helpers — order matters because `db.Execute` is a suffix of others. */
|
|
@@ -193,30 +250,169 @@ export declare class VbaExtractor {
|
|
|
193
250
|
* the `opens-form` edge instead — sharing no logic with CALL_RE.
|
|
194
251
|
*/
|
|
195
252
|
private static readonly OPEN_FORM_ARG_RE;
|
|
253
|
+
/**
|
|
254
|
+
* Issue #48: `DoCmd.OpenReport "<ReportName>"` modelling regex — sibling
|
|
255
|
+
* of `OPEN_FORM_ARG_RE` (hueco 6 expanded). Same literal-or-bare-id argument
|
|
256
|
+
* capture (group 1) and same trailing positional-args drop. The dispatch
|
|
257
|
+
* table `DOCMD_OPEN_DISPATCH` (below) carries the per-method metadata so
|
|
258
|
+
* OpenForm and OpenReport share the same scan/emit pipeline while their
|
|
259
|
+
* edge kinds (`opens-form` vs `opens-report`), stub node kinds
|
|
260
|
+
* (`form-layout` vs `report-layout`), synthetic file-path prefixes, and
|
|
261
|
+
* qualifiedName prefixes (`Form_<Name>` vs `Report_<Name>`) stay
|
|
262
|
+
* distinct.
|
|
263
|
+
*/
|
|
264
|
+
private static readonly OPEN_REPORT_ARG_RE;
|
|
265
|
+
/**
|
|
266
|
+
* Issue #48 dispatch table — shared literal-or-Const argument resolution
|
|
267
|
+
* for `DoCmd.OpenForm` and `DoCmd.OpenReport`. Each entry is everything
|
|
268
|
+
* `scanDoCmdOpenCalls` + `emitOpensStubEdge` need to share the pipeline
|
|
269
|
+
* between methods while keeping the per-method names distinct.
|
|
270
|
+
*
|
|
271
|
+
* OpenQuery is intentionally NOT in this dispatch — it emits an
|
|
272
|
+
* `UnresolvedReference` (not a stub + edge), resolution to the REAL
|
|
273
|
+
* `query` node emitted by `SqlQueryExtractor`. See
|
|
274
|
+
* `OPEN_QUERY_ARG_RE` + `scanDoCmdOpenQuery`.
|
|
275
|
+
*
|
|
276
|
+
* Why a separate dispatch: `DoCmd` is in `RUNTIME_RECEIVER_BLACKLIST`
|
|
277
|
+
* (R4 invariant), so ALL of these methods are intentionally SKIPPED by
|
|
278
|
+
* the generic `CALL_RE` path that would otherwise emit a junk `calls`
|
|
279
|
+
* edge to a synthetic `function` node for `DoCmd.OpenX`. This dispatch
|
|
280
|
+
* matches BEFORE the call-site scan and uses its own emission path.
|
|
281
|
+
*/
|
|
282
|
+
private static readonly DOCMD_OPEN_DISPATCH;
|
|
283
|
+
/**
|
|
284
|
+
* Issue #48: `DoCmd.OpenQuery "<QueryName>"` modelling regex. Emits an
|
|
285
|
+
* `UnresolvedReference` (NOT a stub + edge) so the resolver binds to the
|
|
286
|
+
* REAL `query` node `SqlQueryExtractor` emits for `queries/<Name>.sql`
|
|
287
|
+
* — the same shape as `vba-me-control` and `vba-forms-bang`. The query
|
|
288
|
+
* may not yet exist in the index when the .bas is parsed; the resolver
|
|
289
|
+
* does the binding when the .sql is later indexed.
|
|
290
|
+
*
|
|
291
|
+
* Argument shape: identical to OpenForm/OpenReport — literal `"..."` or
|
|
292
|
+
* bare identifier resolved against local `Const` declarations, falling
|
|
293
|
+
* back to the bare identifier when unknown.
|
|
294
|
+
*/
|
|
295
|
+
private static readonly OPEN_QUERY_ARG_RE;
|
|
196
296
|
/** SQL assigned to a local variable, e.g. `m_SQL = "SELECT ..." & ...`. */
|
|
197
297
|
private static readonly SQL_VAR_ASSIGN_RE;
|
|
198
298
|
/** SQL wrapper called with a variable, e.g. `getdb().Execute m_SQL`. */
|
|
199
299
|
private static readonly SQL_VAR_EXEC_RE;
|
|
200
|
-
/**
|
|
300
|
+
/**
|
|
301
|
+
* Issue #42: `DoCmd.RunSQL <identifier>` (variable form) — the dominant
|
|
302
|
+
* Access idiom for executing a dynamically-built SQL string. Today only
|
|
303
|
+
* the literal form `DoCmd.RunSQL "DELETE FROM X"` is tracked via the
|
|
304
|
+
* `SQL_WRAPPERS` regex at line 1108; the variable form silently dropped
|
|
305
|
+
* table impact for every procedure that builds SQL in a string and runs
|
|
306
|
+
* it through `DoCmd.RunSQL`.
|
|
307
|
+
*
|
|
308
|
+
* This regex is the DoCmd.RunSQL analogue of `SQL_VAR_EXEC_RE` above and
|
|
309
|
+
* is iterated by `scanSqlInLine` (lines 2051+). When a match is found,
|
|
310
|
+
* the captured identifier is resolved against `sqlVariables` (populated
|
|
311
|
+
* by `trackSqlVariableAssignment` with `&`-accumulate semantics — Issue
|
|
312
|
+
* #13) and the resulting SQL string drives `emitSqlTableReferences`.
|
|
313
|
+
*
|
|
314
|
+
* The optional `(?:\(\))?` + `\s*\(?` shape lets the regex match both
|
|
315
|
+
* the parenthesised form `DoCmd.RunSQL(strSQL)` and the no-paren form
|
|
316
|
+
* `DoCmd.RunSQL strSQL` that the existing SQL_WRAPPERS literal regex
|
|
317
|
+
* does not cover. The captured identifier is the only thing we need —
|
|
318
|
+
* we DO NOT try to parse what the variable points at; that's the
|
|
319
|
+
* existing `sqlVariables` map's job.
|
|
320
|
+
*/
|
|
321
|
+
private static readonly SQL_VAR_DOCMD_RUNSQL_RE;
|
|
322
|
+
/**
|
|
323
|
+
* SQL table-name regex scoped to the clauses that introduce a table
|
|
324
|
+
* reference: `FROM <t>`, `JOIN <t>`, `INTO <t>`, `UPDATE <t>`. Adding
|
|
325
|
+
* `JOIN` lets the scanner pick up tables from joined fragments that
|
|
326
|
+
* arrive via `&`-concatenated wrapper literals (e.g.
|
|
327
|
+
* `db.Execute "FROM A" & " JOIN B"`); without it the second literal's
|
|
328
|
+
* table was silently dropped even though the wrapper regex now matches
|
|
329
|
+
* the chain.
|
|
330
|
+
*
|
|
331
|
+
* The captured table name is an optional bracketed/unbracketed schema
|
|
332
|
+
* prefix followed by a `.`, then a bracketed-or-bare identifier — so
|
|
333
|
+
* `FROM dbo.tblCustomers` and `FROM [My Schema].[My Table]` come
|
|
334
|
+
* through as one composite reference. Without the prefix the regex
|
|
335
|
+
* still matches a single identifier byte-identical to the old shape.
|
|
336
|
+
* Brackets in the captured composite are stripped by
|
|
337
|
+
* `emitSqlTableReferences` (`replace(/[\[\]]/g, '')`), so the public
|
|
338
|
+
* node name is the unwrapped form `dbo.tblCustomers` /
|
|
339
|
+
* `My Schema.My Table` — matching how plain `[Order Details]` is also
|
|
340
|
+
* unwrapped to `Order Details`. The identifier class
|
|
341
|
+
* `\[[^\]]+\]|\p{L}[\p{L}\p{N}_]*` (same as the saved-queries
|
|
342
|
+
* `TABLE_RE` in `sql-query-extractor.ts`) ensures bracketed names
|
|
343
|
+
* with spaces — `[Order Details]`, `[My Schema]`, `[My Table]` —
|
|
344
|
+
* are captured whole.
|
|
345
|
+
*/
|
|
201
346
|
private static readonly SQL_TABLE_RE;
|
|
202
347
|
/**
|
|
203
|
-
*
|
|
348
|
+
* Issue #50: TempVars — Access's global key-value store for cross-form
|
|
349
|
+
* state. We model each STATIC-LITERAL key as a synthetic `class` placeholder
|
|
350
|
+
* (same NodeKind as SQL table refs, see `emitReference`) and emit one
|
|
351
|
+
* `references` edge per reading/writing procedure with
|
|
352
|
+
* `metadata.synthesizedBy: 'vba-tempvar'` and `metadata.access` ∈
|
|
353
|
+
* `{'read','write'}` (the user-facing enum; lowercase single-tokens).
|
|
354
|
+
*
|
|
355
|
+
* Three scanner surfaces cover the four real idioms:
|
|
356
|
+
* - `TEMP_VAR_BANG_RE` — `TempVars!clave` (no parens; write or read)
|
|
357
|
+
* - `TEMP_VAR_PAREN_RE` — `TempVars("clave")` (parens; write or read)
|
|
358
|
+
* - `TEMP_VAR_ADD_RE` — `TempVars.Add "clave", v` (always a write)
|
|
359
|
+
*
|
|
360
|
+
* Bang vs paren split by line-source: the bang form has no string literals
|
|
361
|
+
* in scope, so we scan the MASKED line (`maskStringContent` replaces
|
|
362
|
+
* `"…"` content with spaces — same line source the call-site / With
|
|
363
|
+
* event scanners consume). The paren and Add forms have their key INSIDE
|
|
364
|
+
* a `"…"` literal that gets blanked by the masker, so those regexes scan
|
|
365
|
+
* the ORIGINAL (unmasked) line — same split the SQL_TABLE_RE/OpenForm
|
|
366
|
+
* literal scanners already use.
|
|
367
|
+
*
|
|
368
|
+
* Dynamic-key forms — `TempVars(strNombre)`,
|
|
369
|
+
* `TempVars("clave" & suffix)` — are silently unmatched by all three
|
|
370
|
+
* regexes (none of them tolerate a function-call or `&` arg). REQ-CODE-4
|
|
371
|
+
* "unresolvable is silent" applies: these stay silent by design to
|
|
372
|
+
* prevent placeholder-node explosion.
|
|
373
|
+
*/
|
|
374
|
+
private static readonly TEMP_VAR_BANG_RE;
|
|
375
|
+
/**
|
|
376
|
+
* Issue #50 (cont.):
|
|
377
|
+
* `TempVars("clave")` / `TempVars( "clave" )` capture. Scanned
|
|
378
|
+
* over the original (unmasked) line — the literal lives INSIDE a
|
|
379
|
+
* string and would be stripped by `maskStringContent`.
|
|
380
|
+
*/
|
|
381
|
+
private static readonly TEMP_VAR_PAREN_RE;
|
|
382
|
+
/**
|
|
383
|
+
* Issue #50 (cont.):
|
|
384
|
+
* `TempVars.Add "clave", value` capture. Always a write (the
|
|
385
|
+
* `.Add` method inserts/updates the entry). Scanned over the
|
|
386
|
+
* original (unmasked) line for the same string-literal reason as
|
|
387
|
+
* `TEMP_VAR_PAREN_RE`.
|
|
388
|
+
*/
|
|
389
|
+
private static readonly TEMP_VAR_ADD_RE;
|
|
390
|
+
/**
|
|
391
|
+
* `Me.<ControlName>` / `Me!<ControlName>` reference capture — hole 1
|
|
392
|
+
* of VBA control-modeling. Extended by Issue #44 to accept the bang
|
|
393
|
+
* form (the default-collection shortcut Access VBA inherits from VB).
|
|
204
394
|
*
|
|
205
|
-
* Real VBA
|
|
206
|
-
* `Me.lblTitulo.Caption = "Hello"` ← property assignment
|
|
207
|
-
* `Me.txtDescripcion.Value = "World"` ← property assignment
|
|
208
|
-
* `Me.ComandoGrabar.Enabled = True` ← property assignment
|
|
395
|
+
* Real VBA idioms:
|
|
396
|
+
* `Me.lblTitulo.Caption = "Hello"` ← dot form, property assignment
|
|
397
|
+
* `Me.txtDescripcion.Value = "World"` ← dot form, property assignment
|
|
398
|
+
* `Me.ComandoGrabar.Enabled = True` ← dot form, property assignment
|
|
399
|
+
* `Me!txtNombre = "Hello"` ← BANG form (default collection)
|
|
400
|
+
* `Me!txtEstado.Value = 1` ← BANG form, then property
|
|
209
401
|
* `If Nz(Me.MotivoBorrado, "") = "" Then` ← read in expression
|
|
210
402
|
*
|
|
211
403
|
* The existing call-site scanner (CALL_RE) only fires on `Name(`
|
|
212
404
|
* (paren form) and `Me` is in its keyword blacklist anyway, so
|
|
213
405
|
* `Me.<Control>` references are silently invisible. This regex matches
|
|
214
|
-
* the FIRST identifier after `Me.` regardless of what follows
|
|
215
|
-
* property, an index, an assignment, a call argument, etc.) so the
|
|
406
|
+
* the FIRST identifier after `Me.` or `Me!` regardless of what follows
|
|
407
|
+
* (a property, an index, an assignment, a call argument, etc.) so the
|
|
216
408
|
* form → control binding is surfaced as an UnresolvedReference for the
|
|
217
409
|
* resolver to pick up later. Subsequent segments (`.Caption`, `.Value`,
|
|
218
410
|
* `.Enabled`) are intentionally NOT captured — they are properties of
|
|
219
|
-
* the control, not new symbols.
|
|
411
|
+
* the control, not new symbols. The bang (`!`) is the default-collection
|
|
412
|
+
* shortcut: `Me!txtFoo` is semantically identical to `Me.txtFoo` and
|
|
413
|
+
* produces a byte-identical UnresolvedReference (same `referenceName`,
|
|
414
|
+
* `referenceKind`, and `metadata.synthesizedBy`) — the regression test
|
|
415
|
+
* in `__tests__/extraction-vba.test.ts` pins this parity.
|
|
220
416
|
*
|
|
221
417
|
* Provenance: `metadata.synthesizedBy = 'vba-me-control'`. Mirrors the
|
|
222
418
|
* `vba-form-binding` (form→sibling-`.cls`) and `vba-name-resolution`
|
|
@@ -224,6 +420,84 @@ export declare class VbaExtractor {
|
|
|
224
420
|
* in `src/types.ts`.
|
|
225
421
|
*/
|
|
226
422
|
private static readonly ME_CONTROL_RE;
|
|
423
|
+
/**
|
|
424
|
+
* Issue #44: `Forms!<FormName>` / `Forms("<FormName>")!<Ctl>` cross-form
|
|
425
|
+
* reference capture — companion to `ME_CONTROL_RE` above. Access VBA's
|
|
426
|
+
* default-collection shortcut for cross-form control access, captured
|
|
427
|
+
* BEFORE the generic call-site scan and emitted as its own
|
|
428
|
+
* `UnresolvedReference` family.
|
|
429
|
+
*
|
|
430
|
+
* Real VBA idioms:
|
|
431
|
+
* `Forms!FormX!txtY.Value = 1` — bang form, with control segment
|
|
432
|
+
* `Forms!FormX.Recordsource = "..."` — bang form, trailing property access
|
|
433
|
+
* `Set f = Forms!FormX` — bang form, no control segment
|
|
434
|
+
* `Forms("FormX")!txtY.Value = 1` — paren form, with control
|
|
435
|
+
* `Forms![Mi Formulario]!txtY` — bracketed form name (#54 mirror)
|
|
436
|
+
*
|
|
437
|
+
* Why a dedicated scanner (mirroring `OPEN_FORM_ARG_RE` / B4 / DoCmd.OpenForm):
|
|
438
|
+
* `Forms` is in `RUNTIME_RECEIVER_BLACKLIST`, so the generic CALL_RE
|
|
439
|
+
* path silently skips both `Forms!X` and `Forms("X")!Y`. Without this
|
|
440
|
+
* scanner, cross-form UI traffic from `Forms!FormX!txtY.Value` would
|
|
441
|
+
* never surface the form → control binding that the resolver needs to
|
|
442
|
+
* glue form `form-layout` nodes to control `form-instance-control`
|
|
443
|
+
* nodes. The dedicated dispatch fires BEFORE the call-site scan and
|
|
444
|
+
* uses its own emission path; the runtime-blacklist constraint is
|
|
445
|
+
* preserved (we intentionally do not rewrite CALL_RE).
|
|
446
|
+
*
|
|
447
|
+
* What this scanner emits per match:
|
|
448
|
+
* - ONE `UnresolvedReference` whose `referenceName` is the form's
|
|
449
|
+
* identifier (stripped of any surrounding quote or bracket
|
|
450
|
+
* decoration), tagged `metadata.synthesizedBy = 'vba-forms-bang'`
|
|
451
|
+
* and `referenceKind = 'references'`. The control segment (if
|
|
452
|
+
* present) is consumed by the regex so `Forms!FormX!txtY.Value` is
|
|
453
|
+
* captured as a single match, but the control name is NOT emitted
|
|
454
|
+
* as its own reference — control emission is the form's
|
|
455
|
+
* responsibility downstream.
|
|
456
|
+
* - NO synthetic `function` node (W4 graph-pollution invariant — the
|
|
457
|
+
* form is a real `.cls` / `.form.txt` pair the resolver already
|
|
458
|
+
* picks up via the `vba-form-binding` path). Without this guard the
|
|
459
|
+
* bang form would synthesize one stub per cross-form reference
|
|
460
|
+
* site, identical to what `bumpShapes` from #43 audited out.
|
|
461
|
+
*
|
|
462
|
+
* What this scanner does NOT match (intentional, pinned by tests):
|
|
463
|
+
* - `Forms!FormX.Foo` — the trailing `.Foo` IS a property
|
|
464
|
+
* access on the form (e.g. `Recordsource`), NOT a control access.
|
|
465
|
+
* The bang alternative carries a `(?![.\w])` negative-lookahead
|
|
466
|
+
* after the form identifier to drop this shape. The "W4
|
|
467
|
+
* no-synthetic-fn" test pins both halves of this contract.
|
|
468
|
+
* - `rs!Campo` — recordset field access (DAO/ADO
|
|
469
|
+
* default-member field read) is explicitly out of scope for the
|
|
470
|
+
* current change. The runtime-receiver blacklist plus the absence
|
|
471
|
+
* of any `Forms`/`Me` prefix means the existing scanners already
|
|
472
|
+
* skip it cleanly; the "STRETCH SCOPE" test pins that behaviour
|
|
473
|
+
* so a future change to bring recordset bangs in is reviewed
|
|
474
|
+
* explicitly against the bang-form scope decision.
|
|
475
|
+
*
|
|
476
|
+
* Operates on the ORIGINAL (unmasked) line — the paren form
|
|
477
|
+
* `Forms("FormX")!txtY` has the form name INSIDE a string literal, so
|
|
478
|
+
* masking string content would destroy the form identifier. Same
|
|
479
|
+
* unmasked-line constraint as `scanOpenFormCalls`.
|
|
480
|
+
*/
|
|
481
|
+
private static readonly FORMS_BANG_RE;
|
|
482
|
+
/**
|
|
483
|
+
* Issue #46: scan `Set <var> = New <Type>[.<Inner>]` lines — the dominant
|
|
484
|
+
* VBA late-instantiation idiom. Run inside `sweepCallsAndSql`'s proc-stack
|
|
485
|
+
* loop so the surrounding procedure is known. For each match:
|
|
486
|
+
* - register `<var>` in `localVarTypeMap` with `outer=<Type>`,
|
|
487
|
+
* `qualified=<hasInner>`, `assignedWithSet=true` so the PR #61 refined
|
|
488
|
+
* gate lets subsequent `<var>.Member ...` qualified calls resolve via
|
|
489
|
+
* the resolved class name;
|
|
490
|
+
* - emit a `references` edge from the module/class node to a synthetic
|
|
491
|
+
* node named `<Type>`, tagged `synthesizedBy: 'vba-set-new'`.
|
|
492
|
+
*
|
|
493
|
+
* Groups: (1) variable name, (2) outer type, (3) optional inner type.
|
|
494
|
+
* Operates on the MASKED line (string-literal content already replaced
|
|
495
|
+
* with spaces) so `Set x = New Foo` inside a string literal never matches.
|
|
496
|
+
*/
|
|
497
|
+
private static readonly SET_NEW_RE;
|
|
498
|
+
/** Issue #43: track the receiver for `With <expr>` / `End With` blocks. */
|
|
499
|
+
private static readonly WITH_START_RE;
|
|
500
|
+
private static readonly WITH_END_RE;
|
|
227
501
|
/** Keywords we never want to match as call receivers. */
|
|
228
502
|
private static readonly CALL_KEYWORD_BLACKLIST;
|
|
229
503
|
/**
|
|
@@ -253,6 +527,15 @@ export declare class VbaExtractor {
|
|
|
253
527
|
* qualified, non-primitive) identifier — a candidate project-defined class.
|
|
254
528
|
* Qualified types (e.g. `DAO.Recordset`) and primitives (`String`, `Long`)
|
|
255
529
|
* return false so runtime/DAO calls are suppressed.
|
|
530
|
+
*
|
|
531
|
+
* Issue #54 (defensive): brackets are stripped from the lookup key, so a
|
|
532
|
+
* caller that forgets to unwrap a bracketed name still finds the
|
|
533
|
+
* corresponding entry. This is a no-op when the name is already bare —
|
|
534
|
+
* the unwrap pattern only matches an opening `[` at the start and a
|
|
535
|
+
* closing `]` at the end. Today's call sites (`scanCallSites`,
|
|
536
|
+
* `detectQualifiedStatementCall`) already unwrap in the regex captures,
|
|
537
|
+
* so this defensive strip is a belt-and-braces guard for any future
|
|
538
|
+
* caller that forgets.
|
|
256
539
|
*/
|
|
257
540
|
private isLocalProjectClassVar;
|
|
258
541
|
/**
|
|
@@ -268,6 +551,8 @@ export declare class VbaExtractor {
|
|
|
268
551
|
* IS already the target module's name and no resolution is needed.
|
|
269
552
|
*/
|
|
270
553
|
private resolveReceiverType;
|
|
554
|
+
private normalizeWithReceiver;
|
|
555
|
+
private detectWithMemberCall;
|
|
271
556
|
private sweepCallsAndSql;
|
|
272
557
|
private static readonly RAISE_EVENT_RE;
|
|
273
558
|
private scanRaiseEvents;
|
|
@@ -277,16 +562,21 @@ export declare class VbaExtractor {
|
|
|
277
562
|
private callDedupe;
|
|
278
563
|
private synthFunctionNodeIds;
|
|
279
564
|
/**
|
|
280
|
-
* B4 (hueco 6): cache of stub
|
|
281
|
-
* for a given target
|
|
282
|
-
* stubs when `DoCmd.OpenForm "FormTest"`
|
|
283
|
-
*
|
|
565
|
+
* B4 (hueco 6) extended by Issue #48: cache of stub node ids we've already
|
|
566
|
+
* emitted for a given (method, target name) pair in this file. Avoids
|
|
567
|
+
* emitting duplicate stubs when `DoCmd.OpenForm "FormTest"` or
|
|
568
|
+
* `DoCmd.OpenReport "InformeMensual"` shows up N times across N calls.
|
|
569
|
+
* Keyed by `${cacheKey}:${lowerName}` so the OpenForm and OpenReport
|
|
570
|
+
* de-dup buckets stay disjoint — `OpenForm:Form1` ≠ `OpenReport:Form1`.
|
|
571
|
+
* The name part is lowercased so `FormTest` / `formtest` collapse.
|
|
284
572
|
*/
|
|
285
|
-
private
|
|
573
|
+
private opensStubIdsByKey;
|
|
286
574
|
/**
|
|
287
|
-
* Hueco 1: scan a line for `Me.<ControlName>`
|
|
288
|
-
* UnresolvedReference per occurrence, tagged
|
|
289
|
-
* `metadata.synthesizedBy: 'vba-me-control'`.
|
|
575
|
+
* Hueco 1: scan a line for `Me.<ControlName>` / `Me!<ControlName>`
|
|
576
|
+
* patterns and emit one UnresolvedReference per occurrence, tagged
|
|
577
|
+
* `metadata.synthesizedBy: 'vba-me-control'`. Issue #44 extended
|
|
578
|
+
* `ME_CONTROL_RE` from `Me\.` to `Me[.!]` so the bang form (default-
|
|
579
|
+
* collection shortcut) is captured byte-identically to the dot form.
|
|
290
580
|
*
|
|
291
581
|
* Operates on the masked `callScanLine` (string-literal content already
|
|
292
582
|
* replaced with spaces) so `Me.X` inside a string literal is not falsely
|
|
@@ -297,10 +587,76 @@ export declare class VbaExtractor {
|
|
|
297
587
|
*
|
|
298
588
|
* `fromNodeId` is the current procedure's function node — that's the
|
|
299
589
|
* "owner" of the reference (the Sub body that wrote `Me.lblTitulo = …`).
|
|
590
|
+
* The +3 column offset remains correct under Issue #44's regex change
|
|
591
|
+
* because both `Me.` and `Me!` are 3-character prefixes.
|
|
300
592
|
*/
|
|
301
593
|
private scanMeControlReferences;
|
|
594
|
+
/**
|
|
595
|
+
* Issue #44: scan a line for cross-form bang references
|
|
596
|
+
* (`Forms!<FormName>[!<Ctl>]` and `Forms("<FormName>")!<Ctl>`) and
|
|
597
|
+
* emit ONE UnresolvedReference per match with `metadata.synthesizedBy
|
|
598
|
+
* = 'vba-forms-bang'`. Companion to `scanMeControlReferences` above;
|
|
599
|
+
* shares the emission shape (a single `UnresolvedReference` per match,
|
|
600
|
+
* `referenceKind: 'references'`), keeping the W4 invariant that we
|
|
601
|
+
* synthesize NO `function` node for forms.
|
|
602
|
+
*
|
|
603
|
+
* Operates on the ORIGINAL (unmasked) line — the paren form
|
|
604
|
+
* `Forms("FormX")!txtY` carries the form name INSIDE a string literal
|
|
605
|
+
* and would be destroyed by `maskStringContent`. Mirrors
|
|
606
|
+
* `scanOpenFormCalls`'s unmasked-line constraint.
|
|
607
|
+
*
|
|
608
|
+
* Regex alternatives (see `FORMS_BANG_RE`):
|
|
609
|
+
* 1. `Forms!<FormName>` (bare or `[bracketed]`, NOT followed by `.X`)
|
|
610
|
+
* — bang form, may have a trailing `!<Ctl>[.<Prop>]` that is
|
|
611
|
+
* consumed but NOT emitted.
|
|
612
|
+
* 2. `Forms("<FormName>")!<Ctl>` (quoted or bare/bracketed form arg)
|
|
613
|
+
* — paren form, ALWAYS with a control segment.
|
|
614
|
+
*
|
|
615
|
+
* Stripping: the form's identifier is unwrapped of `"` quotes (string
|
|
616
|
+
* literals) and `[…]` brackets (Issue #54 reserved-identifier shape) so
|
|
617
|
+
* the public `referenceName` is the bare form name. Bracketed forms
|
|
618
|
+
* (`Forms![Mi Formulario]`) follow the same strip rules as the paren
|
|
619
|
+
* form so the resolver sees `Mi Formulario` either way.
|
|
620
|
+
*/
|
|
621
|
+
private scanFormsBang;
|
|
302
622
|
private findOrCreateFunctionNodeId;
|
|
303
623
|
private findFunctionNodeByName;
|
|
624
|
+
/**
|
|
625
|
+
* Issue #45: split a single-line VBA `If <cond> Then <body>` into one or
|
|
626
|
+
* more statement-clause fragments that the existing `detectStatementCall`
|
|
627
|
+
* and `detectQualifiedStatementCall` detectors can process. Handles:
|
|
628
|
+
*
|
|
629
|
+
* - `If x Then Foo` → `['Foo']`
|
|
630
|
+
* - `If x Then Foo Else Bar` → `['Foo', 'Bar']`
|
|
631
|
+
* - `If x Then DoA: DoB` → `['DoA', 'DoB']` (colon-separated multi-statement)
|
|
632
|
+
* - `If x Then Foo Else A: B` → `['Foo', 'A', 'B']`
|
|
633
|
+
* - `If x Then GoTo fin` → `[]` (GoTo clause filtered out)
|
|
634
|
+
* - `If x Then Exit Sub` → `[]` (Exit clause filtered out)
|
|
635
|
+
*
|
|
636
|
+
* When the line does NOT match a single-line `If … Then` shape — for
|
|
637
|
+
* instance a block-form `If x Then` whose body lives on subsequent
|
|
638
|
+
* lines — the splitter returns `[<line>]` (the original input) so
|
|
639
|
+
* callers can use this method unconditionally and let the existing
|
|
640
|
+
* per-line scan pick up the body on a separate line.
|
|
641
|
+
*
|
|
642
|
+
* `GoTo`, `Exit`, and `Resume` clauses are filtered at the fragment
|
|
643
|
+
* level (defense in depth): even though `emitStatementCallEdge` already
|
|
644
|
+
* drops these via the `CALL_KEYWORD_BLACKLIST`, filtering here prevents
|
|
645
|
+
* any chance of `detectStatementCall`'s generic identifier extractor
|
|
646
|
+
* matching a substring (e.g. an identifier like `GoToFinishingTouches`)
|
|
647
|
+
* as a side effect of a richer clause where the keyword happens to be
|
|
648
|
+
* the leading token.
|
|
649
|
+
*
|
|
650
|
+
* `line` is the string-literal-masked scan line. The mask makes global
|
|
651
|
+
* `:` splitting safe: real VBA colons never appear inside string
|
|
652
|
+
* literals (already masked to spaces) and never inside expressions
|
|
653
|
+
* inside parens at the source level (a colon ends a statement in VBA,
|
|
654
|
+
* so it cannot appear inside a parenthesised argument list either).
|
|
655
|
+
* The `Else` keyword is a VBA statement-level separator and is
|
|
656
|
+
* forbidden inside parens or expressions, so splitting on
|
|
657
|
+
* `\s+Else\s+` does not need paren tracking either.
|
|
658
|
+
*/
|
|
659
|
+
private splitSingleLineIfClauses;
|
|
304
660
|
/**
|
|
305
661
|
* H1: detect a statement-form Sub call.
|
|
306
662
|
*
|
|
@@ -351,51 +707,112 @@ export declare class VbaExtractor {
|
|
|
351
707
|
*/
|
|
352
708
|
private emitQualifiedStatementCallEdge;
|
|
353
709
|
/**
|
|
354
|
-
* B4 (hueco 6): scan one line of VBA source for
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
710
|
+
* B4 (hueco 6) extended by Issue #48: scan one line of VBA source for
|
|
711
|
+
* `DoCmd.OpenX "Target"` calls where X ∈ {Form, Report} (see the
|
|
712
|
+
* `DOCMD_OPEN_DISPATCH` table). For each match, emit:
|
|
713
|
+
* - a stub node (form-layout / report-layout) for the target, cached
|
|
714
|
+
* per-(method, name) so the same target referenced from N sites
|
|
715
|
+
* emits exactly ONE stub,
|
|
716
|
+
* - an `opens-form` / `opens-report` heuristic edge from the calling
|
|
717
|
+
* Sub to that stub.
|
|
359
718
|
*
|
|
360
719
|
* Both endpoints are pushed into `this.nodes` / `this.edges`, so the
|
|
361
720
|
* per-file edge filter at `index.ts:insertedIds.has(source) &&
|
|
362
721
|
* insertedIds.has(target)` passes the edge naturally without any
|
|
363
722
|
* exemption to the filter.
|
|
364
723
|
*
|
|
365
|
-
* Why a stub and not a direct lookup: the target form lives in a
|
|
366
|
-
* DIFFERENT file (its own `.form.txt`), and the extractor
|
|
367
|
-
* DB access at parse time. The stub's synthetic file path
|
|
368
|
-
* (`synthetic:opensFormStub/<
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
*
|
|
383
|
-
*
|
|
724
|
+
* Why a stub and not a direct lookup: the target form/report lives in a
|
|
725
|
+
* DIFFERENT file (its own `.form.txt` / `.report.txt`), and the extractor
|
|
726
|
+
* doesn't have DB access at parse time. The stub's synthetic file path
|
|
727
|
+
* (`synthetic:opensFormStub/<Name>.form.txt` /
|
|
728
|
+
* `synthetic:opensReportStub/<Name>.form.txt`) guarantees a deterministic
|
|
729
|
+
* node id so re-indexes collapse to the same stub. When the consumer's
|
|
730
|
+
* `.form.txt` / `.report.txt` is later indexed, the real
|
|
731
|
+
* `form-layout` / `report-layout` node carries a different id (it uses
|
|
732
|
+
* the real file path); the stub and the real coexist harmlessly.
|
|
733
|
+
*
|
|
734
|
+
* Why a separate dispatch from CALL_RE: `DoCmd` is in
|
|
735
|
+
* `RUNTIME_RECEIVER_BLACKLIST` (R4 invariant), so `DoCmd.OpenForm` /
|
|
736
|
+
* `DoCmd.OpenReport` are intentionally SKIPPED by the generic CALL_RE
|
|
737
|
+
* path that would otherwise emit a junk `calls` edge to a synthetic
|
|
738
|
+
* `function` node for `DoCmd.OpenX`. The dispatch below matches BEFORE
|
|
739
|
+
* the call-site scan and uses its own emission path.
|
|
740
|
+
*
|
|
741
|
+
* Scope note: literal-string and bare-identifier argument forms are
|
|
742
|
+
* supported. Bare identifiers resolve only through local `Const`
|
|
743
|
+
* declarations; arbitrary variable data-flow remains intentionally
|
|
744
|
+
* out of scope.
|
|
745
|
+
*/
|
|
746
|
+
private scanDoCmdOpenCalls;
|
|
747
|
+
/**
|
|
748
|
+
* B4 (hueco 6) extended by Issue #48: emit a stub `form-layout` /
|
|
749
|
+
* `report-layout` node for `targetName` (cached per dispatch entry so
|
|
750
|
+
* duplicates collapse and OpenForm/OpenReport de-dup buckets stay
|
|
751
|
+
* disjoint) and a single `opens-form` / `opens-report` heuristic edge
|
|
384
752
|
* from `caller` to that stub.
|
|
385
753
|
*
|
|
386
754
|
* The edge carries:
|
|
387
|
-
* - `kind
|
|
388
|
-
* - `provenance: 'heuristic'`
|
|
389
|
-
* - `metadata.targetFormName`
|
|
390
|
-
* - `metadata.synthesizedBy
|
|
391
|
-
* synthesis from the
|
|
755
|
+
* - `kind` — dispatch-specific (`opens-form` / `opens-report`)
|
|
756
|
+
* - `provenance: 'heuristic'` — synthesized, not parsed
|
|
757
|
+
* - `metadata.<dispatchTargetKey>` (e.g. `targetFormName`) — the resolved name
|
|
758
|
+
* - `metadata.synthesizedBy` — dispatch-specific (`vba-opens-form` /
|
|
759
|
+
* `vba-opens-report`); distinguishes this synthesis from the
|
|
760
|
+
* dim/sql/event-handler families
|
|
392
761
|
*
|
|
393
762
|
* The stub's `metadata.stub: true` flag lets downstream UI render
|
|
394
|
-
*
|
|
395
|
-
*
|
|
763
|
+
* stubs distinctly (e.g. with a dashed border) and gives later
|
|
764
|
+
* re-resolution pass a hook for collapse. The stub is
|
|
396
765
|
* line-independent (`line = 0`) so re-indexes produce identical ids.
|
|
397
766
|
*/
|
|
398
|
-
private
|
|
767
|
+
private emitOpensStubEdge;
|
|
768
|
+
/**
|
|
769
|
+
* Issue #48: scan one line of VBA source for `DoCmd.OpenQuery "X"` calls.
|
|
770
|
+
* Each match emits ONE `UnresolvedReference` (NOT a stub + edge) so the
|
|
771
|
+
* resolver binds to the REAL `query` node that `SqlQueryExtractor`
|
|
772
|
+
* produces for `queries/<Name>.sql` (dysflow exports every saved QueryDef
|
|
773
|
+
* + `queries.json` manifest). Falls back to silent when the .sql is not
|
|
774
|
+
* yet in the index — the resolver does the binding when it's later
|
|
775
|
+
* indexed, exactly like `vba-me-control` and `vba-forms-bang`.
|
|
776
|
+
*
|
|
777
|
+
* Companion to `scanDoCmdOpenCalls` but intentionally NOT in the
|
|
778
|
+
* dispatch table — OpenQuery's emission shape (`UnresolvedReference`)
|
|
779
|
+
* is structurally different from OpenForm/OpenReport's (synthetic node
|
|
780
|
+
* + heuristic edge). The two pipelines share the literal-vs-Const
|
|
781
|
+
* argument resolution pattern via `localConstants.get(...)` but emit
|
|
782
|
+
* via two different branches of `unresolvedReferences` vs
|
|
783
|
+
* `nodes`/`edges`.
|
|
784
|
+
*
|
|
785
|
+
* UnresolvedReference shape (per Issue #48 spec — must match
|
|
786
|
+
* SqlQueryExtractor's query node name exactly):
|
|
787
|
+
* - `referenceName` = resolved query name
|
|
788
|
+
* - `referenceKind: 'references'` = same kind the resolver binds
|
|
789
|
+
* - `metadata.synthesizedBy: 'vba-opens-query'`
|
|
790
|
+
* - NO synthetic `function` node (W4 graph-pollution invariant — the
|
|
791
|
+
* real `query` node already exists in the index once `.sql` is
|
|
792
|
+
* processed, and creating stubs would compete with the binding).
|
|
793
|
+
*/
|
|
794
|
+
private scanDoCmdOpenQuery;
|
|
795
|
+
/**
|
|
796
|
+
* Regex matching the chained `& "..."` literals that may follow a
|
|
797
|
+
* wrapper's first literal on the same physical line. Captures the
|
|
798
|
+
* literal CONTENT (group 1); the surrounding `&` and quotes are
|
|
799
|
+
* structural, not data. VBA allows whitespace around `&` and around
|
|
800
|
+
* the inner quotes — handled with `\s*`. The `((?:[^"]|"")*)` body
|
|
801
|
+
* mirrors the wrapper regex so a `""` inside a chained literal still
|
|
802
|
+
* decodes to a single `"`.
|
|
803
|
+
*
|
|
804
|
+
* Cross-physical-line concat via `_` continuation is OUT OF SCOPE for
|
|
805
|
+
* v1 (deferred; see commit message).
|
|
806
|
+
*/
|
|
807
|
+
private static readonly SQL_WRAPPER_CHAIN_RE;
|
|
808
|
+
/**
|
|
809
|
+
* Given the text that follows a SQL wrapper's first literal on the same
|
|
810
|
+
* physical line, return the contents of every `& "..."` chained literal
|
|
811
|
+
* in source order. Operates per-physical-line only — VBA `_` line
|
|
812
|
+
* continuation across physical lines is handled separately by
|
|
813
|
+
* `collectStringLiteralText` for the variable-assignment path.
|
|
814
|
+
*/
|
|
815
|
+
private collectSqlWrapperChain;
|
|
399
816
|
private scanSqlInLine;
|
|
400
817
|
/**
|
|
401
818
|
* #13 fix: `sql = sql & "..."` (self-referential concatenation) must
|
|
@@ -419,6 +836,64 @@ export declare class VbaExtractor {
|
|
|
419
836
|
*/
|
|
420
837
|
private emitReference;
|
|
421
838
|
private synthClassNodeIds;
|
|
839
|
+
/**
|
|
840
|
+
* Issue #50: TempVars placeholder-node de-dup cache. Keys are the
|
|
841
|
+
* deterministic node ids produced by `emitTempVarReference` — those
|
|
842
|
+
* ids intentionally ignore `this.filePath` (use a synthetic
|
|
843
|
+
* `synthetic:tempvar/<key>` path instead) so the SAME key referenced
|
|
844
|
+
* from Form_A.cls AND Form_B.cls collapses to ONE placeholder node.
|
|
845
|
+
* Cross-file id stability is the cross-form state premise that makes
|
|
846
|
+
* `codegraph_explore` connect producer ⇄ consumer in one hop.
|
|
847
|
+
*/
|
|
848
|
+
private synthTempVarNodeIds;
|
|
849
|
+
/**
|
|
850
|
+
* Issue #50: emit one TempVar reading/writing site. Per call:
|
|
851
|
+
* - placeholder `class` node keyed on the synthetic
|
|
852
|
+
* `synthetic:tempvar/<key>` file path so cross-file extraction
|
|
853
|
+
* calls collapse to one node per key,
|
|
854
|
+
* - `references` edge from the calling `function` node
|
|
855
|
+
* (via `findOrCreateFunctionNodeId` — same access pattern as
|
|
856
|
+
* `scanDoCmdOpenCalls` / `scanDoCmdOpenQuery`) carrying
|
|
857
|
+
* `metadata.synthesizedBy: 'vba-tempvar'` AND
|
|
858
|
+
* `metadata.access: 'read' | 'write'`.
|
|
859
|
+
*
|
|
860
|
+
* Reuses the synthetic-`class`-placeholder shape established by
|
|
861
|
+
* `emitReference` for SQL tables, events, Dim types, etc. — so every
|
|
862
|
+
* synthesized `references` edge in the file already carries the same
|
|
863
|
+
* `kind: 'class'` target, and downstream consumers (UI, resolvers,
|
|
864
|
+
* search queries) filter on `metadata.synthesizedBy` rather than
|
|
865
|
+
* NodeKind anyway. The `metadata.synthesizedBy` tag cleanly distinguishes
|
|
866
|
+
* TempVars refs from SQL-table refs inside the same NodeKind bucket.
|
|
867
|
+
*
|
|
868
|
+
* Skips when stack is empty (the writer/reader is module-level code
|
|
869
|
+
* — REQ-CODE-4 "unresolvable/runtime reference is silent"). The
|
|
870
|
+
* spec wires this per-proc only; the module-level shape would need
|
|
871
|
+
* a different emission (no procedure source) and is out of scope.
|
|
872
|
+
*/
|
|
873
|
+
private emitTempVarReference;
|
|
874
|
+
/**
|
|
875
|
+
* Issue #50: scan one line of VBA source for TempVars access sites and
|
|
876
|
+
* emit one `references` edge per site. Three regex runs:
|
|
877
|
+
* - bang form `TempVars!x` over the MASKED line (`!` itself never
|
|
878
|
+
* lives inside a string literal, so masked == unmasked here — using
|
|
879
|
+
* the masked line is conservative against false positives in
|
|
880
|
+
* concatenated string content),
|
|
881
|
+
* - paren form `TempVars("x")` over the ORIGINAL line (the literal is
|
|
882
|
+
* inside a string — masked would strip the key),
|
|
883
|
+
* - Add form `TempVars.Add "x", v` over the ORIGINAL line (same reason),
|
|
884
|
+
* always classified as a write.
|
|
885
|
+
*
|
|
886
|
+
* Each match classifies access by looking at the LINE SUFFIX (after the
|
|
887
|
+
* closing paren / bang-key) on the SAME line: if `=` (and not the
|
|
888
|
+
* nonexistent `==`) is the next non-whitespace character, it's a write.
|
|
889
|
+
* VBA has no `==` so a bare `=` suffix check is safe.
|
|
890
|
+
*
|
|
891
|
+
* The caller parameter comes from `stack[stack.length - 1]` in
|
|
892
|
+
* `sweepCallsAndSql`. Pass `undefined` for module-level access — we
|
|
893
|
+
* drop the edge (per REQ-CODE-4 spirit, runtime references have no
|
|
894
|
+
* static source to anchor against).
|
|
895
|
+
*/
|
|
896
|
+
private sweepTempVars;
|
|
422
897
|
/**
|
|
423
898
|
* Fix 2 (Issue #2): maps `variableName.toLowerCase()` → declared type info.
|
|
424
899
|
* Built by `sweepDimsAndWithEvents`; consulted by `sweepCallsAndSql` to gate
|
|
@@ -426,8 +901,53 @@ export declare class VbaExtractor {
|
|
|
426
901
|
* typed as a SIMPLE (non-qualified, non-primitive) identifier emit edges.
|
|
427
902
|
*/
|
|
428
903
|
private localVarTypeMap;
|
|
429
|
-
/**
|
|
904
|
+
/**
|
|
905
|
+
* Issue #52: Const resolution buckets, scoped per procedure. Key is
|
|
906
|
+
* `'module'` for module-level Consts, or the procedure's `startLine`
|
|
907
|
+
* (stringified) for proc-local Consts. Each bucket maps the lowercase
|
|
908
|
+
* constant name to its simple-literal value (used by `DoCmd.OpenForm` /
|
|
909
|
+
* `OpenReport` / `OpenQuery` argument resolution via `resolveLocalConst`).
|
|
910
|
+
*
|
|
911
|
+
* Two procs declaring the same Const name with different values stay
|
|
912
|
+
* isolated (each in its own bucket); reads look up the current proc's
|
|
913
|
+
* bucket first and fall back to the module bucket. The bucket-per-proc
|
|
914
|
+
* model was chosen over a single file-wide Map (the pre-fix shape) so
|
|
915
|
+
* `DoCmd.OpenForm FORM_DESTINO` resolves to the proc-local value, not
|
|
916
|
+
* whichever was written last.
|
|
917
|
+
*/
|
|
430
918
|
private localConstants;
|
|
919
|
+
/**
|
|
920
|
+
* Issue #52: shared lookup helper for `scanDoCmdOpenCalls` and
|
|
921
|
+
* `scanDoCmdOpenQuery`. The current scope is the procedure whose
|
|
922
|
+
* `startLine` is on top of `procStack` (or `'module'` when the stack is
|
|
923
|
+
* empty). Per-proc bucket first; module bucket is the fallback.
|
|
924
|
+
*/
|
|
925
|
+
private resolveLocalConst;
|
|
926
|
+
/**
|
|
927
|
+
* Issue #52: shared writer. `scopeKey` is `'module'` or the procedure's
|
|
928
|
+
* startLine-as-string. Creates the bucket lazily so callers do not have
|
|
929
|
+
* to pre-allocate per proc. Returns the bucket the value was written to
|
|
930
|
+
* (mostly useful for tests; production code ignores it).
|
|
931
|
+
*/
|
|
932
|
+
private setLocalConstInScope;
|
|
933
|
+
/**
|
|
934
|
+
* Issue #52: the current Const-lookup scope. `'module'` when no procedure
|
|
935
|
+
* is open, otherwise the top-of-stack proc's `startLine` as a string.
|
|
936
|
+
* Both `sweepEnumsAndConsts` (to decide whether to emit a `constant`
|
|
937
|
+
* node) and `sweepCallsAndSql` (to drive OpenForm/OpenQuery resolution)
|
|
938
|
+
* keep this in sync with their per-line stack walk by pushing/popping
|
|
939
|
+
* `procStack` and writing the new top's key here.
|
|
940
|
+
*/
|
|
941
|
+
private currentProcKey;
|
|
942
|
+
/**
|
|
943
|
+
* Issue #52: per-extraction proc-stack shared between `sweepEnumsAndConsts`
|
|
944
|
+
* and `sweepCallsAndSql`. Each sweep clears it at the start so the file's
|
|
945
|
+
* mid-proc structural state never leaks across sweeps. Holds the
|
|
946
|
+
* `startLine` (1-based, matches `ProcInfo.startLine`) of every procedure
|
|
947
|
+
* whose body the sweep has not yet emitted `End Sub`/`End Function`/
|
|
948
|
+
* `End Property` for.
|
|
949
|
+
*/
|
|
950
|
+
private procStack;
|
|
431
951
|
/** Local event name (lowercase) → event node for `RaiseEvent` edge emission. */
|
|
432
952
|
private localEvents;
|
|
433
953
|
}
|