Skip to content

Commit

Permalink
Update: ecmaVersion allows "latest" (#14720)
Browse files Browse the repository at this point in the history
* Revert "Revert "Update: ecmaVersion defaults to 5, and allows "latest" (#14622)" (#14711)"

This reverts commit 97d9bd2.

* chore: use parser.$parser to check if it's espree

* chore: add some tests

* chore: not set default 5

* chore: make the $parser non-enumerable

* chore: use symbol

* chore: a small refactor
  • Loading branch information
aladdin-add committed Jun 26, 2021
1 parent 104c0b5 commit ed1da5d
Show file tree
Hide file tree
Showing 6 changed files with 302 additions and 15 deletions.
4 changes: 2 additions & 2 deletions docs/user-guide/configuring/language-options.md
Expand Up @@ -187,7 +187,7 @@ For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 glo

Parser options are set in your `.eslintrc.*` file by using the `parserOptions` property. The available options are:

* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, or 12 to specify the version of ECMAScript syntax you want to use. You can also set to 2015 (same as 6), 2016 (same as 7), 2017 (same as 8), 2018 (same as 9), 2019 (same as 10), 2020 (same as 11), or 2021 (same as 12) to use the year-based naming.
* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, or 12 to specify the version of ECMAScript syntax you want to use. You can also set to 2015 (same as 6), 2016 (same as 7), 2017 (same as 8), 2018 (same as 9), 2019 (same as 10), 2020 (same as 11), or 2021 (same as 12) to use the year-based naming. You can also set "latest" to use the most recently supported version.
* `sourceType` - set to `"script"` (default) or `"module"` if your code is in ECMAScript modules.
* `ecmaFeatures` - an object indicating which additional language features you'd like to use:
* `globalReturn` - allow `return` statements in the global scope
Expand All @@ -199,7 +199,7 @@ Here's an example `.eslintrc.json` file:
```json
{
"parserOptions": {
"ecmaVersion": 6,
"ecmaVersion": "latest",
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
Expand Down
28 changes: 16 additions & 12 deletions lib/linter/linter.js
Expand Up @@ -37,8 +37,10 @@ const
const debug = require("debug")("eslint:linter");
const MAX_AUTOFIX_PASSES = 10;
const DEFAULT_PARSER_NAME = "espree";
const DEFAULT_ECMA_VERSION = 5;
const commentParser = new ConfigCommentParser();
const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } };
const parserSymbol = Symbol.for("eslint.RuleTester.parser");

//------------------------------------------------------------------------------
// Typedefs
Expand Down Expand Up @@ -432,10 +434,16 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {

/**
* Normalize ECMAScript version from the initial config
* @param {number} ecmaVersion ECMAScript version from the initial config
* @param {Parser} parser The parser which uses this options.
* @param {number} ecmaVersion ECMAScript version from the initial config
* @returns {number} normalized ECMAScript version
*/
function normalizeEcmaVersion(ecmaVersion) {
function normalizeEcmaVersion(parser, ecmaVersion) {
if ((parser[parserSymbol] || parser) === espree) {
if (ecmaVersion === "latest") {
return espree.latestEcmaVersion;
}
}

/*
* Calculate ECMAScript edition number from official year version starting with
Expand Down Expand Up @@ -521,12 +529,13 @@ function normalizeVerifyOptions(providedOptions, config) {

/**
* Combines the provided parserOptions with the options from environments
* @param {string} parserName The parser name which uses this options.
* @param {Parser} parser The parser which uses this options.
* @param {ParserOptions} providedOptions The provided 'parserOptions' key in a config
* @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
* @returns {ParserOptions} Resulting parser options after merge
*/
function resolveParserOptions(parserName, providedOptions, enabledEnvironments) {
function resolveParserOptions(parser, providedOptions, enabledEnvironments) {

const parserOptionsFromEnv = enabledEnvironments
.filter(env => env.parserOptions)
.reduce((parserOptions, env) => merge(parserOptions, env.parserOptions), {});
Expand All @@ -542,12 +551,7 @@ function resolveParserOptions(parserName, providedOptions, enabledEnvironments)
mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false });
}

/*
* TODO: @aladdin-add
* 1. for a 3rd-party parser, do not normalize parserOptions
* 2. for espree, no need to do this (espree will do it)
*/
mergedParserOptions.ecmaVersion = normalizeEcmaVersion(mergedParserOptions.ecmaVersion);
mergedParserOptions.ecmaVersion = normalizeEcmaVersion(parser, mergedParserOptions.ecmaVersion);

return mergedParserOptions;
}
Expand Down Expand Up @@ -606,7 +610,7 @@ function getRuleOptions(ruleConfig) {
*/
function analyzeScope(ast, parserOptions, visitorKeys) {
const ecmaFeatures = parserOptions.ecmaFeatures || {};
const ecmaVersion = parserOptions.ecmaVersion || 5;
const ecmaVersion = parserOptions.ecmaVersion || DEFAULT_ECMA_VERSION;

return eslintScope.analyze(ast, {
ignoreEval: true,
Expand Down Expand Up @@ -1123,7 +1127,7 @@ class Linter {
.map(envName => getEnv(slots, envName))
.filter(env => env);

const parserOptions = resolveParserOptions(parserName, config.parserOptions || {}, enabledEnvs);
const parserOptions = resolveParserOptions(parser, config.parserOptions || {}, enabledEnvs);
const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs);
const settings = config.settings || {};

Expand Down
6 changes: 6 additions & 0 deletions lib/rule-tester/rule-tester.js
Expand Up @@ -53,6 +53,7 @@ const
const ajv = require("../shared/ajv")({ strictDefaults: true });

const espreePath = require.resolve("espree");
const parserSymbol = Symbol.for("eslint.RuleTester.parser");

//------------------------------------------------------------------------------
// Typedefs
Expand Down Expand Up @@ -239,6 +240,7 @@ function defineStartEndAsError(objName, node) {
});
}


/**
* Define `start`/`end` properties of all nodes of the given AST as throwing error.
* @param {ASTNode} ast The root node to errorize `start`/`end` properties.
Expand All @@ -258,8 +260,10 @@ function defineStartEndAsErrorInTree(ast, visitorKeys) {
* @returns {Parser} Wrapped parser object.
*/
function wrapParser(parser) {

if (typeof parser.parseForESLint === "function") {
return {
[parserSymbol]: parser,
parseForESLint(...args) {
const ret = parser.parseForESLint(...args);

Expand All @@ -268,7 +272,9 @@ function wrapParser(parser) {
}
};
}

return {
[parserSymbol]: parser,
parse(...args) {
const ast = parser.parse(...args);

Expand Down
27 changes: 27 additions & 0 deletions tests/fixtures/parsers/empty-program-parser.js
@@ -0,0 +1,27 @@
"use strict";

exports.parse = function (text, parserOptions) {
return {
"type": "Program",
"start": 0,
"end": 0,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 0
}
},
"range": [
0,
0
],
"body": [],
"sourceType": "script",
"comments": [],
"tokens": []
};
};
52 changes: 52 additions & 0 deletions tests/lib/linter/linter.js
Expand Up @@ -11,6 +11,7 @@

const assert = require("chai").assert,
sinon = require("sinon"),
espree = require("espree"),
esprima = require("esprima"),
testParsers = require("../../fixtures/parsers/linter-test-parsers");

Expand Down Expand Up @@ -3492,6 +3493,57 @@ var a = "test2";
});

describe("ecmaVersion", () => {

it("should not support ES6 when no ecmaVersion provided", () => {
const messages = linter.verify("let x = 0;");

assert.strictEqual(messages.length, 1);
});

it("supports ECMAScript version 'latest'", () => {
const messages = linter.verify("let x = 5 ** 7;", {
parserOptions: { ecmaVersion: "latest" }
});

assert.strictEqual(messages.length, 0);
});

it("the 'latest' is equal to espree.lastEcmaVersion", () => {
let ecmaVersion = null;
const config = { rules: { "ecma-version": 2 }, parserOptions: { ecmaVersion: "latest" } };

linter.defineRule("ecma-version", context => ({
Program() {
ecmaVersion = context.parserOptions.ecmaVersion;
}
}));
linter.verify("", config);
assert.strictEqual(ecmaVersion, espree.latestEcmaVersion);
});

it("should pass normalized ecmaVersion to eslint-scope", () => {
let blockScope = null;

linter.defineRule("block-scope", context => ({
BlockStatement() {
blockScope = context.getScope();
}
}));

linter.verify("{}", {
rules: { "block-scope": 2 },
parserOptions: { ecmaVersion: "latest" }
});

assert.strictEqual(blockScope.type, "block");

linter.verify("{}", {
rules: { "block-scope": 2 },
parserOptions: {} // ecmaVersion defaults to 5
});
assert.strictEqual(blockScope.type, "global");
});

describe("it should properly parse let declaration when", () => {
it("the ECMAScript version number is 6", () => {
const messages = linter.verify("let x = 5;", {
Expand Down

0 comments on commit ed1da5d

Please sign in to comment.