Repository: FontoXML/fontoxpath (ts)

Issue:
Primary error hidden if fontoxpath:evaluate is applied to a precompiled expression that throws
This was a fun one to debug - our built-in `fontoxpath:evaluate` function can be applied to both strings (direct XPath source) or elements (representing XPath ASTs). The latter form is used for Fonto Editor's `xq`. Turns out we have some type confusion around the value here in the error handling logic. This works fine in case the expression is a string, but if it's an element the value is treated as an Element while it is in fact a NodePointer to one. This causes a crash in `printAndRethrowError` as it's trying to iterate over its `.childNodes` while it should have used `.node.childNodes` when trying to find the comment that contains the original expression.

Pull request:
Fix handling of AST expressions in fontoxpath:evaluate error handling
Fixes #672

Patch:
diff --git a/src/expressions/functions/builtInFunctions_fontoxpath.ts b/src/expressions/functions/builtInFunctions_fontoxpath.ts
index f55ef9f8..995b4cc1 100644
--- a/src/expressions/functions/builtInFunctions_fontoxpath.ts
+++ b/src/expressions/functions/builtInFunctions_fontoxpath.ts
@@ -42,7 +42,7 @@ function buildResultIterator(
 	args: ISequence,
 	staticContext: StaticContext,
 	executionParameters: ExecutionParameters,
-): { queryValue: Value; resultIterator: IIterator<Value> } {
+): { evaluableExpression: EvaluableExpression; resultIterator: IIterator<Value> } {
 	const queryValue = query.first();
 	const variables = (args.first() as MapValue).keyValuePairs.reduce((expandedArgs, arg) => {
 		expandedArgs[arg.key.value] = createDoublyIterableSequence(arg.value());
@@ -53,9 +53,11 @@ function buildResultIterator(
 	const contextItemSequence = variables['.'] ? variables['.']() : sequenceFactory.empty();
 	delete variables['.'];
 
+	const evaluableExpression = getEvaluableExpressionFromValue(queryValue);
+
 	try {
 		const { expression, staticContext: innerStaticContext } = staticallyCompileXPath(
-			getEvaluableExpressionFromValue(queryValue),
+			evaluableExpression,
 			{
 				allowUpdating: false,
 				allowXQuery: true,
@@ -87,10 +89,10 @@ function buildResultIterator(
 
 		return {
 			resultIterator: expression.evaluate(innerDynamicContext, executionParameters).value,
-			queryValue,
+			evaluableExpression,
 		};
 	} catch (error) {
-		printAndRethrowError(queryValue.value, error);
+		printAndRethrowError(evaluableExpression, error);
 	}
 }
 
@@ -102,11 +104,11 @@ const fontoxpathEvaluate: FunctionDefinitionType = (
 	args,
 ) => {
 	let resultIterator: IIterator<Value>;
-	let queryValue: Value;
+	let evaluableExpression: EvaluableExpression;
 	return sequenceFactory.create({
 		next: () => {
 			if (!resultIterator) {
-				({ resultIterator, queryValue } = buildResultIterator(
+				({ resultIterator, evaluableExpression } = buildResultIterator(
 					query,
 					args,
 					staticContext,
@@ -117,7 +119,7 @@ const fontoxpathEvaluate: FunctionDefinitionType = (
 			try {
 				return resultIterator.next(IterationHint.NONE);
 			} catch (error) {
-				printAndRethrowError(queryValue.value, error);
+				printAndRethrowError(evaluableExpression, error);
 			}
 		},
 	});


Test patch:
diff --git a/test/specs/parsing/functions/functions.fontoxpath.tests.ts b/test/specs/parsing/functions/functions.fontoxpath.tests.ts
index b386bc4a..075aec5e 100644
--- a/test/specs/parsing/functions/functions.fontoxpath.tests.ts
+++ b/test/specs/parsing/functions/functions.fontoxpath.tests.ts
@@ -3,8 +3,10 @@ import {
 	domFacade,
 	evaluateXPath,
 	evaluateXPathToBoolean,
+	evaluateXPathToFirstNode,
 	evaluateXPathToNumber,
 	evaluateXPathToString,
+	parseScript,
 	registerCustomXPathFunction,
 } from 'fontoxpath';
 import * as slimdom from 'slimdom';
@@ -158,6 +160,21 @@ describe('extension functions', () => {
 				42,
 			));
 
+		it('throws when passed an XQueryX program with stack traces that throws', () => {
+			const expr = parseScript('format-integer($n,"A")', { debug: true }, documentNode);
+			chai.assert.throws(
+				() =>
+					evaluateXPathToString(
+						`fontoxpath:evaluate($expr, map{ 'n': 1.2 })`,
+						documentNode,
+						domFacade,
+						{ expr },
+						{ language: evaluateXPath.XQUERY_3_1_LANGUAGE, debug: true },
+					),
+				'XPTY0004',
+			);
+		});
+
 		it('throws when passed nonsense as an XQueryX program', () =>
 			chai.assert.throws(
 				() =>