From 67644b9d7fb9909b44dcfb28b46a44201780d9ca Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Tue, 25 Feb 2025 23:36:55 +1000 Subject: [PATCH 1/9] fix(ruby) symbols, string interpolation, class names with underscores Signed-off-by: jimtng <2554958+jimtng@users.noreply.github.com> --- CHANGES.md | 7 ++ src/languages/ruby.js | 153 +++++++++++++++++++--------- test/markup/ruby/classes.expect.txt | 7 ++ test/markup/ruby/classes.txt | 7 ++ test/markup/ruby/strings.expect.txt | 26 ++++- test/markup/ruby/strings.txt | 26 ++++- test/markup/ruby/symbols.expect.txt | 22 ++++ test/markup/ruby/symbols.txt | 22 ++++ 8 files changed, 220 insertions(+), 50 deletions(-) create mode 100644 test/markup/ruby/classes.expect.txt create mode 100644 test/markup/ruby/classes.txt create mode 100644 test/markup/ruby/symbols.expect.txt create mode 100644 test/markup/ruby/symbols.txt diff --git a/CHANGES.md b/CHANGES.md index 76fa83e573..62cc1846d4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,10 @@ +## Version 11.11.2 + +Core Grammars: + +- fix(ruby) symbols, string interpolation, class names with underscores + + ## Version 11.11.1 - Fixes regression with Rust grammar. diff --git a/src/languages/ruby.js b/src/languages/ruby.js index 91b2cb527d..2ae6896fcf 100644 --- a/src/languages/ruby.js +++ b/src/languages/ruby.js @@ -11,13 +11,7 @@ export default function(hljs) { const regex = hljs.regex; const RUBY_METHOD_RE = '([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)'; // TODO: move concepts like CAMEL_CASE into `modes.js` - const CLASS_NAME_RE = regex.either( - /\b([A-Z]+[a-z0-9]+)+/, - // ends in caps - /\b([A-Z]+[a-z0-9]+)+[A-Z]+/, - ) - ; - const CLASS_NAME_WITH_NAMESPACE_RE = regex.concat(CLASS_NAME_RE, /(::\w+)*/) + const CLASS_NAME_RE = /\b([A-Z]+[a-z0-9_]+)+[A-Z]*/; // very popular ruby built-ins that one might even assume // are actual keywords (despite that not being the case) const PSEUDO_KWS = [ @@ -120,19 +114,16 @@ export default function(hljs) { className: 'subst', begin: /#\{/, end: /\}/, - keywords: RUBY_KEYWORDS + keywords: RUBY_KEYWORDS, + relevance: 10 }; - const STRING = { + const STRING_INTERPOLABLE = { className: 'string', contains: [ hljs.BACKSLASH_ESCAPE, SUBST ], variants: [ - { - begin: /'/, - end: /'/ - }, { begin: /"/, end: /"/ @@ -142,45 +133,45 @@ export default function(hljs) { end: /`/ }, { - begin: /%[qQwWx]?\(/, - end: /\)/ + begin: /%[QWx]?\(/, + end: /\)/, + relevance: 2 }, { - begin: /%[qQwWx]?\[/, - end: /\]/ + begin: /%[QWx]?\[/, + end: /\]/, + relevance: 2 }, { - begin: /%[qQwWx]?\{/, - end: /\}/ + begin: /%[QWx]?\{/, + end: /\}/, + relevance: 2 }, { - begin: /%[qQwWx]?/ + begin: /%[QWx]?/, + relevance: 2 }, { - begin: /%[qQwWx]?\//, - end: /\// + begin: /%[QWx]?\//, + end: /\//, + relevance: 2 }, { - begin: /%[qQwWx]?%/, - end: /%/ + begin: /%[QWx]?%/, + end: /%/, + relevance: 2 }, { - begin: /%[qQwWx]?-/, - end: /-/ + begin: /%[QWx]?-/, + end: /-/, + relevance: 2 }, { - begin: /%[qQwWx]?\|/, - end: /\|/ + begin: /%[QWx]?\|/, + end: /\|/, + relevance: 2 }, - // in the following expressions, \B in the beginning suppresses recognition of ?-sequences - // where ? is the last character of a preceding identifier, as in: `func?4` - { begin: /\B\?(\\\d{1,3})/ }, - { begin: /\B\?(\\x[A-Fa-f0-9]{1,2})/ }, - { begin: /\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/ }, - { begin: /\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/ }, - { begin: /\B\?\\(c|C-)[\x20-\x7e]/ }, - { begin: /\B\?\\?\S/ }, // heredocs { // this guard makes sure that we have an entire heredoc and not a false @@ -202,6 +193,63 @@ export default function(hljs) { } ] }; + const STRING_NONINTERPOLABLE = { + className: 'string', + variants: [ + { + begin: /'/, + end: /'/ + }, + { + begin: /%[qw]?\(/, + end: /\)/, + relevance: 2 + }, + { + begin: /%[qw]?\[/, + end: /\]/, + relevance: 2 + }, + { + begin: /%[qw]?\{/, + end: /\}/, + relevance: 2 + }, + { + begin: /%[qw]?/, + relevance: 2 + }, + { + begin: /%[qw]?\//, + end: /\//, + relevance: 2 + }, + { + begin: /%[qw]?%/, + end: /%/, + relevance: 2 + }, + { + begin: /%[qw]?-/, + end: /-/, + relevance: 2 + }, + { + begin: /%[qw]?\|/, + end: /\|/, + relevance: 2 + }, + // in the following expressions, \B in the beginning suppresses recognition of ?-sequences + // where ? is the last character of a preceding identifier, as in: `func?4` + { begin: /\B\?(\\\d{1,3})/ }, + { begin: /\B\?(\\x[A-Fa-f0-9]{1,2})/ }, + { begin: /\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/ }, + { begin: /\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/ }, + { begin: /\B\?\\(c|C-)[\x20-\x7e]/ }, + { begin: /\B\?\\?\S/ } + ] + }; // Ruby syntax is underdocumented, but this grammar seems to be accurate // as of version 2.7.2 (confirmed with (irb and `Ripper.sexp(...)`) @@ -246,7 +294,7 @@ export default function(hljs) { const INCLUDE_EXTEND = { match: [ /(include|extend)\s+/, - CLASS_NAME_WITH_NAMESPACE_RE + CLASS_NAME_RE ], scope: { 2: "title.class" @@ -259,15 +307,15 @@ export default function(hljs) { { match: [ /class\s+/, - CLASS_NAME_WITH_NAMESPACE_RE, + CLASS_NAME_RE, /\s+<\s+/, - CLASS_NAME_WITH_NAMESPACE_RE + CLASS_NAME_RE ] }, { match: [ /\b(class|module)\s+/, - CLASS_NAME_WITH_NAMESPACE_RE + CLASS_NAME_RE ] } ], @@ -301,7 +349,7 @@ export default function(hljs) { const OBJECT_CREATION = { relevance: 0, match: [ - CLASS_NAME_WITH_NAMESPACE_RE, + CLASS_NAME_RE, /\.new[. (]/ ], scope: { @@ -317,7 +365,8 @@ export default function(hljs) { }; const RUBY_DEFAULT_CONTAINS = [ - STRING, + STRING_INTERPOLABLE, + STRING_NONINTERPOLABLE, CLASS_DEFINITION, INCLUDE_EXTEND, OBJECT_CREATION, @@ -326,7 +375,8 @@ export default function(hljs) { METHOD_DEFINITION, { // swallow namespace qualifiers before symbols - begin: hljs.IDENT_RE + '::' }, + begin: hljs.IDENT_RE + '::' + }, { className: 'symbol', begin: hljs.UNDERSCORE_IDENT_RE + '(!|\\?)?:', @@ -334,12 +384,19 @@ export default function(hljs) { }, { className: 'symbol', - begin: ':(?!\\s)', + begin: '(?Class +ClassName +Class_Name +ClassNAME +ClassName::With::Namespace +ClassName::With.method +::TopLevel::Class \ No newline at end of file diff --git a/test/markup/ruby/classes.txt b/test/markup/ruby/classes.txt new file mode 100644 index 0000000000..ae2c069837 --- /dev/null +++ b/test/markup/ruby/classes.txt @@ -0,0 +1,7 @@ +Class +ClassName +Class_Name +ClassNAME +ClassName::With::Namespace +ClassName::With.method +::TopLevel::Class \ No newline at end of file diff --git a/test/markup/ruby/strings.expect.txt b/test/markup/ruby/strings.expect.txt index 07b8c51197..68a351a3e8 100644 --- a/test/markup/ruby/strings.expect.txt +++ b/test/markup/ruby/strings.expect.txt @@ -27,4 +27,28 @@ c = ?\u{00AF09} c = ?\u{0AF09} c = ?\u{AF9} c = ?\u{F9} -c = ?\u{F} \ No newline at end of file +c = ?\u{F} + +# Interpolable Strings +"string" +"string #{var}" +`string` +`string #{var}` +%W[foo bar] +%W[foo bar #{var}] +%Q[foo bar] +%Q[foo bar #{var}] +%x[foo] +%x[foo #{var}] +<<~DOC + Multiline heredoc + Text #{var} +DOC + +# Non-interpolable Strings +'string' +'string #{var}' +%q[foo] +%q[foo #{var}] +%w[foo] +%w[foo #{var}] \ No newline at end of file diff --git a/test/markup/ruby/strings.txt b/test/markup/ruby/strings.txt index 43d35d656b..2ec98d2f0c 100644 --- a/test/markup/ruby/strings.txt +++ b/test/markup/ruby/strings.txt @@ -27,4 +27,28 @@ c = ?\u{00AF09} c = ?\u{0AF09} c = ?\u{AF9} c = ?\u{F9} -c = ?\u{F} \ No newline at end of file +c = ?\u{F} + +# Interpolable Strings +"string" +"string #{var}" +`string` +`string #{var}` +%W[foo bar] +%W[foo bar #{var}] +%Q[foo bar] +%Q[foo bar #{var}] +%x[foo] +%x[foo #{var}] +<<~DOC + Multiline heredoc + Text #{var} +DOC + +# Non-interpolable Strings +'string' +'string #{var}' +%q[foo] +%q[foo #{var}] +%w[foo] +%w[foo #{var}] diff --git a/test/markup/ruby/symbols.expect.txt b/test/markup/ruby/symbols.expect.txt new file mode 100644 index 0000000000..df75d4fbcc --- /dev/null +++ b/test/markup/ruby/symbols.expect.txt @@ -0,0 +1,22 @@ +:symbol +:Symbol +:_leading +:trailing_ +:contains_underscore +:symbol_CAPS +:"string symbol" +:"interpolated #{test}" +:'string symbol' +:'not interpolated #{test}' +method :symbol +method(:symbol) +method(&:symbol) +assign=:symbol +assign = :symbol +:symbol, others +:1notasymbol +:%q[notasymbol] + +::notsymbol + +hash_symbol: value \ No newline at end of file diff --git a/test/markup/ruby/symbols.txt b/test/markup/ruby/symbols.txt new file mode 100644 index 0000000000..b11c5c04ba --- /dev/null +++ b/test/markup/ruby/symbols.txt @@ -0,0 +1,22 @@ +:symbol +:Symbol +:_leading +:trailing_ +:contains_underscore +:symbol_CAPS +:"string symbol" +:"interpolated #{test}" +:'string symbol' +:'not interpolated #{test}' +method :symbol +method(:symbol) +method(&:symbol) +assign=:symbol +assign = :symbol +:symbol, others +:1notasymbol +:%q[notasymbol] + +::notsymbol + +hash_symbol: value \ No newline at end of file From 1ce45f356095fa1eeae63196aa3032c26127adb7 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Wed, 5 Mar 2025 14:40:46 +1000 Subject: [PATCH 2/9] remove negative lookbehind Signed-off-by: jimtng <2554958+jimtng@users.noreply.github.com> --- src/languages/ruby.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/ruby.js b/src/languages/ruby.js index 2ae6896fcf..c2336b26cc 100644 --- a/src/languages/ruby.js +++ b/src/languages/ruby.js @@ -375,7 +375,7 @@ export default function(hljs) { METHOD_DEFINITION, { // swallow namespace qualifiers before symbols - begin: hljs.IDENT_RE + '::' + begin: '::' }, { className: 'symbol', @@ -384,7 +384,7 @@ export default function(hljs) { }, { className: 'symbol', - begin: '(? Date: Wed, 5 Mar 2025 14:41:30 +1000 Subject: [PATCH 3/9] reduce relevance of string interpolation Signed-off-by: jimtng <2554958+jimtng@users.noreply.github.com> --- src/languages/ruby.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/ruby.js b/src/languages/ruby.js index c2336b26cc..ad2bec8416 100644 --- a/src/languages/ruby.js +++ b/src/languages/ruby.js @@ -115,7 +115,7 @@ export default function(hljs) { begin: /#\{/, end: /\}/, keywords: RUBY_KEYWORDS, - relevance: 10 + relevance: 2 }; const STRING_INTERPOLABLE = { className: 'string', From f53b568ff64716ffb4af1bd4b084a3a66bf482aa Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:29:42 +1000 Subject: [PATCH 4/9] remove relevance Signed-off-by: jimtng <2554958+jimtng@users.noreply.github.com> --- src/languages/ruby.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/languages/ruby.js b/src/languages/ruby.js index ad2bec8416..6d9feca8aa 100644 --- a/src/languages/ruby.js +++ b/src/languages/ruby.js @@ -115,7 +115,6 @@ export default function(hljs) { begin: /#\{/, end: /\}/, keywords: RUBY_KEYWORDS, - relevance: 2 }; const STRING_INTERPOLABLE = { className: 'string', @@ -135,42 +134,34 @@ export default function(hljs) { { begin: /%[QWx]?\(/, end: /\)/, - relevance: 2 }, { begin: /%[QWx]?\[/, end: /\]/, - relevance: 2 }, { begin: /%[QWx]?\{/, end: /\}/, - relevance: 2 }, { begin: /%[QWx]?/, - relevance: 2 }, { begin: /%[QWx]?\//, end: /\//, - relevance: 2 }, { begin: /%[QWx]?%/, end: /%/, - relevance: 2 }, { begin: /%[QWx]?-/, end: /-/, - relevance: 2 }, { begin: /%[QWx]?\|/, end: /\|/, - relevance: 2 }, // heredocs { @@ -203,42 +194,34 @@ export default function(hljs) { { begin: /%[qw]?\(/, end: /\)/, - relevance: 2 }, { begin: /%[qw]?\[/, end: /\]/, - relevance: 2 }, { begin: /%[qw]?\{/, end: /\}/, - relevance: 2 }, { begin: /%[qw]?/, - relevance: 2 }, { begin: /%[qw]?\//, end: /\//, - relevance: 2 }, { begin: /%[qw]?%/, end: /%/, - relevance: 2 }, { begin: /%[qw]?-/, end: /-/, - relevance: 2 }, { begin: /%[qw]?\|/, end: /\|/, - relevance: 2 }, // in the following expressions, \B in the beginning suppresses recognition of ?-sequences // where ? is the last character of a preceding identifier, as in: `func?4` From 094e626c568edea55e71899c482cf3d8a49c9eb1 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:37:47 +1000 Subject: [PATCH 5/9] match single-letter class name Signed-off-by: jimtng <2554958+jimtng@users.noreply.github.com> --- src/languages/ruby.js | 4 ++-- test/markup/ruby/classes.expect.txt | 8 +++++++- test/markup/ruby/classes.txt | 8 +++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/languages/ruby.js b/src/languages/ruby.js index 6d9feca8aa..2e852693a3 100644 --- a/src/languages/ruby.js +++ b/src/languages/ruby.js @@ -11,7 +11,7 @@ export default function(hljs) { const regex = hljs.regex; const RUBY_METHOD_RE = '([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)'; // TODO: move concepts like CAMEL_CASE into `modes.js` - const CLASS_NAME_RE = /\b([A-Z]+[a-z0-9_]+)+[A-Z]*/; + const CLASS_NAME_RE = /\b([A-Z]+[a-z0-9_]*)+[A-Z]*/; // very popular ruby built-ins that one might even assume // are actual keywords (despite that not being the case) const PSEUDO_KWS = [ @@ -362,7 +362,7 @@ export default function(hljs) { }, { className: 'symbol', - begin: hljs.UNDERSCORE_IDENT_RE + '(!|\\?)?:', + begin: hljs.UNDERSCORE_IDENT_RE + '(!|\\?)?:(?!:)', relevance: 0 }, { diff --git a/test/markup/ruby/classes.expect.txt b/test/markup/ruby/classes.expect.txt index 68419cfa01..d3e4580891 100644 --- a/test/markup/ruby/classes.expect.txt +++ b/test/markup/ruby/classes.expect.txt @@ -4,4 +4,10 @@ ClassNAME ClassName::With::Namespace ClassName::With.method -::TopLevel::Class \ No newline at end of file +::TopLevel::Class + +class Class::Name < With::Inheritance +end + +class A::B < C::D +end \ No newline at end of file diff --git a/test/markup/ruby/classes.txt b/test/markup/ruby/classes.txt index ae2c069837..c5ba1a2373 100644 --- a/test/markup/ruby/classes.txt +++ b/test/markup/ruby/classes.txt @@ -4,4 +4,10 @@ Class_Name ClassNAME ClassName::With::Namespace ClassName::With.method -::TopLevel::Class \ No newline at end of file +::TopLevel::Class + +class Class::Name < With::Inheritance +end + +class A::B < C::D +end From 2017467a4acfc92a94a9aa4be924239fe7f29f90 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Thu, 6 Mar 2025 19:47:32 +1000 Subject: [PATCH 6/9] match single char constant Signed-off-by: jimtng <2554958+jimtng@users.noreply.github.com> --- src/languages/ruby.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/ruby.js b/src/languages/ruby.js index 2e852693a3..5bad7e5f13 100644 --- a/src/languages/ruby.js +++ b/src/languages/ruby.js @@ -311,7 +311,7 @@ export default function(hljs) { const UPPER_CASE_CONSTANT = { relevance: 0, - match: /\b[A-Z][A-Z_0-9]+\b/, + match: /\b[A-Z][A-Z_0-9]*\b/, className: "variable.constant" }; From 34ee845581a494d4216de6bbe43a94d85f819669 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Thu, 6 Mar 2025 19:48:09 +1000 Subject: [PATCH 7/9] match class inheritance Signed-off-by: jimtng <2554958+jimtng@users.noreply.github.com> --- src/languages/ruby.js | 53 ++++------------------------- test/markup/ruby/classes.expect.txt | 4 +-- test/markup/ruby/classes.txt | 4 +-- 3 files changed, 10 insertions(+), 51 deletions(-) diff --git a/src/languages/ruby.js b/src/languages/ruby.js index 5bad7e5f13..e50858f23c 100644 --- a/src/languages/ruby.js +++ b/src/languages/ruby.js @@ -10,8 +10,9 @@ Category: common, scripting export default function(hljs) { const regex = hljs.regex; const RUBY_METHOD_RE = '([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)'; + const CLASS_NAME_PATTERN = '([A-Z]+[a-z0-9_]+)+[A-Z]*' // TODO: move concepts like CAMEL_CASE into `modes.js` - const CLASS_NAME_RE = /\b([A-Z]+[a-z0-9_]*)+[A-Z]*/; + const CLASS_NAME_RE = new RegExp(`\\b${CLASS_NAME_PATTERN}`); // very popular ruby built-ins that one might even assume // are actual keywords (despite that not being the case) const PSEUDO_KWS = [ @@ -274,41 +275,6 @@ export default function(hljs) { ] }; - const INCLUDE_EXTEND = { - match: [ - /(include|extend)\s+/, - CLASS_NAME_RE - ], - scope: { - 2: "title.class" - }, - keywords: RUBY_KEYWORDS - }; - - const CLASS_DEFINITION = { - variants: [ - { - match: [ - /class\s+/, - CLASS_NAME_RE, - /\s+<\s+/, - CLASS_NAME_RE - ] - }, - { - match: [ - /\b(class|module)\s+/, - CLASS_NAME_RE - ] - } - ], - scope: { - 2: "title.class", - 4: "title.class.inherited" - }, - keywords: RUBY_KEYWORDS - }; - const UPPER_CASE_CONSTANT = { relevance: 0, match: /\b[A-Z][A-Z_0-9]*\b/, @@ -329,15 +295,10 @@ export default function(hljs) { ] }; - const OBJECT_CREATION = { + const CLASS_INHERITANCE = { relevance: 0, - match: [ - CLASS_NAME_RE, - /\.new[. (]/ - ], - scope: { - 1: "title.class" - } + match: new RegExp(`(?<=\\s*class\\s+(${CLASS_NAME_PATTERN}::)*${CLASS_NAME_PATTERN}\\s*<\\s*(${CLASS_NAME_PATTERN}::)*)${CLASS_NAME_PATTERN}`), + className: "title.class.inherited" }; // CamelCase @@ -350,10 +311,8 @@ export default function(hljs) { const RUBY_DEFAULT_CONTAINS = [ STRING_INTERPOLABLE, STRING_NONINTERPOLABLE, - CLASS_DEFINITION, - INCLUDE_EXTEND, - OBJECT_CREATION, UPPER_CASE_CONSTANT, + CLASS_INHERITANCE, CLASS_REFERENCE, METHOD_DEFINITION, { diff --git a/test/markup/ruby/classes.expect.txt b/test/markup/ruby/classes.expect.txt index d3e4580891..fdfe4b5cba 100644 --- a/test/markup/ruby/classes.expect.txt +++ b/test/markup/ruby/classes.expect.txt @@ -6,8 +6,8 @@ ClassName::With.method ::TopLevel::Class -class Class::Name < With::Inheritance +class Foo::Bar::Baz < Qux::Quux::Corge end -class A::B < C::D +class Child < Parent end \ No newline at end of file diff --git a/test/markup/ruby/classes.txt b/test/markup/ruby/classes.txt index c5ba1a2373..8ee77737cc 100644 --- a/test/markup/ruby/classes.txt +++ b/test/markup/ruby/classes.txt @@ -6,8 +6,8 @@ ClassName::With::Namespace ClassName::With.method ::TopLevel::Class -class Class::Name < With::Inheritance +class Foo::Bar::Baz < Qux::Quux::Corge end -class A::B < C::D +class Child < Parent end From 1e751a93cec6152d4e7c8bda251bb30ef7d1ab63 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Thu, 6 Mar 2025 20:23:07 +1000 Subject: [PATCH 8/9] clean up last commas Signed-off-by: jimtng <2554958+jimtng@users.noreply.github.com> --- src/languages/ruby.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/languages/ruby.js b/src/languages/ruby.js index e50858f23c..907fb60abb 100644 --- a/src/languages/ruby.js +++ b/src/languages/ruby.js @@ -115,7 +115,7 @@ export default function(hljs) { className: 'subst', begin: /#\{/, end: /\}/, - keywords: RUBY_KEYWORDS, + keywords: RUBY_KEYWORDS }; const STRING_INTERPOLABLE = { className: 'string', @@ -134,35 +134,35 @@ export default function(hljs) { }, { begin: /%[QWx]?\(/, - end: /\)/, + end: /\)/ }, { begin: /%[QWx]?\[/, - end: /\]/, + end: /\]/ }, { begin: /%[QWx]?\{/, - end: /\}/, + end: /\}/ }, { begin: /%[QWx]?/, + end: />/ }, { begin: /%[QWx]?\//, - end: /\//, + end: /\// }, { begin: /%[QWx]?%/, - end: /%/, + end: /%/ }, { begin: /%[QWx]?-/, - end: /-/, + end: /-/ }, { begin: /%[QWx]?\|/, - end: /\|/, + end: /\|/ }, // heredocs { @@ -194,35 +194,35 @@ export default function(hljs) { }, { begin: /%[qw]?\(/, - end: /\)/, + end: /\)/ }, { begin: /%[qw]?\[/, - end: /\]/, + end: /\]/ }, { begin: /%[qw]?\{/, - end: /\}/, + end: /\}/ }, { begin: /%[qw]?/, + end: />/ }, { begin: /%[qw]?\//, - end: /\//, + end: /\// }, { begin: /%[qw]?%/, - end: /%/, + end: /%/ }, { begin: /%[qw]?-/, - end: /-/, + end: /-/ }, { begin: /%[qw]?\|/, - end: /\|/, + end: /\|/ }, // in the following expressions, \B in the beginning suppresses recognition of ?-sequences // where ? is the last character of a preceding identifier, as in: `func?4` From eace348e91567fef49df24f2ec4355abb54d4b7b Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Fri, 7 Mar 2025 17:37:06 +1000 Subject: [PATCH 9/9] remove the use of lookbehind Signed-off-by: jimtng <2554958+jimtng@users.noreply.github.com> --- src/languages/ruby.js | 50 +++++++++++++++++++++++++---- test/markup/ruby/classes.expect.txt | 13 +++++--- test/markup/ruby/classes.txt | 11 ++++--- 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/languages/ruby.js b/src/languages/ruby.js index 907fb60abb..6121071b22 100644 --- a/src/languages/ruby.js +++ b/src/languages/ruby.js @@ -10,9 +10,8 @@ Category: common, scripting export default function(hljs) { const regex = hljs.regex; const RUBY_METHOD_RE = '([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)'; - const CLASS_NAME_PATTERN = '([A-Z]+[a-z0-9_]+)+[A-Z]*' // TODO: move concepts like CAMEL_CASE into `modes.js` - const CLASS_NAME_RE = new RegExp(`\\b${CLASS_NAME_PATTERN}`); + const CLASS_NAME_RE = /\b([A-Z]+[a-z0-9_]+)+[A-Z]*/; // very popular ruby built-ins that one might even assume // are actual keywords (despite that not being the case) const PSEUDO_KWS = [ @@ -295,10 +294,9 @@ export default function(hljs) { ] }; - const CLASS_INHERITANCE = { - relevance: 0, - match: new RegExp(`(?<=\\s*class\\s+(${CLASS_NAME_PATTERN}::)*${CLASS_NAME_PATTERN}\\s*<\\s*(${CLASS_NAME_PATTERN}::)*)${CLASS_NAME_PATTERN}`), - className: "title.class.inherited" + const CLASS_ANCESTOR = { + className: "title.class.inherited", + match: CLASS_NAME_RE }; // CamelCase @@ -308,12 +306,50 @@ export default function(hljs) { scope: "title.class" }; + const CLASS_SEPARATOR = { + match: /::/ + }; + + const CLASS_KEYWORD = { + match: /class/, + scope: "keyword" + }; + + const CLASS_INHERITANCE = { + begin: /\s*class\b/, + excludeBegin: true, + variants: [ + { + contains: [ + CLASS_KEYWORD, + { + begin: /\s/, + contains: [ CLASS_REFERENCE, CLASS_SEPARATOR ], + }, + { + begin: /<\s*/, + contains: [ CLASS_ANCESTOR, CLASS_SEPARATOR ] + } + ] + }, + { + contains: [ + CLASS_KEYWORD, + { + begin: /\s/, + contains: [ CLASS_REFERENCE, CLASS_SEPARATOR ] + }, + ] + } + ] + }; + const RUBY_DEFAULT_CONTAINS = [ STRING_INTERPOLABLE, STRING_NONINTERPOLABLE, UPPER_CASE_CONSTANT, - CLASS_INHERITANCE, CLASS_REFERENCE, + CLASS_INHERITANCE, METHOD_DEFINITION, { // swallow namespace qualifiers before symbols diff --git a/test/markup/ruby/classes.expect.txt b/test/markup/ruby/classes.expect.txt index fdfe4b5cba..1b59166e66 100644 --- a/test/markup/ruby/classes.expect.txt +++ b/test/markup/ruby/classes.expect.txt @@ -6,8 +6,11 @@ ClassName::With.method ::TopLevel::Class -class Foo::Bar::Baz < Qux::Quux::Corge -end - -class Child < Parent -end \ No newline at end of file +class Foo::Bar::Baz +class Foo < Qux +class Foo < ::Qux +class Foo<Qux +class Foo::Bar < Qux +class Foo < Qux::Quux +class Foo::Bar::Baz < Qux::Quux +class Foo::Bar::Baz < Qux::Quux::Corge \ No newline at end of file diff --git a/test/markup/ruby/classes.txt b/test/markup/ruby/classes.txt index 8ee77737cc..91cbca4f7f 100644 --- a/test/markup/ruby/classes.txt +++ b/test/markup/ruby/classes.txt @@ -6,8 +6,11 @@ ClassName::With::Namespace ClassName::With.method ::TopLevel::Class +class Foo::Bar::Baz +class Foo < Qux +class Foo < ::Qux +class Foo