@aroman22/codegraph-vba-darwin-arm64 1.15.0 → 1.17.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 +41 -0
- package/lib/dist/db/queries.d.ts.map +1 -1
- package/lib/dist/db/queries.js +78 -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/graph/behavior-evidence.d.ts +162 -0
- package/lib/dist/graph/behavior-evidence.d.ts.map +1 -0
- package/lib/dist/graph/behavior-evidence.js +529 -0
- package/lib/dist/graph/behavior-evidence.js.map +1 -0
- package/lib/dist/index.d.ts +22 -0
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +37 -1
- 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/server-instructions.d.ts +1 -1
- package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
- package/lib/dist/mcp/server-instructions.js +26 -4
- package/lib/dist/mcp/server-instructions.js.map +1 -1
- package/lib/dist/mcp/tools.d.ts +12 -0
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +92 -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/dist/utils/backtrace-helpers.d.ts +14 -2
- package/lib/dist/utils/backtrace-helpers.d.ts.map +1 -1
- package/lib/dist/utils/backtrace-helpers.js +81 -7
- package/lib/dist/utils/backtrace-helpers.js.map +1 -1
- package/lib/dist/utils/sql-impact-helpers.d.ts.map +1 -1
- package/lib/dist/utils/sql-impact-helpers.js +119 -30
- package/lib/dist/utils/sql-impact-helpers.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 +3 -1
- package/package.json +1 -1
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RULES = exports.DEFAULT_DISPLAY_CALLS = exports.DEFAULT_ERROR_CHANNEL_NAMES = void 0;
|
|
4
|
+
exports.newErrorPolicyState = newErrorPolicyState;
|
|
5
|
+
exports.closeErrorPolicy = closeErrorPolicy;
|
|
6
|
+
exports.createErrorPolicyClassifier = createErrorPolicyClassifier;
|
|
7
|
+
/**
|
|
8
|
+
* Error-policy sweep (issue #259, task E2 of `docs/vba-error-handling-plan.md`,
|
|
9
|
+
* extended by issue #260 / task E3).
|
|
10
|
+
*
|
|
11
|
+
* Records how each procedure handles errors as an `errorPolicy` object on the
|
|
12
|
+
* `function` node the procedures sweep already emitted. It adds **zero node
|
|
13
|
+
* kinds and zero edge kinds**, and emits neither a node nor an edge nor an
|
|
14
|
+
* unresolved reference — the node and edge totals of an indexed project are
|
|
15
|
+
* byte-for-byte what they were before this classifier existed. That is the
|
|
16
|
+
* deliberate conclusion of §4 of the plan: 96.5% of the corpus's 3,911 line
|
|
17
|
+
* labels are the same label (`errores`) doing the same job, so a label node
|
|
18
|
+
* would carry exactly one bit — "a handler exists" — which is a boolean field.
|
|
19
|
+
*
|
|
20
|
+
* Issue #260 keeps that invariant while making the handler region visible on
|
|
21
|
+
* the graph the extractor already builds: every edge and every unresolved
|
|
22
|
+
* reference emitted from inside a handler gets `metadata.inErrorHandler:
|
|
23
|
+
* true`, so `Riesgo.Guardar -> MsgBox` (runs only on failure) stops looking
|
|
24
|
+
* identical to `Riesgo.Guardar -> Escribir` (runs always). It is a field
|
|
25
|
+
* added to existing rows — no row is created, dropped or reordered.
|
|
26
|
+
*
|
|
27
|
+
* ## What it answers
|
|
28
|
+
*
|
|
29
|
+
* - "Which risky procedures have no error handling at all?" (§3.1) —
|
|
30
|
+
* `protection: 'none'`, which cannot be grepped: an ABSENCE scoped to a
|
|
31
|
+
* procedure body has no text to match.
|
|
32
|
+
* - "Which `On Error Resume Next` scopes are never closed?" (§3.2) —
|
|
33
|
+
* `resumeNextOpen: true`.
|
|
34
|
+
* - "Where does an error surface to the user?" (§3.3) — `behavior`.
|
|
35
|
+
* - "What does this procedure do ONLY when things go wrong?" (§3.4) — the
|
|
36
|
+
* edges and references carrying `metadata.inErrorHandler`.
|
|
37
|
+
*
|
|
38
|
+
* ## The precision gate
|
|
39
|
+
*
|
|
40
|
+
* **A label defined but never targeted by an `On Error GoTo` is control flow,
|
|
41
|
+
* not a handler.** 137 of the corpus's labels are exactly that (`siguiente`,
|
|
42
|
+
* `salir`, `fin`, `Teardown`). Treating one as a handler produces a
|
|
43
|
+
* confidently wrong answer to "does this procedure handle errors", which is
|
|
44
|
+
* the worst failure mode available here — so a label only opens a handler
|
|
45
|
+
* region when its name is in `targets`, the set of recorded `On Error GoTo`
|
|
46
|
+
* destinations.
|
|
47
|
+
*
|
|
48
|
+
* ## Boundaries — one detector, three consumers
|
|
49
|
+
*
|
|
50
|
+
* The procedure body's START comes from `ctx.functionNodeByStartLine`, which
|
|
51
|
+
* the procedures pre-walk fully populates before this classifier sees its
|
|
52
|
+
* first line. No second `PROC_RE` dispatch: "is this line a procedure
|
|
53
|
+
* declaration" is already answered by the node the extractor emitted, exactly
|
|
54
|
+
* as `scripts/vba-coverage-probe.mjs` derives its own procedure starts.
|
|
55
|
+
*
|
|
56
|
+
* The body's END is `PROCEDURE_END_RE` from `./constants` — the SAME constant
|
|
57
|
+
* `enums-consts.ts`'s `proc-end` rule dispatches and `call-sweep.ts` tests.
|
|
58
|
+
* The plan is explicit about this ("consume that signal, do not add a second
|
|
59
|
+
* end detector"; "Reuse it; do not write a second one"), and it is what makes
|
|
60
|
+
* the colon-separated single-line procedure form work here for free (#208).
|
|
61
|
+
* Unlike its two sibling consumers this one tests the MASKED line, so a
|
|
62
|
+
* string literal containing `": End Sub"` cannot close a procedure early
|
|
63
|
+
* (#209 discipline, applied consistently across this whole file).
|
|
64
|
+
*
|
|
65
|
+
* ## Agreement with the probe
|
|
66
|
+
*
|
|
67
|
+
* `scripts/vba-coverage-probe.mjs` measured the corpus census this task is
|
|
68
|
+
* accepted against, so the two classifiers must agree. `LINE_LABEL_RE` and
|
|
69
|
+
* `NOT_A_LABEL` below are therefore the probe's, not the simplified
|
|
70
|
+
* `^\s*(\w+):\s*$` sketched in the issue: the probe accepts the
|
|
71
|
+
* trailing-statement form (`errores: MsgBox "x"`), guards the
|
|
72
|
+
* `Foo bar:=1` named-argument form, guards statement keywords that can head a
|
|
73
|
+
* colon-terminated line, and accepts accented label names. Keeping the strict
|
|
74
|
+
* sketch instead would have classified every trailing-statement handler as a
|
|
75
|
+
* dangling target and disagreed with the census on both `handlerStartLine`
|
|
76
|
+
* and `danglingTarget`. The probe cannot import this module (it is a
|
|
77
|
+
* standalone ESM script that must also run against a shipped `dist/`), so the
|
|
78
|
+
* two copies are kept in sync by hand — change one, change the other.
|
|
79
|
+
*/
|
|
80
|
+
const constants_1 = require("./constants");
|
|
81
|
+
const error_channel_1 = require("./error-channel");
|
|
82
|
+
const text_utils_1 = require("./text-utils");
|
|
83
|
+
const labels_1 = require("./labels");
|
|
84
|
+
const rules_1 = require("./rules");
|
|
85
|
+
/**
|
|
86
|
+
* `On Error GoTo <label|0|-1>`. The numeric alternative is captured on
|
|
87
|
+
* purpose so the rule can tell a handler from a reset instead of the label
|
|
88
|
+
* rule silently swallowing `0` (which `\w+` would happily match).
|
|
89
|
+
*/
|
|
90
|
+
const ON_ERROR_GOTO_RE = /\bOn\s+Error\s+GoTo\s+(-?\d+|\p{L}[\p{L}\p{N}_]*)/iu;
|
|
91
|
+
/** Global twin of {@link ON_ERROR_GOTO_RE} — every site on one line. */
|
|
92
|
+
const ON_ERROR_GOTO_G = new RegExp(ON_ERROR_GOTO_RE.source, 'giu');
|
|
93
|
+
/** `On Error Resume Next` — opens a suppression scope. */
|
|
94
|
+
const ON_ERROR_RESUME_NEXT_RE = /\bOn\s+Error\s+Resume\s+Next\b/i;
|
|
95
|
+
/** Global twin of {@link ON_ERROR_RESUME_NEXT_RE}. */
|
|
96
|
+
const ON_ERROR_RESUME_NEXT_G = new RegExp(ON_ERROR_RESUME_NEXT_RE.source, 'gi');
|
|
97
|
+
/**
|
|
98
|
+
* `On Error GoTo 0` (disable the handler) and `On Error GoTo -1` (clear the
|
|
99
|
+
* current error). `-1` is valid VBA and appears ZERO times in this corpus;
|
|
100
|
+
* the plan asks for it to be treated as a reset anyway, with a fixture, so
|
|
101
|
+
* the zero-occurrence case stays pinned rather than rotting untested.
|
|
102
|
+
*
|
|
103
|
+
* The trailing `\b` is what keeps `On Error GoTo 01` — a numeric VBA line
|
|
104
|
+
* number, therefore a handler label — out of this rule.
|
|
105
|
+
*/
|
|
106
|
+
const ON_ERROR_RESET_RE = /\bOn\s+Error\s+GoTo\s+(0|-1)\b/i;
|
|
107
|
+
/** Global twin of {@link ON_ERROR_RESET_RE}. */
|
|
108
|
+
const ON_ERROR_RESET_G = new RegExp(ON_ERROR_RESET_RE.source, 'gi');
|
|
109
|
+
/**
|
|
110
|
+
* Issue #263: any `GoTo <target>`, with the optional `On Error ` prefix
|
|
111
|
+
* CAPTURED rather than excluded.
|
|
112
|
+
*
|
|
113
|
+
* Capturing it is what lets one scan separate the two statements without a
|
|
114
|
+
* lookbehind: a match whose group 1 is present is an `On Error GoTo`, already
|
|
115
|
+
* owned by the `on-error-label` and `on-error-reset` rules, and the
|
|
116
|
+
* plain-jump rule skips it. A lookbehind would have to re-encode
|
|
117
|
+
* `On\s+Error\s+` a second time, which is one more place for the two
|
|
118
|
+
* spellings to drift apart.
|
|
119
|
+
*
|
|
120
|
+
* Kept compatible with the probe's `GOTO_RE`, which counts EVERY `GoTo`
|
|
121
|
+
* (both forms) — `gotoStatements` minus the three `On Error` counters is the
|
|
122
|
+
* plain-jump total this rule sees.
|
|
123
|
+
*/
|
|
124
|
+
const GOTO_ANY_G = /\b(On\s+Error\s+)?GoTo\s+(-?\d+|\p{L}[\p{L}\p{N}_]*)/giu;
|
|
125
|
+
/** Non-global twin of {@link GOTO_ANY_G}, for the rule's dispatch pattern. */
|
|
126
|
+
const GOTO_ANY_RE = new RegExp(GOTO_ANY_G.source, 'iu');
|
|
127
|
+
/**
|
|
128
|
+
* A line-label definition. VBA allows a statement to follow on the same line
|
|
129
|
+
* (`errores: MsgBox "x"`), so the trailing-code form is accepted.
|
|
130
|
+
*
|
|
131
|
+
* `(?!=)` keeps the named-argument form (`Foo bar:=1`) out; {@link NOT_A_LABEL}
|
|
132
|
+
* keeps `Case 1:` / `Do:` / `Else:` out. Kept identical to the probe's
|
|
133
|
+
* `LINE_LABEL_RE`.
|
|
134
|
+
*/
|
|
135
|
+
const LINE_LABEL_RE = /^\s*(\p{L}[\p{L}\p{N}_]*)\s*:(?!=)/u;
|
|
136
|
+
/**
|
|
137
|
+
* Statement keywords that can head a colon-terminated line but name no label.
|
|
138
|
+
* Kept identical to the probe's `NOT_A_LABEL`.
|
|
139
|
+
*/
|
|
140
|
+
const NOT_A_LABEL = new Set([
|
|
141
|
+
'case', 'do', 'else', 'end', 'exit', 'for', 'if', 'loop', 'next', 'select',
|
|
142
|
+
'then', 'wend', 'while', 'with', 'rem', 'call', 'set', 'let', 'dim',
|
|
143
|
+
]);
|
|
144
|
+
/* ─────────────────── handler behaviour (issue #260, task E3) ───────────── */
|
|
145
|
+
/**
|
|
146
|
+
* The error-propagation channel this corpus actually uses: a module-level or
|
|
147
|
+
* object field the failing procedure writes and the caller reads. §2.3 of the
|
|
148
|
+
* plan measures 3,602 handlers touching it against 16 that re-raise — VBA's
|
|
149
|
+
* own error mechanism unwinds one frame, the MESSAGE travels through one of
|
|
150
|
+
* these variables.
|
|
151
|
+
*
|
|
152
|
+
* Names only, never substrings, so `ErrorCount` cannot match `Error`.
|
|
153
|
+
*
|
|
154
|
+
* Issue #261 (task E4) moved this list — and the write matcher built from it —
|
|
155
|
+
* into `./error-channel`, so the two consumers can no longer fork: this
|
|
156
|
+
* classifier asks "is this statement a channel WRITE?", `module-vars.ts` asks
|
|
157
|
+
* "is this variable the channel?", and both now read one compiled object that
|
|
158
|
+
* `codegraph.json` → `vba.errorChannel` extends. Re-exported from here because
|
|
159
|
+
* this module was its home and the probe-agreement suite imports it.
|
|
160
|
+
*/
|
|
161
|
+
var error_channel_2 = require("./error-channel");
|
|
162
|
+
Object.defineProperty(exports, "DEFAULT_ERROR_CHANNEL_NAMES", { enumerable: true, get: function () { return error_channel_2.DEFAULT_ERROR_CHANNEL_NAMES; } });
|
|
163
|
+
/**
|
|
164
|
+
* Calls that make an error visible to a human. `MsgBox` is the form-code
|
|
165
|
+
* shape, `Debug.Print` the developer-only one; §2.3 counts both as "display",
|
|
166
|
+
* so this list — identical to the probe's `DEFAULT_DISPLAY_CALLS` — does too.
|
|
167
|
+
*/
|
|
168
|
+
exports.DEFAULT_DISPLAY_CALLS = ['MsgBox', 'Debug.Print'];
|
|
169
|
+
function escapeRe(text) {
|
|
170
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
171
|
+
}
|
|
172
|
+
/** `MsgBox` / `Debug.Print`, anywhere in the statement. The probe's twin. */
|
|
173
|
+
const DISPLAY_CALL_RES = exports.DEFAULT_DISPLAY_CALLS.map((call) => new RegExp(`\\b${call.split('.').map(escapeRe).join('\\s*\\.\\s*')}\\b`, 'i'));
|
|
174
|
+
/** `Err.Raise` — the frame re-throws. The probe's twin. */
|
|
175
|
+
const ERR_RAISE_RE = /\bErr\s*\.\s*Raise\b/i;
|
|
176
|
+
/**
|
|
177
|
+
* Split a masked line into statements and strip any leading `If … Then` /
|
|
178
|
+
* `ElseIf … Then` / `Else` guard, so the house shape
|
|
179
|
+
* `If Err.Number <> 1000 Then p_Error = "…"` presents `p_Error = "…"` to the
|
|
180
|
+
* channel matcher as a statement in its own right.
|
|
181
|
+
*
|
|
182
|
+
* This is also what makes the corpus's fifth most common handler shape —
|
|
183
|
+
* `DoCmd.Hourglass False` cleanup first, the guarded channel write second —
|
|
184
|
+
* classify as `channel`: the signals are collected per statement over the
|
|
185
|
+
* WHOLE region, so a leading call cannot shadow a later write.
|
|
186
|
+
*
|
|
187
|
+
* Kept identical to the probe's `statementsOf`.
|
|
188
|
+
*/
|
|
189
|
+
function splitVbaStatements(line) {
|
|
190
|
+
const statements = [];
|
|
191
|
+
let start = 0;
|
|
192
|
+
let inString = false;
|
|
193
|
+
let inDate = false;
|
|
194
|
+
for (let index = 0; index < line.length; index++) {
|
|
195
|
+
const char = line[index];
|
|
196
|
+
if (char === '"' && !inDate) {
|
|
197
|
+
if (inString && line[index + 1] === '"') {
|
|
198
|
+
index += 1;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
inString = !inString;
|
|
202
|
+
}
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (char === '#' && !inString) {
|
|
206
|
+
if (inDate) {
|
|
207
|
+
inDate = false;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
// `#` is also VBA's Double type suffix (`value#`, `1#`). It only
|
|
211
|
+
// opens a date/time literal when it appears where an expression can
|
|
212
|
+
// begin and a closing `#` exists ahead. A suffix must not hide a later
|
|
213
|
+
// colon statement separator by leaving `inDate` open.
|
|
214
|
+
const before = line.slice(0, index).trimEnd().slice(-1);
|
|
215
|
+
const canOpenDate = before === '' || /[=(:,+\-*/&<>]/.test(before);
|
|
216
|
+
if (canOpenDate && line.indexOf('#', index + 1) >= 0) {
|
|
217
|
+
inDate = true;
|
|
218
|
+
}
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (char === ':' && !inString && !inDate && line[index + 1] !== '=') {
|
|
222
|
+
statements.push(line.slice(start, index));
|
|
223
|
+
start = index + 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
statements.push(line.slice(start));
|
|
227
|
+
return statements;
|
|
228
|
+
}
|
|
229
|
+
function statementsOf(maskedLine) {
|
|
230
|
+
return splitVbaStatements(maskedLine).map((part) => {
|
|
231
|
+
const guard = /^\s*(?:(?:If|ElseIf)\b.*?\bThen\b|Else\b)/i.exec(part);
|
|
232
|
+
return guard ? part.slice(guard[0].length) : part;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/** Non-executable declarations that may legally occur inside a procedure. */
|
|
236
|
+
const PROCEDURE_DECLARATION_RE = /^(?:Dim|Static|Const|Private|Public)\b/i;
|
|
237
|
+
/** One procedure terminator after colon splitting. */
|
|
238
|
+
const PROCEDURE_END_STATEMENT_RE = /^End\s+(?:Sub|Function|Property)\b/i;
|
|
239
|
+
/**
|
|
240
|
+
* Count executable statements on one already comment-stripped, string-masked
|
|
241
|
+
* source line. The procedure declaration, its terminator, declarations, and
|
|
242
|
+
* bare labels are structural; every remaining colon-separated statement is an
|
|
243
|
+
* executable statement. A label with trailing code contributes that code once.
|
|
244
|
+
*/
|
|
245
|
+
function executableStatementsOnLine(maskedLine, procedureStartsHere) {
|
|
246
|
+
let count = 0;
|
|
247
|
+
const parts = splitVbaStatements(maskedLine);
|
|
248
|
+
for (let index = 0; index < parts.length; index++) {
|
|
249
|
+
let statement = (parts[index] ?? '').trim();
|
|
250
|
+
if (!statement)
|
|
251
|
+
continue;
|
|
252
|
+
if (procedureStartsHere && index === 0)
|
|
253
|
+
continue;
|
|
254
|
+
if (PROCEDURE_END_STATEMENT_RE.test(statement))
|
|
255
|
+
continue;
|
|
256
|
+
if (PROCEDURE_DECLARATION_RE.test(statement))
|
|
257
|
+
continue;
|
|
258
|
+
const label = index < parts.length - 1
|
|
259
|
+
? LINE_LABEL_RE.exec(`${statement}:`)
|
|
260
|
+
: null;
|
|
261
|
+
if (label && !NOT_A_LABEL.has((label[1] ?? '').toLowerCase())) {
|
|
262
|
+
statement = statement.slice(label[0].length - 1).trim();
|
|
263
|
+
if (!statement)
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
count += 1;
|
|
267
|
+
}
|
|
268
|
+
return count;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* The three signals one masked line (or line fragment) fires, if any.
|
|
272
|
+
*
|
|
273
|
+
* `errorChannel` is the compiled, config-aware channel (issue #261). A project
|
|
274
|
+
* that adds `lastFailure` to `codegraph.json` → `vba.errorChannel` gets the
|
|
275
|
+
* handlers writing it classified `channel` HERE too, not merely its references
|
|
276
|
+
* flagged in `module-vars.ts` — one list, two consumers, no drift. With no
|
|
277
|
+
* config the list is the four defaults, so every pre-#261 classification, and
|
|
278
|
+
* therefore the probe agreement, is unchanged.
|
|
279
|
+
*/
|
|
280
|
+
function signalsOf(maskedText, errorChannel) {
|
|
281
|
+
const channel = statementsOf(maskedText).some((statement) => errorChannel.writeRes.some((re) => re.test(statement)));
|
|
282
|
+
const display = DISPLAY_CALL_RES.some((re) => re.test(maskedText));
|
|
283
|
+
const reraise = ERR_RAISE_RE.test(maskedText);
|
|
284
|
+
if (!channel && !display && !reraise)
|
|
285
|
+
return null;
|
|
286
|
+
return { channel, display, reraise };
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Record what one masked line contributes to the open body's handler
|
|
290
|
+
* behaviour: the whole line under {@link VbaErrorPolicyState.signalsByLine},
|
|
291
|
+
* and — when the line defines a label — its trailing statement separately
|
|
292
|
+
* under `labelRestSignals`, so a region opened by `errores: MsgBox "x"` reads
|
|
293
|
+
* the `MsgBox` without reading the label.
|
|
294
|
+
*
|
|
295
|
+
* No-op outside a procedure body.
|
|
296
|
+
*/
|
|
297
|
+
function recordHandlerSignals(state, maskedLine, lineNum, errorChannel) {
|
|
298
|
+
if (!state)
|
|
299
|
+
return;
|
|
300
|
+
const whole = signalsOf(maskedLine, errorChannel);
|
|
301
|
+
if (whole)
|
|
302
|
+
state.signalsByLine.set(lineNum, whole);
|
|
303
|
+
const label = LINE_LABEL_RE.exec(maskedLine);
|
|
304
|
+
if (!label)
|
|
305
|
+
return;
|
|
306
|
+
const name = label[1] ?? '';
|
|
307
|
+
if (!name || NOT_A_LABEL.has(name.toLowerCase()))
|
|
308
|
+
return;
|
|
309
|
+
const rest = signalsOf(maskedLine.slice(label[0].length), errorChannel);
|
|
310
|
+
if (rest)
|
|
311
|
+
state.labelRestSignals.set(lineNum, rest);
|
|
312
|
+
}
|
|
313
|
+
/** A fresh accumulator for a procedure body opening at `startLine`. */
|
|
314
|
+
function newErrorPolicyState(startLine, edgeMark, refMark) {
|
|
315
|
+
return {
|
|
316
|
+
startLine,
|
|
317
|
+
protection: 'none',
|
|
318
|
+
targets: new Map(),
|
|
319
|
+
definedLabels: new Map(),
|
|
320
|
+
handlerCount: 0,
|
|
321
|
+
executableStatementCount: 0,
|
|
322
|
+
lastScopeEvent: null,
|
|
323
|
+
openedTarget: null,
|
|
324
|
+
handlerStartLine: null,
|
|
325
|
+
edgeMark,
|
|
326
|
+
refMark,
|
|
327
|
+
signalsByLine: new Map(),
|
|
328
|
+
labelRestSignals: new Map(),
|
|
329
|
+
labelDefs: [],
|
|
330
|
+
onErrorSites: [],
|
|
331
|
+
gotoSites: [],
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Record an `On Error Resume Next` (`opens`) or an `On Error GoTo 0|-1`
|
|
336
|
+
* (`!opens`) at a source position, keeping only the LAST one.
|
|
337
|
+
*
|
|
338
|
+
* Position-ordered rather than counted because the three `On Error` rules are
|
|
339
|
+
* independent table entries: on a colon-separated line carrying both forms,
|
|
340
|
+
* whichever rule the dispatcher happens to reach first must not decide the
|
|
341
|
+
* outcome. `resumeNextOpen` is then "the last scope event opened a scope",
|
|
342
|
+
* which is exactly the probe's `openResumeNextScopes > 0`.
|
|
343
|
+
*/
|
|
344
|
+
function noteScopeEvent(state, line, column, opens) {
|
|
345
|
+
const previous = state.lastScopeEvent;
|
|
346
|
+
if (previous !== null &&
|
|
347
|
+
(previous.line > line ||
|
|
348
|
+
(previous.line === line && previous.column > column))) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
state.lastScopeEvent = { line, column, opens };
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Issue #259: the declarative rule table for the error-policy concern. All
|
|
355
|
+
* four rules are `scan: 'masked'` and `requires: 'inside-procedure'`.
|
|
356
|
+
*
|
|
357
|
+
* `masked` is the #209 discipline: `s = "On Error GoTo errores"` is a string
|
|
358
|
+
* assignment, and a classifier that reads it as a handler would report a
|
|
359
|
+
* protected procedure that is not protected. `inside-procedure` is what keeps
|
|
360
|
+
* module-level noise out — there is no error policy without a procedure to
|
|
361
|
+
* attach it to.
|
|
362
|
+
*
|
|
363
|
+
* The three `On Error` rules re-scan their own line globally instead of
|
|
364
|
+
* acting on the dispatcher's single match, so a colon-separated single-line
|
|
365
|
+
* procedure carrying two `On Error GoTo` statements counts two.
|
|
366
|
+
*/
|
|
367
|
+
exports.RULES = [
|
|
368
|
+
(0, rules_1.defineRule)({
|
|
369
|
+
id: 'on-error-label',
|
|
370
|
+
description: 'Match `On Error GoTo <label>` where the target is not `0` / `-1`; record the label as a handler target, bump `handlerCount`, and set `protection` to `handler`.',
|
|
371
|
+
pattern: ON_ERROR_GOTO_RE,
|
|
372
|
+
requires: 'inside-procedure',
|
|
373
|
+
scan: 'masked',
|
|
374
|
+
emit: (_m, ctx, line, lineNum) => {
|
|
375
|
+
const state = ctx.vbaErrorPolicy;
|
|
376
|
+
if (!state)
|
|
377
|
+
return null;
|
|
378
|
+
let handlers = 0;
|
|
379
|
+
ON_ERROR_GOTO_G.lastIndex = 0;
|
|
380
|
+
let match;
|
|
381
|
+
while ((match = ON_ERROR_GOTO_G.exec(line)) !== null) {
|
|
382
|
+
const target = match[1] ?? '';
|
|
383
|
+
// `0` and `-1` — and ONLY those two — are resets, owned by the
|
|
384
|
+
// `on-error-reset` rule. Everything else is a handler label, including
|
|
385
|
+
// a numeric VBA line number (`On Error GoTo 100`), which is exactly
|
|
386
|
+
// how the probe classifies it. This corpus has 38 `GoTo 0`, no `-1`
|
|
387
|
+
// and no line-number target, so today the two agree vacuously here;
|
|
388
|
+
// from now on they agree by construction.
|
|
389
|
+
if (target === '0' || target === '-1')
|
|
390
|
+
continue;
|
|
391
|
+
const key = target.toLowerCase();
|
|
392
|
+
if (!state.targets.has(key))
|
|
393
|
+
state.targets.set(key, target);
|
|
394
|
+
// Issue #263: the STATEMENT, not just the target. `targets` is a set
|
|
395
|
+
// keyed by name and cannot express "this body routes errors twice";
|
|
396
|
+
// one `handles-error` edge per statement can, and 47 procedures in
|
|
397
|
+
// the corpus need it to.
|
|
398
|
+
state.onErrorSites.push({
|
|
399
|
+
key,
|
|
400
|
+
name: target,
|
|
401
|
+
line: lineNum,
|
|
402
|
+
column: match.index,
|
|
403
|
+
});
|
|
404
|
+
state.handlerCount += 1;
|
|
405
|
+
state.protection = 'handler';
|
|
406
|
+
handlers += 1;
|
|
407
|
+
}
|
|
408
|
+
return handlers > 0 ? { handlers } : null;
|
|
409
|
+
},
|
|
410
|
+
count: (result) => result.handlers,
|
|
411
|
+
}),
|
|
412
|
+
(0, rules_1.defineRule)({
|
|
413
|
+
id: 'on-error-resume-next',
|
|
414
|
+
description: 'Match `On Error Resume Next`; open a suppression scope and set `protection` to `resume-next` unless an `On Error GoTo <label>` already made it `handler`.',
|
|
415
|
+
pattern: ON_ERROR_RESUME_NEXT_RE,
|
|
416
|
+
requires: 'inside-procedure',
|
|
417
|
+
scan: 'masked',
|
|
418
|
+
emit: (_m, ctx, line, lineNum) => {
|
|
419
|
+
const state = ctx.vbaErrorPolicy;
|
|
420
|
+
if (!state)
|
|
421
|
+
return null;
|
|
422
|
+
let scopes = 0;
|
|
423
|
+
ON_ERROR_RESUME_NEXT_G.lastIndex = 0;
|
|
424
|
+
let match;
|
|
425
|
+
while ((match = ON_ERROR_RESUME_NEXT_G.exec(line)) !== null) {
|
|
426
|
+
noteScopeEvent(state, lineNum, match.index, true);
|
|
427
|
+
scopes += 1;
|
|
428
|
+
}
|
|
429
|
+
if (scopes === 0)
|
|
430
|
+
return null;
|
|
431
|
+
// A real handler outranks blanket suppression: a procedure that does
|
|
432
|
+
// both is reported as protected, matching the probe's precedence.
|
|
433
|
+
if (state.protection !== 'handler')
|
|
434
|
+
state.protection = 'resume-next';
|
|
435
|
+
return { scopes };
|
|
436
|
+
},
|
|
437
|
+
count: (result) => result.scopes,
|
|
438
|
+
}),
|
|
439
|
+
(0, rules_1.defineRule)({
|
|
440
|
+
id: 'on-error-reset',
|
|
441
|
+
description: 'Match `On Error GoTo 0` or `On Error GoTo -1`; close the open `On Error Resume Next` scope. `-1` clears the current error rather than disabling the handler, but for this field the two are the same event.',
|
|
442
|
+
pattern: ON_ERROR_RESET_RE,
|
|
443
|
+
requires: 'inside-procedure',
|
|
444
|
+
scan: 'masked',
|
|
445
|
+
emit: (_m, ctx, line, lineNum) => {
|
|
446
|
+
const state = ctx.vbaErrorPolicy;
|
|
447
|
+
if (!state)
|
|
448
|
+
return null;
|
|
449
|
+
let resets = 0;
|
|
450
|
+
ON_ERROR_RESET_G.lastIndex = 0;
|
|
451
|
+
let match;
|
|
452
|
+
while ((match = ON_ERROR_RESET_G.exec(line)) !== null) {
|
|
453
|
+
noteScopeEvent(state, lineNum, match.index, false);
|
|
454
|
+
resets += 1;
|
|
455
|
+
}
|
|
456
|
+
return resets > 0 ? { resets } : null;
|
|
457
|
+
},
|
|
458
|
+
count: (result) => result.resets,
|
|
459
|
+
}),
|
|
460
|
+
(0, rules_1.defineRule)({
|
|
461
|
+
id: 'line-label',
|
|
462
|
+
description: 'Match a `<Label>:` definition; record it, and — ONLY when an `On Error GoTo` already named it — open the handler region on the next line. A label nobody targets is control flow, not a handler.',
|
|
463
|
+
pattern: LINE_LABEL_RE,
|
|
464
|
+
requires: 'inside-procedure',
|
|
465
|
+
scan: 'masked',
|
|
466
|
+
emit: (match, ctx, _line, lineNum) => {
|
|
467
|
+
const state = ctx.vbaErrorPolicy;
|
|
468
|
+
if (!state)
|
|
469
|
+
return null;
|
|
470
|
+
const name = match[1] ?? '';
|
|
471
|
+
if (!name || NOT_A_LABEL.has(name.toLowerCase()))
|
|
472
|
+
return null;
|
|
473
|
+
const key = name.toLowerCase();
|
|
474
|
+
if (!state.definedLabels.has(key)) {
|
|
475
|
+
state.definedLabels.set(key, lineNum);
|
|
476
|
+
// Issue #263: the same definition, keeping the name as written and
|
|
477
|
+
// its column so `labels.ts` can emit a node without re-reading the
|
|
478
|
+
// line. Guarded by the same first-wins condition, so the two views
|
|
479
|
+
// cannot disagree about which definition counts.
|
|
480
|
+
state.labelDefs.push({
|
|
481
|
+
key,
|
|
482
|
+
name,
|
|
483
|
+
line: lineNum,
|
|
484
|
+
column: match.index ?? 0,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
// The precision gate. `targets` holds only `On Error GoTo` destinations,
|
|
488
|
+
// so a `siguiente:` / `salir:` / `Teardown:` label leaves the policy
|
|
489
|
+
// untouched and the procedure keeps whatever protection it earned.
|
|
490
|
+
if (state.handlerStartLine === null && state.targets.has(key)) {
|
|
491
|
+
state.handlerStartLine = lineNum + 1;
|
|
492
|
+
state.openedTarget = key;
|
|
493
|
+
}
|
|
494
|
+
return { label: name };
|
|
495
|
+
},
|
|
496
|
+
}),
|
|
497
|
+
(0, rules_1.defineRule)({
|
|
498
|
+
id: 'goto-jump',
|
|
499
|
+
description: 'Issue #263: match a plain `GoTo <label>` jump — an `On Error GoTo` is skipped, since that is a routing decision the `on-error-label` rule already owns — and record it for a `references` edge onto the label node.',
|
|
500
|
+
pattern: GOTO_ANY_RE,
|
|
501
|
+
requires: 'inside-procedure',
|
|
502
|
+
scan: 'masked',
|
|
503
|
+
emit: (_m, ctx, line, lineNum) => {
|
|
504
|
+
const state = ctx.vbaErrorPolicy;
|
|
505
|
+
if (!state)
|
|
506
|
+
return null;
|
|
507
|
+
let jumps = 0;
|
|
508
|
+
GOTO_ANY_G.lastIndex = 0;
|
|
509
|
+
let match;
|
|
510
|
+
while ((match = GOTO_ANY_G.exec(line)) !== null) {
|
|
511
|
+
// Group 1 present means `On Error GoTo` — a different statement with
|
|
512
|
+
// a different edge kind, owned elsewhere in this table.
|
|
513
|
+
if (match[1])
|
|
514
|
+
continue;
|
|
515
|
+
const target = match[2] ?? '';
|
|
516
|
+
// A numeric target is a VBA LINE NUMBER, not a line label:
|
|
517
|
+
// `LINE_LABEL_RE` cannot define one, so a node for it can never
|
|
518
|
+
// exist and referencing it would fabricate a permanent dangling
|
|
519
|
+
// reference for legal code. Out of scope, deliberately.
|
|
520
|
+
if (!target || /^-?\d+$/.test(target))
|
|
521
|
+
continue;
|
|
522
|
+
state.gotoSites.push({
|
|
523
|
+
key: target.toLowerCase(),
|
|
524
|
+
name: target,
|
|
525
|
+
line: lineNum,
|
|
526
|
+
column: match.index,
|
|
527
|
+
});
|
|
528
|
+
jumps += 1;
|
|
529
|
+
}
|
|
530
|
+
return jumps > 0 ? { jumps } : null;
|
|
531
|
+
},
|
|
532
|
+
count: (result) => result.jumps,
|
|
533
|
+
}),
|
|
534
|
+
];
|
|
535
|
+
/**
|
|
536
|
+
* Fold the open accumulator into the procedure's `metadata.errorPolicy` and
|
|
537
|
+
* clear it. `endLine` is the procedure's terminating `End` line — the handler
|
|
538
|
+
* region's closing boundary.
|
|
539
|
+
*
|
|
540
|
+
* Called from three places: the `End Sub` boundary (the normal path), a new
|
|
541
|
+
* procedure declaration while a body is somehow still open (malformed VBA
|
|
542
|
+
* with no `End`), and end-of-file. All three converge on the same shape, so a
|
|
543
|
+
* truncated module still records a policy instead of silently dropping it.
|
|
544
|
+
*/
|
|
545
|
+
function closeErrorPolicy(ctx, endLine) {
|
|
546
|
+
const state = ctx.vbaErrorPolicy;
|
|
547
|
+
if (!state)
|
|
548
|
+
return;
|
|
549
|
+
ctx.vbaErrorPolicy = null;
|
|
550
|
+
// The handler region is "the EARLIEST label definition in this body that an
|
|
551
|
+
// `On Error GoTo` names" — the probe's `handlerLabelDefs[0]`, since it
|
|
552
|
+
// collects definitions in line order. The `line-label` rule already opened
|
|
553
|
+
// the region for the common case (label defined after the `On Error GoTo`
|
|
554
|
+
// that targets it), but two orderings need the whole body to be known before
|
|
555
|
+
// they resolve: a label defined BEFORE its `On Error GoTo`, and a body whose
|
|
556
|
+
// first targeted definition is only targeted later. Re-resolving here is
|
|
557
|
+
// what keeps this classifier and the probe from disagreeing on
|
|
558
|
+
// `handlerStartLine`; the rule's incremental open is confirmed by it.
|
|
559
|
+
let best = null;
|
|
560
|
+
for (const [key, line] of state.definedLabels) {
|
|
561
|
+
if (!state.targets.has(key))
|
|
562
|
+
continue;
|
|
563
|
+
if (best === null || line < best.line)
|
|
564
|
+
best = { key, line };
|
|
565
|
+
}
|
|
566
|
+
if (best) {
|
|
567
|
+
state.handlerStartLine = best.line + 1;
|
|
568
|
+
state.openedTarget = best.key;
|
|
569
|
+
}
|
|
570
|
+
const firstTarget = state.targets.keys().next().value ?? null;
|
|
571
|
+
const labelKey = state.openedTarget ?? firstTarget;
|
|
572
|
+
const danglingKey = [...state.targets.keys()].find((key) => !state.definedLabels.has(key)) ??
|
|
573
|
+
null;
|
|
574
|
+
const policy = {
|
|
575
|
+
protection: state.protection,
|
|
576
|
+
handlerLabel: labelKey ? (state.targets.get(labelKey) ?? null) : null,
|
|
577
|
+
handlerStartLine: state.handlerStartLine,
|
|
578
|
+
handlerEndLine: state.handlerStartLine === null ? null : endLine,
|
|
579
|
+
// Issue #260 (task E3): derived below, from the signals collected over
|
|
580
|
+
// the handler region this function has just finished resolving.
|
|
581
|
+
behavior: classifyBehavior(state, best, endLine),
|
|
582
|
+
handlerCount: state.handlerCount,
|
|
583
|
+
resumeNextOpen: state.lastScopeEvent?.opens ?? false,
|
|
584
|
+
executableStatementCount: state.executableStatementCount,
|
|
585
|
+
danglingTarget: danglingKey
|
|
586
|
+
? (state.targets.get(danglingKey) ?? null)
|
|
587
|
+
: null,
|
|
588
|
+
};
|
|
589
|
+
// Issue #263 (task E6): publish the label nodes and their edges BEFORE the
|
|
590
|
+
// stamping pass below, so a `GoTo` or a second `On Error GoTo` written
|
|
591
|
+
// inside the handler region is flagged `inErrorHandler` by #260's single
|
|
592
|
+
// stamping point like every other edge, rather than becoming the one
|
|
593
|
+
// emitter that quietly opted out of it.
|
|
594
|
+
(0, labels_1.emitLabelNodes)(ctx, state, policy, endLine);
|
|
595
|
+
// Issue #260: THE single stamping point. Every edge and every unresolved
|
|
596
|
+
// reference this procedure body emitted is already in `ctx`, so one pass
|
|
597
|
+
// over its own slice of the two accumulators flags all of them — instead
|
|
598
|
+
// of six emitters each remembering a flag, which is six places to forget
|
|
599
|
+
// it. New emitters inherit the behaviour for free.
|
|
600
|
+
markErrorHandlerRegion(ctx, state, policy);
|
|
601
|
+
const node = ctx.functionNodeByStartLine.get(state.startLine);
|
|
602
|
+
if (!node)
|
|
603
|
+
return;
|
|
604
|
+
if (!node.metadata)
|
|
605
|
+
node.metadata = {};
|
|
606
|
+
node.metadata.errorPolicy = policy;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Issue #260: fold the per-line signals collected over the whole body down to
|
|
610
|
+
* the handler region, and name the result.
|
|
611
|
+
*
|
|
612
|
+
* Only a `handler`-protected procedure gets a `behavior` at all: an
|
|
613
|
+
* `On Error Resume Next` body has no handler to describe, and neither does an
|
|
614
|
+
* unprotected one, so both stay `null`. A `handler` procedure whose region is
|
|
615
|
+
* empty — or whose target label is never defined — is `unknown`, not `null`:
|
|
616
|
+
* "it handles errors and does nothing recognisable with them" is a different
|
|
617
|
+
* fact from "it does not handle errors". Both rules are the probe's.
|
|
618
|
+
*
|
|
619
|
+
* `mixed` is what MORE THAN ONE signal means. The three are collected
|
|
620
|
+
* independently precisely so evaluation order cannot silently pick a winner —
|
|
621
|
+
* §2.3 of the plan reports 921 handlers that both record the error AND show
|
|
622
|
+
* it, and the pre-probe classifier hid every one of them behind whichever
|
|
623
|
+
* test it happened to run first.
|
|
624
|
+
*/
|
|
625
|
+
function classifyBehavior(state, region, endLine) {
|
|
626
|
+
if (state.protection !== 'handler')
|
|
627
|
+
return null;
|
|
628
|
+
const signals = {
|
|
629
|
+
channel: false,
|
|
630
|
+
display: false,
|
|
631
|
+
reraise: false,
|
|
632
|
+
};
|
|
633
|
+
if (region) {
|
|
634
|
+
const merge = (part) => {
|
|
635
|
+
if (!part)
|
|
636
|
+
return;
|
|
637
|
+
signals.channel ||= part.channel;
|
|
638
|
+
signals.display ||= part.display;
|
|
639
|
+
signals.reraise ||= part.reraise;
|
|
640
|
+
};
|
|
641
|
+
// The label's own line contributes ONLY its trailing statement — the
|
|
642
|
+
// `errores: MsgBox "x"` one-liner form — because the label itself is not
|
|
643
|
+
// handler code. Every later line contributes in full.
|
|
644
|
+
merge(state.labelRestSignals.get(region.line));
|
|
645
|
+
for (const [line, part] of state.signalsByLine) {
|
|
646
|
+
if (line > region.line && line <= endLine)
|
|
647
|
+
merge(part);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
const hit = ['channel', 'display', 'reraise'].filter((name) => signals[name]);
|
|
651
|
+
if (hit.length === 0)
|
|
652
|
+
return 'unknown';
|
|
653
|
+
if (hit.length === 1)
|
|
654
|
+
return hit[0];
|
|
655
|
+
return 'mixed';
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Issue #260: stamp `metadata.inErrorHandler: true` on every edge and every
|
|
659
|
+
* unresolved reference this procedure emitted from inside its handler region.
|
|
660
|
+
*
|
|
661
|
+
* Scoped two ways, and both matter:
|
|
662
|
+
*
|
|
663
|
+
* - by POSITION in the accumulators (`edgeMark` / `refMark`, taken when the
|
|
664
|
+
* body opened), so the scan is per-procedure rather than per-file and a
|
|
665
|
+
* module with N procedures stays linear rather than quadratic;
|
|
666
|
+
* - by LINE against `[handlerStartLine, handlerEndLine]`, the exact region
|
|
667
|
+
* published on the node. A row with no line — a `contains` edge, say — is
|
|
668
|
+
* structural, not emitted from a statement, and is never flagged.
|
|
669
|
+
*
|
|
670
|
+
* The flag is only ever set to `true`; its ABSENCE is the "not in a handler"
|
|
671
|
+
* encoding, so this adds a key to a minority of rows instead of a `false` to
|
|
672
|
+
* every one of them. Nothing here creates, drops or reorders a row.
|
|
673
|
+
*/
|
|
674
|
+
function markErrorHandlerRegion(ctx, state, policy) {
|
|
675
|
+
const start = policy.handlerStartLine;
|
|
676
|
+
const end = policy.handlerEndLine;
|
|
677
|
+
if (start === null || end === null)
|
|
678
|
+
return;
|
|
679
|
+
for (let i = state.edgeMark; i < ctx.edges.length; i++) {
|
|
680
|
+
const edge = ctx.edges[i];
|
|
681
|
+
if (!edge || edge.line === undefined)
|
|
682
|
+
continue;
|
|
683
|
+
if (edge.line < start || edge.line > end)
|
|
684
|
+
continue;
|
|
685
|
+
if (!edge.metadata)
|
|
686
|
+
edge.metadata = {};
|
|
687
|
+
edge.metadata.inErrorHandler = true;
|
|
688
|
+
}
|
|
689
|
+
for (let i = state.refMark; i < ctx.unresolvedReferences.length; i++) {
|
|
690
|
+
const ref = ctx.unresolvedReferences[i];
|
|
691
|
+
if (!ref)
|
|
692
|
+
continue;
|
|
693
|
+
if (ref.line < start || ref.line > end)
|
|
694
|
+
continue;
|
|
695
|
+
if (!ref.metadata)
|
|
696
|
+
ref.metadata = {};
|
|
697
|
+
ref.metadata.inErrorHandler = true;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Issue #259: factory for the error-policy classifier.
|
|
702
|
+
*
|
|
703
|
+
* `count` stays at 0 for the life of the classifier, deliberately. The
|
|
704
|
+
* orchestrator derives `hasAnySymbols` from the sum of every classifier's
|
|
705
|
+
* `count`, and this one emits no symbols at all — it only annotates nodes the
|
|
706
|
+
* procedures sweep already created. Letting it count would make a file whose
|
|
707
|
+
* only content is an `On Error` line look like a file with symbols.
|
|
708
|
+
*/
|
|
709
|
+
function createErrorPolicyClassifier() {
|
|
710
|
+
let lastLineNum = 0;
|
|
711
|
+
let pendingExecutableLine = null;
|
|
712
|
+
let pendingStartsProcedure = false;
|
|
713
|
+
const countExecutableLine = (ctx, maskedLine, procedureStartsHere) => {
|
|
714
|
+
const continued = / _\s*$/.test(maskedLine);
|
|
715
|
+
const fragment = continued
|
|
716
|
+
? maskedLine.replace(/ _\s*$/, ' ')
|
|
717
|
+
: maskedLine;
|
|
718
|
+
if (pendingExecutableLine === null) {
|
|
719
|
+
pendingExecutableLine = fragment;
|
|
720
|
+
pendingStartsProcedure = procedureStartsHere;
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
pendingExecutableLine += fragment.trimStart();
|
|
724
|
+
}
|
|
725
|
+
if (continued)
|
|
726
|
+
return;
|
|
727
|
+
ctx.vbaErrorPolicy.executableStatementCount += executableStatementsOnLine(pendingExecutableLine, pendingStartsProcedure);
|
|
728
|
+
pendingExecutableLine = null;
|
|
729
|
+
pendingStartsProcedure = false;
|
|
730
|
+
};
|
|
731
|
+
const flushExecutableLine = (ctx) => {
|
|
732
|
+
if (pendingExecutableLine === null || ctx.vbaErrorPolicy === null)
|
|
733
|
+
return;
|
|
734
|
+
countExecutableLine(ctx, '', false);
|
|
735
|
+
};
|
|
736
|
+
return {
|
|
737
|
+
name: 'errorPolicy',
|
|
738
|
+
count: 0,
|
|
739
|
+
classifyLine(line, i, ctx) {
|
|
740
|
+
const lineNum = i + 1;
|
|
741
|
+
lastLineNum = lineNum;
|
|
742
|
+
const maskedLine = (0, text_utils_1.maskStringContent)(line);
|
|
743
|
+
const procedureStartsHere = ctx.functionNodeByStartLine.has(lineNum);
|
|
744
|
+
// Procedure START: the pre-walk already emitted a function node for
|
|
745
|
+
// this line, so no second `PROC_RE` dispatch is needed.
|
|
746
|
+
if (procedureStartsHere) {
|
|
747
|
+
flushExecutableLine(ctx);
|
|
748
|
+
closeErrorPolicy(ctx, lineNum - 1);
|
|
749
|
+
ctx.vbaErrorPolicy = newErrorPolicyState(lineNum, ctx.edges.length, ctx.unresolvedReferences.length);
|
|
750
|
+
}
|
|
751
|
+
if (ctx.vbaErrorPolicy) {
|
|
752
|
+
countExecutableLine(ctx, maskedLine, procedureStartsHere);
|
|
753
|
+
}
|
|
754
|
+
(0, rules_1.runRules)(exports.RULES, ctx, line, maskedLine, lineNum, {
|
|
755
|
+
'inside-procedure': ctx.vbaErrorPolicy !== null,
|
|
756
|
+
});
|
|
757
|
+
// Issue #260: collect this line's handler signals for the OPEN body.
|
|
758
|
+
// Collected for the whole body, not just the region: the region's first
|
|
759
|
+
// line is only certain once the body has been fully read, so
|
|
760
|
+
// `closeErrorPolicy` does the filtering. Sparse — a line with no signal
|
|
761
|
+
// stores nothing, which is the overwhelming majority of them.
|
|
762
|
+
recordHandlerSignals(ctx.vbaErrorPolicy, maskedLine, lineNum, ctx.errorChannel ?? (0, error_channel_1.defaultErrorChannel)());
|
|
763
|
+
// Procedure END. Deliberately independent of the start branch above: a
|
|
764
|
+
// colon-separated single-line procedure carries both markers (#208).
|
|
765
|
+
if (ctx.vbaErrorPolicy !== null && constants_1.PROCEDURE_END_RE.test(maskedLine)) {
|
|
766
|
+
closeErrorPolicy(ctx, lineNum);
|
|
767
|
+
}
|
|
768
|
+
},
|
|
769
|
+
finalize(ctx) {
|
|
770
|
+
flushExecutableLine(ctx);
|
|
771
|
+
closeErrorPolicy(ctx, lastLineNum);
|
|
772
|
+
},
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
//# sourceMappingURL=errors.js.map
|