From 14919efcc3e323fd3fe0b2432cc37986ddcd94a9 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 27 Feb 2023 14:01:33 -0800 Subject: [PATCH 1/3] feat: Serialize parsers/processors in flat config This implements a check for a 'meta' key on parsers and processors that contains information to help flat config serialize these objects for use with caching and also --print-config on the command line. Fixes #16875 --- lib/config/flat-config-array.js | 49 +++++++- tests/lib/config/flat-config-array.js | 170 +++++++++++++++++++++++++- 2 files changed, 210 insertions(+), 9 deletions(-) diff --git a/lib/config/flat-config-array.js b/lib/config/flat-config-array.js index 24b456da57c..d46ffc9a00a 100644 --- a/lib/config/flat-config-array.js +++ b/lib/config/flat-config-array.js @@ -36,6 +36,29 @@ function splitPluginIdentifier(identifier) { }; } +/** + * Returns the name of an object in the config by reading its `meta` key. + * @param {Object} object The object to check. + * @returns {string?} The name of the object if found or `null` if there + * is no name. + */ +function getObjectId(object) { + + if (!object.meta) { + return null; + } + + if (!object.meta.name) { + return null; + } + + if (object.meta.version) { + return `${object.meta.name}@${object.meta.version}`; + } + + return object.meta.name; +} + const originalBaseConfig = Symbol("originalBaseConfig"); //----------------------------------------------------------------------------- @@ -145,16 +168,25 @@ class FlatConfigArray extends ConfigArray { // Check parser value if (languageOptions && languageOptions.parser) { - if (typeof languageOptions.parser === "string") { - const { pluginName, objectName: localParserName } = splitPluginIdentifier(languageOptions.parser); + const { parser } = languageOptions; + + if (typeof parser === "string") { + const { pluginName, objectName: localParserName } = splitPluginIdentifier(parser); - parserName = languageOptions.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" && parser.meta) { + parserName = getObjectId(parser); + + if (!parserName) { + invalidParser = true; + } + } else { invalidParser = true; } @@ -172,6 +204,13 @@ class FlatConfigArray extends ConfigArray { } config.processor = plugins[pluginName].processors[localProcessorName]; + } else if (typeof processor === "object" && processor.meta) { + processorName = getObjectId(processor); + + if (!processorName) { + invalidProcessor = true; + } + } else { invalidProcessor = true; } @@ -185,11 +224,11 @@ class FlatConfigArray extends ConfigArray { value: function() { if (invalidParser) { - throw new Error("Caching is not supported when parser is an object."); + throw new Error("Could not serialize parser object (missing 'meta' object)."); } if (invalidProcessor) { - throw new Error("Caching is not supported when processor is an object."); + throw new Error("Could not serialize processor object (missing 'meta' object)."); } return { diff --git a/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js index 0b284f61f07..2c1c24658a2 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -217,7 +217,7 @@ describe("FlatConfigArray", () => { assert.strictEqual(stringify(actual), stringify(expected)); }); - it("should throw an error when config with parser object is normalized", () => { + it("should throw an error when config with unnamed parser object is normalized", () => { const configs = new FlatConfigArray([{ languageOptions: { @@ -233,11 +233,93 @@ describe("FlatConfigArray", () => { assert.throws(() => { config.toJSON(); - }, /Caching is not supported/u); + }, /Could not serialize parser/u); }); - it("should throw an error when config with processor object is normalized", () => { + it("should throw an error when config with unnamed parser object with empty meta object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: {}, + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Could not serialize parser/u); + + }); + + it("should not throw an error when config with named parser object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: { + name: "custom-parser" + }, + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "custom-parser", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: void 0 + }); + + }); + + it("should not throw an error when config with named and versioned parser object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: { + name: "custom-parser", + version: "0.1.0" + }, + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "custom-parser@0.1.0", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: void 0 + }); + + }); + + it("should throw an error when config with unnamed processor object is normalized", () => { const configs = new FlatConfigArray([{ processor: { @@ -252,10 +334,90 @@ describe("FlatConfigArray", () => { assert.throws(() => { config.toJSON(); - }, /Caching is not supported/u); + }, /Could not serialize processor/u); }); + it("should throw an error when config with processor object with empty meta object is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + meta: {}, + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Could not serialize processor/u); + + }); + + + it("should not throw an error when config with named processor object is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + meta: { + name: "custom-processor" + }, + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "@/espree", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: "custom-processor" + }); + + }); + + it("should not throw an error when config with named and versioned processor object is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + meta: { + name: "custom-processor", + version: "1.2.3" + }, + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "@/espree", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: "custom-processor@1.2.3" + }); + + }); }); From b85abbd31c982e35e3b29b9b2e0ae24865010a8d Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 28 Feb 2023 10:37:54 -0800 Subject: [PATCH 2/3] Add support for non-meta properties --- lib/config/flat-config-array.js | 30 +++++-- tests/lib/config/flat-config-array.js | 116 ++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 9 deletions(-) diff --git a/lib/config/flat-config-array.js b/lib/config/flat-config-array.js index d46ffc9a00a..514bea9b91d 100644 --- a/lib/config/flat-config-array.js +++ b/lib/config/flat-config-array.js @@ -44,19 +44,31 @@ function splitPluginIdentifier(identifier) { */ function getObjectId(object) { - if (!object.meta) { - return null; + // first check old-style name + let name = object.name; + + if (!name) { + + if (!object.meta) { + return null; + } + + name = object.meta.name; } - if (!object.meta.name) { - return null; + // now check for old-style version + let version = object.version; + + if (!version) { + version = object.meta && object.meta.version; } - if (object.meta.version) { - return `${object.meta.name}@${object.meta.version}`; + // if there's a version then append that + if (version) { + return `${name}@${version}`; } - return object.meta.name; + return name; } const originalBaseConfig = Symbol("originalBaseConfig"); @@ -180,7 +192,7 @@ class FlatConfigArray extends ConfigArray { } languageOptions.parser = plugins[pluginName].parsers[localParserName]; - } else if (typeof parser === "object" && parser.meta) { + } else if (typeof parser === "object") { parserName = getObjectId(parser); if (!parserName) { @@ -204,7 +216,7 @@ class FlatConfigArray extends ConfigArray { } config.processor = plugins[pluginName].processors[localProcessorName]; - } else if (typeof processor === "object" && processor.meta) { + } else if (typeof processor === "object") { processorName = getObjectId(processor); if (!processorName) { diff --git a/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js index 2c1c24658a2..9a7dce5a670 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -319,6 +319,66 @@ describe("FlatConfigArray", () => { }); + it("should not throw an error when config with meta-named and versioned parser object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: { + name: "custom-parser" + }, + version: "0.1.0", + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "custom-parser@0.1.0", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: void 0 + }); + + }); + + it("should not throw an error when config with named and versioned parser object outside of meta object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + name: "custom-parser", + version: "0.1.0", + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "custom-parser@0.1.0", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: void 0 + }); + + }); + it("should throw an error when config with unnamed processor object is normalized", () => { const configs = new FlatConfigArray([{ @@ -388,6 +448,33 @@ describe("FlatConfigArray", () => { }); + it("should not throw an error when config with named processor object without meta is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + name: "custom-processor", + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "@/espree", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: "custom-processor" + }); + + }); + it("should not throw an error when config with named and versioned processor object is normalized", () => { const configs = new FlatConfigArray([{ @@ -402,6 +489,35 @@ describe("FlatConfigArray", () => { }]); + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "@/espree", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: "custom-processor@1.2.3" + }); + + }); + + it("should not throw an error when config with named and versioned processor object without meta is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + name: "custom-processor", + version: "1.2.3", + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + configs.normalizeSync(); const config = configs.getConfig("foo.js"); From 92e2129015397f2555a6630d922591c60eb41934 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 7 Mar 2023 11:01:08 -0800 Subject: [PATCH 3/3] Fix edge case --- lib/config/flat-config-array.js | 4 ++++ tests/lib/config/flat-config-array.js | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/lib/config/flat-config-array.js b/lib/config/flat-config-array.js index 514bea9b91d..94634e7887a 100644 --- a/lib/config/flat-config-array.js +++ b/lib/config/flat-config-array.js @@ -54,6 +54,10 @@ function getObjectId(object) { } name = object.meta.name; + + if (!name) { + return null; + } } // now check for old-style version diff --git a/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js index 9a7dce5a670..c74beaec47d 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -258,6 +258,29 @@ describe("FlatConfigArray", () => { }); + it("should throw an error when config with unnamed parser object with only meta version is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: { + version: "0.1.1" + }, + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Could not serialize parser/u); + + }); + it("should not throw an error when config with named parser object is normalized", () => { const configs = new FlatConfigArray([{