import type { FlowDefinition } from "../types"; /** * 流程图静态校验(任务:审查报告 P0-03)。 * 在执行前拦截结构性错误,避免运行期以诡异方式失败。 * 只做纯结构检查,不读目录/注册表(组件是否存在由运行时校验并给出友好错误)。 */ export interface FlowIssue { severity: "error" | "warning"; code: string; nodeId?: string; edgeId?: string; message: string; } const CONTROL_FLOW_COMPONENTS = new Set([ "logic_control_if_else", "logic_control_count_for", "logic_control_array_for_v3", "logic_control_dictionary_for_v1", "logic_control_while", "infinite_loop", "logic_control_try_catch" ]); export function validateFlowGraph(flow: FlowDefinition): FlowIssue[] { const issues: FlowIssue[] = []; const nodeIds = new Set(); for (const node of flow.nodes) { if (nodeIds.has(node.id)) { issues.push({ severity: "error", code: "duplicate_node_id", nodeId: node.id, message: `节点 ID 重复:${node.id}` }); } nodeIds.add(node.id); if (node.parentId && !flow.nodes.some((candidate) => candidate.id === node.parentId)) { issues.push({ severity: "error", code: "orphan_child", nodeId: node.id, message: `节点「${node.label || node.id}」挂在不存在的父节点 ${node.parentId} 下` }); } } for (const edge of flow.edges) { if (!nodeIds.has(edge.source)) { issues.push({ severity: "error", code: "dangling_edge", edgeId: edge.id, message: `连线 ${edge.id} 的起点 ${edge.source} 不存在于画布` }); } if (!nodeIds.has(edge.target)) { issues.push({ severity: "error", code: "dangling_edge", edgeId: edge.id, message: `连线 ${edge.id} 的终点 ${edge.target} 不存在于画布` }); } if (edge.source === edge.target && nodeIds.has(edge.source)) { issues.push({ severity: "error", code: "self_loop", edgeId: edge.id, nodeId: edge.source, message: `节点「${edge.source}」连接到自己,执行时会立即跳过` }); } } // 顶层图环检测:执行引擎从起点沿边游走且不重访,环意味着存在永远不会执行的分支或意图不明的回边 const top = flow.nodes.filter((node) => !node.parentId); const topIds = new Set(top.map((node) => node.id)); const edges = flow.edges.filter((edge) => topIds.has(edge.source) && topIds.has(edge.target)); const adjacency = new Map(); for (const edge of edges) { if (edge.source === edge.target) continue; adjacency.set(edge.source, [...(adjacency.get(edge.source) || []), edge.target]); } const incoming = new Set(edges.map((edge) => edge.target)); const start = top.find((node) => !incoming.has(node.id) && node.componentId.startsWith("Trigger.")) || top.find((node) => !incoming.has(node.id)) || top[0]; if (start) { const state = new Map(); const reachable = new Set(); const visit = (nodeId: string, path: string[]): void => { if (state.get(nodeId) === "done") return; if (state.get(nodeId) === "visiting") { const cycleStart = path.indexOf(nodeId); const cycle = [...path.slice(cycleStart), nodeId].join(" → "); issues.push({ severity: "error", code: "cycle", nodeId, message: `顶层流程存在环,环上的节点执行顺序不明确:${cycle}` }); return; } state.set(nodeId, "visiting"); reachable.add(nodeId); for (const next of adjacency.get(nodeId) || []) visit(next, [...path, nodeId]); state.set(nodeId, "done"); }; visit(start.id, [start.id]); for (const node of top) { if (!reachable.has(node.id)) { const knownContainer = CONTROL_FLOW_COMPONENTS.has(node.componentId); if (!knownContainer && !flow.edges.some((edge) => edge.target === node.id)) { issues.push({ severity: "warning", code: "unreachable_node", nodeId: node.id, message: `节点「${node.label || node.id}」没有从起点可达的连线,执行时不会运行` }); } } } } return issues; } export function hasBlockingIssues(issues: FlowIssue[]): boolean { return issues.some((issue) => issue.severity === "error"); }