Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update: ecmaVersion allows "latest" #14720

Merged
merged 7 commits into from Jun 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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