@aroman22/codegraph-vba 1.15.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/daemon-release.d.ts +7 -0
- package/dist/db/queries.d.ts +30 -0
- package/dist/extraction/access-erd-extractor.d.ts +57 -0
- package/dist/extraction/extraction-version.d.ts +1 -1
- package/dist/extraction/grammars.d.ts +20 -0
- package/dist/extraction/index.d.ts +1 -0
- package/dist/extraction/parse-pool.d.ts +8 -3
- package/dist/extraction/sql-query-extractor.d.ts +16 -14
- package/dist/extraction/sql-table-scan.d.ts +184 -0
- package/dist/extraction/tree-sitter.d.ts +10 -1
- package/dist/extraction/vba/call-sweep.d.ts +2 -7
- package/dist/extraction/vba/calls.d.ts +24 -4
- package/dist/extraction/vba/constants.d.ts +7 -14
- package/dist/extraction/vba/context.d.ts +443 -7
- package/dist/extraction/vba/controls.d.ts +18 -1
- package/dist/extraction/vba/declarations.d.ts +1 -6
- package/dist/extraction/vba/dims.d.ts +11 -7
- package/dist/extraction/vba/docmd.d.ts +29 -2
- package/dist/extraction/vba/enums-consts.d.ts +7 -14
- package/dist/extraction/vba/error-channel.d.ts +57 -0
- package/dist/extraction/vba/errors.d.ts +64 -0
- package/dist/extraction/vba/filesystem-statements.d.ts +23 -0
- package/dist/extraction/vba/implements.d.ts +1 -6
- package/dist/extraction/vba/labels.d.ts +26 -0
- package/dist/extraction/vba/module-vars.d.ts +36 -0
- package/dist/extraction/vba/options.d.ts +88 -0
- package/dist/extraction/vba/parameters.d.ts +35 -0
- package/dist/extraction/vba/procedures.d.ts +1 -9
- package/dist/extraction/vba/rules.d.ts +10 -5
- package/dist/extraction/vba/runtime-objects.d.ts +59 -0
- package/dist/extraction/vba/signature.d.ts +57 -0
- package/dist/extraction/vba/sql-wrapper.d.ts +76 -3
- package/dist/extraction/vba/text-utils.d.ts +62 -2
- package/dist/extraction/vba-extractor.d.ts +18 -1
- package/dist/extraction/vba-form-extractor.d.ts +28 -14
- package/dist/extraction/vba-preprocess.d.ts +89 -3
- package/dist/extraction/vba-source.d.ts +0 -10
- package/dist/extraction/vba-test-manifest-extractor.d.ts +0 -6
- package/dist/mcp/daemon-paths.d.ts +6 -0
- package/dist/mcp/daemon-registry.d.ts +82 -7
- package/dist/mcp/daemon-watchdog.d.ts +12 -0
- package/dist/mcp/daemon.d.ts +20 -1
- package/dist/mcp/proxy.d.ts +32 -0
- package/dist/project-config.d.ts +43 -2
- package/dist/resolution/index.d.ts +47 -2
- package/dist/resolution/name-matcher.d.ts +25 -0
- package/dist/resolution/vba-runtime-objects.d.ts +11 -11
- package/dist/types.d.ts +13 -5
- package/package.json +7 -7
|
@@ -1,7 +1,68 @@
|
|
|
1
|
-
import { VbaExtractorContext } from './context';
|
|
2
|
-
export declare function scanSqlInLine(ctx: VbaExtractorContext, line: string, lineNum: number, dedupe: Set<string>, sqlVariables: Map<string, string>): void;
|
|
1
|
+
import { VbaExtractorContext, ProcInfo } from './context';
|
|
3
2
|
/**
|
|
4
|
-
* #
|
|
3
|
+
* Issue #252 — `metadata.synthesizedBy` for a table reached through SQL
|
|
4
|
+
* assigned to a binding property at RUNTIME. Deliberately distinct from the
|
|
5
|
+
* `.form.txt` sweep's static `vba-row-source` so an audit can tell the two
|
|
6
|
+
* provenances apart: one is what the designer stored, the other is what the
|
|
7
|
+
* code actually binds.
|
|
8
|
+
*/
|
|
9
|
+
export declare const RUNTIME_BINDING_SYNTHESIZER = "vba-row-source-dynamic";
|
|
10
|
+
/**
|
|
11
|
+
* Issue #244 — the wrapper list used when `codegraph.json` →
|
|
12
|
+
* `vba.sqlWrappers` is absent. A STRICT SUPERSET of the pre-#244 behaviour:
|
|
13
|
+
*
|
|
14
|
+
* - `db` covers every receiver the old `…db\b` regex reached (see
|
|
15
|
+
* {@link receiverMatches} for the suffix arm that preserves it) plus the
|
|
16
|
+
* `db`-prefixed locals it missed (`dbUse`, `dbToUse`, `m_dbLanzadera`).
|
|
17
|
+
* - `getdb` covers the per-backend accessor family (`getdbHPS()`,
|
|
18
|
+
* `getdbExpedientes()`, `getdbLanzadera()`) the issue measured at 26-36%
|
|
19
|
+
* of all execution sites.
|
|
20
|
+
* - `CurrentDb` / `DBEngine` are the Access built-ins.
|
|
21
|
+
* - `qd` / `qdf` are the conventional DAO `QueryDef` receivers.
|
|
22
|
+
* - `DoCmd.RunSQL` is the Access statement form (both the literal and the
|
|
23
|
+
* variable spelling, previously two dedicated regexes).
|
|
24
|
+
* - `Connection.Execute` / `Recordset.Open` are the ADO pair.
|
|
25
|
+
*
|
|
26
|
+
* Project-specific accessors that do not fit these names are added through
|
|
27
|
+
* `vba.sqlWrappers`, which EXTENDS this list rather than replacing it.
|
|
28
|
+
*/
|
|
29
|
+
export declare const DEFAULT_SQL_WRAPPERS: readonly string[];
|
|
30
|
+
/** One parsed wrapper entry. Both fields are lowercase — VBA is case-insensitive. */
|
|
31
|
+
interface SqlWrapperMatcher {
|
|
32
|
+
/** Receiver name fragment, e.g. `getdb` from `"getdb"` or `cnn` from `"cnn.Execute"`. */
|
|
33
|
+
receiver: string;
|
|
34
|
+
/** Method name, or `null` for a bare entry (which implies {@link DEFAULT_WRAPPER_METHODS}). */
|
|
35
|
+
method: string | null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The per-extractor compiled wrapper state. Built ONCE per `VbaExtractor`
|
|
39
|
+
* (see `vba-extractor.ts`) and cached on `VbaExtractorContext` — the two
|
|
40
|
+
* RegExps are stateful (`/g`) and `scanSqlInLine` runs on every line of every
|
|
41
|
+
* file, so re-compiling them per line (which is what the pre-#244 code did)
|
|
42
|
+
* is pure waste on the hottest path in VBA extraction.
|
|
43
|
+
*/
|
|
44
|
+
export interface CompiledSqlWrappers {
|
|
45
|
+
/** Literal form — `<receiver>[()].<method> "…"`. Stateful; reset before use. */
|
|
46
|
+
readonly literalRe: RegExp;
|
|
47
|
+
/** Variable form — `<receiver>[()].<method> <identifier>`. Stateful; reset before use. */
|
|
48
|
+
readonly varRe: RegExp;
|
|
49
|
+
/** Parsed entries, defaults first, project entries appended. */
|
|
50
|
+
readonly matchers: readonly SqlWrapperMatcher[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Parse the plain-string wrapper entries into matchers and pair them with a
|
|
54
|
+
* fresh pair of scanning RegExps.
|
|
55
|
+
*
|
|
56
|
+
* `configured` entries are APPENDED to {@link DEFAULT_SQL_WRAPPERS}, never
|
|
57
|
+
* substituted for them: a project that names its own accessor must not lose
|
|
58
|
+
* `CurrentDb` in the trade. Entries are accepted in exactly two forms — a
|
|
59
|
+
* bare identifier fragment (`"getdb"`) or a `receiver.method` pair
|
|
60
|
+
* (`"cnn.Execute"`). Anything else is dropped here; `project-config.ts`
|
|
61
|
+
* already warned about it at load time.
|
|
62
|
+
*/
|
|
63
|
+
export declare function compileSqlWrappers(configured?: readonly string[]): CompiledSqlWrappers;
|
|
64
|
+
/**
|
|
65
|
+
* Issue #13: `sql = sql & "..."` (self-referential concatenation) must
|
|
5
66
|
* ACCUMULATE the new fragment onto whatever was already tracked for
|
|
6
67
|
* `varName`, not overwrite it. Overwriting silently dropped earlier
|
|
7
68
|
* fragments' tables — typically the initial `FROM <table>` in
|
|
@@ -14,4 +75,16 @@ export declare function scanSqlInLine(ctx: VbaExtractorContext, line: string, li
|
|
|
14
75
|
* behavior is unchanged.
|
|
15
76
|
*/
|
|
16
77
|
export declare function trackSqlVariableAssignment(lines: string[], lineIndex: number, sqlVariables: Map<string, string>): void;
|
|
78
|
+
/**
|
|
79
|
+
* Issue #255: scan one line for domain-aggregate calls and emit the saved-query
|
|
80
|
+
* reference their second argument names.
|
|
81
|
+
*
|
|
82
|
+
* Deliberately NOT handled: a domain spelled as a full SQL statement
|
|
83
|
+
* (`DLookup("N", "SELECT Id FROM T")`), which Access also accepts. It is
|
|
84
|
+
* rejected by `looksLikeSavedQueryName`'s verb gate and produces nothing —
|
|
85
|
+
* the same silence T11 chose for every literal it cannot classify.
|
|
86
|
+
*/
|
|
87
|
+
export declare function scanDomainFunctionsInLine(ctx: VbaExtractorContext, line: string, lineNum: number, dedupe: Set<string>, caller: ProcInfo): void;
|
|
88
|
+
export declare function scanSqlInLine(ctx: VbaExtractorContext, line: string, lineNum: number, dedupe: Set<string>, sqlVariables: Map<string, string>, caller?: ProcInfo): void;
|
|
89
|
+
export {};
|
|
17
90
|
//# sourceMappingURL=sql-wrapper.d.ts.map
|
|
@@ -11,8 +11,17 @@ export declare function foldVisibility(raw: string): 'public' | 'private';
|
|
|
11
11
|
* call-site patterns inside `"..."` spans are invisible to CALL_RE and
|
|
12
12
|
* the statement-form detectors. Column positions are preserved (each
|
|
13
13
|
* character is replaced 1-for-1) so any col-based metadata stays correct.
|
|
14
|
+
*
|
|
15
|
+
* Issue #265: `filler` selects the replacement character. The default `' '`
|
|
16
|
+
* is what every column-sensitive scanner wants — a masked literal reads as
|
|
17
|
+
* blank space. The statement-call detectors need the opposite: they must
|
|
18
|
+
* still be able to tell `MsgBox "x"` (an argument list, therefore
|
|
19
|
+
* unambiguously a call) from a bare `MsgBox` read, and a space-masked line
|
|
20
|
+
* collapses the two. Passing `'_'` keeps the literal *visible* as an opaque
|
|
21
|
+
* token while preserving both the 1-for-1 column mapping and the property
|
|
22
|
+
* that nothing inside the literal can be parsed as VBA syntax.
|
|
14
23
|
*/
|
|
15
|
-
export declare function maskStringContent(line: string): string;
|
|
24
|
+
export declare function maskStringContent(line: string, filler?: string): string;
|
|
16
25
|
/**
|
|
17
26
|
* #13 helper: escape a variable name for safe interpolation into the
|
|
18
27
|
* self-reference RegExp built by `trackSqlVariableAssignment`. VBA
|
|
@@ -26,7 +35,6 @@ export declare function parseConstDeclarations(body: string): Array<{
|
|
|
26
35
|
asType: string;
|
|
27
36
|
value: string | null;
|
|
28
37
|
}>;
|
|
29
|
-
export declare function splitOutsideVbaStrings(value: string, separator: string): string[];
|
|
30
38
|
/**
|
|
31
39
|
* Issue #50 helper: return true iff `line.charAt(fromIndex..)` (after the
|
|
32
40
|
* matched TempVars site, including any trailing whitespace) holds an `=`
|
|
@@ -65,4 +73,56 @@ export declare function parseEventHandlerName(name: string): {
|
|
|
65
73
|
controlName: string;
|
|
66
74
|
eventName: string;
|
|
67
75
|
} | null;
|
|
76
|
+
/**
|
|
77
|
+
* Issue #247 helper: parse a FORM-LEVEL Access event-handler Sub name.
|
|
78
|
+
*
|
|
79
|
+
* `parseEventHandlerName` above deliberately refuses `Form_*` because a
|
|
80
|
+
* form-level event fires on the form object, not on a control — routing it
|
|
81
|
+
* through the control path would synthesize a bogus `form-instance-control`
|
|
82
|
+
* node literally named `Form`. This helper is the other half of that
|
|
83
|
+
* decision: it recognises exactly the names the control parser rejects and
|
|
84
|
+
* hands them to the caller so they can be wired to the sibling
|
|
85
|
+
* `form-layout` / `report-layout` node instead.
|
|
86
|
+
*
|
|
87
|
+
* The owner segment must be exactly `Form` or `Report` (case-insensitive,
|
|
88
|
+
* matching VBA's case-insensitive identifiers), and the suffix must be a
|
|
89
|
+
* known Access event name. That second gate is what separates the real
|
|
90
|
+
* lifecycle handler `Form_Load` from an ordinary class method that merely
|
|
91
|
+
* happens to be called `Form_Helper`.
|
|
92
|
+
*/
|
|
93
|
+
export declare function parseFormLevelEventHandlerName(name: string): {
|
|
94
|
+
ownerName: string;
|
|
95
|
+
eventName: string;
|
|
96
|
+
} | null;
|
|
97
|
+
/**
|
|
98
|
+
* Sibling layout extension implied by an Access code-behind prefix.
|
|
99
|
+
* `Form_*` binds to a `.form.txt`; `Report_*` binds to a `.report.txt`.
|
|
100
|
+
*/
|
|
101
|
+
export type CodeBehindExt = '.form.txt' | '.report.txt';
|
|
102
|
+
/**
|
|
103
|
+
* Issue #249 helper: the `Form_` / `Report_` code-behind prefix carried by a
|
|
104
|
+
* module's RESOLVED `Attribute VB_Name`, used as the fallback when the file
|
|
105
|
+
* on disk does not carry the prefix in its basename.
|
|
106
|
+
*
|
|
107
|
+
* Both binding sites (the event-handler synthesis in `procedures.ts` and the
|
|
108
|
+
* `Me.<Control>` sweep in `controls.ts`) keep their own basename check as the
|
|
109
|
+
* fast path and consult this only on a basename miss, so the common case —
|
|
110
|
+
* a Dysflow export whose filename and `VB_Name` agree — is byte-for-byte
|
|
111
|
+
* unchanged.
|
|
112
|
+
*
|
|
113
|
+
* `classNamePrefix` is `null` for `.bas` modules and holds the resolved
|
|
114
|
+
* `VB_Name` (or the extension-stripped basename when the module carries no
|
|
115
|
+
* `VB_Name`) for `.cls` modules, so this fallback can only ever fire for a
|
|
116
|
+
* class module. That is deliberate: the guard that keeps a plain service
|
|
117
|
+
* class such as `InformeRiesgoPDFServicio.cls` — whose methods
|
|
118
|
+
* (`GenerarHTML_Principal`, `GetEstilosCSS_PDF`) look like event handlers to
|
|
119
|
+
* a naive `<X>_<Y>` split — from synthesizing hundreds of spurious
|
|
120
|
+
* `form-instance-control` stubs is that NEITHER its filename NOR its
|
|
121
|
+
* `VB_Name` starts with `Form_` / `Report_`. Widening the prefix test would
|
|
122
|
+
* reopen exactly that hole, so the test stays the same canonical Access
|
|
123
|
+
* naming convention; only the string it is applied to widens.
|
|
124
|
+
*
|
|
125
|
+
* The trailing `.+` is load-bearing: a bare `Form_` names no form.
|
|
126
|
+
*/
|
|
127
|
+
export declare function codeBehindExtFromVbName(classNamePrefix: string | null): CodeBehindExt | null;
|
|
68
128
|
//# sourceMappingURL=text-utils.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ExtractionResult } from '../types';
|
|
2
|
+
import type { VbaExtractionOptions } from './vba/options';
|
|
2
3
|
import type { VbaExtractionRule } from './vba/rules';
|
|
3
4
|
/**
|
|
4
5
|
* Issue #152: per-file fanout cap for `RaiseEvent <EventName>` edges. An
|
|
@@ -62,7 +63,6 @@ export declare class VbaExtractor {
|
|
|
62
63
|
private filePath;
|
|
63
64
|
private source;
|
|
64
65
|
private ctx;
|
|
65
|
-
private vbaTargets?;
|
|
66
66
|
/**
|
|
67
67
|
* Issue #152: per-file fanout cap for `RaiseEvent <EventName>` edges.
|
|
68
68
|
* When a single event is raised more than this many times in one file,
|
|
@@ -72,6 +72,23 @@ export declare class VbaExtractor {
|
|
|
72
72
|
* the orchestrator: callers that don't pass a value get the default.
|
|
73
73
|
*/
|
|
74
74
|
private maxRaiseFanout;
|
|
75
|
+
/**
|
|
76
|
+
* Issue #243: the full options object, retained so classifiers that grow
|
|
77
|
+
* a new knob read it from one place instead of a new private field per
|
|
78
|
+
* knob. `targets`/`maxRaiseFanout` keep their dedicated fields because
|
|
79
|
+
* they are read on hot paths.
|
|
80
|
+
*/
|
|
81
|
+
private options;
|
|
82
|
+
/**
|
|
83
|
+
* Issue #243 — the options-object form. Every in-repo call site uses this.
|
|
84
|
+
*/
|
|
85
|
+
constructor(filePath: string, source: string, options?: VbaExtractionOptions);
|
|
86
|
+
/**
|
|
87
|
+
* @deprecated Pass a {@link VbaExtractionOptions} object instead:
|
|
88
|
+
* `new VbaExtractor(filePath, source, { targets, maxRaiseFanout })`.
|
|
89
|
+
* The positional 3rd/4th parameters are kept working for one release so
|
|
90
|
+
* out-of-repo callers are not broken by #243; they will be removed after.
|
|
91
|
+
*/
|
|
75
92
|
constructor(filePath: string, source: string, vbaTargets?: Record<string, boolean>, maxRaiseFanout?: number);
|
|
76
93
|
extract(): ExtractionResult;
|
|
77
94
|
/**
|
|
@@ -17,6 +17,14 @@ export declare class VbaFormExtractor {
|
|
|
17
17
|
* stays self-contained per the project's per-extractor state rule).
|
|
18
18
|
*/
|
|
19
19
|
private synthClassNodeIds;
|
|
20
|
+
/**
|
|
21
|
+
* Issue #256: external-backend (`IN "<path>"`) node ids already emitted
|
|
22
|
+
* for this form. Same per-extractor de-dup discipline as
|
|
23
|
+
* `synthClassNodeIds`, keyed on the node id — which is derived from the
|
|
24
|
+
* NORMALIZED backend path, so two bindings naming the same `.accdb`
|
|
25
|
+
* share one node while each still emits its own edge.
|
|
26
|
+
*/
|
|
27
|
+
private externalBackendNodeIds;
|
|
20
28
|
constructor(filePath: string, source: string);
|
|
21
29
|
extract(): ExtractionResult;
|
|
22
30
|
private createFileNode;
|
|
@@ -63,23 +71,11 @@ export declare class VbaFormExtractor {
|
|
|
63
71
|
private emitExpressionHandlers;
|
|
64
72
|
private emitExpressionHandler;
|
|
65
73
|
private emitSourceObjectReference;
|
|
66
|
-
/**
|
|
67
|
-
* Issue #49 — copy of `VbaExtractor.SQL_TABLE_RE`. Same source / flags:
|
|
68
|
-
* captures the table name that follows `FROM`/`JOIN`/`INTO`/`UPDATE`,
|
|
69
|
-
* tolerates bracketed `[Order Details]` identifiers and `\p{L}` Unicode
|
|
70
|
-
* identifiers, and allows an optional schema prefix (`[dbo].[tblA]`).
|
|
71
|
-
*
|
|
72
|
-
* We duplicate the regex (rather than exporting it from `VbaExtractor`)
|
|
73
|
-
* because `VbaExtractor.SQL_TABLE_RE` is `private static` and the
|
|
74
|
-
* project's per-extractor state rule keeps each extractor's helpers
|
|
75
|
-
* self-contained — see the file-level JSDoc on `VbaExtractor`.
|
|
76
|
-
*/
|
|
77
|
-
private static readonly SQL_TABLE_RE;
|
|
78
74
|
/**
|
|
79
75
|
* Issue #49 — classify a RecordSource/RowSource value as SQL.
|
|
80
76
|
* Anything starting with a SQL keyword (`SELECT`, `PARAMETERS`, `WITH`,
|
|
81
77
|
* `UPDATE`, `INSERT`, `DELETE`) — case-insensitive — is treated as a
|
|
82
|
-
* SQL statement and run through `
|
|
78
|
+
* SQL statement and run through `scanSqlTables`. Anything else is a
|
|
83
79
|
* bare table-or-query name and emitted as a single reference.
|
|
84
80
|
*
|
|
85
81
|
* `WITH` is included because Access/JET supports CTE-style `WITH` queries
|
|
@@ -103,6 +99,20 @@ export declare class VbaFormExtractor {
|
|
|
103
99
|
* name — same dual-match `vba-sql-impact`'s `extractFormBindings` does).
|
|
104
100
|
*/
|
|
105
101
|
private emitTableReference;
|
|
102
|
+
/**
|
|
103
|
+
* Issue #256 — emit one `references` edge from `sourceNodeId` to the
|
|
104
|
+
* external database file an Access `IN "<path>"` clause points at.
|
|
105
|
+
*
|
|
106
|
+
* The target node is built by the shared `buildExternalBackendNode` so
|
|
107
|
+
* it is byte-identical to the node the in-code SQL sweep and the saved-
|
|
108
|
+
* query extractor emit for the same path: the graph ends up with ONE
|
|
109
|
+
* node per external backend, however many places name it. The edge
|
|
110
|
+
* carries only `synthesizedBy` — the `external` / `backendPath` facts
|
|
111
|
+
* describe the node, and live there.
|
|
112
|
+
*
|
|
113
|
+
* `backendPath` is already normalized by `scanSqlExternalBackends`.
|
|
114
|
+
*/
|
|
115
|
+
private emitExternalBackendReference;
|
|
106
116
|
/**
|
|
107
117
|
* Link a bound control to its enclosing form/report's single bare table.
|
|
108
118
|
* Expressions and SQL/absent RecordSource values stay metadata-only: column
|
|
@@ -112,7 +122,7 @@ export declare class VbaFormExtractor {
|
|
|
112
122
|
private emitControlSourceReference;
|
|
113
123
|
/**
|
|
114
124
|
* Issue #49 — dispatch the value of a RecordSource/RowSource binding.
|
|
115
|
-
* If SQL, run `
|
|
125
|
+
* If SQL, run `scanSqlTables` over the value and emit one edge per
|
|
116
126
|
* distinct table (within-value dedup so `FROM tblA JOIN tblA` emits a
|
|
117
127
|
* single edge). If a bare name, emit a single edge with the name as-is
|
|
118
128
|
* — the resolver handles the dual-match against `query` and `class`
|
|
@@ -122,6 +132,10 @@ export declare class VbaFormExtractor {
|
|
|
122
132
|
* the regex capture already includes them as part of the value, so a
|
|
123
133
|
* single `.replace(/""/g, '"')` collapses them — same technique the
|
|
124
134
|
* `extractStringLiterals` helper uses for its emitted `text` field.
|
|
135
|
+
*
|
|
136
|
+
* Issue #203: `scanSqlTables` also drops SQL reserved-word captures
|
|
137
|
+
* (`WHERE`, `ORDER`, `SET`, …) so a malformed SQL string never
|
|
138
|
+
* poisons the graph with phantom `class` nodes.
|
|
125
139
|
*/
|
|
126
140
|
private emitBinding;
|
|
127
141
|
}
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
*
|
|
33
33
|
* Source unchanged guarantee: every helper treats `src` as read-only.
|
|
34
34
|
*/
|
|
35
|
+
import type { ExtractionError } from '../types';
|
|
35
36
|
/**
|
|
36
37
|
* Collapse VBA line continuations: any line whose last non-newline character is
|
|
37
38
|
* `_` (preceded by exactly one space, per VBA convention) is joined to the
|
|
@@ -93,8 +94,36 @@ export declare function stripVbaComments(src: string): string;
|
|
|
93
94
|
* promote this evaluator to full integer arithmetic.
|
|
94
95
|
*
|
|
95
96
|
* Directives and inactive branch lines are replaced with empty strings so
|
|
96
|
-
* downstream extraction keeps source-line parity.
|
|
97
|
-
*
|
|
97
|
+
* downstream extraction keeps source-line parity.
|
|
98
|
+
*
|
|
99
|
+
* **Issue #206 contract change.** Previously the docstring read
|
|
100
|
+
* *"Unsupported/unsafe expressions evaluate to false rather than
|
|
101
|
+
* throwing"* — accurate, but it framed silent whole-branch source
|
|
102
|
+
* deletion as a safety property. It was not: false-negative extraction is
|
|
103
|
+
* indistinguishable from correct extraction to every downstream consumer
|
|
104
|
+
* (an `#If MODE = "DEBUG" Then` whose `"` throws silently blanks the
|
|
105
|
+
* branch and the symbol vanishes from the graph).
|
|
106
|
+
*
|
|
107
|
+
* The new contract:
|
|
108
|
+
*
|
|
109
|
+
* 1. **Unterminated `#If`/`#ElseIf`**: if the directive stack is
|
|
110
|
+
* non-empty after the loop ends, an `ExtractionError` (severity
|
|
111
|
+
* `warning`, code `unterminated_if`) is pushed onto the optional
|
|
112
|
+
* `errors` array AND the un-blanked lines from the earliest
|
|
113
|
+
* unclosed `#If` are re-emitted. An unterminated `#If` is far
|
|
114
|
+
* more likely a typo than a genuinely dead file tail.
|
|
115
|
+
* 2. **Could-not-evaluate expressions**: `evaluateConditionalExpression`
|
|
116
|
+
* now distinguishes `evaluated: false` (a definitive false — e.g.
|
|
117
|
+
* `#If 0 Then`, unknown identifier falling through to 0) from
|
|
118
|
+
* `could-not-evaluate` (the lexer/parser hit an unsupported token,
|
|
119
|
+
* or `tokenize`/`parse` threw). On could-not-evaluate, the branch
|
|
120
|
+
* is **kept active** (conservative for a code-intelligence tool —
|
|
121
|
+
* a spurious symbol is recoverable, a missing one is invisible)
|
|
122
|
+
* and a warning is emitted through the same `errors` channel.
|
|
123
|
+
*
|
|
124
|
+
* The `errors` channel is OPTIONAL — every existing call site (and
|
|
125
|
+
* existing test) continues to work without it. The orchestrator can
|
|
126
|
+
* supply it to surface the warnings through `ctx.errors`.
|
|
98
127
|
*/
|
|
99
128
|
export declare function preprocessConditionalCompilation(src: string, customTargets?: Record<string, boolean>,
|
|
100
129
|
/**
|
|
@@ -106,7 +135,64 @@ export declare function preprocessConditionalCompilation(src: string, customTarg
|
|
|
106
135
|
* inner sub-totals. Undefined → no-op, zero cost (just the
|
|
107
136
|
* `if (timings)` null-check on every CC line).
|
|
108
137
|
*/
|
|
109
|
-
timings?: Map<string, number> | null
|
|
138
|
+
timings?: Map<string, number> | null,
|
|
139
|
+
/**
|
|
140
|
+
* Issue #206: optional `ExtractionError` sink for diagnostics the
|
|
141
|
+
* preprocessor wants to surface to extraction callers:
|
|
142
|
+
* - `unterminated_if` (warning) — `#If` / `#ElseIf` pushed but
|
|
143
|
+
* never closed by `#End If` (a likely typo).
|
|
144
|
+
* - `unparseable_expression` (warning) — a `#If` / `#ElseIf`
|
|
145
|
+
* expression the lexer/parser could not handle (the branch is
|
|
146
|
+
* still kept active under the conservative contract).
|
|
147
|
+
*
|
|
148
|
+
* Undefined → no-op, zero cost (the `if (errors)` null-check on
|
|
149
|
+
* every warning site). The orchestrator passes `this.ctx.errors` so
|
|
150
|
+
* the warnings route through `ExtractionResult.errors`.
|
|
151
|
+
*/
|
|
152
|
+
errors?: ExtractionError[],
|
|
153
|
+
/**
|
|
154
|
+
* Issue #206: optional file path included on every pushed error
|
|
155
|
+
* when the caller wants telemetry to know which file the warning
|
|
156
|
+
* came from. Independent of `errors` so a caller can attach the
|
|
157
|
+
* path even when collecting warnings elsewhere.
|
|
158
|
+
*/
|
|
159
|
+
filePath?: string): string;
|
|
160
|
+
/**
|
|
161
|
+
* Issue #206 discriminated return value for `evaluateConditionalExpression`.
|
|
162
|
+
*
|
|
163
|
+
* - `{ kind: 'evaluated', value }` — the lexer+parser produced a
|
|
164
|
+
* definitive answer. `value` is the truthiness of the expression
|
|
165
|
+
* under VBA semantics (non-zero = true).
|
|
166
|
+
* - `{ kind: 'could-not-evaluate' }` — the lexer/parser threw
|
|
167
|
+
* (unsupported token: string literal, floating-point literal,
|
|
168
|
+
* unrecognized character, mismatched paren, etc.). The caller MUST
|
|
169
|
+
* treat this as ACTIVE under the conservative contract from
|
|
170
|
+
* Issue #206 — see `preprocessConditionalCompilation`.
|
|
171
|
+
*/
|
|
172
|
+
export type ConditionalEvaluation = {
|
|
173
|
+
readonly kind: 'evaluated';
|
|
174
|
+
readonly value: boolean;
|
|
175
|
+
} | {
|
|
176
|
+
readonly kind: 'could-not-evaluate';
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Issue #206: marker error class for explicitly-unsupported VBA
|
|
180
|
+
* conditional-compilation tokens. The legacy `throw new Error(...)`
|
|
181
|
+
* from `tokenize` was indistinguishable from a real bug
|
|
182
|
+
* (`Unexpected trailing token`, mismatched paren); both bubbled into
|
|
183
|
+
* the same `catch { return false }`. The new helper carries the
|
|
184
|
+
* specific reason so the caller can attribute warnings precisely and
|
|
185
|
+
* distinguish "the lexer simply cannot handle this token" from a
|
|
186
|
+
* legitimate parse failure (which is also routed to
|
|
187
|
+
* could-not-evaluate, but with the original message preserved).
|
|
188
|
+
*/
|
|
189
|
+
export declare class UnparseableExpressionError extends Error {
|
|
190
|
+
/** Coarse category: `string_literal`, `float_literal`, `unsupported_character`. */
|
|
191
|
+
readonly reason: string;
|
|
192
|
+
/** The verbatim fragment that triggered the throw (for diagnostics). */
|
|
193
|
+
readonly fragment: string;
|
|
194
|
+
constructor(reason: string, fragment: string);
|
|
195
|
+
}
|
|
110
196
|
export interface StringLiteralSpan {
|
|
111
197
|
/** The literal content (no surrounding quotes; `""` collapsed to `"`). */
|
|
112
198
|
text: string;
|
|
@@ -45,14 +45,4 @@ export interface ReadVbaSourceResult {
|
|
|
45
45
|
* @param opts optional dependencies; see {@link ReadVbaSourceOptions}.
|
|
46
46
|
*/
|
|
47
47
|
export declare function readVbaSource(filePath: string, opts?: ReadVbaSourceOptions): ReadVbaSourceResult;
|
|
48
|
-
/**
|
|
49
|
-
* String-level BOM strip — survives when the upstream `fsp.readFile(path, 'utf-8')`
|
|
50
|
-
* already decoded the bytes (it's the only way the `\uFEFF` char survives in
|
|
51
|
-
* a string). Used as a defensive last resort when a read site has NOT gone
|
|
52
|
-
* through `readVbaSource` and the source text starts with the BOM marker.
|
|
53
|
-
*
|
|
54
|
-
* Most call sites should prefer `readVbaSource` for fresh reads; this is
|
|
55
|
-
* a string post-process for callers that already have a `string` in hand.
|
|
56
|
-
*/
|
|
57
|
-
export declare function stripUtf8Bom(text: string): string;
|
|
58
48
|
//# sourceMappingURL=vba-source.d.ts.map
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
import { ExtractionResult } from '../types';
|
|
2
|
-
/**
|
|
3
|
-
* Content-shape gate: is `parsed` a VBA test manifest — a top-level `tests`
|
|
4
|
-
* array with at least one item carrying a string `procedure`? Pure; the file's
|
|
5
|
-
* basename is gated separately by `isVbaTestManifestFile` in `grammars.ts`.
|
|
6
|
-
*/
|
|
7
|
-
export declare function isVbaTestManifestShape(parsed: unknown): boolean;
|
|
8
2
|
export declare class VbaTestManifestExtractor {
|
|
9
3
|
private filePath;
|
|
10
4
|
private source;
|
|
@@ -52,6 +52,12 @@ export declare function getDaemonSocketCandidates(projectRoot: string): string[]
|
|
|
52
52
|
export declare function getDaemonSocketPath(projectRoot: string): string;
|
|
53
53
|
/** Absolute path to the daemon pid lockfile for `projectRoot`. */
|
|
54
54
|
export declare function getDaemonPidPath(projectRoot: string): string;
|
|
55
|
+
/** Root-scoped lease held while intentional release owns lifecycle cleanup. */
|
|
56
|
+
export declare function getDaemonReleaseLeasePath(projectRoot: string): string;
|
|
57
|
+
/** Exclusive marker for release-lease recovery/heartbeat publication. */
|
|
58
|
+
export declare function getDaemonReleaseRecoveryPath(projectRoot: string): string;
|
|
59
|
+
/** Short root-scoped arbitration lock shared by startup and release publication. */
|
|
60
|
+
export declare function getDaemonLifecyclePath(projectRoot: string): string;
|
|
55
61
|
/** Structured contents of the pid lockfile. */
|
|
56
62
|
export interface DaemonLockInfo {
|
|
57
63
|
pid: number;
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global daemon registry + stop/list control — the discovery layer behind
|
|
3
|
+
* `codegraph list` and `codegraph stop [--all]`.
|
|
4
|
+
*
|
|
5
|
+
* Every per-project daemon already writes an authoritative lockfile at
|
|
6
|
+
* `<root>/.codegraph/daemon.pid`. That's enough to stop ONE daemon you can name,
|
|
7
|
+
* but there's no central place to find them ALL — which `list` and `stop --all`
|
|
8
|
+
* need. So each daemon also drops a tiny record under `~/.codegraph/daemons/` on
|
|
9
|
+
* start and removes it on graceful shutdown.
|
|
10
|
+
*
|
|
11
|
+
* The registry is a DISCOVERY index, never a source of truth: the live pid is.
|
|
12
|
+
* A SIGKILL'd daemon can't remove its own record, so readers prune any record
|
|
13
|
+
* whose pid is dead (`isProcessAlive`). Every write/read is best-effort — a
|
|
14
|
+
* registry hiccup must never break the daemon or a command; worst case `list`
|
|
15
|
+
* momentarily misses or over-lists one, which the next liveness prune corrects.
|
|
16
|
+
*
|
|
17
|
+
* Cross-platform by construction: only files + `process.kill(pid, signal)`,
|
|
18
|
+
* which behave consistently on macOS/Linux (real signals) and Windows (mapped to
|
|
19
|
+
* TerminateProcess). Validated live on all three.
|
|
20
|
+
*/
|
|
21
|
+
import * as fs from 'fs';
|
|
1
22
|
export interface DaemonRecord {
|
|
2
23
|
/** Realpath'd project root the daemon serves. */
|
|
3
24
|
root: string;
|
|
@@ -32,16 +53,70 @@ export declare function listDaemons(opts?: {
|
|
|
32
53
|
export interface StopResult {
|
|
33
54
|
root: string;
|
|
34
55
|
pid: number | null;
|
|
35
|
-
|
|
36
|
-
|
|
56
|
+
outcome: 'released' | 'not-running' | 'no-daemon' | 'identity-mismatch' | 'unreachable' | 'termination-failed';
|
|
57
|
+
failure?: string;
|
|
58
|
+
}
|
|
59
|
+
export interface ExpectedDaemon {
|
|
60
|
+
root: string;
|
|
61
|
+
pid: number;
|
|
62
|
+
version: string;
|
|
63
|
+
socketPath: string;
|
|
64
|
+
startedAt: number;
|
|
65
|
+
}
|
|
66
|
+
export interface DaemonReleaseDeps {
|
|
67
|
+
isAlive?: (pid: number) => boolean;
|
|
68
|
+
waitForDeath?: (pid: number, timeoutMs: number) => Promise<boolean>;
|
|
69
|
+
requestControl?: (expected: ExpectedDaemon) => Promise<'releasing' | 'identity-mismatch' | 'unreachable'>;
|
|
70
|
+
beforeArtifactDelete?: () => void;
|
|
71
|
+
leaseLinkSync?: typeof fs.linkSync;
|
|
72
|
+
afterLifecycleAcquired?: () => void;
|
|
73
|
+
afterReleaseLeasePublished?: (expected: ExpectedDaemon) => void;
|
|
74
|
+
}
|
|
75
|
+
export declare const DAEMON_RELEASE_LEASE_TTL_MS = 30000;
|
|
76
|
+
export interface DaemonLifecycleLock {
|
|
77
|
+
token: string;
|
|
78
|
+
ownerPid: number;
|
|
79
|
+
createdAt: number;
|
|
80
|
+
expiresAt: number;
|
|
81
|
+
}
|
|
82
|
+
/** Atomically acquire the shared startup/release publication arbiter. */
|
|
83
|
+
export declare function tryAcquireDaemonLifecycleLock(root: string, options?: {
|
|
84
|
+
now?: number;
|
|
85
|
+
linkSync?: typeof fs.linkSync;
|
|
86
|
+
}): DaemonLifecycleLock | null;
|
|
87
|
+
export declare function releaseDaemonLifecycleLock(root: string, lock: DaemonLifecycleLock): void;
|
|
88
|
+
export interface DaemonReleaseLease {
|
|
89
|
+
token: string;
|
|
90
|
+
ownerPid: number;
|
|
91
|
+
createdAt: number;
|
|
92
|
+
heartbeatAt: number;
|
|
93
|
+
expiresAt: number;
|
|
94
|
+
generation: Pick<ExpectedDaemon, 'pid' | 'startedAt' | 'socketPath'>;
|
|
37
95
|
}
|
|
96
|
+
/** True while intentional release exclusively owns this root's lifecycle. */
|
|
97
|
+
export declare function hasActiveDaemonReleaseLease(root: string, now?: number, hooks?: {
|
|
98
|
+
afterRecoveryRead?: () => void;
|
|
99
|
+
}): boolean;
|
|
100
|
+
/** Atomically claim release/cleanup ownership for one daemon generation. */
|
|
101
|
+
export declare function tryAcquireDaemonReleaseLease(root: string, expected: ExpectedDaemon, options?: {
|
|
102
|
+
now?: number;
|
|
103
|
+
linkSync?: typeof fs.linkSync;
|
|
104
|
+
}): DaemonReleaseLease | null;
|
|
105
|
+
/** Publish a complete renewed generation atomically under the recovery marker. */
|
|
106
|
+
export declare function refreshDaemonReleaseLease(root: string, lease: DaemonReleaseLease, now?: number): boolean;
|
|
107
|
+
/** Resolve an explicit project root to the same canonical form daemons use. */
|
|
108
|
+
export declare function canonicalDaemonRoot(root: string): string;
|
|
109
|
+
/** Stable map/set key for aliases of the same canonical root. */
|
|
110
|
+
export declare function canonicalDaemonRootKey(root: string): string;
|
|
111
|
+
/** Project-scoped, idempotent release contract shared by CLI and MCP. */
|
|
112
|
+
export declare function releaseDaemonAt(root: string, deps?: DaemonReleaseDeps): Promise<StopResult>;
|
|
38
113
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* falling back to the registry.
|
|
114
|
+
* Release the daemon serving `root` through its authenticated socket handshake,
|
|
115
|
+
* wait for confirmed process death, then sweep ownership artifacts. Never sends
|
|
116
|
+
* a signal based only on pidfile/registry liveness. `root` must be realpath'd.
|
|
43
117
|
*/
|
|
44
|
-
export declare function stopDaemonAt(root: string): Promise<StopResult>;
|
|
118
|
+
export declare function stopDaemonAt(root: string, deps?: DaemonReleaseDeps): Promise<StopResult>;
|
|
119
|
+
export declare function requestDaemonRelease(expected: ExpectedDaemon): Promise<'releasing' | 'identity-mismatch' | 'unreachable'>;
|
|
45
120
|
/** Stop every registered, live daemon. */
|
|
46
121
|
export declare function stopAllDaemons(): Promise<StopResult[]>;
|
|
47
122
|
//# sourceMappingURL=daemon-registry.d.ts.map
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* Spawned detaches (own session/process group) so closing the MCP process
|
|
17
17
|
* does not take the watchdog down with it.
|
|
18
18
|
*/
|
|
19
|
+
import { type StopResult } from './daemon-registry';
|
|
19
20
|
export interface DaemonWatchdogOptions {
|
|
20
21
|
/** Poll interval (ms). Default 30s. */
|
|
21
22
|
intervalMs?: number;
|
|
@@ -33,6 +34,10 @@ export interface DaemonWatchdogOptions {
|
|
|
33
34
|
windowsHide?: boolean;
|
|
34
35
|
stdio?: 'ignore' | ['ignore', number, number];
|
|
35
36
|
}) => boolean;
|
|
37
|
+
/** Release seam for deterministic overlap/failure tests. */
|
|
38
|
+
releaseFn?: (root: string) => Promise<StopResult>;
|
|
39
|
+
/** Async boundary before spawn; defaults to one microtask for release ordering. */
|
|
40
|
+
beforeSpawn?: () => Promise<void>;
|
|
36
41
|
}
|
|
37
42
|
/**
|
|
38
43
|
* Per-project daemon liveness watchdog. Registers roots; polls every
|
|
@@ -40,16 +45,23 @@ export interface DaemonWatchdogOptions {
|
|
|
40
45
|
*/
|
|
41
46
|
export declare class DaemonWatchdog {
|
|
42
47
|
private readonly roots;
|
|
48
|
+
private readonly releasing;
|
|
43
49
|
private interval;
|
|
44
50
|
private readonly intervalMs;
|
|
45
51
|
private readonly scriptPath;
|
|
46
52
|
private readonly nodePath;
|
|
47
53
|
private readonly spawnFn;
|
|
54
|
+
private readonly releaseFn;
|
|
55
|
+
private readonly beforeSpawn;
|
|
48
56
|
constructor(opts?: DaemonWatchdogOptions);
|
|
49
57
|
/** Watch a project root: if its daemon dies, respawn it. Idempotent. */
|
|
50
58
|
watch(root: string): void;
|
|
59
|
+
/** Begin a new lifecycle for an intentionally released root. */
|
|
60
|
+
resume(root: string): void;
|
|
51
61
|
/** Stop watching a root. Idempotent. */
|
|
52
62
|
unwatch(root: string): void;
|
|
63
|
+
/** Intentionally release one project without allowing the next tick to respawn it. */
|
|
64
|
+
release(root: string): Promise<StopResult>;
|
|
53
65
|
/** Number of roots currently being watched. */
|
|
54
66
|
size(): number;
|
|
55
67
|
/**
|
package/dist/mcp/daemon.d.ts
CHANGED
|
@@ -63,8 +63,17 @@ export interface DaemonHello {
|
|
|
63
63
|
codegraph: string;
|
|
64
64
|
pid: number;
|
|
65
65
|
socketPath: string;
|
|
66
|
+
root: string;
|
|
67
|
+
startedAt: number;
|
|
66
68
|
protocol: 1;
|
|
67
69
|
}
|
|
70
|
+
export interface DaemonReleaseControl {
|
|
71
|
+
codegraph_control: 1;
|
|
72
|
+
action: 'release';
|
|
73
|
+
root: string;
|
|
74
|
+
pid: number;
|
|
75
|
+
startedAt: number;
|
|
76
|
+
}
|
|
68
77
|
/**
|
|
69
78
|
* Optional reverse-handshake line a proxy sends right after it verifies the
|
|
70
79
|
* daemon hello, carrying its own pids so the daemon can reap the client if its
|
|
@@ -112,9 +121,16 @@ export declare class Daemon {
|
|
|
112
121
|
private stopping;
|
|
113
122
|
private socketPath;
|
|
114
123
|
private pidPath;
|
|
124
|
+
private startedAt;
|
|
125
|
+
private acquiredGeneration;
|
|
126
|
+
private listenSocket;
|
|
127
|
+
private exit;
|
|
115
128
|
constructor(projectRoot: string, opts?: {
|
|
116
129
|
idleTimeoutMs?: number;
|
|
117
130
|
maxIdleMs?: number;
|
|
131
|
+
generation?: DaemonLockInfo;
|
|
132
|
+
listenSocket?: (socketPath: string, onConnection: (socket: net.Socket) => void) => Promise<net.Server>;
|
|
133
|
+
exit?: (code: number) => void;
|
|
118
134
|
});
|
|
119
135
|
/**
|
|
120
136
|
* Bind the socket, kick off engine init, and register signal handlers. The
|
|
@@ -222,7 +238,9 @@ export type AcquireResult = {
|
|
|
222
238
|
* widened). The race's worst case is two daemons briefly; on a single external
|
|
223
239
|
* drive that's strictly better than the daemon never starting at all.
|
|
224
240
|
*/
|
|
225
|
-
export declare function tryAcquireDaemonLock(projectRoot: string
|
|
241
|
+
export declare function tryAcquireDaemonLock(projectRoot: string, options?: {
|
|
242
|
+
afterLifecycleAcquired?: () => void;
|
|
243
|
+
}): AcquireResult;
|
|
226
244
|
/**
|
|
227
245
|
* Exclusive-create the pidfile (O_CREAT|O_EXCL via the `wx` flag) and write the
|
|
228
246
|
* full record through the same fd — the hard-link-free fallback used by
|
|
@@ -285,6 +303,7 @@ export declare function peerIsDead(peers: {
|
|
|
285
303
|
pid: number | null;
|
|
286
304
|
hostPid: number | null;
|
|
287
305
|
}, isAlive: (pid: number) => boolean): boolean;
|
|
306
|
+
export declare function parseReleaseControlLine(line: string): DaemonReleaseControl | null;
|
|
288
307
|
/** Exported for test stubs that need to bound the hello-line read. */
|
|
289
308
|
export { MAX_HELLO_LINE_BYTES };
|
|
290
309
|
//# sourceMappingURL=daemon.d.ts.map
|