diff --git a/CHANGES.md b/CHANGES.md
index 2ad4fc51f6..78adafe977 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -11,6 +11,7 @@ New Grammars:
Grammars:
+- enh(ruby) lots of small Ruby cleanups/improvements [Josh Goebel][]
- enh(objectivec) add `type` and `variable.language` scopes [Josh Goebel][]
- enh(xml) support processing instructions (#3492) [Josh Goebel][]
- enh(ruby ) better support multi-line IRB prompts
diff --git a/src/highlight.js b/src/highlight.js
index ed0e187fd5..f81cc458e9 100644
--- a/src/highlight.js
+++ b/src/highlight.js
@@ -277,8 +277,8 @@ const HLJS = function(hljs) {
*/
function emitMultiClass(scope, match) {
let i = 1;
- // eslint-disable-next-line no-undefined
- while (match[i] !== undefined) {
+ const max = match.length - 1;
+ while (i <= max) {
if (!scope._emit[i]) { i++; continue; }
const klass = language.classNameAliases[scope[i]] || scope[i];
const text = match[i];
diff --git a/src/languages/ruby.js b/src/languages/ruby.js
index 59e77edec9..0504039a0a 100644
--- a/src/languages/ruby.js
+++ b/src/languages/ruby.js
@@ -10,15 +10,71 @@ Category: common
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 RUBY_KEYWORDS = {
- keyword:
- 'and then defined module in return redo if BEGIN retry end for self when '
- + 'next until do begin unless END rescue else break undef not super class case '
- + 'require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor '
- + '__FILE__',
- built_in: 'proc lambda',
- literal:
- 'true false nil'
+ "variable.constant": [
+ "__FILE__",
+ "__LINE__"
+ ],
+ "variable.language": [
+ "self",
+ "super",
+ ],
+ keyword: [
+ "alias",
+ "and",
+ "attr_accessor",
+ "attr_reader",
+ "attr_writer",
+ "begin",
+ "BEGIN",
+ "break",
+ "case",
+ "class",
+ "defined",
+ "do",
+ "else",
+ "elsif",
+ "end",
+ "END",
+ "ensure",
+ "for",
+ "if",
+ "in",
+ "include",
+ "module",
+ "next",
+ "not",
+ "or",
+ "redo",
+ "require",
+ "rescue",
+ "retry",
+ "return",
+ "then",
+ "undef",
+ "unless",
+ "until",
+ "when",
+ "while",
+ "yield",
+ ],
+ built_in: [
+ "proc",
+ "lambda"
+ ],
+ literal: [
+ "true",
+ "false",
+ "nil"
+ ]
};
const YARDOCTAG = {
className: 'doctag',
@@ -42,7 +98,7 @@ export default function(hljs) {
relevance: 10
}
),
- hljs.COMMENT('^__END__', '\\n$')
+ hljs.COMMENT('^__END__', hljs.MATCH_NOTHING_RE)
];
const SUBST = {
className: 'subst',
@@ -156,49 +212,82 @@ export default function(hljs) {
};
const PARAMS = {
- className: 'params',
- begin: '\\(',
- end: '\\)',
- endsParent: true,
+ variants: [
+ {
+ match: /\(\)/,
+ },
+ {
+ className: 'params',
+ begin: /\(/,
+ end: /(?=\))/,
+ excludeBegin: true,
+ endsParent: true,
+ keywords: RUBY_KEYWORDS,
+ }
+ ]
+ };
+
+ const CLASS_DEFINITION = {
+ variants: [
+ {
+ match: [
+ /class\s+/,
+ CLASS_NAME_WITH_NAMESPACE_RE,
+ /\s+<\s+/,
+ CLASS_NAME_WITH_NAMESPACE_RE
+ ]
+ },
+ {
+ match: [
+ /class\s+/,
+ CLASS_NAME_WITH_NAMESPACE_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/,
+ className: "variable.constant"
+ };
+
+ const METHOD_DEFINITION = {
+ match: [
+ /def/, /\s+/,
+ RUBY_METHOD_RE
+ ],
+ scope: {
+ 1: "keyword",
+ 3: "title.function"
+ },
+ contains: [
+ PARAMS
+ ]
+ };
+
+ const OBJECT_CREATION = {
+ relevance: 0,
+ match: [
+ CLASS_NAME_WITH_NAMESPACE_RE,
+ /\.new[ (]/
+ ],
+ scope: {
+ 1: "title.class"
+ }
+ };
+
const RUBY_DEFAULT_CONTAINS = [
STRING,
- {
- className: 'class',
- beginKeywords: 'class module',
- end: '$|;',
- illegal: /=/,
- contains: [
- hljs.inherit(hljs.TITLE_MODE, { begin: '[A-Za-z_]\\w*(::\\w+)*(\\?|!)?' }),
- {
- begin: '<\\s*',
- contains: [
- {
- begin: '(' + hljs.IDENT_RE + '::)?' + hljs.IDENT_RE,
- // we already get points for <, we don't need poitns
- // for the name also
- relevance: 0
- }
- ]
- }
- ].concat(COMMENT_MODES)
- },
- {
- className: 'function',
- // def method_name(
- // def method_name;
- // def method_name (end of line)
- begin: regex.concat(/def\s+/, regex.lookahead(RUBY_METHOD_RE + "\\s*(\\(|;|$)")),
- relevance: 0, // relevance comes from kewords
- keywords: "def",
- end: '$|;',
- contains: [
- hljs.inherit(hljs.TITLE_MODE, { begin: RUBY_METHOD_RE }),
- PARAMS
- ].concat(COMMENT_MODES)
- },
+ CLASS_DEFINITION,
+ OBJECT_CREATION,
+ UPPER_CASE_CONSTANT,
+ METHOD_DEFINITION,
{
// swallow namespace qualifiers before symbols
begin: hljs.IDENT_RE + '::' },
@@ -227,6 +316,8 @@ export default function(hljs) {
className: 'params',
begin: /\|/,
end: /\|/,
+ excludeBegin: true,
+ excludeEnd: true,
relevance: 0, // this could be a lot of things (in other languages) other than params
keywords: RUBY_KEYWORDS
},
diff --git a/test/markup/erb/default.expect.txt b/test/markup/erb/default.expect.txt
index 47a8cf878d..2473ac1b61 100644
--- a/test/markup/erb/default.expect.txt
+++ b/test/markup/erb/default.expect.txt
@@ -1,6 +1,6 @@
-<% @posts.each do |post| %>
+<% @posts.each do |post| %>
<p><%= link_to post.title, post %></p>
<% end %>
diff --git a/test/markup/haml/default.expect.txt b/test/markup/haml/default.expect.txt
index ccb3a210f4..7a422eb11f 100644
--- a/test/markup/haml/default.expect.txt
+++ b/test/markup/haml/default.expect.txt
@@ -5,7 +5,7 @@
%ul(style='margin: 0')
- -items.each do |i|
+ -items.each do |i|
%i= i
= variable
=variable2
diff --git a/test/markup/ruby/heredoc.expect.txt b/test/markup/ruby/heredoc.expect.txt
index 3f9379314e..cb3fd9f31b 100644
--- a/test/markup/ruby/heredoc.expect.txt
+++ b/test/markup/ruby/heredoc.expect.txt
@@ -13,7 +13,7 @@ message = <<-MESSAGE.chomp
This looks good
MESSAGE
-def foo()
+def foo()
msg = <<-HTML
<div>
<h4>#{bar}</h4>
@@ -21,7 +21,7 @@ MESSAGE
HTML
end
-def baz()
+def baz()
msg = <<~FOO
<div>
<h4>#{bar}</h4>
diff --git a/test/markup/ruby/prompt.expect.txt b/test/markup/ruby/prompt.expect.txt
index 0d6baee8c5..bef182b866 100644
--- a/test/markup/ruby/prompt.expect.txt
+++ b/test/markup/ruby/prompt.expect.txt
@@ -7,7 +7,7 @@
jruby-1.7.16 :001 > "RVM-Format"
->> obj = OpenStruct.new :integer => 987, :symbol => :so_great
+>> obj = OpenStruct.new :integer => 987, :symbol => :so_great
=> #<OpenStruct integer=987, symbol=:so_great>
>> [obj,obj,obj]
=> [#<OpenStruct integer=987, symbol=:so_great>, #<OpenStruct integer=987, symbol=:so_great>, #<OpenStruct integer=987, symbol=:so_great>]
@@ -22,8 +22,8 @@
irb(main):002:0> test = 1
-irb(main):001:1* class Secret
-irb(main):002:2* def [](x)
+irb(main):001:1* class Secret
+irb(main):002:2* def [](x)
irb(main):003:2* "TREASURE" if x==42
irb(main):004:1* end
irb(main):005:0> end