From 892e6e58c5a07a549d3104de3b6b5879797dc97f Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 15 Mar 2023 18:06:37 -0700 Subject: [PATCH] feat: languageOptions.parser must be an object. (#16985) * feat: languageOptions.parser must be an object. Changes languageOptions.parser to disallow string values. Fixes #16930 * Add parser:null check * Remove unnecessary check --- .../use/configure/configuration-files-new.md | 6 +- lib/config/default-config.js | 5 +- lib/config/flat-config-array.js | 12 +--- lib/config/flat-config-schema.js | 22 ++---- tests/lib/config/flat-config-array.js | 70 +++++-------------- tests/lib/eslint/flat-eslint.js | 2 +- tests/lib/linter/linter.js | 28 ++------ 7 files changed, 31 insertions(+), 114 deletions(-) diff --git a/docs/src/use/configure/configuration-files-new.md b/docs/src/use/configure/configuration-files-new.md index 02318bceb76..a57bbf0f235 100644 --- a/docs/src/use/configure/configuration-files-new.md +++ b/docs/src/use/configure/configuration-files-new.md @@ -41,8 +41,8 @@ Each configuration object contains all of the information ESLint needs to execut * `ecmaVersion` - The version of ECMAScript to support. May be any year (i.e., `2022`) or version (i.e., `5`). Set to `"latest"` for the most recent supported version. (default: `"latest"`) * `sourceType` - The type of JavaScript source code. Possible values are `"script"` for traditional script files, `"module"` for ECMAScript modules (ESM), and `"commonjs"` for CommonJS files. (default: `"module"` for `.js` and `.mjs` files; `"commonjs"` for `.cjs` files) * `globals` - An object specifying additional objects that should be added to the global scope during linting. - * `parser` - Either an object containing a `parse()` method or a string indicating the name of a parser inside of a plugin (i.e., `"pluginName/parserName"`). (default: `"@/espree"`) - * `parserOptions` - An object specifying additional options that are passed directly to the `parser()` method on the parser. The available options are parser-dependent. + * `parser` - An object containing a `parse()` method or a `parseForESLint()` method. (default: [`espree`](https://github.com/eslint/espree)) + * `parserOptions` - An object specifying additional options that are passed directly to the `parse()` or `parseForESLint()` method on the parser. The available options are parser-dependent. * `linterOptions` - An object containing settings related to the linting process. * `noInlineConfig` - A Boolean value indicating if inline configuration is allowed. * `reportUnusedDisableDirectives` - A Boolean value indicating if unused disable directives should be tracked and reported. @@ -251,7 +251,7 @@ export default [ #### Configuring a custom parser and its options -In many cases, you can use the default parser that ESLint ships with for parsing your JavaScript code. You can optionally override the default parser by using the `parser` property. The `parser` property can be either a string in the format `"pluginName/parserName"` (indicating to retrieve the parser from a plugin) or an object containing either a `parse()` method or a `parseForESLint()` method. For example, you can use the [`@babel/eslint-parser`](https://www.npmjs.com/package/@babel/eslint-parser) package to allow ESLint to parse experimental syntax: +In many cases, you can use the default parser that ESLint ships with for parsing your JavaScript code. You can optionally override the default parser by using the `parser` property. The `parser` property must be an object containing either a `parse()` method or a `parseForESLint()` method. For example, you can use the [`@babel/eslint-parser`](https://www.npmjs.com/package/@babel/eslint-parser) package to allow ESLint to parse experimental syntax: ```js import babelParser from "@babel/eslint-parser"; diff --git a/lib/config/default-config.js b/lib/config/default-config.js index aa0dfb2a522..99ea7b9f84e 100644 --- a/lib/config/default-config.js +++ b/lib/config/default-config.js @@ -19,9 +19,6 @@ exports.defaultConfig = [ { plugins: { "@": { - parsers: { - espree: require("espree") - }, /* * Because we try to delay loading rules until absolutely @@ -43,7 +40,7 @@ exports.defaultConfig = [ languageOptions: { sourceType: "module", ecmaVersion: "latest", - parser: "@/espree", + parser: require("espree"), parserOptions: {} } }, diff --git a/lib/config/flat-config-array.js b/lib/config/flat-config-array.js index 78b4ef44bac..4e82391fc7d 100644 --- a/lib/config/flat-config-array.js +++ b/lib/config/flat-config-array.js @@ -192,17 +192,7 @@ class FlatConfigArray extends ConfigArray { if (languageOptions && languageOptions.parser) { const { parser } = languageOptions; - if (typeof parser === "string") { - const { pluginName, objectName: localParserName } = splitPluginIdentifier(parser); - - parserName = parser; - - if (!plugins || !plugins[pluginName] || !plugins[pluginName].parsers || !plugins[pluginName].parsers[localParserName]) { - throw new TypeError(`Key "parser": Could not find "${localParserName}" in plugin "${pluginName}".`); - } - - languageOptions.parser = plugins[pluginName].parsers[localParserName]; - } else if (typeof parser === "object") { + if (typeof parser === "object") { parserName = getObjectId(parser); if (!parserName) { diff --git a/lib/config/flat-config-schema.js b/lib/config/flat-config-schema.js index cb8e7961add..bb6e9f899ac 100644 --- a/lib/config/flat-config-schema.js +++ b/lib/config/flat-config-schema.js @@ -179,18 +179,6 @@ function assertIsObject(value) { } } -/** - * Validates that a value is an object or a string. - * @param {any} value The value to check. - * @returns {void} - * @throws {TypeError} If the value isn't an object or a string. - */ -function assertIsObjectOrString(value) { - if ((!value || typeof value !== "object") && typeof value !== "string") { - throw new TypeError("Expected an object or string."); - } -} - //----------------------------------------------------------------------------- // Low-Level Schemas //----------------------------------------------------------------------------- @@ -242,15 +230,13 @@ const globalsSchema = { const parserSchema = { merge: "replace", validate(value) { - assertIsObjectOrString(value); - if (typeof value === "object" && typeof value.parse !== "function" && typeof value.parseForESLint !== "function") { - throw new TypeError("Expected object to have a parse() or parseForESLint() method."); + if (!value || typeof value !== "object" || + (typeof value.parse !== "function" && typeof value.parseForESLint !== "function") + ) { + throw new TypeError("Expected object with parse() or parseForESLint() method."); } - if (typeof value === "string") { - assertIsPluginMemberName(value); - } } }; diff --git a/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js index c06e4da7798..2b8e8f64ec3 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -16,6 +16,7 @@ const { recommended: recommendedConfig } = require("@eslint/js").configs; const stringify = require("json-stable-stringify-without-jsonify"); +const espree = require("espree"); //----------------------------------------------------------------------------- // Helpers @@ -190,6 +191,7 @@ describe("FlatConfigArray", () => { }); describe("Serialization of configs", () => { + it("should convert config into normalized JSON object", () => { const configs = new FlatConfigArray([{ @@ -207,7 +209,7 @@ describe("FlatConfigArray", () => { languageOptions: { ecmaVersion: "latest", sourceType: "module", - parser: "@/espree", + parser: `espree@${espree.version}`, parserOptions: {} }, processor: void 0 @@ -463,7 +465,7 @@ describe("FlatConfigArray", () => { assert.deepStrictEqual(config.toJSON(), { languageOptions: { ecmaVersion: "latest", - parser: "@/espree", + parser: `espree@${espree.version}`, parserOptions: {}, sourceType: "module" }, @@ -490,7 +492,7 @@ describe("FlatConfigArray", () => { assert.deepStrictEqual(config.toJSON(), { languageOptions: { ecmaVersion: "latest", - parser: "@/espree", + parser: `espree@${espree.version}`, parserOptions: {}, sourceType: "module" }, @@ -521,7 +523,7 @@ describe("FlatConfigArray", () => { assert.deepStrictEqual(config.toJSON(), { languageOptions: { ecmaVersion: "latest", - parser: "@/espree", + parser: `espree@${espree.version}`, parserOptions: {}, sourceType: "module" }, @@ -550,7 +552,7 @@ describe("FlatConfigArray", () => { assert.deepStrictEqual(config.toJSON(), { languageOptions: { ecmaVersion: "latest", - parser: "@/espree", + parser: `espree@${espree.version}`, parserOptions: {}, sourceType: "module" }, @@ -1340,21 +1342,21 @@ describe("FlatConfigArray", () => { parser: true } } - ], "Expected an object or string."); + ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); }); - it("should error when an unexpected value is found", async () => { + it("should error when a null is found", async () => { await assertInvalidConfig([ { languageOptions: { - parser: "true" + parser: null } } - ], /Expected string in the form "pluginName\/objectName"/u); + ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); }); - it("should error when a plugin parser can't be found", async () => { + it("should error when a parser is a string", async () => { await assertInvalidConfig([ { @@ -1362,7 +1364,7 @@ describe("FlatConfigArray", () => { parser: "foo/bar" } } - ], "Key \"parser\": Could not find \"bar\" in plugin \"foo\"."); + ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); }); it("should error when a value doesn't have a parse() method", async () => { @@ -1373,7 +1375,7 @@ describe("FlatConfigArray", () => { parser: {} } } - ], "Expected object to have a parse() or parseForESLint() method."); + ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); }); it("should merge two objects when second object has overrides", () => { @@ -1388,24 +1390,12 @@ describe("FlatConfigArray", () => { } }, { - plugins: { - "@foo/baz": { - parsers: { - bar: stubParser - } - } - }, languageOptions: { - parser: "@foo/baz/bar" + parser: stubParser } } ], { plugins: { - "@foo/baz": { - parsers: { - bar: stubParser - } - }, ...baseConfig.plugins }, languageOptions: { @@ -1420,27 +1410,14 @@ describe("FlatConfigArray", () => { return assertMergedResult([ { - plugins: { - foo: { - parsers: { - bar: stubParser - } - } - }, - languageOptions: { - parser: "foo/bar" + parser: stubParser } }, { } ], { plugins: { - foo: { - parsers: { - bar: stubParser - } - }, ...baseConfig.plugins }, @@ -1460,25 +1437,12 @@ describe("FlatConfigArray", () => { { }, { - plugins: { - foo: { - parsers: { - bar: stubParser - } - } - }, - languageOptions: { - parser: "foo/bar" + parser: stubParser } } ], { plugins: { - foo: { - parsers: { - bar: stubParser - } - }, ...baseConfig.plugins }, diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index 187b09d7baa..d79e4f92d4b 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -773,7 +773,7 @@ describe("FlatESLint", () => { overrideConfigFile: true }); - await assert.rejects(async () => await eslint.lintFiles(["lib/cli.js"]), /Expected string in the form "pluginName\/objectName" but found "test11"/u); + await assert.rejects(async () => await eslint.lintFiles(["lib/cli.js"]), /Expected object with parse\(\) or parseForESLint\(\) method/u); }); it("should report zero messages when given a directory with a .js2 file", async () => { diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index 7a1ba3779c0..0ece277dc9a 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -7878,15 +7878,8 @@ describe("Linter with FlatConfigArray", () => { }; const config = { - plugins: { - test: { - parsers: { - "test-parser": parser - } - } - }, languageOptions: { - parser: "test/test-parser" + parser } }; @@ -7925,15 +7918,8 @@ describe("Linter with FlatConfigArray", () => { it("should use parseForESLint() in custom parser when custom parser is specified", () => { const config = { - plugins: { - test: { - parsers: { - "enhanced-parser": testParsers.enhancedParser - } - } - }, languageOptions: { - parser: "test/enhanced-parser" + parser: testParsers.enhancedParser } }; @@ -7949,9 +7935,6 @@ describe("Linter with FlatConfigArray", () => { const config = { plugins: { test: { - parsers: { - "enhanced-parser": testParsers.enhancedParser - }, rules: { "test-service-rule": { create: context => ({ @@ -7967,7 +7950,7 @@ describe("Linter with FlatConfigArray", () => { } }, languageOptions: { - parser: "test/enhanced-parser" + parser: testParsers.enhancedParser }, rules: { "test/test-service-rule": 2 @@ -7988,9 +7971,6 @@ describe("Linter with FlatConfigArray", () => { const config = { plugins: { test: { - parsers: { - "enhanced-parser": testParsers.enhancedParser - }, rules: { "test-service-rule": { create: context => ({ @@ -8006,7 +7986,7 @@ describe("Linter with FlatConfigArray", () => { } }, languageOptions: { - parser: "test/enhanced-parser" + parser: testParsers.enhancedParser }, rules: { "test/test-service-rule": 2