source: node_modules/highlight.js/lib/core.js@ e48199a

main
Last change on this file since e48199a was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

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