/ src / theme / languages / ruby.js
ruby.js
  1  /*! `ruby` grammar compiled for Highlight.js 11.10.0 */
  2    (function(){
  3      var hljsGrammar = (function () {
  4    'use strict';
  5  
  6    /*
  7    Language: Ruby
  8    Description: Ruby is a dynamic, open source programming language with a focus on simplicity and productivity.
  9    Website: https://www.ruby-lang.org/
 10    Author: Anton Kovalyov <anton@kovalyov.net>
 11    Contributors: Peter Leonov <gojpeg@yandex.ru>, Vasily Polovnyov <vast@whiteants.net>, Loren Segal <lsegal@soen.ca>, Pascal Hurni <phi@ruby-reactive.org>, Cedric Sohrauer <sohrauer@googlemail.com>
 12    Category: common, scripting
 13    */
 14  
 15    function ruby(hljs) {
 16      const regex = hljs.regex;
 17      const RUBY_METHOD_RE = '([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)';
 18      // TODO: move concepts like CAMEL_CASE into `modes.js`
 19      const CLASS_NAME_RE = regex.either(
 20        /\b([A-Z]+[a-z0-9]+)+/,
 21        // ends in caps
 22        /\b([A-Z]+[a-z0-9]+)+[A-Z]+/,
 23      )
 24      ;
 25      const CLASS_NAME_WITH_NAMESPACE_RE = regex.concat(CLASS_NAME_RE, /(::\w+)*/);
 26      // very popular ruby built-ins that one might even assume
 27      // are actual keywords (despite that not being the case)
 28      const PSEUDO_KWS = [
 29        "include",
 30        "extend",
 31        "prepend",
 32        "public",
 33        "private",
 34        "protected",
 35        "raise",
 36        "throw"
 37      ];
 38      const RUBY_KEYWORDS = {
 39        "variable.constant": [
 40          "__FILE__",
 41          "__LINE__",
 42          "__ENCODING__"
 43        ],
 44        "variable.language": [
 45          "self",
 46          "super",
 47        ],
 48        keyword: [
 49          "alias",
 50          "and",
 51          "begin",
 52          "BEGIN",
 53          "break",
 54          "case",
 55          "class",
 56          "defined",
 57          "do",
 58          "else",
 59          "elsif",
 60          "end",
 61          "END",
 62          "ensure",
 63          "for",
 64          "if",
 65          "in",
 66          "module",
 67          "next",
 68          "not",
 69          "or",
 70          "redo",
 71          "require",
 72          "rescue",
 73          "retry",
 74          "return",
 75          "then",
 76          "undef",
 77          "unless",
 78          "until",
 79          "when",
 80          "while",
 81          "yield",
 82          ...PSEUDO_KWS
 83        ],
 84        built_in: [
 85          "proc",
 86          "lambda",
 87          "attr_accessor",
 88          "attr_reader",
 89          "attr_writer",
 90          "define_method",
 91          "private_constant",
 92          "module_function"
 93        ],
 94        literal: [
 95          "true",
 96          "false",
 97          "nil"
 98        ]
 99      };
100      const YARDOCTAG = {
101        className: 'doctag',
102        begin: '@[A-Za-z]+'
103      };
104      const IRB_OBJECT = {
105        begin: '#<',
106        end: '>'
107      };
108      const COMMENT_MODES = [
109        hljs.COMMENT(
110          '#',
111          '$',
112          { contains: [ YARDOCTAG ] }
113        ),
114        hljs.COMMENT(
115          '^=begin',
116          '^=end',
117          {
118            contains: [ YARDOCTAG ],
119            relevance: 10
120          }
121        ),
122        hljs.COMMENT('^__END__', hljs.MATCH_NOTHING_RE)
123      ];
124      const SUBST = {
125        className: 'subst',
126        begin: /#\{/,
127        end: /\}/,
128        keywords: RUBY_KEYWORDS
129      };
130      const STRING = {
131        className: 'string',
132        contains: [
133          hljs.BACKSLASH_ESCAPE,
134          SUBST
135        ],
136        variants: [
137          {
138            begin: /'/,
139            end: /'/
140          },
141          {
142            begin: /"/,
143            end: /"/
144          },
145          {
146            begin: /`/,
147            end: /`/
148          },
149          {
150            begin: /%[qQwWx]?\(/,
151            end: /\)/
152          },
153          {
154            begin: /%[qQwWx]?\[/,
155            end: /\]/
156          },
157          {
158            begin: /%[qQwWx]?\{/,
159            end: /\}/
160          },
161          {
162            begin: /%[qQwWx]?</,
163            end: />/
164          },
165          {
166            begin: /%[qQwWx]?\//,
167            end: /\//
168          },
169          {
170            begin: /%[qQwWx]?%/,
171            end: /%/
172          },
173          {
174            begin: /%[qQwWx]?-/,
175            end: /-/
176          },
177          {
178            begin: /%[qQwWx]?\|/,
179            end: /\|/
180          },
181          // in the following expressions, \B in the beginning suppresses recognition of ?-sequences
182          // where ? is the last character of a preceding identifier, as in: `func?4`
183          { begin: /\B\?(\\\d{1,3})/ },
184          { begin: /\B\?(\\x[A-Fa-f0-9]{1,2})/ },
185          { begin: /\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/ },
186          { begin: /\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/ },
187          { begin: /\B\?\\(c|C-)[\x20-\x7e]/ },
188          { begin: /\B\?\\?\S/ },
189          // heredocs
190          {
191            // this guard makes sure that we have an entire heredoc and not a false
192            // positive (auto-detect, etc.)
193            begin: regex.concat(
194              /<<[-~]?'?/,
195              regex.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/)
196            ),
197            contains: [
198              hljs.END_SAME_AS_BEGIN({
199                begin: /(\w+)/,
200                end: /(\w+)/,
201                contains: [
202                  hljs.BACKSLASH_ESCAPE,
203                  SUBST
204                ]
205              })
206            ]
207          }
208        ]
209      };
210  
211      // Ruby syntax is underdocumented, but this grammar seems to be accurate
212      // as of version 2.7.2 (confirmed with (irb and `Ripper.sexp(...)`)
213      // https://docs.ruby-lang.org/en/2.7.0/doc/syntax/literals_rdoc.html#label-Numbers
214      const decimal = '[1-9](_?[0-9])*|0';
215      const digits = '[0-9](_?[0-9])*';
216      const NUMBER = {
217        className: 'number',
218        relevance: 0,
219        variants: [
220          // decimal integer/float, optionally exponential or rational, optionally imaginary
221          { begin: `\\b(${decimal})(\\.(${digits}))?([eE][+-]?(${digits})|r)?i?\\b` },
222  
223          // explicit decimal/binary/octal/hexadecimal integer,
224          // optionally rational and/or imaginary
225          { begin: "\\b0[dD][0-9](_?[0-9])*r?i?\\b" },
226          { begin: "\\b0[bB][0-1](_?[0-1])*r?i?\\b" },
227          { begin: "\\b0[oO][0-7](_?[0-7])*r?i?\\b" },
228          { begin: "\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b" },
229  
230          // 0-prefixed implicit octal integer, optionally rational and/or imaginary
231          { begin: "\\b0(_?[0-7])+r?i?\\b" }
232        ]
233      };
234  
235      const PARAMS = {
236        variants: [
237          {
238            match: /\(\)/,
239          },
240          {
241            className: 'params',
242            begin: /\(/,
243            end: /(?=\))/,
244            excludeBegin: true,
245            endsParent: true,
246            keywords: RUBY_KEYWORDS,
247          }
248        ]
249      };
250  
251      const INCLUDE_EXTEND = {
252        match: [
253          /(include|extend)\s+/,
254          CLASS_NAME_WITH_NAMESPACE_RE
255        ],
256        scope: {
257          2: "title.class"
258        },
259        keywords: RUBY_KEYWORDS
260      };
261  
262      const CLASS_DEFINITION = {
263        variants: [
264          {
265            match: [
266              /class\s+/,
267              CLASS_NAME_WITH_NAMESPACE_RE,
268              /\s+<\s+/,
269              CLASS_NAME_WITH_NAMESPACE_RE
270            ]
271          },
272          {
273            match: [
274              /\b(class|module)\s+/,
275              CLASS_NAME_WITH_NAMESPACE_RE
276            ]
277          }
278        ],
279        scope: {
280          2: "title.class",
281          4: "title.class.inherited"
282        },
283        keywords: RUBY_KEYWORDS
284      };
285  
286      const UPPER_CASE_CONSTANT = {
287        relevance: 0,
288        match: /\b[A-Z][A-Z_0-9]+\b/,
289        className: "variable.constant"
290      };
291  
292      const METHOD_DEFINITION = {
293        match: [
294          /def/, /\s+/,
295          RUBY_METHOD_RE
296        ],
297        scope: {
298          1: "keyword",
299          3: "title.function"
300        },
301        contains: [
302          PARAMS
303        ]
304      };
305  
306      const OBJECT_CREATION = {
307        relevance: 0,
308        match: [
309          CLASS_NAME_WITH_NAMESPACE_RE,
310          /\.new[. (]/
311        ],
312        scope: {
313          1: "title.class"
314        }
315      };
316  
317      // CamelCase
318      const CLASS_REFERENCE = {
319        relevance: 0,
320        match: CLASS_NAME_RE,
321        scope: "title.class"
322      };
323  
324      const RUBY_DEFAULT_CONTAINS = [
325        STRING,
326        CLASS_DEFINITION,
327        INCLUDE_EXTEND,
328        OBJECT_CREATION,
329        UPPER_CASE_CONSTANT,
330        CLASS_REFERENCE,
331        METHOD_DEFINITION,
332        {
333          // swallow namespace qualifiers before symbols
334          begin: hljs.IDENT_RE + '::' },
335        {
336          className: 'symbol',
337          begin: hljs.UNDERSCORE_IDENT_RE + '(!|\\?)?:',
338          relevance: 0
339        },
340        {
341          className: 'symbol',
342          begin: ':(?!\\s)',
343          contains: [
344            STRING,
345            { begin: RUBY_METHOD_RE }
346          ],
347          relevance: 0
348        },
349        NUMBER,
350        {
351          // negative-look forward attempts to prevent false matches like:
352          // @ident@ or $ident$ that might indicate this is not ruby at all
353          className: "variable",
354          begin: '(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])' + `(?![A-Za-z])(?![@$?'])`
355        },
356        {
357          className: 'params',
358          begin: /\|/,
359          end: /\|/,
360          excludeBegin: true,
361          excludeEnd: true,
362          relevance: 0, // this could be a lot of things (in other languages) other than params
363          keywords: RUBY_KEYWORDS
364        },
365        { // regexp container
366          begin: '(' + hljs.RE_STARTERS_RE + '|unless)\\s*',
367          keywords: 'unless',
368          contains: [
369            {
370              className: 'regexp',
371              contains: [
372                hljs.BACKSLASH_ESCAPE,
373                SUBST
374              ],
375              illegal: /\n/,
376              variants: [
377                {
378                  begin: '/',
379                  end: '/[a-z]*'
380                },
381                {
382                  begin: /%r\{/,
383                  end: /\}[a-z]*/
384                },
385                {
386                  begin: '%r\\(',
387                  end: '\\)[a-z]*'
388                },
389                {
390                  begin: '%r!',
391                  end: '![a-z]*'
392                },
393                {
394                  begin: '%r\\[',
395                  end: '\\][a-z]*'
396                }
397              ]
398            }
399          ].concat(IRB_OBJECT, COMMENT_MODES),
400          relevance: 0
401        }
402      ].concat(IRB_OBJECT, COMMENT_MODES);
403  
404      SUBST.contains = RUBY_DEFAULT_CONTAINS;
405      PARAMS.contains = RUBY_DEFAULT_CONTAINS;
406  
407      // >>
408      // ?>
409      const SIMPLE_PROMPT = "[>?]>";
410      // irb(main):001:0>
411      const DEFAULT_PROMPT = "[\\w#]+\\(\\w+\\):\\d+:\\d+[>*]";
412      const RVM_PROMPT = "(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>";
413  
414      const IRB_DEFAULT = [
415        {
416          begin: /^\s*=>/,
417          starts: {
418            end: '$',
419            contains: RUBY_DEFAULT_CONTAINS
420          }
421        },
422        {
423          className: 'meta.prompt',
424          begin: '^(' + SIMPLE_PROMPT + "|" + DEFAULT_PROMPT + '|' + RVM_PROMPT + ')(?=[ ])',
425          starts: {
426            end: '$',
427            keywords: RUBY_KEYWORDS,
428            contains: RUBY_DEFAULT_CONTAINS
429          }
430        }
431      ];
432  
433      COMMENT_MODES.unshift(IRB_OBJECT);
434  
435      return {
436        name: 'Ruby',
437        aliases: [
438          'rb',
439          'gemspec',
440          'podspec',
441          'thor',
442          'irb'
443        ],
444        keywords: RUBY_KEYWORDS,
445        illegal: /\/\*/,
446        contains: [ hljs.SHEBANG({ binary: "ruby" }) ]
447          .concat(IRB_DEFAULT)
448          .concat(COMMENT_MODES)
449          .concat(RUBY_DEFAULT_CONTAINS)
450      };
451    }
452  
453    return ruby;
454  
455  })();
456  
457      hljs.registerLanguage('ruby', hljsGrammar);
458    })();