/** * Message-topology edges (change: add-message-topology-edges). * * A client/server, IPC, or Redux-style app often has no call between the code that builds a * message and the code that handles it: `ws.send({ type: "restart" })` on one side, and * `switch (msg.type) { case "restart": … }` on the other. This module collects both sides from a * parsed TypeScript/JavaScript file so the call graph's synthesis pass can pair them: * * - a **consumer clause** is a `switch (x.)` case, the consequent of * `if (x. === )`, or the rest of a function after an early-exit guard * `if (x. !== ) return;`, keyed `=`. It becomes a registration whose * handler is the clause's single callee when the clause body is one call, and the enclosing * function otherwise; * - a **construction site** is an object literal carrying `: ` that is handed straight * to a transport call (`send`, `postMessage`, `dispatch`, …, optionally through * `JSON.stringify`), keyed the same way. It becomes a dispatch from its enclosing function. * * A tag pairs only when it is static: a string, a substitution-free template, or `A.B` where `A` * is bound at module scope or imported (an enum, a constant object). A parameter or local * (`case ev.name:`) is a run-time value, as is another message's discriminant (`m.type`). * * Two precision gates, both measured on this repository before they were added: * * - only the discriminant fields in {@link MESSAGE_DISCRIMINANT_FIELDS} count. Any object literal * can carry a string property, and collecting all of them would make every `{ name: "x" }` a * message; * - only a literal handed to a transport call is a message. A plain data enum (`{ kind: "calls" }` * built, then tested by `if (e.kind === "calls")` far away) has the same shape as a message * protocol. Without this gate, 150 of the 154 edges the rule produced on this repository were * such data-flow pairings, not message hops. A Redux action creator that RETURNS its action is * therefore not paired; the `dispatch({ type })` call site is. * * Besides pairable registrations, the collector records two kinds of MARKER registration (no * handler): a clause no indexed function contains ({@link unattributedConsumerKey}), and a clause * that compares the field against a named constant it cannot evaluate ({@link uncomparableKey}). * Markers never pair; they only make a send's disclosure say what is actually known. * * Deterministic and no LLM: a fixed walk over the tree the extractor already parsed. */ import type Parser from 'tree-sitter'; /** Languages whose construction and consumer idioms this module recovers. */ export declare const MESSAGE_TOPOLOGY_LANGUAGES: ReadonlySet; /** The provenance label on every synthesized message edge. */ export declare const MESSAGE_TOPOLOGY_RULE = "message-topology"; /** The discriminant field names a message protocol switches on. Closed on purpose (see module doc). */ export declare const MESSAGE_DISCRIMINANT_FIELDS: ReadonlySet; export declare function mayContainMessageTopology(content: string): boolean; /** Marker key: a clause selects `key`, but no indexed function contains it. */ export declare function unattributedConsumerKey(key: string): string; /** Marker key: a clause compares `field` against the named constant `name`, which it cannot evaluate. */ export declare function uncomparableKey(field: string, name: string): string; /** True for a marker registration key (one that never pairs). */ export declare function isMarkerKey(key: string): boolean; /** The discriminant field of a pairing key (`type=str:x` → `type`). */ export declare function keyField(key: string): string; /** * The constant name a send's tag could be spelled as on the consumer side: `const:types.X` → `X`, * `str:X` → `X`. A clause testing `case X:` (an imported constant) may hold exactly that value. */ export declare function tagConstantName(key: string): string | undefined; /** * One handler clause: key → the node ids that handle it. `file` is the clause's file, which the * test-file rule needs for a marker. */ export interface MessageRegistration { key: string; handlerIds: string[]; /** The function enclosing the clause, which the fan-out cap counts (one per clause container). */ container?: string; file?: string; } /** One construction site. */ export interface MessageDispatch { key: string; callerId: string; line: number; } /** A construction site, with the source of the transport call it is handed to. */ export interface MessageSend { key: string; /** Absent when no indexed function contains the send. */ callerId?: string; filePath: string; line: number; /** * Byte span of the transport call in the file. The caller slices bounded evidence from it — the * call's full text is never copied, since nested sends would retain it quadratically. */ start: number; end: number; } export interface MessageSites { registrations: MessageRegistration[]; dispatches: MessageDispatch[]; sends: MessageSend[]; } /** What the collector needs from the call-graph builder, injected so this module stays a leaf. */ export interface MessageCollectorEnv { filePath: string; /** Node id of the indexed function enclosing a byte offset. */ enclosingId: (startIndex: number) => string | undefined; /** Node id of a called name, resolved without guessing, or undefined. */ resolveCallee: (name: string) => string | undefined; } /** * Collect one parsed TS/JS file's message-topology sites into `out`, in ONE top-down walk that * carries the ancestor stack — no `.parent` call anywhere, since tree-sitter answers each one by * re-walking from the root. */ export declare function collectMessageSites(root: Parser.SyntaxNode, baseEnv: MessageCollectorEnv, out: MessageSites): void; //# sourceMappingURL=message-topology.d.ts.map