From c07a63117a10448ff03cf029a30ea7adfabc8c0a Mon Sep 17 00:00:00 2001 From: David Anson Date: Sun, 21 Sep 2025 15:25:25 -0700 Subject: [PATCH] Update custom rule params.config object to be a copy of the configuration object to prevent modification of user data by custom rule, add test case to validate config.params value for custom rules in different scenarios. --- lib/markdownlint.mjs | 2 +- test/markdownlint-test-custom-rules.mjs | 78 ++++++++++++++++++------- 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/lib/markdownlint.mjs b/lib/markdownlint.mjs index 73194b20..951f1f2e 100644 --- a/lib/markdownlint.mjs +++ b/lib/markdownlint.mjs @@ -545,7 +545,7 @@ function lintContent( ...paramsBase, ...tokens, parsers, - "config": effectiveConfig[ruleName] + "config": (typeof effectiveConfig[ruleName] === "object") ? { ...effectiveConfig[ruleName] } : effectiveConfig[ruleName] }; // eslint-disable-next-line jsdoc/require-jsdoc function throwError(property) { diff --git a/test/markdownlint-test-custom-rules.mjs b/test/markdownlint-test-custom-rules.mjs index 0cb331d6..614fa58d 100644 --- a/test/markdownlint-test-custom-rules.mjs +++ b/test/markdownlint-test-custom-rules.mjs @@ -2016,7 +2016,7 @@ test("customRulesParamsAreFrozen", (t) => { }); test("customRulesParamsAreStable", (t) => { - t.plan(4); + t.plan(6); const config1 = { "value1": 10 }; const config2 = { "value2": 20 }; /** @type {import("markdownlint").Options} */ @@ -2038,17 +2038,10 @@ test("customRulesParamsAreStable", (t) => { "function": (params) => { const { config } = params; - t.deepEqual( - config, - config1, - `Unexpected config in sync path: ${config}.` - ); + t.deepEqual(config, config1, `Unexpected config in sync path: ${config}.`); return Promise.resolve().then(() => { - t.deepEqual( - config, - config1, - `Unexpected config in async path: ${config}.` - ); + t.deepEqual(config, config1, `Unexpected config in async path: ${config}.`); + config.extra = 1; }); } }, @@ -2061,17 +2054,10 @@ test("customRulesParamsAreStable", (t) => { "function": (params) => { const { config } = params; - t.deepEqual( - config, - config2, - `Unexpected config in sync path: ${config}.` - ); + t.deepEqual(config, config2, `Unexpected config in sync path: ${config}.`); return Promise.resolve().then(() => { - t.deepEqual( - config, - config2, - `Unexpected config in async path: ${config}.` - ); + t.deepEqual(config, config2, `Unexpected config in async path: ${config}.`); + config.extra = 2; }); } } @@ -2080,6 +2066,56 @@ test("customRulesParamsAreStable", (t) => { "string": "# Heading" } }; + return lintPromise(options).then(() => { + t.deepEqual(config1, { "value1": 10 }); + t.deepEqual(config2, { "value2": 20 }); + }); +}); + +test("customRulesParamsAreExpected", (t) => { + t.plan(4); + /** @type {import("markdownlint").Options} */ + const options = { + "config": { + // "name1" default enabled + "name2": true, + "name3": {}, + "name4": { "value": 10 } + }, + "customRules": [ + { + "names": [ "name1" ], + "description": "description", + "tags": [ "tag" ], + "parser": "none", + "function": (params) => t.deepEqual(params.config, true) + }, + { + "names": [ "name2" ], + "description": "description", + "tags": [ "tag" ], + "parser": "none", + "function": (params) => t.deepEqual(params.config, {}) + }, + { + "names": [ "name3" ], + "description": "description", + "tags": [ "tag" ], + "parser": "none", + "function": (params) => t.deepEqual(params.config, {}) + }, + { + "names": [ "name4" ], + "description": "description", + "tags": [ "tag" ], + "parser": "none", + "function": (params) => t.deepEqual(params.config, { "value": 10 }) + } + ], + "strings": { + "string": "# Heading" + } + }; return lintPromise(options); });