Skip to content

Commit

Permalink
feat: languageOptions.parser must be an object. (#16985)
Browse files Browse the repository at this point in the history
* feat: languageOptions.parser must be an object.

Changes languageOptions.parser to disallow string values.

Fixes #16930

* Add parser:null check

* Remove unnecessary check
  • Loading branch information
nzakas committed Mar 16, 2023
1 parent ada6a3e commit 892e6e5
Show file tree
Hide file tree
Showing 7 changed files with 31 additions and 114 deletions.
6 changes: 3 additions & 3 deletions docs/src/use/configure/configuration-files-new.md
Expand Up @@ -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.
Expand Down Expand Up @@ -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";
Expand Down
5 changes: 1 addition & 4 deletions lib/config/default-config.js
Expand Up @@ -19,9 +19,6 @@ exports.defaultConfig = [
{
plugins: {
"@": {
parsers: {
espree: require("espree")
},

/*
* Because we try to delay loading rules until absolutely
Expand All @@ -43,7 +40,7 @@ exports.defaultConfig = [
languageOptions: {
sourceType: "module",
ecmaVersion: "latest",
parser: "@/espree",
parser: require("espree"),
parserOptions: {}
}
},
Expand Down
12 changes: 1 addition & 11 deletions lib/config/flat-config-array.js
Expand Up @@ -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) {
Expand Down
22 changes: 4 additions & 18 deletions lib/config/flat-config-schema.js
Expand Up @@ -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
//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -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);
}
}
};

Expand Down
70 changes: 17 additions & 53 deletions tests/lib/config/flat-config-array.js
Expand Up @@ -16,6 +16,7 @@ const {
recommended: recommendedConfig
} = require("@eslint/js").configs;
const stringify = require("json-stable-stringify-without-jsonify");
const espree = require("espree");

//-----------------------------------------------------------------------------
// Helpers
Expand Down Expand Up @@ -190,6 +191,7 @@ describe("FlatConfigArray", () => {
});

describe("Serialization of configs", () => {

it("should convert config into normalized JSON object", () => {

const configs = new FlatConfigArray([{
Expand All @@ -207,7 +209,7 @@ describe("FlatConfigArray", () => {
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
parser: "@/espree",
parser: `espree@${espree.version}`,
parserOptions: {}
},
processor: void 0
Expand Down Expand Up @@ -463,7 +465,7 @@ describe("FlatConfigArray", () => {
assert.deepStrictEqual(config.toJSON(), {
languageOptions: {
ecmaVersion: "latest",
parser: "@/espree",
parser: `espree@${espree.version}`,
parserOptions: {},
sourceType: "module"
},
Expand All @@ -490,7 +492,7 @@ describe("FlatConfigArray", () => {
assert.deepStrictEqual(config.toJSON(), {
languageOptions: {
ecmaVersion: "latest",
parser: "@/espree",
parser: `espree@${espree.version}`,
parserOptions: {},
sourceType: "module"
},
Expand Down Expand Up @@ -521,7 +523,7 @@ describe("FlatConfigArray", () => {
assert.deepStrictEqual(config.toJSON(), {
languageOptions: {
ecmaVersion: "latest",
parser: "@/espree",
parser: `espree@${espree.version}`,
parserOptions: {},
sourceType: "module"
},
Expand Down Expand Up @@ -550,7 +552,7 @@ describe("FlatConfigArray", () => {
assert.deepStrictEqual(config.toJSON(), {
languageOptions: {
ecmaVersion: "latest",
parser: "@/espree",
parser: `espree@${espree.version}`,
parserOptions: {},
sourceType: "module"
},
Expand Down Expand Up @@ -1340,29 +1342,29 @@ 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([
{
languageOptions: {
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 () => {
Expand All @@ -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", () => {
Expand All @@ -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: {
Expand All @@ -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
},

Expand All @@ -1460,25 +1437,12 @@ describe("FlatConfigArray", () => {
{
},
{
plugins: {
foo: {
parsers: {
bar: stubParser
}
}
},

languageOptions: {
parser: "foo/bar"
parser: stubParser
}
}
], {
plugins: {
foo: {
parsers: {
bar: stubParser
}
},
...baseConfig.plugins
},

Expand Down
2 changes: 1 addition & 1 deletion tests/lib/eslint/flat-eslint.js
Expand Up @@ -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 () => {
Expand Down
28 changes: 4 additions & 24 deletions tests/lib/linter/linter.js
Expand Up @@ -7878,15 +7878,8 @@ describe("Linter with FlatConfigArray", () => {
};

const config = {
plugins: {
test: {
parsers: {
"test-parser": parser
}
}
},
languageOptions: {
parser: "test/test-parser"
parser
}
};

Expand Down Expand Up @@ -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
}
};

Expand All @@ -7949,9 +7935,6 @@ describe("Linter with FlatConfigArray", () => {
const config = {
plugins: {
test: {
parsers: {
"enhanced-parser": testParsers.enhancedParser
},
rules: {
"test-service-rule": {
create: context => ({
Expand All @@ -7967,7 +7950,7 @@ describe("Linter with FlatConfigArray", () => {
}
},
languageOptions: {
parser: "test/enhanced-parser"
parser: testParsers.enhancedParser
},
rules: {
"test/test-service-rule": 2
Expand All @@ -7988,9 +7971,6 @@ describe("Linter with FlatConfigArray", () => {
const config = {
plugins: {
test: {
parsers: {
"enhanced-parser": testParsers.enhancedParser
},
rules: {
"test-service-rule": {
create: context => ({
Expand All @@ -8006,7 +7986,7 @@ describe("Linter with FlatConfigArray", () => {
}
},
languageOptions: {
parser: "test/enhanced-parser"
parser: testParsers.enhancedParser
},
rules: {
"test/test-service-rule": 2
Expand Down

0 comments on commit 892e6e5

Please sign in to comment.