diff --git a/src/ast.js b/src/ast.js index ce345e50a..74d8ef320 100644 --- a/src/ast.js +++ b/src/ast.js @@ -179,6 +179,9 @@ AST.prototype.isRightAssociative = function (operator) { */ AST.prototype.swapLocations = function (target, first, last, parser) { if (this.withPositions) { + if (!target || !target.loc || !first || !first.loc || !last || !last.loc) { + return; + } target.loc.start = first.loc.start; target.loc.end = last.loc.end; if (this.withSource) { @@ -198,6 +201,9 @@ AST.prototype.swapLocations = function (target, first, last, parser) { */ AST.prototype.resolveLocations = function (target, first, last, parser) { if (this.withPositions) { + if (!target || !target.loc || !first || !first.loc || !last || !last.loc) { + return; + } if (target.loc.start.offset > first.loc.start.offset) { target.loc.start = first.loc.start; } diff --git a/src/parser.js b/src/parser.js index 5e5ef1fa5..f55201c7b 100644 --- a/src/parser.js +++ b/src/parser.js @@ -357,12 +357,21 @@ Parser.prototype.raiseError = function (message, msgExpect, expect, token) { throw err; } // Error node : + // Temporarily set prev to the current token's end so the error node + // spans from the current token's start to its end (not the previous token's end). + const savedPrev = this.prev; + this.prev = [ + this.lexer.yylloc.last_line, + this.lexer.yylloc.last_column, + this.lexer.offset, + ]; const node = this.ast.prepare("error", null, this)( message, token, this.lexer.yylloc.first_line, expect, ); + this.prev = savedPrev; this._errors.push(node); return node; }; diff --git a/test/snapshot/__snapshots__/graceful.test.js.snap b/test/snapshot/__snapshots__/graceful.test.js.snap index 43f97676b..9c1b78da7 100644 --- a/test/snapshot/__snapshots__/graceful.test.js.snap +++ b/test/snapshot/__snapshots__/graceful.test.js.snap @@ -1,5 +1,281 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +exports[`Test graceful mode suppressErrors with withPositions should not throw on call-like expression followed by block (issue #1185) 1`] = ` +Program { + "children": [ + ExpressionStatement { + "expression": OffsetLookup { + "kind": "offsetlookup", + "loc": Location { + "end": Position { + "column": 13, + "line": 1, + "offset": 13, + }, + "source": null, + "start": Position { + "column": 6, + "line": 1, + "offset": 6, + }, + }, + "offset": Variable { + "curly": false, + "kind": "variable", + "loc": Location { + "end": Position { + "column": 12, + "line": 1, + "offset": 12, + }, + "source": null, + "start": Position { + "column": 10, + "line": 1, + "offset": 10, + }, + }, + "name": "a", + }, + "what": Call { + "arguments": [], + "kind": "call", + "loc": Location { + "end": Position { + "column": 9, + "line": 1, + "offset": 9, + }, + "source": null, + "start": Position { + "column": 6, + "line": 1, + "offset": 6, + }, + }, + "what": Name { + "kind": "name", + "loc": Location { + "end": Position { + "column": 7, + "line": 1, + "offset": 7, + }, + "source": null, + "start": Position { + "column": 6, + "line": 1, + "offset": 6, + }, + }, + "name": "f", + "resolution": "uqn", + }, + }, + }, + "kind": "expressionstatement", + "loc": Location { + "end": Position { + "column": 13, + "line": 1, + "offset": 13, + }, + "source": null, + "start": Position { + "column": 6, + "line": 1, + "offset": 6, + }, + }, + }, + ExpressionStatement { + "expression": undefined, + "kind": "expressionstatement", + "loc": Location { + "end": Position { + "column": 14, + "line": 1, + "offset": 14, + }, + "source": null, + "start": Position { + "column": 13, + "line": 1, + "offset": 13, + }, + }, + }, + ], + "errors": [ + Error { + "expected": "}", + "kind": "error", + "line": 1, + "loc": Location { + "end": Position { + "column": 13, + "line": 1, + "offset": 13, + }, + "source": null, + "start": Position { + "column": 12, + "line": 1, + "offset": 12, + }, + }, + "message": "Parse Error : syntax error, unexpected ';', expecting '}' on line 1", + "token": "';'", + }, + Error { + "expected": "EXPR", + "kind": "error", + "line": 1, + "loc": Location { + "end": Position { + "column": 14, + "line": 1, + "offset": 14, + }, + "source": null, + "start": Position { + "column": 13, + "line": 1, + "offset": 13, + }, + }, + "message": "Parse Error : syntax error, unexpected '}' on line 1", + "token": "'}'", + }, + ], + "kind": "program", + "loc": Location { + "end": Position { + "column": 14, + "line": 1, + "offset": 14, + }, + "source": null, + "start": Position { + "column": 0, + "line": 1, + "offset": 0, + }, + }, +} +`; + +exports[`Test graceful mode suppressErrors with withPositions should not throw on silent expression with incomplete binary (issue #1185) 1`] = ` +Program { + "children": [ + ExpressionStatement { + "expression": Bin { + "kind": "bin", + "left": Silent { + "expr": Variable { + "curly": false, + "kind": "variable", + "loc": Location { + "end": Position { + "column": 9, + "line": 1, + "offset": 9, + }, + "source": null, + "start": Position { + "column": 7, + "line": 1, + "offset": 7, + }, + }, + "name": "a", + }, + "kind": "silent", + "loc": Location { + "end": Position { + "column": 9, + "line": 1, + "offset": 9, + }, + "source": null, + "start": Position { + "column": 6, + "line": 1, + "offset": 6, + }, + }, + }, + "loc": Location { + "end": Position { + "column": 12, + "line": 1, + "offset": 12, + }, + "source": null, + "start": Position { + "column": 7, + "line": 1, + "offset": 7, + }, + }, + "right": undefined, + "type": "-", + }, + "kind": "expressionstatement", + "loc": Location { + "end": Position { + "column": 12, + "line": 1, + "offset": 12, + }, + "source": null, + "start": Position { + "column": 7, + "line": 1, + "offset": 7, + }, + }, + }, + ], + "errors": [ + Error { + "expected": "EXPR", + "kind": "error", + "line": 1, + "loc": Location { + "end": Position { + "column": 12, + "line": 1, + "offset": 12, + }, + "source": null, + "start": Position { + "column": 11, + "line": 1, + "offset": 11, + }, + }, + "message": "Parse Error : syntax error, unexpected ';' on line 1", + "token": "';'", + }, + ], + "kind": "program", + "loc": Location { + "end": Position { + "column": 12, + "line": 1, + "offset": 12, + }, + "source": null, + "start": Position { + "column": 0, + "line": 1, + "offset": 0, + }, + }, +} +`; + exports[`Test graceful mode to suppress errors interface 1`] = ` Program { "children": [ diff --git a/test/snapshot/graceful.test.js b/test/snapshot/graceful.test.js index d6fc6cb98..5da8bab41 100644 --- a/test/snapshot/graceful.test.js +++ b/test/snapshot/graceful.test.js @@ -1,6 +1,25 @@ const parser = require("../main"); describe("Test graceful mode", function () { + describe("suppressErrors with withPositions", function () { + const test = parser.create({ + parser: { + suppressErrors: true, + }, + ast: { + withPositions: true, + }, + }); + + it("should not throw on call-like expression followed by block (issue #1185)", function () { + expect(test.parseCode("