From 507ff2864dc5fa4ea624382b659de630350a2a5c Mon Sep 17 00:00:00 2001 From: lumir Date: Thu, 8 Oct 2026 20:44:06 +0900 Subject: [PATCH] fix: escaped parentheses in nested labels in `no-reversed-media-syntax` --- src/rules/no-reversed-media-syntax.js | 4 +- tests/rules/no-reversed-media-syntax.test.js | 131 +++++++++++++++++++ 2 files changed, 133 insertions(+), 2 deletions(-) diff --git a/src/rules/no-reversed-media-syntax.js b/src/rules/no-reversed-media-syntax.js index 82e00ec8..141beb74 100644 --- a/src/rules/no-reversed-media-syntax.js +++ b/src/rules/no-reversed-media-syntax.js @@ -22,11 +22,11 @@ /** * Matches reversed link/image syntax like `(text)[url]`, ignoring escaped characters like `\(text\)[url]`. - * The nested group is `\([^()]*\)`, not `\([\s\S]*\)`, so that it cannot overlap the + * The nested group is `\((?:\\[\s\S]|[^()\\])*\)`, not `\([\s\S]*\)`, so that it cannot overlap the * other alternatives. Overlapping alternatives cause catastrophic backtracking. */ const reversedPattern = - /(?<=(?(?:\\.|[^()\\]|\([^()]*\))*)\)\[(?(?:\\.|[^\]\\\r\n])*)\](?!\()/gu; + /(?<=(?(?:\\.|[^()\\]|\((?:\\[\s\S]|[^()\\])*\))*)\)\[(?(?:\\.|[^\]\\\r\n])*)\](?!\()/gu; //----------------------------------------------------------------------------- // Rule Definition diff --git a/tests/rules/no-reversed-media-syntax.test.js b/tests/rules/no-reversed-media-syntax.test.js index a084df3d..4988665e 100644 --- a/tests/rules/no-reversed-media-syntax.test.js +++ b/tests/rules/no-reversed-media-syntax.test.js @@ -588,6 +588,137 @@ ruleTester.run("no-reversed-media-syntax", rule, { }, ], }, + // Nested parentheses with escaped characters + { + code: "(a (b \\(c\\)) d)[x]", + output: "[a (b \\(c\\)) d](x)", + errors: [ + { + messageId: "reversedSyntax", + line: 1, + column: 1, + endLine: 1, + endColumn: 19, + }, + ], + }, + { + code: "!(a (b \\(c\\)) d)[x]", + output: "![a (b \\(c\\)) d](x)", + errors: [ + { + messageId: "reversedSyntax", + line: 1, + column: 2, + endLine: 1, + endColumn: 20, + }, + ], + }, + { + code: "(a (b\\( c))[x]", + output: "[a (b\\( c)](x)", + errors: [ + { + messageId: "reversedSyntax", + line: 1, + column: 1, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: "!(a (b\\( c))[x]", + output: "![a (b\\( c)](x)", + errors: [ + { + messageId: "reversedSyntax", + line: 1, + column: 2, + endLine: 1, + endColumn: 16, + }, + ], + }, + { + code: "(a (b\\) c))[x]", + output: "[a (b\\) c)](x)", + errors: [ + { + messageId: "reversedSyntax", + line: 1, + column: 1, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: "!(a (b\\) c))[x]", + output: "![a (b\\) c)](x)", + errors: [ + { + messageId: "reversedSyntax", + line: 1, + column: 2, + endLine: 1, + endColumn: 16, + }, + ], + }, + { + code: "(a (b\\\\) c)[x]", + output: "[a (b\\\\) c](x)", + errors: [ + { + messageId: "reversedSyntax", + line: 1, + column: 1, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: "!(a (b\\\\) c)[x]", + output: "![a (b\\\\) c](x)", + errors: [ + { + messageId: "reversedSyntax", + line: 1, + column: 2, + endLine: 1, + endColumn: 16, + }, + ], + }, + { + code: "(a (b\\\nc))[x]", + output: "[a (b\\\nc)](x)", + errors: [ + { + messageId: "reversedSyntax", + line: 1, + column: 1, + endLine: 2, + endColumn: 7, + }, + ], + }, + { + code: "!(a (b\\\nc))[x]", + output: "![a (b\\\nc)](x)", + errors: [ + { + messageId: "reversedSyntax", + line: 1, + column: 2, + endLine: 2, + endColumn: 7, + }, + ], + }, // Heading { code: "# (ESLint)[https://eslint.org/]",