diff options
author | Michael Catanzaro <mcatanzaro@gnome.org> | 2021-01-06 12:53:43 -0600 |
---|---|---|
committer | Michael Catanzaro <mcatanzaro@gnome.org> | 2021-01-06 12:55:58 -0600 |
commit | 64c9f9ecf1fc2d457eade173cb97e4cd8c083676 (patch) | |
tree | 9f9c7b09e985430652f86436421ce6d1ab6a0051 /third-party | |
parent | 43cc89b82d31c40c4685d546487c116108900441 (diff) | |
download | epiphany-64c9f9ecf1fc2d457eade173cb97e4cd8c083676.tar.gz |
Update to highlight.js 10.5.0mcatanzaro/highlightjs-10.5.0
Diffstat (limited to 'third-party')
-rw-r--r-- | third-party/highlightjs/highlight.js | 807 |
1 files 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 @@ -513,6 +364,18 @@ var hljs = (function () { } /** + * 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' }, |