From 71fdc0dffadd69579b4ff8ebead6f131dbccbe8c Mon Sep 17 00:00:00 2001 From: jerasmus Date: Fri, 27 Jun 2025 10:28:16 +0200 Subject: [PATCH 1/2] fix(ruby): properly highlight nested %r{} regex Previously, the Ruby grammar only recognized %r{} regex literals in limited contexts, causing patterns like %r{(\.{2}|\A/)} to break syntax highlighting. This fix: - Adds a top-level REGEXP mode to support all %r{} variations globally - Enables nested brace handling inside %r{...} - Removes redundant %r handling tied to RE_STARTERS_RE - Adds a test case to regexes.txt to prevent regression --- CHANGES.md | 2 ++ src/languages/ruby.js | 38 +++++++++++++++++------------ test/markup/ruby/regexes.expect.txt | 1 + test/markup/ruby/regexes.txt | 1 + 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4b7c3021fd..bf95f0277d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,6 +25,7 @@ Core Grammars: - enh(json) add json5 support [Kerry Shetline][] - fix(css) `unicode-range` parsing, issue #4253 [Kerry Shetline][] - fix(csharp) Support digit separators [te-ing][] +- fix(ruby) support nested content in `%r{}` regex literals outside expression contexts [Jacques Erasmus][] Documentation: @@ -53,6 +54,7 @@ CONTRIBUTORS [Thomas Gorissen]: https://github.com/serrynaimo [te-ing]: https://github.com/te-ing [Anthony Martin]: https://github.com/anthony-c-martin +[Jacques Erasmus]: https://github.com/j-erasmus ## Version 11.11.1 diff --git a/src/languages/ruby.js b/src/languages/ruby.js index 91b2cb527d..57c908bb56 100644 --- a/src/languages/ruby.js +++ b/src/languages/ruby.js @@ -227,6 +227,27 @@ export default function(hljs) { ] }; + const REGEXP = { + className: 'regexp', + contains: [ + hljs.BACKSLASH_ESCAPE, + SUBST, + { + begin: /\{/, end: /\}/, + contains: ['self', hljs.BACKSLASH_ESCAPE, SUBST], + relevance: 0 + } + ], + illegal: /\n/, + variants: [ + { begin: /%r\{/, end: /\}[a-z]*/ }, + { begin: '%r\\(', end: '\\)[a-z]*' }, + { begin: '%r!', end: '![a-z]*' }, + { begin: '%r\\[', end: '\\][a-z]*' }, + { begin: '/', end: '/[a-z]*' } + ] + }; + const PARAMS = { variants: [ { @@ -324,6 +345,7 @@ export default function(hljs) { UPPER_CASE_CONSTANT, CLASS_REFERENCE, METHOD_DEFINITION, + REGEXP, { // swallow namespace qualifiers before symbols begin: hljs.IDENT_RE + '::' }, @@ -372,22 +394,6 @@ export default function(hljs) { { begin: '/', end: '/[a-z]*' - }, - { - begin: /%r\{/, - end: /\}[a-z]*/ - }, - { - begin: '%r\\(', - end: '\\)[a-z]*' - }, - { - begin: '%r!', - end: '![a-z]*' - }, - { - begin: '%r\\[', - end: '\\][a-z]*' } ] } diff --git a/test/markup/ruby/regexes.expect.txt b/test/markup/ruby/regexes.expect.txt index 0c108383cf..760dac83c8 100644 --- a/test/markup/ruby/regexes.expect.txt +++ b/test/markup/ruby/regexes.expect.txt @@ -3,3 +3,4 @@ str =~ %r{foo|bar|buz$} str =~ %r!foo|bar$! str =~ %r[foo|bar$] str =~ %r(\(foo|bar\)$) +str =~ %r{(\.{2}|\A/)} diff --git a/test/markup/ruby/regexes.txt b/test/markup/ruby/regexes.txt index 9d31e03b44..e457db5fd5 100644 --- a/test/markup/ruby/regexes.txt +++ b/test/markup/ruby/regexes.txt @@ -3,3 +3,4 @@ str =~ %r{foo|bar|buz$} str =~ %r!foo|bar$! str =~ %r[foo|bar$] str =~ %r(\(foo|bar\)$) +str =~ %r{(\.{2}|\A/)} From 10e9c97f4afee3b62e7ad76974c7bfcf14b04a1c Mon Sep 17 00:00:00 2001 From: jerasmus Date: Fri, 4 Jul 2025 13:47:12 +0200 Subject: [PATCH 2/2] fix(ruby): handle braces correctly inside regex - Add character class handling to prevent {} inside [] from being treated as interpolation - Support escaped closing brackets (\]) within character classes - Ensure quantifiers like {3} still work outside character classes - Add test cases to verify edge cases are handled properly --- src/languages/ruby.js | 15 +++++++++++---- test/markup/ruby/regexes.expect.txt | 5 +++++ test/markup/ruby/regexes.txt | 5 +++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/languages/ruby.js b/src/languages/ruby.js index 57c908bb56..ce2ffc18a5 100644 --- a/src/languages/ruby.js +++ b/src/languages/ruby.js @@ -232,6 +232,14 @@ export default function(hljs) { contains: [ hljs.BACKSLASH_ESCAPE, SUBST, + { + begin: /\[/, + end: /\]/, + contains: [ + hljs.BACKSLASH_ESCAPE, + { begin: /\\./ } + ] + }, { begin: /\{/, end: /\}/, contains: ['self', hljs.BACKSLASH_ESCAPE, SUBST], @@ -241,10 +249,9 @@ export default function(hljs) { illegal: /\n/, variants: [ { begin: /%r\{/, end: /\}[a-z]*/ }, - { begin: '%r\\(', end: '\\)[a-z]*' }, - { begin: '%r!', end: '![a-z]*' }, - { begin: '%r\\[', end: '\\][a-z]*' }, - { begin: '/', end: '/[a-z]*' } + { begin: /%r\(/, end: /\)[a-z]*/ }, + { begin: /%r!/, end: /![a-z]*/ }, + { begin: /%r\[/, end: /\][a-z]*/ } ] }; diff --git a/test/markup/ruby/regexes.expect.txt b/test/markup/ruby/regexes.expect.txt index 760dac83c8..2a9a63d80d 100644 --- a/test/markup/ruby/regexes.expect.txt +++ b/test/markup/ruby/regexes.expect.txt @@ -4,3 +4,8 @@ str =~ %r!foo|bar$! str =~ %r[foo|bar$] str =~ %r(\(foo|bar\)$) str =~ %r{(\.{2}|\A/)} +str =~ /[{}]/ +str =~ /[\]]/ +str =~ /[a-z]{3}/ +str =~ %r{[{}]+} +str =~ /#{var}[{}]/ diff --git a/test/markup/ruby/regexes.txt b/test/markup/ruby/regexes.txt index e457db5fd5..d60c2a24e1 100644 --- a/test/markup/ruby/regexes.txt +++ b/test/markup/ruby/regexes.txt @@ -4,3 +4,8 @@ str =~ %r!foo|bar$! str =~ %r[foo|bar$] str =~ %r(\(foo|bar\)$) str =~ %r{(\.{2}|\A/)} +str =~ /[{}]/ +str =~ /[\]]/ +str =~ /[a-z]{3}/ +str =~ %r{[{}]+} +str =~ /#{var}[{}]/