@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.
Files changed (230) hide show
  1. package/lib/dist/bin/codegraph.js +13 -7
  2. package/lib/dist/bin/codegraph.js.map +1 -1
  3. package/lib/dist/bin/daemon-release.d.ts +7 -0
  4. package/lib/dist/bin/daemon-release.d.ts.map +1 -0
  5. package/lib/dist/bin/daemon-release.js +18 -0
  6. package/lib/dist/bin/daemon-release.js.map +1 -0
  7. package/lib/dist/db/queries.d.ts +41 -0
  8. package/lib/dist/db/queries.d.ts.map +1 -1
  9. package/lib/dist/db/queries.js +78 -1
  10. package/lib/dist/db/queries.js.map +1 -1
  11. package/lib/dist/extraction/access-erd-extractor.d.ts +57 -0
  12. package/lib/dist/extraction/access-erd-extractor.d.ts.map +1 -0
  13. package/lib/dist/extraction/access-erd-extractor.js +389 -0
  14. package/lib/dist/extraction/access-erd-extractor.js.map +1 -0
  15. package/lib/dist/extraction/extraction-version.d.ts +1 -1
  16. package/lib/dist/extraction/extraction-version.js +1 -1
  17. package/lib/dist/extraction/grammars.d.ts +20 -0
  18. package/lib/dist/extraction/grammars.d.ts.map +1 -1
  19. package/lib/dist/extraction/grammars.js +34 -0
  20. package/lib/dist/extraction/grammars.js.map +1 -1
  21. package/lib/dist/extraction/index.d.ts +1 -0
  22. package/lib/dist/extraction/index.d.ts.map +1 -1
  23. package/lib/dist/extraction/index.js +371 -351
  24. package/lib/dist/extraction/index.js.map +1 -1
  25. package/lib/dist/extraction/parse-pool.d.ts +8 -3
  26. package/lib/dist/extraction/parse-pool.d.ts.map +1 -1
  27. package/lib/dist/extraction/parse-pool.js +1 -2
  28. package/lib/dist/extraction/parse-pool.js.map +1 -1
  29. package/lib/dist/extraction/parse-worker.js +7 -6
  30. package/lib/dist/extraction/parse-worker.js.map +1 -1
  31. package/lib/dist/extraction/sql-query-extractor.d.ts +16 -14
  32. package/lib/dist/extraction/sql-query-extractor.d.ts.map +1 -1
  33. package/lib/dist/extraction/sql-query-extractor.js +43 -31
  34. package/lib/dist/extraction/sql-query-extractor.js.map +1 -1
  35. package/lib/dist/extraction/sql-table-scan.d.ts +184 -0
  36. package/lib/dist/extraction/sql-table-scan.d.ts.map +1 -0
  37. package/lib/dist/extraction/sql-table-scan.js +288 -0
  38. package/lib/dist/extraction/sql-table-scan.js.map +1 -0
  39. package/lib/dist/extraction/tree-sitter.d.ts +10 -1
  40. package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
  41. package/lib/dist/extraction/tree-sitter.js +34 -3
  42. package/lib/dist/extraction/tree-sitter.js.map +1 -1
  43. package/lib/dist/extraction/vba/call-sweep.d.ts +2 -7
  44. package/lib/dist/extraction/vba/call-sweep.d.ts.map +1 -1
  45. package/lib/dist/extraction/vba/call-sweep.js +144 -90
  46. package/lib/dist/extraction/vba/call-sweep.js.map +1 -1
  47. package/lib/dist/extraction/vba/calls.d.ts +24 -4
  48. package/lib/dist/extraction/vba/calls.d.ts.map +1 -1
  49. package/lib/dist/extraction/vba/calls.js +42 -12
  50. package/lib/dist/extraction/vba/calls.js.map +1 -1
  51. package/lib/dist/extraction/vba/constants.d.ts +7 -14
  52. package/lib/dist/extraction/vba/constants.d.ts.map +1 -1
  53. package/lib/dist/extraction/vba/constants.js +8 -36
  54. package/lib/dist/extraction/vba/constants.js.map +1 -1
  55. package/lib/dist/extraction/vba/context.d.ts +443 -7
  56. package/lib/dist/extraction/vba/context.d.ts.map +1 -1
  57. package/lib/dist/extraction/vba/context.js +325 -29
  58. package/lib/dist/extraction/vba/context.js.map +1 -1
  59. package/lib/dist/extraction/vba/controls.d.ts +18 -1
  60. package/lib/dist/extraction/vba/controls.d.ts.map +1 -1
  61. package/lib/dist/extraction/vba/controls.js +99 -27
  62. package/lib/dist/extraction/vba/controls.js.map +1 -1
  63. package/lib/dist/extraction/vba/declarations.d.ts +1 -6
  64. package/lib/dist/extraction/vba/declarations.d.ts.map +1 -1
  65. package/lib/dist/extraction/vba/declarations.js +5 -39
  66. package/lib/dist/extraction/vba/declarations.js.map +1 -1
  67. package/lib/dist/extraction/vba/dims.d.ts +11 -7
  68. package/lib/dist/extraction/vba/dims.d.ts.map +1 -1
  69. package/lib/dist/extraction/vba/dims.js +265 -29
  70. package/lib/dist/extraction/vba/dims.js.map +1 -1
  71. package/lib/dist/extraction/vba/docmd.d.ts +29 -2
  72. package/lib/dist/extraction/vba/docmd.d.ts.map +1 -1
  73. package/lib/dist/extraction/vba/docmd.js +297 -18
  74. package/lib/dist/extraction/vba/docmd.js.map +1 -1
  75. package/lib/dist/extraction/vba/enums-consts.d.ts +7 -14
  76. package/lib/dist/extraction/vba/enums-consts.d.ts.map +1 -1
  77. package/lib/dist/extraction/vba/enums-consts.js +11 -45
  78. package/lib/dist/extraction/vba/enums-consts.js.map +1 -1
  79. package/lib/dist/extraction/vba/error-channel.d.ts +57 -0
  80. package/lib/dist/extraction/vba/error-channel.d.ts.map +1 -0
  81. package/lib/dist/extraction/vba/error-channel.js +128 -0
  82. package/lib/dist/extraction/vba/error-channel.js.map +1 -0
  83. package/lib/dist/extraction/vba/errors.d.ts +64 -0
  84. package/lib/dist/extraction/vba/errors.d.ts.map +1 -0
  85. package/lib/dist/extraction/vba/errors.js +775 -0
  86. package/lib/dist/extraction/vba/errors.js.map +1 -0
  87. package/lib/dist/extraction/vba/filesystem-statements.d.ts +23 -0
  88. package/lib/dist/extraction/vba/filesystem-statements.d.ts.map +1 -0
  89. package/lib/dist/extraction/vba/filesystem-statements.js +70 -0
  90. package/lib/dist/extraction/vba/filesystem-statements.js.map +1 -0
  91. package/lib/dist/extraction/vba/implements.d.ts +1 -6
  92. package/lib/dist/extraction/vba/implements.d.ts.map +1 -1
  93. package/lib/dist/extraction/vba/implements.js +1 -22
  94. package/lib/dist/extraction/vba/implements.js.map +1 -1
  95. package/lib/dist/extraction/vba/labels.d.ts +26 -0
  96. package/lib/dist/extraction/vba/labels.d.ts.map +1 -0
  97. package/lib/dist/extraction/vba/labels.js +180 -0
  98. package/lib/dist/extraction/vba/labels.js.map +1 -0
  99. package/lib/dist/extraction/vba/module-vars.d.ts +36 -0
  100. package/lib/dist/extraction/vba/module-vars.d.ts.map +1 -0
  101. package/lib/dist/extraction/vba/module-vars.js +145 -0
  102. package/lib/dist/extraction/vba/module-vars.js.map +1 -0
  103. package/lib/dist/extraction/vba/options.d.ts +88 -0
  104. package/lib/dist/extraction/vba/options.d.ts.map +1 -0
  105. package/lib/dist/extraction/vba/options.js +39 -0
  106. package/lib/dist/extraction/vba/options.js.map +1 -0
  107. package/lib/dist/extraction/vba/parameters.d.ts +35 -0
  108. package/lib/dist/extraction/vba/parameters.d.ts.map +1 -0
  109. package/lib/dist/extraction/vba/parameters.js +110 -0
  110. package/lib/dist/extraction/vba/parameters.js.map +1 -0
  111. package/lib/dist/extraction/vba/procedures.d.ts +1 -9
  112. package/lib/dist/extraction/vba/procedures.d.ts.map +1 -1
  113. package/lib/dist/extraction/vba/procedures.js +174 -112
  114. package/lib/dist/extraction/vba/procedures.js.map +1 -1
  115. package/lib/dist/extraction/vba/rules.d.ts +10 -5
  116. package/lib/dist/extraction/vba/rules.d.ts.map +1 -1
  117. package/lib/dist/extraction/vba/rules.js +19 -0
  118. package/lib/dist/extraction/vba/rules.js.map +1 -1
  119. package/lib/dist/extraction/vba/runtime-objects.d.ts +59 -0
  120. package/lib/dist/extraction/vba/runtime-objects.d.ts.map +1 -0
  121. package/lib/dist/extraction/vba/runtime-objects.js +106 -0
  122. package/lib/dist/extraction/vba/runtime-objects.js.map +1 -0
  123. package/lib/dist/extraction/vba/signature.d.ts +57 -0
  124. package/lib/dist/extraction/vba/signature.d.ts.map +1 -0
  125. package/lib/dist/extraction/vba/signature.js +164 -0
  126. package/lib/dist/extraction/vba/signature.js.map +1 -0
  127. package/lib/dist/extraction/vba/sql-wrapper.d.ts +76 -3
  128. package/lib/dist/extraction/vba/sql-wrapper.d.ts.map +1 -1
  129. package/lib/dist/extraction/vba/sql-wrapper.js +725 -151
  130. package/lib/dist/extraction/vba/sql-wrapper.js.map +1 -1
  131. package/lib/dist/extraction/vba/tempvars.d.ts.map +1 -1
  132. package/lib/dist/extraction/vba/tempvars.js +4 -0
  133. package/lib/dist/extraction/vba/tempvars.js.map +1 -1
  134. package/lib/dist/extraction/vba/text-utils.d.ts +62 -2
  135. package/lib/dist/extraction/vba/text-utils.d.ts.map +1 -1
  136. package/lib/dist/extraction/vba/text-utils.js +84 -6
  137. package/lib/dist/extraction/vba/text-utils.js.map +1 -1
  138. package/lib/dist/extraction/vba-extractor.d.ts +18 -1
  139. package/lib/dist/extraction/vba-extractor.d.ts.map +1 -1
  140. package/lib/dist/extraction/vba-extractor.js +111 -16
  141. package/lib/dist/extraction/vba-extractor.js.map +1 -1
  142. package/lib/dist/extraction/vba-form-extractor.d.ts +28 -14
  143. package/lib/dist/extraction/vba-form-extractor.d.ts.map +1 -1
  144. package/lib/dist/extraction/vba-form-extractor.js +56 -21
  145. package/lib/dist/extraction/vba-form-extractor.js.map +1 -1
  146. package/lib/dist/extraction/vba-preprocess.d.ts +89 -3
  147. package/lib/dist/extraction/vba-preprocess.d.ts.map +1 -1
  148. package/lib/dist/extraction/vba-preprocess.js +221 -57
  149. package/lib/dist/extraction/vba-preprocess.js.map +1 -1
  150. package/lib/dist/extraction/vba-source.d.ts +0 -10
  151. package/lib/dist/extraction/vba-source.d.ts.map +1 -1
  152. package/lib/dist/extraction/vba-source.js +0 -13
  153. package/lib/dist/extraction/vba-source.js.map +1 -1
  154. package/lib/dist/extraction/vba-test-manifest-extractor.d.ts +0 -6
  155. package/lib/dist/extraction/vba-test-manifest-extractor.d.ts.map +1 -1
  156. package/lib/dist/extraction/vba-test-manifest-extractor.js +0 -1
  157. package/lib/dist/extraction/vba-test-manifest-extractor.js.map +1 -1
  158. package/lib/dist/graph/behavior-evidence.d.ts +162 -0
  159. package/lib/dist/graph/behavior-evidence.d.ts.map +1 -0
  160. package/lib/dist/graph/behavior-evidence.js +529 -0
  161. package/lib/dist/graph/behavior-evidence.js.map +1 -0
  162. package/lib/dist/index.d.ts +22 -0
  163. package/lib/dist/index.d.ts.map +1 -1
  164. package/lib/dist/index.js +37 -1
  165. package/lib/dist/index.js.map +1 -1
  166. package/lib/dist/mcp/daemon-manager.d.ts.map +1 -1
  167. package/lib/dist/mcp/daemon-manager.js +7 -3
  168. package/lib/dist/mcp/daemon-manager.js.map +1 -1
  169. package/lib/dist/mcp/daemon-paths.d.ts +6 -0
  170. package/lib/dist/mcp/daemon-paths.d.ts.map +1 -1
  171. package/lib/dist/mcp/daemon-paths.js +15 -0
  172. package/lib/dist/mcp/daemon-paths.js.map +1 -1
  173. package/lib/dist/mcp/daemon-registry.d.ts +82 -7
  174. package/lib/dist/mcp/daemon-registry.d.ts.map +1 -1
  175. package/lib/dist/mcp/daemon-registry.js +557 -45
  176. package/lib/dist/mcp/daemon-registry.js.map +1 -1
  177. package/lib/dist/mcp/daemon-watchdog.d.ts +12 -0
  178. package/lib/dist/mcp/daemon-watchdog.d.ts.map +1 -1
  179. package/lib/dist/mcp/daemon-watchdog.js +37 -7
  180. package/lib/dist/mcp/daemon-watchdog.js.map +1 -1
  181. package/lib/dist/mcp/daemon.d.ts +20 -1
  182. package/lib/dist/mcp/daemon.d.ts.map +1 -1
  183. package/lib/dist/mcp/daemon.js +123 -30
  184. package/lib/dist/mcp/daemon.js.map +1 -1
  185. package/lib/dist/mcp/index.d.ts.map +1 -1
  186. package/lib/dist/mcp/index.js +13 -2
  187. package/lib/dist/mcp/index.js.map +1 -1
  188. package/lib/dist/mcp/proxy.d.ts +32 -0
  189. package/lib/dist/mcp/proxy.d.ts.map +1 -1
  190. package/lib/dist/mcp/proxy.js +163 -31
  191. package/lib/dist/mcp/proxy.js.map +1 -1
  192. package/lib/dist/mcp/server-instructions.d.ts +1 -1
  193. package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
  194. package/lib/dist/mcp/server-instructions.js +26 -4
  195. package/lib/dist/mcp/server-instructions.js.map +1 -1
  196. package/lib/dist/mcp/tools.d.ts +12 -0
  197. package/lib/dist/mcp/tools.d.ts.map +1 -1
  198. package/lib/dist/mcp/tools.js +92 -1
  199. package/lib/dist/mcp/tools.js.map +1 -1
  200. package/lib/dist/project-config.d.ts +43 -2
  201. package/lib/dist/project-config.d.ts.map +1 -1
  202. package/lib/dist/project-config.js +86 -2
  203. package/lib/dist/project-config.js.map +1 -1
  204. package/lib/dist/resolution/index.d.ts +47 -2
  205. package/lib/dist/resolution/index.d.ts.map +1 -1
  206. package/lib/dist/resolution/index.js +160 -5
  207. package/lib/dist/resolution/index.js.map +1 -1
  208. package/lib/dist/resolution/name-matcher.d.ts +25 -0
  209. package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
  210. package/lib/dist/resolution/name-matcher.js +59 -0
  211. package/lib/dist/resolution/name-matcher.js.map +1 -1
  212. package/lib/dist/resolution/vba-runtime-objects.d.ts +11 -11
  213. package/lib/dist/resolution/vba-runtime-objects.d.ts.map +1 -1
  214. package/lib/dist/resolution/vba-runtime-objects.js +18 -38
  215. package/lib/dist/resolution/vba-runtime-objects.js.map +1 -1
  216. package/lib/dist/types.d.ts +13 -5
  217. package/lib/dist/types.d.ts.map +1 -1
  218. package/lib/dist/types.js +17 -0
  219. package/lib/dist/types.js.map +1 -1
  220. package/lib/dist/utils/backtrace-helpers.d.ts +14 -2
  221. package/lib/dist/utils/backtrace-helpers.d.ts.map +1 -1
  222. package/lib/dist/utils/backtrace-helpers.js +81 -7
  223. package/lib/dist/utils/backtrace-helpers.js.map +1 -1
  224. package/lib/dist/utils/sql-impact-helpers.d.ts.map +1 -1
  225. package/lib/dist/utils/sql-impact-helpers.js +119 -30
  226. package/lib/dist/utils/sql-impact-helpers.js.map +1 -1
  227. package/lib/node_modules/.modules.yaml +1 -1
  228. package/lib/node_modules/.pnpm-workspace-state-v1.json +1 -1
  229. package/lib/package.json +3 -1
  230. 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