import { SyntaxNode } from '@lezer/common'; import { LRParser } from '@lezer/lr'; export const ErrorId = 0; interface ParserErrorBoundary { startLineNumber: number; startColumn: number; endLineNumber: number; endColumn: number; error: string; } interface ParseError { text: string; node: SyntaxNode; } export function validateQuery(query: string, interpolatedQuery: string, queryLines: string[], parser: LRParser): ParserErrorBoundary[] | false { if (!query) { return false; } const interpolatedErrors: ParseError[] = parseQuery(interpolatedQuery, parser); if (!interpolatedErrors.length) { return false; } let parseErrors: ParseError[] = interpolatedErrors; if (query !== interpolatedQuery) { const queryErrors: ParseError[] = parseQuery(query, parser); parseErrors = interpolatedErrors.flatMap((interpolatedError) => queryErrors.filter((queryError) => interpolatedError.text === queryError.text) || interpolatedError); } return parseErrors.map((parseError) => findErrorBoundary(query, queryLines, parseError)).filter(isErrorBoundary); } function parseQuery(query: string, parser: LRParser) { const parseErrors: ParseError[] = []; const tree = parser.parse(query); tree.iterate({ enter: (nodeRef): false | void => { if (nodeRef.type.id === ErrorId) { const node = nodeRef.node; parseErrors.push({ node: node, text: query.substring(node.from, node.to), }); } }, }); return parseErrors; } function findErrorBoundary(query: string, queryLines: string[], parseError: ParseError): ParserErrorBoundary | null { if (queryLines.length === 1) { const isEmptyString = parseError.node.from === parseError.node.to; const errorNode = isEmptyString && parseError.node.parent ? parseError.node.parent : parseError.node; const error = isEmptyString ? query.substring(errorNode.from, errorNode.to) : parseError.text; return { startLineNumber: 1, startColumn: errorNode.from + 1, endLineNumber: 1, endColumn: errorNode.to + 1, error, }; } let startPos = 0, endPos = 0; for (let line = 0; line < queryLines.length; line++) { endPos = startPos + queryLines[line].length; if (parseError.node.from > endPos) { startPos += queryLines[line].length + 1; continue; } return { startLineNumber: line + 1, startColumn: parseError.node.from - startPos + 1, endLineNumber: line + 1, endColumn: parseError.node.to - startPos + 1, error: parseError.text, }; } return null; } function isErrorBoundary(boundary: ParserErrorBoundary | null): boundary is ParserErrorBoundary { return boundary !== null; }