[epiphany/mcatanzaro/highlightjs-10.5.0] Update to highlight.js 10.5.0




commit 703b45b4535faada00319d69b0305c7ab46efc4f
Author: Michael Catanzaro <mcatanzaro gnome org>
Date:   Wed Jan 6 12:53:43 2021 -0600

    Update to highlight.js 10.5.0

 third-party/highlightjs/highlight.js | 807 ++++++++++++++++++++---------------
 1 file changed, 465 insertions(+), 342 deletions(-)
---
diff --git a/third-party/highlightjs/highlight.js b/third-party/highlightjs/highlight.js
index bb7a3c759..de28408ef 100644
--- a/third-party/highlightjs/highlight.js
+++ b/third-party/highlightjs/highlight.js
@@ -1,5 +1,5 @@
 /*
-  Highlight.js 10.4.0 (4055826e)
+  Highlight.js 10.5.0 (af20048d)
   License: BSD-3-Clause
   Copyright (c) 2006-2020, Ivan Sagalaev
 */
@@ -86,155 +86,6 @@ var hljs = (function () {
       return /** @type {T} */ (result);
     }
 
-    /* Stream merging */
-
-    /**
-     * @typedef Event
-     * @property {'start'|'stop'} event
-     * @property {number} offset
-     * @property {Node} node
-     */
-
-    /**
-     * @param {Node} node
-     */
-    function tag(node) {
-      return node.nodeName.toLowerCase();
-    }
-
-    /**
-     * @param {Node} node
-     */
-    function nodeStream(node) {
-      /** @type Event[] */
-      const result = [];
-      (function _nodeStream(node, offset) {
-        for (let child = node.firstChild; child; child = child.nextSibling) {
-          if (child.nodeType === 3) {
-            offset += child.nodeValue.length;
-          } else if (child.nodeType === 1) {
-            result.push({
-              event: 'start',
-              offset: offset,
-              node: child
-            });
-            offset = _nodeStream(child, offset);
-            // Prevent void elements from having an end tag that would actually
-            // double them in the output. There are more void elements in HTML
-            // but we list only those realistically expected in code display.
-            if (!tag(child).match(/br|hr|img|input/)) {
-              result.push({
-                event: 'stop',
-                offset: offset,
-                node: child
-              });
-            }
-          }
-        }
-        return offset;
-      })(node, 0);
-      return result;
-    }
-
-    /**
-     * @param {any} original - the original stream
-     * @param {any} highlighted - stream of the highlighted source
-     * @param {string} value - the original source itself
-     */
-    function mergeStreams(original, highlighted, value) {
-      let processed = 0;
-      let result = '';
-      const nodeStack = [];
-
-      function selectStream() {
-        if (!original.length || !highlighted.length) {
-          return original.length ? original : highlighted;
-        }
-        if (original[0].offset !== highlighted[0].offset) {
-          return (original[0].offset < highlighted[0].offset) ? original : highlighted;
-        }
-
-        /*
-        To avoid starting the stream just before it should stop the order is
-        ensured that original always starts first and closes last:
-
-        if (event1 == 'start' && event2 == 'start')
-          return original;
-        if (event1 == 'start' && event2 == 'stop')
-          return highlighted;
-        if (event1 == 'stop' && event2 == 'start')
-          return original;
-        if (event1 == 'stop' && event2 == 'stop')
-          return highlighted;
-
-        ... which is collapsed to:
-        */
-        return highlighted[0].event === 'start' ? original : highlighted;
-      }
-
-      /**
-       * @param {Node} node
-       */
-      function open(node) {
-        /** @param {Attr} attr */
-        function attributeString(attr) {
-          return ' ' + attr.nodeName + '="' + escapeHTML(attr.value) + '"';
-        }
-        // @ts-ignore
-        result += '<' + tag(node) + [].map.call(node.attributes, attributeString).join('') + '>';
-      }
-
-      /**
-       * @param {Node} node
-       */
-      function close(node) {
-        result += '</' + tag(node) + '>';
-      }
-
-      /**
-       * @param {Event} event
-       */
-      function render(event) {
-        (event.event === 'start' ? open : close)(event.node);
-      }
-
-      while (original.length || highlighted.length) {
-        let stream = selectStream();
-        result += escapeHTML(value.substring(processed, stream[0].offset));
-        processed = stream[0].offset;
-        if (stream === original) {
-          /*
-          On any opening or closing tag of the original markup we first close
-          the entire highlighted node stack, then render the original tag along
-          with all the following original tags at the same offset and then
-          reopen all the tags on the highlighted stack.
-          */
-          nodeStack.reverse().forEach(close);
-          do {
-            render(stream.splice(0, 1)[0]);
-            stream = selectStream();
-          } while (stream === original && stream.length && stream[0].offset === processed);
-          nodeStack.reverse().forEach(open);
-        } else {
-          if (stream[0].event === 'start') {
-            nodeStack.push(stream[0].node);
-          } else {
-            nodeStack.pop();
-          }
-          render(stream.splice(0, 1)[0]);
-        }
-      }
-      return result + escapeHTML(value.substr(processed));
-    }
-
-    var utils = /*#__PURE__*/Object.freeze({
-        __proto__: null,
-        escapeHTML: escapeHTML,
-        inherit: inherit,
-        nodeStream: nodeStream,
-        mergeStreams: mergeStreams
-    });
-
     /**
      * @typedef {object} Renderer
      * @property {(text: string) => void} addText
@@ -512,6 +363,18 @@ var hljs = (function () {
       return joined;
     }
 
+    /**
+     * Any of the passed expresssions may match
+     *
+     * Creates a huge this | this | that | that match
+     * @param {(RegExp | string)[] } args
+     * @returns {string}
+     */
+    function either(...args) {
+      const joined = '(' + args.map((x) => source(x)).join("|") + ")";
+      return joined;
+    }
+
     /**
      * @param {RegExp} re
      * @returns {number}
@@ -777,6 +640,88 @@ var hljs = (function () {
         END_SAME_AS_BEGIN: END_SAME_AS_BEGIN
     });
 
+    // Grammar extensions / plugins
+    // See: https://github.com/highlightjs/highlight.js/issues/2833
+
+    // Grammar extensions allow "syntactic sugar" to be added to the grammar modes
+    // without requiring any underlying changes to the compiler internals.
+
+    // `compileMatch` being the perfect small example of now allowing a grammar
+    // author to write `match` when they desire to match a single expression rather
+    // than being forced to use `begin`.  The extension then just moves `match` into
+    // `begin` when it runs.  Ie, no features have been added, but we've just made
+    // the experience of writing (and reading grammars) a little bit nicer.
+
+    // ------
+
+    // TODO: We need negative look-behind support to do this properly
+    /**
+     * Skip a match if it has a preceding dot
+     *
+     * This is used for `beginKeywords` to prevent matching expressions such as
+     * `bob.keyword.do()`. The mode compiler automatically wires this up as a
+     * special _internal_ 'on:begin' callback for modes with `beginKeywords`
+     * @param {RegExpMatchArray} match
+     * @param {CallbackResponse} response
+     */
+    function skipIfhasPrecedingDot(match, response) {
+      const before = match.input[match.index - 1];
+      if (before === ".") {
+        response.ignoreMatch();
+      }
+    }
+
+
+    /**
+     * `beginKeywords` syntactic sugar
+     * @type {CompilerExt}
+     */
+    function beginKeywords(mode, parent) {
+      if (!parent) return;
+      if (!mode.beginKeywords) return;
+
+      // for languages with keywords that include non-word characters checking for
+      // a word boundary is not sufficient, so instead we check for a word boundary
+      // or whitespace - this does no harm in any case since our keyword engine
+      // doesn't allow spaces in keywords anyways and we still check for the boundary
+      // first
+      mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')(?!\\.)(?=\\b|\\s)';
+      mode.__beforeBegin = skipIfhasPrecedingDot;
+      mode.keywords = mode.keywords || mode.beginKeywords;
+      delete mode.beginKeywords;
+    }
+
+    /**
+     * Allow `illegal` to contain an array of illegal values
+     * @type {CompilerExt}
+     */
+    function compileIllegal(mode, _parent) {
+      if (!Array.isArray(mode.illegal)) return;
+
+      mode.illegal = either(...mode.illegal);
+    }
+
+    /**
+     * `match` to match a single expression for readability
+     * @type {CompilerExt}
+     */
+    function compileMatch(mode, _parent) {
+      if (!mode.match) return;
+      if (mode.begin || mode.end) throw new Error("begin & end are not supported with match");
+
+      mode.begin = mode.match;
+      delete mode.match;
+    }
+
+    /**
+     * provides the default 1 relevance to all modes
+     * @type {CompilerExt}
+     */
+    function compileRelevance(mode, _parent) {
+      // eslint-disable-next-line no-undefined
+      if (mode.relevance === undefined) mode.relevance = 1;
+    }
+
     // keywords that should have no default relevance value
     const COMMON_KEYWORDS = [
       'of',
@@ -792,6 +737,72 @@ var hljs = (function () {
       'value' // common variable name
     ];
 
+    /**
+     * Given raw keywords from a language definition, compile them.
+     *
+     * @param {string | Record<string,string>} rawKeywords
+     * @param {boolean} caseInsensitive
+     */
+    function compileKeywords(rawKeywords, caseInsensitive) {
+      /** @type KeywordDict */
+      const compiledKeywords = {};
+
+      if (typeof rawKeywords === 'string') { // string
+        splitAndCompile('keyword', rawKeywords);
+      } else {
+        Object.keys(rawKeywords).forEach(function(className) {
+          splitAndCompile(className, rawKeywords[className]);
+        });
+      }
+      return compiledKeywords;
+
+      // ---
+
+      /**
+       * Compiles an individual list of keywords
+       *
+       * Ex: "for if when while|5"
+       *
+       * @param {string} className
+       * @param {string} keywordList
+       */
+      function splitAndCompile(className, keywordList) {
+        if (caseInsensitive) {
+          keywordList = keywordList.toLowerCase();
+        }
+        keywordList.split(' ').forEach(function(keyword) {
+          const pair = keyword.split('|');
+          compiledKeywords[pair[0]] = [className, scoreForKeyword(pair[0], pair[1])];
+        });
+      }
+    }
+
+    /**
+     * Returns the proper score for a given keyword
+     *
+     * Also takes into account comment keywords, which will be scored 0 UNLESS
+     * another score has been manually assigned.
+     * @param {string} keyword
+     * @param {string} [providedScore]
+     */
+    function scoreForKeyword(keyword, providedScore) {
+      // manual scores always win over common keywords
+      // so you can force a score of 1 if you really insist
+      if (providedScore) {
+        return Number(providedScore);
+      }
+
+      return commonKeyword(keyword) ? 0 : 1;
+    }
+
+    /**
+     * Determines if a given keyword is common or not
+     *
+     * @param {string} keyword */
+    function commonKeyword(keyword) {
+      return COMMON_KEYWORDS.includes(keyword.toLowerCase());
+    }
+
     // compilation
 
     /**
@@ -800,9 +811,10 @@ var hljs = (function () {
      * Given the raw result of a language definition (Language), compiles this so
      * that it is ready for highlighting code.
      * @param {Language} language
+     * @param {{plugins: HLJSPlugin[]}} opts
      * @returns {CompiledLanguage}
      */
-    function compileLanguage(language) {
+    function compileLanguage(language, { plugins }) {
       /**
        * Builds a regex with the case sensativility of the current language
        *
@@ -1013,8 +1025,8 @@ var hljs = (function () {
 
         mode.contains.forEach(term => mm.addRule(term.begin, { rule: term, type: "begin" }));
 
-        if (mode.terminator_end) {
-          mm.addRule(mode.terminator_end, { type: "end" });
+        if (mode.terminatorEnd) {
+          mm.addRule(mode.terminatorEnd, { type: "end" });
         }
         if (mode.illegal) {
           mm.addRule(mode.illegal, { type: "illegal" });
@@ -1023,23 +1035,6 @@ var hljs = (function () {
         return mm;
       }
 
-      // TODO: We need negative look-behind support to do this properly
-      /**
-       * Skip a match if it has a preceding dot
-       *
-       * This is used for `beginKeywords` to prevent matching expressions such as
-       * `bob.keyword.do()`. The mode compiler automatically wires this up as a
-       * special _internal_ 'on:begin' callback for modes with `beginKeywords`
-       * @param {RegExpMatchArray} match
-       * @param {CallbackResponse} response
-       */
-      function skipIfhasPrecedingDot(match, response) {
-        const before = match.input[match.index - 1];
-        if (before === ".") {
-          response.ignoreMatch();
-        }
-      }
-
       /** skip vs abort vs ignore
        *
        * @skip   - The mode is still entered and exited normally (and contains rules apply),
@@ -1082,12 +1077,28 @@ var hljs = (function () {
       function compileMode(mode, parent) {
         const cmode = /** @type CompiledMode */ (mode);
         if (mode.compiled) return cmode;
-        mode.compiled = true;
+
+        [
+          // do this early so compiler extensions generally don't have to worry about
+          // the distinction between match/begin
+          compileMatch
+        ].forEach(ext => ext(mode, parent));
+
+        language.compilerExtensions.forEach(ext => ext(mode, parent));
 
         // __beforeBegin is considered private API, internal use only
         mode.__beforeBegin = null;
 
-        mode.keywords = mode.keywords || mode.beginKeywords;
+        [
+          beginKeywords,
+          // do this later so compiler extensions that come earlier have access to the
+          // raw array if they wanted to perhaps manipulate it, etc.
+          compileIllegal,
+          // default to 1 relevance if not specified
+          compileRelevance
+        ].forEach(ext => ext(mode, parent));
+
+        mode.compiled = true;
 
         let keywordPattern = null;
         if (typeof mode.keywords === "object") {
@@ -1106,31 +1117,21 @@ var hljs = (function () {
 
         // `mode.lexemes` was the old standard before we added and now recommend
         // using `keywords.$pattern` to pass the keyword pattern
-        cmode.keywordPatternRe = langRe(mode.lexemes || keywordPattern || /\w+/, true);
+        keywordPattern = keywordPattern || mode.lexemes || /\w+/;
+        cmode.keywordPatternRe = langRe(keywordPattern, true);
 
         if (parent) {
-          if (mode.beginKeywords) {
-            // for languages with keywords that include non-word characters checking for
-            // a word boundary is not sufficient, so instead we check for a word boundary
-            // or whitespace - this does no harm in any case since our keyword engine
-            // doesn't allow spaces in keywords anyways and we still check for the boundary
-            // first
-            mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')(?!\\.)(?=\\b|\\s)';
-            mode.__beforeBegin = skipIfhasPrecedingDot;
-          }
           if (!mode.begin) mode.begin = /\B|\b/;
           cmode.beginRe = langRe(mode.begin);
           if (mode.endSameAsBegin) mode.end = mode.begin;
           if (!mode.end && !mode.endsWithParent) mode.end = /\B|\b/;
           if (mode.end) cmode.endRe = langRe(mode.end);
-          cmode.terminator_end = source(mode.end) || '';
-          if (mode.endsWithParent && parent.terminator_end) {
-            cmode.terminator_end += (mode.end ? '|' : '') + parent.terminator_end;
+          cmode.terminatorEnd = source(mode.end) || '';
+          if (mode.endsWithParent && parent.terminatorEnd) {
+            cmode.terminatorEnd += (mode.end ? '|' : '') + parent.terminatorEnd;
           }
         }
-        if (mode.illegal) cmode.illegalRe = langRe(mode.illegal);
-        // eslint-disable-next-line no-undefined
-        if (mode.relevance === undefined) mode.relevance = 1;
+        if (mode.illegal) cmode.illegalRe = langRe(/** @type {RegExp | string} */ (mode.illegal));
         if (!mode.contains) mode.contains = [];
 
         mode.contains = [].concat(...mode.contains.map(function(c) {
@@ -1146,6 +1147,8 @@ var hljs = (function () {
         return cmode;
       }
 
+      if (!language.compilerExtensions) language.compilerExtensions = [];
+
       // self is not valid at the top-level
       if (language.contains && language.contains.includes('self')) {
         throw new Error("ERR: contains `self` is not supported at the top-level of a language.  See 
documentation.");
@@ -1185,8 +1188,8 @@ var hljs = (function () {
      * @returns {Mode | Mode[]}
      * */
     function expandOrCloneMode(mode) {
-      if (mode.variants && !mode.cached_variants) {
-        mode.cached_variants = mode.variants.map(function(variant) {
+      if (mode.variants && !mode.cachedVariants) {
+        mode.cachedVariants = mode.variants.map(function(variant) {
           return inherit(mode, { variants: null }, variant);
         });
       }
@@ -1194,8 +1197,8 @@ var hljs = (function () {
       // EXPAND
       // if we have variants then essentially "replace" the mode with the variants
       // this happens in compileMode, where this function is called from
-      if (mode.cached_variants) {
-        return mode.cached_variants;
+      if (mode.cachedVariants) {
+        return mode.cachedVariants;
       }
 
       // CLONE
@@ -1214,77 +1217,7 @@ var hljs = (function () {
       return mode;
     }
 
-    /***********************************************
-      Keywords
-    ***********************************************/
-
-    /**
-     * Given raw keywords from a language definition, compile them.
-     *
-     * @param {string | Record<string,string>} rawKeywords
-     * @param {boolean} caseInsensitive
-     */
-    function compileKeywords(rawKeywords, caseInsensitive) {
-      /** @type KeywordDict */
-      const compiledKeywords = {};
-
-      if (typeof rawKeywords === 'string') { // string
-        splitAndCompile('keyword', rawKeywords);
-      } else {
-        Object.keys(rawKeywords).forEach(function(className) {
-          splitAndCompile(className, rawKeywords[className]);
-        });
-      }
-      return compiledKeywords;
-
-      // ---
-
-      /**
-       * Compiles an individual list of keywords
-       *
-       * Ex: "for if when while|5"
-       *
-       * @param {string} className
-       * @param {string} keywordList
-       */
-      function splitAndCompile(className, keywordList) {
-        if (caseInsensitive) {
-          keywordList = keywordList.toLowerCase();
-        }
-        keywordList.split(' ').forEach(function(keyword) {
-          const pair = keyword.split('|');
-          compiledKeywords[pair[0]] = [className, scoreForKeyword(pair[0], pair[1])];
-        });
-      }
-    }
-
-    /**
-     * Returns the proper score for a given keyword
-     *
-     * Also takes into account comment keywords, which will be scored 0 UNLESS
-     * another score has been manually assigned.
-     * @param {string} keyword
-     * @param {string} [providedScore]
-     */
-    function scoreForKeyword(keyword, providedScore) {
-      // manual scores always win over common keywords
-      // so you can force a score of 1 if you really insist
-      if (providedScore) {
-        return Number(providedScore);
-      }
-
-      return commonKeyword(keyword) ? 0 : 1;
-    }
-
-    /**
-     * Determines if a given keyword is common or not
-     *
-     * @param {string} keyword */
-    function commonKeyword(keyword) {
-      return COMMON_KEYWORDS.includes(keyword.toLowerCase());
-    }
-
-    var version = "10.4.0";
+    var version = "10.5.0";
 
     // @ts-nocheck
 
@@ -1304,7 +1237,7 @@ var hljs = (function () {
         computed: {
           className() {
             if (this.unknownLanguage) return "";
-      
+
             return "hljs " + this.detectedLanguage;
           },
           highlighted() {
@@ -1314,8 +1247,8 @@ var hljs = (function () {
               this.unknownLanguage = true;
               return escapeHTML(this.code);
             }
-      
-            let result;
+
+            let result = {};
             if (this.autoDetect) {
               result = hljs.highlightAuto(this.code);
               this.detectedLanguage = result.language;
@@ -1338,12 +1271,13 @@ var hljs = (function () {
           return createElement("pre", {}, [
             createElement("code", {
               class: this.className,
-              domProps: { innerHTML: this.highlighted }})
+              domProps: { innerHTML: this.highlighted }
+            })
           ]);
         }
         // template: `<pre><code :class="className" v-html="highlighted"></code></pre>`
       };
-      
+
       const VuePlugin = {
         install(Vue) {
           Vue.component('highlightjs', Component);
@@ -1353,6 +1287,191 @@ var hljs = (function () {
       return { Component, VuePlugin };
     }
 
+    /* plugin itself */
+
+    /** @type {HLJSPlugin} */
+    const mergeHTMLPlugin = {
+      "after:highlightBlock": ({ block, result, text }) => {
+        const originalStream = nodeStream(block);
+        if (!originalStream.length) return;
+
+        const resultNode = document.createElement('div');
+        resultNode.innerHTML = result.value;
+        result.value = mergeStreams(originalStream, nodeStream(resultNode), text);
+      }
+    };
+
+    /* Stream merging support functions */
+
+    /**
+     * @typedef Event
+     * @property {'start'|'stop'} event
+     * @property {number} offset
+     * @property {Node} node
+     */
+
+    /**
+     * @param {Node} node
+     */
+    function tag(node) {
+      return node.nodeName.toLowerCase();
+    }
+
+    /**
+     * @param {Node} node
+     */
+    function nodeStream(node) {
+      /** @type Event[] */
+      const result = [];
+      (function _nodeStream(node, offset) {
+        for (let child = node.firstChild; child; child = child.nextSibling) {
+          if (child.nodeType === 3) {
+            offset += child.nodeValue.length;
+          } else if (child.nodeType === 1) {
+            result.push({
+              event: 'start',
+              offset: offset,
+              node: child
+            });
+            offset = _nodeStream(child, offset);
+            // Prevent void elements from having an end tag that would actually
+            // double them in the output. There are more void elements in HTML
+            // but we list only those realistically expected in code display.
+            if (!tag(child).match(/br|hr|img|input/)) {
+              result.push({
+                event: 'stop',
+                offset: offset,
+                node: child
+              });
+            }
+          }
+        }
+        return offset;
+      })(node, 0);
+      return result;
+    }
+
+    /**
+     * @param {any} original - the original stream
+     * @param {any} highlighted - stream of the highlighted source
+     * @param {string} value - the original source itself
+     */
+    function mergeStreams(original, highlighted, value) {
+      let processed = 0;
+      let result = '';
+      const nodeStack = [];
+
+      function selectStream() {
+        if (!original.length || !highlighted.length) {
+          return original.length ? original : highlighted;
+        }
+        if (original[0].offset !== highlighted[0].offset) {
+          return (original[0].offset < highlighted[0].offset) ? original : highlighted;
+        }
+
+        /*
+        To avoid starting the stream just before it should stop the order is
+        ensured that original always starts first and closes last:
+
+        if (event1 == 'start' && event2 == 'start')
+          return original;
+        if (event1 == 'start' && event2 == 'stop')
+          return highlighted;
+        if (event1 == 'stop' && event2 == 'start')
+          return original;
+        if (event1 == 'stop' && event2 == 'stop')
+          return highlighted;
+
+        ... which is collapsed to:
+        */
+        return highlighted[0].event === 'start' ? original : highlighted;
+      }
+
+      /**
+       * @param {Node} node
+       */
+      function open(node) {
+        /** @param {Attr} attr */
+        function attributeString(attr) {
+          return ' ' + attr.nodeName + '="' + escapeHTML(attr.value) + '"';
+        }
+        // @ts-ignore
+        result += '<' + tag(node) + [].map.call(node.attributes, attributeString).join('') + '>';
+      }
+
+      /**
+       * @param {Node} node
+       */
+      function close(node) {
+        result += '</' + tag(node) + '>';
+      }
+
+      /**
+       * @param {Event} event
+       */
+      function render(event) {
+        (event.event === 'start' ? open : close)(event.node);
+      }
+
+      while (original.length || highlighted.length) {
+        let stream = selectStream();
+        result += escapeHTML(value.substring(processed, stream[0].offset));
+        processed = stream[0].offset;
+        if (stream === original) {
+          /*
+          On any opening or closing tag of the original markup we first close
+          the entire highlighted node stack, then render the original tag along
+          with all the following original tags at the same offset and then
+          reopen all the tags on the highlighted stack.
+          */
+          nodeStack.reverse().forEach(close);
+          do {
+            render(stream.splice(0, 1)[0]);
+            stream = selectStream();
+          } while (stream === original && stream.length && stream[0].offset === processed);
+          nodeStack.reverse().forEach(open);
+        } else {
+          if (stream[0].event === 'start') {
+            nodeStack.push(stream[0].node);
+          } else {
+            nodeStack.pop();
+          }
+          render(stream.splice(0, 1)[0]);
+        }
+      }
+      return result + escapeHTML(value.substr(processed));
+    }
+
+    /*
+
+    For the reasoning behind this please see:
+    https://github.com/highlightjs/highlight.js/issues/2880#issuecomment-747275419
+
+    */
+
+    /**
+     * @param {string} message
+     */
+    const error = (message) => {
+      console.error(message);
+    };
+
+    /**
+     * @param {string} message
+     * @param {any} args
+     */
+    const warn = (message, ...args) => {
+      console.log(`WARN: ${message}`, ...args);
+    };
+
+    /**
+     * @param {string} version
+     * @param {string} message
+     */
+    const deprecated = (version, message) => {
+      console.log(`Deprecated as of ${version}. ${message}`);
+    };
+
     /*
     Syntax highlighting with language autodetection.
     https://highlightjs.org/
@@ -1360,8 +1479,6 @@ var hljs = (function () {
 
     const escape$1 = escapeHTML;
     const inherit$1 = inherit;
-
-    const { nodeStream: nodeStream$1, mergeStreams: mergeStreams$1 } = utils;
     const NO_MATCH = Symbol("nomatch");
 
     /**
@@ -1369,10 +1486,6 @@ var hljs = (function () {
      * @returns {HLJSApi}
      */
     const HLJS = function(hljs) {
-      // Convenience variables for build-in objects
-      /** @type {unknown[]} */
-      const ArrayProto = [];
-
       // Global internal variables used within the highlight.js library.
       /** @type {Record<string, Language>} */
       const languages = Object.create(null);
@@ -1427,8 +1540,8 @@ var hljs = (function () {
         if (match) {
           const language = getLanguage(match[1]);
           if (!language) {
-            console.warn(LANGUAGE_NOT_FOUND.replace("{}", match[1]));
-            console.warn("Falling back to no-highlight mode for this block.", block);
+            warn(LANGUAGE_NOT_FOUND.replace("{}", match[1]));
+            warn("Falling back to no-highlight mode for this block.", block);
           }
           return language ? match[1] : 'no-highlight';
         }
@@ -1455,7 +1568,7 @@ var hljs = (function () {
        * @property {boolean} illegal - indicates whether any illegal matches were found
       */
       function highlight(languageName, code, ignoreIllegals, continuation) {
-        /** @type {{ code: string, language: string, result?: any }} */
+        /** @type {BeforeHighlightContext} */
         const context = {
           code,
           language: languageName
@@ -1809,11 +1922,11 @@ var hljs = (function () {
 
         const language = getLanguage(languageName);
         if (!language) {
-          console.error(LANGUAGE_NOT_FOUND.replace("{}", languageName));
+          error(LANGUAGE_NOT_FOUND.replace("{}", languageName));
           throw new Error('Unknown language: "' + languageName + '"');
         }
 
-        const md = compileLanguage(language);
+        const md = compileLanguage(language, { plugins });
         let result = '';
         /** @type {CompiledMode} */
         let top = continuation || md;
@@ -1992,24 +2105,42 @@ var hljs = (function () {
       /**
        * Builds new class name for block given the language name
        *
-       * @param {string} prevClassName
+       * @param {HTMLElement} element
        * @param {string} [currentLang]
        * @param {string} [resultLang]
        */
-      function buildClassName(prevClassName, currentLang, resultLang) {
+      function updateClassName(element, currentLang, resultLang) {
         const language = currentLang ? aliases[currentLang] : resultLang;
-        const result = [prevClassName.trim()];
 
-        if (!prevClassName.match(/\bhljs\b/)) {
-          result.push('hljs');
-        }
+        element.classList.add("hljs");
+        if (language) element.classList.add(language);
+      }
 
-        if (!prevClassName.includes(language)) {
-          result.push(language);
+      /** @type {HLJSPlugin} */
+      const brPlugin = {
+        "before:highlightBlock": ({ block }) => {
+          if (options.useBR) {
+            block.innerHTML = block.innerHTML.replace(/\n/g, '').replace(/<br[ /]*>/g, '\n');
+          }
+        },
+        "after:highlightBlock": ({ result }) => {
+          if (options.useBR) {
+            result.value = result.value.replace(/\n/g, "<br>");
+          }
         }
+      };
 
-        return result.join(' ').trim();
-      }
+      const TAB_REPLACE_RE = /^(<[^>]+>|\t)+/gm;
+      /** @type {HLJSPlugin} */
+      const tabReplacePlugin = {
+        "after:highlightBlock": ({ result }) => {
+          if (options.tabReplace) {
+            result.value = result.value.replace(TAB_REPLACE_RE, (m) =>
+              m.replace(/\t/g, options.tabReplace)
+            );
+          }
+        }
+      };
 
       /**
        * Applies highlighting to a DOM node containing code. Accepts a DOM node and
@@ -2027,27 +2158,14 @@ var hljs = (function () {
         fire("before:highlightBlock",
           { block: element, language: language });
 
-        if (options.useBR) {
-          node = document.createElement('div');
-          node.innerHTML = element.innerHTML.replace(/\n/g, '').replace(/<br[ /]*>/g, '\n');
-        } else {
-          node = element;
-        }
+        node = element;
         const text = node.textContent;
         const result = language ? highlight(language, text, true) : highlightAuto(text);
 
-        const originalStream = nodeStream$1(node);
-        if (originalStream.length) {
-          const resultNode = document.createElement('div');
-          resultNode.innerHTML = result.value;
-          result.value = mergeStreams$1(originalStream, nodeStream$1(resultNode), text);
-        }
-        result.value = fixMarkup(result.value);
-
-        fire("after:highlightBlock", { block: element, result: result });
+        fire("after:highlightBlock", { block: element, result, text });
 
         element.innerHTML = result.value;
-        element.className = buildClassName(element.className, language, result.language);
+        updateClassName(element, language, result.language);
         element.result = {
           language: result.language,
           // TODO: remove with version 11.0
@@ -2071,8 +2189,8 @@ var hljs = (function () {
        */
       function configure(userOptions) {
         if (userOptions.useBR) {
-          console.warn("'useBR' option is deprecated and will be removed entirely in v11.0");
-          console.warn("Please see https://github.com/highlightjs/highlight.js/issues/2559";);
+          deprecated("10.3.0", "'useBR' will be removed entirely in v11.0");
+          deprecated("10.3.0", "Please see https://github.com/highlightjs/highlight.js/issues/2559";);
         }
         options = inherit$1(options, userOptions);
       }
@@ -2087,7 +2205,7 @@ var hljs = (function () {
         initHighlighting.called = true;
 
         const blocks = document.querySelectorAll('pre code');
-        ArrayProto.forEach.call(blocks, highlightBlock);
+        blocks.forEach(highlightBlock);
       };
 
       // Higlights all when DOMContentLoaded fires
@@ -2106,10 +2224,10 @@ var hljs = (function () {
         let lang = null;
         try {
           lang = languageDefinition(hljs);
-        } catch (error) {
-          console.error("Language definition for '{}' could not be registered.".replace("{}", languageName));
+        } catch (error$1) {
+          error("Language definition for '{}' could not be registered.".replace("{}", languageName));
           // hard or soft error
-          if (!SAFE_MODE) { throw error; } else { console.error(error); }
+          if (!SAFE_MODE) { throw error$1; } else { error(error$1); }
           // languages that have serious errors are replaced with essentially a
           // "plaintext" stand-in so that the code blocks will still get normal
           // css classes applied to them - and one bad language won't break the
@@ -2143,8 +2261,8 @@ var hljs = (function () {
         @returns {Language | never}
       */
       function requireLanguage(name) {
-        console.warn("requireLanguage is deprecated and will be removed entirely in the future.");
-        console.warn("Please see https://github.com/highlightjs/highlight.js/pull/2844";);
+        deprecated("10.4.0", "requireLanguage will be removed entirely in v11.");
+        deprecated("10.4.0", "Please see https://github.com/highlightjs/highlight.js/pull/2844";);
 
         const lang = getLanguage(name);
         if (lang) { return lang; }
@@ -2211,8 +2329,8 @@ var hljs = (function () {
       @returns {string}
       */
       function deprecateFixMarkup(arg) {
-        console.warn("fixMarkup is deprecated and will be removed entirely in v11.0");
-        console.warn("Please see https://github.com/highlightjs/highlight.js/issues/2534";);
+        deprecated("10.2.0", "fixMarkup will be removed entirely in v11.0");
+        deprecated("10.2.0", "Please see https://github.com/highlightjs/highlight.js/issues/2534";);
 
         return fixMarkup(arg);
       }
@@ -2253,6 +2371,10 @@ var hljs = (function () {
       // merge all the modes/regexs into our main object
       Object.assign(hljs, MODES);
 
+      // built-in plugins, likely to be moved out of core in the future
+      hljs.addPlugin(brPlugin); // slated to be removed in v11
+      hljs.addPlugin(mergeHTMLPlugin);
+      hljs.addPlugin(tabReplacePlugin);
       return hljs;
     };
 
@@ -2715,7 +2837,7 @@ hljs.registerLanguage('javascript', function () {
       ]
     };
     const JSDOC_COMMENT = hljs.COMMENT(
-      '/\\*\\*',
+      /\/\*\*(?!\/)/,
       '\\*/',
       {
         relevance: 0,
@@ -2862,8 +2984,8 @@ hljs.registerLanguage('javascript', function () {
               '[^()]*(\\(' +
               '[^()]*(\\(' +
               '[^()]*' +
-              '\\))*[^()]*' +
-              '\\))*[^()]*' +
+              '\\)[^()]*)*' +
+              '\\)[^()]*)*' +
               '\\)|' + hljs.UNDERSCORE_IDENT_RE + ')\\s*=>',
               returnBegin: true,
               end: '\\s*=>',
@@ -2953,8 +3075,8 @@ hljs.registerLanguage('javascript', function () {
             '[^()]*(\\(' +
               '[^()]*(\\(' +
                 '[^()]*' +
-              '\\))*[^()]*' +
-            '\\))*[^()]*' +
+              '\\)[^()]*)*' +
+            '\\)[^()]*)*' +
             '\\)\\s*\\{', // end parens
           returnBegin:true,
           contains: [
@@ -3076,30 +3198,31 @@ hljs.registerLanguage('xml', function () {
   Language: HTML, XML
   Website: https://www.w3.org/XML/
   Category: common
+  Audit: 2020
   */
 
   /** @type LanguageFn */
   function xml(hljs) {
     // Element names can contain letters, digits, hyphens, underscores, and periods
     const TAG_NAME_RE = concat(/[A-Z_]/, optional(/[A-Z0-9_.-]+:/), /[A-Z0-9_.-]*/);
-    const XML_IDENT_RE = '[A-Za-z0-9\\._:-]+';
+    const XML_IDENT_RE = /[A-Za-z0-9._:-]+/;
     const XML_ENTITIES = {
       className: 'symbol',
-      begin: '&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;'
+      begin: /&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/
     };
     const XML_META_KEYWORDS = {
-      begin: '\\s',
+      begin: /\s/,
       contains: [
         {
           className: 'meta-keyword',
-          begin: '#?[a-z_][a-z1-9_-]+',
-          illegal: '\\n'
+          begin: /#?[a-z_][a-z1-9_-]+/,
+          illegal: /\n/
         }
       ]
     };
     const XML_META_PAR_KEYWORDS = hljs.inherit(XML_META_KEYWORDS, {
-      begin: '\\(',
-      end: '\\)'
+      begin: /\(/,
+      end: /\)/
     });
     const APOS_META_STRING_MODE = hljs.inherit(hljs.APOS_STRING_MODE, {
       className: 'meta-string'
@@ -3162,8 +3285,8 @@ hljs.registerLanguage('xml', function () {
       contains: [
         {
           className: 'meta',
-          begin: '<![a-z]',
-          end: '>',
+          begin: /<![a-z]/,
+          end: />/,
           relevance: 10,
           contains: [
             XML_META_KEYWORDS,
@@ -3171,13 +3294,13 @@ hljs.registerLanguage('xml', function () {
             APOS_META_STRING_MODE,
             XML_META_PAR_KEYWORDS,
             {
-              begin: '\\[',
-              end: '\\]',
+              begin: /\[/,
+              end: /\]/,
               contains: [
                 {
                   className: 'meta',
-                  begin: '<![a-z]',
-                  end: '>',
+                  begin: /<![a-z]/,
+                  end: />/,
                   contains: [
                     XML_META_KEYWORDS,
                     XML_META_PAR_KEYWORDS,
@@ -3190,15 +3313,15 @@ hljs.registerLanguage('xml', function () {
           ]
         },
         hljs.COMMENT(
-          '<!--',
-          '-->',
+          /<!--/,
+          /-->/,
           {
             relevance: 10
           }
         ),
         {
-          begin: '<!\\[CDATA\\[',
-          end: '\\]\\]>',
+          begin: /<!\[CDATA\[/,
+          end: /\]\]>/,
           relevance: 10
         },
         XML_ENTITIES,
@@ -3216,14 +3339,14 @@ hljs.registerLanguage('xml', function () {
           ending braket. The '$' is needed for the lexeme to be recognized
           by hljs.subMode() that tests lexemes outside the stream.
           */
-          begin: '<style(?=\\s|>)',
-          end: '>',
+          begin: /<style(?=\s|>)/,
+          end: />/,
           keywords: {
             name: 'style'
           },
           contains: [ TAG_INTERNALS ],
           starts: {
-            end: '</style>',
+            end: /<\/style>/,
             returnEnd: true,
             subLanguage: [
               'css',
@@ -3234,8 +3357,8 @@ hljs.registerLanguage('xml', function () {
         {
           className: 'tag',
           // See the comment in the <style tag about the lookahead pattern
-          begin: '<script(?=\\s|>)',
-          end: '>',
+          begin: /<script(?=\s|>)/,
+          end: />/,
           keywords: {
             name: 'script'
           },


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]