Skip to content

Commit 01557ce

Browse files
authored
feat: Implement Language#normalizeLanguageOptions() (#19104)
* feat: Implement Language#normalizeLanguageOptions() fixes #19037 * Move normalizeOptions logic * Fix FlatConfigArray tests
1 parent 902e707 commit 01557ce

File tree

5 files changed

+147
-58
lines changed

5 files changed

+147
-58
lines changed

docs/src/extend/languages.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ The following optional members allow you to customize how ESLint interacts with
8787
* `visitorKeys` - visitor keys that are specific to the AST or CST. This is used to optimize traversal of the AST or CST inside of ESLint. While not required, it is strongly recommended, especially for AST or CST formats that deviate significantly from ESTree format.
8888
* `defaultLanguageOptions` - default `languageOptions` when the language is used. User-specified `languageOptions` are merged with this object when calculating the config for the file being linted.
8989
* `matchesSelectorClass(className, node, ancestry)` - allows you to specify selector classes, such as `:expression`, that match more than one node. This method is called whenever an [esquery](https://github.com/estools/esquery) selector contains a `:` followed by an identifier.
90+
* `normalizeLanguageOptions(languageOptions)` - takes a validated language options object and normalizes its values. This is helpful for backwards compatibility when language options properties change.
9091

9192
See [`JSONLanguage`](https://github.com/eslint/json/blob/main/src/languages/json-language.js) as an example of a basic `Language` class.
9293

lib/config/config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ class Config {
195195
throw new TypeError(`Key "languageOptions": ${error.message}`, { cause: error });
196196
}
197197

198+
// Normalize language options if necessary
199+
if (this.language.normalizeLanguageOptions) {
200+
this.languageOptions = this.language.normalizeLanguageOptions(this.languageOptions);
201+
}
202+
198203
// Check processor value
199204
if (processor) {
200205
this.processor = processor;

lib/languages/js/index.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const espree = require("espree");
1616
const eslintScope = require("eslint-scope");
1717
const evk = require("eslint-visitor-keys");
1818
const { validateLanguageOptions } = require("./validate-language-options");
19+
const { LATEST_ECMA_VERSION } = require("../../../conf/ecma-version");
1920

2021
//-----------------------------------------------------------------------------
2122
// Type Definitions
@@ -31,6 +32,7 @@ const { validateLanguageOptions } = require("./validate-language-options");
3132

3233
const debug = createDebug("eslint:languages:js");
3334
const DEFAULT_ECMA_VERSION = 5;
35+
const parserSymbol = Symbol.for("eslint.RuleTester.parser");
3436

3537
/**
3638
* Analyze scope of the given AST.
@@ -55,6 +57,47 @@ function analyzeScope(ast, languageOptions, visitorKeys) {
5557
});
5658
}
5759

60+
/**
61+
* Determines if a given object is Espree.
62+
* @param {Object} parser The parser to check.
63+
* @returns {boolean} True if the parser is Espree or false if not.
64+
*/
65+
function isEspree(parser) {
66+
return !!(parser === espree || parser[parserSymbol] === espree);
67+
}
68+
69+
/**
70+
* Normalize ECMAScript version from the initial config into languageOptions (year)
71+
* format.
72+
* @param {any} [ecmaVersion] ECMAScript version from the initial config
73+
* @returns {number} normalized ECMAScript version
74+
*/
75+
function normalizeEcmaVersionForLanguageOptions(ecmaVersion) {
76+
77+
switch (ecmaVersion) {
78+
case 3:
79+
return 3;
80+
81+
// void 0 = no ecmaVersion specified so use the default
82+
case 5:
83+
case void 0:
84+
return 5;
85+
86+
default:
87+
if (typeof ecmaVersion === "number") {
88+
return ecmaVersion >= 2015 ? ecmaVersion : ecmaVersion + 2009;
89+
}
90+
}
91+
92+
/*
93+
* We default to the latest supported ecmaVersion for everything else.
94+
* Remember, this is for languageOptions.ecmaVersion, which sets the version
95+
* that is used for a number of processes inside of ESLint. It's normally
96+
* safe to assume people want the latest unless otherwise specified.
97+
*/
98+
return LATEST_ECMA_VERSION;
99+
}
100+
58101
//-----------------------------------------------------------------------------
59102
// Exports
60103
//-----------------------------------------------------------------------------
@@ -79,6 +122,39 @@ module.exports = {
79122

80123
validateLanguageOptions,
81124

125+
/**
126+
* Normalizes the language options.
127+
* @param {Object} languageOptions The language options to normalize.
128+
* @returns {Object} The normalized language options.
129+
*/
130+
normalizeLanguageOptions(languageOptions) {
131+
132+
languageOptions.ecmaVersion = normalizeEcmaVersionForLanguageOptions(
133+
languageOptions.ecmaVersion
134+
);
135+
136+
// Espree expects this information to be passed in
137+
if (isEspree(languageOptions.parser)) {
138+
const parserOptions = languageOptions.parserOptions;
139+
140+
if (languageOptions.sourceType) {
141+
142+
parserOptions.sourceType = languageOptions.sourceType;
143+
144+
if (
145+
parserOptions.sourceType === "module" &&
146+
parserOptions.ecmaFeatures &&
147+
parserOptions.ecmaFeatures.globalReturn
148+
) {
149+
parserOptions.ecmaFeatures.globalReturn = false;
150+
}
151+
}
152+
}
153+
154+
return languageOptions;
155+
156+
},
157+
82158
/**
83159
* Determines if a given node matches a given selector class.
84160
* @param {string} className The class name to check.

lib/linter/linter.js

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1655,34 +1655,8 @@ class Linter {
16551655

16561656
const slots = internalSlotsMap.get(this);
16571657
const config = providedConfig || {};
1658+
const { settings = {}, languageOptions } = config;
16581659
const options = normalizeVerifyOptions(providedOptions, config);
1659-
const languageOptions = config.languageOptions;
1660-
1661-
if (config.language === jslang) {
1662-
languageOptions.ecmaVersion = normalizeEcmaVersionForLanguageOptions(
1663-
languageOptions.ecmaVersion
1664-
);
1665-
1666-
// Espree expects this information to be passed in
1667-
if (isEspree(languageOptions.parser)) {
1668-
const parserOptions = languageOptions.parserOptions;
1669-
1670-
if (languageOptions.sourceType) {
1671-
1672-
parserOptions.sourceType = languageOptions.sourceType;
1673-
1674-
if (
1675-
parserOptions.sourceType === "module" &&
1676-
parserOptions.ecmaFeatures &&
1677-
parserOptions.ecmaFeatures.globalReturn
1678-
) {
1679-
parserOptions.ecmaFeatures.globalReturn = false;
1680-
}
1681-
}
1682-
}
1683-
}
1684-
1685-
const settings = config.settings || {};
16861660

16871661
if (!slots.lastSourceCode) {
16881662
let t;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy