machina-inspect 1.0.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["MACHINA_TYPE"],"sources":["../src/handler-analyzer.ts","../src/graph-builder.ts","../src/checks/unreachable.ts","../src/checks/onenter-loop.ts","../src/checks/missing-handler.ts","../src/inspect.ts"],"sourcesContent":["// =============================================================================\n// handler-analyzer.ts — Acorn-based static analysis of function handlers\n//\n// Parses handler.toString() with acorn, walks the AST for ReturnStatement\n// nodes that return string literals, and determines confidence based on\n// whether the return is nested in a conditional or not.\n//\n// This is best-effort: we catch explicit `return \"stateName\"` patterns.\n// Dynamic returns (variables, template literals, computed expressions) are\n// silently skipped — we produce no false edges, just miss some.\n// =============================================================================\n\nimport * as acorn from \"acorn\";\n\nexport interface HandlerTarget {\n target: string;\n confidence: \"definite\" | \"possible\";\n}\n\n// acorn nodes have a `type` string field plus arbitrary children.\n// We use a loose type to keep the AST walking code simple.\n// Declared early so AstFunctionNode can reference it.\ntype AcornNode = {\n type: string;\n body?: AcornNode | AcornNode[];\n argument?: AcornNode | null;\n value?: unknown;\n consequent?: AcornNode | AcornNode[];\n alternate?: AcornNode | null;\n cases?: AcornNode[];\n block?: AcornNode;\n handler?: AcornNode | null;\n finalizer?: AcornNode | null;\n left?: AcornNode;\n right?: AcornNode;\n expression?: AcornNode | boolean;\n declarations?: AcornNode[];\n init?: AcornNode | null;\n test?: AcornNode | null;\n update?: AcornNode | null;\n params?: AcornNode[];\n [key: string]: unknown;\n};\n\n/**\n * A union of ESTree node types that represent a callable handler function.\n * Structurally compatible with both acorn nodes and ESLint's @types/estree,\n * so walkHandlerAst can accept either without type juggling at the call site.\n *\n * The index signature `[key: string]: unknown` keeps this loose enough that\n * acorn's extra fields (start, end, loc) don't cause structural mismatches.\n */\nexport type AstFunctionNode = {\n type: \"FunctionDeclaration\" | \"FunctionExpression\" | \"ArrowFunctionExpression\";\n body: AcornNode;\n expression?: boolean; // true for concise arrow functions (no block body)\n params: AcornNode[];\n [key: string]: unknown;\n};\n\n// Node types that make a return \"conditional\" — any string-returning\n// ReturnStatement nested inside one of these becomes \"possible\".\nconst CONDITIONAL_ANCESTORS = new Set([\n \"IfStatement\",\n \"SwitchStatement\",\n \"SwitchCase\",\n \"ConditionalExpression\",\n \"LogicalExpression\",\n \"TryStatement\",\n]);\n\ninterface ReturnInfo {\n target: string;\n isConditional: boolean;\n}\n\nconst PARSE_OPTS: acorn.Options = { ecmaVersion: 2022, sourceType: \"script\" };\n\n/**\n * Try to parse a function's toString() output using several strategies.\n *\n * Function.prototype.toString() produces different formats:\n * - Named function declaration: `function name() { ... }` → parses as-is\n * - Anonymous function expression: `function () { ... }` → needs parens wrapper\n * - Arrow function: `() => { ... }` → parses as-is\n * - Method shorthand: `name() { ... }` → needs object literal wrapper\n *\n * Returns null if none of the strategies succeed (unusual syntax, etc.).\n */\nfunction tryParse(source: string): acorn.Node | null {\n // Strategy 1: parse as-is (named function declarations, arrow functions)\n try {\n return acorn.parse(source, PARSE_OPTS);\n } catch {\n // continue to next strategy\n }\n\n // Strategy 2: wrap in parens (anonymous function expressions)\n try {\n return acorn.parse(`(${source})`, PARSE_OPTS);\n } catch {\n // continue to next strategy\n }\n\n // Strategy 3: wrap as object method (method shorthand `name() { ... }`)\n try {\n return acorn.parse(`({${source}})`, PARSE_OPTS);\n } catch {\n // All strategies exhausted — fail silently\n return null;\n }\n}\n\n/**\n * Find the first ArrowFunctionExpression node in a parsed AST.\n * Returns undefined if no arrow function exists at the top level.\n */\nfunction findArrowFunction(node: AcornNode): AcornNode | undefined {\n if (node.type === \"ArrowFunctionExpression\") {\n return node;\n }\n if (node.type === \"Program\" || node.type === \"ExpressionStatement\") {\n const children = getChildren(node);\n for (const child of children) {\n const found = findArrowFunction(child);\n if (found) {\n return found;\n }\n }\n }\n return undefined;\n}\n\n/**\n * Handle concise arrow functions (`() => \"state\"` with no block body).\n *\n * Acorn represents these with `expression: true` on the ArrowFunctionExpression\n * and the body as a Literal directly — there is no ReturnStatement, so the\n * general walkForReturns() misses them entirely.\n *\n * Returns:\n * - HandlerTarget[] with one definite entry if the body is a string Literal\n * - [] if the body is a non-string expression (no false edges produced)\n * - undefined if the AST doesn't match the concise arrow pattern at all\n * (caller should fall through to the general walker)\n */\nfunction checkConciseArrow(node: AstFunctionNode): HandlerTarget[] | undefined {\n // A concise arrow has `expression: true` and the body is not a BlockStatement.\n if (node.type !== \"ArrowFunctionExpression\" || !node.expression) {\n // Not a concise arrow — let checkConciseArrow's caller also check\n // the parsed Program wrapper for arrow functions embedded in Program nodes.\n return undefined;\n }\n const body = node.body as AcornNode;\n if (body && body.type === \"Literal\" && typeof body.value === \"string\") {\n return [{ target: body.value, confidence: \"definite\" }];\n }\n // Non-string concise arrow (e.g., `() => someVar`) — no targets extractable\n return [];\n}\n\n/**\n * Handle concise arrow functions found inside a parsed Program/ExpressionStatement.\n * Used by analyzeHandler after tryParse() wraps the source in a Program node.\n */\nfunction checkConciseArrowInAst(ast: AcornNode): HandlerTarget[] | undefined {\n const arrowNode = findArrowFunction(ast);\n if (!arrowNode || !arrowNode.expression) {\n return undefined;\n }\n const body = arrowNode.body as AcornNode;\n if (body && body.type === \"Literal\" && typeof body.value === \"string\") {\n return [{ target: body.value, confidence: \"definite\" }];\n }\n return [];\n}\n\n/**\n * Walk a pre-parsed handler function AST node and extract transition targets.\n *\n * Accepts a FunctionDeclaration, FunctionExpression, or ArrowFunctionExpression\n * node (as produced by acorn or the ESLint rule engine). Returns an array of\n * { target, confidence } pairs. Empty array means no statically-determinable\n * string return targets were found.\n *\n * This is the shared core used by both:\n * - analyzeHandler() — the runtime path (parses fn.toString() first)\n * - ESLint rules — the AST path (ESLint already has the parsed node)\n */\nexport function walkHandlerAst(node: AstFunctionNode): HandlerTarget[] {\n // Concise arrow functions (`() => \"state\"`) have no ReturnStatement.\n // The body is the expression directly — handle before the general walker.\n const conciseResult = checkConciseArrow(node);\n if (conciseResult !== undefined) {\n return conciseResult;\n }\n\n const returns: ReturnInfo[] = [];\n walkForReturns(node as unknown as AcornNode, [], returns);\n\n if (returns.length === 0) {\n return [];\n }\n\n // Determine confidence:\n // - A single non-conditional return that's the ONLY string return → \"definite\"\n // - Multiple returns or conditional returns → all \"possible\"\n const allNonConditional = returns.every(r => !r.isConditional);\n const isSoleReturn = returns.length === 1;\n\n return returns.map(({ target, isConditional }) => ({\n target,\n confidence: allNonConditional && isSoleReturn && !isConditional ? \"definite\" : \"possible\",\n }));\n}\n\n/**\n * Analyze a handler function and extract transition targets.\n *\n * Returns an array of { target, confidence } pairs. Empty array means\n * no statically-determinable string return targets were found.\n *\n * Safe to call on any function — parse errors return empty (fail silently).\n */\nexport function analyzeHandler(fn: (...args: unknown[]) => unknown): HandlerTarget[] {\n const source = fn.toString();\n\n // Native/bound functions have no analyzable body — skip them.\n if (source.includes(\"[native code]\")) {\n return [];\n }\n\n const ast = tryParse(source);\n if (!ast) {\n return [];\n }\n\n // Concise arrow functions (`() => \"state\"`) have no ReturnStatement in their\n // AST. Instead, the ArrowFunctionExpression node has `expression: true` and\n // the body is a Literal directly. Handle this before the general walker.\n const conciseResult = checkConciseArrowInAst(ast as unknown as AcornNode);\n if (conciseResult !== undefined) {\n return conciseResult;\n }\n\n // For block-body functions, find the top-level function node and delegate\n // to the shared AST walker.\n const fnNode = findTopLevelFunctionNode(ast as unknown as AcornNode);\n if (fnNode) {\n return walkHandlerAst(fnNode as unknown as AstFunctionNode);\n }\n\n // Fallback: walk the raw AST directly (method shorthand wrapper case)\n const returns: ReturnInfo[] = [];\n walkForReturns(ast as unknown as AcornNode, [], returns);\n\n if (returns.length === 0) {\n return [];\n }\n\n const allNonConditional = returns.every(r => !r.isConditional);\n const isSoleReturn = returns.length === 1;\n\n return returns.map(({ target, isConditional }) => ({\n target,\n confidence: allNonConditional && isSoleReturn && !isConditional ? \"definite\" : \"possible\",\n }));\n}\n\n/**\n * Find the top-level function node in a parsed Program AST.\n * Returns the first FunctionDeclaration, FunctionExpression, or\n * ArrowFunctionExpression at the top level.\n */\nfunction findTopLevelFunctionNode(ast: AcornNode): AcornNode | undefined {\n const children = getChildren(ast);\n for (const child of children) {\n if (\n child.type === \"FunctionDeclaration\" ||\n child.type === \"FunctionExpression\" ||\n child.type === \"ArrowFunctionExpression\"\n ) {\n return child;\n }\n // ExpressionStatement wrapper (anonymous function expressions wrapped in parens)\n if (child.type === \"ExpressionStatement\") {\n const grandChildren = getChildren(child);\n for (const gc of grandChildren) {\n if (\n gc.type === \"FunctionDeclaration\" ||\n gc.type === \"FunctionExpression\" ||\n gc.type === \"ArrowFunctionExpression\"\n ) {\n return gc;\n }\n }\n }\n }\n return undefined;\n}\n\n/**\n * Collect string literals from a conditional expression tree.\n * Used when a ReturnStatement's argument is a ternary or logical expression.\n * All findings are marked as \"possible\" since they're conditional.\n */\nfunction collectStringLiteralsFromExpression(node: AcornNode, results: ReturnInfo[]): void {\n if (!node || typeof node !== \"object\") {\n return;\n }\n if (node.type === \"Literal\" && typeof node.value === \"string\") {\n results.push({ target: node.value as string, isConditional: true });\n return;\n }\n // Recurse into conditional/logical branches\n if (node.type === \"ConditionalExpression\") {\n collectStringLiteralsFromExpression(node.consequent as AcornNode, results);\n collectStringLiteralsFromExpression(node.alternate as AcornNode, results);\n } else if (node.type === \"LogicalExpression\") {\n collectStringLiteralsFromExpression(node.left as AcornNode, results);\n collectStringLiteralsFromExpression(node.right as AcornNode, results);\n }\n // For any other expression type, don't recurse further — we can't reliably\n // determine whether the result will be a state name.\n}\n\n/**\n * Recursively walk the AST, collecting ReturnStatement nodes that\n * return string literals. Tracks whether any CONDITIONAL_ANCESTOR\n * node exists in the ancestor chain.\n *\n * We skip nested function bodies (arrow functions, regular functions,\n * class methods) — their returns belong to a different function scope.\n */\nfunction walkForReturns(node: AcornNode, ancestors: string[], results: ReturnInfo[]): void {\n if (!node || typeof node !== \"object\") {\n return;\n }\n\n if (node.type === \"ReturnStatement\") {\n const arg = node.argument;\n if (!arg) {\n return;\n }\n if (arg.type === \"Literal\" && typeof arg.value === \"string\") {\n const isConditional = ancestors.some(a => CONDITIONAL_ANCESTORS.has(a));\n results.push({ target: arg.value, isConditional });\n } else {\n // Non-literal return value (ternary, logical, expression, etc.)\n // Walk into the argument to find any string literals in conditional branches.\n // All targets found this way are \"possible\" since they're behind some expression.\n collectStringLiteralsFromExpression(arg, results);\n }\n return;\n }\n\n // Skip nested function scopes — their returns are not ours\n if (\n node.type === \"FunctionDeclaration\" ||\n node.type === \"FunctionExpression\" ||\n node.type === \"ArrowFunctionExpression\"\n ) {\n // The outermost function (index 0 in a Program body) IS our function,\n // so we need to visit it. We detect \"outermost\" by checking if ancestors\n // has any function-type ancestor already.\n const hasFunctionAncestor = ancestors.some(\n a =>\n a === \"FunctionDeclaration\" ||\n a === \"FunctionExpression\" ||\n a === \"ArrowFunctionExpression\"\n );\n if (hasFunctionAncestor) {\n // Nested function — skip its body entirely\n return;\n }\n // Fall through to recurse into the outermost function body\n }\n\n const nextAncestors = [...ancestors, node.type];\n const children = getChildren(node);\n for (const child of children) {\n if (child && typeof child === \"object\") {\n walkForReturns(child as AcornNode, nextAncestors, results);\n }\n }\n}\n\n/**\n * Extract the child nodes we want to recurse into from an AST node.\n * Returns a flat list of child nodes (some may be arrays in the AST).\n */\nfunction getChildren(node: AcornNode): AcornNode[] {\n const children: AcornNode[] = [];\n\n const pushIfNode = (val: unknown) => {\n if (val && typeof val === \"object\" && \"type\" in (val as object)) {\n children.push(val as AcornNode);\n }\n };\n\n const pushArray = (arr: unknown) => {\n if (Array.isArray(arr)) {\n for (const item of arr) {\n pushIfNode(item);\n }\n }\n };\n\n switch (node.type) {\n case \"Program\":\n pushArray(node.body);\n break;\n case \"ExpressionStatement\":\n pushIfNode(node.expression);\n break;\n case \"FunctionDeclaration\":\n case \"FunctionExpression\":\n case \"ArrowFunctionExpression\":\n if (node.body) {\n pushIfNode(node.body);\n }\n break;\n case \"BlockStatement\":\n pushArray(node.body);\n break;\n case \"ReturnStatement\":\n pushIfNode(node.argument);\n break;\n case \"IfStatement\":\n pushIfNode(node.consequent);\n pushIfNode(node.alternate);\n break;\n case \"SwitchStatement\":\n pushArray(node.cases);\n break;\n case \"SwitchCase\":\n pushArray(node.consequent);\n break;\n case \"TryStatement\":\n pushIfNode(node.block);\n pushIfNode(node.handler);\n pushIfNode(node.finalizer);\n break;\n case \"CatchClause\":\n pushIfNode(node.body as AcornNode | undefined);\n break;\n case \"ConditionalExpression\":\n pushIfNode(node.consequent as AcornNode | undefined);\n pushIfNode(node.alternate as AcornNode | undefined);\n break;\n case \"LogicalExpression\":\n pushIfNode(node.left);\n pushIfNode(node.right);\n break;\n case \"VariableDeclaration\":\n pushArray(node.declarations);\n break;\n case \"VariableDeclarator\":\n pushIfNode(node.init);\n break;\n case \"ForStatement\":\n pushIfNode(node.body as AcornNode | undefined);\n break;\n case \"WhileStatement\":\n case \"DoWhileStatement\":\n pushIfNode(node.body as AcornNode | undefined);\n break;\n default:\n // For any node type we don't explicitly handle, try common child fields.\n // This prevents us from silently missing returns in unusual constructs.\n for (const key of Object.keys(node)) {\n // ESLint AST nodes have circular `parent` pointers that Acorn nodes lack.\n // Traversing into `parent` causes infinite recursion.\n if (\n key === \"type\" ||\n key === \"start\" ||\n key === \"end\" ||\n key === \"loc\" ||\n key === \"parent\" ||\n key === \"range\"\n ) {\n continue;\n }\n const val = node[key];\n if (Array.isArray(val)) {\n pushArray(val);\n } else {\n pushIfNode(val);\n }\n }\n }\n\n return children;\n}\n","// =============================================================================\n// graph-builder.ts — Build a StateGraph IR from an FSM config or live instance\n//\n// Two input shapes exist:\n// 1. Config object (pre-instantiation) — _child values are raw FSM instances\n// 2. Live instance (post-construction) — _child values are ChildLink wrappers\n// (BehavioralFsm mutates config.states in-place during wrapChildLinks())\n//\n// The MACHINA_TYPE symbol discriminates between config and live instance inputs,\n// and between raw FSM instances and ChildLink wrappers on _child fields.\n// =============================================================================\n\nimport { MACHINA_TYPE, type ChildLink } from \"machina\";\nimport { analyzeHandler } from \"./handler-analyzer\";\nimport type { StateGraph, StateNode, TransitionEdge, InspectInput } from \"./types\";\n\n// Keys that are lifecycle/structural — not input handlers\nconst SPECIAL_KEYS = new Set([\"_onEnter\", \"_onExit\", \"_child\"]);\n\n// Shape of what we need from a live FSM instance\ninterface FsmLike {\n id: string;\n initialState: string;\n states: Record<string, Record<string, unknown>>;\n}\n\n/**\n * Normalize the input to a consistent internal shape.\n * Config objects are passed through; live instances have their\n * public fields extracted.\n */\nfunction normalizeInput(input: InspectInput): FsmLike {\n // Live instance: has MACHINA_TYPE stamped on it\n if (MACHINA_TYPE in (input as object)) {\n const instance = input as unknown as FsmLike & { [key: symbol]: string };\n return {\n id: instance.id,\n initialState: instance.initialState,\n states: instance.states,\n };\n }\n // Config object: read directly\n return input as FsmLike;\n}\n\n/**\n * Extract transition edges from a single handler value.\n * String shorthands produce one definite edge. Function handlers\n * are analyzed by the acorn-based handler-analyzer.\n */\nfunction extractEdges(\n fromState: string,\n inputName: string,\n handlerValue: unknown\n): TransitionEdge[] {\n if (typeof handlerValue === \"string\") {\n return [{ inputName, from: fromState, to: handlerValue, confidence: \"definite\" }];\n }\n\n if (typeof handlerValue === \"function\") {\n const targets = analyzeHandler(handlerValue as (...args: unknown[]) => unknown);\n return targets.map(({ target, confidence }) => ({\n inputName,\n from: fromState,\n to: target,\n confidence,\n }));\n }\n\n return [];\n}\n\n/**\n * Detect whether a _child value is a ChildLink wrapper (post-construction)\n * vs a raw FSM instance (pre-construction config path).\n *\n * ChildLink objects have a `canHandle` method and an `instance` field.\n * Raw FSM instances have MACHINA_TYPE stamped on them.\n */\nfunction isChildLink(value: unknown): value is ChildLink {\n return (\n typeof value === \"object\" && value !== null && \"canHandle\" in value && \"instance\" in value\n );\n}\n\n/**\n * Detect a plain config object that looks like an FSM config (has `initialState`\n * and `states`). This covers eval'd user input in the explorer, where _child is\n * a bare object literal rather than a createFsm() instance.\n */\nfunction isConfigShaped(value: unknown): value is FsmLike {\n return (\n typeof value === \"object\" && value !== null && \"initialState\" in value && \"states\" in value\n );\n}\n\n/**\n * Build a StateGraph from an FSM config or live instance.\n * Child FSMs declared via _child are recursively built into child graphs.\n *\n * @param input - A config object or a live Fsm/BehavioralFsm instance\n */\nexport function buildStateGraph(input: InspectInput): StateGraph {\n const fsm = normalizeInput(input);\n const nodes: Record<string, StateNode> = {};\n const children: Record<string, StateGraph> = {};\n\n for (const stateName of Object.keys(fsm.states)) {\n const stateObj = fsm.states[stateName];\n const edges: TransitionEdge[] = [];\n\n for (const key of Object.keys(stateObj)) {\n const value = stateObj[key];\n\n if (key === \"_child\") {\n // _child values come in three shapes:\n // 1. ChildLink wrapper (live instance post-construction)\n // 2. Raw FSM instance with MACHINA_TYPE (config passed a createFsm() result)\n // 3. Plain config object with initialState + states (e.g. from eval'd user input)\n if (isChildLink(value)) {\n children[stateName] = buildStateGraph(\n value.instance as unknown as InspectInput\n );\n } else if (typeof value === \"object\" && value !== null && MACHINA_TYPE in value) {\n children[stateName] = buildStateGraph(value as unknown as InspectInput);\n } else if (isConfigShaped(value)) {\n children[stateName] = buildStateGraph(value as unknown as InspectInput);\n }\n continue;\n }\n\n if (SPECIAL_KEYS.has(key)) {\n // _onEnter / _onExit: analyze function handlers for bounce edges,\n // but label them with source \"_onEnter\" or \"_onExit\" so the\n // loop detector can find them.\n if (key === \"_onEnter\" && typeof value === \"function\") {\n const targets = analyzeHandler(value as (...args: unknown[]) => unknown);\n for (const { target, confidence } of targets) {\n edges.push({\n inputName: \"_onEnter\",\n from: stateName,\n to: target,\n confidence,\n });\n }\n }\n continue;\n }\n\n // Regular input handlers (including catch-all \"*\")\n for (const edge of extractEdges(stateName, key, value)) {\n edges.push(edge);\n }\n }\n\n nodes[stateName] = { name: stateName, edges };\n }\n\n const graph: StateGraph = {\n fsmId: fsm.id,\n initialState: fsm.initialState,\n nodes,\n children,\n };\n\n return graph;\n}\n","// =============================================================================\n// unreachable.ts — Detect states with no inbound path from initialState\n//\n// BFS from initialState, following ALL edges (definite and possible).\n// Any node not reached is unreachable. Child graphs are analyzed independently\n// from their own initialState.\n// =============================================================================\n\nimport type { StateGraph, Finding } from \"../types\";\n\n/**\n * Check for unreachable states in the graph.\n * BFS from initialState — any unvisited node is a finding.\n * Recurses into child graphs independently.\n */\nexport function checkUnreachable(graph: StateGraph, parentState?: string): Finding[] {\n const findings: Finding[] = [];\n\n findings.push(...findUnreachableInGraph(graph, parentState));\n\n for (const [childParentState, childGraph] of Object.entries(graph.children)) {\n findings.push(...checkUnreachable(childGraph, childParentState));\n }\n\n return findings;\n}\n\nfunction findUnreachableInGraph(graph: StateGraph, parentState?: string): Finding[] {\n const { fsmId, initialState, nodes } = graph;\n const visited = new Set<string>();\n const queue: string[] = [];\n\n if (initialState in nodes) {\n queue.push(initialState);\n visited.add(initialState);\n }\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n const node = nodes[current];\n if (!node) {\n continue;\n }\n for (const edge of node.edges) {\n if (!visited.has(edge.to) && edge.to in nodes) {\n visited.add(edge.to);\n queue.push(edge.to);\n }\n }\n }\n\n const findings: Finding[] = [];\n for (const stateName of Object.keys(nodes)) {\n if (!visited.has(stateName)) {\n findings.push({\n type: \"unreachable-state\",\n message: `State \"${stateName}\" is unreachable from initialState \"${initialState}\" in FSM \"${fsmId}\".`,\n fsmId,\n states: [stateName],\n parentState,\n });\n }\n }\n\n return findings;\n}\n","// =============================================================================\n// onenter-loop.ts — Detect unconditional _onEnter transition cycles\n//\n// Only reports cycles where ALL edges are \"definite\" (unconditional).\n// Conditional _onEnter bounces are an intentional pattern — not bugs.\n// Self-loops are excluded: the runtime silently ignores same-state transitions.\n// =============================================================================\n\nimport type { StateGraph, Finding } from \"../types\";\n\n/**\n * Check for _onEnter transition loops in the graph.\n * Only reports cycles where every edge is \"definite\".\n * Recurses into child graphs.\n */\nexport function checkOnEnterLoops(graph: StateGraph, parentState?: string): Finding[] {\n const findings: Finding[] = [];\n\n findings.push(...findOnEnterLoopsInGraph(graph, parentState));\n\n for (const [childParentState, childGraph] of Object.entries(graph.children)) {\n findings.push(...checkOnEnterLoops(childGraph, childParentState));\n }\n\n return findings;\n}\n\nfunction findOnEnterLoopsInGraph(graph: StateGraph, parentState?: string): Finding[] {\n const { fsmId, nodes } = graph;\n\n // Build the _onEnter-only subgraph: edges where inputName === \"_onEnter\"\n // and confidence === \"definite\", excluding self-loops (same-state transitions\n // are no-ops in the runtime — they hit the same-state guard and return early).\n const onEnterEdges: Record<string, string[]> = {};\n\n for (const [stateName, node] of Object.entries(nodes)) {\n const definiteOnEnterTargets = node.edges\n .filter(\n e => e.inputName === \"_onEnter\" && e.confidence === \"definite\" && e.to !== stateName // exclude self-loops\n )\n .map(e => e.to);\n\n if (definiteOnEnterTargets.length > 0) {\n onEnterEdges[stateName] = definiteOnEnterTargets;\n }\n }\n\n // DFS cycle detection on the _onEnter subgraph.\n // We use the \"white-gray-black\" algorithm: unvisited, in-stack, done.\n const WHITE = 0,\n GRAY = 1,\n BLACK = 2;\n const color: Record<string, number> = {};\n\n for (const stateName of Object.keys(nodes)) {\n color[stateName] = WHITE;\n }\n\n const findings: Finding[] = [];\n const reportedCycles = new Set<string>();\n\n const dfs = (node: string, stack: string[]): void => {\n color[node] = GRAY;\n stack.push(node);\n\n for (const neighbor of onEnterEdges[node] ?? []) {\n if (color[neighbor] === GRAY) {\n // Found a back edge — extract the cycle from the stack\n const cycleStart = stack.indexOf(neighbor);\n const cycle = stack.slice(cycleStart);\n\n // Deduplicate: sort cycle members to produce a canonical key\n const cycleKey = [...cycle].sort().join(\",\");\n if (!reportedCycles.has(cycleKey)) {\n reportedCycles.add(cycleKey);\n findings.push({\n type: \"onenter-loop\",\n message:\n `Unconditional _onEnter loop detected in FSM \"${fsmId}\": ` +\n cycle.join(\" → \") +\n ` → ${neighbor}`,\n fsmId,\n states: cycle,\n parentState,\n });\n }\n } else if (color[neighbor] === WHITE) {\n dfs(neighbor, stack);\n }\n }\n\n stack.pop();\n color[node] = BLACK;\n };\n\n for (const stateName of Object.keys(nodes)) {\n if (color[stateName] === WHITE) {\n dfs(stateName, []);\n }\n }\n\n return findings;\n}\n","// =============================================================================\n// missing-handler.ts — Detect states missing handlers for known inputs\n//\n// Collects the union of all input names across all edges in a graph\n// (excluding _onEnter, _onExit, and *), then flags any state that handles\n// fewer inputs than the global union — unless that state has a * catch-all,\n// which implicitly handles every input.\n//\n// Limitation: Only inputs visible as graph edges are included. Function\n// handlers that produce no statically-extractable return target are invisible\n// to the graph, so the input union may be incomplete. This check is\n// best-effort and operates on what the graph knows.\n//\n// Does NOT recurse into child graphs automatically — child graphs have their\n// own independent input sets. The top-level checkMissingHandlers recurses\n// into children the same way all other checks do.\n// =============================================================================\n\nimport type { StateGraph, Finding } from \"../types\";\n\n// These are structural keys in the state object, not real inputs.\n// Including them in the \"all inputs\" union would cause false positives\n// on every FSM.\nconst EXCLUDED_INPUTS = new Set([\"_onEnter\", \"_onExit\", \"*\"]);\n\n/**\n * Check for states with missing handlers relative to the FSM's full input set.\n * States with a `*` catch-all edge are skipped entirely.\n * Recurses into child graphs independently.\n */\nexport function checkMissingHandlers(graph: StateGraph, parentState?: string): Finding[] {\n const findings: Finding[] = [];\n\n findings.push(...findMissingHandlersInGraph(graph, parentState));\n\n for (const [childParentState, childGraph] of Object.entries(graph.children)) {\n findings.push(...checkMissingHandlers(childGraph, childParentState));\n }\n\n return findings;\n}\n\nfunction findMissingHandlersInGraph(graph: StateGraph, parentState?: string): Finding[] {\n const { fsmId, nodes } = graph;\n\n // Collect the global input union: every inputName across all edges in\n // all states, minus the structural keys we never want to check.\n const allInputs = new Set<string>();\n for (const node of Object.values(nodes)) {\n for (const edge of node.edges) {\n if (!EXCLUDED_INPUTS.has(edge.inputName)) {\n allInputs.add(edge.inputName);\n }\n }\n }\n\n // Nothing to compare if the FSM has no named inputs at all (e.g., single\n // state with only _onEnter, or an empty FSM).\n if (allInputs.size === 0) {\n return [];\n }\n\n const findings: Finding[] = [];\n\n for (const [stateName, node] of Object.entries(nodes)) {\n // States with a * catch-all handle every input by definition.\n // Reporting missing handlers here would always be a false positive.\n const hasCatchAll = node.edges.some(e => e.inputName === \"*\");\n if (hasCatchAll) {\n continue;\n }\n\n // Collect the inputs this state explicitly handles (via edges).\n const stateInputs = new Set<string>();\n for (const edge of node.edges) {\n if (!EXCLUDED_INPUTS.has(edge.inputName)) {\n stateInputs.add(edge.inputName);\n }\n }\n\n // Diff: inputs the FSM knows about that this state doesn't handle.\n const missingInputs = [...allInputs].filter(input => !stateInputs.has(input));\n\n if (missingInputs.length > 0) {\n findings.push({\n type: \"missing-handler\",\n message:\n `State \"${stateName}\" in FSM \"${fsmId}\" is missing handlers for: ` +\n missingInputs.map(i => `\"${i}\"`).join(\", \") +\n \".\",\n fsmId,\n states: [stateName],\n inputs: missingInputs,\n parentState,\n });\n }\n }\n\n return findings;\n}\n","// =============================================================================\n// inspect.ts — Public API entry points\n//\n// inspect() is the convenience function: build graph + run all checks.\n// inspectGraph() is for callers that already have a graph (e.g., tools\n// that build the graph once and run multiple check passes).\n// =============================================================================\n\nimport { buildStateGraph } from \"./graph-builder\";\nimport { checkUnreachable } from \"./checks/unreachable\";\nimport { checkOnEnterLoops } from \"./checks/onenter-loop\";\nimport { checkMissingHandlers } from \"./checks/missing-handler\";\nimport type { StateGraph, Finding, InspectInput } from \"./types\";\n\n/**\n * Build a state graph from the input and run all structural checks.\n * Returns an array of findings — empty means no issues detected.\n *\n * @param input - A config object or a live Fsm/BehavioralFsm instance\n */\nexport function inspect(input: InspectInput): Finding[] {\n const graph = buildStateGraph(input);\n return inspectGraph(graph);\n}\n\n/**\n * Run all structural checks against a pre-built state graph.\n * Useful when you've already called buildStateGraph() for another purpose\n * (e.g., diagram export) and want to avoid building it twice.\n *\n * @param graph - A StateGraph produced by buildStateGraph()\n */\nexport function inspectGraph(graph: StateGraph): Finding[] {\n return [\n ...checkUnreachable(graph),\n ...checkOnEnterLoops(graph),\n ...checkMissingHandlers(graph),\n ];\n}\n"],"mappings":"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"}
@@ -0,0 +1,200 @@
1
+ import { MachinaInstance } from "machina";
2
+
3
+ //#region src/types.d.ts
4
+ /**
5
+ * Accepted inputs for buildStateGraph() and inspect().
6
+ * Either a raw config object or a live Fsm/BehavioralFsm instance.
7
+ */
8
+ type InspectInput = {
9
+ id: string;
10
+ initialState: string;
11
+ states: Record<string, Record<string, unknown>>;
12
+ } | MachinaInstance;
13
+ /**
14
+ * A directed transition edge in the state graph.
15
+ * Represents a possible path from one state to another.
16
+ */
17
+ interface TransitionEdge {
18
+ /** The input name that triggers this transition (or "_onEnter" for bounce transitions) */
19
+ inputName: string;
20
+ /** The source state */
21
+ from: string;
22
+ /** The target state */
23
+ to: string;
24
+ /**
25
+ * Confidence level for this edge:
26
+ * - "definite": unconditional transition (string shorthand, or sole top-level return)
27
+ * - "possible": conditional transition (inside if/switch/ternary/logical)
28
+ */
29
+ confidence: "definite" | "possible";
30
+ }
31
+ /**
32
+ * A node in the state graph — one per state.
33
+ */
34
+ interface StateNode {
35
+ /** The state name */
36
+ name: string;
37
+ /** All outbound transition edges from this state */
38
+ edges: TransitionEdge[];
39
+ }
40
+ /**
41
+ * The state graph intermediate representation.
42
+ * Built by buildStateGraph() from a config or live FSM instance.
43
+ */
44
+ interface StateGraph {
45
+ /** The FSM's unique identifier */
46
+ fsmId: string;
47
+ /** The initial state name (BFS root for reachability analysis) */
48
+ initialState: string;
49
+ /** All nodes in the graph, keyed by state name */
50
+ nodes: Record<string, StateNode>;
51
+ /**
52
+ * Child graphs, keyed by the parent state name.
53
+ * Built recursively when _child FSMs are present.
54
+ */
55
+ children: Record<string, StateGraph>;
56
+ }
57
+ /**
58
+ * Shared fields for all finding types.
59
+ */
60
+ interface BaseFinding {
61
+ /** Human-readable description of the issue */
62
+ message: string;
63
+ /** The FSM id where the issue was found */
64
+ fsmId: string;
65
+ /**
66
+ * The relevant state name(s). For unreachable-state: the unreachable
67
+ * state. For onenter-loop: all states in the cycle.
68
+ * For missing-handler: the state that is missing handlers.
69
+ */
70
+ states: string[];
71
+ /**
72
+ * For child FSM findings: the parent state that owns this child FSM.
73
+ * Undefined for top-level FSM findings.
74
+ */
75
+ parentState?: string;
76
+ }
77
+ /**
78
+ * A state that has no inbound path from the initial state.
79
+ */
80
+ interface UnreachableFinding extends BaseFinding {
81
+ type: "unreachable-state";
82
+ }
83
+ /**
84
+ * An unconditional _onEnter transition cycle that will infinite-loop the runtime.
85
+ */
86
+ interface OnEnterLoopFinding extends BaseFinding {
87
+ type: "onenter-loop";
88
+ }
89
+ /**
90
+ * A state that is missing handlers for inputs that other states in the FSM handle.
91
+ * The `inputs` field lists the input names that this state does not handle.
92
+ * States with a `*` catch-all handler are excluded from this check.
93
+ *
94
+ * Limitation: only inputs visible as graph edges are included in the comparison.
95
+ * Function handlers with no statically-extractable return are invisible to this check.
96
+ */
97
+ interface MissingHandlerFinding extends BaseFinding {
98
+ type: "missing-handler";
99
+ /** The input names that this state is missing handlers for */
100
+ inputs: string[];
101
+ }
102
+ /**
103
+ * A structural issue found by one of the analysis checks.
104
+ * Discriminated by the `type` field — narrow on `finding.type` to access
105
+ * type-specific fields like `MissingHandlerFinding.inputs`.
106
+ */
107
+ type Finding = UnreachableFinding | OnEnterLoopFinding | MissingHandlerFinding;
108
+ //#endregion
109
+ //#region src/graph-builder.d.ts
110
+ /**
111
+ * Build a StateGraph from an FSM config or live instance.
112
+ * Child FSMs declared via _child are recursively built into child graphs.
113
+ *
114
+ * @param input - A config object or a live Fsm/BehavioralFsm instance
115
+ */
116
+ declare function buildStateGraph(input: InspectInput): StateGraph;
117
+ //#endregion
118
+ //#region src/inspect.d.ts
119
+ /**
120
+ * Build a state graph from the input and run all structural checks.
121
+ * Returns an array of findings — empty means no issues detected.
122
+ *
123
+ * @param input - A config object or a live Fsm/BehavioralFsm instance
124
+ */
125
+ declare function inspect(input: InspectInput): Finding[];
126
+ /**
127
+ * Run all structural checks against a pre-built state graph.
128
+ * Useful when you've already called buildStateGraph() for another purpose
129
+ * (e.g., diagram export) and want to avoid building it twice.
130
+ *
131
+ * @param graph - A StateGraph produced by buildStateGraph()
132
+ */
133
+ declare function inspectGraph(graph: StateGraph): Finding[];
134
+ //#endregion
135
+ //#region src/handler-analyzer.d.ts
136
+ interface HandlerTarget {
137
+ target: string;
138
+ confidence: "definite" | "possible";
139
+ }
140
+ type AcornNode = {
141
+ type: string;
142
+ body?: AcornNode | AcornNode[];
143
+ argument?: AcornNode | null;
144
+ value?: unknown;
145
+ consequent?: AcornNode | AcornNode[];
146
+ alternate?: AcornNode | null;
147
+ cases?: AcornNode[];
148
+ block?: AcornNode;
149
+ handler?: AcornNode | null;
150
+ finalizer?: AcornNode | null;
151
+ left?: AcornNode;
152
+ right?: AcornNode;
153
+ expression?: AcornNode | boolean;
154
+ declarations?: AcornNode[];
155
+ init?: AcornNode | null;
156
+ test?: AcornNode | null;
157
+ update?: AcornNode | null;
158
+ params?: AcornNode[];
159
+ [key: string]: unknown;
160
+ };
161
+ /**
162
+ * A union of ESTree node types that represent a callable handler function.
163
+ * Structurally compatible with both acorn nodes and ESLint's @types/estree,
164
+ * so walkHandlerAst can accept either without type juggling at the call site.
165
+ *
166
+ * The index signature `[key: string]: unknown` keeps this loose enough that
167
+ * acorn's extra fields (start, end, loc) don't cause structural mismatches.
168
+ */
169
+ type AstFunctionNode = {
170
+ type: "FunctionDeclaration" | "FunctionExpression" | "ArrowFunctionExpression";
171
+ body: AcornNode;
172
+ expression?: boolean;
173
+ params: AcornNode[];
174
+ [key: string]: unknown;
175
+ };
176
+ /**
177
+ * Walk a pre-parsed handler function AST node and extract transition targets.
178
+ *
179
+ * Accepts a FunctionDeclaration, FunctionExpression, or ArrowFunctionExpression
180
+ * node (as produced by acorn or the ESLint rule engine). Returns an array of
181
+ * { target, confidence } pairs. Empty array means no statically-determinable
182
+ * string return targets were found.
183
+ *
184
+ * This is the shared core used by both:
185
+ * - analyzeHandler() — the runtime path (parses fn.toString() first)
186
+ * - ESLint rules — the AST path (ESLint already has the parsed node)
187
+ */
188
+ declare function walkHandlerAst(node: AstFunctionNode): HandlerTarget[];
189
+ /**
190
+ * Analyze a handler function and extract transition targets.
191
+ *
192
+ * Returns an array of { target, confidence } pairs. Empty array means
193
+ * no statically-determinable string return targets were found.
194
+ *
195
+ * Safe to call on any function — parse errors return empty (fail silently).
196
+ */
197
+ declare function analyzeHandler(fn: (...args: unknown[]) => unknown): HandlerTarget[];
198
+ //#endregion
199
+ export { type AstFunctionNode, type BaseFinding, type Finding, type HandlerTarget, type InspectInput, type MissingHandlerFinding, type OnEnterLoopFinding, type StateGraph, type StateNode, type TransitionEdge, type UnreachableFinding, analyzeHandler, buildStateGraph, inspect, inspectGraph, walkHandlerAst };
200
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/graph-builder.ts","../src/inspect.ts","../src/handler-analyzer.ts"],"mappings":";;;;;AAkBA;;KAAY,YAAA;EAEF,EAAA;EACA,YAAA;EACA,MAAA,EAAQ,MAAA,SAAe,MAAA;AAAA,IAE3B,eAAA;;;;;UAUW,cAAA;EAZgB;EAc7B,SAAA;EAZiB;EAcjB,IAAA;EAJa;EAMb,EAAA;;;;;;EAMA,UAAA;AAAA;;AAMJ;;UAAiB,SAAA;EAIQ;EAFrB,IAAA;EAEA;EAAA,KAAA,EAAO,cAAA;AAAA;;AAOX;;;UAAiB,UAAA;EAMN;EAJP,KAAA;EASU;EAPV,YAAA;EAOgB;EALhB,KAAA,EAAO,MAAA,SAAe,SAAA;EAFtB;;;;EAOA,QAAA,EAAU,MAAA,SAAe,UAAA;AAAA;;;;UAUZ,WAAA;EAAW;EAExB,OAAA;EAFwB;EAIxB,KAAA;EAAA;;;;;EAMA,MAAA;EAWgC;;;;EANhC,WAAA;AAAA;;;;UAMa,kBAAA,SAA2B,WAAA;EACxC,IAAA;AAAA;;;;UAMa,kBAAA,SAA2B,WAAA;EACxC,IAAA;AAAA;AAsBJ;;;;;;;;AAAA,UAXiB,qBAAA,SAA8B,WAAA;EAC3C,IAAA;EAU4D;EAR5D,MAAA;AAAA;;;;ACzBJ;;KDiCY,OAAA,GAAU,kBAAA,GAAqB,kBAAA,GAAqB,qBAAA;;;;;AArHhE;;;;iBCoFgB,eAAA,CAAgB,KAAA,EAAO,YAAA,GAAe,UAAA;;;;;ADpFtD;;;;iBEEgB,OAAA,CAAQ,KAAA,EAAO,YAAA,GAAe,OAAA;;;;;;;;iBAY9B,YAAA,CAAa,KAAA,EAAO,UAAA,GAAa,OAAA;;;UClBhC,aAAA;EACb,MAAA;EACA,UAAA;AAAA;AAAA,KAMC,SAAA;EACD,IAAA;EACA,IAAA,GAAO,SAAA,GAAY,SAAA;EACnB,QAAA,GAAW,SAAA;EACX,KAAA;EACA,UAAA,GAAa,SAAA,GAAY,SAAA;EACzB,SAAA,GAAY,SAAA;EACZ,KAAA,GAAQ,SAAA;EACR,KAAA,GAAQ,SAAA;EACR,OAAA,GAAU,SAAA;EACV,SAAA,GAAY,SAAA;EACZ,IAAA,GAAO,SAAA;EACP,KAAA,GAAQ,SAAA;EACR,UAAA,GAAa,SAAA;EACb,YAAA,GAAe,SAAA;EACf,IAAA,GAAO,SAAA;EACP,IAAA,GAAO,SAAA;EACP,MAAA,GAAS,SAAA;EACT,MAAA,GAAS,SAAA;EAAA,CACR,GAAA;AAAA;;;;;AHWL;;;;KGAY,eAAA;EACR,IAAA;EACA,IAAA,EAAM,SAAA;EACN,UAAA;EACA,MAAA,EAAQ,SAAA;EAAA,CACP,GAAA;AAAA;;;;;;;;;;;;;iBAoIW,cAAA,CAAe,IAAA,EAAM,eAAA,GAAkB,aAAA;;;;;AHzGvD;;;;iBG4IgB,cAAA,CAAe,EAAA,MAAQ,IAAA,0BAA8B,aAAA"}
@@ -0,0 +1,200 @@
1
+ import { MachinaInstance } from "machina";
2
+
3
+ //#region src/types.d.ts
4
+ /**
5
+ * Accepted inputs for buildStateGraph() and inspect().
6
+ * Either a raw config object or a live Fsm/BehavioralFsm instance.
7
+ */
8
+ type InspectInput = {
9
+ id: string;
10
+ initialState: string;
11
+ states: Record<string, Record<string, unknown>>;
12
+ } | MachinaInstance;
13
+ /**
14
+ * A directed transition edge in the state graph.
15
+ * Represents a possible path from one state to another.
16
+ */
17
+ interface TransitionEdge {
18
+ /** The input name that triggers this transition (or "_onEnter" for bounce transitions) */
19
+ inputName: string;
20
+ /** The source state */
21
+ from: string;
22
+ /** The target state */
23
+ to: string;
24
+ /**
25
+ * Confidence level for this edge:
26
+ * - "definite": unconditional transition (string shorthand, or sole top-level return)
27
+ * - "possible": conditional transition (inside if/switch/ternary/logical)
28
+ */
29
+ confidence: "definite" | "possible";
30
+ }
31
+ /**
32
+ * A node in the state graph — one per state.
33
+ */
34
+ interface StateNode {
35
+ /** The state name */
36
+ name: string;
37
+ /** All outbound transition edges from this state */
38
+ edges: TransitionEdge[];
39
+ }
40
+ /**
41
+ * The state graph intermediate representation.
42
+ * Built by buildStateGraph() from a config or live FSM instance.
43
+ */
44
+ interface StateGraph {
45
+ /** The FSM's unique identifier */
46
+ fsmId: string;
47
+ /** The initial state name (BFS root for reachability analysis) */
48
+ initialState: string;
49
+ /** All nodes in the graph, keyed by state name */
50
+ nodes: Record<string, StateNode>;
51
+ /**
52
+ * Child graphs, keyed by the parent state name.
53
+ * Built recursively when _child FSMs are present.
54
+ */
55
+ children: Record<string, StateGraph>;
56
+ }
57
+ /**
58
+ * Shared fields for all finding types.
59
+ */
60
+ interface BaseFinding {
61
+ /** Human-readable description of the issue */
62
+ message: string;
63
+ /** The FSM id where the issue was found */
64
+ fsmId: string;
65
+ /**
66
+ * The relevant state name(s). For unreachable-state: the unreachable
67
+ * state. For onenter-loop: all states in the cycle.
68
+ * For missing-handler: the state that is missing handlers.
69
+ */
70
+ states: string[];
71
+ /**
72
+ * For child FSM findings: the parent state that owns this child FSM.
73
+ * Undefined for top-level FSM findings.
74
+ */
75
+ parentState?: string;
76
+ }
77
+ /**
78
+ * A state that has no inbound path from the initial state.
79
+ */
80
+ interface UnreachableFinding extends BaseFinding {
81
+ type: "unreachable-state";
82
+ }
83
+ /**
84
+ * An unconditional _onEnter transition cycle that will infinite-loop the runtime.
85
+ */
86
+ interface OnEnterLoopFinding extends BaseFinding {
87
+ type: "onenter-loop";
88
+ }
89
+ /**
90
+ * A state that is missing handlers for inputs that other states in the FSM handle.
91
+ * The `inputs` field lists the input names that this state does not handle.
92
+ * States with a `*` catch-all handler are excluded from this check.
93
+ *
94
+ * Limitation: only inputs visible as graph edges are included in the comparison.
95
+ * Function handlers with no statically-extractable return are invisible to this check.
96
+ */
97
+ interface MissingHandlerFinding extends BaseFinding {
98
+ type: "missing-handler";
99
+ /** The input names that this state is missing handlers for */
100
+ inputs: string[];
101
+ }
102
+ /**
103
+ * A structural issue found by one of the analysis checks.
104
+ * Discriminated by the `type` field — narrow on `finding.type` to access
105
+ * type-specific fields like `MissingHandlerFinding.inputs`.
106
+ */
107
+ type Finding = UnreachableFinding | OnEnterLoopFinding | MissingHandlerFinding;
108
+ //#endregion
109
+ //#region src/graph-builder.d.ts
110
+ /**
111
+ * Build a StateGraph from an FSM config or live instance.
112
+ * Child FSMs declared via _child are recursively built into child graphs.
113
+ *
114
+ * @param input - A config object or a live Fsm/BehavioralFsm instance
115
+ */
116
+ declare function buildStateGraph(input: InspectInput): StateGraph;
117
+ //#endregion
118
+ //#region src/inspect.d.ts
119
+ /**
120
+ * Build a state graph from the input and run all structural checks.
121
+ * Returns an array of findings — empty means no issues detected.
122
+ *
123
+ * @param input - A config object or a live Fsm/BehavioralFsm instance
124
+ */
125
+ declare function inspect(input: InspectInput): Finding[];
126
+ /**
127
+ * Run all structural checks against a pre-built state graph.
128
+ * Useful when you've already called buildStateGraph() for another purpose
129
+ * (e.g., diagram export) and want to avoid building it twice.
130
+ *
131
+ * @param graph - A StateGraph produced by buildStateGraph()
132
+ */
133
+ declare function inspectGraph(graph: StateGraph): Finding[];
134
+ //#endregion
135
+ //#region src/handler-analyzer.d.ts
136
+ interface HandlerTarget {
137
+ target: string;
138
+ confidence: "definite" | "possible";
139
+ }
140
+ type AcornNode = {
141
+ type: string;
142
+ body?: AcornNode | AcornNode[];
143
+ argument?: AcornNode | null;
144
+ value?: unknown;
145
+ consequent?: AcornNode | AcornNode[];
146
+ alternate?: AcornNode | null;
147
+ cases?: AcornNode[];
148
+ block?: AcornNode;
149
+ handler?: AcornNode | null;
150
+ finalizer?: AcornNode | null;
151
+ left?: AcornNode;
152
+ right?: AcornNode;
153
+ expression?: AcornNode | boolean;
154
+ declarations?: AcornNode[];
155
+ init?: AcornNode | null;
156
+ test?: AcornNode | null;
157
+ update?: AcornNode | null;
158
+ params?: AcornNode[];
159
+ [key: string]: unknown;
160
+ };
161
+ /**
162
+ * A union of ESTree node types that represent a callable handler function.
163
+ * Structurally compatible with both acorn nodes and ESLint's @types/estree,
164
+ * so walkHandlerAst can accept either without type juggling at the call site.
165
+ *
166
+ * The index signature `[key: string]: unknown` keeps this loose enough that
167
+ * acorn's extra fields (start, end, loc) don't cause structural mismatches.
168
+ */
169
+ type AstFunctionNode = {
170
+ type: "FunctionDeclaration" | "FunctionExpression" | "ArrowFunctionExpression";
171
+ body: AcornNode;
172
+ expression?: boolean;
173
+ params: AcornNode[];
174
+ [key: string]: unknown;
175
+ };
176
+ /**
177
+ * Walk a pre-parsed handler function AST node and extract transition targets.
178
+ *
179
+ * Accepts a FunctionDeclaration, FunctionExpression, or ArrowFunctionExpression
180
+ * node (as produced by acorn or the ESLint rule engine). Returns an array of
181
+ * { target, confidence } pairs. Empty array means no statically-determinable
182
+ * string return targets were found.
183
+ *
184
+ * This is the shared core used by both:
185
+ * - analyzeHandler() — the runtime path (parses fn.toString() first)
186
+ * - ESLint rules — the AST path (ESLint already has the parsed node)
187
+ */
188
+ declare function walkHandlerAst(node: AstFunctionNode): HandlerTarget[];
189
+ /**
190
+ * Analyze a handler function and extract transition targets.
191
+ *
192
+ * Returns an array of { target, confidence } pairs. Empty array means
193
+ * no statically-determinable string return targets were found.
194
+ *
195
+ * Safe to call on any function — parse errors return empty (fail silently).
196
+ */
197
+ declare function analyzeHandler(fn: (...args: unknown[]) => unknown): HandlerTarget[];
198
+ //#endregion
199
+ export { type AstFunctionNode, type BaseFinding, type Finding, type HandlerTarget, type InspectInput, type MissingHandlerFinding, type OnEnterLoopFinding, type StateGraph, type StateNode, type TransitionEdge, type UnreachableFinding, analyzeHandler, buildStateGraph, inspect, inspectGraph, walkHandlerAst };
200
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/graph-builder.ts","../src/inspect.ts","../src/handler-analyzer.ts"],"mappings":";;;;;AAkBA;;KAAY,YAAA;EAEF,EAAA;EACA,YAAA;EACA,MAAA,EAAQ,MAAA,SAAe,MAAA;AAAA,IAE3B,eAAA;;;;;UAUW,cAAA;EAZgB;EAc7B,SAAA;EAZiB;EAcjB,IAAA;EAJa;EAMb,EAAA;;;;;;EAMA,UAAA;AAAA;;AAMJ;;UAAiB,SAAA;EAIQ;EAFrB,IAAA;EAEA;EAAA,KAAA,EAAO,cAAA;AAAA;;AAOX;;;UAAiB,UAAA;EAMN;EAJP,KAAA;EASU;EAPV,YAAA;EAOgB;EALhB,KAAA,EAAO,MAAA,SAAe,SAAA;EAFtB;;;;EAOA,QAAA,EAAU,MAAA,SAAe,UAAA;AAAA;;;;UAUZ,WAAA;EAAW;EAExB,OAAA;EAFwB;EAIxB,KAAA;EAAA;;;;;EAMA,MAAA;EAWgC;;;;EANhC,WAAA;AAAA;;;;UAMa,kBAAA,SAA2B,WAAA;EACxC,IAAA;AAAA;;;;UAMa,kBAAA,SAA2B,WAAA;EACxC,IAAA;AAAA;AAsBJ;;;;;;;;AAAA,UAXiB,qBAAA,SAA8B,WAAA;EAC3C,IAAA;EAU4D;EAR5D,MAAA;AAAA;;;;ACzBJ;;KDiCY,OAAA,GAAU,kBAAA,GAAqB,kBAAA,GAAqB,qBAAA;;;;;AArHhE;;;;iBCoFgB,eAAA,CAAgB,KAAA,EAAO,YAAA,GAAe,UAAA;;;;;ADpFtD;;;;iBEEgB,OAAA,CAAQ,KAAA,EAAO,YAAA,GAAe,OAAA;;;;;;;;iBAY9B,YAAA,CAAa,KAAA,EAAO,UAAA,GAAa,OAAA;;;UClBhC,aAAA;EACb,MAAA;EACA,UAAA;AAAA;AAAA,KAMC,SAAA;EACD,IAAA;EACA,IAAA,GAAO,SAAA,GAAY,SAAA;EACnB,QAAA,GAAW,SAAA;EACX,KAAA;EACA,UAAA,GAAa,SAAA,GAAY,SAAA;EACzB,SAAA,GAAY,SAAA;EACZ,KAAA,GAAQ,SAAA;EACR,KAAA,GAAQ,SAAA;EACR,OAAA,GAAU,SAAA;EACV,SAAA,GAAY,SAAA;EACZ,IAAA,GAAO,SAAA;EACP,KAAA,GAAQ,SAAA;EACR,UAAA,GAAa,SAAA;EACb,YAAA,GAAe,SAAA;EACf,IAAA,GAAO,SAAA;EACP,IAAA,GAAO,SAAA;EACP,MAAA,GAAS,SAAA;EACT,MAAA,GAAS,SAAA;EAAA,CACR,GAAA;AAAA;;;;;AHWL;;;;KGAY,eAAA;EACR,IAAA;EACA,IAAA,EAAM,SAAA;EACN,UAAA;EACA,MAAA,EAAQ,SAAA;EAAA,CACP,GAAA;AAAA;;;;;;;;;;;;;iBAoIW,cAAA,CAAe,IAAA,EAAM,eAAA,GAAkB,aAAA;;;;;AHzGvD;;;;iBG4IgB,cAAA,CAAe,EAAA,MAAQ,IAAA,0BAA8B,aAAA"}