Skip to content

Commit b0674be

Browse files
authored
feat: Customization of serialization for languageOptions (#19760)
* feat: Customization serialization for languageOptions fixes #19759 * Update docs and tests * Check result of toJSON is not a function and throw error * Allow toJSON() on languageOptions * Reduce duplicated code * Prefer toJSON over meta
1 parent 3ec2082 commit b0674be

File tree

3 files changed

+554
-228
lines changed

3 files changed

+554
-228
lines changed

docs/src/extend/languages.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ The following optional members allow you to customize how ESLint interacts with
8888
- `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.
8989
- `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.
9090
- `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.
91-
- `normalizeLanguageOptions(languageOptions)` - takes a validated language options object and normalizes its values. This is helpful for backwards compatibility when language options properties change.
91+
- `normalizeLanguageOptions(languageOptions)` - takes a validated language options object and normalizes its values. This is helpful for backwards compatibility when language options properties change and also to add custom serialization with a `toJSON()` method.
9292

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

lib/config/config.js

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,27 @@ function getObjectId(object) {
265265
return name;
266266
}
267267

268+
/**
269+
* Asserts that a value is not a function.
270+
* @param {any} value The value to check.
271+
* @param {string} key The key of the value in the object.
272+
* @param {string} objectKey The key of the object being checked.
273+
* @returns {void}
274+
* @throws {TypeError} If the value is a function.
275+
*/
276+
function assertNotFunction(value, key, objectKey) {
277+
if (typeof value === "function") {
278+
const error = new TypeError(
279+
`Cannot serialize key "${key}" in "${objectKey}": Function values are not supported.`,
280+
);
281+
282+
error.messageTemplate = "config-serialize-function";
283+
error.messageData = { key, objectKey };
284+
285+
throw error;
286+
}
287+
}
288+
268289
/**
269290
* Converts a languageOptions object to a JSON representation.
270291
* @param {Record<string, any>} languageOptions The options to create a JSON
@@ -274,31 +295,33 @@ function getObjectId(object) {
274295
* @throws {TypeError} If a function is found in the languageOptions.
275296
*/
276297
function languageOptionsToJSON(languageOptions, objectKey = "languageOptions") {
298+
if (typeof languageOptions.toJSON === "function") {
299+
const result = languageOptions.toJSON();
300+
301+
assertNotFunction(result, "toJSON", objectKey);
302+
303+
return result;
304+
}
305+
277306
const result = {};
278307

279308
for (const [key, value] of Object.entries(languageOptions)) {
280309
if (value) {
281310
if (typeof value === "object") {
282311
const name = getObjectId(value);
283312

284-
if (name && hasMethod(value)) {
313+
if (typeof value.toJSON === "function") {
314+
result[key] = value.toJSON();
315+
assertNotFunction(result[key], key, objectKey);
316+
} else if (name && hasMethod(value)) {
285317
result[key] = name;
286318
} else {
287319
result[key] = languageOptionsToJSON(value, key);
288320
}
289321
continue;
290322
}
291323

292-
if (typeof value === "function") {
293-
const error = new TypeError(
294-
`Cannot serialize key "${key}" in ${objectKey}: Function values are not supported.`,
295-
);
296-
297-
error.messageTemplate = "config-serialize-function";
298-
error.messageData = { key, objectKey };
299-
300-
throw error;
301-
}
324+
assertNotFunction(value, key, objectKey);
302325
}
303326

304327
result[key] = value;

0 commit comments

Comments
 (0)