UNPKG

75.5 kBJavaScriptView Raw
1/*!
2 Highlight.js v11.6.0 (git: bed790f3f3)
3 (c) 2006-2022 undefined and other contributors
4 License: BSD-3-Clause
5 */
6var deepFreezeEs6 = {exports: {}};
7
8function deepFreeze(obj) {
9 if (obj instanceof Map) {
10 obj.clear = obj.delete = obj.set = function () {
11 throw new Error('map is read-only');
12 };
13 } else if (obj instanceof Set) {
14 obj.add = obj.clear = obj.delete = function () {
15 throw new Error('set is read-only');
16 };
17 }
18
19 // Freeze self
20 Object.freeze(obj);
21
22 Object.getOwnPropertyNames(obj).forEach(function (name) {
23 var prop = obj[name];
24
25 // Freeze prop if it is an object
26 if (typeof prop == 'object' && !Object.isFrozen(prop)) {
27 deepFreeze(prop);
28 }
29 });
30
31 return obj;
32}
33
34deepFreezeEs6.exports = deepFreeze;
35deepFreezeEs6.exports.default = deepFreeze;
36
37/** @typedef {import('highlight.js').CallbackResponse} CallbackResponse */
38/** @typedef {import('highlight.js').CompiledMode} CompiledMode */
39/** @implements CallbackResponse */
40
41class Response {
42 /**
43 * @param {CompiledMode} mode
44 */
45 constructor(mode) {
46 // eslint-disable-next-line no-undefined
47 if (mode.data === undefined) mode.data = {};
48
49 this.data = mode.data;
50 this.isMatchIgnored = false;
51 }
52
53 ignoreMatch() {
54 this.isMatchIgnored = true;
55 }
56}
57
58/**
59 * @param {string} value
60 * @returns {string}
61 */
62function escapeHTML(value) {
63 return value
64 .replace(/&/g, '&')
65 .replace(/</g, '&lt;')
66 .replace(/>/g, '&gt;')
67 .replace(/"/g, '&quot;')
68 .replace(/'/g, '&#x27;');
69}
70
71/**
72 * performs a shallow merge of multiple objects into one
73 *
74 * @template T
75 * @param {T} original
76 * @param {Record<string,any>[]} objects
77 * @returns {T} a single new object
78 */
79function inherit$1(original, ...objects) {
80 /** @type Record<string,any> */
81 const result = Object.create(null);
82
83 for (const key in original) {
84 result[key] = original[key];
85 }
86 objects.forEach(function(obj) {
87 for (const key in obj) {
88 result[key] = obj[key];
89 }
90 });
91 return /** @type {T} */ (result);
92}
93
94/**
95 * @typedef {object} Renderer
96 * @property {(text: string) => void} addText
97 * @property {(node: Node) => void} openNode
98 * @property {(node: Node) => void} closeNode
99 * @property {() => string} value
100 */
101
102/** @typedef {{scope?: string, language?: string, sublanguage?: boolean}} Node */
103/** @typedef {{walk: (r: Renderer) => void}} Tree */
104/** */
105
106const SPAN_CLOSE = '</span>';
107
108/**
109 * Determines if a node needs to be wrapped in <span>
110 *
111 * @param {Node} node */
112const emitsWrappingTags = (node) => {
113 // rarely we can have a sublanguage where language is undefined
114 // TODO: track down why
115 return !!node.scope || (node.sublanguage && node.language);
116};
117
118/**
119 *
120 * @param {string} name
121 * @param {{prefix:string}} options
122 */
123const scopeToCSSClass = (name, { prefix }) => {
124 if (name.includes(".")) {
125 const pieces = name.split(".");
126 return [
127 `${prefix}${pieces.shift()}`,
128 ...(pieces.map((x, i) => `${x}${"_".repeat(i + 1)}`))
129 ].join(" ");
130 }
131 return `${prefix}${name}`;
132};
133
134/** @type {Renderer} */
135class HTMLRenderer {
136 /**
137 * Creates a new HTMLRenderer
138 *
139 * @param {Tree} parseTree - the parse tree (must support `walk` API)
140 * @param {{classPrefix: string}} options
141 */
142 constructor(parseTree, options) {
143 this.buffer = "";
144 this.classPrefix = options.classPrefix;
145 parseTree.walk(this);
146 }
147
148 /**
149 * Adds texts to the output stream
150 *
151 * @param {string} text */
152 addText(text) {
153 this.buffer += escapeHTML(text);
154 }
155
156 /**
157 * Adds a node open to the output stream (if needed)
158 *
159 * @param {Node} node */
160 openNode(node) {
161 if (!emitsWrappingTags(node)) return;
162
163 let className = "";
164 if (node.sublanguage) {
165 className = `language-${node.language}`;
166 } else {
167 className = scopeToCSSClass(node.scope, { prefix: this.classPrefix });
168 }
169 this.span(className);
170 }
171
172 /**
173 * Adds a node close to the output stream (if needed)
174 *
175 * @param {Node} node */
176 closeNode(node) {
177 if (!emitsWrappingTags(node)) return;
178
179 this.buffer += SPAN_CLOSE;
180 }
181
182 /**
183 * returns the accumulated buffer
184 */
185 value() {
186 return this.buffer;
187 }
188
189 // helpers
190
191 /**
192 * Builds a span element
193 *
194 * @param {string} className */
195 span(className) {
196 this.buffer += `<span class="${className}">`;
197 }
198}
199
200/** @typedef {{scope?: string, language?: string, sublanguage?: boolean, children: Node[]} | string} Node */
201/** @typedef {{scope?: string, language?: string, sublanguage?: boolean, children: Node[]} } DataNode */
202/** @typedef {import('highlight.js').Emitter} Emitter */
203/** */
204
205/** @returns {DataNode} */
206const newNode = (opts = {}) => {
207 /** @type DataNode */
208 const result = { children: [] };
209 Object.assign(result, opts);
210 return result;
211};
212
213class TokenTree {
214 constructor() {
215 /** @type DataNode */
216 this.rootNode = newNode();
217 this.stack = [this.rootNode];
218 }
219
220 get top() {
221 return this.stack[this.stack.length - 1];
222 }
223
224 get root() { return this.rootNode; }
225
226 /** @param {Node} node */
227 add(node) {
228 this.top.children.push(node);
229 }
230
231 /** @param {string} scope */
232 openNode(scope) {
233 /** @type Node */
234 const node = newNode({ scope });
235 this.add(node);
236 this.stack.push(node);
237 }
238
239 closeNode() {
240 if (this.stack.length > 1) {
241 return this.stack.pop();
242 }
243 // eslint-disable-next-line no-undefined
244 return undefined;
245 }
246
247 closeAllNodes() {
248 while (this.closeNode());
249 }
250
251 toJSON() {
252 return JSON.stringify(this.rootNode, null, 4);
253 }
254
255 /**
256 * @typedef { import("./html_renderer").Renderer } Renderer
257 * @param {Renderer} builder
258 */
259 walk(builder) {
260 // this does not
261 return this.constructor._walk(builder, this.rootNode);
262 // this works
263 // return TokenTree._walk(builder, this.rootNode);
264 }
265
266 /**
267 * @param {Renderer} builder
268 * @param {Node} node
269 */
270 static _walk(builder, node) {
271 if (typeof node === "string") {
272 builder.addText(node);
273 } else if (node.children) {
274 builder.openNode(node);
275 node.children.forEach((child) => this._walk(builder, child));
276 builder.closeNode(node);
277 }
278 return builder;
279 }
280
281 /**
282 * @param {Node} node
283 */
284 static _collapse(node) {
285 if (typeof node === "string") return;
286 if (!node.children) return;
287
288 if (node.children.every(el => typeof el === "string")) {
289 // node.text = node.children.join("");
290 // delete node.children;
291 node.children = [node.children.join("")];
292 } else {
293 node.children.forEach((child) => {
294 TokenTree._collapse(child);
295 });
296 }
297 }
298}
299
300/**
301 Currently this is all private API, but this is the minimal API necessary
302 that an Emitter must implement to fully support the parser.
303
304 Minimal interface:
305
306 - addKeyword(text, scope)
307 - addText(text)
308 - addSublanguage(emitter, subLanguageName)
309 - finalize()
310 - openNode(scope)
311 - closeNode()
312 - closeAllNodes()
313 - toHTML()
314
315*/
316
317/**
318 * @implements {Emitter}
319 */
320class TokenTreeEmitter extends TokenTree {
321 /**
322 * @param {*} options
323 */
324 constructor(options) {
325 super();
326 this.options = options;
327 }
328
329 /**
330 * @param {string} text
331 * @param {string} scope
332 */
333 addKeyword(text, scope) {
334 if (text === "") { return; }
335
336 this.openNode(scope);
337 this.addText(text);
338 this.closeNode();
339 }
340
341 /**
342 * @param {string} text
343 */
344 addText(text) {
345 if (text === "") { return; }
346
347 this.add(text);
348 }
349
350 /**
351 * @param {Emitter & {root: DataNode}} emitter
352 * @param {string} name
353 */
354 addSublanguage(emitter, name) {
355 /** @type DataNode */
356 const node = emitter.root;
357 node.sublanguage = true;
358 node.language = name;
359 this.add(node);
360 }
361
362 toHTML() {
363 const renderer = new HTMLRenderer(this, this.options);
364 return renderer.value();
365 }
366
367 finalize() {
368 return true;
369 }
370}
371
372/**
373 * @param {string} value
374 * @returns {RegExp}
375 * */
376
377/**
378 * @param {RegExp | string } re
379 * @returns {string}
380 */
381function source(re) {
382 if (!re) return null;
383 if (typeof re === "string") return re;
384
385 return re.source;
386}
387
388/**
389 * @param {RegExp | string } re
390 * @returns {string}
391 */
392function lookahead(re) {
393 return concat('(?=', re, ')');
394}
395
396/**
397 * @param {RegExp | string } re
398 * @returns {string}
399 */
400function anyNumberOfTimes(re) {
401 return concat('(?:', re, ')*');
402}
403
404/**
405 * @param {RegExp | string } re
406 * @returns {string}
407 */
408function optional(re) {
409 return concat('(?:', re, ')?');
410}
411
412/**
413 * @param {...(RegExp | string) } args
414 * @returns {string}
415 */
416function concat(...args) {
417 const joined = args.map((x) => source(x)).join("");
418 return joined;
419}
420
421/**
422 * @param { Array<string | RegExp | Object> } args
423 * @returns {object}
424 */
425function stripOptionsFromArgs(args) {
426 const opts = args[args.length - 1];
427
428 if (typeof opts === 'object' && opts.constructor === Object) {
429 args.splice(args.length - 1, 1);
430 return opts;
431 } else {
432 return {};
433 }
434}
435
436/** @typedef { {capture?: boolean} } RegexEitherOptions */
437
438/**
439 * Any of the passed expresssions may match
440 *
441 * Creates a huge this | this | that | that match
442 * @param {(RegExp | string)[] | [...(RegExp | string)[], RegexEitherOptions]} args
443 * @returns {string}
444 */
445function either(...args) {
446 /** @type { object & {capture?: boolean} } */
447 const opts = stripOptionsFromArgs(args);
448 const joined = '('
449 + (opts.capture ? "" : "?:")
450 + args.map((x) => source(x)).join("|") + ")";
451 return joined;
452}
453
454/**
455 * @param {RegExp | string} re
456 * @returns {number}
457 */
458function countMatchGroups(re) {
459 return (new RegExp(re.toString() + '|')).exec('').length - 1;
460}
461
462/**
463 * Does lexeme start with a regular expression match at the beginning
464 * @param {RegExp} re
465 * @param {string} lexeme
466 */
467function startsWith(re, lexeme) {
468 const match = re && re.exec(lexeme);
469 return match && match.index === 0;
470}
471
472// BACKREF_RE matches an open parenthesis or backreference. To avoid
473// an incorrect parse, it additionally matches the following:
474// - [...] elements, where the meaning of parentheses and escapes change
475// - other escape sequences, so we do not misparse escape sequences as
476// interesting elements
477// - non-matching or lookahead parentheses, which do not capture. These
478// follow the '(' with a '?'.
479const BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;
480
481// **INTERNAL** Not intended for outside usage
482// join logically computes regexps.join(separator), but fixes the
483// backreferences so they continue to match.
484// it also places each individual regular expression into it's own
485// match group, keeping track of the sequencing of those match groups
486// is currently an exercise for the caller. :-)
487/**
488 * @param {(string | RegExp)[]} regexps
489 * @param {{joinWith: string}} opts
490 * @returns {string}
491 */
492function _rewriteBackreferences(regexps, { joinWith }) {
493 let numCaptures = 0;
494
495 return regexps.map((regex) => {
496 numCaptures += 1;
497 const offset = numCaptures;
498 let re = source(regex);
499 let out = '';
500
501 while (re.length > 0) {
502 const match = BACKREF_RE.exec(re);
503 if (!match) {
504 out += re;
505 break;
506 }
507 out += re.substring(0, match.index);
508 re = re.substring(match.index + match[0].length);
509 if (match[0][0] === '\\' && match[1]) {
510 // Adjust the backreference.
511 out += '\\' + String(Number(match[1]) + offset);
512 } else {
513 out += match[0];
514 if (match[0] === '(') {
515 numCaptures++;
516 }
517 }
518 }
519 return out;
520 }).map(re => `(${re})`).join(joinWith);
521}
522
523/** @typedef {import('highlight.js').Mode} Mode */
524/** @typedef {import('highlight.js').ModeCallback} ModeCallback */
525
526// Common regexps
527const MATCH_NOTHING_RE = /\b\B/;
528const IDENT_RE = '[a-zA-Z]\\w*';
529const UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\w*';
530const NUMBER_RE = '\\b\\d+(\\.\\d+)?';
531const C_NUMBER_RE = '(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float
532const BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b...
533const RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~';
534
535/**
536* @param { Partial<Mode> & {binary?: string | RegExp} } opts
537*/
538const SHEBANG = (opts = {}) => {
539 const beginShebang = /^#![ ]*\//;
540 if (opts.binary) {
541 opts.begin = concat(
542 beginShebang,
543 /.*\b/,
544 opts.binary,
545 /\b.*/);
546 }
547 return inherit$1({
548 scope: 'meta',
549 begin: beginShebang,
550 end: /$/,
551 relevance: 0,
552 /** @type {ModeCallback} */
553 "on:begin": (m, resp) => {
554 if (m.index !== 0) resp.ignoreMatch();
555 }
556 }, opts);
557};
558
559// Common modes
560const BACKSLASH_ESCAPE = {
561 begin: '\\\\[\\s\\S]', relevance: 0
562};
563const APOS_STRING_MODE = {
564 scope: 'string',
565 begin: '\'',
566 end: '\'',
567 illegal: '\\n',
568 contains: [BACKSLASH_ESCAPE]
569};
570const QUOTE_STRING_MODE = {
571 scope: 'string',
572 begin: '"',
573 end: '"',
574 illegal: '\\n',
575 contains: [BACKSLASH_ESCAPE]
576};
577const PHRASAL_WORDS_MODE = {
578 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/
579};
580/**
581 * Creates a comment mode
582 *
583 * @param {string | RegExp} begin
584 * @param {string | RegExp} end
585 * @param {Mode | {}} [modeOptions]
586 * @returns {Partial<Mode>}
587 */
588const COMMENT = function(begin, end, modeOptions = {}) {
589 const mode = inherit$1(
590 {
591 scope: 'comment',
592 begin,
593 end,
594 contains: []
595 },
596 modeOptions
597 );
598 mode.contains.push({
599 scope: 'doctag',
600 // hack to avoid the space from being included. the space is necessary to
601 // match here to prevent the plain text rule below from gobbling up doctags
602 begin: '[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)',
603 end: /(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,
604 excludeBegin: true,
605 relevance: 0
606 });
607 const ENGLISH_WORD = either(
608 // list of common 1 and 2 letter words in English
609 "I",
610 "a",
611 "is",
612 "so",
613 "us",
614 "to",
615 "at",
616 "if",
617 "in",
618 "it",
619 "on",
620 // note: this is not an exhaustive list of contractions, just popular ones
621 /[A-Za-z]+['](d|ve|re|ll|t|s|n)/, // contractions - can't we'd they're let's, etc
622 /[A-Za-z]+[-][a-z]+/, // `no-way`, etc.
623 /[A-Za-z][a-z]{2,}/ // allow capitalized words at beginning of sentences
624 );
625 // looking like plain text, more likely to be a comment
626 mode.contains.push(
627 {
628 // TODO: how to include ", (, ) without breaking grammars that use these for
629 // comment delimiters?
630 // begin: /[ ]+([()"]?([A-Za-z'-]{3,}|is|a|I|so|us|[tT][oO]|at|if|in|it|on)[.]?[()":]?([.][ ]|[ ]|\))){3}/
631 // ---
632
633 // this tries to find sequences of 3 english words in a row (without any
634 // "programming" type syntax) this gives us a strong signal that we've
635 // TRULY found a comment - vs perhaps scanning with the wrong language.
636 // It's possible to find something that LOOKS like the start of the
637 // comment - but then if there is no readable text - good chance it is a
638 // false match and not a comment.
639 //
640 // for a visual example please see:
641 // https://github.com/highlightjs/highlight.js/issues/2827
642
643 begin: concat(
644 /[ ]+/, // necessary to prevent us gobbling up doctags like /* @author Bob Mcgill */
645 '(',
646 ENGLISH_WORD,
647 /[.]?[:]?([.][ ]|[ ])/,
648 '){3}') // look for 3 words in a row
649 }
650 );
651 return mode;
652};
653const C_LINE_COMMENT_MODE = COMMENT('//', '$');
654const C_BLOCK_COMMENT_MODE = COMMENT('/\\*', '\\*/');
655const HASH_COMMENT_MODE = COMMENT('#', '$');
656const NUMBER_MODE = {
657 scope: 'number',
658 begin: NUMBER_RE,
659 relevance: 0
660};
661const C_NUMBER_MODE = {
662 scope: 'number',
663 begin: C_NUMBER_RE,
664 relevance: 0
665};
666const BINARY_NUMBER_MODE = {
667 scope: 'number',
668 begin: BINARY_NUMBER_RE,
669 relevance: 0
670};
671const REGEXP_MODE = {
672 // this outer rule makes sure we actually have a WHOLE regex and not simply
673 // an expression such as:
674 //
675 // 3 / something
676 //
677 // (which will then blow up when regex's `illegal` sees the newline)
678 begin: /(?=\/[^/\n]*\/)/,
679 contains: [{
680 scope: 'regexp',
681 begin: /\//,
682 end: /\/[gimuy]*/,
683 illegal: /\n/,
684 contains: [
685 BACKSLASH_ESCAPE,
686 {
687 begin: /\[/,
688 end: /\]/,
689 relevance: 0,
690 contains: [BACKSLASH_ESCAPE]
691 }
692 ]
693 }]
694};
695const TITLE_MODE = {
696 scope: 'title',
697 begin: IDENT_RE,
698 relevance: 0
699};
700const UNDERSCORE_TITLE_MODE = {
701 scope: 'title',
702 begin: UNDERSCORE_IDENT_RE,
703 relevance: 0
704};
705const METHOD_GUARD = {
706 // excludes method names from keyword processing
707 begin: '\\.\\s*' + UNDERSCORE_IDENT_RE,
708 relevance: 0
709};
710
711/**
712 * Adds end same as begin mechanics to a mode
713 *
714 * Your mode must include at least a single () match group as that first match
715 * group is what is used for comparison
716 * @param {Partial<Mode>} mode
717 */
718const END_SAME_AS_BEGIN = function(mode) {
719 return Object.assign(mode,
720 {
721 /** @type {ModeCallback} */
722 'on:begin': (m, resp) => { resp.data._beginMatch = m[1]; },
723 /** @type {ModeCallback} */
724 'on:end': (m, resp) => { if (resp.data._beginMatch !== m[1]) resp.ignoreMatch(); }
725 });
726};
727
728var MODES = /*#__PURE__*/Object.freeze({
729 __proto__: null,
730 MATCH_NOTHING_RE: MATCH_NOTHING_RE,
731 IDENT_RE: IDENT_RE,
732 UNDERSCORE_IDENT_RE: UNDERSCORE_IDENT_RE,
733 NUMBER_RE: NUMBER_RE,
734 C_NUMBER_RE: C_NUMBER_RE,
735 BINARY_NUMBER_RE: BINARY_NUMBER_RE,
736 RE_STARTERS_RE: RE_STARTERS_RE,
737 SHEBANG: SHEBANG,
738 BACKSLASH_ESCAPE: BACKSLASH_ESCAPE,
739 APOS_STRING_MODE: APOS_STRING_MODE,
740 QUOTE_STRING_MODE: QUOTE_STRING_MODE,
741 PHRASAL_WORDS_MODE: PHRASAL_WORDS_MODE,
742 COMMENT: COMMENT,
743 C_LINE_COMMENT_MODE: C_LINE_COMMENT_MODE,
744 C_BLOCK_COMMENT_MODE: C_BLOCK_COMMENT_MODE,
745 HASH_COMMENT_MODE: HASH_COMMENT_MODE,
746 NUMBER_MODE: NUMBER_MODE,
747 C_NUMBER_MODE: C_NUMBER_MODE,
748 BINARY_NUMBER_MODE: BINARY_NUMBER_MODE,
749 REGEXP_MODE: REGEXP_MODE,
750 TITLE_MODE: TITLE_MODE,
751 UNDERSCORE_TITLE_MODE: UNDERSCORE_TITLE_MODE,
752 METHOD_GUARD: METHOD_GUARD,
753 END_SAME_AS_BEGIN: END_SAME_AS_BEGIN
754});
755
756/**
757@typedef {import('highlight.js').CallbackResponse} CallbackResponse
758@typedef {import('highlight.js').CompilerExt} CompilerExt
759*/
760
761// Grammar extensions / plugins
762// See: https://github.com/highlightjs/highlight.js/issues/2833
763
764// Grammar extensions allow "syntactic sugar" to be added to the grammar modes
765// without requiring any underlying changes to the compiler internals.
766
767// `compileMatch` being the perfect small example of now allowing a grammar
768// author to write `match` when they desire to match a single expression rather
769// than being forced to use `begin`. The extension then just moves `match` into
770// `begin` when it runs. Ie, no features have been added, but we've just made
771// the experience of writing (and reading grammars) a little bit nicer.
772
773// ------
774
775// TODO: We need negative look-behind support to do this properly
776/**
777 * Skip a match if it has a preceding dot
778 *
779 * This is used for `beginKeywords` to prevent matching expressions such as
780 * `bob.keyword.do()`. The mode compiler automatically wires this up as a
781 * special _internal_ 'on:begin' callback for modes with `beginKeywords`
782 * @param {RegExpMatchArray} match
783 * @param {CallbackResponse} response
784 */
785function skipIfHasPrecedingDot(match, response) {
786 const before = match.input[match.index - 1];
787 if (before === ".") {
788 response.ignoreMatch();
789 }
790}
791
792/**
793 *
794 * @type {CompilerExt}
795 */
796function scopeClassName(mode, _parent) {
797 // eslint-disable-next-line no-undefined
798 if (mode.className !== undefined) {
799 mode.scope = mode.className;
800 delete mode.className;
801 }
802}
803
804/**
805 * `beginKeywords` syntactic sugar
806 * @type {CompilerExt}
807 */
808function beginKeywords(mode, parent) {
809 if (!parent) return;
810 if (!mode.beginKeywords) return;
811
812 // for languages with keywords that include non-word characters checking for
813 // a word boundary is not sufficient, so instead we check for a word boundary
814 // or whitespace - this does no harm in any case since our keyword engine
815 // doesn't allow spaces in keywords anyways and we still check for the boundary
816 // first
817 mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')(?!\\.)(?=\\b|\\s)';
818 mode.__beforeBegin = skipIfHasPrecedingDot;
819 mode.keywords = mode.keywords || mode.beginKeywords;
820 delete mode.beginKeywords;
821
822 // prevents double relevance, the keywords themselves provide
823 // relevance, the mode doesn't need to double it
824 // eslint-disable-next-line no-undefined
825 if (mode.relevance === undefined) mode.relevance = 0;
826}
827
828/**
829 * Allow `illegal` to contain an array of illegal values
830 * @type {CompilerExt}
831 */
832function compileIllegal(mode, _parent) {
833 if (!Array.isArray(mode.illegal)) return;
834
835 mode.illegal = either(...mode.illegal);
836}
837
838/**
839 * `match` to match a single expression for readability
840 * @type {CompilerExt}
841 */
842function compileMatch(mode, _parent) {
843 if (!mode.match) return;
844 if (mode.begin || mode.end) throw new Error("begin & end are not supported with match");
845
846 mode.begin = mode.match;
847 delete mode.match;
848}
849
850/**
851 * provides the default 1 relevance to all modes
852 * @type {CompilerExt}
853 */
854function compileRelevance(mode, _parent) {
855 // eslint-disable-next-line no-undefined
856 if (mode.relevance === undefined) mode.relevance = 1;
857}
858
859// allow beforeMatch to act as a "qualifier" for the match
860// the full match begin must be [beforeMatch][begin]
861const beforeMatchExt = (mode, parent) => {
862 if (!mode.beforeMatch) return;
863 // starts conflicts with endsParent which we need to make sure the child
864 // rule is not matched multiple times
865 if (mode.starts) throw new Error("beforeMatch cannot be used with starts");
866
867 const originalMode = Object.assign({}, mode);
868 Object.keys(mode).forEach((key) => { delete mode[key]; });
869
870 mode.keywords = originalMode.keywords;
871 mode.begin = concat(originalMode.beforeMatch, lookahead(originalMode.begin));
872 mode.starts = {
873 relevance: 0,
874 contains: [
875 Object.assign(originalMode, { endsParent: true })
876 ]
877 };
878 mode.relevance = 0;
879
880 delete originalMode.beforeMatch;
881};
882
883// keywords that should have no default relevance value
884const COMMON_KEYWORDS = [
885 'of',
886 'and',
887 'for',
888 'in',
889 'not',
890 'or',
891 'if',
892 'then',
893 'parent', // common variable name
894 'list', // common variable name
895 'value' // common variable name
896];
897
898const DEFAULT_KEYWORD_SCOPE = "keyword";
899
900/**
901 * Given raw keywords from a language definition, compile them.
902 *
903 * @param {string | Record<string,string|string[]> | Array<string>} rawKeywords
904 * @param {boolean} caseInsensitive
905 */
906function compileKeywords(rawKeywords, caseInsensitive, scopeName = DEFAULT_KEYWORD_SCOPE) {
907 /** @type KeywordDict */
908 const compiledKeywords = Object.create(null);
909
910 // input can be a string of keywords, an array of keywords, or a object with
911 // named keys representing scopeName (which can then point to a string or array)
912 if (typeof rawKeywords === 'string') {
913 compileList(scopeName, rawKeywords.split(" "));
914 } else if (Array.isArray(rawKeywords)) {
915 compileList(scopeName, rawKeywords);
916 } else {
917 Object.keys(rawKeywords).forEach(function(scopeName) {
918 // collapse all our objects back into the parent object
919 Object.assign(
920 compiledKeywords,
921 compileKeywords(rawKeywords[scopeName], caseInsensitive, scopeName)
922 );
923 });
924 }
925 return compiledKeywords;
926
927 // ---
928
929 /**
930 * Compiles an individual list of keywords
931 *
932 * Ex: "for if when while|5"
933 *
934 * @param {string} scopeName
935 * @param {Array<string>} keywordList
936 */
937 function compileList(scopeName, keywordList) {
938 if (caseInsensitive) {
939 keywordList = keywordList.map(x => x.toLowerCase());
940 }
941 keywordList.forEach(function(keyword) {
942 const pair = keyword.split('|');
943 compiledKeywords[pair[0]] = [scopeName, scoreForKeyword(pair[0], pair[1])];
944 });
945 }
946}
947
948/**
949 * Returns the proper score for a given keyword
950 *
951 * Also takes into account comment keywords, which will be scored 0 UNLESS
952 * another score has been manually assigned.
953 * @param {string} keyword
954 * @param {string} [providedScore]
955 */
956function scoreForKeyword(keyword, providedScore) {
957 // manual scores always win over common keywords
958 // so you can force a score of 1 if you really insist
959 if (providedScore) {
960 return Number(providedScore);
961 }
962
963 return commonKeyword(keyword) ? 0 : 1;
964}
965
966/**
967 * Determines if a given keyword is common or not
968 *
969 * @param {string} keyword */
970function commonKeyword(keyword) {
971 return COMMON_KEYWORDS.includes(keyword.toLowerCase());
972}
973
974/*
975
976For the reasoning behind this please see:
977https://github.com/highlightjs/highlight.js/issues/2880#issuecomment-747275419
978
979*/
980
981/**
982 * @type {Record<string, boolean>}
983 */
984const seenDeprecations = {};
985
986/**
987 * @param {string} message
988 */
989const error = (message) => {
990 console.error(message);
991};
992
993/**
994 * @param {string} message
995 * @param {any} args
996 */
997const warn = (message, ...args) => {
998 console.log(`WARN: ${message}`, ...args);
999};
1000
1001/**
1002 * @param {string} version
1003 * @param {string} message
1004 */
1005const deprecated = (version, message) => {
1006 if (seenDeprecations[`${version}/${message}`]) return;
1007
1008 console.log(`Deprecated as of ${version}. ${message}`);
1009 seenDeprecations[`${version}/${message}`] = true;
1010};
1011
1012/* eslint-disable no-throw-literal */
1013
1014/**
1015@typedef {import('highlight.js').CompiledMode} CompiledMode
1016*/
1017
1018const MultiClassError = new Error();
1019
1020/**
1021 * Renumbers labeled scope names to account for additional inner match
1022 * groups that otherwise would break everything.
1023 *
1024 * Lets say we 3 match scopes:
1025 *
1026 * { 1 => ..., 2 => ..., 3 => ... }
1027 *
1028 * So what we need is a clean match like this:
1029 *
1030 * (a)(b)(c) => [ "a", "b", "c" ]
1031 *
1032 * But this falls apart with inner match groups:
1033 *
1034 * (a)(((b)))(c) => ["a", "b", "b", "b", "c" ]
1035 *
1036 * Our scopes are now "out of alignment" and we're repeating `b` 3 times.
1037 * What needs to happen is the numbers are remapped:
1038 *
1039 * { 1 => ..., 2 => ..., 5 => ... }
1040 *
1041 * We also need to know that the ONLY groups that should be output
1042 * are 1, 2, and 5. This function handles this behavior.
1043 *
1044 * @param {CompiledMode} mode
1045 * @param {Array<RegExp | string>} regexes
1046 * @param {{key: "beginScope"|"endScope"}} opts
1047 */
1048function remapScopeNames(mode, regexes, { key }) {
1049 let offset = 0;
1050 const scopeNames = mode[key];
1051 /** @type Record<number,boolean> */
1052 const emit = {};
1053 /** @type Record<number,string> */
1054 const positions = {};
1055
1056 for (let i = 1; i <= regexes.length; i++) {
1057 positions[i + offset] = scopeNames[i];
1058 emit[i + offset] = true;
1059 offset += countMatchGroups(regexes[i - 1]);
1060 }
1061 // we use _emit to keep track of which match groups are "top-level" to avoid double
1062 // output from inside match groups
1063 mode[key] = positions;
1064 mode[key]._emit = emit;
1065 mode[key]._multi = true;
1066}
1067
1068/**
1069 * @param {CompiledMode} mode
1070 */
1071function beginMultiClass(mode) {
1072 if (!Array.isArray(mode.begin)) return;
1073
1074 if (mode.skip || mode.excludeBegin || mode.returnBegin) {
1075 error("skip, excludeBegin, returnBegin not compatible with beginScope: {}");
1076 throw MultiClassError;
1077 }
1078
1079 if (typeof mode.beginScope !== "object" || mode.beginScope === null) {
1080 error("beginScope must be object");
1081 throw MultiClassError;
1082 }
1083
1084 remapScopeNames(mode, mode.begin, { key: "beginScope" });
1085 mode.begin = _rewriteBackreferences(mode.begin, { joinWith: "" });
1086}
1087
1088/**
1089 * @param {CompiledMode} mode
1090 */
1091function endMultiClass(mode) {
1092 if (!Array.isArray(mode.end)) return;
1093
1094 if (mode.skip || mode.excludeEnd || mode.returnEnd) {
1095 error("skip, excludeEnd, returnEnd not compatible with endScope: {}");
1096 throw MultiClassError;
1097 }
1098
1099 if (typeof mode.endScope !== "object" || mode.endScope === null) {
1100 error("endScope must be object");
1101 throw MultiClassError;
1102 }
1103
1104 remapScopeNames(mode, mode.end, { key: "endScope" });
1105 mode.end = _rewriteBackreferences(mode.end, { joinWith: "" });
1106}
1107
1108/**
1109 * this exists only to allow `scope: {}` to be used beside `match:`
1110 * Otherwise `beginScope` would necessary and that would look weird
1111
1112 {
1113 match: [ /def/, /\w+/ ]
1114 scope: { 1: "keyword" , 2: "title" }
1115 }
1116
1117 * @param {CompiledMode} mode
1118 */
1119function scopeSugar(mode) {
1120 if (mode.scope && typeof mode.scope === "object" && mode.scope !== null) {
1121 mode.beginScope = mode.scope;
1122 delete mode.scope;
1123 }
1124}
1125
1126/**
1127 * @param {CompiledMode} mode
1128 */
1129function MultiClass(mode) {
1130 scopeSugar(mode);
1131
1132 if (typeof mode.beginScope === "string") {
1133 mode.beginScope = { _wrap: mode.beginScope };
1134 }
1135 if (typeof mode.endScope === "string") {
1136 mode.endScope = { _wrap: mode.endScope };
1137 }
1138
1139 beginMultiClass(mode);
1140 endMultiClass(mode);
1141}
1142
1143/**
1144@typedef {import('highlight.js').Mode} Mode
1145@typedef {import('highlight.js').CompiledMode} CompiledMode
1146@typedef {import('highlight.js').Language} Language
1147@typedef {import('highlight.js').HLJSPlugin} HLJSPlugin
1148@typedef {import('highlight.js').CompiledLanguage} CompiledLanguage
1149*/
1150
1151// compilation
1152
1153/**
1154 * Compiles a language definition result
1155 *
1156 * Given the raw result of a language definition (Language), compiles this so
1157 * that it is ready for highlighting code.
1158 * @param {Language} language
1159 * @returns {CompiledLanguage}
1160 */
1161function compileLanguage(language) {
1162 /**
1163 * Builds a regex with the case sensitivity of the current language
1164 *
1165 * @param {RegExp | string} value
1166 * @param {boolean} [global]
1167 */
1168 function langRe(value, global) {
1169 return new RegExp(
1170 source(value),
1171 'm'
1172 + (language.case_insensitive ? 'i' : '')
1173 + (language.unicodeRegex ? 'u' : '')
1174 + (global ? 'g' : '')
1175 );
1176 }
1177
1178 /**
1179 Stores multiple regular expressions and allows you to quickly search for
1180 them all in a string simultaneously - returning the first match. It does
1181 this by creating a huge (a|b|c) regex - each individual item wrapped with ()
1182 and joined by `|` - using match groups to track position. When a match is
1183 found checking which position in the array has content allows us to figure
1184 out which of the original regexes / match groups triggered the match.
1185
1186 The match object itself (the result of `Regex.exec`) is returned but also
1187 enhanced by merging in any meta-data that was registered with the regex.
1188 This is how we keep track of which mode matched, and what type of rule
1189 (`illegal`, `begin`, end, etc).
1190 */
1191 class MultiRegex {
1192 constructor() {
1193 this.matchIndexes = {};
1194 // @ts-ignore
1195 this.regexes = [];
1196 this.matchAt = 1;
1197 this.position = 0;
1198 }
1199
1200 // @ts-ignore
1201 addRule(re, opts) {
1202 opts.position = this.position++;
1203 // @ts-ignore
1204 this.matchIndexes[this.matchAt] = opts;
1205 this.regexes.push([opts, re]);
1206 this.matchAt += countMatchGroups(re) + 1;
1207 }
1208
1209 compile() {
1210 if (this.regexes.length === 0) {
1211 // avoids the need to check length every time exec is called
1212 // @ts-ignore
1213 this.exec = () => null;
1214 }
1215 const terminators = this.regexes.map(el => el[1]);
1216 this.matcherRe = langRe(_rewriteBackreferences(terminators, { joinWith: '|' }), true);
1217 this.lastIndex = 0;
1218 }
1219
1220 /** @param {string} s */
1221 exec(s) {
1222 this.matcherRe.lastIndex = this.lastIndex;
1223 const match = this.matcherRe.exec(s);
1224 if (!match) { return null; }
1225
1226 // eslint-disable-next-line no-undefined
1227 const i = match.findIndex((el, i) => i > 0 && el !== undefined);
1228 // @ts-ignore
1229 const matchData = this.matchIndexes[i];
1230 // trim off any earlier non-relevant match groups (ie, the other regex
1231 // match groups that make up the multi-matcher)
1232 match.splice(0, i);
1233
1234 return Object.assign(match, matchData);
1235 }
1236 }
1237
1238 /*
1239 Created to solve the key deficiently with MultiRegex - there is no way to
1240 test for multiple matches at a single location. Why would we need to do
1241 that? In the future a more dynamic engine will allow certain matches to be
1242 ignored. An example: if we matched say the 3rd regex in a large group but
1243 decided to ignore it - we'd need to started testing again at the 4th
1244 regex... but MultiRegex itself gives us no real way to do that.
1245
1246 So what this class creates MultiRegexs on the fly for whatever search
1247 position they are needed.
1248
1249 NOTE: These additional MultiRegex objects are created dynamically. For most
1250 grammars most of the time we will never actually need anything more than the
1251 first MultiRegex - so this shouldn't have too much overhead.
1252
1253 Say this is our search group, and we match regex3, but wish to ignore it.
1254
1255 regex1 | regex2 | regex3 | regex4 | regex5 ' ie, startAt = 0
1256
1257 What we need is a new MultiRegex that only includes the remaining
1258 possibilities:
1259
1260 regex4 | regex5 ' ie, startAt = 3
1261
1262 This class wraps all that complexity up in a simple API... `startAt` decides
1263 where in the array of expressions to start doing the matching. It
1264 auto-increments, so if a match is found at position 2, then startAt will be
1265 set to 3. If the end is reached startAt will return to 0.
1266
1267 MOST of the time the parser will be setting startAt manually to 0.
1268 */
1269 class ResumableMultiRegex {
1270 constructor() {
1271 // @ts-ignore
1272 this.rules = [];
1273 // @ts-ignore
1274 this.multiRegexes = [];
1275 this.count = 0;
1276
1277 this.lastIndex = 0;
1278 this.regexIndex = 0;
1279 }
1280
1281 // @ts-ignore
1282 getMatcher(index) {
1283 if (this.multiRegexes[index]) return this.multiRegexes[index];
1284
1285 const matcher = new MultiRegex();
1286 this.rules.slice(index).forEach(([re, opts]) => matcher.addRule(re, opts));
1287 matcher.compile();
1288 this.multiRegexes[index] = matcher;
1289 return matcher;
1290 }
1291
1292 resumingScanAtSamePosition() {
1293 return this.regexIndex !== 0;
1294 }
1295
1296 considerAll() {
1297 this.regexIndex = 0;
1298 }
1299
1300 // @ts-ignore
1301 addRule(re, opts) {
1302 this.rules.push([re, opts]);
1303 if (opts.type === "begin") this.count++;
1304 }
1305
1306 /** @param {string} s */
1307 exec(s) {
1308 const m = this.getMatcher(this.regexIndex);
1309 m.lastIndex = this.lastIndex;
1310 let result = m.exec(s);
1311
1312 // The following is because we have no easy way to say "resume scanning at the
1313 // existing position but also skip the current rule ONLY". What happens is
1314 // all prior rules are also skipped which can result in matching the wrong
1315 // thing. Example of matching "booger":
1316
1317 // our matcher is [string, "booger", number]
1318 //
1319 // ....booger....
1320
1321 // if "booger" is ignored then we'd really need a regex to scan from the
1322 // SAME position for only: [string, number] but ignoring "booger" (if it
1323 // was the first match), a simple resume would scan ahead who knows how
1324 // far looking only for "number", ignoring potential string matches (or
1325 // future "booger" matches that might be valid.)
1326
1327 // So what we do: We execute two matchers, one resuming at the same
1328 // position, but the second full matcher starting at the position after:
1329
1330 // /--- resume first regex match here (for [number])
1331 // |/---- full match here for [string, "booger", number]
1332 // vv
1333 // ....booger....
1334
1335 // Which ever results in a match first is then used. So this 3-4 step
1336 // process essentially allows us to say "match at this position, excluding
1337 // a prior rule that was ignored".
1338 //
1339 // 1. Match "booger" first, ignore. Also proves that [string] does non match.
1340 // 2. Resume matching for [number]
1341 // 3. Match at index + 1 for [string, "booger", number]
1342 // 4. If #2 and #3 result in matches, which came first?
1343 if (this.resumingScanAtSamePosition()) {
1344 if (result && result.index === this.lastIndex) ; else { // use the second matcher result
1345 const m2 = this.getMatcher(0);
1346 m2.lastIndex = this.lastIndex + 1;
1347 result = m2.exec(s);
1348 }
1349 }
1350
1351 if (result) {
1352 this.regexIndex += result.position + 1;
1353 if (this.regexIndex === this.count) {
1354 // wrap-around to considering all matches again
1355 this.considerAll();
1356 }
1357 }
1358
1359 return result;
1360 }
1361 }
1362
1363 /**
1364 * Given a mode, builds a huge ResumableMultiRegex that can be used to walk
1365 * the content and find matches.
1366 *
1367 * @param {CompiledMode} mode
1368 * @returns {ResumableMultiRegex}
1369 */
1370 function buildModeRegex(mode) {
1371 const mm = new ResumableMultiRegex();
1372
1373 mode.contains.forEach(term => mm.addRule(term.begin, { rule: term, type: "begin" }));
1374
1375 if (mode.terminatorEnd) {
1376 mm.addRule(mode.terminatorEnd, { type: "end" });
1377 }
1378 if (mode.illegal) {
1379 mm.addRule(mode.illegal, { type: "illegal" });
1380 }
1381
1382 return mm;
1383 }
1384
1385 /** skip vs abort vs ignore
1386 *
1387 * @skip - The mode is still entered and exited normally (and contains rules apply),
1388 * but all content is held and added to the parent buffer rather than being
1389 * output when the mode ends. Mostly used with `sublanguage` to build up
1390 * a single large buffer than can be parsed by sublanguage.
1391 *
1392 * - The mode begin ands ends normally.
1393 * - Content matched is added to the parent mode buffer.
1394 * - The parser cursor is moved forward normally.
1395 *
1396 * @abort - A hack placeholder until we have ignore. Aborts the mode (as if it
1397 * never matched) but DOES NOT continue to match subsequent `contains`
1398 * modes. Abort is bad/suboptimal because it can result in modes
1399 * farther down not getting applied because an earlier rule eats the
1400 * content but then aborts.
1401 *
1402 * - The mode does not begin.
1403 * - Content matched by `begin` is added to the mode buffer.
1404 * - The parser cursor is moved forward accordingly.
1405 *
1406 * @ignore - Ignores the mode (as if it never matched) and continues to match any
1407 * subsequent `contains` modes. Ignore isn't technically possible with
1408 * the current parser implementation.
1409 *
1410 * - The mode does not begin.
1411 * - Content matched by `begin` is ignored.
1412 * - The parser cursor is not moved forward.
1413 */
1414
1415 /**
1416 * Compiles an individual mode
1417 *
1418 * This can raise an error if the mode contains certain detectable known logic
1419 * issues.
1420 * @param {Mode} mode
1421 * @param {CompiledMode | null} [parent]
1422 * @returns {CompiledMode | never}
1423 */
1424 function compileMode(mode, parent) {
1425 const cmode = /** @type CompiledMode */ (mode);
1426 if (mode.isCompiled) return cmode;
1427
1428 [
1429 scopeClassName,
1430 // do this early so compiler extensions generally don't have to worry about
1431 // the distinction between match/begin
1432 compileMatch,
1433 MultiClass,
1434 beforeMatchExt
1435 ].forEach(ext => ext(mode, parent));
1436
1437 language.compilerExtensions.forEach(ext => ext(mode, parent));
1438
1439 // __beforeBegin is considered private API, internal use only
1440 mode.__beforeBegin = null;
1441
1442 [
1443 beginKeywords,
1444 // do this later so compiler extensions that come earlier have access to the
1445 // raw array if they wanted to perhaps manipulate it, etc.
1446 compileIllegal,
1447 // default to 1 relevance if not specified
1448 compileRelevance
1449 ].forEach(ext => ext(mode, parent));
1450
1451 mode.isCompiled = true;
1452
1453 let keywordPattern = null;
1454 if (typeof mode.keywords === "object" && mode.keywords.$pattern) {
1455 // we need a copy because keywords might be compiled multiple times
1456 // so we can't go deleting $pattern from the original on the first
1457 // pass
1458 mode.keywords = Object.assign({}, mode.keywords);
1459 keywordPattern = mode.keywords.$pattern;
1460 delete mode.keywords.$pattern;
1461 }
1462 keywordPattern = keywordPattern || /\w+/;
1463
1464 if (mode.keywords) {
1465 mode.keywords = compileKeywords(mode.keywords, language.case_insensitive);
1466 }
1467
1468 cmode.keywordPatternRe = langRe(keywordPattern, true);
1469
1470 if (parent) {
1471 if (!mode.begin) mode.begin = /\B|\b/;
1472 cmode.beginRe = langRe(cmode.begin);
1473 if (!mode.end && !mode.endsWithParent) mode.end = /\B|\b/;
1474 if (mode.end) cmode.endRe = langRe(cmode.end);
1475 cmode.terminatorEnd = source(cmode.end) || '';
1476 if (mode.endsWithParent && parent.terminatorEnd) {
1477 cmode.terminatorEnd += (mode.end ? '|' : '') + parent.terminatorEnd;
1478 }
1479 }
1480 if (mode.illegal) cmode.illegalRe = langRe(/** @type {RegExp | string} */ (mode.illegal));
1481 if (!mode.contains) mode.contains = [];
1482
1483 mode.contains = [].concat(...mode.contains.map(function(c) {
1484 return expandOrCloneMode(c === 'self' ? mode : c);
1485 }));
1486 mode.contains.forEach(function(c) { compileMode(/** @type Mode */ (c), cmode); });
1487
1488 if (mode.starts) {
1489 compileMode(mode.starts, parent);
1490 }
1491
1492 cmode.matcher = buildModeRegex(cmode);
1493 return cmode;
1494 }
1495
1496 if (!language.compilerExtensions) language.compilerExtensions = [];
1497
1498 // self is not valid at the top-level
1499 if (language.contains && language.contains.includes('self')) {
1500 throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");
1501 }
1502
1503 // we need a null object, which inherit will guarantee
1504 language.classNameAliases = inherit$1(language.classNameAliases || {});
1505
1506 return compileMode(/** @type Mode */ (language));
1507}
1508
1509/**
1510 * Determines if a mode has a dependency on it's parent or not
1511 *
1512 * If a mode does have a parent dependency then often we need to clone it if
1513 * it's used in multiple places so that each copy points to the correct parent,
1514 * where-as modes without a parent can often safely be re-used at the bottom of
1515 * a mode chain.
1516 *
1517 * @param {Mode | null} mode
1518 * @returns {boolean} - is there a dependency on the parent?
1519 * */
1520function dependencyOnParent(mode) {
1521 if (!mode) return false;
1522
1523 return mode.endsWithParent || dependencyOnParent(mode.starts);
1524}
1525
1526/**
1527 * Expands a mode or clones it if necessary
1528 *
1529 * This is necessary for modes with parental dependenceis (see notes on
1530 * `dependencyOnParent`) and for nodes that have `variants` - which must then be
1531 * exploded into their own individual modes at compile time.
1532 *
1533 * @param {Mode} mode
1534 * @returns {Mode | Mode[]}
1535 * */
1536function expandOrCloneMode(mode) {
1537 if (mode.variants && !mode.cachedVariants) {
1538 mode.cachedVariants = mode.variants.map(function(variant) {
1539 return inherit$1(mode, { variants: null }, variant);
1540 });
1541 }
1542
1543 // EXPAND
1544 // if we have variants then essentially "replace" the mode with the variants
1545 // this happens in compileMode, where this function is called from
1546 if (mode.cachedVariants) {
1547 return mode.cachedVariants;
1548 }
1549
1550 // CLONE
1551 // if we have dependencies on parents then we need a unique
1552 // instance of ourselves, so we can be reused with many
1553 // different parents without issue
1554 if (dependencyOnParent(mode)) {
1555 return inherit$1(mode, { starts: mode.starts ? inherit$1(mode.starts) : null });
1556 }
1557
1558 if (Object.isFrozen(mode)) {
1559 return inherit$1(mode);
1560 }
1561
1562 // no special dependency issues, just return ourselves
1563 return mode;
1564}
1565
1566var version = "11.6.0";
1567
1568class HTMLInjectionError extends Error {
1569 constructor(reason, html) {
1570 super(reason);
1571 this.name = "HTMLInjectionError";
1572 this.html = html;
1573 }
1574}
1575
1576/*
1577Syntax highlighting with language autodetection.
1578https://highlightjs.org/
1579*/
1580
1581/**
1582@typedef {import('highlight.js').Mode} Mode
1583@typedef {import('highlight.js').CompiledMode} CompiledMode
1584@typedef {import('highlight.js').CompiledScope} CompiledScope
1585@typedef {import('highlight.js').Language} Language
1586@typedef {import('highlight.js').HLJSApi} HLJSApi
1587@typedef {import('highlight.js').HLJSPlugin} HLJSPlugin
1588@typedef {import('highlight.js').PluginEvent} PluginEvent
1589@typedef {import('highlight.js').HLJSOptions} HLJSOptions
1590@typedef {import('highlight.js').LanguageFn} LanguageFn
1591@typedef {import('highlight.js').HighlightedHTMLElement} HighlightedHTMLElement
1592@typedef {import('highlight.js').BeforeHighlightContext} BeforeHighlightContext
1593@typedef {import('highlight.js/private').MatchType} MatchType
1594@typedef {import('highlight.js/private').KeywordData} KeywordData
1595@typedef {import('highlight.js/private').EnhancedMatch} EnhancedMatch
1596@typedef {import('highlight.js/private').AnnotatedError} AnnotatedError
1597@typedef {import('highlight.js').AutoHighlightResult} AutoHighlightResult
1598@typedef {import('highlight.js').HighlightOptions} HighlightOptions
1599@typedef {import('highlight.js').HighlightResult} HighlightResult
1600*/
1601
1602
1603const escape = escapeHTML;
1604const inherit = inherit$1;
1605const NO_MATCH = Symbol("nomatch");
1606const MAX_KEYWORD_HITS = 7;
1607
1608/**
1609 * @param {any} hljs - object that is extended (legacy)
1610 * @returns {HLJSApi}
1611 */
1612const HLJS = function(hljs) {
1613 // Global internal variables used within the highlight.js library.
1614 /** @type {Record<string, Language>} */
1615 const languages = Object.create(null);
1616 /** @type {Record<string, string>} */
1617 const aliases = Object.create(null);
1618 /** @type {HLJSPlugin[]} */
1619 const plugins = [];
1620
1621 // safe/production mode - swallows more errors, tries to keep running
1622 // even if a single syntax or parse hits a fatal error
1623 let SAFE_MODE = true;
1624 const LANGUAGE_NOT_FOUND = "Could not find the language '{}', did you forget to load/include a language module?";
1625 /** @type {Language} */
1626 const PLAINTEXT_LANGUAGE = { disableAutodetect: true, name: 'Plain text', contains: [] };
1627
1628 // Global options used when within external APIs. This is modified when
1629 // calling the `hljs.configure` function.
1630 /** @type HLJSOptions */
1631 let options = {
1632 ignoreUnescapedHTML: false,
1633 throwUnescapedHTML: false,
1634 noHighlightRe: /^(no-?highlight)$/i,
1635 languageDetectRe: /\blang(?:uage)?-([\w-]+)\b/i,
1636 classPrefix: 'hljs-',
1637 cssSelector: 'pre code',
1638 languages: null,
1639 // beta configuration options, subject to change, welcome to discuss
1640 // https://github.com/highlightjs/highlight.js/issues/1086
1641 __emitter: TokenTreeEmitter
1642 };
1643
1644 /* Utility functions */
1645
1646 /**
1647 * Tests a language name to see if highlighting should be skipped
1648 * @param {string} languageName
1649 */
1650 function shouldNotHighlight(languageName) {
1651 return options.noHighlightRe.test(languageName);
1652 }
1653
1654 /**
1655 * @param {HighlightedHTMLElement} block - the HTML element to determine language for
1656 */
1657 function blockLanguage(block) {
1658 let classes = block.className + ' ';
1659
1660 classes += block.parentNode ? block.parentNode.className : '';
1661
1662 // language-* takes precedence over non-prefixed class names.
1663 const match = options.languageDetectRe.exec(classes);
1664 if (match) {
1665 const language = getLanguage(match[1]);
1666 if (!language) {
1667 warn(LANGUAGE_NOT_FOUND.replace("{}", match[1]));
1668 warn("Falling back to no-highlight mode for this block.", block);
1669 }
1670 return language ? match[1] : 'no-highlight';
1671 }
1672
1673 return classes
1674 .split(/\s+/)
1675 .find((_class) => shouldNotHighlight(_class) || getLanguage(_class));
1676 }
1677
1678 /**
1679 * Core highlighting function.
1680 *
1681 * OLD API
1682 * highlight(lang, code, ignoreIllegals, continuation)
1683 *
1684 * NEW API
1685 * highlight(code, {lang, ignoreIllegals})
1686 *
1687 * @param {string} codeOrLanguageName - the language to use for highlighting
1688 * @param {string | HighlightOptions} optionsOrCode - the code to highlight
1689 * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail
1690 *
1691 * @returns {HighlightResult} Result - an object that represents the result
1692 * @property {string} language - the language name
1693 * @property {number} relevance - the relevance score
1694 * @property {string} value - the highlighted HTML code
1695 * @property {string} code - the original raw code
1696 * @property {CompiledMode} top - top of the current mode stack
1697 * @property {boolean} illegal - indicates whether any illegal matches were found
1698 */
1699 function highlight(codeOrLanguageName, optionsOrCode, ignoreIllegals) {
1700 let code = "";
1701 let languageName = "";
1702 if (typeof optionsOrCode === "object") {
1703 code = codeOrLanguageName;
1704 ignoreIllegals = optionsOrCode.ignoreIllegals;
1705 languageName = optionsOrCode.language;
1706 } else {
1707 // old API
1708 deprecated("10.7.0", "highlight(lang, code, ...args) has been deprecated.");
1709 deprecated("10.7.0", "Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277");
1710 languageName = codeOrLanguageName;
1711 code = optionsOrCode;
1712 }
1713
1714 // https://github.com/highlightjs/highlight.js/issues/3149
1715 // eslint-disable-next-line no-undefined
1716 if (ignoreIllegals === undefined) { ignoreIllegals = true; }
1717
1718 /** @type {BeforeHighlightContext} */
1719 const context = {
1720 code,
1721 language: languageName
1722 };
1723 // the plugin can change the desired language or the code to be highlighted
1724 // just be changing the object it was passed
1725 fire("before:highlight", context);
1726
1727 // a before plugin can usurp the result completely by providing it's own
1728 // in which case we don't even need to call highlight
1729 const result = context.result
1730 ? context.result
1731 : _highlight(context.language, context.code, ignoreIllegals);
1732
1733 result.code = context.code;
1734 // the plugin can change anything in result to suite it
1735 fire("after:highlight", result);
1736
1737 return result;
1738 }
1739
1740 /**
1741 * private highlight that's used internally and does not fire callbacks
1742 *
1743 * @param {string} languageName - the language to use for highlighting
1744 * @param {string} codeToHighlight - the code to highlight
1745 * @param {boolean?} [ignoreIllegals] - whether to ignore illegal matches, default is to bail
1746 * @param {CompiledMode?} [continuation] - current continuation mode, if any
1747 * @returns {HighlightResult} - result of the highlight operation
1748 */
1749 function _highlight(languageName, codeToHighlight, ignoreIllegals, continuation) {
1750 const keywordHits = Object.create(null);
1751
1752 /**
1753 * Return keyword data if a match is a keyword
1754 * @param {CompiledMode} mode - current mode
1755 * @param {string} matchText - the textual match
1756 * @returns {KeywordData | false}
1757 */
1758 function keywordData(mode, matchText) {
1759 return mode.keywords[matchText];
1760 }
1761
1762 function processKeywords() {
1763 if (!top.keywords) {
1764 emitter.addText(modeBuffer);
1765 return;
1766 }
1767
1768 let lastIndex = 0;
1769 top.keywordPatternRe.lastIndex = 0;
1770 let match = top.keywordPatternRe.exec(modeBuffer);
1771 let buf = "";
1772
1773 while (match) {
1774 buf += modeBuffer.substring(lastIndex, match.index);
1775 const word = language.case_insensitive ? match[0].toLowerCase() : match[0];
1776 const data = keywordData(top, word);
1777 if (data) {
1778 const [kind, keywordRelevance] = data;
1779 emitter.addText(buf);
1780 buf = "";
1781
1782 keywordHits[word] = (keywordHits[word] || 0) + 1;
1783 if (keywordHits[word] <= MAX_KEYWORD_HITS) relevance += keywordRelevance;
1784 if (kind.startsWith("_")) {
1785 // _ implied for relevance only, do not highlight
1786 // by applying a class name
1787 buf += match[0];
1788 } else {
1789 const cssClass = language.classNameAliases[kind] || kind;
1790 emitter.addKeyword(match[0], cssClass);
1791 }
1792 } else {
1793 buf += match[0];
1794 }
1795 lastIndex = top.keywordPatternRe.lastIndex;
1796 match = top.keywordPatternRe.exec(modeBuffer);
1797 }
1798 buf += modeBuffer.substring(lastIndex);
1799 emitter.addText(buf);
1800 }
1801
1802 function processSubLanguage() {
1803 if (modeBuffer === "") return;
1804 /** @type HighlightResult */
1805 let result = null;
1806
1807 if (typeof top.subLanguage === 'string') {
1808 if (!languages[top.subLanguage]) {
1809 emitter.addText(modeBuffer);
1810 return;
1811 }
1812 result = _highlight(top.subLanguage, modeBuffer, true, continuations[top.subLanguage]);
1813 continuations[top.subLanguage] = /** @type {CompiledMode} */ (result._top);
1814 } else {
1815 result = highlightAuto(modeBuffer, top.subLanguage.length ? top.subLanguage : null);
1816 }
1817
1818 // Counting embedded language score towards the host language may be disabled
1819 // with zeroing the containing mode relevance. Use case in point is Markdown that
1820 // allows XML everywhere and makes every XML snippet to have a much larger Markdown
1821 // score.
1822 if (top.relevance > 0) {
1823 relevance += result.relevance;
1824 }
1825 emitter.addSublanguage(result._emitter, result.language);
1826 }
1827
1828 function processBuffer() {
1829 if (top.subLanguage != null) {
1830 processSubLanguage();
1831 } else {
1832 processKeywords();
1833 }
1834 modeBuffer = '';
1835 }
1836
1837 /**
1838 * @param {CompiledScope} scope
1839 * @param {RegExpMatchArray} match
1840 */
1841 function emitMultiClass(scope, match) {
1842 let i = 1;
1843 const max = match.length - 1;
1844 while (i <= max) {
1845 if (!scope._emit[i]) { i++; continue; }
1846 const klass = language.classNameAliases[scope[i]] || scope[i];
1847 const text = match[i];
1848 if (klass) {
1849 emitter.addKeyword(text, klass);
1850 } else {
1851 modeBuffer = text;
1852 processKeywords();
1853 modeBuffer = "";
1854 }
1855 i++;
1856 }
1857 }
1858
1859 /**
1860 * @param {CompiledMode} mode - new mode to start
1861 * @param {RegExpMatchArray} match
1862 */
1863 function startNewMode(mode, match) {
1864 if (mode.scope && typeof mode.scope === "string") {
1865 emitter.openNode(language.classNameAliases[mode.scope] || mode.scope);
1866 }
1867 if (mode.beginScope) {
1868 // beginScope just wraps the begin match itself in a scope
1869 if (mode.beginScope._wrap) {
1870 emitter.addKeyword(modeBuffer, language.classNameAliases[mode.beginScope._wrap] || mode.beginScope._wrap);
1871 modeBuffer = "";
1872 } else if (mode.beginScope._multi) {
1873 // at this point modeBuffer should just be the match
1874 emitMultiClass(mode.beginScope, match);
1875 modeBuffer = "";
1876 }
1877 }
1878
1879 top = Object.create(mode, { parent: { value: top } });
1880 return top;
1881 }
1882
1883 /**
1884 * @param {CompiledMode } mode - the mode to potentially end
1885 * @param {RegExpMatchArray} match - the latest match
1886 * @param {string} matchPlusRemainder - match plus remainder of content
1887 * @returns {CompiledMode | void} - the next mode, or if void continue on in current mode
1888 */
1889 function endOfMode(mode, match, matchPlusRemainder) {
1890 let matched = startsWith(mode.endRe, matchPlusRemainder);
1891
1892 if (matched) {
1893 if (mode["on:end"]) {
1894 const resp = new Response(mode);
1895 mode["on:end"](match, resp);
1896 if (resp.isMatchIgnored) matched = false;
1897 }
1898
1899 if (matched) {
1900 while (mode.endsParent && mode.parent) {
1901 mode = mode.parent;
1902 }
1903 return mode;
1904 }
1905 }
1906 // even if on:end fires an `ignore` it's still possible
1907 // that we might trigger the end node because of a parent mode
1908 if (mode.endsWithParent) {
1909 return endOfMode(mode.parent, match, matchPlusRemainder);
1910 }
1911 }
1912
1913 /**
1914 * Handle matching but then ignoring a sequence of text
1915 *
1916 * @param {string} lexeme - string containing full match text
1917 */
1918 function doIgnore(lexeme) {
1919 if (top.matcher.regexIndex === 0) {
1920 // no more regexes to potentially match here, so we move the cursor forward one
1921 // space
1922 modeBuffer += lexeme[0];
1923 return 1;
1924 } else {
1925 // no need to move the cursor, we still have additional regexes to try and
1926 // match at this very spot
1927 resumeScanAtSamePosition = true;
1928 return 0;
1929 }
1930 }
1931
1932 /**
1933 * Handle the start of a new potential mode match
1934 *
1935 * @param {EnhancedMatch} match - the current match
1936 * @returns {number} how far to advance the parse cursor
1937 */
1938 function doBeginMatch(match) {
1939 const lexeme = match[0];
1940 const newMode = match.rule;
1941
1942 const resp = new Response(newMode);
1943 // first internal before callbacks, then the public ones
1944 const beforeCallbacks = [newMode.__beforeBegin, newMode["on:begin"]];
1945 for (const cb of beforeCallbacks) {
1946 if (!cb) continue;
1947 cb(match, resp);
1948 if (resp.isMatchIgnored) return doIgnore(lexeme);
1949 }
1950
1951 if (newMode.skip) {
1952 modeBuffer += lexeme;
1953 } else {
1954 if (newMode.excludeBegin) {
1955 modeBuffer += lexeme;
1956 }
1957 processBuffer();
1958 if (!newMode.returnBegin && !newMode.excludeBegin) {
1959 modeBuffer = lexeme;
1960 }
1961 }
1962 startNewMode(newMode, match);
1963 return newMode.returnBegin ? 0 : lexeme.length;
1964 }
1965
1966 /**
1967 * Handle the potential end of mode
1968 *
1969 * @param {RegExpMatchArray} match - the current match
1970 */
1971 function doEndMatch(match) {
1972 const lexeme = match[0];
1973 const matchPlusRemainder = codeToHighlight.substring(match.index);
1974
1975 const endMode = endOfMode(top, match, matchPlusRemainder);
1976 if (!endMode) { return NO_MATCH; }
1977
1978 const origin = top;
1979 if (top.endScope && top.endScope._wrap) {
1980 processBuffer();
1981 emitter.addKeyword(lexeme, top.endScope._wrap);
1982 } else if (top.endScope && top.endScope._multi) {
1983 processBuffer();
1984 emitMultiClass(top.endScope, match);
1985 } else if (origin.skip) {
1986 modeBuffer += lexeme;
1987 } else {
1988 if (!(origin.returnEnd || origin.excludeEnd)) {
1989 modeBuffer += lexeme;
1990 }
1991 processBuffer();
1992 if (origin.excludeEnd) {
1993 modeBuffer = lexeme;
1994 }
1995 }
1996 do {
1997 if (top.scope) {
1998 emitter.closeNode();
1999 }
2000 if (!top.skip && !top.subLanguage) {
2001 relevance += top.relevance;
2002 }
2003 top = top.parent;
2004 } while (top !== endMode.parent);
2005 if (endMode.starts) {
2006 startNewMode(endMode.starts, match);
2007 }
2008 return origin.returnEnd ? 0 : lexeme.length;
2009 }
2010
2011 function processContinuations() {
2012 const list = [];
2013 for (let current = top; current !== language; current = current.parent) {
2014 if (current.scope) {
2015 list.unshift(current.scope);
2016 }
2017 }
2018 list.forEach(item => emitter.openNode(item));
2019 }
2020
2021 /** @type {{type?: MatchType, index?: number, rule?: Mode}}} */
2022 let lastMatch = {};
2023
2024 /**
2025 * Process an individual match
2026 *
2027 * @param {string} textBeforeMatch - text preceding the match (since the last match)
2028 * @param {EnhancedMatch} [match] - the match itself
2029 */
2030 function processLexeme(textBeforeMatch, match) {
2031 const lexeme = match && match[0];
2032
2033 // add non-matched text to the current mode buffer
2034 modeBuffer += textBeforeMatch;
2035
2036 if (lexeme == null) {
2037 processBuffer();
2038 return 0;
2039 }
2040
2041 // we've found a 0 width match and we're stuck, so we need to advance
2042 // this happens when we have badly behaved rules that have optional matchers to the degree that
2043 // sometimes they can end up matching nothing at all
2044 // Ref: https://github.com/highlightjs/highlight.js/issues/2140
2045 if (lastMatch.type === "begin" && match.type === "end" && lastMatch.index === match.index && lexeme === "") {
2046 // spit the "skipped" character that our regex choked on back into the output sequence
2047 modeBuffer += codeToHighlight.slice(match.index, match.index + 1);
2048 if (!SAFE_MODE) {
2049 /** @type {AnnotatedError} */
2050 const err = new Error(`0 width match regex (${languageName})`);
2051 err.languageName = languageName;
2052 err.badRule = lastMatch.rule;
2053 throw err;
2054 }
2055 return 1;
2056 }
2057 lastMatch = match;
2058
2059 if (match.type === "begin") {
2060 return doBeginMatch(match);
2061 } else if (match.type === "illegal" && !ignoreIllegals) {
2062 // illegal match, we do not continue processing
2063 /** @type {AnnotatedError} */
2064 const err = new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.scope || '<unnamed>') + '"');
2065 err.mode = top;
2066 throw err;
2067 } else if (match.type === "end") {
2068 const processed = doEndMatch(match);
2069 if (processed !== NO_MATCH) {
2070 return processed;
2071 }
2072 }
2073
2074 // edge case for when illegal matches $ (end of line) which is technically
2075 // a 0 width match but not a begin/end match so it's not caught by the
2076 // first handler (when ignoreIllegals is true)
2077 if (match.type === "illegal" && lexeme === "") {
2078 // advance so we aren't stuck in an infinite loop
2079 return 1;
2080 }
2081
2082 // infinite loops are BAD, this is a last ditch catch all. if we have a
2083 // decent number of iterations yet our index (cursor position in our
2084 // parsing) still 3x behind our index then something is very wrong
2085 // so we bail
2086 if (iterations > 100000 && iterations > match.index * 3) {
2087 const err = new Error('potential infinite loop, way more iterations than matches');
2088 throw err;
2089 }
2090
2091 /*
2092 Why might be find ourselves here? An potential end match that was
2093 triggered but could not be completed. IE, `doEndMatch` returned NO_MATCH.
2094 (this could be because a callback requests the match be ignored, etc)
2095
2096 This causes no real harm other than stopping a few times too many.
2097 */
2098
2099 modeBuffer += lexeme;
2100 return lexeme.length;
2101 }
2102
2103 const language = getLanguage(languageName);
2104 if (!language) {
2105 error(LANGUAGE_NOT_FOUND.replace("{}", languageName));
2106 throw new Error('Unknown language: "' + languageName + '"');
2107 }
2108
2109 const md = compileLanguage(language);
2110 let result = '';
2111 /** @type {CompiledMode} */
2112 let top = continuation || md;
2113 /** @type Record<string,CompiledMode> */
2114 const continuations = {}; // keep continuations for sub-languages
2115 const emitter = new options.__emitter(options);
2116 processContinuations();
2117 let modeBuffer = '';
2118 let relevance = 0;
2119 let index = 0;
2120 let iterations = 0;
2121 let resumeScanAtSamePosition = false;
2122
2123 try {
2124 top.matcher.considerAll();
2125
2126 for (;;) {
2127 iterations++;
2128 if (resumeScanAtSamePosition) {
2129 // only regexes not matched previously will now be
2130 // considered for a potential match
2131 resumeScanAtSamePosition = false;
2132 } else {
2133 top.matcher.considerAll();
2134 }
2135 top.matcher.lastIndex = index;
2136
2137 const match = top.matcher.exec(codeToHighlight);
2138 // console.log("match", match[0], match.rule && match.rule.begin)
2139
2140 if (!match) break;
2141
2142 const beforeMatch = codeToHighlight.substring(index, match.index);
2143 const processedCount = processLexeme(beforeMatch, match);
2144 index = match.index + processedCount;
2145 }
2146 processLexeme(codeToHighlight.substring(index));
2147 emitter.closeAllNodes();
2148 emitter.finalize();
2149 result = emitter.toHTML();
2150
2151 return {
2152 language: languageName,
2153 value: result,
2154 relevance: relevance,
2155 illegal: false,
2156 _emitter: emitter,
2157 _top: top
2158 };
2159 } catch (err) {
2160 if (err.message && err.message.includes('Illegal')) {
2161 return {
2162 language: languageName,
2163 value: escape(codeToHighlight),
2164 illegal: true,
2165 relevance: 0,
2166 _illegalBy: {
2167 message: err.message,
2168 index: index,
2169 context: codeToHighlight.slice(index - 100, index + 100),
2170 mode: err.mode,
2171 resultSoFar: result
2172 },
2173 _emitter: emitter
2174 };
2175 } else if (SAFE_MODE) {
2176 return {
2177 language: languageName,
2178 value: escape(codeToHighlight),
2179 illegal: false,
2180 relevance: 0,
2181 errorRaised: err,
2182 _emitter: emitter,
2183 _top: top
2184 };
2185 } else {
2186 throw err;
2187 }
2188 }
2189 }
2190
2191 /**
2192 * returns a valid highlight result, without actually doing any actual work,
2193 * auto highlight starts with this and it's possible for small snippets that
2194 * auto-detection may not find a better match
2195 * @param {string} code
2196 * @returns {HighlightResult}
2197 */
2198 function justTextHighlightResult(code) {
2199 const result = {
2200 value: escape(code),
2201 illegal: false,
2202 relevance: 0,
2203 _top: PLAINTEXT_LANGUAGE,
2204 _emitter: new options.__emitter(options)
2205 };
2206 result._emitter.addText(code);
2207 return result;
2208 }
2209
2210 /**
2211 Highlighting with language detection. Accepts a string with the code to
2212 highlight. Returns an object with the following properties:
2213
2214 - language (detected language)
2215 - relevance (int)
2216 - value (an HTML string with highlighting markup)
2217 - secondBest (object with the same structure for second-best heuristically
2218 detected language, may be absent)
2219
2220 @param {string} code
2221 @param {Array<string>} [languageSubset]
2222 @returns {AutoHighlightResult}
2223 */
2224 function highlightAuto(code, languageSubset) {
2225 languageSubset = languageSubset || options.languages || Object.keys(languages);
2226 const plaintext = justTextHighlightResult(code);
2227
2228 const results = languageSubset.filter(getLanguage).filter(autoDetection).map(name =>
2229 _highlight(name, code, false)
2230 );
2231 results.unshift(plaintext); // plaintext is always an option
2232
2233 const sorted = results.sort((a, b) => {
2234 // sort base on relevance
2235 if (a.relevance !== b.relevance) return b.relevance - a.relevance;
2236
2237 // always award the tie to the base language
2238 // ie if C++ and Arduino are tied, it's more likely to be C++
2239 if (a.language && b.language) {
2240 if (getLanguage(a.language).supersetOf === b.language) {
2241 return 1;
2242 } else if (getLanguage(b.language).supersetOf === a.language) {
2243 return -1;
2244 }
2245 }
2246
2247 // otherwise say they are equal, which has the effect of sorting on
2248 // relevance while preserving the original ordering - which is how ties
2249 // have historically been settled, ie the language that comes first always
2250 // wins in the case of a tie
2251 return 0;
2252 });
2253
2254 const [best, secondBest] = sorted;
2255
2256 /** @type {AutoHighlightResult} */
2257 const result = best;
2258 result.secondBest = secondBest;
2259
2260 return result;
2261 }
2262
2263 /**
2264 * Builds new class name for block given the language name
2265 *
2266 * @param {HTMLElement} element
2267 * @param {string} [currentLang]
2268 * @param {string} [resultLang]
2269 */
2270 function updateClassName(element, currentLang, resultLang) {
2271 const language = (currentLang && aliases[currentLang]) || resultLang;
2272
2273 element.classList.add("hljs");
2274 element.classList.add(`language-${language}`);
2275 }
2276
2277 /**
2278 * Applies highlighting to a DOM node containing code.
2279 *
2280 * @param {HighlightedHTMLElement} element - the HTML element to highlight
2281 */
2282 function highlightElement(element) {
2283 /** @type HTMLElement */
2284 let node = null;
2285 const language = blockLanguage(element);
2286
2287 if (shouldNotHighlight(language)) return;
2288
2289 fire("before:highlightElement",
2290 { el: element, language: language });
2291
2292 // we should be all text, no child nodes (unescaped HTML) - this is possibly
2293 // an HTML injection attack - it's likely too late if this is already in
2294 // production (the code has likely already done its damage by the time
2295 // we're seeing it)... but we yell loudly about this so that hopefully it's
2296 // more likely to be caught in development before making it to production
2297 if (element.children.length > 0) {
2298 if (!options.ignoreUnescapedHTML) {
2299 console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk.");
2300 console.warn("https://github.com/highlightjs/highlight.js/wiki/security");
2301 console.warn("The element with unescaped HTML:");
2302 console.warn(element);
2303 }
2304 if (options.throwUnescapedHTML) {
2305 const err = new HTMLInjectionError(
2306 "One of your code blocks includes unescaped HTML.",
2307 element.innerHTML
2308 );
2309 throw err;
2310 }
2311 }
2312
2313 node = element;
2314 const text = node.textContent;
2315 const result = language ? highlight(text, { language, ignoreIllegals: true }) : highlightAuto(text);
2316
2317 element.innerHTML = result.value;
2318 updateClassName(element, language, result.language);
2319 element.result = {
2320 language: result.language,
2321 // TODO: remove with version 11.0
2322 re: result.relevance,
2323 relevance: result.relevance
2324 };
2325 if (result.secondBest) {
2326 element.secondBest = {
2327 language: result.secondBest.language,
2328 relevance: result.secondBest.relevance
2329 };
2330 }
2331
2332 fire("after:highlightElement", { el: element, result, text });
2333 }
2334
2335 /**
2336 * Updates highlight.js global options with the passed options
2337 *
2338 * @param {Partial<HLJSOptions>} userOptions
2339 */
2340 function configure(userOptions) {
2341 options = inherit(options, userOptions);
2342 }
2343
2344 // TODO: remove v12, deprecated
2345 const initHighlighting = () => {
2346 highlightAll();
2347 deprecated("10.6.0", "initHighlighting() deprecated. Use highlightAll() now.");
2348 };
2349
2350 // TODO: remove v12, deprecated
2351 function initHighlightingOnLoad() {
2352 highlightAll();
2353 deprecated("10.6.0", "initHighlightingOnLoad() deprecated. Use highlightAll() now.");
2354 }
2355
2356 let wantsHighlight = false;
2357
2358 /**
2359 * auto-highlights all pre>code elements on the page
2360 */
2361 function highlightAll() {
2362 // if we are called too early in the loading process
2363 if (document.readyState === "loading") {
2364 wantsHighlight = true;
2365 return;
2366 }
2367
2368 const blocks = document.querySelectorAll(options.cssSelector);
2369 blocks.forEach(highlightElement);
2370 }
2371
2372 function boot() {
2373 // if a highlight was requested before DOM was loaded, do now
2374 if (wantsHighlight) highlightAll();
2375 }
2376
2377 // make sure we are in the browser environment
2378 if (typeof window !== 'undefined' && window.addEventListener) {
2379 window.addEventListener('DOMContentLoaded', boot, false);
2380 }
2381
2382 /**
2383 * Register a language grammar module
2384 *
2385 * @param {string} languageName
2386 * @param {LanguageFn} languageDefinition
2387 */
2388 function registerLanguage(languageName, languageDefinition) {
2389 let lang = null;
2390 try {
2391 lang = languageDefinition(hljs);
2392 } catch (error$1) {
2393 error("Language definition for '{}' could not be registered.".replace("{}", languageName));
2394 // hard or soft error
2395 if (!SAFE_MODE) { throw error$1; } else { error(error$1); }
2396 // languages that have serious errors are replaced with essentially a
2397 // "plaintext" stand-in so that the code blocks will still get normal
2398 // css classes applied to them - and one bad language won't break the
2399 // entire highlighter
2400 lang = PLAINTEXT_LANGUAGE;
2401 }
2402 // give it a temporary name if it doesn't have one in the meta-data
2403 if (!lang.name) lang.name = languageName;
2404 languages[languageName] = lang;
2405 lang.rawDefinition = languageDefinition.bind(null, hljs);
2406
2407 if (lang.aliases) {
2408 registerAliases(lang.aliases, { languageName });
2409 }
2410 }
2411
2412 /**
2413 * Remove a language grammar module
2414 *
2415 * @param {string} languageName
2416 */
2417 function unregisterLanguage(languageName) {
2418 delete languages[languageName];
2419 for (const alias of Object.keys(aliases)) {
2420 if (aliases[alias] === languageName) {
2421 delete aliases[alias];
2422 }
2423 }
2424 }
2425
2426 /**
2427 * @returns {string[]} List of language internal names
2428 */
2429 function listLanguages() {
2430 return Object.keys(languages);
2431 }
2432
2433 /**
2434 * @param {string} name - name of the language to retrieve
2435 * @returns {Language | undefined}
2436 */
2437 function getLanguage(name) {
2438 name = (name || '').toLowerCase();
2439 return languages[name] || languages[aliases[name]];
2440 }
2441
2442 /**
2443 *
2444 * @param {string|string[]} aliasList - single alias or list of aliases
2445 * @param {{languageName: string}} opts
2446 */
2447 function registerAliases(aliasList, { languageName }) {
2448 if (typeof aliasList === 'string') {
2449 aliasList = [aliasList];
2450 }
2451 aliasList.forEach(alias => { aliases[alias.toLowerCase()] = languageName; });
2452 }
2453
2454 /**
2455 * Determines if a given language has auto-detection enabled
2456 * @param {string} name - name of the language
2457 */
2458 function autoDetection(name) {
2459 const lang = getLanguage(name);
2460 return lang && !lang.disableAutodetect;
2461 }
2462
2463 /**
2464 * Upgrades the old highlightBlock plugins to the new
2465 * highlightElement API
2466 * @param {HLJSPlugin} plugin
2467 */
2468 function upgradePluginAPI(plugin) {
2469 // TODO: remove with v12
2470 if (plugin["before:highlightBlock"] && !plugin["before:highlightElement"]) {
2471 plugin["before:highlightElement"] = (data) => {
2472 plugin["before:highlightBlock"](
2473 Object.assign({ block: data.el }, data)
2474 );
2475 };
2476 }
2477 if (plugin["after:highlightBlock"] && !plugin["after:highlightElement"]) {
2478 plugin["after:highlightElement"] = (data) => {
2479 plugin["after:highlightBlock"](
2480 Object.assign({ block: data.el }, data)
2481 );
2482 };
2483 }
2484 }
2485
2486 /**
2487 * @param {HLJSPlugin} plugin
2488 */
2489 function addPlugin(plugin) {
2490 upgradePluginAPI(plugin);
2491 plugins.push(plugin);
2492 }
2493
2494 /**
2495 *
2496 * @param {PluginEvent} event
2497 * @param {any} args
2498 */
2499 function fire(event, args) {
2500 const cb = event;
2501 plugins.forEach(function(plugin) {
2502 if (plugin[cb]) {
2503 plugin[cb](args);
2504 }
2505 });
2506 }
2507
2508 /**
2509 * DEPRECATED
2510 * @param {HighlightedHTMLElement} el
2511 */
2512 function deprecateHighlightBlock(el) {
2513 deprecated("10.7.0", "highlightBlock will be removed entirely in v12.0");
2514 deprecated("10.7.0", "Please use highlightElement now.");
2515
2516 return highlightElement(el);
2517 }
2518
2519 /* Interface definition */
2520 Object.assign(hljs, {
2521 highlight,
2522 highlightAuto,
2523 highlightAll,
2524 highlightElement,
2525 // TODO: Remove with v12 API
2526 highlightBlock: deprecateHighlightBlock,
2527 configure,
2528 initHighlighting,
2529 initHighlightingOnLoad,
2530 registerLanguage,
2531 unregisterLanguage,
2532 listLanguages,
2533 getLanguage,
2534 registerAliases,
2535 autoDetection,
2536 inherit,
2537 addPlugin
2538 });
2539
2540 hljs.debugMode = function() { SAFE_MODE = false; };
2541 hljs.safeMode = function() { SAFE_MODE = true; };
2542 hljs.versionString = version;
2543
2544 hljs.regex = {
2545 concat: concat,
2546 lookahead: lookahead,
2547 either: either,
2548 optional: optional,
2549 anyNumberOfTimes: anyNumberOfTimes
2550 };
2551
2552 for (const key in MODES) {
2553 // @ts-ignore
2554 if (typeof MODES[key] === "object") {
2555 // @ts-ignore
2556 deepFreezeEs6.exports(MODES[key]);
2557 }
2558 }
2559
2560 // merge all the modes/regexes into our main object
2561 Object.assign(hljs, MODES);
2562
2563 return hljs;
2564};
2565
2566// export an "instance" of the highlighter
2567var highlight = HLJS({});
2568
2569export { highlight as default };