From e0219411c64228c60aade046085f93fb487d2846 Mon Sep 17 00:00:00 2001 From: David Anson Date: Sat, 5 Oct 2024 18:07:45 -0700 Subject: [PATCH] Refactor RegExps to avoid the possibility of polynomial backtracking (fixes #657). --- README.md | 2 +- demo/markdownlint-browser.js | 10 +++++----- eslint.config.mjs | 1 - helpers/helpers.js | 2 +- lib/markdownit.cjs | 6 +++--- lib/md020.js | 2 +- test/markdownlint-test.js | 5 ++--- test/rules/rules.js | 2 +- 8 files changed, 14 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 35c698b5..06fb8aa2 100644 --- a/README.md +++ b/README.md @@ -525,7 +525,7 @@ specify a custom `RegExp` or use the value `null` to disable the feature. The default value: ```javascript -/((^---\s*$[\s\S]+?^---\s*)|(^\+\+\+\s*$[\s\S]+?^(\+\+\+|\.\.\.)\s*)|(^\{\s*$[\s\S]+?^\}\s*))(\r\n|\r|\n|$)/m +/((^---[^\S\r\n\u2028\u2029]*$[\s\S]+?^---\s*)|(^\+\+\+[^\S\r\n\u2028\u2029]*$[\s\S]+?^(\+\+\+|\.\.\.)\s*)|(^\{[^\S\r\n\u2028\u2029]*$[\s\S]+?^\}\s*))(\r\n|\r|\n|$)/m ``` Ignores [YAML](https://en.wikipedia.org/wiki/YAML), diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index f698f5ac..ebc5e526 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -36,7 +36,7 @@ module.exports.nextLinesRe = nextLinesRe; // Regular expression for matching common front matter (YAML and TOML) module.exports.frontMatterRe = - /((^---\s*$[\s\S]+?^---\s*)|(^\+\+\+\s*$[\s\S]+?^(\+\+\+|\.\.\.)\s*)|(^\{\s*$[\s\S]+?^\}\s*))(\r\n|\r|\n|$)/m; + /((^---[^\S\r\n\u2028\u2029]*$[\s\S]+?^---\s*)|(^\+\+\+[^\S\r\n\u2028\u2029]*$[\s\S]+?^(\+\+\+|\.\.\.)\s*)|(^\{[^\S\r\n\u2028\u2029]*$[\s\S]+?^\}\s*))(\r\n|\r|\n|$)/m; // Regular expression for matching the start of inline disable/enable comments const inlineCommentStartRe = @@ -1554,7 +1554,7 @@ function freezeToken(token) { /** * Annotate tokens with line/lineNumber and freeze them. * - * @param {import("markdown-it").Token[]} tokens Array of markdown-it tokens. + * @param {Object[]} tokens Array of markdown-it tokens. * @param {string[]} lines Lines of Markdown content. * @returns {void} */ @@ -1613,18 +1613,18 @@ function annotateAndFreezeTokens(tokens, lines) { * @param {import("./markdownlint").Plugin[]} markdownItPlugins Additional plugins. * @param {string} content Markdown content. * @param {string[]} lines Lines of Markdown content. - * @returns {import("markdown-it").Token[]} Array of markdown-it tokens. + * @returns {import("../lib/markdownlint").MarkdownItToken} Array of markdown-it tokens. */ function getMarkdownItTokens(markdownItPlugins, content, lines) { const markdownit = __webpack_require__(/*! markdown-it */ "markdown-it"); const md = markdownit({ "html": true }); - // const markdownItPlugins = options.markdownItPlugins || []; for (const plugin of markdownItPlugins) { // @ts-ignore md.use(...plugin); } const tokens = md.parse(content, {}); annotateAndFreezeTokens(tokens, lines); + // @ts-ignore return tokens; }; @@ -4192,7 +4192,7 @@ module.exports = { for (const [ lineIndex, line ] of lines.entries()) { if (!ignoreBlockLineNumbers.has(lineIndex + 1)) { const match = - /^(#+)([ \t]*)([^#]*?[^#\\])([ \t]*)((?:\\#)?)(#+)(\s*)$/.exec(line); + /^(#+)([ \t]*)([^# \t\\]|[^# \t][^#]*?[^# \t\\])([ \t]*)((?:\\#)?)(#+)(\s*)$/.exec(line); if (match) { const [ , diff --git a/eslint.config.mjs b/eslint.config.mjs index 12273a44..bbf19c32 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -75,7 +75,6 @@ export default [ "prefer-destructuring": "off", "prefer-named-capture-group": "off", "prefer-template": "off", - "regexp/no-super-linear-backtracking": "off", "require-unicode-regexp": "off", "sort-imports": "off", "sort-keys": "off", diff --git a/helpers/helpers.js b/helpers/helpers.js index c1f3203f..10a8097d 100644 --- a/helpers/helpers.js +++ b/helpers/helpers.js @@ -24,7 +24,7 @@ module.exports.nextLinesRe = nextLinesRe; // Regular expression for matching common front matter (YAML and TOML) module.exports.frontMatterRe = - /((^---\s*$[\s\S]+?^---\s*)|(^\+\+\+\s*$[\s\S]+?^(\+\+\+|\.\.\.)\s*)|(^\{\s*$[\s\S]+?^\}\s*))(\r\n|\r|\n|$)/m; + /((^---[^\S\r\n\u2028\u2029]*$[\s\S]+?^---\s*)|(^\+\+\+[^\S\r\n\u2028\u2029]*$[\s\S]+?^(\+\+\+|\.\.\.)\s*)|(^\{[^\S\r\n\u2028\u2029]*$[\s\S]+?^\}\s*))(\r\n|\r|\n|$)/m; // Regular expression for matching the start of inline disable/enable comments const inlineCommentStartRe = diff --git a/lib/markdownit.cjs b/lib/markdownit.cjs index 691e0330..5c6349a8 100644 --- a/lib/markdownit.cjs +++ b/lib/markdownit.cjs @@ -92,7 +92,7 @@ function freezeToken(token) { /** * Annotate tokens with line/lineNumber and freeze them. * - * @param {import("markdown-it").Token[]} tokens Array of markdown-it tokens. + * @param {Object[]} tokens Array of markdown-it tokens. * @param {string[]} lines Lines of Markdown content. * @returns {void} */ @@ -151,18 +151,18 @@ function annotateAndFreezeTokens(tokens, lines) { * @param {import("./markdownlint").Plugin[]} markdownItPlugins Additional plugins. * @param {string} content Markdown content. * @param {string[]} lines Lines of Markdown content. - * @returns {import("markdown-it").Token[]} Array of markdown-it tokens. + * @returns {import("../lib/markdownlint").MarkdownItToken} Array of markdown-it tokens. */ function getMarkdownItTokens(markdownItPlugins, content, lines) { const markdownit = require("markdown-it"); const md = markdownit({ "html": true }); - // const markdownItPlugins = options.markdownItPlugins || []; for (const plugin of markdownItPlugins) { // @ts-ignore md.use(...plugin); } const tokens = md.parse(content, {}); annotateAndFreezeTokens(tokens, lines); + // @ts-ignore return tokens; }; diff --git a/lib/md020.js b/lib/md020.js index 611996fa..5bd2a099 100644 --- a/lib/md020.js +++ b/lib/md020.js @@ -22,7 +22,7 @@ module.exports = { for (const [ lineIndex, line ] of lines.entries()) { if (!ignoreBlockLineNumbers.has(lineIndex + 1)) { const match = - /^(#+)([ \t]*)([^#]*?[^#\\])([ \t]*)((?:\\#)?)(#+)(\s*)$/.exec(line); + /^(#+)([ \t]*)([^# \t\\]|[^# \t][^#]*?[^# \t\\])([ \t]*)((?:\\#)?)(#+)(\s*)$/.exec(line); if (match) { const [ , diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index 178bd88e..6cf7ebb9 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -1034,8 +1034,7 @@ test("validateConfigExampleJson", (t) => { const ajv = new Ajv(ajvOptions); const validateSchema = ajv.compile(configSchema); t.is( - // eslint-disable-next-line regexp/optimal-quantifier-concatenation - configSchema.$id.replace(/^.*v(?\d+\.\d+\.\d+).*$/u, "$"), + configSchema.$id.replace(/^.*\/v(?\d+\.\d+\.\d+)\/.*$/u, "$"), version ); t.is(configSchema.$id, configSchema.properties.$schema.default); @@ -1081,7 +1080,7 @@ test("allBuiltInRulesHaveValidUrl", (t) => { }); test("someCustomRulesHaveValidUrl", (t) => { - t.plan(8); + t.plan(9); for (const rule of customRules.all) { t.true(!rule.information || (Object.getPrototypeOf(rule.information) === URL.prototype)); diff --git a/test/rules/rules.js b/test/rules/rules.js index 083726a7..6b7650d2 100644 --- a/test/rules/rules.js +++ b/test/rules/rules.js @@ -3,7 +3,7 @@ "use strict"; const anyBlockquote = require("./any-blockquote"); -module.exports.anyBlockquote = anyBlockquote; +module.exports.anyBlockquote = anyBlockquote[1]; const everyNLines = require("./every-n-lines"); module.exports.everyNLines = everyNLines;