diff --git a/src/jsonata.js b/src/jsonata.js index 8863013f..639c2d12 100644 --- a/src/jsonata.js +++ b/src/jsonata.js @@ -1234,13 +1234,22 @@ var jsonata = (function() { } } // apply the procedure + var procName = expr.procedure.type === 'path' ? expr.procedure.steps[0].value : expr.procedure.value; try { + if(typeof proc === 'object') { + proc.token = procName; + proc.position = expr.position; + } result = yield * apply(proc, evaluatedArgs, input, environment); } catch (err) { - // add the position field to the error - err.position = expr.position; - // and the function identifier - err.token = expr.procedure.type === 'path' ? expr.procedure.steps[0].value : expr.procedure.value; + if(!err.position) { + // add the position field to the error + err.position = expr.position; + } + if (!err.token) { + // and the function identifier + err.token = procName; + } throw err; } return result; @@ -1262,6 +1271,10 @@ var jsonata = (function() { // the function returned a tail-call thunk // unpack it, evaluate its arguments, and apply the tail call var next = yield * evaluate(result.body.procedure, result.input, result.environment); + if(result.body.procedure.type === 'variable') { + next.token = result.body.procedure.value; + } + next.position = result.body.procedure.position; var evaluatedArgs = []; for(var ii = 0; ii < result.body.arguments.length; ii++) { evaluatedArgs.push(yield * evaluate(result.body.arguments[ii], result.input, result.environment)); @@ -1282,40 +1295,50 @@ var jsonata = (function() { */ function* applyInner(proc, args, input, environment) { var result; - var validatedArgs = args; - if(proc) { - validatedArgs = validateArguments(proc.signature, args, input); - } + try { + var validatedArgs = args; + if (proc) { + validatedArgs = validateArguments(proc.signature, args, input); + } - if (isLambda(proc)) { - result = yield * applyProcedure(proc, validatedArgs); - } else if (proc && proc._jsonata_function === true) { - var focus = { - environment: environment, - input: input - }; - // the `focus` is passed in as the `this` for the invoked function - result = proc.implementation.apply(focus, validatedArgs); - // `proc.implementation` might be a generator function - // and `result` might be a generator - if so, yield - if(isIterable(result)) { - result = yield *result; + if (isLambda(proc)) { + result = yield* applyProcedure(proc, validatedArgs); + } else if (proc && proc._jsonata_function === true) { + var focus = { + environment: environment, + input: input + }; + // the `focus` is passed in as the `this` for the invoked function + result = proc.implementation.apply(focus, validatedArgs); + // `proc.implementation` might be a generator function + // and `result` might be a generator - if so, yield + if (isIterable(result)) { + result = yield* result; + } + } else if (typeof proc === 'function') { + // typically these are functions that are returned by the invocation of plugin functions + // the `input` is being passed in as the `this` for the invoked function + // this is so that functions that return objects containing functions can chain + // e.g. $func().next().next() + result = proc.apply(input, validatedArgs); + /* istanbul ignore next */ + if (isIterable(result)) { + result = yield* result; + } + } else { + throw { + code: "T1006", + stack: (new Error()).stack + }; } - } else if (typeof proc === 'function') { - // typically these are functions that are returned by the invocation of plugin functions - // the `input` is being passed in as the `this` for the invoked function - // this is so that functions that return objects containing functions can chain - // e.g. $func().next().next() - result = proc.apply(input, validatedArgs); - /* istanbul ignore next */ - if(isIterable(result)) { - result = yield *result; + } catch(err) { + if(proc) { + if (typeof err.token == 'undefined' && typeof proc.token !== 'undefined') { + err.token = proc.token; + } + err.position = proc.position; } - } else { - throw { - code: "T1006", - stack: (new Error()).stack - }; + throw err; } return result; } diff --git a/test/implementation-tests.js b/test/implementation-tests.js index 23e0b7eb..9c58a0dc 100644 --- a/test/implementation-tests.js +++ b/test/implementation-tests.js @@ -296,7 +296,7 @@ describe("Tests that use the $clone() function", () => { expr.evaluate(testdata2); }) .to.throw() - .to.deep.contain({ position: 21, code: 'T2013' }); + .to.deep.contain({ code: 'T2013' }); }); }); }); @@ -829,7 +829,7 @@ describe("Tests that include infinite recursion", () => { expr.evaluate(); }) .to.throw() - .to.deep.contain({ position: 46, code: "U1001" }); + .to.deep.contain({ token: "inf", position: 32, code: "U1001" }); }); }); @@ -837,12 +837,12 @@ describe("Tests that include infinite recursion", () => { this.timeout(5000); it("should throw error", function() { expect(function() { - var expr = jsonata("(" + " $inf := function(){$inf()};" + " $inf()" + ")"); + var expr = jsonata("( $inf := function(){$inf()}; $inf())"); timeboxExpression(expr, 1000, 500); expr.evaluate(); }) .to.throw() - .to.deep.contain({ position: 37, code: "U1001" }); + .to.deep.contain({ token: "inf", code: "U1001" }); }); }); }); diff --git a/test/run-test-suite-async.js b/test/run-test-suite-async.js index dac423a4..2235a423 100644 --- a/test/run-test-suite-async.js +++ b/test/run-test-suite-async.js @@ -134,6 +134,11 @@ describe("JSONata Test Suite - async mode", () => { // Second is that a (defined) result was provided. In this case, // we do a deep equality check against the expected result. return expect(jsonataPromise(expr, dataset, testcase.bindings)).to.eventually.deep.equal(testcase.result); + } else if ("error" in testcase) { + // If an error was expected, + // we do a deep equality check against the expected error structure. + return expect(jsonataPromise(expr, dataset, testcase.bindings)).to.be.rejected + .and.eventually.have.property('code', testcase.error.code); } else if ("code" in testcase) { // Finally, if a `code` field was specified, we expected the // evaluation to fail and include the specified code in the diff --git a/test/run-test-suite.js b/test/run-test-suite.js index 0859cbfa..6c032e4d 100644 --- a/test/run-test-suite.js +++ b/test/run-test-suite.js @@ -118,6 +118,14 @@ describe("JSONata Test Suite", () => { // we do a deep equality check against the expected result. let result = expr.evaluate(dataset, testcase.bindings); expect(result).to.deep.equal(testcase.result); + } else if ("error" in testcase) { + // If an error was expected, + // we do a deep equality check against the expected error structure. + expect(function() { + expr.evaluate(dataset, testcase.bindings); + }) + .to.throw() + .to.deep.contain(testcase.error); } else if ("code" in testcase) { // Finally, if a `code` field was specified, we expected the // evaluation to fail and include the specified code in the diff --git a/test/test-suite/groups/errors/case025.json b/test/test-suite/groups/errors/case025.json new file mode 100644 index 00000000..b5c5343a --- /dev/null +++ b/test/test-suite/groups/errors/case025.json @@ -0,0 +1,9 @@ +{ + "expr": "( $A := function(){$min(2, 3)}; $A() )", + "dataset": null, + "bindings": {}, + "error": { + "code": "T0410", + "token": "min" + } +} diff --git a/test/test-suite/groups/errors/case026.json b/test/test-suite/groups/errors/case026.json new file mode 100644 index 00000000..c30b7a36 --- /dev/null +++ b/test/test-suite/groups/errors/case026.json @@ -0,0 +1,9 @@ +{ + "expr": "( $B := function(){''}; $A := function(){2 + $B()}; $A() )", + "dataset": null, + "bindings": {}, + "error": { + "code": "T2002", + "token": "+" + } +}