From 493d464e1564aea0ea33000389771d42ddece2cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=9B=E5=AE=9A=E8=B0=94=E7=9A=84=E7=8C=AB?= Date: Tue, 2 Apr 2019 05:01:45 +0800 Subject: [PATCH] Breaking: validate parser options (fixes #384) (#412) * Breaking: validate parser options (fixes #384) it will throw an error if any of the conditions is true: + ecmaVersion is invalid + sourceType is invalid + ecmaVersion < 6 & sourceType: "module" * fix failing tests * Update lib/espree.js Co-Authored-By: aladdin-add * Update tests/lib/ecma-version.js Co-Authored-By: aladdin-add * chore: update test * Chore: move some assginment to func normalizeOptions * Update lib/espree.js Co-Authored-By: aladdin-add * Update lib/espree.js Co-Authored-By: aladdin-add * Update ecma-version.js --- lib/espree.js | 61 ++++++++++++++++++++++++++++++--------- tests/lib/ecma-version.js | 24 +++++++++++++++ tests/lib/parse.js | 2 +- 3 files changed, 72 insertions(+), 15 deletions(-) diff --git a/lib/espree.js b/lib/espree.js index 107affd1..6c57a87c 100644 --- a/lib/espree.js +++ b/lib/espree.js @@ -10,12 +10,14 @@ const STATE = Symbol("espree's internal state"); const ESPRIMA_FINISH_NODE = Symbol("espree's esprimaFinishNode"); const tokTypes = Object.assign({}, acorn.tokTypes, jsx.tokTypes); + /** * Normalize ECMAScript version from the initial config * @param {number} ecmaVersion ECMAScript version from the initial config + * @throws {Error} throws an error if the ecmaVersion is invalid. * @returns {number} normalized ECMAScript version */ -function normalizeEcmaVersion(ecmaVersion) { +function normalizeEcmaVersion(ecmaVersion = DEFAULT_ECMA_VERSION) { if (typeof ecmaVersion === "number") { let version = ecmaVersion; @@ -35,12 +37,42 @@ function normalizeEcmaVersion(ecmaVersion) { case 10: return version; - default: - throw new Error("Invalid ecmaVersion."); + // no default } - } else { - return DEFAULT_ECMA_VERSION; } + + throw new Error("Invalid ecmaVersion."); +} + +/** + * Normalize sourceType from the initial config + * @param {string} sourceType to normalize + * @throws {Error} throw an error if sourceType is invalid + * @returns {string} normalized sourceType + */ +function normalizeSourceType(sourceType = "script") { + if (sourceType === "script" || sourceType === "module") { + return sourceType; + } + throw new Error("Invalid sourceType."); +} + +/** + * Normalize parserOptions + * @param {Object} options the parser options to normalize + * @throws {Error} throw an error if found invalid option. + * @returns {Object} normalized options + */ +function normalizeOptions(options) { + const ecmaVersion = normalizeEcmaVersion(options.ecmaVersion); + const sourceType = normalizeSourceType(options.sourceType); + const ranges = options.range === true; + const locations = options.loc === 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 }); } /** @@ -77,17 +109,16 @@ function convertAcornCommentToEsprimaComment(block, text, start, end, startLoc, } module.exports = () => Parser => class Espree extends Parser { - constructor(options, code) { - if (typeof options !== "object" || options === null) { - options = {}; + constructor(opts, code) { + if (typeof opts !== "object" || opts === null) { + opts = {}; } if (typeof code !== "string" && !(code instanceof String)) { code = String(code); } + const options = normalizeOptions(opts); const ecmaFeatures = options.ecmaFeatures || {}; - const ecmaVersion = normalizeEcmaVersion(options.ecmaVersion); - const isModule = options.sourceType === "module"; const tokenTranslator = options.tokens === true ? new TokenTranslator(tokTypes, code) @@ -95,10 +126,12 @@ module.exports = () => Parser => class Espree extends Parser { // Initialize acorn parser. super({ - ecmaVersion: isModule ? Math.max(6, ecmaVersion) : ecmaVersion, - sourceType: isModule ? "module" : "script", - ranges: options.range === true, - locations: options.loc === true, + + // TODO: use {...options} when spread is supported(Node.js >= 8.3.0). + ecmaVersion: options.ecmaVersion, + sourceType: options.sourceType, + ranges: options.ranges, + locations: options.locations, // Truthy value is true for backward compatibility. allowReturnOutsideFunction: Boolean(ecmaFeatures.globalReturn), diff --git a/tests/lib/ecma-version.js b/tests/lib/ecma-version.js index bd533d4e..64409853 100644 --- a/tests/lib/ecma-version.js +++ b/tests/lib/ecma-version.js @@ -141,6 +141,30 @@ describe("ecmaVersion", () => { }, /Invalid ecmaVersion/); }); + it("Should throw error when non-numeric year is provided", () => { + assert.throws(() => { + espree.parse( + "let foo = bar;", { + ecmaVersion: "2015", + comment: true, + tokens: true, + range: true, + loc: true + } + ); + }, /Invalid ecmaVersion/); + }); + + it("Should throw error when using module in pre-ES6", () => { + assert.throws(() => { + espree.parse( + "let foo = bar;", { + ecmaVersion: 5, + sourceType: "module" + } + ); + }, /sourceType 'module' is not supported when ecmaVersion < 2015/); + }); }); }); diff --git a/tests/lib/parse.js b/tests/lib/parse.js index acae3b99..fe0b73ed 100644 --- a/tests/lib/parse.js +++ b/tests/lib/parse.js @@ -24,7 +24,7 @@ describe("parse()", () => { it("should have correct column number when strict mode error occurs", () => { try { - espree.parse("function fn(a, a) {\n}", { sourceType: "module" }); + espree.parse("function fn(a, a) {\n}", { ecmaVersion: 6, sourceType: "module" }); } catch (err) { assert.strictEqual(err.column, 16); }