Skip to content

Commit ed1da5d

Browse files
authored
Update: ecmaVersion allows "latest" (#14720)
* 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
1 parent 104c0b5 commit ed1da5d

6 files changed

Lines changed: 302 additions & 15 deletions

File tree

docs/user-guide/configuring/language-options.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 glo
187187

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

190-
* `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.
190+
* `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.
191191
* `sourceType` - set to `"script"` (default) or `"module"` if your code is in ECMAScript modules.
192192
* `ecmaFeatures` - an object indicating which additional language features you'd like to use:
193193
* `globalReturn` - allow `return` statements in the global scope
@@ -199,7 +199,7 @@ Here's an example `.eslintrc.json` file:
199199
```json
200200
{
201201
"parserOptions": {
202-
"ecmaVersion": 6,
202+
"ecmaVersion": "latest",
203203
"sourceType": "module",
204204
"ecmaFeatures": {
205205
"jsx": true

lib/linter/linter.js

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ const
3737
const debug = require("debug")("eslint:linter");
3838
const MAX_AUTOFIX_PASSES = 10;
3939
const DEFAULT_PARSER_NAME = "espree";
40+
const DEFAULT_ECMA_VERSION = 5;
4041
const commentParser = new ConfigCommentParser();
4142
const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } };
43+
const parserSymbol = Symbol.for("eslint.RuleTester.parser");
4244

4345
//------------------------------------------------------------------------------
4446
// Typedefs
@@ -432,10 +434,16 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
432434

433435
/**
434436
* Normalize ECMAScript version from the initial config
435-
* @param {number} ecmaVersion ECMAScript version from the initial config
437+
* @param {Parser} parser The parser which uses this options.
438+
* @param {number} ecmaVersion ECMAScript version from the initial config
436439
* @returns {number} normalized ECMAScript version
437440
*/
438-
function normalizeEcmaVersion(ecmaVersion) {
441+
function normalizeEcmaVersion(parser, ecmaVersion) {
442+
if ((parser[parserSymbol] || parser) === espree) {
443+
if (ecmaVersion === "latest") {
444+
return espree.latestEcmaVersion;
445+
}
446+
}
439447

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

522530
/**
523531
* Combines the provided parserOptions with the options from environments
524-
* @param {string} parserName The parser name which uses this options.
532+
* @param {Parser} parser The parser which uses this options.
525533
* @param {ParserOptions} providedOptions The provided 'parserOptions' key in a config
526534
* @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
527535
* @returns {ParserOptions} Resulting parser options after merge
528536
*/
529-
function resolveParserOptions(parserName, providedOptions, enabledEnvironments) {
537+
function resolveParserOptions(parser, providedOptions, enabledEnvironments) {
538+
530539
const parserOptionsFromEnv = enabledEnvironments
531540
.filter(env => env.parserOptions)
532541
.reduce((parserOptions, env) => merge(parserOptions, env.parserOptions), {});
@@ -542,12 +551,7 @@ function resolveParserOptions(parserName, providedOptions, enabledEnvironments)
542551
mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false });
543552
}
544553

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

552556
return mergedParserOptions;
553557
}
@@ -606,7 +610,7 @@ function getRuleOptions(ruleConfig) {
606610
*/
607611
function analyzeScope(ast, parserOptions, visitorKeys) {
608612
const ecmaFeatures = parserOptions.ecmaFeatures || {};
609-
const ecmaVersion = parserOptions.ecmaVersion || 5;
613+
const ecmaVersion = parserOptions.ecmaVersion || DEFAULT_ECMA_VERSION;
610614

611615
return eslintScope.analyze(ast, {
612616
ignoreEval: true,
@@ -1123,7 +1127,7 @@ class Linter {
11231127
.map(envName => getEnv(slots, envName))
11241128
.filter(env => env);
11251129

1126-
const parserOptions = resolveParserOptions(parserName, config.parserOptions || {}, enabledEnvs);
1130+
const parserOptions = resolveParserOptions(parser, config.parserOptions || {}, enabledEnvs);
11271131
const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs);
11281132
const settings = config.settings || {};
11291133

lib/rule-tester/rule-tester.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const
5353
const ajv = require("../shared/ajv")({ strictDefaults: true });
5454

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

5758
//------------------------------------------------------------------------------
5859
// Typedefs
@@ -239,6 +240,7 @@ function defineStartEndAsError(objName, node) {
239240
});
240241
}
241242

243+
242244
/**
243245
* Define `start`/`end` properties of all nodes of the given AST as throwing error.
244246
* @param {ASTNode} ast The root node to errorize `start`/`end` properties.
@@ -258,8 +260,10 @@ function defineStartEndAsErrorInTree(ast, visitorKeys) {
258260
* @returns {Parser} Wrapped parser object.
259261
*/
260262
function wrapParser(parser) {
263+
261264
if (typeof parser.parseForESLint === "function") {
262265
return {
266+
[parserSymbol]: parser,
263267
parseForESLint(...args) {
264268
const ret = parser.parseForESLint(...args);
265269

@@ -268,7 +272,9 @@ function wrapParser(parser) {
268272
}
269273
};
270274
}
275+
271276
return {
277+
[parserSymbol]: parser,
272278
parse(...args) {
273279
const ast = parser.parse(...args);
274280

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"use strict";
2+
3+
exports.parse = function (text, parserOptions) {
4+
return {
5+
"type": "Program",
6+
"start": 0,
7+
"end": 0,
8+
"loc": {
9+
"start": {
10+
"line": 1,
11+
"column": 0
12+
},
13+
"end": {
14+
"line": 1,
15+
"column": 0
16+
}
17+
},
18+
"range": [
19+
0,
20+
0
21+
],
22+
"body": [],
23+
"sourceType": "script",
24+
"comments": [],
25+
"tokens": []
26+
};
27+
};

tests/lib/linter/linter.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
const assert = require("chai").assert,
1313
sinon = require("sinon"),
14+
espree = require("espree"),
1415
esprima = require("esprima"),
1516
testParsers = require("../../fixtures/parsers/linter-test-parsers");
1617

@@ -3492,6 +3493,57 @@ var a = "test2";
34923493
});
34933494

34943495
describe("ecmaVersion", () => {
3496+
3497+
it("should not support ES6 when no ecmaVersion provided", () => {
3498+
const messages = linter.verify("let x = 0;");
3499+
3500+
assert.strictEqual(messages.length, 1);
3501+
});
3502+
3503+
it("supports ECMAScript version 'latest'", () => {
3504+
const messages = linter.verify("let x = 5 ** 7;", {
3505+
parserOptions: { ecmaVersion: "latest" }
3506+
});
3507+
3508+
assert.strictEqual(messages.length, 0);
3509+
});
3510+
3511+
it("the 'latest' is equal to espree.lastEcmaVersion", () => {
3512+
let ecmaVersion = null;
3513+
const config = { rules: { "ecma-version": 2 }, parserOptions: { ecmaVersion: "latest" } };
3514+
3515+
linter.defineRule("ecma-version", context => ({
3516+
Program() {
3517+
ecmaVersion = context.parserOptions.ecmaVersion;
3518+
}
3519+
}));
3520+
linter.verify("", config);
3521+
assert.strictEqual(ecmaVersion, espree.latestEcmaVersion);
3522+
});
3523+
3524+
it("should pass normalized ecmaVersion to eslint-scope", () => {
3525+
let blockScope = null;
3526+
3527+
linter.defineRule("block-scope", context => ({
3528+
BlockStatement() {
3529+
blockScope = context.getScope();
3530+
}
3531+
}));
3532+
3533+
linter.verify("{}", {
3534+
rules: { "block-scope": 2 },
3535+
parserOptions: { ecmaVersion: "latest" }
3536+
});
3537+
3538+
assert.strictEqual(blockScope.type, "block");
3539+
3540+
linter.verify("{}", {
3541+
rules: { "block-scope": 2 },
3542+
parserOptions: {} // ecmaVersion defaults to 5
3543+
});
3544+
assert.strictEqual(blockScope.type, "global");
3545+
});
3546+
34953547
describe("it should properly parse let declaration when", () => {
34963548
it("the ECMAScript version number is 6", () => {
34973549
const messages = linter.verify("let x = 5;", {

0 commit comments

Comments
 (0)