@aroman22/codegraph-vba-darwin-arm64 1.15.0 → 1.16.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/bin/codegraph.js +13 -7
- package/lib/dist/bin/codegraph.js.map +1 -1
- package/lib/dist/bin/daemon-release.d.ts +7 -0
- package/lib/dist/bin/daemon-release.d.ts.map +1 -0
- package/lib/dist/bin/daemon-release.js +18 -0
- package/lib/dist/bin/daemon-release.js.map +1 -0
- package/lib/dist/db/queries.d.ts +30 -0
- package/lib/dist/db/queries.d.ts.map +1 -1
- package/lib/dist/db/queries.js +51 -1
- package/lib/dist/db/queries.js.map +1 -1
- package/lib/dist/extraction/access-erd-extractor.d.ts +57 -0
- package/lib/dist/extraction/access-erd-extractor.d.ts.map +1 -0
- package/lib/dist/extraction/access-erd-extractor.js +389 -0
- package/lib/dist/extraction/access-erd-extractor.js.map +1 -0
- package/lib/dist/extraction/extraction-version.d.ts +1 -1
- package/lib/dist/extraction/extraction-version.js +1 -1
- package/lib/dist/extraction/grammars.d.ts +20 -0
- package/lib/dist/extraction/grammars.d.ts.map +1 -1
- package/lib/dist/extraction/grammars.js +34 -0
- package/lib/dist/extraction/grammars.js.map +1 -1
- package/lib/dist/extraction/index.d.ts +1 -0
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js +371 -351
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/extraction/parse-pool.d.ts +8 -3
- package/lib/dist/extraction/parse-pool.d.ts.map +1 -1
- package/lib/dist/extraction/parse-pool.js +1 -2
- package/lib/dist/extraction/parse-pool.js.map +1 -1
- package/lib/dist/extraction/parse-worker.js +7 -6
- package/lib/dist/extraction/parse-worker.js.map +1 -1
- package/lib/dist/extraction/sql-query-extractor.d.ts +16 -14
- package/lib/dist/extraction/sql-query-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/sql-query-extractor.js +43 -31
- package/lib/dist/extraction/sql-query-extractor.js.map +1 -1
- package/lib/dist/extraction/sql-table-scan.d.ts +184 -0
- package/lib/dist/extraction/sql-table-scan.d.ts.map +1 -0
- package/lib/dist/extraction/sql-table-scan.js +288 -0
- package/lib/dist/extraction/sql-table-scan.js.map +1 -0
- package/lib/dist/extraction/tree-sitter.d.ts +10 -1
- package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.js +34 -3
- package/lib/dist/extraction/tree-sitter.js.map +1 -1
- package/lib/dist/extraction/vba/call-sweep.d.ts +2 -7
- package/lib/dist/extraction/vba/call-sweep.d.ts.map +1 -1
- package/lib/dist/extraction/vba/call-sweep.js +144 -90
- package/lib/dist/extraction/vba/call-sweep.js.map +1 -1
- package/lib/dist/extraction/vba/calls.d.ts +24 -4
- package/lib/dist/extraction/vba/calls.d.ts.map +1 -1
- package/lib/dist/extraction/vba/calls.js +42 -12
- package/lib/dist/extraction/vba/calls.js.map +1 -1
- package/lib/dist/extraction/vba/constants.d.ts +7 -14
- package/lib/dist/extraction/vba/constants.d.ts.map +1 -1
- package/lib/dist/extraction/vba/constants.js +8 -36
- package/lib/dist/extraction/vba/constants.js.map +1 -1
- package/lib/dist/extraction/vba/context.d.ts +443 -7
- package/lib/dist/extraction/vba/context.d.ts.map +1 -1
- package/lib/dist/extraction/vba/context.js +325 -29
- package/lib/dist/extraction/vba/context.js.map +1 -1
- package/lib/dist/extraction/vba/controls.d.ts +18 -1
- package/lib/dist/extraction/vba/controls.d.ts.map +1 -1
- package/lib/dist/extraction/vba/controls.js +99 -27
- package/lib/dist/extraction/vba/controls.js.map +1 -1
- package/lib/dist/extraction/vba/declarations.d.ts +1 -6
- package/lib/dist/extraction/vba/declarations.d.ts.map +1 -1
- package/lib/dist/extraction/vba/declarations.js +5 -39
- package/lib/dist/extraction/vba/declarations.js.map +1 -1
- package/lib/dist/extraction/vba/dims.d.ts +11 -7
- package/lib/dist/extraction/vba/dims.d.ts.map +1 -1
- package/lib/dist/extraction/vba/dims.js +265 -29
- package/lib/dist/extraction/vba/dims.js.map +1 -1
- package/lib/dist/extraction/vba/docmd.d.ts +29 -2
- package/lib/dist/extraction/vba/docmd.d.ts.map +1 -1
- package/lib/dist/extraction/vba/docmd.js +297 -18
- package/lib/dist/extraction/vba/docmd.js.map +1 -1
- package/lib/dist/extraction/vba/enums-consts.d.ts +7 -14
- package/lib/dist/extraction/vba/enums-consts.d.ts.map +1 -1
- package/lib/dist/extraction/vba/enums-consts.js +11 -45
- package/lib/dist/extraction/vba/enums-consts.js.map +1 -1
- package/lib/dist/extraction/vba/error-channel.d.ts +57 -0
- package/lib/dist/extraction/vba/error-channel.d.ts.map +1 -0
- package/lib/dist/extraction/vba/error-channel.js +128 -0
- package/lib/dist/extraction/vba/error-channel.js.map +1 -0
- package/lib/dist/extraction/vba/errors.d.ts +64 -0
- package/lib/dist/extraction/vba/errors.d.ts.map +1 -0
- package/lib/dist/extraction/vba/errors.js +775 -0
- package/lib/dist/extraction/vba/errors.js.map +1 -0
- package/lib/dist/extraction/vba/filesystem-statements.d.ts +23 -0
- package/lib/dist/extraction/vba/filesystem-statements.d.ts.map +1 -0
- package/lib/dist/extraction/vba/filesystem-statements.js +70 -0
- package/lib/dist/extraction/vba/filesystem-statements.js.map +1 -0
- package/lib/dist/extraction/vba/implements.d.ts +1 -6
- package/lib/dist/extraction/vba/implements.d.ts.map +1 -1
- package/lib/dist/extraction/vba/implements.js +1 -22
- package/lib/dist/extraction/vba/implements.js.map +1 -1
- package/lib/dist/extraction/vba/labels.d.ts +26 -0
- package/lib/dist/extraction/vba/labels.d.ts.map +1 -0
- package/lib/dist/extraction/vba/labels.js +180 -0
- package/lib/dist/extraction/vba/labels.js.map +1 -0
- package/lib/dist/extraction/vba/module-vars.d.ts +36 -0
- package/lib/dist/extraction/vba/module-vars.d.ts.map +1 -0
- package/lib/dist/extraction/vba/module-vars.js +145 -0
- package/lib/dist/extraction/vba/module-vars.js.map +1 -0
- package/lib/dist/extraction/vba/options.d.ts +88 -0
- package/lib/dist/extraction/vba/options.d.ts.map +1 -0
- package/lib/dist/extraction/vba/options.js +39 -0
- package/lib/dist/extraction/vba/options.js.map +1 -0
- package/lib/dist/extraction/vba/parameters.d.ts +35 -0
- package/lib/dist/extraction/vba/parameters.d.ts.map +1 -0
- package/lib/dist/extraction/vba/parameters.js +110 -0
- package/lib/dist/extraction/vba/parameters.js.map +1 -0
- package/lib/dist/extraction/vba/procedures.d.ts +1 -9
- package/lib/dist/extraction/vba/procedures.d.ts.map +1 -1
- package/lib/dist/extraction/vba/procedures.js +174 -112
- package/lib/dist/extraction/vba/procedures.js.map +1 -1
- package/lib/dist/extraction/vba/rules.d.ts +10 -5
- package/lib/dist/extraction/vba/rules.d.ts.map +1 -1
- package/lib/dist/extraction/vba/rules.js +19 -0
- package/lib/dist/extraction/vba/rules.js.map +1 -1
- package/lib/dist/extraction/vba/runtime-objects.d.ts +59 -0
- package/lib/dist/extraction/vba/runtime-objects.d.ts.map +1 -0
- package/lib/dist/extraction/vba/runtime-objects.js +106 -0
- package/lib/dist/extraction/vba/runtime-objects.js.map +1 -0
- package/lib/dist/extraction/vba/signature.d.ts +57 -0
- package/lib/dist/extraction/vba/signature.d.ts.map +1 -0
- package/lib/dist/extraction/vba/signature.js +164 -0
- package/lib/dist/extraction/vba/signature.js.map +1 -0
- package/lib/dist/extraction/vba/sql-wrapper.d.ts +76 -3
- package/lib/dist/extraction/vba/sql-wrapper.d.ts.map +1 -1
- package/lib/dist/extraction/vba/sql-wrapper.js +725 -151
- package/lib/dist/extraction/vba/sql-wrapper.js.map +1 -1
- package/lib/dist/extraction/vba/tempvars.d.ts.map +1 -1
- package/lib/dist/extraction/vba/tempvars.js +4 -0
- package/lib/dist/extraction/vba/tempvars.js.map +1 -1
- package/lib/dist/extraction/vba/text-utils.d.ts +62 -2
- package/lib/dist/extraction/vba/text-utils.d.ts.map +1 -1
- package/lib/dist/extraction/vba/text-utils.js +84 -6
- package/lib/dist/extraction/vba/text-utils.js.map +1 -1
- package/lib/dist/extraction/vba-extractor.d.ts +18 -1
- package/lib/dist/extraction/vba-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/vba-extractor.js +111 -16
- package/lib/dist/extraction/vba-extractor.js.map +1 -1
- package/lib/dist/extraction/vba-form-extractor.d.ts +28 -14
- package/lib/dist/extraction/vba-form-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/vba-form-extractor.js +56 -21
- package/lib/dist/extraction/vba-form-extractor.js.map +1 -1
- package/lib/dist/extraction/vba-preprocess.d.ts +89 -3
- package/lib/dist/extraction/vba-preprocess.d.ts.map +1 -1
- package/lib/dist/extraction/vba-preprocess.js +221 -57
- package/lib/dist/extraction/vba-preprocess.js.map +1 -1
- package/lib/dist/extraction/vba-source.d.ts +0 -10
- package/lib/dist/extraction/vba-source.d.ts.map +1 -1
- package/lib/dist/extraction/vba-source.js +0 -13
- package/lib/dist/extraction/vba-source.js.map +1 -1
- package/lib/dist/extraction/vba-test-manifest-extractor.d.ts +0 -6
- package/lib/dist/extraction/vba-test-manifest-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/vba-test-manifest-extractor.js +0 -1
- package/lib/dist/extraction/vba-test-manifest-extractor.js.map +1 -1
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +9 -0
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/mcp/daemon-manager.d.ts.map +1 -1
- package/lib/dist/mcp/daemon-manager.js +7 -3
- package/lib/dist/mcp/daemon-manager.js.map +1 -1
- package/lib/dist/mcp/daemon-paths.d.ts +6 -0
- package/lib/dist/mcp/daemon-paths.d.ts.map +1 -1
- package/lib/dist/mcp/daemon-paths.js +15 -0
- package/lib/dist/mcp/daemon-paths.js.map +1 -1
- package/lib/dist/mcp/daemon-registry.d.ts +82 -7
- package/lib/dist/mcp/daemon-registry.d.ts.map +1 -1
- package/lib/dist/mcp/daemon-registry.js +557 -45
- package/lib/dist/mcp/daemon-registry.js.map +1 -1
- package/lib/dist/mcp/daemon-watchdog.d.ts +12 -0
- package/lib/dist/mcp/daemon-watchdog.d.ts.map +1 -1
- package/lib/dist/mcp/daemon-watchdog.js +37 -7
- package/lib/dist/mcp/daemon-watchdog.js.map +1 -1
- package/lib/dist/mcp/daemon.d.ts +20 -1
- package/lib/dist/mcp/daemon.d.ts.map +1 -1
- package/lib/dist/mcp/daemon.js +123 -30
- package/lib/dist/mcp/daemon.js.map +1 -1
- package/lib/dist/mcp/index.d.ts.map +1 -1
- package/lib/dist/mcp/index.js +13 -2
- package/lib/dist/mcp/index.js.map +1 -1
- package/lib/dist/mcp/proxy.d.ts +32 -0
- package/lib/dist/mcp/proxy.d.ts.map +1 -1
- package/lib/dist/mcp/proxy.js +163 -31
- package/lib/dist/mcp/proxy.js.map +1 -1
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +29 -1
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/project-config.d.ts +43 -2
- package/lib/dist/project-config.d.ts.map +1 -1
- package/lib/dist/project-config.js +86 -2
- package/lib/dist/project-config.js.map +1 -1
- package/lib/dist/resolution/index.d.ts +47 -2
- package/lib/dist/resolution/index.d.ts.map +1 -1
- package/lib/dist/resolution/index.js +160 -5
- package/lib/dist/resolution/index.js.map +1 -1
- package/lib/dist/resolution/name-matcher.d.ts +25 -0
- package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
- package/lib/dist/resolution/name-matcher.js +59 -0
- package/lib/dist/resolution/name-matcher.js.map +1 -1
- package/lib/dist/resolution/vba-runtime-objects.d.ts +11 -11
- package/lib/dist/resolution/vba-runtime-objects.d.ts.map +1 -1
- package/lib/dist/resolution/vba-runtime-objects.js +18 -38
- package/lib/dist/resolution/vba-runtime-objects.js.map +1 -1
- package/lib/dist/types.d.ts +13 -5
- package/lib/dist/types.d.ts.map +1 -1
- package/lib/dist/types.js +17 -0
- package/lib/dist/types.js.map +1 -1
- package/lib/node_modules/.modules.yaml +1 -1
- package/lib/node_modules/.pnpm-workspace-state-v1.json +1 -1
- package/lib/package.json +2 -1
- package/package.json +1 -1
|
@@ -1,93 +1,273 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.DEFAULT_SQL_WRAPPERS = exports.RUNTIME_BINDING_SYNTHESIZER = void 0;
|
|
4
|
+
exports.compileSqlWrappers = compileSqlWrappers;
|
|
4
5
|
exports.trackSqlVariableAssignment = trackSqlVariableAssignment;
|
|
6
|
+
exports.scanDomainFunctionsInLine = scanDomainFunctionsInLine;
|
|
7
|
+
exports.scanSqlInLine = scanSqlInLine;
|
|
5
8
|
/**
|
|
6
9
|
* SQL-in-strings sweep (REQ-CODE-8). Scans SQL wrapper calls
|
|
7
10
|
* (`DoCmd.RunSQL "…"`, `*db.OpenRecordset "…"`, `getdb().Execute "…"`) and
|
|
8
11
|
* variable-form executions, tracks `sql = sql & "…"` accumulation, and emits
|
|
9
12
|
* `references` edges (via `ctx.emitReference`) to the table names found in the
|
|
10
13
|
* FROM/JOIN/INTO/UPDATE clauses.
|
|
14
|
+
*
|
|
15
|
+
* Issue #203: the `FROM/JOIN/INTO/UPDATE <table>` regex is the canonical
|
|
16
|
+
* source from `src/extraction/sql-table-scan.ts` — every table-name
|
|
17
|
+
* capture path in the project imports from there so a reserved word
|
|
18
|
+
* (`WHERE`, `ORDER`, `SET`, …) can never be emitted as a table
|
|
19
|
+
* reference, and a non-literal operand dropped by `&`-concatenation
|
|
20
|
+
* (`"DELETE FROM " & tabla & " WHERE x"`) is replaced with a `?`
|
|
21
|
+
* sentinel that the regex can never match. The shared module also
|
|
22
|
+
* emits the read/write access direction so this file no longer
|
|
23
|
+
* re-implements `classifySqlAccess`.
|
|
24
|
+
*
|
|
25
|
+
* Issue #244: which receivers count as a database handle is no longer baked
|
|
26
|
+
* into the scanning regexes. `DEFAULT_SQL_WRAPPERS` lists the built-in ones
|
|
27
|
+
* and `codegraph.json` → `vba.sqlWrappers` extends that list with a project's
|
|
28
|
+
* own accessors. Config entries are identifier fragments or `receiver.method`
|
|
29
|
+
* pairs — never raw regex, which in this per-line hot path would be a
|
|
30
|
+
* catastrophic-backtracking foot-gun.
|
|
31
|
+
*
|
|
32
|
+
* Issue #255: the Access domain aggregate functions (`DLookup`, `DCount`, …)
|
|
33
|
+
* name a table or a saved query in their SECOND argument. They live here
|
|
34
|
+
* rather than in a file of their own because the only thing they add is a call
|
|
35
|
+
* shape — the "is this a saved query?" gate and the emit path are the ones
|
|
36
|
+
* issue #253 already built, reused verbatim.
|
|
11
37
|
*/
|
|
12
|
-
const vba_preprocess_1 = require("../vba-preprocess");
|
|
13
38
|
const text_utils_1 = require("./text-utils");
|
|
14
|
-
|
|
15
|
-
const SQL_WRAPPERS = [
|
|
16
|
-
{ name: 'DoCmd.RunSQL', re: /\bDoCmd\.RunSQL\s+"((?:[^"]|"")*)"/g },
|
|
17
|
-
{ name: '*db.OpenRecordset', re: /\b(?:\p{L}[\p{L}\p{N}_]*)?db\b(?:\(\))?\.OpenRecordset\s+"((?:[^"]|"")*)"/giu },
|
|
18
|
-
{ name: '*db.Execute', re: /\b(?:\p{L}[\p{L}\p{N}_]*)?db\b(?:\(\))?\.Execute\s+"((?:[^"]|"")*)"/giu },
|
|
19
|
-
// Fix 4 (Issue #4): inline-literal forms `getdb().Execute "..."` and
|
|
20
|
-
// `getdb().OpenRecordset "..."` — the variable form is covered by
|
|
21
|
-
// SQL_VAR_EXEC_RE but the direct-literal form was missing.
|
|
22
|
-
{ name: 'getdb().Execute', re: /\bgetdb\(\)\.Execute\s+"((?:[^"]|"")*)"/g },
|
|
23
|
-
{ name: 'getdb().OpenRecordset', re: /\bgetdb\(\)\.OpenRecordset\s+"((?:[^"]|"")*)"/g },
|
|
24
|
-
];
|
|
39
|
+
const sql_table_scan_1 = require("../sql-table-scan");
|
|
25
40
|
/** SQL assigned to a local variable, e.g. `m_SQL = "SELECT ..." & ...`. */
|
|
26
41
|
const SQL_VAR_ASSIGN_RE = /^\s*(\p{L}[\p{L}\p{N}_]*)\s*=\s*(.*)$/iu;
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* `
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* the
|
|
59
|
-
*
|
|
60
|
-
* The
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
42
|
+
/**
|
|
43
|
+
* `metadata.synthesizedBy` for a table reached through a SQL wrapper call
|
|
44
|
+
* (`DoCmd.RunSQL`, `db.Execute`, `db.OpenRecordset`, …).
|
|
45
|
+
*/
|
|
46
|
+
const SQL_TABLE_SYNTHESIZER = 'vba-sql-table';
|
|
47
|
+
/**
|
|
48
|
+
* Issue #252 — `metadata.synthesizedBy` for a table reached through SQL
|
|
49
|
+
* assigned to a binding property at RUNTIME. Deliberately distinct from the
|
|
50
|
+
* `.form.txt` sweep's static `vba-row-source` so an audit can tell the two
|
|
51
|
+
* provenances apart: one is what the designer stored, the other is what the
|
|
52
|
+
* code actually binds.
|
|
53
|
+
*/
|
|
54
|
+
exports.RUNTIME_BINDING_SYNTHESIZER = 'vba-row-source-dynamic';
|
|
55
|
+
/**
|
|
56
|
+
* Issue #252 — one receiver-chain segment on the left of a binding
|
|
57
|
+
* assignment: a plain identifier or a `[bracketed name]`, optionally
|
|
58
|
+
* followed by a single call/index parenthesis (`Controls("cbo")`,
|
|
59
|
+
* `Item(0)`). Parentheses are matched non-nested on purpose — a
|
|
60
|
+
* bounded, backtracking-safe shape for a per-line hot path.
|
|
61
|
+
*/
|
|
62
|
+
const BINDING_SEGMENT = String.raw `(?:\[[^\]\r\n]+\]|[\p{L}_][\p{L}\p{N}_]*)(?:\s*\([^()\r\n]*\))?`;
|
|
63
|
+
/**
|
|
64
|
+
* Issue #252 — `<receiver>.(RowSource|RecordSource|ControlSource|Filter|OrderBy) = <value>`.
|
|
65
|
+
*
|
|
66
|
+
* Group 1 is the property, group 2 the raw right-hand side.
|
|
67
|
+
*
|
|
68
|
+
* Shape notes, because each piece earns its place:
|
|
69
|
+
*
|
|
70
|
+
* - Anchored at `^\s*`, and the receiver is built out of identifier
|
|
71
|
+
* segments joined by `.` or `!` — never a permissive `.*`. That is what
|
|
72
|
+
* rejects a COMPARISON (`If Me.Filter = "…" Then`): `If` is an
|
|
73
|
+
* identifier segment but no `.` follows it, and the pattern cannot
|
|
74
|
+
* restart mid-line. A `'` comment line is rejected by the same anchor.
|
|
75
|
+
* - The receiver chain is OPTIONAL so the bare `With` form
|
|
76
|
+
* (`With Me.cbo` … `.RowSource = "…"`) resolves; that spelling is
|
|
77
|
+
* pervasive in real Access code.
|
|
78
|
+
* - `(?![\p{L}\p{N}_])` after the property name keeps `Me.FilterOn = True`
|
|
79
|
+
* and `Me.OrderByOn = True` out — they are booleans, not SQL.
|
|
80
|
+
* - Every quantifier is bounded by a mandatory separator (`.`/`!`/`(`), so
|
|
81
|
+
* the pattern cannot backtrack catastrophically on a long line.
|
|
82
|
+
*/
|
|
83
|
+
const BINDING_ASSIGN_RE = new RegExp(String.raw `^\s*(?:${BINDING_SEGMENT}(?:\s*[.!]\s*${BINDING_SEGMENT})*)?\s*[.!]\s*(RowSource|RecordSource|ControlSource|Filter|OrderBy)(?![\p{L}\p{N}_])\s*=\s*(.+)$`, 'iu');
|
|
84
|
+
/** Issue #252 — a right-hand side that is exactly one bare identifier. */
|
|
85
|
+
const BINDING_VAR_RE = /^\p{L}[\p{L}\p{N}_]*$/u;
|
|
86
|
+
/**
|
|
87
|
+
* Issue #244 — the two execution-site regexes.
|
|
88
|
+
*
|
|
89
|
+
* Both capture the RECEIVER (group 1) and the METHOD (group 2) generically
|
|
90
|
+
* and leave the "is this a database handle?" decision to
|
|
91
|
+
* {@link receiverMatches}, driven by the wrapper list. The pre-#244 shape
|
|
92
|
+
* hard-coded the receiver as "any identifier ENDING in `db`", which silently
|
|
93
|
+
* dropped every per-backend accessor a real multi-database Access project
|
|
94
|
+
* uses (`getdbHPS()`, `getdbExpedientes()`, `dbToUse`, `m_dbLanzadera`, and
|
|
95
|
+
* every DAO `QueryDef` receiver).
|
|
96
|
+
*
|
|
97
|
+
* Shape notes, in the order they appear:
|
|
98
|
+
*
|
|
99
|
+
* - `(?:\(\))?` — the accessor-call form `getdb().Execute`.
|
|
100
|
+
* - `(?=[\s("])` / `(?=[\s(])` — the method name must END at whitespace, an
|
|
101
|
+
* opening paren, or (literal form) the opening quote. Without it the
|
|
102
|
+
* greedy method group can give characters back and read `db.Executed` as
|
|
103
|
+
* method `Execute` + argument `d`.
|
|
104
|
+
* - `\s*\(?\s*` — the parenthesised call form. Its absence in the literal
|
|
105
|
+
* regex was the second defect the issue reports:
|
|
106
|
+
* `getdb().OpenRecordset("SELECT * FROM TbX")` fell through BOTH the
|
|
107
|
+
* literal path (which demanded whitespace before the quote) and the
|
|
108
|
+
* variable path (a literal is not an identifier), so the statement
|
|
109
|
+
* produced no table reference at all.
|
|
110
|
+
*
|
|
111
|
+
* Every quantifier is bounded and the alternatives inside the literal body
|
|
112
|
+
* (`[^"]` vs `""`) are disjoint, so neither regex can backtrack
|
|
113
|
+
* catastrophically — which is also why config entries are identifier
|
|
114
|
+
* fragments and never raw regex.
|
|
115
|
+
*/
|
|
116
|
+
const SQL_WRAPPER_LITERAL_RE = /\b(\p{L}[\p{L}\p{N}_]*)(?:\(\))?\.(\p{L}[\p{L}\p{N}_]*)(?=[\s("])\s*\(?\s*"((?:[^"]|"")*)"/giu;
|
|
117
|
+
/**
|
|
118
|
+
* Variable form — `getdb().Execute strSQL`, `DoCmd.RunSQL(strSQL)`,
|
|
119
|
+
* `qdf.Execute sql`. Group 3 is the identifier, resolved against the
|
|
120
|
+
* `sqlVariables` map that `trackSqlVariableAssignment` fills with
|
|
121
|
+
* `&`-accumulate semantics (Issue #13). We deliberately do NOT try to parse
|
|
122
|
+
* what the variable points at here; unresolved identifiers are skipped.
|
|
123
|
+
*/
|
|
124
|
+
const SQL_WRAPPER_VAR_RE = /\b(\p{L}[\p{L}\p{N}_]*)(?:\(\))?\.(\p{L}[\p{L}\p{N}_]*)(?=[\s(])\s*\(?\s*(\p{L}[\p{L}\p{N}_]*)\s*\)?/giu;
|
|
125
|
+
/**
|
|
126
|
+
* The methods a BARE identifier entry (`"getdb"`) implies. An explicit
|
|
127
|
+
* `receiver.method` entry (`"cnn.Execute"`) names its own method and ignores
|
|
128
|
+
* this set.
|
|
129
|
+
*/
|
|
130
|
+
const DEFAULT_WRAPPER_METHODS = new Set([
|
|
131
|
+
'openrecordset',
|
|
132
|
+
'execute',
|
|
133
|
+
]);
|
|
134
|
+
/**
|
|
135
|
+
* Issue #244 — the wrapper list used when `codegraph.json` →
|
|
136
|
+
* `vba.sqlWrappers` is absent. A STRICT SUPERSET of the pre-#244 behaviour:
|
|
137
|
+
*
|
|
138
|
+
* - `db` covers every receiver the old `…db\b` regex reached (see
|
|
139
|
+
* {@link receiverMatches} for the suffix arm that preserves it) plus the
|
|
140
|
+
* `db`-prefixed locals it missed (`dbUse`, `dbToUse`, `m_dbLanzadera`).
|
|
141
|
+
* - `getdb` covers the per-backend accessor family (`getdbHPS()`,
|
|
142
|
+
* `getdbExpedientes()`, `getdbLanzadera()`) the issue measured at 26-36%
|
|
143
|
+
* of all execution sites.
|
|
144
|
+
* - `CurrentDb` / `DBEngine` are the Access built-ins.
|
|
145
|
+
* - `qd` / `qdf` are the conventional DAO `QueryDef` receivers.
|
|
146
|
+
* - `DoCmd.RunSQL` is the Access statement form (both the literal and the
|
|
147
|
+
* variable spelling, previously two dedicated regexes).
|
|
148
|
+
* - `Connection.Execute` / `Recordset.Open` are the ADO pair.
|
|
149
|
+
*
|
|
150
|
+
* Project-specific accessors that do not fit these names are added through
|
|
151
|
+
* `vba.sqlWrappers`, which EXTENDS this list rather than replacing it.
|
|
152
|
+
*/
|
|
153
|
+
exports.DEFAULT_SQL_WRAPPERS = [
|
|
154
|
+
'db',
|
|
155
|
+
'getdb',
|
|
156
|
+
'CurrentDb',
|
|
157
|
+
'DBEngine',
|
|
158
|
+
'qd',
|
|
159
|
+
'qdf',
|
|
160
|
+
'DoCmd.RunSQL',
|
|
161
|
+
'Connection.Execute',
|
|
162
|
+
'Recordset.Open',
|
|
163
|
+
];
|
|
164
|
+
/**
|
|
165
|
+
* Parse the plain-string wrapper entries into matchers and pair them with a
|
|
166
|
+
* fresh pair of scanning RegExps.
|
|
167
|
+
*
|
|
168
|
+
* `configured` entries are APPENDED to {@link DEFAULT_SQL_WRAPPERS}, never
|
|
169
|
+
* substituted for them: a project that names its own accessor must not lose
|
|
170
|
+
* `CurrentDb` in the trade. Entries are accepted in exactly two forms — a
|
|
171
|
+
* bare identifier fragment (`"getdb"`) or a `receiver.method` pair
|
|
172
|
+
* (`"cnn.Execute"`). Anything else is dropped here; `project-config.ts`
|
|
173
|
+
* already warned about it at load time.
|
|
174
|
+
*/
|
|
175
|
+
function compileSqlWrappers(configured) {
|
|
176
|
+
const matchers = [];
|
|
177
|
+
const seen = new Set();
|
|
178
|
+
for (const raw of [...exports.DEFAULT_SQL_WRAPPERS, ...(configured ?? [])]) {
|
|
179
|
+
const entry = (raw ?? '').trim().toLowerCase();
|
|
180
|
+
if (!entry || seen.has(entry))
|
|
181
|
+
continue;
|
|
182
|
+
const dot = entry.indexOf('.');
|
|
183
|
+
if (dot < 0) {
|
|
184
|
+
if (!/^\p{L}[\p{L}\p{N}_]*$/u.test(entry))
|
|
185
|
+
continue;
|
|
186
|
+
seen.add(entry);
|
|
187
|
+
matchers.push({ receiver: entry, method: null });
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const receiver = entry.slice(0, dot);
|
|
191
|
+
const method = entry.slice(dot + 1);
|
|
192
|
+
if (!/^\p{L}[\p{L}\p{N}_]*$/u.test(receiver) ||
|
|
193
|
+
!/^\p{L}[\p{L}\p{N}_]*$/u.test(method)) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
seen.add(entry);
|
|
197
|
+
matchers.push({ receiver, method });
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
literalRe: new RegExp(SQL_WRAPPER_LITERAL_RE.source, SQL_WRAPPER_LITERAL_RE.flags),
|
|
201
|
+
varRe: new RegExp(SQL_WRAPPER_VAR_RE.source, SQL_WRAPPER_VAR_RE.flags),
|
|
202
|
+
matchers,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* The default wrappers, compiled once for the whole module. Used by any
|
|
207
|
+
* `VbaExtractorContext` built without options (tests, out-of-repo callers) so
|
|
208
|
+
* "no config" behaves exactly like "config absent" and still never compiles a
|
|
209
|
+
* RegExp per line. Safe to share: `scanSqlInLine` resets `lastIndex` before
|
|
210
|
+
* every scan and never re-enters itself.
|
|
211
|
+
*/
|
|
212
|
+
let defaultCompiled = null;
|
|
213
|
+
function defaultSqlWrappers() {
|
|
214
|
+
if (defaultCompiled === null)
|
|
215
|
+
defaultCompiled = compileSqlWrappers();
|
|
216
|
+
return defaultCompiled;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Does `receiver` name a database handle under `pattern`?
|
|
220
|
+
*
|
|
221
|
+
* A leading `m_` / `p_` scope prefix is stripped first — `m_dbLanzadera` is
|
|
222
|
+
* the same handle as `dbLanzadera`, and VBA codebases use both spellings for
|
|
223
|
+
* the same variable.
|
|
224
|
+
*
|
|
225
|
+
* Then TWO arms, both comparing case-insensitively:
|
|
226
|
+
*
|
|
227
|
+
* - PREFIX — the arm this issue adds, and the one every per-backend
|
|
228
|
+
* accessor needs: `getdbHPS`, `getdbExpedientes`, `dbUse`, `dbToUse`.
|
|
229
|
+
* The pattern must END AT A SEGMENT BOUNDARY: what follows it is either
|
|
230
|
+
* nothing, or a character that is neither a lowercase letter nor `_`
|
|
231
|
+
* (a capital or a digit).
|
|
232
|
+
* Without that, `db` would also claim `dbg`, `dbase` and `db_test` —
|
|
233
|
+
* names that merely START with the same two letters. Every execution
|
|
234
|
+
* receiver in the three measured corpora (`getdbNC`, `getdbAGEDO`,
|
|
235
|
+
* `dbUse`, `p_db`, `qdf`, …) clears the boundary.
|
|
236
|
+
* - SUFFIX — `MiBaseDatosdb` for pattern `db`. This is what the pre-#244
|
|
237
|
+
* regex `\b(?:\p{L}[\p{L}\p{N}_]*)?db\b` matched, kept so the defaults
|
|
238
|
+
* stay a strict superset of the old behaviour instead of trading one
|
|
239
|
+
* blind spot for another.
|
|
240
|
+
*
|
|
241
|
+
* The method check in {@link matchesWrapper} is the second gate: `dbg.Print`
|
|
242
|
+
* fails both the boundary rule AND the method rule.
|
|
243
|
+
*/
|
|
244
|
+
function receiverMatches(receiver, pattern) {
|
|
245
|
+
const lower = receiver.toLowerCase();
|
|
246
|
+
const bare = lower.startsWith('m_') || lower.startsWith('p_') ? lower.slice(2) : lower;
|
|
247
|
+
if (bare.endsWith(pattern))
|
|
248
|
+
return true;
|
|
249
|
+
if (!bare.startsWith(pattern))
|
|
250
|
+
return false;
|
|
251
|
+
if (bare.length === pattern.length)
|
|
252
|
+
return true;
|
|
253
|
+
// Segment boundary: read the ORIGINAL (un-lowercased) character so
|
|
254
|
+
// `dbUse` splits at the capital and `dbase` does not split at all.
|
|
255
|
+
const next = receiver.slice(receiver.length - bare.length).charAt(pattern.length);
|
|
256
|
+
return next !== '_' && next === next.toUpperCase();
|
|
257
|
+
}
|
|
258
|
+
/** Is `<receiver>.<method>` an execution site under any configured wrapper? */
|
|
259
|
+
function matchesWrapper(wrappers, receiver, method) {
|
|
260
|
+
const methodLower = method.toLowerCase();
|
|
261
|
+
for (const matcher of wrappers.matchers) {
|
|
262
|
+
const methodOk = matcher.method === null
|
|
263
|
+
? DEFAULT_WRAPPER_METHODS.has(methodLower)
|
|
264
|
+
: matcher.method === methodLower;
|
|
265
|
+
if (!methodOk)
|
|
266
|
+
continue;
|
|
267
|
+
if (receiverMatches(receiver, matcher.receiver))
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
return false;
|
|
91
271
|
}
|
|
92
272
|
/**
|
|
93
273
|
* Regex matching the chained `& "..."` literals that may follow a
|
|
@@ -101,70 +281,25 @@ function classifySqlAccess(sqlString, clause) {
|
|
|
101
281
|
* Cross-physical-line concat via `_` continuation is OUT OF SCOPE for
|
|
102
282
|
* v1 (deferred; see commit message).
|
|
103
283
|
*/
|
|
104
|
-
const SQL_WRAPPER_CHAIN_RE = /&\s*"((?:[^"]|"")*)"/g;
|
|
105
284
|
/**
|
|
106
285
|
* Given the text that follows a SQL wrapper's first literal on the same
|
|
107
|
-
* physical line, return
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
286
|
+
* physical line, return one fragment per `&`-concatenated operand in
|
|
287
|
+
* source order. Each fragment is either the CONTENT of a `"..."` literal
|
|
288
|
+
* or a `?` sentinel for any non-literal operand (a variable, function
|
|
289
|
+
* call, expression). The `?` sentinel can never match the
|
|
290
|
+
* `scanSqlTables` identifier class, so `"DELETE FROM " & tabla & " WHERE x"`
|
|
291
|
+
* becomes `"DELETE FROM ? WHERE x"` — the reserved-word reject list
|
|
292
|
+
* drops the `WHERE` capture and no `vba-sql-table` edge is emitted.
|
|
293
|
+
*
|
|
294
|
+
* Operates per-physical-line only — VBA `_` line continuation across
|
|
295
|
+
* physical lines is handled separately by `collectConcatFragments` for
|
|
296
|
+
* the variable-assignment path (see below).
|
|
111
297
|
*/
|
|
112
298
|
function collectSqlWrapperChain(rest) {
|
|
113
|
-
|
|
114
|
-
const re = new RegExp(SQL_WRAPPER_CHAIN_RE.source, SQL_WRAPPER_CHAIN_RE.flags);
|
|
115
|
-
let m;
|
|
116
|
-
while ((m = re.exec(rest)) !== null) {
|
|
117
|
-
out.push(m[1] ?? '');
|
|
118
|
-
}
|
|
119
|
-
return out;
|
|
120
|
-
}
|
|
121
|
-
function scanSqlInLine(ctx, line, lineNum, dedupe, sqlVariables) {
|
|
122
|
-
for (const { re } of SQL_WRAPPERS) {
|
|
123
|
-
// Each wrapper regex is stateful (has /g); reset before use.
|
|
124
|
-
const localRe = new RegExp(re.source, re.flags);
|
|
125
|
-
let m;
|
|
126
|
-
while ((m = localRe.exec(line)) !== null) {
|
|
127
|
-
const firstLiteral = m[1] ?? '';
|
|
128
|
-
// After the wrapper regex consumes up to and including the closing
|
|
129
|
-
// `"` of the first literal, walk the rest of the line for any
|
|
130
|
-
// `& "..."` chains and concatenate every literal's content. Joining
|
|
131
|
-
// with a space (mirrors `collectStringLiteralText`) keeps adjacent
|
|
132
|
-
// `FROM tblA` & `FROM tblB` separated so `SQL_TABLE_RE` finds both.
|
|
133
|
-
const rest = line.slice(m.index + m[0].length);
|
|
134
|
-
const chain = collectSqlWrapperChain(rest);
|
|
135
|
-
const joined = [firstLiteral, ...chain].join(' ');
|
|
136
|
-
emitSqlTableReferences(ctx, joined, lineNum, dedupe);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
const localRe = new RegExp(SQL_VAR_EXEC_RE.source, SQL_VAR_EXEC_RE.flags);
|
|
140
|
-
let vm;
|
|
141
|
-
while ((vm = localRe.exec(line)) !== null) {
|
|
142
|
-
const varName = (vm[1] ?? '').toLowerCase();
|
|
143
|
-
const sqlString = sqlVariables.get(varName);
|
|
144
|
-
if (!sqlString)
|
|
145
|
-
continue;
|
|
146
|
-
emitSqlTableReferences(ctx, sqlString, lineNum, dedupe);
|
|
147
|
-
}
|
|
148
|
-
// Issue #42: `DoCmd.RunSQL <identifier>` (variable form). Mirrors the
|
|
149
|
-
// SQL_VAR_EXEC_RE path above but for the Access-style `DoCmd.RunSQL`
|
|
150
|
-
// idiom — the dominant pattern in real-world VBA modules. Resolve the
|
|
151
|
-
// captured identifier against `sqlVariables` (populated by
|
|
152
|
-
// `trackSqlVariableAssignment` with `&`-accumulate semantics, Issue
|
|
153
|
-
// #13) and feed the resolved SQL string into `emitSqlTableReferences`.
|
|
154
|
-
// Unresolved identifiers (no row in the map) are silently skipped —
|
|
155
|
-
// same graceful-no-op contract as SQL_VAR_EXEC_RE.
|
|
156
|
-
const docmdLocalRe = new RegExp(SQL_VAR_DOCMD_RUNSQL_RE.source, SQL_VAR_DOCMD_RUNSQL_RE.flags);
|
|
157
|
-
let dm;
|
|
158
|
-
while ((dm = docmdLocalRe.exec(line)) !== null) {
|
|
159
|
-
const varName = (dm[1] ?? '').toLowerCase();
|
|
160
|
-
const sqlString = sqlVariables.get(varName);
|
|
161
|
-
if (!sqlString)
|
|
162
|
-
continue;
|
|
163
|
-
emitSqlTableReferences(ctx, sqlString, lineNum, dedupe);
|
|
164
|
-
}
|
|
299
|
+
return collectConcatFragments(rest);
|
|
165
300
|
}
|
|
166
301
|
/**
|
|
167
|
-
* #13
|
|
302
|
+
* Issue #13: `sql = sql & "..."` (self-referential concatenation) must
|
|
168
303
|
* ACCUMULATE the new fragment onto whatever was already tracked for
|
|
169
304
|
* `varName`, not overwrite it. Overwriting silently dropped earlier
|
|
170
305
|
* fragments' tables — typically the initial `FROM <table>` in
|
|
@@ -196,34 +331,473 @@ function trackSqlVariableAssignment(lines, lineIndex, sqlVariables) {
|
|
|
196
331
|
sqlVariables.set(varName, newFragment);
|
|
197
332
|
}
|
|
198
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Walk the lines starting at `startIndex` and collect every
|
|
336
|
+
* `&`-concatenated fragment — `"..."` literal CONTENT or a `?`
|
|
337
|
+
* sentinel for non-literal operands. Multi-line concat (via `_`
|
|
338
|
+
* continuation) is handled by carrying on while the current physical
|
|
339
|
+
* line ends with `&`.
|
|
340
|
+
*
|
|
341
|
+
* Replaces the legacy implementation that used `extractStringLiterals`
|
|
342
|
+
* to extract every `"..."` and silently dropped every non-literal
|
|
343
|
+
* operand between them — the source of Issue #203's silently-wrong
|
|
344
|
+
* `vba-sql-table` captures.
|
|
345
|
+
*/
|
|
199
346
|
function collectStringLiteralText(lines, startIndex) {
|
|
200
347
|
const fragments = [];
|
|
201
348
|
for (let i = startIndex; i < lines.length; i++) {
|
|
202
349
|
const line = lines[i] ?? '';
|
|
203
|
-
|
|
204
|
-
fragments.push(lit.text);
|
|
205
|
-
}
|
|
350
|
+
fragments.push(...collectConcatFragments(line));
|
|
206
351
|
if (!line.trimEnd().endsWith('&'))
|
|
207
352
|
break;
|
|
208
353
|
}
|
|
209
354
|
return fragments.join(' ');
|
|
210
355
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
356
|
+
/**
|
|
357
|
+
* Walk a single source string and emit one fragment per
|
|
358
|
+
* `&`-concatenated operand.
|
|
359
|
+
*
|
|
360
|
+
* - A `"..."` literal → its content (with `""` doubled-quote escapes
|
|
361
|
+
* collapsed to a single `"`).
|
|
362
|
+
* - A non-literal operand (any non-whitespace token — variable,
|
|
363
|
+
* function call, expression, the `m_SQL = ` prefix on an
|
|
364
|
+
* assignment) that sits BETWEEN two operands in the chain → emit
|
|
365
|
+
* a `?` sentinel so the gap can never match an identifier in
|
|
366
|
+
* `scanSqlTables`.
|
|
367
|
+
* - Leading / trailing / inter-fragment whitespace is skipped (the
|
|
368
|
+
* `[...].join(' ')` in the callers handles the gap).
|
|
369
|
+
*
|
|
370
|
+
* Implementation: a small state machine that walks the string,
|
|
371
|
+
* alternating between two modes:
|
|
372
|
+
*
|
|
373
|
+
* 1. IN-LITERAL — collect until matching `"`, honour `""` escapes.
|
|
374
|
+
* 2. IN-GAP — scan until next `&` (concat operator) or next `"` (start
|
|
375
|
+
* of next literal). If the gap contained any non-whitespace
|
|
376
|
+
* characters AND we previously emitted a literal, emit a `?`
|
|
377
|
+
* sentinel — that's the "we don't know this table" signal the
|
|
378
|
+
* regex needs.
|
|
379
|
+
*
|
|
380
|
+
* Why mode 2 stops at `"`: a `"` inside a non-literal operand is the
|
|
381
|
+
* start of the NEXT literal in the chain; treating it as part of the
|
|
382
|
+
* gap would silently consume a real literal and re-introduce the
|
|
383
|
+
* Issue #203 bug.
|
|
384
|
+
*/
|
|
385
|
+
function collectConcatFragments(src) {
|
|
386
|
+
const out = [];
|
|
387
|
+
let i = 0;
|
|
388
|
+
let lastEmittedLiteral = false;
|
|
389
|
+
while (i < src.length) {
|
|
390
|
+
const ch = src[i] ?? '';
|
|
391
|
+
if (ch === '"') {
|
|
392
|
+
// Mode 1 — literal.
|
|
393
|
+
let text = '';
|
|
394
|
+
i++;
|
|
395
|
+
while (i < src.length) {
|
|
396
|
+
const c = src[i] ?? '';
|
|
397
|
+
if (c === '"' && src[i + 1] === '"') {
|
|
398
|
+
text += '"';
|
|
399
|
+
i += 2;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (c === '"' || c === '\n') {
|
|
403
|
+
i++;
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
text += c;
|
|
407
|
+
i++;
|
|
408
|
+
}
|
|
409
|
+
out.push(text);
|
|
410
|
+
lastEmittedLiteral = true;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (/\s/.test(ch)) {
|
|
414
|
+
i++;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
// Mode 2 — gap (non-literal operand). Walk until the next `"` or
|
|
418
|
+
// `&`. If we saw non-whitespace content AND a literal came before
|
|
419
|
+
// us in this chain, emit the `?` sentinel so the gap can never
|
|
420
|
+
// bridge two SQL keywords.
|
|
421
|
+
let sawContent = false;
|
|
422
|
+
while (i < src.length) {
|
|
423
|
+
const c = src[i] ?? '';
|
|
424
|
+
if (c === '"' || c === '&')
|
|
425
|
+
break;
|
|
426
|
+
if (!/\s/.test(c))
|
|
427
|
+
sawContent = true;
|
|
428
|
+
i++;
|
|
429
|
+
}
|
|
430
|
+
if (src[i] === '&') {
|
|
431
|
+
i++;
|
|
432
|
+
}
|
|
433
|
+
if (sawContent && lastEmittedLiteral) {
|
|
434
|
+
out.push('?');
|
|
435
|
+
}
|
|
436
|
+
lastEmittedLiteral = false;
|
|
437
|
+
}
|
|
438
|
+
return out;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Issue #253: `QueryDefs("nombreConsulta")` names a saved query directly,
|
|
442
|
+
* with no wrapper method involved, so it gets its own pattern rather than a
|
|
443
|
+
* row in the wrapper table. String-literal argument only — a variable holds a
|
|
444
|
+
* name nothing static can know.
|
|
445
|
+
*/
|
|
446
|
+
const QUERY_DEFS_RE = /\bQueryDefs\s*\(\s*"([^"]*)"\s*\)/giu;
|
|
447
|
+
/**
|
|
448
|
+
* Issue #253: does this literal read as a SQL statement rather than the name
|
|
449
|
+
* of a saved query?
|
|
450
|
+
*
|
|
451
|
+
* The gate is deliberately WIDER than the four DML verbs the issue names. A
|
|
452
|
+
* literal is rejected as SQL if it contains any of these keywords anywhere,
|
|
453
|
+
* because the cost of the two mistakes is not symmetric: treating SQL as a
|
|
454
|
+
* query name emits a reference to a name no `queries/*.sql` will ever carry
|
|
455
|
+
* (a permanent `failed` reference and a confusing one), while treating an
|
|
456
|
+
* oddly-named query as SQL emits nothing at all. Silent beats wrong.
|
|
457
|
+
*/
|
|
458
|
+
const SQL_KEYWORD_RE = /\b(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE|JOIN|INTO|VALUES|SET|CREATE|ALTER|DROP|UNION|GROUP|ORDER|HAVING|TRANSFORM|PARAMETERS|EXEC|EXECUTE|DISTINCT|TABLE)\b/iu;
|
|
459
|
+
/**
|
|
460
|
+
* Issue #253: an Access object name — letters, digits, underscores and
|
|
461
|
+
* interior spaces, nothing else. No punctuation, no operators, no wildcards.
|
|
462
|
+
*
|
|
463
|
+
* The length cap is not cosmetic: it is the last line of defence against a
|
|
464
|
+
* long keyword-free string (a message, a `Value List` payload) being read as
|
|
465
|
+
* a query name. Access itself caps object names at 64 characters.
|
|
466
|
+
*/
|
|
467
|
+
const ACCESS_OBJECT_NAME_RE = /^[\p{L}_][\p{L}\p{N}_ ]{0,63}$/u;
|
|
468
|
+
/** True when a wrapper's literal argument names a saved query, not a statement. */
|
|
469
|
+
function looksLikeSavedQueryName(text) {
|
|
470
|
+
const trimmed = text.trim();
|
|
471
|
+
if (!trimmed)
|
|
472
|
+
return false;
|
|
473
|
+
if (SQL_KEYWORD_RE.test(trimmed))
|
|
474
|
+
return false;
|
|
475
|
+
return ACCESS_OBJECT_NAME_RE.test(trimmed);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Issue #253: emit ONE `dao-query` unresolved reference for a saved-query
|
|
479
|
+
* name, from the CALLING PROCEDURE (not the module).
|
|
480
|
+
*
|
|
481
|
+
* The procedure is the source on purpose: `SqlQueryExtractor` already links
|
|
482
|
+
* `query -> table`, so a `procedure -> query` reference is the hop that makes
|
|
483
|
+
* `procedure -> query -> table` traversable end to end. An edge hung off the
|
|
484
|
+
* module would leave that flow broken at its first hop.
|
|
485
|
+
*
|
|
486
|
+
* No node is created. The resolver binds this to the REAL `query` node built
|
|
487
|
+
* from `queries/<Name>.sql`, and declines when there is none — a name that
|
|
488
|
+
* matches neither a query nor a table stays an actionable `failed` reference
|
|
489
|
+
* rather than becoming a fabricated table placeholder.
|
|
490
|
+
*/
|
|
491
|
+
function emitSavedQueryReference(ctx, queryName, lineNum, column, caller, dedupe) {
|
|
492
|
+
const trimmed = queryName.trim();
|
|
493
|
+
if (!trimmed)
|
|
494
|
+
return;
|
|
495
|
+
// The `query-name:` prefix keeps this de-dup bucket disjoint from the table
|
|
496
|
+
// and external-backend buckets that share the same set.
|
|
497
|
+
const key = `${lineNum}:query-name:${trimmed.toLocaleLowerCase('en-US')}`;
|
|
498
|
+
if (dedupe.has(key))
|
|
499
|
+
return;
|
|
500
|
+
dedupe.add(key);
|
|
501
|
+
ctx.unresolvedReferences.push({
|
|
502
|
+
fromNodeId: ctx.findOrCreateFunctionNodeId(caller),
|
|
503
|
+
referenceName: trimmed,
|
|
504
|
+
referenceKind: 'dao-query',
|
|
505
|
+
line: lineNum,
|
|
506
|
+
column,
|
|
507
|
+
filePath: ctx.filePath,
|
|
508
|
+
language: 'vba',
|
|
509
|
+
metadata: { synthesizedBy: 'vba-query-name' },
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Issue #255 (task T13): the Access domain aggregate functions. Every one of
|
|
514
|
+
* them takes the DOMAIN — the name of a table or of a saved query — as its
|
|
515
|
+
* SECOND argument.
|
|
516
|
+
*
|
|
517
|
+
* The regex recognises the call shape only. The name gate and the emit path
|
|
518
|
+
* are T11's (`looksLikeSavedQueryName` / `emitSavedQueryReference`), so a
|
|
519
|
+
* domain name is answered by exactly the same "is this a saved query?" rule
|
|
520
|
+
* that already answers `OpenRecordset("qryX")` and `QueryDefs("qryX")` — one
|
|
521
|
+
* resolver, not two.
|
|
522
|
+
*
|
|
523
|
+
* The trailing `\s*\(` is load-bearing twice over: it anchors the name so a
|
|
524
|
+
* project helper called `DLookupSeguro(` can never be read as `DLookup`, and
|
|
525
|
+
* it hands {@link secondArgumentText} the paren to start walking from.
|
|
526
|
+
*/
|
|
527
|
+
const DOMAIN_FUNCTION_RE = /\b(DLookup|DCount|DSum|DMax|DMin|DAvg|DFirst|DLast)\s*\(/giu;
|
|
528
|
+
/**
|
|
529
|
+
* A domain argument we are willing to read: EXACTLY one string literal and
|
|
530
|
+
* nothing else. A variable (`DLookup("N", tabla)`) or an expression
|
|
531
|
+
* (`DLookup("N", "Tb" & sufijo)`) names something only the runtime knows, and
|
|
532
|
+
* is skipped in silence rather than guessed at.
|
|
533
|
+
*/
|
|
534
|
+
const LONE_STRING_LITERAL_RE = /^"((?:[^"]|"")*)"$/u;
|
|
535
|
+
/**
|
|
536
|
+
* Return the raw source text of the SECOND argument of the call whose opening
|
|
537
|
+
* paren sits at `openParen`, or `null` when the call has fewer than two
|
|
538
|
+
* arguments.
|
|
539
|
+
*
|
|
540
|
+
* Walks characters instead of splitting on `,` because two ordinary spellings
|
|
541
|
+
* break a naive split: a comma inside the criteria literal
|
|
542
|
+
* (`DLookup("N", "T", "Id In (1,2)")`) and a comma inside a nested call
|
|
543
|
+
* (`DLookup("N", "T", "Id=" & Nz(x, 0))`). Only a comma at depth 1 and outside
|
|
544
|
+
* a string literal separates arguments.
|
|
545
|
+
*/
|
|
546
|
+
function secondArgumentText(src, openParen) {
|
|
547
|
+
const args = [];
|
|
548
|
+
let current = '';
|
|
549
|
+
let depth = 1;
|
|
550
|
+
let i = openParen + 1;
|
|
551
|
+
while (i < src.length && args.length < 2) {
|
|
552
|
+
const ch = src[i] ?? '';
|
|
553
|
+
if (ch === '"') {
|
|
554
|
+
// Copy the literal through verbatim, honouring `""` escapes, so a comma
|
|
555
|
+
// or a paren inside it can never be read as structure.
|
|
556
|
+
current += ch;
|
|
557
|
+
i++;
|
|
558
|
+
while (i < src.length) {
|
|
559
|
+
const c = src[i] ?? '';
|
|
560
|
+
if (c === '"' && src[i + 1] === '"') {
|
|
561
|
+
current += '""';
|
|
562
|
+
i += 2;
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
current += c;
|
|
566
|
+
i++;
|
|
567
|
+
if (c === '"')
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
if (ch === '(') {
|
|
573
|
+
depth++;
|
|
574
|
+
current += ch;
|
|
575
|
+
i++;
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
if (ch === ')') {
|
|
579
|
+
depth--;
|
|
580
|
+
if (depth === 0) {
|
|
581
|
+
args.push(current);
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
current += ch;
|
|
585
|
+
i++;
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
if (ch === ',' && depth === 1) {
|
|
589
|
+
args.push(current);
|
|
590
|
+
current = '';
|
|
591
|
+
i++;
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
current += ch;
|
|
595
|
+
i++;
|
|
596
|
+
}
|
|
597
|
+
return args.length >= 2 ? args[1] ?? null : null;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Does the file being scanned declare its own procedure by this name?
|
|
601
|
+
*
|
|
602
|
+
* A project that ships its own `DLookup` wrapper means the call site is user
|
|
603
|
+
* code, not the Access built-in, and its second argument carries no promise of
|
|
604
|
+
* naming a domain. The call itself still resolves to that user code through
|
|
605
|
+
* the ordinary call-site scan — this gate only stops the extra domain
|
|
606
|
+
* reference from being invented on top of it.
|
|
607
|
+
*
|
|
608
|
+
* `localProcs` is keyed by the DECLARED spelling and VBA identifiers are
|
|
609
|
+
* case-insensitive, so the keys are compared folded. The procedures sweep is a
|
|
610
|
+
* PRE-walk (see `vba-extractor.ts`), so a helper declared further down the
|
|
611
|
+
* file is already visible by the time this line is scanned.
|
|
612
|
+
*/
|
|
613
|
+
function declaresOwnProcedure(ctx, name) {
|
|
614
|
+
const wanted = name.toLocaleLowerCase('en-US');
|
|
615
|
+
for (const declared of ctx.localProcs.keys()) {
|
|
616
|
+
if (declared.toLocaleLowerCase('en-US') === wanted)
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Issue #255: scan one line for domain-aggregate calls and emit the saved-query
|
|
623
|
+
* reference their second argument names.
|
|
624
|
+
*
|
|
625
|
+
* Deliberately NOT handled: a domain spelled as a full SQL statement
|
|
626
|
+
* (`DLookup("N", "SELECT Id FROM T")`), which Access also accepts. It is
|
|
627
|
+
* rejected by `looksLikeSavedQueryName`'s verb gate and produces nothing —
|
|
628
|
+
* the same silence T11 chose for every literal it cannot classify.
|
|
629
|
+
*/
|
|
630
|
+
function scanDomainFunctionsInLine(ctx, line, lineNum, dedupe, caller) {
|
|
631
|
+
DOMAIN_FUNCTION_RE.lastIndex = 0;
|
|
632
|
+
let m;
|
|
633
|
+
while ((m = DOMAIN_FUNCTION_RE.exec(line)) !== null) {
|
|
634
|
+
if (declaresOwnProcedure(ctx, m[1] ?? ''))
|
|
635
|
+
continue;
|
|
636
|
+
// `m[0]` ends with the opening paren, so its last index is where the
|
|
637
|
+
// argument list starts.
|
|
638
|
+
const raw = secondArgumentText(line, m.index + m[0].length - 1);
|
|
639
|
+
if (raw === null)
|
|
640
|
+
continue;
|
|
641
|
+
const literal = LONE_STRING_LITERAL_RE.exec(raw.trim());
|
|
642
|
+
if (!literal)
|
|
643
|
+
continue;
|
|
644
|
+
const domain = (literal[1] ?? '').replace(/""/g, '"');
|
|
645
|
+
if (!looksLikeSavedQueryName(domain))
|
|
646
|
+
continue;
|
|
647
|
+
emitSavedQueryReference(ctx, domain, lineNum, m.index, caller, dedupe);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function scanSqlInLine(ctx, line, lineNum, dedupe, sqlVariables, caller) {
|
|
651
|
+
// Issue #244: one compiled wrapper set per extractor, cached on the
|
|
652
|
+
// context. A context built without options (tests, out-of-repo callers)
|
|
653
|
+
// falls back to the module-level defaults — never to a per-line compile.
|
|
654
|
+
const wrappers = ctx.sqlWrappers ?? defaultSqlWrappers();
|
|
655
|
+
// Literal form — `getdb().Execute "DELETE FROM T"`,
|
|
656
|
+
// `CurrentDb.OpenRecordset("SELECT * FROM T")`, `DoCmd.RunSQL "…"`.
|
|
657
|
+
const literalRe = wrappers.literalRe;
|
|
658
|
+
literalRe.lastIndex = 0;
|
|
659
|
+
let m;
|
|
660
|
+
while ((m = literalRe.exec(line)) !== null) {
|
|
661
|
+
if (!matchesWrapper(wrappers, m[1] ?? '', m[2] ?? ''))
|
|
662
|
+
continue;
|
|
663
|
+
const firstLiteral = m[3] ?? '';
|
|
664
|
+
// After the wrapper regex consumes up to and including the closing
|
|
665
|
+
// `"` of the first literal, walk the rest of the line for any
|
|
666
|
+
// `& "..."` chains and concatenate every literal's content. Joining
|
|
667
|
+
// with a space (mirrors `collectStringLiteralText`) keeps adjacent
|
|
668
|
+
// `FROM tblA` & `FROM tblB` separated so `scanSqlTables` finds both.
|
|
669
|
+
const rest = line.slice(m.index + m[0].length);
|
|
670
|
+
const chain = collectSqlWrapperChain(rest);
|
|
671
|
+
const joined = [firstLiteral, ...chain].join(' ');
|
|
672
|
+
// Issue #253: a wrapper handed a literal that is NOT a statement but IS a
|
|
673
|
+
// bare Access object name is naming a saved query. Verb detection wins:
|
|
674
|
+
// anything that reads as SQL takes the table path below, unchanged.
|
|
675
|
+
if (caller && chain.length === 0 && looksLikeSavedQueryName(joined)) {
|
|
676
|
+
emitSavedQueryReference(ctx, joined, lineNum, m.index, caller, dedupe);
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
emitSqlTableReferences(ctx, joined, lineNum, dedupe);
|
|
680
|
+
}
|
|
681
|
+
// Variable form — `getdb().Execute strSQL`, `qdf.Execute sql`,
|
|
682
|
+
// `DoCmd.RunSQL(strSQL)` (Issue #42). The captured identifier is resolved
|
|
683
|
+
// against `sqlVariables` (populated by `trackSqlVariableAssignment` with
|
|
684
|
+
// `&`-accumulate semantics, Issue #13); an identifier with no row in the
|
|
685
|
+
// map is silently skipped — we never guess what a variable holds.
|
|
686
|
+
const varRe = wrappers.varRe;
|
|
687
|
+
varRe.lastIndex = 0;
|
|
688
|
+
let vm;
|
|
689
|
+
while ((vm = varRe.exec(line)) !== null) {
|
|
690
|
+
if (!matchesWrapper(wrappers, vm[1] ?? '', vm[2] ?? ''))
|
|
691
|
+
continue;
|
|
692
|
+
const varName = (vm[3] ?? '').toLowerCase();
|
|
693
|
+
const sqlString = sqlVariables.get(varName);
|
|
694
|
+
if (!sqlString)
|
|
695
|
+
continue;
|
|
696
|
+
// A variable that accumulated a bare name rather than a statement names a
|
|
697
|
+
// saved query, exactly as the literal form does.
|
|
698
|
+
if (caller && looksLikeSavedQueryName(sqlString)) {
|
|
699
|
+
emitSavedQueryReference(ctx, sqlString, lineNum, vm.index, caller, dedupe);
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
emitSqlTableReferences(ctx, sqlString, lineNum, dedupe);
|
|
703
|
+
}
|
|
704
|
+
// Issue #253: `db.QueryDefs("nombreConsulta")` names a saved query with no
|
|
705
|
+
// wrapper method in sight, so it is scanned independently of the wrapper
|
|
706
|
+
// table. Literal argument only.
|
|
707
|
+
if (caller) {
|
|
708
|
+
QUERY_DEFS_RE.lastIndex = 0;
|
|
709
|
+
let qm;
|
|
710
|
+
while ((qm = QUERY_DEFS_RE.exec(line)) !== null) {
|
|
711
|
+
const name = qm[1] ?? '';
|
|
712
|
+
if (!looksLikeSavedQueryName(name))
|
|
713
|
+
continue;
|
|
714
|
+
emitSavedQueryReference(ctx, name, lineNum, qm.index, caller, dedupe);
|
|
715
|
+
}
|
|
716
|
+
// Issue #255: `DLookup("Nombre", "TbUsuarios", "Id=1")` and its seven
|
|
717
|
+
// siblings name their domain — a table or a saved query — in argument 2.
|
|
718
|
+
// Same gate, same emit path, same resolver as the saved-query names above.
|
|
719
|
+
scanDomainFunctionsInLine(ctx, line, lineNum, dedupe, caller);
|
|
720
|
+
}
|
|
721
|
+
// Issue #252 — SQL bound to a form/control property at runtime.
|
|
722
|
+
scanRuntimeBindingAssignment(ctx, line, lineNum, dedupe, sqlVariables);
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Issue #252 — SQL assigned to a binding property at runtime.
|
|
726
|
+
*
|
|
727
|
+
* This is the half of the data-binding problem the `.form.txt` sweep cannot
|
|
728
|
+
* see. The measured corpus binds in code, not in the designer: 161
|
|
729
|
+
* `.RowSource =` assignments across `.cls`/`.bas`, against ZERO
|
|
730
|
+
* `RecordSource` and ZERO `ControlSource` occurrences in `00_EXPEDIENTES`'s
|
|
731
|
+
* `.form.txt` files. Everything those statements touch was invisible.
|
|
732
|
+
*
|
|
733
|
+
* The right-hand side goes through the SAME pipeline the wrapper path uses —
|
|
734
|
+
* {@link collectConcatFragments} then `scanSqlTables` — deliberately, and not
|
|
735
|
+
* a parallel one. That is what makes the `?` sentinel for non-literal
|
|
736
|
+
* operands and the shared reserved-word reject list apply here for free:
|
|
737
|
+
* `.RowSource = "SELECT " & campo & " FROM " & tabla` becomes
|
|
738
|
+
* `SELECT ? FROM ?` and yields no table at all, rather than the confident
|
|
739
|
+
* wrong `FROM` / `WHERE` captures Issue #203 documents.
|
|
740
|
+
*
|
|
741
|
+
* A value that is NOT SQL — a bare saved-query name, a `Value List` payload,
|
|
742
|
+
* a plain field name on `ControlSource` — produces nothing here on its own,
|
|
743
|
+
* because `scanSqlTables` finds no clause keyword in it. Connecting a bare
|
|
744
|
+
* query name to its saved query is T11's job, not this one; guessing would
|
|
745
|
+
* be exactly the "confident wrong edge" the project forbids.
|
|
746
|
+
*/
|
|
747
|
+
function scanRuntimeBindingAssignment(ctx, line, lineNum, dedupe, sqlVariables) {
|
|
748
|
+
const m = BINDING_ASSIGN_RE.exec(line);
|
|
749
|
+
if (!m)
|
|
750
|
+
return;
|
|
751
|
+
const rhs = (m[2] ?? '').trim();
|
|
752
|
+
if (!rhs)
|
|
753
|
+
return;
|
|
754
|
+
// Literal form — one or more `"..."` operands, possibly `&`-chained.
|
|
755
|
+
const fragments = collectConcatFragments(rhs);
|
|
756
|
+
if (fragments.length > 0) {
|
|
757
|
+
emitSqlTableReferences(ctx, fragments.join(' '), lineNum, dedupe, exports.RUNTIME_BINDING_SYNTHESIZER);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
// Variable form — `.RowSource = strSQL`. Resolved against the SAME
|
|
761
|
+
// `sqlVariables` map the wrapper path uses, so the `&`-accumulate
|
|
762
|
+
// semantics of Issue #13 and the procedure scoping of Issue #204 apply
|
|
763
|
+
// unchanged. An identifier with no row in the map is skipped: we never
|
|
764
|
+
// guess what a variable holds.
|
|
765
|
+
if (!BINDING_VAR_RE.test(rhs))
|
|
766
|
+
return;
|
|
767
|
+
const sqlString = sqlVariables.get(rhs.toLowerCase());
|
|
768
|
+
if (!sqlString)
|
|
769
|
+
return;
|
|
770
|
+
emitSqlTableReferences(ctx, sqlString, lineNum, dedupe, exports.RUNTIME_BINDING_SYNTHESIZER);
|
|
771
|
+
}
|
|
772
|
+
function emitSqlTableReferences(ctx, sqlString, lineNum, dedupe, synthesizedBy = SQL_TABLE_SYNTHESIZER) {
|
|
773
|
+
// Issue #203: delegate to the shared scanner. It owns the
|
|
774
|
+
// `FROM/JOIN/INTO/UPDATE <table>` regex, the reserved-word reject
|
|
775
|
+
// list, the `?`-sentinel fallback (see `vba/sql-wrapper.ts:144-178`)
|
|
776
|
+
// and the read/write access direction.
|
|
777
|
+
//
|
|
778
|
+
// Issue #252: the de-dup key is namespaced by `synthesizedBy` so the
|
|
779
|
+
// runtime-binding path and the wrapper path keep disjoint buckets. A
|
|
780
|
+
// single line can legitimately be both (`Set rs = db.OpenRecordset(sql)`
|
|
781
|
+
// has no binding, but a `With` body can interleave the two shapes), and
|
|
782
|
+
// one path must never silence the other's edge.
|
|
783
|
+
for (const row of (0, sql_table_scan_1.scanSqlTables)(sqlString)) {
|
|
784
|
+
const key = `${synthesizedBy}:${lineNum}:${row.table}`;
|
|
785
|
+
if (dedupe.has(key))
|
|
786
|
+
continue;
|
|
787
|
+
dedupe.add(key);
|
|
788
|
+
ctx.emitReference(row.table, lineNum, 0, synthesizedBy, row.access);
|
|
789
|
+
}
|
|
790
|
+
// Issue #256: the Access `IN "<path>"` clause points the statement at
|
|
791
|
+
// another database file. It is a cross-backend edge, so it gets its
|
|
792
|
+
// own `file`-kind target rather than being mistaken for a table.
|
|
793
|
+
// The `external-db:` prefix keeps this de-dup bucket disjoint from the
|
|
794
|
+
// table bucket above (a table name can never contain a colon).
|
|
795
|
+
for (const backendPath of (0, sql_table_scan_1.scanSqlExternalBackends)(sqlString)) {
|
|
796
|
+
const key = `${lineNum}:external-db:${backendPath}`;
|
|
223
797
|
if (dedupe.has(key))
|
|
224
798
|
continue;
|
|
225
799
|
dedupe.add(key);
|
|
226
|
-
ctx.
|
|
800
|
+
ctx.emitExternalBackendReference(backendPath, lineNum, 0);
|
|
227
801
|
}
|
|
228
802
|
}
|
|
229
803
|
//# sourceMappingURL=sql-wrapper.js.map
|