Skip to content

Commit ba62db8

Browse files
author
nicosammito
committed
feat: integrate flow source code generation for improved validation execution
1 parent 7ee92bc commit ba62db8

1 file changed

Lines changed: 34 additions & 99 deletions

File tree

src/validation/getFlowValidation.ts

Lines changed: 34 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
NodeParameter,
99
ReferenceValue
1010
} from "@code0-tech/sagittarius-graphql-types";
11-
import {createCompilerHost, getSharedTypeDeclarations, ValidationResult} from "../utils";
11+
import {createCompilerHost, generateFlowSourceCode, getSharedTypeDeclarations, ValidationResult} from "../utils";
1212

1313
const sanitizeId = (id: string) => id.replace(/[^a-zA-Z0-9]/g, '_');
1414

@@ -20,101 +20,9 @@ export const getFlowValidation = (
2020
functions?: FunctionDefinition[],
2121
dataTypes?: DataType[]
2222
): ValidationResult => {
23-
const visited = new Set<string>();
24-
const nodes = flow?.nodes?.nodes || [];
2523

26-
const funcMap = new Map(functions?.map(f => [f.identifier, f]));
2724

28-
/**
29-
* Recursive function to generate TypeScript code for a node and its execution path.
30-
*/
31-
const generateNodeCode = (
32-
nodeId: string,
33-
indent: string = ""
34-
): string => {
35-
if (visited.has(nodeId)) return "";
36-
37-
const node = nodes.find(n => n?.id === nodeId);
38-
if (!node || !node.functionDefinition) return "";
39-
40-
visited.add(nodeId);
41-
42-
const funcDef = funcMap.get(node.functionDefinition?.identifier);
43-
if (!funcDef) return `${indent}// Error: Function ${node.functionDefinition.identifier} not found\n`;
44-
45-
const params = node.parameters?.nodes as NodeParameter[] || [];
46-
47-
const args = params.map((p, index) => {
48-
const val = p.value;
49-
if (!val) return "undefined";
50-
51-
if (val.__typename === "ReferenceValue") {
52-
const ref = val as ReferenceValue;
53-
if (!ref.nodeFunctionId) return "undefined";
54-
55-
let refCode = ref.parameterIndex !== undefined
56-
? `p_${sanitizeId(ref.nodeFunctionId)}_${ref.parameterIndex}`
57-
: `node_${sanitizeId(ref.nodeFunctionId)}`;
58-
59-
ref.referencePath?.forEach(pathObj => {
60-
refCode += `?.${pathObj.path}`;
61-
});
62-
63-
return `/* @pos ${nodeId} ${index} */ ${refCode}`;
64-
}
65-
66-
if (val.__typename === "LiteralValue") {
67-
return `/* @pos ${nodeId} ${index} */ ${JSON.stringify(val.value)}`;
68-
}
69-
70-
if (val.__typename === "NodeFunctionIdWrapper") {
71-
const wrapper = val as NodeFunctionIdWrapper;
72-
const lambdaArgName = `p_${sanitizeId(nodeId)}_${index}`;
73-
const subTreeCode = generateNodeCode(wrapper.id!, indent + " ");
74-
return `/* @pos ${nodeId} ${index} */ (${lambdaArgName}) => {\n${subTreeCode}${indent}}`;
75-
}
76-
77-
return "undefined";
78-
}).join(", ");
79-
80-
const varName = `node_${sanitizeId(node.id!)}`;
81-
const funcName = `fn_${node?.functionDefinition?.identifier?.replace(/::/g, '_')}`;
82-
83-
// Add 'as any' cast only if undefined arguments are passed to a generic function to avoid false-positive errors.
84-
const needsAnyCast = args.includes("undefined");
85-
const isReturnNode = node.functionDefinition.identifier === "std::control::return";
86-
let code = `${indent}${isReturnNode ? "return " : `var ${varName} = `}${funcName}(${args})${needsAnyCast ? " as any" : ""} ;\n`;
87-
88-
if (node.nextNodeId) {
89-
code += generateNodeCode(node.nextNodeId, indent);
90-
}
91-
92-
return code;
93-
};
94-
95-
// 1. Generate Declarations
96-
const typeDefs = getSharedTypeDeclarations(dataTypes);
97-
98-
const funcDeclarations = functions?.map(funcDef => {
99-
return `declare function fn_${funcDef.identifier?.replace(/::/g, '_')}${funcDef.signature}`;
100-
}).join('\n');
101-
102-
const nextNodeIds = new Set(nodes.map(n => n?.nextNodeId).filter(id => !!id));
103-
const subTreeIds = new Set<string>();
104-
nodes.forEach(n => {
105-
n?.parameters?.nodes?.forEach((p: any) => {
106-
if (p?.value?.__typename === "NodeFunctionIdWrapper" && p.value.id) {
107-
subTreeIds.add(p.value.id);
108-
}
109-
});
110-
});
111-
112-
const executionCode = nodes
113-
.filter(n => n?.id && !nextNodeIds.has(n.id) && !subTreeIds.has(n.id))
114-
.map(n => generateNodeCode(n!.id!))
115-
.join('\n');
116-
117-
const sourceCode = `${typeDefs}\n${funcDeclarations}\n\n// --- Flow ---\n${executionCode}`;
25+
const sourceCode = generateFlowSourceCode(flow, functions, dataTypes);
11826

11927
// 3. Virtual TypeScript Compilation
12028
const fileName = "index.ts";
@@ -134,11 +42,38 @@ export const getFlowValidation = (
13442

13543
if (d.start !== undefined) {
13644
const fullText = sourceFile.getFullText();
137-
const textBefore = fullText.substring(0, d.start);
138-
const posMatch = textBefore.match(/\/\* @pos ([^ ]+) (\d+) \*\/\s*$/);
139-
if (posMatch) {
140-
nodeId = posMatch[1] as NodeFunction['id'];
141-
parameterIndex = parseInt(posMatch[2], 10);
45+
46+
// Search for position marker comment near the error location
47+
// The error position is typically the start of the problematic token (e.g., "undefined")
48+
const searchStart = Math.max(0, d.start - 300);
49+
const searchEnd = Math.min(fullText.length, d.start);
50+
const searchText = fullText.substring(searchStart, searchEnd);
51+
52+
// Find all @pos comments in the search range
53+
const posRegex = /\/\* @pos ([^ ]+) (\d+) \*\//g;
54+
let match;
55+
let closestMatch: RegExpExecArray | null = null;
56+
let closestCommentEnd = -1;
57+
58+
// Collect all matches and find the one whose end is closest to d.start
59+
// We want the comment that is immediately before the error
60+
while ((match = posRegex.exec(searchText)) !== null) {
61+
const commentStart = searchStart + match.index;
62+
const commentEnd = commentStart + match[0].length;
63+
64+
// Only consider comments that end before or very close to the error start
65+
// This ensures we get the @pos comment that directly precedes the problematic argument
66+
if (commentEnd <= d.start!) {
67+
if (commentEnd > closestCommentEnd) {
68+
closestCommentEnd = commentEnd;
69+
closestMatch = match;
70+
}
71+
}
72+
}
73+
74+
if (closestMatch) {
75+
nodeId = closestMatch[1] as NodeFunction['id'];
76+
parameterIndex = parseInt(closestMatch[2], 10);
14277
}
14378
}
14479

0 commit comments

Comments
 (0)