core.js (64062B)
1 // https://github.com/substack/deep-freeze/blob/master/index.js 2 /** @param {any} obj */ 3 function deepFreeze(obj) { 4 Object.freeze(obj); 5 6 var objIsFunction = typeof obj === 'function'; 7 8 Object.getOwnPropertyNames(obj).forEach(function(prop) { 9 if (Object.hasOwnProperty.call(obj, prop) 10 && obj[prop] !== null 11 && (typeof obj[prop] === "object" || typeof obj[prop] === "function") 12 // IE11 fix: https://github.com/highlightjs/highlight.js/issues/2318 13 // TODO: remove in the future 14 && (objIsFunction ? prop !== 'caller' && prop !== 'callee' && prop !== 'arguments' : true) 15 && !Object.isFrozen(obj[prop])) { 16 deepFreeze(obj[prop]); 17 } 18 }); 19 20 return obj; 21 } 22 23 class Response { 24 /** 25 * @param {CompiledMode} mode 26 */ 27 constructor(mode) { 28 // eslint-disable-next-line no-undefined 29 if (mode.data === undefined) mode.data = {}; 30 31 this.data = mode.data; 32 } 33 34 ignoreMatch() { 35 this.ignore = true; 36 } 37 } 38 39 /** 40 * @param {string} value 41 * @returns {string} 42 */ 43 function escapeHTML(value) { 44 return value 45 .replace(/&/g, '&') 46 .replace(/</g, '<') 47 .replace(/>/g, '>') 48 .replace(/"/g, '"') 49 .replace(/'/g, '''); 50 } 51 52 /** 53 * performs a shallow merge of multiple objects into one 54 * 55 * @template T 56 * @param {T} original 57 * @param {Record<string,any>[]} objects 58 * @returns {T} a single new object 59 */ 60 function inherit(original, ...objects) { 61 /** @type Record<string,any> */ 62 var result = {}; 63 64 for (const key in original) { 65 result[key] = original[key]; 66 } 67 objects.forEach(function(obj) { 68 for (const key in obj) { 69 result[key] = obj[key]; 70 } 71 }); 72 return /** @type {T} */ (result); 73 } 74 75 /* Stream merging */ 76 77 /** 78 * @typedef Event 79 * @property {'start'|'stop'} event 80 * @property {number} offset 81 * @property {Node} node 82 */ 83 84 /** 85 * @param {Node} node 86 */ 87 function tag(node) { 88 return node.nodeName.toLowerCase(); 89 } 90 91 /** 92 * @param {Node} node 93 */ 94 function nodeStream(node) { 95 /** @type Event[] */ 96 var result = []; 97 (function _nodeStream(node, offset) { 98 for (var child = node.firstChild; child; child = child.nextSibling) { 99 if (child.nodeType === 3) { 100 offset += child.nodeValue.length; 101 } else if (child.nodeType === 1) { 102 result.push({ 103 event: 'start', 104 offset: offset, 105 node: child 106 }); 107 offset = _nodeStream(child, offset); 108 // Prevent void elements from having an end tag that would actually 109 // double them in the output. There are more void elements in HTML 110 // but we list only those realistically expected in code display. 111 if (!tag(child).match(/br|hr|img|input/)) { 112 result.push({ 113 event: 'stop', 114 offset: offset, 115 node: child 116 }); 117 } 118 } 119 } 120 return offset; 121 })(node, 0); 122 return result; 123 } 124 125 /** 126 * @param {any} original - the original stream 127 * @param {any} highlighted - stream of the highlighted source 128 * @param {string} value - the original source itself 129 */ 130 function mergeStreams(original, highlighted, value) { 131 var processed = 0; 132 var result = ''; 133 var nodeStack = []; 134 135 function selectStream() { 136 if (!original.length || !highlighted.length) { 137 return original.length ? original : highlighted; 138 } 139 if (original[0].offset !== highlighted[0].offset) { 140 return (original[0].offset < highlighted[0].offset) ? original : highlighted; 141 } 142 143 /* 144 To avoid starting the stream just before it should stop the order is 145 ensured that original always starts first and closes last: 146 147 if (event1 == 'start' && event2 == 'start') 148 return original; 149 if (event1 == 'start' && event2 == 'stop') 150 return highlighted; 151 if (event1 == 'stop' && event2 == 'start') 152 return original; 153 if (event1 == 'stop' && event2 == 'stop') 154 return highlighted; 155 156 ... which is collapsed to: 157 */ 158 return highlighted[0].event === 'start' ? original : highlighted; 159 } 160 161 /** 162 * @param {Node} node 163 */ 164 function open(node) { 165 /** @param {Attr} attr */ 166 function attr_str(attr) { 167 return ' ' + attr.nodeName + '="' + escapeHTML(attr.value) + '"'; 168 } 169 // @ts-ignore 170 result += '<' + tag(node) + [].map.call(node.attributes, attr_str).join('') + '>'; 171 } 172 173 /** 174 * @param {Node} node 175 */ 176 function close(node) { 177 result += '</' + tag(node) + '>'; 178 } 179 180 /** 181 * @param {Event} event 182 */ 183 function render(event) { 184 (event.event === 'start' ? open : close)(event.node); 185 } 186 187 while (original.length || highlighted.length) { 188 var stream = selectStream(); 189 result += escapeHTML(value.substring(processed, stream[0].offset)); 190 processed = stream[0].offset; 191 if (stream === original) { 192 /* 193 On any opening or closing tag of the original markup we first close 194 the entire highlighted node stack, then render the original tag along 195 with all the following original tags at the same offset and then 196 reopen all the tags on the highlighted stack. 197 */ 198 nodeStack.reverse().forEach(close); 199 do { 200 render(stream.splice(0, 1)[0]); 201 stream = selectStream(); 202 } while (stream === original && stream.length && stream[0].offset === processed); 203 nodeStack.reverse().forEach(open); 204 } else { 205 if (stream[0].event === 'start') { 206 nodeStack.push(stream[0].node); 207 } else { 208 nodeStack.pop(); 209 } 210 render(stream.splice(0, 1)[0]); 211 } 212 } 213 return result + escapeHTML(value.substr(processed)); 214 } 215 216 var utils = /*#__PURE__*/Object.freeze({ 217 __proto__: null, 218 escapeHTML: escapeHTML, 219 inherit: inherit, 220 nodeStream: nodeStream, 221 mergeStreams: mergeStreams 222 }); 223 224 /** 225 * @typedef {object} Renderer 226 * @property {(text: string) => void} addText 227 * @property {(node: Node) => void} openNode 228 * @property {(node: Node) => void} closeNode 229 * @property {() => string} value 230 */ 231 232 /** @typedef {{kind?: string, sublanguage?: boolean}} Node */ 233 /** @typedef {{walk: (r: Renderer) => void}} Tree */ 234 /** */ 235 236 const SPAN_CLOSE = '</span>'; 237 238 /** 239 * Determines if a node needs to be wrapped in <span> 240 * 241 * @param {Node} node */ 242 const emitsWrappingTags = (node) => { 243 return !!node.kind; 244 }; 245 246 /** @type {Renderer} */ 247 class HTMLRenderer { 248 /** 249 * Creates a new HTMLRenderer 250 * 251 * @param {Tree} parseTree - the parse tree (must support `walk` API) 252 * @param {{classPrefix: string}} options 253 */ 254 constructor(parseTree, options) { 255 this.buffer = ""; 256 this.classPrefix = options.classPrefix; 257 parseTree.walk(this); 258 } 259 260 /** 261 * Adds texts to the output stream 262 * 263 * @param {string} text */ 264 addText(text) { 265 this.buffer += escapeHTML(text); 266 } 267 268 /** 269 * Adds a node open to the output stream (if needed) 270 * 271 * @param {Node} node */ 272 openNode(node) { 273 if (!emitsWrappingTags(node)) return; 274 275 let className = node.kind; 276 if (!node.sublanguage) { 277 className = `${this.classPrefix}${className}`; 278 } 279 this.span(className); 280 } 281 282 /** 283 * Adds a node close to the output stream (if needed) 284 * 285 * @param {Node} node */ 286 closeNode(node) { 287 if (!emitsWrappingTags(node)) return; 288 289 this.buffer += SPAN_CLOSE; 290 } 291 292 /** 293 * returns the accumulated buffer 294 */ 295 value() { 296 return this.buffer; 297 } 298 299 // helpers 300 301 /** 302 * Builds a span element 303 * 304 * @param {string} className */ 305 span(className) { 306 this.buffer += `<span class="${className}">`; 307 } 308 } 309 310 /** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} | string} Node */ 311 /** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} } DataNode */ 312 /** */ 313 314 class TokenTree { 315 constructor() { 316 /** @type DataNode */ 317 this.rootNode = { children: [] }; 318 this.stack = [this.rootNode]; 319 } 320 321 get top() { 322 return this.stack[this.stack.length - 1]; 323 } 324 325 get root() { return this.rootNode; } 326 327 /** @param {Node} node */ 328 add(node) { 329 this.top.children.push(node); 330 } 331 332 /** @param {string} kind */ 333 openNode(kind) { 334 /** @type Node */ 335 const node = { kind, children: [] }; 336 this.add(node); 337 this.stack.push(node); 338 } 339 340 closeNode() { 341 if (this.stack.length > 1) { 342 return this.stack.pop(); 343 } 344 // eslint-disable-next-line no-undefined 345 return undefined; 346 } 347 348 closeAllNodes() { 349 while (this.closeNode()); 350 } 351 352 toJSON() { 353 return JSON.stringify(this.rootNode, null, 4); 354 } 355 356 /** 357 * @typedef { import("./html_renderer").Renderer } Renderer 358 * @param {Renderer} builder 359 */ 360 walk(builder) { 361 // this does not 362 return this.constructor._walk(builder, this.rootNode); 363 // this works 364 // return TokenTree._walk(builder, this.rootNode); 365 } 366 367 /** 368 * @param {Renderer} builder 369 * @param {Node} node 370 */ 371 static _walk(builder, node) { 372 if (typeof node === "string") { 373 builder.addText(node); 374 } else if (node.children) { 375 builder.openNode(node); 376 node.children.forEach((child) => this._walk(builder, child)); 377 builder.closeNode(node); 378 } 379 return builder; 380 } 381 382 /** 383 * @param {Node} node 384 */ 385 static _collapse(node) { 386 if (typeof node === "string") return; 387 if (!node.children) return; 388 389 if (node.children.every(el => typeof el === "string")) { 390 // node.text = node.children.join(""); 391 // delete node.children; 392 node.children = [node.children.join("")]; 393 } else { 394 node.children.forEach((child) => { 395 TokenTree._collapse(child); 396 }); 397 } 398 } 399 } 400 401 /** 402 Currently this is all private API, but this is the minimal API necessary 403 that an Emitter must implement to fully support the parser. 404 405 Minimal interface: 406 407 - addKeyword(text, kind) 408 - addText(text) 409 - addSublanguage(emitter, subLanguageName) 410 - finalize() 411 - openNode(kind) 412 - closeNode() 413 - closeAllNodes() 414 - toHTML() 415 416 */ 417 418 /** 419 * @implements {Emitter} 420 */ 421 class TokenTreeEmitter extends TokenTree { 422 /** 423 * @param {*} options 424 */ 425 constructor(options) { 426 super(); 427 this.options = options; 428 } 429 430 /** 431 * @param {string} text 432 * @param {string} kind 433 */ 434 addKeyword(text, kind) { 435 if (text === "") { return; } 436 437 this.openNode(kind); 438 this.addText(text); 439 this.closeNode(); 440 } 441 442 /** 443 * @param {string} text 444 */ 445 addText(text) { 446 if (text === "") { return; } 447 448 this.add(text); 449 } 450 451 /** 452 * @param {Emitter & {root: DataNode}} emitter 453 * @param {string} name 454 */ 455 addSublanguage(emitter, name) { 456 /** @type DataNode */ 457 const node = emitter.root; 458 node.kind = name; 459 node.sublanguage = true; 460 this.add(node); 461 } 462 463 toHTML() { 464 const renderer = new HTMLRenderer(this, this.options); 465 return renderer.value(); 466 } 467 468 finalize() { 469 return true; 470 } 471 } 472 473 /** 474 * @param {string} value 475 * @returns {RegExp} 476 * */ 477 function escape(value) { 478 return new RegExp(value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'm'); 479 } 480 481 /** 482 * @param {RegExp | string } re 483 * @returns {string} 484 */ 485 function source(re) { 486 if (!re) return null; 487 if (typeof re === "string") return re; 488 489 return re.source; 490 } 491 492 /** 493 * @param {...(RegExp | string) } args 494 * @returns {string} 495 */ 496 function concat(...args) { 497 const joined = args.map((x) => source(x)).join(""); 498 return joined; 499 } 500 501 /** 502 * @param {RegExp} re 503 * @returns {number} 504 */ 505 function countMatchGroups(re) { 506 return (new RegExp(re.toString() + '|')).exec('').length - 1; 507 } 508 509 /** 510 * Does lexeme start with a regular expression match at the beginning 511 * @param {RegExp} re 512 * @param {string} lexeme 513 */ 514 function startsWith(re, lexeme) { 515 var match = re && re.exec(lexeme); 516 return match && match.index === 0; 517 } 518 519 // join logically computes regexps.join(separator), but fixes the 520 // backreferences so they continue to match. 521 // it also places each individual regular expression into it's own 522 // match group, keeping track of the sequencing of those match groups 523 // is currently an exercise for the caller. :-) 524 /** 525 * @param {(string | RegExp)[]} regexps 526 * @param {string} separator 527 * @returns {string} 528 */ 529 function join(regexps, separator = "|") { 530 // backreferenceRe matches an open parenthesis or backreference. To avoid 531 // an incorrect parse, it additionally matches the following: 532 // - [...] elements, where the meaning of parentheses and escapes change 533 // - other escape sequences, so we do not misparse escape sequences as 534 // interesting elements 535 // - non-matching or lookahead parentheses, which do not capture. These 536 // follow the '(' with a '?'. 537 var backreferenceRe = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./; 538 var numCaptures = 0; 539 var ret = ''; 540 for (var i = 0; i < regexps.length; i++) { 541 numCaptures += 1; 542 var offset = numCaptures; 543 var re = source(regexps[i]); 544 if (i > 0) { 545 ret += separator; 546 } 547 ret += "("; 548 while (re.length > 0) { 549 var match = backreferenceRe.exec(re); 550 if (match == null) { 551 ret += re; 552 break; 553 } 554 ret += re.substring(0, match.index); 555 re = re.substring(match.index + match[0].length); 556 if (match[0][0] === '\\' && match[1]) { 557 // Adjust the backreference. 558 ret += '\\' + String(Number(match[1]) + offset); 559 } else { 560 ret += match[0]; 561 if (match[0] === '(') { 562 numCaptures++; 563 } 564 } 565 } 566 ret += ")"; 567 } 568 return ret; 569 } 570 571 // Common regexps 572 const IDENT_RE = '[a-zA-Z]\\w*'; 573 const UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\w*'; 574 const NUMBER_RE = '\\b\\d+(\\.\\d+)?'; 575 const C_NUMBER_RE = '(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float 576 const BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b... 577 const RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~'; 578 579 /** 580 * @param { Partial<Mode> & {binary?: string | RegExp} } opts 581 */ 582 const SHEBANG = (opts = {}) => { 583 const beginShebang = /^#![ ]*\//; 584 if (opts.binary) { 585 opts.begin = concat( 586 beginShebang, 587 /.*\b/, 588 opts.binary, 589 /\b.*/); 590 } 591 return inherit({ 592 className: 'meta', 593 begin: beginShebang, 594 end: /$/, 595 relevance: 0, 596 /** @type {ModeCallback} */ 597 "on:begin": (m, resp) => { 598 if (m.index !== 0) resp.ignoreMatch(); 599 } 600 }, opts); 601 }; 602 603 // Common modes 604 const BACKSLASH_ESCAPE = { 605 begin: '\\\\[\\s\\S]', relevance: 0 606 }; 607 const APOS_STRING_MODE = { 608 className: 'string', 609 begin: '\'', 610 end: '\'', 611 illegal: '\\n', 612 contains: [BACKSLASH_ESCAPE] 613 }; 614 const QUOTE_STRING_MODE = { 615 className: 'string', 616 begin: '"', 617 end: '"', 618 illegal: '\\n', 619 contains: [BACKSLASH_ESCAPE] 620 }; 621 const PHRASAL_WORDS_MODE = { 622 begin: /\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ 623 }; 624 /** 625 * Creates a comment mode 626 * 627 * @param {string | RegExp} begin 628 * @param {string | RegExp} end 629 * @param {Mode | {}} [modeOptions] 630 * @returns {Partial<Mode>} 631 */ 632 const COMMENT = function(begin, end, modeOptions = {}) { 633 var mode = inherit( 634 { 635 className: 'comment', 636 begin, 637 end, 638 contains: [] 639 }, 640 modeOptions 641 ); 642 mode.contains.push(PHRASAL_WORDS_MODE); 643 mode.contains.push({ 644 className: 'doctag', 645 begin: '(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):', 646 relevance: 0 647 }); 648 return mode; 649 }; 650 const C_LINE_COMMENT_MODE = COMMENT('//', '$'); 651 const C_BLOCK_COMMENT_MODE = COMMENT('/\\*', '\\*/'); 652 const HASH_COMMENT_MODE = COMMENT('#', '$'); 653 const NUMBER_MODE = { 654 className: 'number', 655 begin: NUMBER_RE, 656 relevance: 0 657 }; 658 const C_NUMBER_MODE = { 659 className: 'number', 660 begin: C_NUMBER_RE, 661 relevance: 0 662 }; 663 const BINARY_NUMBER_MODE = { 664 className: 'number', 665 begin: BINARY_NUMBER_RE, 666 relevance: 0 667 }; 668 const CSS_NUMBER_MODE = { 669 className: 'number', 670 begin: NUMBER_RE + '(' + 671 '%|em|ex|ch|rem' + 672 '|vw|vh|vmin|vmax' + 673 '|cm|mm|in|pt|pc|px' + 674 '|deg|grad|rad|turn' + 675 '|s|ms' + 676 '|Hz|kHz' + 677 '|dpi|dpcm|dppx' + 678 ')?', 679 relevance: 0 680 }; 681 const REGEXP_MODE = { 682 // this outer rule makes sure we actually have a WHOLE regex and not simply 683 // an expression such as: 684 // 685 // 3 / something 686 // 687 // (which will then blow up when regex's `illegal` sees the newline) 688 begin: /(?=\/[^/\n]*\/)/, 689 contains: [{ 690 className: 'regexp', 691 begin: /\//, 692 end: /\/[gimuy]*/, 693 illegal: /\n/, 694 contains: [ 695 BACKSLASH_ESCAPE, 696 { 697 begin: /\[/, 698 end: /\]/, 699 relevance: 0, 700 contains: [BACKSLASH_ESCAPE] 701 } 702 ] 703 }] 704 }; 705 const TITLE_MODE = { 706 className: 'title', 707 begin: IDENT_RE, 708 relevance: 0 709 }; 710 const UNDERSCORE_TITLE_MODE = { 711 className: 'title', 712 begin: UNDERSCORE_IDENT_RE, 713 relevance: 0 714 }; 715 const METHOD_GUARD = { 716 // excludes method names from keyword processing 717 begin: '\\.\\s*' + UNDERSCORE_IDENT_RE, 718 relevance: 0 719 }; 720 721 /** 722 * Adds end same as begin mechanics to a mode 723 * 724 * Your mode must include at least a single () match group as that first match 725 * group is what is used for comparison 726 * @param {Partial<Mode>} mode 727 */ 728 const END_SAME_AS_BEGIN = function(mode) { 729 return Object.assign(mode, 730 { 731 /** @type {ModeCallback} */ 732 'on:begin': (m, resp) => { resp.data._beginMatch = m[1]; }, 733 /** @type {ModeCallback} */ 734 'on:end': (m, resp) => { if (resp.data._beginMatch !== m[1]) resp.ignoreMatch(); } 735 }); 736 }; 737 738 var MODES = /*#__PURE__*/Object.freeze({ 739 __proto__: null, 740 IDENT_RE: IDENT_RE, 741 UNDERSCORE_IDENT_RE: UNDERSCORE_IDENT_RE, 742 NUMBER_RE: NUMBER_RE, 743 C_NUMBER_RE: C_NUMBER_RE, 744 BINARY_NUMBER_RE: BINARY_NUMBER_RE, 745 RE_STARTERS_RE: RE_STARTERS_RE, 746 SHEBANG: SHEBANG, 747 BACKSLASH_ESCAPE: BACKSLASH_ESCAPE, 748 APOS_STRING_MODE: APOS_STRING_MODE, 749 QUOTE_STRING_MODE: QUOTE_STRING_MODE, 750 PHRASAL_WORDS_MODE: PHRASAL_WORDS_MODE, 751 COMMENT: COMMENT, 752 C_LINE_COMMENT_MODE: C_LINE_COMMENT_MODE, 753 C_BLOCK_COMMENT_MODE: C_BLOCK_COMMENT_MODE, 754 HASH_COMMENT_MODE: HASH_COMMENT_MODE, 755 NUMBER_MODE: NUMBER_MODE, 756 C_NUMBER_MODE: C_NUMBER_MODE, 757 BINARY_NUMBER_MODE: BINARY_NUMBER_MODE, 758 CSS_NUMBER_MODE: CSS_NUMBER_MODE, 759 REGEXP_MODE: REGEXP_MODE, 760 TITLE_MODE: TITLE_MODE, 761 UNDERSCORE_TITLE_MODE: UNDERSCORE_TITLE_MODE, 762 METHOD_GUARD: METHOD_GUARD, 763 END_SAME_AS_BEGIN: END_SAME_AS_BEGIN 764 }); 765 766 // keywords that should have no default relevance value 767 var COMMON_KEYWORDS = 'of and for in not or if then'.split(' '); 768 769 // compilation 770 771 /** 772 * Compiles a language definition result 773 * 774 * Given the raw result of a language definition (Language), compiles this so 775 * that it is ready for highlighting code. 776 * @param {Language} language 777 * @returns {CompiledLanguage} 778 */ 779 function compileLanguage(language) { 780 /** 781 * Builds a regex with the case sensativility of the current language 782 * 783 * @param {RegExp | string} value 784 * @param {boolean} [global] 785 */ 786 function langRe(value, global) { 787 return new RegExp( 788 source(value), 789 'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '') 790 ); 791 } 792 793 /** 794 Stores multiple regular expressions and allows you to quickly search for 795 them all in a string simultaneously - returning the first match. It does 796 this by creating a huge (a|b|c) regex - each individual item wrapped with () 797 and joined by `|` - using match groups to track position. When a match is 798 found checking which position in the array has content allows us to figure 799 out which of the original regexes / match groups triggered the match. 800 801 The match object itself (the result of `Regex.exec`) is returned but also 802 enhanced by merging in any meta-data that was registered with the regex. 803 This is how we keep track of which mode matched, and what type of rule 804 (`illegal`, `begin`, end, etc). 805 */ 806 class MultiRegex { 807 constructor() { 808 this.matchIndexes = {}; 809 // @ts-ignore 810 this.regexes = []; 811 this.matchAt = 1; 812 this.position = 0; 813 } 814 815 // @ts-ignore 816 addRule(re, opts) { 817 opts.position = this.position++; 818 // @ts-ignore 819 this.matchIndexes[this.matchAt] = opts; 820 this.regexes.push([opts, re]); 821 this.matchAt += countMatchGroups(re) + 1; 822 } 823 824 compile() { 825 if (this.regexes.length === 0) { 826 // avoids the need to check length every time exec is called 827 // @ts-ignore 828 this.exec = () => null; 829 } 830 const terminators = this.regexes.map(el => el[1]); 831 this.matcherRe = langRe(join(terminators), true); 832 this.lastIndex = 0; 833 } 834 835 /** @param {string} s */ 836 exec(s) { 837 this.matcherRe.lastIndex = this.lastIndex; 838 const match = this.matcherRe.exec(s); 839 if (!match) { return null; } 840 841 // eslint-disable-next-line no-undefined 842 const i = match.findIndex((el, i) => i > 0 && el !== undefined); 843 // @ts-ignore 844 const matchData = this.matchIndexes[i]; 845 // trim off any earlier non-relevant match groups (ie, the other regex 846 // match groups that make up the multi-matcher) 847 match.splice(0, i); 848 849 return Object.assign(match, matchData); 850 } 851 } 852 853 /* 854 Created to solve the key deficiently with MultiRegex - there is no way to 855 test for multiple matches at a single location. Why would we need to do 856 that? In the future a more dynamic engine will allow certain matches to be 857 ignored. An example: if we matched say the 3rd regex in a large group but 858 decided to ignore it - we'd need to started testing again at the 4th 859 regex... but MultiRegex itself gives us no real way to do that. 860 861 So what this class creates MultiRegexs on the fly for whatever search 862 position they are needed. 863 864 NOTE: These additional MultiRegex objects are created dynamically. For most 865 grammars most of the time we will never actually need anything more than the 866 first MultiRegex - so this shouldn't have too much overhead. 867 868 Say this is our search group, and we match regex3, but wish to ignore it. 869 870 regex1 | regex2 | regex3 | regex4 | regex5 ' ie, startAt = 0 871 872 What we need is a new MultiRegex that only includes the remaining 873 possibilities: 874 875 regex4 | regex5 ' ie, startAt = 3 876 877 This class wraps all that complexity up in a simple API... `startAt` decides 878 where in the array of expressions to start doing the matching. It 879 auto-increments, so if a match is found at position 2, then startAt will be 880 set to 3. If the end is reached startAt will return to 0. 881 882 MOST of the time the parser will be setting startAt manually to 0. 883 */ 884 class ResumableMultiRegex { 885 constructor() { 886 // @ts-ignore 887 this.rules = []; 888 // @ts-ignore 889 this.multiRegexes = []; 890 this.count = 0; 891 892 this.lastIndex = 0; 893 this.regexIndex = 0; 894 } 895 896 // @ts-ignore 897 getMatcher(index) { 898 if (this.multiRegexes[index]) return this.multiRegexes[index]; 899 900 const matcher = new MultiRegex(); 901 this.rules.slice(index).forEach(([re, opts]) => matcher.addRule(re, opts)); 902 matcher.compile(); 903 this.multiRegexes[index] = matcher; 904 return matcher; 905 } 906 907 resumingScanAtSamePosition() { 908 return this.regexIndex !== 0; 909 } 910 911 considerAll() { 912 this.regexIndex = 0; 913 } 914 915 // @ts-ignore 916 addRule(re, opts) { 917 this.rules.push([re, opts]); 918 if (opts.type === "begin") this.count++; 919 } 920 921 /** @param {string} s */ 922 exec(s) { 923 const m = this.getMatcher(this.regexIndex); 924 m.lastIndex = this.lastIndex; 925 let result = m.exec(s); 926 927 // The following is because we have no easy way to say "resume scanning at the 928 // existing position but also skip the current rule ONLY". What happens is 929 // all prior rules are also skipped which can result in matching the wrong 930 // thing. Example of matching "booger": 931 932 // our matcher is [string, "booger", number] 933 // 934 // ....booger.... 935 936 // if "booger" is ignored then we'd really need a regex to scan from the 937 // SAME position for only: [string, number] but ignoring "booger" (if it 938 // was the first match), a simple resume would scan ahead who knows how 939 // far looking only for "number", ignoring potential string matches (or 940 // future "booger" matches that might be valid.) 941 942 // So what we do: We execute two matchers, one resuming at the same 943 // position, but the second full matcher starting at the position after: 944 945 // /--- resume first regex match here (for [number]) 946 // |/---- full match here for [string, "booger", number] 947 // vv 948 // ....booger.... 949 950 // Which ever results in a match first is then used. So this 3-4 step 951 // process essentially allows us to say "match at this position, excluding 952 // a prior rule that was ignored". 953 // 954 // 1. Match "booger" first, ignore. Also proves that [string] does non match. 955 // 2. Resume matching for [number] 956 // 3. Match at index + 1 for [string, "booger", number] 957 // 4. If #2 and #3 result in matches, which came first? 958 if (this.resumingScanAtSamePosition()) { 959 if (result && result.index === this.lastIndex) ; else { // use the second matcher result 960 const m2 = this.getMatcher(0); 961 m2.lastIndex = this.lastIndex + 1; 962 result = m2.exec(s); 963 } 964 } 965 966 if (result) { 967 this.regexIndex += result.position + 1; 968 if (this.regexIndex === this.count) { 969 // wrap-around to considering all matches again 970 this.considerAll(); 971 } 972 } 973 974 return result; 975 } 976 } 977 978 /** 979 * Given a mode, builds a huge ResumableMultiRegex that can be used to walk 980 * the content and find matches. 981 * 982 * @param {CompiledMode} mode 983 * @returns {ResumableMultiRegex} 984 */ 985 function buildModeRegex(mode) { 986 const mm = new ResumableMultiRegex(); 987 988 mode.contains.forEach(term => mm.addRule(term.begin, { rule: term, type: "begin" })); 989 990 if (mode.terminator_end) { 991 mm.addRule(mode.terminator_end, { type: "end" }); 992 } 993 if (mode.illegal) { 994 mm.addRule(mode.illegal, { type: "illegal" }); 995 } 996 997 return mm; 998 } 999 1000 // TODO: We need negative look-behind support to do this properly 1001 /** 1002 * Skip a match if it has a preceding or trailing dot 1003 * 1004 * This is used for `beginKeywords` to prevent matching expressions such as 1005 * `bob.keyword.do()`. The mode compiler automatically wires this up as a 1006 * special _internal_ 'on:begin' callback for modes with `beginKeywords` 1007 * @param {RegExpMatchArray} match 1008 * @param {CallbackResponse} response 1009 */ 1010 function skipIfhasPrecedingOrTrailingDot(match, response) { 1011 const before = match.input[match.index - 1]; 1012 const after = match.input[match.index + match[0].length]; 1013 if (before === "." || after === ".") { 1014 response.ignoreMatch(); 1015 } 1016 } 1017 1018 /** skip vs abort vs ignore 1019 * 1020 * @skip - The mode is still entered and exited normally (and contains rules apply), 1021 * but all content is held and added to the parent buffer rather than being 1022 * output when the mode ends. Mostly used with `sublanguage` to build up 1023 * a single large buffer than can be parsed by sublanguage. 1024 * 1025 * - The mode begin ands ends normally. 1026 * - Content matched is added to the parent mode buffer. 1027 * - The parser cursor is moved forward normally. 1028 * 1029 * @abort - A hack placeholder until we have ignore. Aborts the mode (as if it 1030 * never matched) but DOES NOT continue to match subsequent `contains` 1031 * modes. Abort is bad/suboptimal because it can result in modes 1032 * farther down not getting applied because an earlier rule eats the 1033 * content but then aborts. 1034 * 1035 * - The mode does not begin. 1036 * - Content matched by `begin` is added to the mode buffer. 1037 * - The parser cursor is moved forward accordingly. 1038 * 1039 * @ignore - Ignores the mode (as if it never matched) and continues to match any 1040 * subsequent `contains` modes. Ignore isn't technically possible with 1041 * the current parser implementation. 1042 * 1043 * - The mode does not begin. 1044 * - Content matched by `begin` is ignored. 1045 * - The parser cursor is not moved forward. 1046 */ 1047 1048 /** 1049 * Compiles an individual mode 1050 * 1051 * This can raise an error if the mode contains certain detectable known logic 1052 * issues. 1053 * @param {Mode} mode 1054 * @param {CompiledMode | null} [parent] 1055 * @returns {CompiledMode | never} 1056 */ 1057 function compileMode(mode, parent) { 1058 const cmode = /** @type CompiledMode */ (mode); 1059 if (mode.compiled) return cmode; 1060 mode.compiled = true; 1061 1062 // __beforeBegin is considered private API, internal use only 1063 mode.__beforeBegin = null; 1064 1065 mode.keywords = mode.keywords || mode.beginKeywords; 1066 1067 let kw_pattern = null; 1068 if (typeof mode.keywords === "object") { 1069 kw_pattern = mode.keywords.$pattern; 1070 delete mode.keywords.$pattern; 1071 } 1072 1073 if (mode.keywords) { 1074 mode.keywords = compileKeywords(mode.keywords, language.case_insensitive); 1075 } 1076 1077 // both are not allowed 1078 if (mode.lexemes && kw_pattern) { 1079 throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) "); 1080 } 1081 1082 // `mode.lexemes` was the old standard before we added and now recommend 1083 // using `keywords.$pattern` to pass the keyword pattern 1084 cmode.keywordPatternRe = langRe(mode.lexemes || kw_pattern || /\w+/, true); 1085 1086 if (parent) { 1087 if (mode.beginKeywords) { 1088 // for languages with keywords that include non-word characters checking for 1089 // a word boundary is not sufficient, so instead we check for a word boundary 1090 // or whitespace - this does no harm in any case since our keyword engine 1091 // doesn't allow spaces in keywords anyways and we still check for the boundary 1092 // first 1093 mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')(?=\\b|\\s)'; 1094 mode.__beforeBegin = skipIfhasPrecedingOrTrailingDot; 1095 } 1096 if (!mode.begin) mode.begin = /\B|\b/; 1097 cmode.beginRe = langRe(mode.begin); 1098 if (mode.endSameAsBegin) mode.end = mode.begin; 1099 if (!mode.end && !mode.endsWithParent) mode.end = /\B|\b/; 1100 if (mode.end) cmode.endRe = langRe(mode.end); 1101 cmode.terminator_end = source(mode.end) || ''; 1102 if (mode.endsWithParent && parent.terminator_end) { 1103 cmode.terminator_end += (mode.end ? '|' : '') + parent.terminator_end; 1104 } 1105 } 1106 if (mode.illegal) cmode.illegalRe = langRe(mode.illegal); 1107 // eslint-disable-next-line no-undefined 1108 if (mode.relevance === undefined) mode.relevance = 1; 1109 if (!mode.contains) mode.contains = []; 1110 1111 mode.contains = [].concat(...mode.contains.map(function(c) { 1112 return expand_or_clone_mode(c === 'self' ? mode : c); 1113 })); 1114 mode.contains.forEach(function(c) { compileMode(/** @type Mode */ (c), cmode); }); 1115 1116 if (mode.starts) { 1117 compileMode(mode.starts, parent); 1118 } 1119 1120 cmode.matcher = buildModeRegex(cmode); 1121 return cmode; 1122 } 1123 1124 // self is not valid at the top-level 1125 if (language.contains && language.contains.includes('self')) { 1126 throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation."); 1127 } 1128 return compileMode(/** @type Mode */ (language)); 1129 } 1130 1131 /** 1132 * Determines if a mode has a dependency on it's parent or not 1133 * 1134 * If a mode does have a parent dependency then often we need to clone it if 1135 * it's used in multiple places so that each copy points to the correct parent, 1136 * where-as modes without a parent can often safely be re-used at the bottom of 1137 * a mode chain. 1138 * 1139 * @param {Mode | null} mode 1140 * @returns {boolean} - is there a dependency on the parent? 1141 * */ 1142 function dependencyOnParent(mode) { 1143 if (!mode) return false; 1144 1145 return mode.endsWithParent || dependencyOnParent(mode.starts); 1146 } 1147 1148 /** 1149 * Expands a mode or clones it if necessary 1150 * 1151 * This is necessary for modes with parental dependenceis (see notes on 1152 * `dependencyOnParent`) and for nodes that have `variants` - which must then be 1153 * exploded into their own individual modes at compile time. 1154 * 1155 * @param {Mode} mode 1156 * @returns {Mode | Mode[]} 1157 * */ 1158 function expand_or_clone_mode(mode) { 1159 if (mode.variants && !mode.cached_variants) { 1160 mode.cached_variants = mode.variants.map(function(variant) { 1161 return inherit(mode, { variants: null }, variant); 1162 }); 1163 } 1164 1165 // EXPAND 1166 // if we have variants then essentially "replace" the mode with the variants 1167 // this happens in compileMode, where this function is called from 1168 if (mode.cached_variants) { 1169 return mode.cached_variants; 1170 } 1171 1172 // CLONE 1173 // if we have dependencies on parents then we need a unique 1174 // instance of ourselves, so we can be reused with many 1175 // different parents without issue 1176 if (dependencyOnParent(mode)) { 1177 return inherit(mode, { starts: mode.starts ? inherit(mode.starts) : null }); 1178 } 1179 1180 if (Object.isFrozen(mode)) { 1181 return inherit(mode); 1182 } 1183 1184 // no special dependency issues, just return ourselves 1185 return mode; 1186 } 1187 1188 /*********************************************** 1189 Keywords 1190 ***********************************************/ 1191 1192 /** 1193 * Given raw keywords from a language definition, compile them. 1194 * 1195 * @param {string | Record<string,string>} rawKeywords 1196 * @param {boolean} case_insensitive 1197 */ 1198 function compileKeywords(rawKeywords, case_insensitive) { 1199 /** @type KeywordDict */ 1200 var compiled_keywords = {}; 1201 1202 if (typeof rawKeywords === 'string') { // string 1203 splitAndCompile('keyword', rawKeywords); 1204 } else { 1205 Object.keys(rawKeywords).forEach(function(className) { 1206 splitAndCompile(className, rawKeywords[className]); 1207 }); 1208 } 1209 return compiled_keywords; 1210 1211 // --- 1212 1213 /** 1214 * Compiles an individual list of keywords 1215 * 1216 * Ex: "for if when while|5" 1217 * 1218 * @param {string} className 1219 * @param {string} keywordList 1220 */ 1221 function splitAndCompile(className, keywordList) { 1222 if (case_insensitive) { 1223 keywordList = keywordList.toLowerCase(); 1224 } 1225 keywordList.split(' ').forEach(function(keyword) { 1226 var pair = keyword.split('|'); 1227 compiled_keywords[pair[0]] = [className, scoreForKeyword(pair[0], pair[1])]; 1228 }); 1229 } 1230 } 1231 1232 /** 1233 * Returns the proper score for a given keyword 1234 * 1235 * Also takes into account comment keywords, which will be scored 0 UNLESS 1236 * another score has been manually assigned. 1237 * @param {string} keyword 1238 * @param {string} [providedScore] 1239 */ 1240 function scoreForKeyword(keyword, providedScore) { 1241 // manual scores always win over common keywords 1242 // so you can force a score of 1 if you really insist 1243 if (providedScore) { 1244 return Number(providedScore); 1245 } 1246 1247 return commonKeyword(keyword) ? 0 : 1; 1248 } 1249 1250 /** 1251 * Determines if a given keyword is common or not 1252 * 1253 * @param {string} keyword */ 1254 function commonKeyword(keyword) { 1255 return COMMON_KEYWORDS.includes(keyword.toLowerCase()); 1256 } 1257 1258 var version = "10.2.1"; 1259 1260 // @ts-nocheck 1261 1262 function hasValueOrEmptyAttribute(value) { 1263 return Boolean(value || value === ""); 1264 } 1265 1266 const Component = { 1267 props: ["language", "code", "autodetect"], 1268 data: function() { 1269 return { 1270 detectedLanguage: "", 1271 unknownLanguage: false 1272 }; 1273 }, 1274 computed: { 1275 className() { 1276 if (this.unknownLanguage) return ""; 1277 1278 return "hljs " + this.detectedLanguage; 1279 }, 1280 highlighted() { 1281 // no idea what language to use, return raw code 1282 if (!this.autoDetect && !hljs.getLanguage(this.language)) { 1283 console.warn(`The language "${this.language}" you specified could not be found.`); 1284 this.unknownLanguage = true; 1285 return escapeHTML(this.code); 1286 } 1287 1288 let result; 1289 if (this.autoDetect) { 1290 result = hljs.highlightAuto(this.code); 1291 this.detectedLanguage = result.language; 1292 } else { 1293 result = hljs.highlight(this.language, this.code, this.ignoreIllegals); 1294 this.detectectLanguage = this.language; 1295 } 1296 return result.value; 1297 }, 1298 autoDetect() { 1299 return !this.language || hasValueOrEmptyAttribute(this.autodetect); 1300 }, 1301 ignoreIllegals() { 1302 return true; 1303 } 1304 }, 1305 // this avoids needing to use a whole Vue compilation pipeline just 1306 // to build Highlight.js 1307 render(createElement) { 1308 return createElement("pre", {}, [ 1309 createElement("code", { 1310 class: this.className, 1311 domProps: { innerHTML: this.highlighted }}) 1312 ]); 1313 } 1314 // template: `<pre><code :class="className" v-html="highlighted"></code></pre>` 1315 }; 1316 1317 const VuePlugin = { 1318 install(Vue) { 1319 Vue.component('highlightjs', Component); 1320 } 1321 }; 1322 1323 /* 1324 Syntax highlighting with language autodetection. 1325 https://highlightjs.org/ 1326 */ 1327 1328 const escape$1 = escapeHTML; 1329 const inherit$1 = inherit; 1330 1331 const { nodeStream: nodeStream$1, mergeStreams: mergeStreams$1 } = utils; 1332 const NO_MATCH = Symbol("nomatch"); 1333 1334 /** 1335 * @param {any} hljs - object that is extended (legacy) 1336 * @returns {HLJSApi} 1337 */ 1338 const HLJS = function(hljs) { 1339 // Convenience variables for build-in objects 1340 /** @type {unknown[]} */ 1341 var ArrayProto = []; 1342 1343 // Global internal variables used within the highlight.js library. 1344 /** @type {Record<string, Language>} */ 1345 var languages = Object.create(null); 1346 /** @type {Record<string, string>} */ 1347 var aliases = Object.create(null); 1348 /** @type {HLJSPlugin[]} */ 1349 var plugins = []; 1350 1351 // safe/production mode - swallows more errors, tries to keep running 1352 // even if a single syntax or parse hits a fatal error 1353 var SAFE_MODE = true; 1354 var fixMarkupRe = /(^(<[^>]+>|\t|)+|\n)/gm; 1355 var LANGUAGE_NOT_FOUND = "Could not find the language '{}', did you forget to load/include a language module?"; 1356 /** @type {Language} */ 1357 const PLAINTEXT_LANGUAGE = { disableAutodetect: true, name: 'Plain text', contains: [] }; 1358 1359 // Global options used when within external APIs. This is modified when 1360 // calling the `hljs.configure` function. 1361 /** @type HLJSOptions */ 1362 var options = { 1363 noHighlightRe: /^(no-?highlight)$/i, 1364 languageDetectRe: /\blang(?:uage)?-([\w-]+)\b/i, 1365 classPrefix: 'hljs-', 1366 tabReplace: null, 1367 useBR: false, 1368 languages: null, 1369 // beta configuration options, subject to change, welcome to discuss 1370 // https://github.com/highlightjs/highlight.js/issues/1086 1371 __emitter: TokenTreeEmitter 1372 }; 1373 1374 /* Utility functions */ 1375 1376 /** 1377 * Tests a language name to see if highlighting should be skipped 1378 * @param {string} languageName 1379 */ 1380 function shouldNotHighlight(languageName) { 1381 return options.noHighlightRe.test(languageName); 1382 } 1383 1384 /** 1385 * @param {HighlightedHTMLElement} block - the HTML element to determine language for 1386 */ 1387 function blockLanguage(block) { 1388 var classes = block.className + ' '; 1389 1390 classes += block.parentNode ? block.parentNode.className : ''; 1391 1392 // language-* takes precedence over non-prefixed class names. 1393 const match = options.languageDetectRe.exec(classes); 1394 if (match) { 1395 var language = getLanguage(match[1]); 1396 if (!language) { 1397 console.warn(LANGUAGE_NOT_FOUND.replace("{}", match[1])); 1398 console.warn("Falling back to no-highlight mode for this block.", block); 1399 } 1400 return language ? match[1] : 'no-highlight'; 1401 } 1402 1403 return classes 1404 .split(/\s+/) 1405 .find((_class) => shouldNotHighlight(_class) || getLanguage(_class)); 1406 } 1407 1408 /** 1409 * Core highlighting function. 1410 * 1411 * @param {string} languageName - the language to use for highlighting 1412 * @param {string} code - the code to highlight 1413 * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail 1414 * @param {Mode} [continuation] - current continuation mode, if any 1415 * 1416 * @returns {HighlightResult} Result - an object that represents the result 1417 * @property {string} language - the language name 1418 * @property {number} relevance - the relevance score 1419 * @property {string} value - the highlighted HTML code 1420 * @property {string} code - the original raw code 1421 * @property {Mode} top - top of the current mode stack 1422 * @property {boolean} illegal - indicates whether any illegal matches were found 1423 */ 1424 function highlight(languageName, code, ignoreIllegals, continuation) { 1425 /** @type {{ code: string, language: string, result?: any }} */ 1426 var context = { 1427 code, 1428 language: languageName 1429 }; 1430 // the plugin can change the desired language or the code to be highlighted 1431 // just be changing the object it was passed 1432 fire("before:highlight", context); 1433 1434 // a before plugin can usurp the result completely by providing it's own 1435 // in which case we don't even need to call highlight 1436 var result = context.result ? 1437 context.result : 1438 _highlight(context.language, context.code, ignoreIllegals, continuation); 1439 1440 result.code = context.code; 1441 // the plugin can change anything in result to suite it 1442 fire("after:highlight", result); 1443 1444 return result; 1445 } 1446 1447 /** 1448 * private highlight that's used internally and does not fire callbacks 1449 * 1450 * @param {string} languageName - the language to use for highlighting 1451 * @param {string} code - the code to highlight 1452 * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail 1453 * @param {Mode} [continuation] - current continuation mode, if any 1454 */ 1455 function _highlight(languageName, code, ignoreIllegals, continuation) { 1456 var codeToHighlight = code; 1457 1458 /** 1459 * Return keyword data if a match is a keyword 1460 * @param {CompiledMode} mode - current mode 1461 * @param {RegExpMatchArray} match - regexp match data 1462 * @returns {KeywordData | false} 1463 */ 1464 function keywordData(mode, match) { 1465 var matchText = language.case_insensitive ? match[0].toLowerCase() : match[0]; 1466 return Object.prototype.hasOwnProperty.call(mode.keywords, matchText) && mode.keywords[matchText]; 1467 } 1468 1469 function processKeywords() { 1470 if (!top.keywords) { 1471 emitter.addText(mode_buffer); 1472 return; 1473 } 1474 1475 let last_index = 0; 1476 top.keywordPatternRe.lastIndex = 0; 1477 let match = top.keywordPatternRe.exec(mode_buffer); 1478 let buf = ""; 1479 1480 while (match) { 1481 buf += mode_buffer.substring(last_index, match.index); 1482 const data = keywordData(top, match); 1483 if (data) { 1484 const [kind, keywordRelevance] = data; 1485 emitter.addText(buf); 1486 buf = ""; 1487 1488 relevance += keywordRelevance; 1489 emitter.addKeyword(match[0], kind); 1490 } else { 1491 buf += match[0]; 1492 } 1493 last_index = top.keywordPatternRe.lastIndex; 1494 match = top.keywordPatternRe.exec(mode_buffer); 1495 } 1496 buf += mode_buffer.substr(last_index); 1497 emitter.addText(buf); 1498 } 1499 1500 function processSubLanguage() { 1501 if (mode_buffer === "") return; 1502 /** @type HighlightResult */ 1503 var result = null; 1504 1505 if (typeof top.subLanguage === 'string') { 1506 if (!languages[top.subLanguage]) { 1507 emitter.addText(mode_buffer); 1508 return; 1509 } 1510 result = _highlight(top.subLanguage, mode_buffer, true, continuations[top.subLanguage]); 1511 continuations[top.subLanguage] = result.top; 1512 } else { 1513 result = highlightAuto(mode_buffer, top.subLanguage.length ? top.subLanguage : null); 1514 } 1515 1516 // Counting embedded language score towards the host language may be disabled 1517 // with zeroing the containing mode relevance. Use case in point is Markdown that 1518 // allows XML everywhere and makes every XML snippet to have a much larger Markdown 1519 // score. 1520 if (top.relevance > 0) { 1521 relevance += result.relevance; 1522 } 1523 emitter.addSublanguage(result.emitter, result.language); 1524 } 1525 1526 function processBuffer() { 1527 if (top.subLanguage != null) { 1528 processSubLanguage(); 1529 } else { 1530 processKeywords(); 1531 } 1532 mode_buffer = ''; 1533 } 1534 1535 /** 1536 * @param {Mode} mode - new mode to start 1537 */ 1538 function startNewMode(mode) { 1539 if (mode.className) { 1540 emitter.openNode(mode.className); 1541 } 1542 top = Object.create(mode, { parent: { value: top } }); 1543 return top; 1544 } 1545 1546 /** 1547 * @param {CompiledMode } mode - the mode to potentially end 1548 * @param {RegExpMatchArray} match - the latest match 1549 * @param {string} matchPlusRemainder - match plus remainder of content 1550 * @returns {CompiledMode | void} - the next mode, or if void continue on in current mode 1551 */ 1552 function endOfMode(mode, match, matchPlusRemainder) { 1553 let matched = startsWith(mode.endRe, matchPlusRemainder); 1554 1555 if (matched) { 1556 if (mode["on:end"]) { 1557 const resp = new Response(mode); 1558 mode["on:end"](match, resp); 1559 if (resp.ignore) matched = false; 1560 } 1561 1562 if (matched) { 1563 while (mode.endsParent && mode.parent) { 1564 mode = mode.parent; 1565 } 1566 return mode; 1567 } 1568 } 1569 // even if on:end fires an `ignore` it's still possible 1570 // that we might trigger the end node because of a parent mode 1571 if (mode.endsWithParent) { 1572 return endOfMode(mode.parent, match, matchPlusRemainder); 1573 } 1574 } 1575 1576 /** 1577 * Handle matching but then ignoring a sequence of text 1578 * 1579 * @param {string} lexeme - string containing full match text 1580 */ 1581 function doIgnore(lexeme) { 1582 if (top.matcher.regexIndex === 0) { 1583 // no more regexs to potentially match here, so we move the cursor forward one 1584 // space 1585 mode_buffer += lexeme[0]; 1586 return 1; 1587 } else { 1588 // no need to move the cursor, we still have additional regexes to try and 1589 // match at this very spot 1590 resumeScanAtSamePosition = true; 1591 return 0; 1592 } 1593 } 1594 1595 /** 1596 * Handle the start of a new potential mode match 1597 * 1598 * @param {EnhancedMatch} match - the current match 1599 * @returns {number} how far to advance the parse cursor 1600 */ 1601 function doBeginMatch(match) { 1602 var lexeme = match[0]; 1603 var new_mode = match.rule; 1604 1605 const resp = new Response(new_mode); 1606 // first internal before callbacks, then the public ones 1607 const beforeCallbacks = [new_mode.__beforeBegin, new_mode["on:begin"]]; 1608 for (const cb of beforeCallbacks) { 1609 if (!cb) continue; 1610 cb(match, resp); 1611 if (resp.ignore) return doIgnore(lexeme); 1612 } 1613 1614 if (new_mode && new_mode.endSameAsBegin) { 1615 new_mode.endRe = escape(lexeme); 1616 } 1617 1618 if (new_mode.skip) { 1619 mode_buffer += lexeme; 1620 } else { 1621 if (new_mode.excludeBegin) { 1622 mode_buffer += lexeme; 1623 } 1624 processBuffer(); 1625 if (!new_mode.returnBegin && !new_mode.excludeBegin) { 1626 mode_buffer = lexeme; 1627 } 1628 } 1629 startNewMode(new_mode); 1630 // if (mode["after:begin"]) { 1631 // let resp = new Response(mode); 1632 // mode["after:begin"](match, resp); 1633 // } 1634 return new_mode.returnBegin ? 0 : lexeme.length; 1635 } 1636 1637 /** 1638 * Handle the potential end of mode 1639 * 1640 * @param {RegExpMatchArray} match - the current match 1641 */ 1642 function doEndMatch(match) { 1643 var lexeme = match[0]; 1644 var matchPlusRemainder = codeToHighlight.substr(match.index); 1645 1646 var end_mode = endOfMode(top, match, matchPlusRemainder); 1647 if (!end_mode) { return NO_MATCH; } 1648 1649 var origin = top; 1650 if (origin.skip) { 1651 mode_buffer += lexeme; 1652 } else { 1653 if (!(origin.returnEnd || origin.excludeEnd)) { 1654 mode_buffer += lexeme; 1655 } 1656 processBuffer(); 1657 if (origin.excludeEnd) { 1658 mode_buffer = lexeme; 1659 } 1660 } 1661 do { 1662 if (top.className) { 1663 emitter.closeNode(); 1664 } 1665 if (!top.skip && !top.subLanguage) { 1666 relevance += top.relevance; 1667 } 1668 top = top.parent; 1669 } while (top !== end_mode.parent); 1670 if (end_mode.starts) { 1671 if (end_mode.endSameAsBegin) { 1672 end_mode.starts.endRe = end_mode.endRe; 1673 } 1674 startNewMode(end_mode.starts); 1675 } 1676 return origin.returnEnd ? 0 : lexeme.length; 1677 } 1678 1679 function processContinuations() { 1680 var list = []; 1681 for (var current = top; current !== language; current = current.parent) { 1682 if (current.className) { 1683 list.unshift(current.className); 1684 } 1685 } 1686 list.forEach(item => emitter.openNode(item)); 1687 } 1688 1689 /** @type {{type?: MatchType, index?: number, rule?: Mode}}} */ 1690 var lastMatch = {}; 1691 1692 /** 1693 * Process an individual match 1694 * 1695 * @param {string} textBeforeMatch - text preceeding the match (since the last match) 1696 * @param {EnhancedMatch} [match] - the match itself 1697 */ 1698 function processLexeme(textBeforeMatch, match) { 1699 var lexeme = match && match[0]; 1700 1701 // add non-matched text to the current mode buffer 1702 mode_buffer += textBeforeMatch; 1703 1704 if (lexeme == null) { 1705 processBuffer(); 1706 return 0; 1707 } 1708 1709 // we've found a 0 width match and we're stuck, so we need to advance 1710 // this happens when we have badly behaved rules that have optional matchers to the degree that 1711 // sometimes they can end up matching nothing at all 1712 // Ref: https://github.com/highlightjs/highlight.js/issues/2140 1713 if (lastMatch.type === "begin" && match.type === "end" && lastMatch.index === match.index && lexeme === "") { 1714 // spit the "skipped" character that our regex choked on back into the output sequence 1715 mode_buffer += codeToHighlight.slice(match.index, match.index + 1); 1716 if (!SAFE_MODE) { 1717 /** @type {AnnotatedError} */ 1718 const err = new Error('0 width match regex'); 1719 err.languageName = languageName; 1720 err.badRule = lastMatch.rule; 1721 throw err; 1722 } 1723 return 1; 1724 } 1725 lastMatch = match; 1726 1727 if (match.type === "begin") { 1728 return doBeginMatch(match); 1729 } else if (match.type === "illegal" && !ignoreIllegals) { 1730 // illegal match, we do not continue processing 1731 /** @type {AnnotatedError} */ 1732 const err = new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.className || '<unnamed>') + '"'); 1733 err.mode = top; 1734 throw err; 1735 } else if (match.type === "end") { 1736 var processed = doEndMatch(match); 1737 if (processed !== NO_MATCH) { 1738 return processed; 1739 } 1740 } 1741 1742 // edge case for when illegal matches $ (end of line) which is technically 1743 // a 0 width match but not a begin/end match so it's not caught by the 1744 // first handler (when ignoreIllegals is true) 1745 if (match.type === "illegal" && lexeme === "") { 1746 // advance so we aren't stuck in an infinite loop 1747 return 1; 1748 } 1749 1750 // infinite loops are BAD, this is a last ditch catch all. if we have a 1751 // decent number of iterations yet our index (cursor position in our 1752 // parsing) still 3x behind our index then something is very wrong 1753 // so we bail 1754 if (iterations > 100000 && iterations > match.index * 3) { 1755 const err = new Error('potential infinite loop, way more iterations than matches'); 1756 throw err; 1757 } 1758 1759 /* 1760 Why might be find ourselves here? Only one occasion now. An end match that was 1761 triggered but could not be completed. When might this happen? When an `endSameasBegin` 1762 rule sets the end rule to a specific match. Since the overall mode termination rule that's 1763 being used to scan the text isn't recompiled that means that any match that LOOKS like 1764 the end (but is not, because it is not an exact match to the beginning) will 1765 end up here. A definite end match, but when `doEndMatch` tries to "reapply" 1766 the end rule and fails to match, we wind up here, and just silently ignore the end. 1767 1768 This causes no real harm other than stopping a few times too many. 1769 */ 1770 1771 mode_buffer += lexeme; 1772 return lexeme.length; 1773 } 1774 1775 var language = getLanguage(languageName); 1776 if (!language) { 1777 console.error(LANGUAGE_NOT_FOUND.replace("{}", languageName)); 1778 throw new Error('Unknown language: "' + languageName + '"'); 1779 } 1780 1781 var md = compileLanguage(language); 1782 var result = ''; 1783 /** @type {CompiledMode} */ 1784 var top = continuation || md; 1785 /** @type Record<string,Mode> */ 1786 var continuations = {}; // keep continuations for sub-languages 1787 var emitter = new options.__emitter(options); 1788 processContinuations(); 1789 var mode_buffer = ''; 1790 var relevance = 0; 1791 var index = 0; 1792 var iterations = 0; 1793 var resumeScanAtSamePosition = false; 1794 1795 try { 1796 top.matcher.considerAll(); 1797 1798 for (;;) { 1799 iterations++; 1800 if (resumeScanAtSamePosition) { 1801 // only regexes not matched previously will now be 1802 // considered for a potential match 1803 resumeScanAtSamePosition = false; 1804 } else { 1805 top.matcher.considerAll(); 1806 } 1807 top.matcher.lastIndex = index; 1808 1809 const match = top.matcher.exec(codeToHighlight); 1810 // console.log("match", match[0], match.rule && match.rule.begin) 1811 1812 if (!match) break; 1813 1814 const beforeMatch = codeToHighlight.substring(index, match.index); 1815 const processedCount = processLexeme(beforeMatch, match); 1816 index = match.index + processedCount; 1817 } 1818 processLexeme(codeToHighlight.substr(index)); 1819 emitter.closeAllNodes(); 1820 emitter.finalize(); 1821 result = emitter.toHTML(); 1822 1823 return { 1824 relevance: relevance, 1825 value: result, 1826 language: languageName, 1827 illegal: false, 1828 emitter: emitter, 1829 top: top 1830 }; 1831 } catch (err) { 1832 if (err.message && err.message.includes('Illegal')) { 1833 return { 1834 illegal: true, 1835 illegalBy: { 1836 msg: err.message, 1837 context: codeToHighlight.slice(index - 100, index + 100), 1838 mode: err.mode 1839 }, 1840 sofar: result, 1841 relevance: 0, 1842 value: escape$1(codeToHighlight), 1843 emitter: emitter 1844 }; 1845 } else if (SAFE_MODE) { 1846 return { 1847 illegal: false, 1848 relevance: 0, 1849 value: escape$1(codeToHighlight), 1850 emitter: emitter, 1851 language: languageName, 1852 top: top, 1853 errorRaised: err 1854 }; 1855 } else { 1856 throw err; 1857 } 1858 } 1859 } 1860 1861 /** 1862 * returns a valid highlight result, without actually doing any actual work, 1863 * auto highlight starts with this and it's possible for small snippets that 1864 * auto-detection may not find a better match 1865 * @param {string} code 1866 * @returns {HighlightResult} 1867 */ 1868 function justTextHighlightResult(code) { 1869 const result = { 1870 relevance: 0, 1871 emitter: new options.__emitter(options), 1872 value: escape$1(code), 1873 illegal: false, 1874 top: PLAINTEXT_LANGUAGE 1875 }; 1876 result.emitter.addText(code); 1877 return result; 1878 } 1879 1880 /** 1881 Highlighting with language detection. Accepts a string with the code to 1882 highlight. Returns an object with the following properties: 1883 1884 - language (detected language) 1885 - relevance (int) 1886 - value (an HTML string with highlighting markup) 1887 - second_best (object with the same structure for second-best heuristically 1888 detected language, may be absent) 1889 1890 @param {string} code 1891 @param {Array<string>} [languageSubset] 1892 @returns {AutoHighlightResult} 1893 */ 1894 function highlightAuto(code, languageSubset) { 1895 languageSubset = languageSubset || options.languages || Object.keys(languages); 1896 var result = justTextHighlightResult(code); 1897 var secondBest = result; 1898 languageSubset.filter(getLanguage).filter(autoDetection).forEach(function(name) { 1899 var current = _highlight(name, code, false); 1900 current.language = name; 1901 if (current.relevance > secondBest.relevance) { 1902 secondBest = current; 1903 } 1904 if (current.relevance > result.relevance) { 1905 secondBest = result; 1906 result = current; 1907 } 1908 }); 1909 if (secondBest.language) { 1910 // second_best (with underscore) is the expected API 1911 result.second_best = secondBest; 1912 } 1913 return result; 1914 } 1915 1916 /** 1917 Post-processing of the highlighted markup: 1918 1919 - replace TABs with something more useful 1920 - replace real line-breaks with '<br>' for non-pre containers 1921 1922 @param {string} html 1923 @returns {string} 1924 */ 1925 function fixMarkup(html) { 1926 if (!(options.tabReplace || options.useBR)) { 1927 return html; 1928 } 1929 1930 return html.replace(fixMarkupRe, match => { 1931 if (match === '\n') { 1932 return options.useBR ? '<br>' : match; 1933 } else if (options.tabReplace) { 1934 return match.replace(/\t/g, options.tabReplace); 1935 } 1936 return match; 1937 }); 1938 } 1939 1940 /** 1941 * Builds new class name for block given the language name 1942 * 1943 * @param {string} prevClassName 1944 * @param {string} [currentLang] 1945 * @param {string} [resultLang] 1946 */ 1947 function buildClassName(prevClassName, currentLang, resultLang) { 1948 var language = currentLang ? aliases[currentLang] : resultLang; 1949 var result = [prevClassName.trim()]; 1950 1951 if (!prevClassName.match(/\bhljs\b/)) { 1952 result.push('hljs'); 1953 } 1954 1955 if (!prevClassName.includes(language)) { 1956 result.push(language); 1957 } 1958 1959 return result.join(' ').trim(); 1960 } 1961 1962 /** 1963 * Applies highlighting to a DOM node containing code. Accepts a DOM node and 1964 * two optional parameters for fixMarkup. 1965 * 1966 * @param {HighlightedHTMLElement} element - the HTML element to highlight 1967 */ 1968 function highlightBlock(element) { 1969 /** @type HTMLElement */ 1970 let node = null; 1971 const language = blockLanguage(element); 1972 1973 if (shouldNotHighlight(language)) return; 1974 1975 fire("before:highlightBlock", 1976 { block: element, language: language }); 1977 1978 if (options.useBR) { 1979 node = document.createElement('div'); 1980 node.innerHTML = element.innerHTML.replace(/\n/g, '').replace(/<br[ /]*>/g, '\n'); 1981 } else { 1982 node = element; 1983 } 1984 const text = node.textContent; 1985 const result = language ? highlight(language, text, true) : highlightAuto(text); 1986 1987 const originalStream = nodeStream$1(node); 1988 if (originalStream.length) { 1989 const resultNode = document.createElement('div'); 1990 resultNode.innerHTML = result.value; 1991 result.value = mergeStreams$1(originalStream, nodeStream$1(resultNode), text); 1992 } 1993 result.value = fixMarkup(result.value); 1994 1995 fire("after:highlightBlock", { block: element, result: result }); 1996 1997 element.innerHTML = result.value; 1998 element.className = buildClassName(element.className, language, result.language); 1999 element.result = { 2000 language: result.language, 2001 // TODO: remove with version 11.0 2002 re: result.relevance, 2003 relavance: result.relevance 2004 }; 2005 if (result.second_best) { 2006 element.second_best = { 2007 language: result.second_best.language, 2008 // TODO: remove with version 11.0 2009 re: result.second_best.relevance, 2010 relavance: result.second_best.relevance 2011 }; 2012 } 2013 } 2014 2015 /** 2016 * Updates highlight.js global options with the passed options 2017 * 2018 * @param {{}} userOptions 2019 */ 2020 function configure(userOptions) { 2021 options = inherit$1(options, userOptions); 2022 } 2023 2024 /** 2025 * Highlights to all <pre><code> blocks on a page 2026 * 2027 * @type {Function & {called?: boolean}} 2028 */ 2029 const initHighlighting = () => { 2030 if (initHighlighting.called) return; 2031 initHighlighting.called = true; 2032 2033 var blocks = document.querySelectorAll('pre code'); 2034 ArrayProto.forEach.call(blocks, highlightBlock); 2035 }; 2036 2037 // Higlights all when DOMContentLoaded fires 2038 function initHighlightingOnLoad() { 2039 // @ts-ignore 2040 window.addEventListener('DOMContentLoaded', initHighlighting, false); 2041 } 2042 2043 /** 2044 * Register a language grammar module 2045 * 2046 * @param {string} languageName 2047 * @param {LanguageFn} languageDefinition 2048 */ 2049 function registerLanguage(languageName, languageDefinition) { 2050 var lang = null; 2051 try { 2052 lang = languageDefinition(hljs); 2053 } catch (error) { 2054 console.error("Language definition for '{}' could not be registered.".replace("{}", languageName)); 2055 // hard or soft error 2056 if (!SAFE_MODE) { throw error; } else { console.error(error); } 2057 // languages that have serious errors are replaced with essentially a 2058 // "plaintext" stand-in so that the code blocks will still get normal 2059 // css classes applied to them - and one bad language won't break the 2060 // entire highlighter 2061 lang = PLAINTEXT_LANGUAGE; 2062 } 2063 // give it a temporary name if it doesn't have one in the meta-data 2064 if (!lang.name) lang.name = languageName; 2065 languages[languageName] = lang; 2066 lang.rawDefinition = languageDefinition.bind(null, hljs); 2067 2068 if (lang.aliases) { 2069 registerAliases(lang.aliases, { languageName }); 2070 } 2071 } 2072 2073 /** 2074 * @returns {string[]} List of language internal names 2075 */ 2076 function listLanguages() { 2077 return Object.keys(languages); 2078 } 2079 2080 /** 2081 intended usage: When one language truly requires another 2082 2083 Unlike `getLanguage`, this will throw when the requested language 2084 is not available. 2085 2086 @param {string} name - name of the language to fetch/require 2087 @returns {Language | never} 2088 */ 2089 function requireLanguage(name) { 2090 var lang = getLanguage(name); 2091 if (lang) { return lang; } 2092 2093 var err = new Error('The \'{}\' language is required, but not loaded.'.replace('{}', name)); 2094 throw err; 2095 } 2096 2097 /** 2098 * @param {string} name - name of the language to retrieve 2099 * @returns {Language | undefined} 2100 */ 2101 function getLanguage(name) { 2102 name = (name || '').toLowerCase(); 2103 return languages[name] || languages[aliases[name]]; 2104 } 2105 2106 /** 2107 * 2108 * @param {string|string[]} aliasList - single alias or list of aliases 2109 * @param {{languageName: string}} opts 2110 */ 2111 function registerAliases(aliasList, { languageName }) { 2112 if (typeof aliasList === 'string') { 2113 aliasList = [aliasList]; 2114 } 2115 aliasList.forEach(alias => { aliases[alias] = languageName; }); 2116 } 2117 2118 /** 2119 * Determines if a given language has auto-detection enabled 2120 * @param {string} name - name of the language 2121 */ 2122 function autoDetection(name) { 2123 var lang = getLanguage(name); 2124 return lang && !lang.disableAutodetect; 2125 } 2126 2127 /** 2128 * @param {HLJSPlugin} plugin 2129 */ 2130 function addPlugin(plugin) { 2131 plugins.push(plugin); 2132 } 2133 2134 /** 2135 * 2136 * @param {PluginEvent} event 2137 * @param {any} args 2138 */ 2139 function fire(event, args) { 2140 var cb = event; 2141 plugins.forEach(function(plugin) { 2142 if (plugin[cb]) { 2143 plugin[cb](args); 2144 } 2145 }); 2146 } 2147 2148 /* fixMarkup is deprecated and will be removed entirely in v11 */ 2149 function deprecate_fixMarkup(arg) { 2150 console.warn("fixMarkup is deprecated and will be removed entirely in v11.0"); 2151 console.warn("Please see https://github.com/highlightjs/highlight.js/issues/2534"); 2152 2153 return fixMarkup(arg) 2154 } 2155 2156 /* Interface definition */ 2157 Object.assign(hljs, { 2158 highlight, 2159 highlightAuto, 2160 fixMarkup: deprecate_fixMarkup, 2161 highlightBlock, 2162 configure, 2163 initHighlighting, 2164 initHighlightingOnLoad, 2165 registerLanguage, 2166 listLanguages, 2167 getLanguage, 2168 registerAliases, 2169 requireLanguage, 2170 autoDetection, 2171 inherit: inherit$1, 2172 addPlugin, 2173 // plugins for frameworks 2174 vuePlugin: VuePlugin 2175 }); 2176 2177 hljs.debugMode = function() { SAFE_MODE = false; }; 2178 hljs.safeMode = function() { SAFE_MODE = true; }; 2179 hljs.versionString = version; 2180 2181 for (const key in MODES) { 2182 // @ts-ignore 2183 if (typeof MODES[key] === "object") { 2184 // @ts-ignore 2185 deepFreeze(MODES[key]); 2186 } 2187 } 2188 2189 // merge all the modes/regexs into our main object 2190 Object.assign(hljs, MODES); 2191 2192 return hljs; 2193 }; 2194 2195 // export an "instance" of the highlighter 2196 var highlight = HLJS({}); 2197 2198 module.exports = highlight;