From c92c756afbdc2b9fad5d5c2bea037fe238d70e8a Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Sun, 14 Jul 2019 05:16:02 +0900 Subject: [PATCH 1/2] Fix: a failing test with acorn 6.2.0 --- ...for-of-with-function-initializer.result.js | 714 +----------------- 1 file changed, 5 insertions(+), 709 deletions(-) diff --git a/tests/fixtures/ecma-version/6/forOf/for-of-with-function-initializer.result.js b/tests/fixtures/ecma-version/6/forOf/for-of-with-function-initializer.result.js index 344e65bf..b95a7a0a 100644 --- a/tests/fixtures/ecma-version/6/forOf/for-of-with-function-initializer.result.js +++ b/tests/fixtures/ecma-version/6/forOf/for-of-with-function-initializer.result.js @@ -1,710 +1,6 @@ module.exports = { - "type": "Program", - "loc": { - "start": { - "line": 1, - "column": 0 - }, - "end": { - "line": 1, - "column": 64 - } - }, - "range": [ - 0, - 64 - ], - "body": [ - { - "type": "ForOfStatement", - "loc": { - "start": { - "line": 1, - "column": 0 - }, - "end": { - "line": 1, - "column": 64 - } - }, - "range": [ - 0, - 64 - ], - "left": { - "type": "VariableDeclaration", - "loc": { - "start": { - "line": 1, - "column": 5 - }, - "end": { - "line": 1, - "column": 43 - } - }, - "range": [ - 5, - 43 - ], - "declarations": [ - { - "type": "VariableDeclarator", - "loc": { - "start": { - "line": 1, - "column": 9 - }, - "end": { - "line": 1, - "column": 43 - } - }, - "range": [ - 9, - 43 - ], - "id": { - "type": "Identifier", - "loc": { - "start": { - "line": 1, - "column": 9 - }, - "end": { - "line": 1, - "column": 10 - } - }, - "range": [ - 9, - 10 - ], - "name": "i" - }, - "init": { - "type": "FunctionExpression", - "loc": { - "start": { - "line": 1, - "column": 13 - }, - "end": { - "line": 1, - "column": 43 - } - }, - "range": [ - 13, - 43 - ], - "id": null, - "generator": false, - "expression": false, - "params": [], - "body": { - "type": "BlockStatement", - "loc": { - "start": { - "line": 1, - "column": 24 - }, - "end": { - "line": 1, - "column": 43 - } - }, - "range": [ - 24, - 43 - ], - "body": [ - { - "type": "ReturnStatement", - "loc": { - "start": { - "line": 1, - "column": 26 - }, - "end": { - "line": 1, - "column": 41 - } - }, - "range": [ - 26, - 41 - ], - "argument": { - "type": "BinaryExpression", - "loc": { - "start": { - "line": 1, - "column": 33 - }, - "end": { - "line": 1, - "column": 41 - } - }, - "range": [ - 33, - 41 - ], - "left": { - "type": "Literal", - "loc": { - "start": { - "line": 1, - "column": 33 - }, - "end": { - "line": 1, - "column": 35 - } - }, - "range": [ - 33, - 35 - ], - "value": 10, - "raw": "10" - }, - "operator": "in", - "right": { - "type": "ArrayExpression", - "loc": { - "start": { - "line": 1, - "column": 39 - }, - "end": { - "line": 1, - "column": 41 - } - }, - "range": [ - 39, - 41 - ], - "elements": [] - } - } - } - ] - } - } - } - ], - "kind": "var" - }, - "right": { - "type": "Identifier", - "loc": { - "start": { - "line": 1, - "column": 47 - }, - "end": { - "line": 1, - "column": 51 - } - }, - "range": [ - 47, - 51 - ], - "name": "list" - }, - "body": { - "type": "ExpressionStatement", - "loc": { - "start": { - "line": 1, - "column": 53 - }, - "end": { - "line": 1, - "column": 64 - } - }, - "range": [ - 53, - 64 - ], - "expression": { - "type": "CallExpression", - "loc": { - "start": { - "line": 1, - "column": 53 - }, - "end": { - "line": 1, - "column": 63 - } - }, - "range": [ - 53, - 63 - ], - "callee": { - "type": "Identifier", - "loc": { - "start": { - "line": 1, - "column": 53 - }, - "end": { - "line": 1, - "column": 60 - } - }, - "range": [ - 53, - 60 - ], - "name": "process" - }, - "arguments": [ - { - "type": "Identifier", - "loc": { - "start": { - "line": 1, - "column": 61 - }, - "end": { - "line": 1, - "column": 62 - } - }, - "range": [ - 61, - 62 - ], - "name": "x" - } - ] - } - } - } - ], - "sourceType": "script", - "tokens": [ - { - "type": "Keyword", - "value": "for", - "loc": { - "start": { - "line": 1, - "column": 0 - }, - "end": { - "line": 1, - "column": 3 - } - }, - "range": [ - 0, - 3 - ] - }, - { - "type": "Punctuator", - "value": "(", - "loc": { - "start": { - "line": 1, - "column": 4 - }, - "end": { - "line": 1, - "column": 5 - } - }, - "range": [ - 4, - 5 - ] - }, - { - "type": "Keyword", - "value": "var", - "loc": { - "start": { - "line": 1, - "column": 5 - }, - "end": { - "line": 1, - "column": 8 - } - }, - "range": [ - 5, - 8 - ] - }, - { - "type": "Identifier", - "value": "i", - "loc": { - "start": { - "line": 1, - "column": 9 - }, - "end": { - "line": 1, - "column": 10 - } - }, - "range": [ - 9, - 10 - ] - }, - { - "type": "Punctuator", - "value": "=", - "loc": { - "start": { - "line": 1, - "column": 11 - }, - "end": { - "line": 1, - "column": 12 - } - }, - "range": [ - 11, - 12 - ] - }, - { - "type": "Keyword", - "value": "function", - "loc": { - "start": { - "line": 1, - "column": 13 - }, - "end": { - "line": 1, - "column": 21 - } - }, - "range": [ - 13, - 21 - ] - }, - { - "type": "Punctuator", - "value": "(", - "loc": { - "start": { - "line": 1, - "column": 21 - }, - "end": { - "line": 1, - "column": 22 - } - }, - "range": [ - 21, - 22 - ] - }, - { - "type": "Punctuator", - "value": ")", - "loc": { - "start": { - "line": 1, - "column": 22 - }, - "end": { - "line": 1, - "column": 23 - } - }, - "range": [ - 22, - 23 - ] - }, - { - "type": "Punctuator", - "value": "{", - "loc": { - "start": { - "line": 1, - "column": 24 - }, - "end": { - "line": 1, - "column": 25 - } - }, - "range": [ - 24, - 25 - ] - }, - { - "type": "Keyword", - "value": "return", - "loc": { - "start": { - "line": 1, - "column": 26 - }, - "end": { - "line": 1, - "column": 32 - } - }, - "range": [ - 26, - 32 - ] - }, - { - "type": "Numeric", - "value": "10", - "loc": { - "start": { - "line": 1, - "column": 33 - }, - "end": { - "line": 1, - "column": 35 - } - }, - "range": [ - 33, - 35 - ] - }, - { - "type": "Keyword", - "value": "in", - "loc": { - "start": { - "line": 1, - "column": 36 - }, - "end": { - "line": 1, - "column": 38 - } - }, - "range": [ - 36, - 38 - ] - }, - { - "type": "Punctuator", - "value": "[", - "loc": { - "start": { - "line": 1, - "column": 39 - }, - "end": { - "line": 1, - "column": 40 - } - }, - "range": [ - 39, - 40 - ] - }, - { - "type": "Punctuator", - "value": "]", - "loc": { - "start": { - "line": 1, - "column": 40 - }, - "end": { - "line": 1, - "column": 41 - } - }, - "range": [ - 40, - 41 - ] - }, - { - "type": "Punctuator", - "value": "}", - "loc": { - "start": { - "line": 1, - "column": 42 - }, - "end": { - "line": 1, - "column": 43 - } - }, - "range": [ - 42, - 43 - ] - }, - { - "type": "Identifier", - "value": "of", - "loc": { - "start": { - "line": 1, - "column": 44 - }, - "end": { - "line": 1, - "column": 46 - } - }, - "range": [ - 44, - 46 - ] - }, - { - "type": "Identifier", - "value": "list", - "loc": { - "start": { - "line": 1, - "column": 47 - }, - "end": { - "line": 1, - "column": 51 - } - }, - "range": [ - 47, - 51 - ] - }, - { - "type": "Punctuator", - "value": ")", - "loc": { - "start": { - "line": 1, - "column": 51 - }, - "end": { - "line": 1, - "column": 52 - } - }, - "range": [ - 51, - 52 - ] - }, - { - "type": "Identifier", - "value": "process", - "loc": { - "start": { - "line": 1, - "column": 53 - }, - "end": { - "line": 1, - "column": 60 - } - }, - "range": [ - 53, - 60 - ] - }, - { - "type": "Punctuator", - "value": "(", - "loc": { - "start": { - "line": 1, - "column": 60 - }, - "end": { - "line": 1, - "column": 61 - } - }, - "range": [ - 60, - 61 - ] - }, - { - "type": "Identifier", - "value": "x", - "loc": { - "start": { - "line": 1, - "column": 61 - }, - "end": { - "line": 1, - "column": 62 - } - }, - "range": [ - 61, - 62 - ] - }, - { - "type": "Punctuator", - "value": ")", - "loc": { - "start": { - "line": 1, - "column": 62 - }, - "end": { - "line": 1, - "column": 63 - } - }, - "range": [ - 62, - 63 - ] - }, - { - "type": "Punctuator", - "value": ";", - "loc": { - "start": { - "line": 1, - "column": 63 - }, - "end": { - "line": 1, - "column": 64 - } - }, - "range": [ - 63, - 64 - ] - } - ] -}; \ No newline at end of file + index: 5, + lineNumber: 1, + column: 6, + message: "for-of loop variable declaration may not have an initializer" +}; From 8208b4da78212f5427645293a079e8b83319ef0f Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Thu, 25 Jul 2019 20:21:43 +0900 Subject: [PATCH 2/2] New: options.recoverableErrors (eslint/rfcs#19) --- espree.js | 9 ++- lib/espree.js | 44 +++++++++--- tests/lib/recoverable-errors.js | 116 ++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 11 deletions(-) create mode 100644 tests/lib/recoverable-errors.js diff --git a/espree.js b/espree.js index 7c16b696..accec3cf 100644 --- a/espree.js +++ b/espree.js @@ -129,8 +129,11 @@ function tokenize(code, options) { */ function parse(code, options) { const Parser = parsers.get(options); + const parser = new Parser(options, code); + const ast = parser.parse(); + const recoverableErrors = parser.recoverableErrors; - return new Parser(options, code).parse(); + return { ast, recoverableErrors }; } //------------------------------------------------------------------------------ @@ -141,7 +144,9 @@ exports.version = require("./package.json").version; exports.tokenize = tokenize; -exports.parse = parse; +exports.parseForESLint = parse; + +exports.parse = (code, options) => parse(code, options).ast; // Deep copy. /* istanbul ignore next */ diff --git a/lib/espree.js b/lib/espree.js index 6c57a87c..e9a99586 100644 --- a/lib/espree.js +++ b/lib/espree.js @@ -68,11 +68,36 @@ function normalizeOptions(options) { const sourceType = normalizeSourceType(options.sourceType); const ranges = options.range === true; const locations = options.loc === true; + const recoverableErrors = options.recoverableErrors === true; if (sourceType === "module" && ecmaVersion < 6) { throw new Error("sourceType 'module' is not supported when ecmaVersion < 2015. Consider adding `{ ecmaVersion: 2015 }` to the parser options."); } - return Object.assign({}, options, { ecmaVersion, sourceType, ranges, locations }); + return Object.assign( + {}, + options, + { ecmaVersion, sourceType, ranges, locations, recoverableErrors } + ); +} + +/** + * Create a new syntax error object. + * @param {string} input The source code. + * @param {number} pos The location the error happened. + * @param {string} message The error message. + * @param {SyntaxError[]} recoverableErrors The recovered errors. + * @returns {SyntaxError} The syntax error object. + */ +function createSyntaxError(input, pos, message, recoverableErrors) { + const loc = acorn.getLineInfo(input, pos); + const err = new SyntaxError(message); + + err.index = pos; + err.lineNumber = loc.line; + err.column = loc.column + 1; // acorn uses 0-based columns + err.recoverableErrors = recoverableErrors; + + return err; } /** @@ -167,6 +192,9 @@ module.exports = () => Parser => class Espree extends Parser { jsxAttrValueToken: false, lastToken: null }; + + // Public. + this.recoverableErrors = options.recoverableErrors ? [] : void 0; } tokenize() { @@ -243,13 +271,7 @@ module.exports = () => Parser => class Espree extends Parser { * @returns {void} */ raise(pos, message) { - const loc = acorn.getLineInfo(this.input, pos); - const err = new SyntaxError(message); - - err.index = pos; - err.lineNumber = loc.line; - err.column = loc.column + 1; // acorn uses 0-based columns - throw err; + throw createSyntaxError(this.input, pos, message, this.recoverableErrors); } /** @@ -260,7 +282,11 @@ module.exports = () => Parser => class Espree extends Parser { * @returns {void} */ raiseRecoverable(pos, message) { - this.raise(pos, message); + if (this.recoverableErrors) { + this.recoverableErrors.push(createSyntaxError(this.input, pos, message)); + } else { + this.raise(pos, message); + } } /** diff --git a/tests/lib/recoverable-errors.js b/tests/lib/recoverable-errors.js new file mode 100644 index 00000000..f699a066 --- /dev/null +++ b/tests/lib/recoverable-errors.js @@ -0,0 +1,116 @@ +/** + * @fileoverview Tests for options.recoverableErrors. + * @author Toru Nagashima + */ +"use strict"; + +const assert = require("assert"); +const { parseForESLint } = require("../../espree"); + +/** + * Gets a raw version of the AST that is suitable for comparison. This is necessary + * due to the different order of properties across parsers. + * @param {ASTNode} ast The AST to convert. + * @returns {ASTNode} The converted AST. + * @private + */ +function getRaw(ast) { + return JSON.parse(JSON.stringify(ast, (key, value) => { + + // Delete `node.start` and `node.end`. + if ((key === "start" || key === "end") && typeof value === "number") { + return void 0; + } + + // Delete `error.stack`. + if (value instanceof Error) { + return Object.assign({ message: value.message }, value); + } + + return value; + })); +} + +/** + * Assert a given code to throw the expected error. + * @param {Function} f The code to execute. + * @param {Object} expected The expected properties. + * @returns {void} + */ +function assertError(f, expected) { + try { + f(); + } catch (actual) { + assert.deepStrictEqual(getRaw(actual), expected); + return; + } + + assert.fail("should throw."); +} + +describe("'parseForESLint()' function with 'options.recoverableErrors'", () => { + it("should return AST and errors if `let a, a;` was given.", () => { + const { ast, recoverableErrors } = parseForESLint("let a, a;", { ecmaVersion: 2015, recoverableErrors: true }); + + assert.deepStrictEqual(getRaw(ast), { + type: "Program", + sourceType: "script", + body: [ + { + type: "VariableDeclaration", + kind: "let", + declarations: [ + { + type: "VariableDeclarator", + id: { + name: "a", + type: "Identifier" + }, + init: null + }, + { + type: "VariableDeclarator", + id: { + name: "a", + type: "Identifier" + }, + init: null + } + ] + } + ] + }); + assert.deepStrictEqual(getRaw(recoverableErrors), [ + { + column: 8, + index: 7, + lineNumber: 1, + message: "Identifier 'a' has already been declared" + } + ]); + }); + + it("should throw an error that has 'recoverableErrors' property if `let a, a; {` was given.", () => { + assertError( + () => parseForESLint("let a, a; {", { ecmaVersion: 2015, recoverableErrors: true }), + + // Top-level is the fatal error. + { + column: 12, + index: 11, + lineNumber: 1, + message: "Unexpected token", + + // The fatal error has the recovered syntax errors. + recoverableErrors: [ + { + column: 8, + index: 7, + lineNumber: 1, + message: "Identifier 'a' has already been declared" + } + ] + } + ); + }); +});