l0bsterssg

node.js static responsive blog post generator
Log | Files | Refs | README

core.js (64062B)


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