From fe47931e18b3e2c919803e7dbc1849931295e785 Mon Sep 17 00:00:00 2001 From: Michael Catanzaro Date: Wed, 18 Nov 2020 14:43:20 -0600 Subject: Update to highlight.js 10.4.0 --- third-party/highlightjs/highlight.js | 4448 ++++++++++++++++++---------------- 1 file changed, 2398 insertions(+), 2050 deletions(-) diff --git a/third-party/highlightjs/highlight.js b/third-party/highlightjs/highlight.js index 08b11e98c..bb7a3c759 100644 --- a/third-party/highlightjs/highlight.js +++ b/third-party/highlightjs/highlight.js @@ -1,2209 +1,2265 @@ /* - Highlight.js 10.2.0 (519f7798) + Highlight.js 10.4.0 (4055826e) License: BSD-3-Clause Copyright (c) 2006-2020, Ivan Sagalaev */ var hljs = (function () { - 'use strict'; - - // https://github.com/substack/deep-freeze/blob/master/index.js - - function deepFreeze(obj) { - Object.freeze(obj); - - var objIsFunction = typeof obj === 'function'; - - Object.getOwnPropertyNames(obj).forEach(function(prop) { - if (Object.hasOwnProperty.call(obj, prop) - && obj[prop] !== null - && (typeof obj[prop] === "object" || typeof obj[prop] === "function") - // IE11 fix: https://github.com/highlightjs/highlight.js/issues/2318 - // TODO: remove in the future - && (objIsFunction ? prop !== 'caller' && prop !== 'callee' && prop !== 'arguments' : true) - && !Object.isFrozen(obj[prop])) { - deepFreeze(obj[prop]); - } - }); + 'use strict'; + + function deepFreeze(obj) { + if (obj instanceof Map) { + obj.clear = obj.delete = obj.set = function () { + throw new Error('map is read-only'); + }; + } else if (obj instanceof Set) { + obj.add = obj.clear = obj.delete = function () { + throw new Error('set is read-only'); + }; + } - return obj; - } + // Freeze self + Object.freeze(obj); - class Response { - /** - * @param {CompiledMode} mode - */ - constructor(mode) { - // eslint-disable-next-line no-undefined - if (mode.data === undefined) mode.data = {}; + Object.getOwnPropertyNames(obj).forEach(function (name) { + var prop = obj[name]; - this.data = mode.data; - } + // Freeze prop if it is an object + if (typeof prop == 'object' && !Object.isFrozen(prop)) { + deepFreeze(prop); + } + }); - ignoreMatch() { - this.ignore = true; + return obj; } - } - /** - * @param {string} value - * @returns {string} - */ - function escapeHTML(value) { - return value - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - } + var deepFreezeEs6 = deepFreeze; + var _default = deepFreeze; + deepFreezeEs6.default = _default; - /** - * performs a shallow merge of multiple objects into one - * - * @template T - * @param {T} original - * @param {Record[]} objects - * @returns {T} a single new object - */ - function inherit(original, ...objects) { - /** @type Record */ - var result = {}; + class Response { + + constructor(mode) { + // eslint-disable-next-line no-undefined + if (mode.data === undefined) mode.data = {}; - for (const key in original) { - result[key] = original[key]; - } - objects.forEach(function(obj) { - for (const key in obj) { - result[key] = obj[key]; + this.data = mode.data; } - }); - return /** @type {T} */ (result); - } - /* Stream merging */ + ignoreMatch() { + this.ignore = true; + } + } - /** - * @typedef Event - * @property {'start'|'stop'} event - * @property {number} offset - * @property {Node} node - */ + /** + * @param {string} value + * @returns {string} + */ + function escapeHTML(value) { + return value + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } - /** - * @param {Node} node - */ - function tag(node) { - return node.nodeName.toLowerCase(); - } + /** + * performs a shallow merge of multiple objects into one + * + * @template T + * @param {T} original + * @param {Record[]} objects + * @returns {T} a single new object + */ + function inherit(original, ...objects) { + /** @type Record */ + const result = Object.create(null); - /** - * @param {Node} node - */ - function nodeStream(node) { - /** @type Event[] */ - var result = []; - (function _nodeStream(node, offset) { - for (var 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 - }); - } - } + for (const key in original) { + result[key] = original[key]; } - 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) { - var processed = 0; - var result = ''; - var nodeStack = []; + objects.forEach(function(obj) { + for (const key in obj) { + result[key] = obj[key]; + } + }); + return /** @type {T} */ (result); + } - 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; - } + /* Stream merging */ - /* - 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; - } + /** + * @typedef Event + * @property {'start'|'stop'} event + * @property {number} offset + * @property {Node} node + */ /** * @param {Node} node */ - function open(node) { - /** @param {Attr} attr */ - function attr_str(attr) { - return ' ' + attr.nodeName + '="' + escapeHTML(attr.value) + '"'; - } - // @ts-ignore - result += '<' + tag(node) + [].map.call(node.attributes, attr_str).join('') + '>'; + function tag(node) { + return node.nodeName.toLowerCase(); } /** * @param {Node} node */ - function close(node) { - result += ''; + 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 {Event} event + * @param {any} original - the original stream + * @param {any} highlighted - stream of the highlighted source + * @param {string} value - the original source itself */ - function render(event) { - (event.event === 'start' ? open : close)(event.node); - } + 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; + } - while (original.length || highlighted.length) { - var 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. + 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: */ - 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 highlighted[0].event === 'start' ? original : highlighted; } - } - return result + escapeHTML(value.substr(processed)); - } - var utils = /*#__PURE__*/Object.freeze({ - __proto__: null, - escapeHTML: escapeHTML, - inherit: inherit, - nodeStream: nodeStream, - mergeStreams: mergeStreams - }); + /** + * @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('') + '>'; + } - /** - * @typedef {object} Renderer - * @property {(text: string) => void} addText - * @property {(node: Node) => void} openNode - * @property {(node: Node) => void} closeNode - * @property {() => string} value - */ + /** + * @param {Node} node + */ + function close(node) { + result += ''; + } - /** @typedef {{kind?: string, sublanguage?: boolean}} Node */ - /** @typedef {{walk: (r: Renderer) => void}} Tree */ - /** */ + /** + * @param {Event} event + */ + function render(event) { + (event.event === 'start' ? open : close)(event.node); + } - const SPAN_CLOSE = ''; + 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)); + } - /** - * Determines if a node needs to be wrapped in - * - * @param {Node} node */ - const emitsWrappingTags = (node) => { - return !!node.kind; - }; + var utils = /*#__PURE__*/Object.freeze({ + __proto__: null, + escapeHTML: escapeHTML, + inherit: inherit, + nodeStream: nodeStream, + mergeStreams: mergeStreams + }); - /** @type {Renderer} */ - class HTMLRenderer { /** - * Creates a new HTMLRenderer - * - * @param {Tree} parseTree - the parse tree (must support `walk` API) - * @param {{classPrefix: string}} options + * @typedef {object} Renderer + * @property {(text: string) => void} addText + * @property {(node: Node) => void} openNode + * @property {(node: Node) => void} closeNode + * @property {() => string} value */ - constructor(parseTree, options) { - this.buffer = ""; - this.classPrefix = options.classPrefix; - parseTree.walk(this); - } - /** - * Adds texts to the output stream - * - * @param {string} text */ - addText(text) { - this.buffer += escapeHTML(text); - } + /** @typedef {{kind?: string, sublanguage?: boolean}} Node */ + /** @typedef {{walk: (r: Renderer) => void}} Tree */ + /** */ + + const SPAN_CLOSE = ''; /** - * Adds a node open to the output stream (if needed) + * Determines if a node needs to be wrapped in * * @param {Node} node */ - openNode(node) { - if (!emitsWrappingTags(node)) return; + const emitsWrappingTags = (node) => { + return !!node.kind; + }; - let className = node.kind; - if (!node.sublanguage) { - className = `${this.classPrefix}${className}`; + /** @type {Renderer} */ + class HTMLRenderer { + /** + * Creates a new HTMLRenderer + * + * @param {Tree} parseTree - the parse tree (must support `walk` API) + * @param {{classPrefix: string}} options + */ + constructor(parseTree, options) { + this.buffer = ""; + this.classPrefix = options.classPrefix; + parseTree.walk(this); } - this.span(className); - } - /** - * Adds a node close to the output stream (if needed) - * - * @param {Node} node */ - closeNode(node) { - if (!emitsWrappingTags(node)) return; + /** + * Adds texts to the output stream + * + * @param {string} text */ + addText(text) { + this.buffer += escapeHTML(text); + } - this.buffer += SPAN_CLOSE; - } + /** + * Adds a node open to the output stream (if needed) + * + * @param {Node} node */ + openNode(node) { + if (!emitsWrappingTags(node)) return; - /** - * returns the accumulated buffer - */ - value() { - return this.buffer; - } + let className = node.kind; + if (!node.sublanguage) { + className = `${this.classPrefix}${className}`; + } + this.span(className); + } - // helpers + /** + * Adds a node close to the output stream (if needed) + * + * @param {Node} node */ + closeNode(node) { + if (!emitsWrappingTags(node)) return; - /** - * Builds a span element - * - * @param {string} className */ - span(className) { - this.buffer += ``; - } - } + this.buffer += SPAN_CLOSE; + } - /** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} | string} Node */ - /** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} } DataNode */ - /** */ + /** + * returns the accumulated buffer + */ + value() { + return this.buffer; + } - class TokenTree { - constructor() { - /** @type DataNode */ - this.rootNode = { children: [] }; - this.stack = [this.rootNode]; - } + // helpers - get top() { - return this.stack[this.stack.length - 1]; + /** + * Builds a span element + * + * @param {string} className */ + span(className) { + this.buffer += ``; + } } - get root() { return this.rootNode; } + /** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} | string} Node */ + /** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} } DataNode */ + /** */ - /** @param {Node} node */ - add(node) { - this.top.children.push(node); - } + class TokenTree { + constructor() { + /** @type DataNode */ + this.rootNode = { children: [] }; + this.stack = [this.rootNode]; + } - /** @param {string} kind */ - openNode(kind) { - /** @type Node */ - const node = { kind, children: [] }; - this.add(node); - this.stack.push(node); - } + get top() { + return this.stack[this.stack.length - 1]; + } + + get root() { return this.rootNode; } - closeNode() { - if (this.stack.length > 1) { - return this.stack.pop(); + /** @param {Node} node */ + add(node) { + this.top.children.push(node); } - // eslint-disable-next-line no-undefined - return undefined; - } - closeAllNodes() { - while (this.closeNode()); - } + /** @param {string} kind */ + openNode(kind) { + /** @type Node */ + const node = { kind, children: [] }; + this.add(node); + this.stack.push(node); + } - toJSON() { - return JSON.stringify(this.rootNode, null, 4); - } + closeNode() { + if (this.stack.length > 1) { + return this.stack.pop(); + } + // eslint-disable-next-line no-undefined + return undefined; + } - /** - * @typedef { import("./html_renderer").Renderer } Renderer - * @param {Renderer} builder - */ - walk(builder) { - // this does not - return this.constructor._walk(builder, this.rootNode); - // this works - // return TokenTree._walk(builder, this.rootNode); + closeAllNodes() { + while (this.closeNode()); + } + + toJSON() { + return JSON.stringify(this.rootNode, null, 4); + } + + /** + * @typedef { import("./html_renderer").Renderer } Renderer + * @param {Renderer} builder + */ + walk(builder) { + // this does not + return this.constructor._walk(builder, this.rootNode); + // this works + // return TokenTree._walk(builder, this.rootNode); + } + + /** + * @param {Renderer} builder + * @param {Node} node + */ + static _walk(builder, node) { + if (typeof node === "string") { + builder.addText(node); + } else if (node.children) { + builder.openNode(node); + node.children.forEach((child) => this._walk(builder, child)); + builder.closeNode(node); + } + return builder; + } + + /** + * @param {Node} node + */ + static _collapse(node) { + if (typeof node === "string") return; + if (!node.children) return; + + if (node.children.every(el => typeof el === "string")) { + // node.text = node.children.join(""); + // delete node.children; + node.children = [node.children.join("")]; + } else { + node.children.forEach((child) => { + TokenTree._collapse(child); + }); + } + } } /** - * @param {Renderer} builder - * @param {Node} node - */ - static _walk(builder, node) { - if (typeof node === "string") { - builder.addText(node); - } else if (node.children) { - builder.openNode(node); - node.children.forEach((child) => this._walk(builder, child)); - builder.closeNode(node); - } - return builder; - } + Currently this is all private API, but this is the minimal API necessary + that an Emitter must implement to fully support the parser. + + Minimal interface: + + - addKeyword(text, kind) + - addText(text) + - addSublanguage(emitter, subLanguageName) + - finalize() + - openNode(kind) + - closeNode() + - closeAllNodes() + - toHTML() + + */ /** - * @param {Node} node + * @implements {Emitter} */ - static _collapse(node) { - if (typeof node === "string") return; - if (!node.children) return; - - if (node.children.every(el => typeof el === "string")) { - // node.text = node.children.join(""); - // delete node.children; - node.children = [node.children.join("")]; - } else { - node.children.forEach((child) => { - TokenTree._collapse(child); - }); + class TokenTreeEmitter extends TokenTree { + /** + * @param {*} options + */ + constructor(options) { + super(); + this.options = options; } - } - } - /** - Currently this is all private API, but this is the minimal API necessary - that an Emitter must implement to fully support the parser. + /** + * @param {string} text + * @param {string} kind + */ + addKeyword(text, kind) { + if (text === "") { return; } - Minimal interface: + this.openNode(kind); + this.addText(text); + this.closeNode(); + } - - addKeyword(text, kind) - - addText(text) - - addSublanguage(emitter, subLanguageName) - - finalize() - - openNode(kind) - - closeNode() - - closeAllNodes() - - toHTML() + /** + * @param {string} text + */ + addText(text) { + if (text === "") { return; } - */ + this.add(text); + } - /** - * @implements {Emitter} - */ - class TokenTreeEmitter extends TokenTree { - /** - * @param {*} options - */ - constructor(options) { - super(); - this.options = options; + /** + * @param {Emitter & {root: DataNode}} emitter + * @param {string} name + */ + addSublanguage(emitter, name) { + /** @type DataNode */ + const node = emitter.root; + node.kind = name; + node.sublanguage = true; + this.add(node); + } + + toHTML() { + const renderer = new HTMLRenderer(this, this.options); + return renderer.value(); + } + + finalize() { + return true; + } } /** - * @param {string} text - * @param {string} kind - */ - addKeyword(text, kind) { - if (text === "") { return; } - - this.openNode(kind); - this.addText(text); - this.closeNode(); + * @param {string} value + * @returns {RegExp} + * */ + function escape(value) { + return new RegExp(value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'm'); } /** - * @param {string} text + * @param {RegExp | string } re + * @returns {string} */ - addText(text) { - if (text === "") { return; } + function source(re) { + if (!re) return null; + if (typeof re === "string") return re; - this.add(text); + return re.source; } /** - * @param {Emitter & {root: DataNode}} emitter - * @param {string} name + * @param {...(RegExp | string) } args + * @returns {string} */ - addSublanguage(emitter, name) { - /** @type DataNode */ - const node = emitter.root; - node.kind = name; - node.sublanguage = true; - this.add(node); + function concat(...args) { + const joined = args.map((x) => source(x)).join(""); + return joined; } - toHTML() { - const renderer = new HTMLRenderer(this, this.options); - return renderer.value(); + /** + * @param {RegExp} re + * @returns {number} + */ + function countMatchGroups(re) { + return (new RegExp(re.toString() + '|')).exec('').length - 1; } - finalize() { - return true; + /** + * Does lexeme start with a regular expression match at the beginning + * @param {RegExp} re + * @param {string} lexeme + */ + function startsWith(re, lexeme) { + const match = re && re.exec(lexeme); + return match && match.index === 0; } - } - - /** - * @param {string} value - * @returns {RegExp} - * */ - function escape(value) { - return new RegExp(value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'm'); - } - - /** - * @param {RegExp | string } re - * @returns {string} - */ - function source(re) { - if (!re) return null; - if (typeof re === "string") return re; - - return re.source; - } - - /** - * @param {...(RegExp | string) } args - * @returns {string} - */ - function concat(...args) { - const joined = args.map((x) => source(x)).join(""); - return joined; - } - - /** - * @param {RegExp} re - * @returns {number} - */ - function countMatchGroups(re) { - return (new RegExp(re.toString() + '|')).exec('').length - 1; - } - - /** - * Does lexeme start with a regular expression match at the beginning - * @param {RegExp} re - * @param {string} lexeme - */ - function startsWith(re, lexeme) { - var match = re && re.exec(lexeme); - return match && match.index === 0; - } - // join logically computes regexps.join(separator), but fixes the - // backreferences so they continue to match. - // it also places each individual regular expression into it's own - // match group, keeping track of the sequencing of those match groups - // is currently an exercise for the caller. :-) - /** - * @param {(string | RegExp)[]} regexps - * @param {string} separator - * @returns {string} - */ - function join(regexps, separator = "|") { - // backreferenceRe matches an open parenthesis or backreference. To avoid - // an incorrect parse, it additionally matches the following: - // - [...] elements, where the meaning of parentheses and escapes change - // - other escape sequences, so we do not misparse escape sequences as - // interesting elements - // - non-matching or lookahead parentheses, which do not capture. These - // follow the '(' with a '?'. - var backreferenceRe = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./; - var numCaptures = 0; - var ret = ''; - for (var i = 0; i < regexps.length; i++) { - numCaptures += 1; - var offset = numCaptures; - var re = source(regexps[i]); - if (i > 0) { - ret += separator; - } - ret += "("; - while (re.length > 0) { - var match = backreferenceRe.exec(re); - if (match == null) { - ret += re; - break; - } - ret += re.substring(0, match.index); - re = re.substring(match.index + match[0].length); - if (match[0][0] === '\\' && match[1]) { - // Adjust the backreference. - ret += '\\' + String(Number(match[1]) + offset); - } else { - ret += match[0]; - if (match[0] === '(') { - numCaptures++; + // join logically computes regexps.join(separator), but fixes the + // backreferences so they continue to match. + // it also places each individual regular expression into it's own + // match group, keeping track of the sequencing of those match groups + // is currently an exercise for the caller. :-) + /** + * @param {(string | RegExp)[]} regexps + * @param {string} separator + * @returns {string} + */ + function join(regexps, separator = "|") { + // backreferenceRe matches an open parenthesis or backreference. To avoid + // an incorrect parse, it additionally matches the following: + // - [...] elements, where the meaning of parentheses and escapes change + // - other escape sequences, so we do not misparse escape sequences as + // interesting elements + // - non-matching or lookahead parentheses, which do not capture. These + // follow the '(' with a '?'. + const backreferenceRe = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./; + let numCaptures = 0; + let ret = ''; + for (let i = 0; i < regexps.length; i++) { + numCaptures += 1; + const offset = numCaptures; + let re = source(regexps[i]); + if (i > 0) { + ret += separator; + } + ret += "("; + while (re.length > 0) { + const match = backreferenceRe.exec(re); + if (match == null) { + ret += re; + break; + } + ret += re.substring(0, match.index); + re = re.substring(match.index + match[0].length); + if (match[0][0] === '\\' && match[1]) { + // Adjust the backreference. + ret += '\\' + String(Number(match[1]) + offset); + } else { + ret += match[0]; + if (match[0] === '(') { + numCaptures++; + } } } + ret += ")"; } - ret += ")"; + return ret; } - return ret; - } - // Common regexps - const IDENT_RE = '[a-zA-Z]\\w*'; - const UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\w*'; - const NUMBER_RE = '\\b\\d+(\\.\\d+)?'; - const C_NUMBER_RE = '(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float - const BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b... - const RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~'; - - /** - * @param { Partial & {binary?: string | RegExp} } opts - */ - const SHEBANG = (opts = {}) => { - const beginShebang = /^#![ ]*\//; - if (opts.binary) { - opts.begin = concat( - beginShebang, - /.*\b/, - opts.binary, - /\b.*/); - } - return inherit({ - className: 'meta', - begin: beginShebang, - end: /$/, - relevance: 0, - /** @type {ModeCallback} */ - "on:begin": (m, resp) => { - if (m.index !== 0) resp.ignoreMatch(); - } - }, opts); - }; - - // Common modes - const BACKSLASH_ESCAPE = { - begin: '\\\\[\\s\\S]', relevance: 0 - }; - const APOS_STRING_MODE = { - className: 'string', - begin: '\'', - end: '\'', - illegal: '\\n', - contains: [BACKSLASH_ESCAPE] - }; - const QUOTE_STRING_MODE = { - className: 'string', - begin: '"', - end: '"', - illegal: '\\n', - contains: [BACKSLASH_ESCAPE] - }; - const PHRASAL_WORDS_MODE = { - 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/ - }; - /** - * Creates a comment mode - * - * @param {string | RegExp} begin - * @param {string | RegExp} end - * @param {Mode | {}} [modeOptions] - * @returns {Partial} - */ - const COMMENT = function(begin, end, modeOptions = {}) { - var mode = inherit( - { - className: 'comment', - begin, - end, - contains: [] - }, - modeOptions - ); - mode.contains.push(PHRASAL_WORDS_MODE); - mode.contains.push({ - className: 'doctag', - begin: '(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):', - relevance: 0 - }); - return mode; - }; - const C_LINE_COMMENT_MODE = COMMENT('//', '$'); - const C_BLOCK_COMMENT_MODE = COMMENT('/\\*', '\\*/'); - const HASH_COMMENT_MODE = COMMENT('#', '$'); - const NUMBER_MODE = { - className: 'number', - begin: NUMBER_RE, - relevance: 0 - }; - const C_NUMBER_MODE = { - className: 'number', - begin: C_NUMBER_RE, - relevance: 0 - }; - const BINARY_NUMBER_MODE = { - className: 'number', - begin: BINARY_NUMBER_RE, - relevance: 0 - }; - const CSS_NUMBER_MODE = { - className: 'number', - begin: NUMBER_RE + '(' + - '%|em|ex|ch|rem' + - '|vw|vh|vmin|vmax' + - '|cm|mm|in|pt|pc|px' + - '|deg|grad|rad|turn' + - '|s|ms' + - '|Hz|kHz' + - '|dpi|dpcm|dppx' + - ')?', - relevance: 0 - }; - const REGEXP_MODE = { - // this outer rule makes sure we actually have a WHOLE regex and not simply - // an expression such as: - // - // 3 / something - // - // (which will then blow up when regex's `illegal` sees the newline) - begin: /(?=\/[^/\n]*\/)/, - contains: [{ - className: 'regexp', - begin: /\//, - end: /\/[gimuy]*/, - illegal: /\n/, - contains: [ - BACKSLASH_ESCAPE, - { - begin: /\[/, - end: /\]/, - relevance: 0, - contains: [BACKSLASH_ESCAPE] - } - ] - }] - }; - const TITLE_MODE = { - className: 'title', - begin: IDENT_RE, - relevance: 0 - }; - const UNDERSCORE_TITLE_MODE = { - className: 'title', - begin: UNDERSCORE_IDENT_RE, - relevance: 0 - }; - const METHOD_GUARD = { - // excludes method names from keyword processing - begin: '\\.\\s*' + UNDERSCORE_IDENT_RE, - relevance: 0 - }; + // Common regexps + const IDENT_RE = '[a-zA-Z]\\w*'; + const UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\w*'; + const NUMBER_RE = '\\b\\d+(\\.\\d+)?'; + const C_NUMBER_RE = '(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float + const BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b... + const RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~'; - /** - * Adds end same as begin mechanics to a mode - * - * Your mode must include at least a single () match group as that first match - * group is what is used for comparison - * @param {Partial} mode - */ - const END_SAME_AS_BEGIN = function(mode) { - return Object.assign(mode, - { - /** @type {ModeCallback} */ - 'on:begin': (m, resp) => { resp.data._beginMatch = m[1]; }, + /** + * @param { Partial & {binary?: string | RegExp} } opts + */ + const SHEBANG = (opts = {}) => { + const beginShebang = /^#![ ]*\//; + if (opts.binary) { + opts.begin = concat( + beginShebang, + /.*\b/, + opts.binary, + /\b.*/); + } + return inherit({ + className: 'meta', + begin: beginShebang, + end: /$/, + relevance: 0, /** @type {ModeCallback} */ - 'on:end': (m, resp) => { if (resp.data._beginMatch !== m[1]) resp.ignoreMatch(); } - }); - }; - - var MODES = /*#__PURE__*/Object.freeze({ - __proto__: null, - IDENT_RE: IDENT_RE, - UNDERSCORE_IDENT_RE: UNDERSCORE_IDENT_RE, - NUMBER_RE: NUMBER_RE, - C_NUMBER_RE: C_NUMBER_RE, - BINARY_NUMBER_RE: BINARY_NUMBER_RE, - RE_STARTERS_RE: RE_STARTERS_RE, - SHEBANG: SHEBANG, - BACKSLASH_ESCAPE: BACKSLASH_ESCAPE, - APOS_STRING_MODE: APOS_STRING_MODE, - QUOTE_STRING_MODE: QUOTE_STRING_MODE, - PHRASAL_WORDS_MODE: PHRASAL_WORDS_MODE, - COMMENT: COMMENT, - C_LINE_COMMENT_MODE: C_LINE_COMMENT_MODE, - C_BLOCK_COMMENT_MODE: C_BLOCK_COMMENT_MODE, - HASH_COMMENT_MODE: HASH_COMMENT_MODE, - NUMBER_MODE: NUMBER_MODE, - C_NUMBER_MODE: C_NUMBER_MODE, - BINARY_NUMBER_MODE: BINARY_NUMBER_MODE, - CSS_NUMBER_MODE: CSS_NUMBER_MODE, - REGEXP_MODE: REGEXP_MODE, - TITLE_MODE: TITLE_MODE, - UNDERSCORE_TITLE_MODE: UNDERSCORE_TITLE_MODE, - METHOD_GUARD: METHOD_GUARD, - END_SAME_AS_BEGIN: END_SAME_AS_BEGIN - }); - - // keywords that should have no default relevance value - var COMMON_KEYWORDS = 'of and for in not or if then'.split(' '); - - // compilation + "on:begin": (m, resp) => { + if (m.index !== 0) resp.ignoreMatch(); + } + }, opts); + }; - /** - * Compiles a language definition result - * - * Given the raw result of a language definition (Language), compiles this so - * that it is ready for highlighting code. - * @param {Language} language - * @returns {CompiledLanguage} - */ - function compileLanguage(language) { + // Common modes + const BACKSLASH_ESCAPE = { + begin: '\\\\[\\s\\S]', relevance: 0 + }; + const APOS_STRING_MODE = { + className: 'string', + begin: '\'', + end: '\'', + illegal: '\\n', + contains: [BACKSLASH_ESCAPE] + }; + const QUOTE_STRING_MODE = { + className: 'string', + begin: '"', + end: '"', + illegal: '\\n', + contains: [BACKSLASH_ESCAPE] + }; + const PHRASAL_WORDS_MODE = { + 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/ + }; /** - * Builds a regex with the case sensativility of the current language + * Creates a comment mode * - * @param {RegExp | string} value - * @param {boolean} [global] + * @param {string | RegExp} begin + * @param {string | RegExp} end + * @param {Mode | {}} [modeOptions] + * @returns {Partial} */ - function langRe(value, global) { - return new RegExp( - source(value), - 'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '') + const COMMENT = function(begin, end, modeOptions = {}) { + const mode = inherit( + { + className: 'comment', + begin, + end, + contains: [] + }, + modeOptions ); - } + mode.contains.push(PHRASAL_WORDS_MODE); + mode.contains.push({ + className: 'doctag', + begin: '(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):', + relevance: 0 + }); + return mode; + }; + const C_LINE_COMMENT_MODE = COMMENT('//', '$'); + const C_BLOCK_COMMENT_MODE = COMMENT('/\\*', '\\*/'); + const HASH_COMMENT_MODE = COMMENT('#', '$'); + const NUMBER_MODE = { + className: 'number', + begin: NUMBER_RE, + relevance: 0 + }; + const C_NUMBER_MODE = { + className: 'number', + begin: C_NUMBER_RE, + relevance: 0 + }; + const BINARY_NUMBER_MODE = { + className: 'number', + begin: BINARY_NUMBER_RE, + relevance: 0 + }; + const CSS_NUMBER_MODE = { + className: 'number', + begin: NUMBER_RE + '(' + + '%|em|ex|ch|rem' + + '|vw|vh|vmin|vmax' + + '|cm|mm|in|pt|pc|px' + + '|deg|grad|rad|turn' + + '|s|ms' + + '|Hz|kHz' + + '|dpi|dpcm|dppx' + + ')?', + relevance: 0 + }; + const REGEXP_MODE = { + // this outer rule makes sure we actually have a WHOLE regex and not simply + // an expression such as: + // + // 3 / something + // + // (which will then blow up when regex's `illegal` sees the newline) + begin: /(?=\/[^/\n]*\/)/, + contains: [{ + className: 'regexp', + begin: /\//, + end: /\/[gimuy]*/, + illegal: /\n/, + contains: [ + BACKSLASH_ESCAPE, + { + begin: /\[/, + end: /\]/, + relevance: 0, + contains: [BACKSLASH_ESCAPE] + } + ] + }] + }; + const TITLE_MODE = { + className: 'title', + begin: IDENT_RE, + relevance: 0 + }; + const UNDERSCORE_TITLE_MODE = { + className: 'title', + begin: UNDERSCORE_IDENT_RE, + relevance: 0 + }; + const METHOD_GUARD = { + // excludes method names from keyword processing + begin: '\\.\\s*' + UNDERSCORE_IDENT_RE, + relevance: 0 + }; /** - Stores multiple regular expressions and allows you to quickly search for - them all in a string simultaneously - returning the first match. It does - this by creating a huge (a|b|c) regex - each individual item wrapped with () - and joined by `|` - using match groups to track position. When a match is - found checking which position in the array has content allows us to figure - out which of the original regexes / match groups triggered the match. - - The match object itself (the result of `Regex.exec`) is returned but also - enhanced by merging in any meta-data that was registered with the regex. - This is how we keep track of which mode matched, and what type of rule - (`illegal`, `begin`, end, etc). - */ - class MultiRegex { - constructor() { - this.matchIndexes = {}; - // @ts-ignore - this.regexes = []; - this.matchAt = 1; - this.position = 0; - } - - // @ts-ignore - addRule(re, opts) { - opts.position = this.position++; - // @ts-ignore - this.matchIndexes[this.matchAt] = opts; - this.regexes.push([opts, re]); - this.matchAt += countMatchGroups(re) + 1; - } + * Adds end same as begin mechanics to a mode + * + * Your mode must include at least a single () match group as that first match + * group is what is used for comparison + * @param {Partial} mode + */ + const END_SAME_AS_BEGIN = function(mode) { + return Object.assign(mode, + { + /** @type {ModeCallback} */ + 'on:begin': (m, resp) => { resp.data._beginMatch = m[1]; }, + /** @type {ModeCallback} */ + 'on:end': (m, resp) => { if (resp.data._beginMatch !== m[1]) resp.ignoreMatch(); } + }); + }; - compile() { - if (this.regexes.length === 0) { - // avoids the need to check length every time exec is called - // @ts-ignore - this.exec = () => null; - } - const terminators = this.regexes.map(el => el[1]); - this.matcherRe = langRe(join(terminators), true); - this.lastIndex = 0; - } + var MODES = /*#__PURE__*/Object.freeze({ + __proto__: null, + IDENT_RE: IDENT_RE, + UNDERSCORE_IDENT_RE: UNDERSCORE_IDENT_RE, + NUMBER_RE: NUMBER_RE, + C_NUMBER_RE: C_NUMBER_RE, + BINARY_NUMBER_RE: BINARY_NUMBER_RE, + RE_STARTERS_RE: RE_STARTERS_RE, + SHEBANG: SHEBANG, + BACKSLASH_ESCAPE: BACKSLASH_ESCAPE, + APOS_STRING_MODE: APOS_STRING_MODE, + QUOTE_STRING_MODE: QUOTE_STRING_MODE, + PHRASAL_WORDS_MODE: PHRASAL_WORDS_MODE, + COMMENT: COMMENT, + C_LINE_COMMENT_MODE: C_LINE_COMMENT_MODE, + C_BLOCK_COMMENT_MODE: C_BLOCK_COMMENT_MODE, + HASH_COMMENT_MODE: HASH_COMMENT_MODE, + NUMBER_MODE: NUMBER_MODE, + C_NUMBER_MODE: C_NUMBER_MODE, + BINARY_NUMBER_MODE: BINARY_NUMBER_MODE, + CSS_NUMBER_MODE: CSS_NUMBER_MODE, + REGEXP_MODE: REGEXP_MODE, + TITLE_MODE: TITLE_MODE, + UNDERSCORE_TITLE_MODE: UNDERSCORE_TITLE_MODE, + METHOD_GUARD: METHOD_GUARD, + END_SAME_AS_BEGIN: END_SAME_AS_BEGIN + }); - /** @param {string} s */ - exec(s) { - this.matcherRe.lastIndex = this.lastIndex; - const match = this.matcherRe.exec(s); - if (!match) { return null; } + // keywords that should have no default relevance value + const COMMON_KEYWORDS = [ + 'of', + 'and', + 'for', + 'in', + 'not', + 'or', + 'if', + 'then', + 'parent', // common variable name + 'list', // common variable name + 'value' // common variable name + ]; - // eslint-disable-next-line no-undefined - const i = match.findIndex((el, i) => i > 0 && el !== undefined); - // @ts-ignore - const matchData = this.matchIndexes[i]; - // trim off any earlier non-relevant match groups (ie, the other regex - // match groups that make up the multi-matcher) - match.splice(0, i); + // compilation - return Object.assign(match, matchData); + /** + * Compiles a language definition result + * + * Given the raw result of a language definition (Language), compiles this so + * that it is ready for highlighting code. + * @param {Language} language + * @returns {CompiledLanguage} + */ + function compileLanguage(language) { + /** + * Builds a regex with the case sensativility of the current language + * + * @param {RegExp | string} value + * @param {boolean} [global] + */ + function langRe(value, global) { + return new RegExp( + source(value), + 'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '') + ); } - } - - /* - Created to solve the key deficiently with MultiRegex - there is no way to - test for multiple matches at a single location. Why would we need to do - that? In the future a more dynamic engine will allow certain matches to be - ignored. An example: if we matched say the 3rd regex in a large group but - decided to ignore it - we'd need to started testing again at the 4th - regex... but MultiRegex itself gives us no real way to do that. - - So what this class creates MultiRegexs on the fly for whatever search - position they are needed. - NOTE: These additional MultiRegex objects are created dynamically. For most - grammars most of the time we will never actually need anything more than the - first MultiRegex - so this shouldn't have too much overhead. - - Say this is our search group, and we match regex3, but wish to ignore it. - - regex1 | regex2 | regex3 | regex4 | regex5 ' ie, startAt = 0 - - What we need is a new MultiRegex that only includes the remaining - possibilities: - - regex4 | regex5 ' ie, startAt = 3 - - This class wraps all that complexity up in a simple API... `startAt` decides - where in the array of expressions to start doing the matching. It - auto-increments, so if a match is found at position 2, then startAt will be - set to 3. If the end is reached startAt will return to 0. + /** + Stores multiple regular expressions and allows you to quickly search for + them all in a string simultaneously - returning the first match. It does + this by creating a huge (a|b|c) regex - each individual item wrapped with () + and joined by `|` - using match groups to track position. When a match is + found checking which position in the array has content allows us to figure + out which of the original regexes / match groups triggered the match. + + The match object itself (the result of `Regex.exec`) is returned but also + enhanced by merging in any meta-data that was registered with the regex. + This is how we keep track of which mode matched, and what type of rule + (`illegal`, `begin`, end, etc). + */ + class MultiRegex { + constructor() { + this.matchIndexes = {}; + // @ts-ignore + this.regexes = []; + this.matchAt = 1; + this.position = 0; + } - MOST of the time the parser will be setting startAt manually to 0. - */ - class ResumableMultiRegex { - constructor() { - // @ts-ignore - this.rules = []; // @ts-ignore - this.multiRegexes = []; - this.count = 0; + addRule(re, opts) { + opts.position = this.position++; + // @ts-ignore + this.matchIndexes[this.matchAt] = opts; + this.regexes.push([opts, re]); + this.matchAt += countMatchGroups(re) + 1; + } - this.lastIndex = 0; - this.regexIndex = 0; - } + compile() { + if (this.regexes.length === 0) { + // avoids the need to check length every time exec is called + // @ts-ignore + this.exec = () => null; + } + const terminators = this.regexes.map(el => el[1]); + this.matcherRe = langRe(join(terminators), true); + this.lastIndex = 0; + } - // @ts-ignore - getMatcher(index) { - if (this.multiRegexes[index]) return this.multiRegexes[index]; + /** @param {string} s */ + exec(s) { + this.matcherRe.lastIndex = this.lastIndex; + const match = this.matcherRe.exec(s); + if (!match) { return null; } - const matcher = new MultiRegex(); - this.rules.slice(index).forEach(([re, opts]) => matcher.addRule(re, opts)); - matcher.compile(); - this.multiRegexes[index] = matcher; - return matcher; - } + // eslint-disable-next-line no-undefined + const i = match.findIndex((el, i) => i > 0 && el !== undefined); + // @ts-ignore + const matchData = this.matchIndexes[i]; + // trim off any earlier non-relevant match groups (ie, the other regex + // match groups that make up the multi-matcher) + match.splice(0, i); - resumingScanAtSamePosition() { - return this.regexIndex !== 0; + return Object.assign(match, matchData); + } } - considerAll() { - this.regexIndex = 0; - } + /* + Created to solve the key deficiently with MultiRegex - there is no way to + test for multiple matches at a single location. Why would we need to do + that? In the future a more dynamic engine will allow certain matches to be + ignored. An example: if we matched say the 3rd regex in a large group but + decided to ignore it - we'd need to started testing again at the 4th + regex... but MultiRegex itself gives us no real way to do that. - // @ts-ignore - addRule(re, opts) { - this.rules.push([re, opts]); - if (opts.type === "begin") this.count++; - } + So what this class creates MultiRegexs on the fly for whatever search + position they are needed. - /** @param {string} s */ - exec(s) { - const m = this.getMatcher(this.regexIndex); - m.lastIndex = this.lastIndex; - let result = m.exec(s); + NOTE: These additional MultiRegex objects are created dynamically. For most + grammars most of the time we will never actually need anything more than the + first MultiRegex - so this shouldn't have too much overhead. - // The following is because we have no easy way to say "resume scanning at the - // existing position but also skip the current rule ONLY". What happens is - // all prior rules are also skipped which can result in matching the wrong - // thing. Example of matching "booger": + Say this is our search group, and we match regex3, but wish to ignore it. - // our matcher is [string, "booger", number] - // - // ....booger.... + regex1 | regex2 | regex3 | regex4 | regex5 ' ie, startAt = 0 - // if "booger" is ignored then we'd really need a regex to scan from the - // SAME position for only: [string, number] but ignoring "booger" (if it - // was the first match), a simple resume would scan ahead who knows how - // far looking only for "number", ignoring potential string matches (or - // future "booger" matches that might be valid.) + What we need is a new MultiRegex that only includes the remaining + possibilities: - // So what we do: We execute two matchers, one resuming at the same - // position, but the second full matcher starting at the position after: + regex4 | regex5 ' ie, startAt = 3 - // /--- resume first regex match here (for [number]) - // |/---- full match here for [string, "booger", number] - // vv - // ....booger.... + This class wraps all that complexity up in a simple API... `startAt` decides + where in the array of expressions to start doing the matching. It + auto-increments, so if a match is found at position 2, then startAt will be + set to 3. If the end is reached startAt will return to 0. - // Which ever results in a match first is then used. So this 3-4 step - // process essentially allows us to say "match at this position, excluding - // a prior rule that was ignored". - // - // 1. Match "booger" first, ignore. Also proves that [string] does non match. - // 2. Resume matching for [number] - // 3. Match at index + 1 for [string, "booger", number] - // 4. If #2 and #3 result in matches, which came first? - if (this.resumingScanAtSamePosition()) { - if (result && result.index === this.lastIndex) ; else { // use the second matcher result - const m2 = this.getMatcher(0); - m2.lastIndex = this.lastIndex + 1; - result = m2.exec(s); - } - } + MOST of the time the parser will be setting startAt manually to 0. + */ + class ResumableMultiRegex { + constructor() { + // @ts-ignore + this.rules = []; + // @ts-ignore + this.multiRegexes = []; + this.count = 0; - if (result) { - this.regexIndex += result.position + 1; - if (this.regexIndex === this.count) { - // wrap-around to considering all matches again - this.considerAll(); - } + this.lastIndex = 0; + this.regexIndex = 0; } - return result; - } - } - - /** - * Given a mode, builds a huge ResumableMultiRegex that can be used to walk - * the content and find matches. - * - * @param {CompiledMode} mode - * @returns {ResumableMultiRegex} - */ - function buildModeRegex(mode) { - const mm = new ResumableMultiRegex(); - - 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.illegal) { - mm.addRule(mode.illegal, { type: "illegal" }); - } - - return mm; - } - - // TODO: We need negative look-behind support to do this properly - /** - * Skip a match if it has a preceding or trailing 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 skipIfhasPrecedingOrTrailingDot(match, response) { - const before = match.input[match.index - 1]; - const after = match.input[match.index + match[0].length]; - if (before === "." || after === ".") { - response.ignoreMatch(); - } - } + // @ts-ignore + getMatcher(index) { + if (this.multiRegexes[index]) return this.multiRegexes[index]; + + const matcher = new MultiRegex(); + this.rules.slice(index).forEach(([re, opts]) => matcher.addRule(re, opts)); + matcher.compile(); + this.multiRegexes[index] = matcher; + return matcher; + } - /** skip vs abort vs ignore - * - * @skip - The mode is still entered and exited normally (and contains rules apply), - * but all content is held and added to the parent buffer rather than being - * output when the mode ends. Mostly used with `sublanguage` to build up - * a single large buffer than can be parsed by sublanguage. - * - * - The mode begin ands ends normally. - * - Content matched is added to the parent mode buffer. - * - The parser cursor is moved forward normally. - * - * @abort - A hack placeholder until we have ignore. Aborts the mode (as if it - * never matched) but DOES NOT continue to match subsequent `contains` - * modes. Abort is bad/suboptimal because it can result in modes - * farther down not getting applied because an earlier rule eats the - * content but then aborts. - * - * - The mode does not begin. - * - Content matched by `begin` is added to the mode buffer. - * - The parser cursor is moved forward accordingly. - * - * @ignore - Ignores the mode (as if it never matched) and continues to match any - * subsequent `contains` modes. Ignore isn't technically possible with - * the current parser implementation. - * - * - The mode does not begin. - * - Content matched by `begin` is ignored. - * - The parser cursor is not moved forward. - */ + resumingScanAtSamePosition() { + return this.regexIndex !== 0; + } - /** - * Compiles an individual mode - * - * This can raise an error if the mode contains certain detectable known logic - * issues. - * @param {Mode} mode - * @param {CompiledMode | null} [parent] - * @returns {CompiledMode | never} - */ - function compileMode(mode, parent) { - const cmode = /** @type CompiledMode */ (mode); - if (mode.compiled) return cmode; - mode.compiled = true; + considerAll() { + this.regexIndex = 0; + } - // __beforeBegin is considered private API, internal use only - mode.__beforeBegin = null; + // @ts-ignore + addRule(re, opts) { + this.rules.push([re, opts]); + if (opts.type === "begin") this.count++; + } - mode.keywords = mode.keywords || mode.beginKeywords; + /** @param {string} s */ + exec(s) { + const m = this.getMatcher(this.regexIndex); + m.lastIndex = this.lastIndex; + let result = m.exec(s); + + // The following is because we have no easy way to say "resume scanning at the + // existing position but also skip the current rule ONLY". What happens is + // all prior rules are also skipped which can result in matching the wrong + // thing. Example of matching "booger": + + // our matcher is [string, "booger", number] + // + // ....booger.... + + // if "booger" is ignored then we'd really need a regex to scan from the + // SAME position for only: [string, number] but ignoring "booger" (if it + // was the first match), a simple resume would scan ahead who knows how + // far looking only for "number", ignoring potential string matches (or + // future "booger" matches that might be valid.) + + // So what we do: We execute two matchers, one resuming at the same + // position, but the second full matcher starting at the position after: + + // /--- resume first regex match here (for [number]) + // |/---- full match here for [string, "booger", number] + // vv + // ....booger.... + + // Which ever results in a match first is then used. So this 3-4 step + // process essentially allows us to say "match at this position, excluding + // a prior rule that was ignored". + // + // 1. Match "booger" first, ignore. Also proves that [string] does non match. + // 2. Resume matching for [number] + // 3. Match at index + 1 for [string, "booger", number] + // 4. If #2 and #3 result in matches, which came first? + if (this.resumingScanAtSamePosition()) { + if (result && result.index === this.lastIndex) ; else { // use the second matcher result + const m2 = this.getMatcher(0); + m2.lastIndex = this.lastIndex + 1; + result = m2.exec(s); + } + } - let kw_pattern = null; - if (typeof mode.keywords === "object") { - kw_pattern = mode.keywords.$pattern; - delete mode.keywords.$pattern; - } + if (result) { + this.regexIndex += result.position + 1; + if (this.regexIndex === this.count) { + // wrap-around to considering all matches again + this.considerAll(); + } + } - if (mode.keywords) { - mode.keywords = compileKeywords(mode.keywords, language.case_insensitive); + return result; + } } - // both are not allowed - if (mode.lexemes && kw_pattern) { - throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) "); - } + /** + * Given a mode, builds a huge ResumableMultiRegex that can be used to walk + * the content and find matches. + * + * @param {CompiledMode} mode + * @returns {ResumableMultiRegex} + */ + function buildModeRegex(mode) { + const mm = new ResumableMultiRegex(); - // `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 || kw_pattern || /\w+/, true); + mode.contains.forEach(term => mm.addRule(term.begin, { rule: term, type: "begin" })); - 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 = skipIfhasPrecedingOrTrailingDot; + if (mode.terminator_end) { + mm.addRule(mode.terminator_end, { type: "end" }); } - 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; + if (mode.illegal) { + mm.addRule(mode.illegal, { type: "illegal" }); } - } - if (mode.illegal) cmode.illegalRe = langRe(mode.illegal); - // eslint-disable-next-line no-undefined - if (mode.relevance === undefined) mode.relevance = 1; - if (!mode.contains) mode.contains = []; - - mode.contains = [].concat(...mode.contains.map(function(c) { - return expand_or_clone_mode(c === 'self' ? mode : c); - })); - mode.contains.forEach(function(c) { compileMode(/** @type Mode */ (c), cmode); }); - if (mode.starts) { - compileMode(mode.starts, parent); + return mm; } - cmode.matcher = buildModeRegex(cmode); - return cmode; - } - - // 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."); - } - return compileMode(/** @type Mode */ (language)); - } + // 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(); + } + } - /** - * Determines if a mode has a dependency on it's parent or not - * - * If a mode does have a parent dependency then often we need to clone it if - * it's used in multiple places so that each copy points to the correct parent, - * where-as modes without a parent can often safely be re-used at the bottom of - * a mode chain. - * - * @param {Mode | null} mode - * @returns {boolean} - is there a dependency on the parent? - * */ - function dependencyOnParent(mode) { - if (!mode) return false; + /** skip vs abort vs ignore + * + * @skip - The mode is still entered and exited normally (and contains rules apply), + * but all content is held and added to the parent buffer rather than being + * output when the mode ends. Mostly used with `sublanguage` to build up + * a single large buffer than can be parsed by sublanguage. + * + * - The mode begin ands ends normally. + * - Content matched is added to the parent mode buffer. + * - The parser cursor is moved forward normally. + * + * @abort - A hack placeholder until we have ignore. Aborts the mode (as if it + * never matched) but DOES NOT continue to match subsequent `contains` + * modes. Abort is bad/suboptimal because it can result in modes + * farther down not getting applied because an earlier rule eats the + * content but then aborts. + * + * - The mode does not begin. + * - Content matched by `begin` is added to the mode buffer. + * - The parser cursor is moved forward accordingly. + * + * @ignore - Ignores the mode (as if it never matched) and continues to match any + * subsequent `contains` modes. Ignore isn't technically possible with + * the current parser implementation. + * + * - The mode does not begin. + * - Content matched by `begin` is ignored. + * - The parser cursor is not moved forward. + */ - return mode.endsWithParent || dependencyOnParent(mode.starts); - } + /** + * Compiles an individual mode + * + * This can raise an error if the mode contains certain detectable known logic + * issues. + * @param {Mode} mode + * @param {CompiledMode | null} [parent] + * @returns {CompiledMode | never} + */ + function compileMode(mode, parent) { + const cmode = /** @type CompiledMode */ (mode); + if (mode.compiled) return cmode; + mode.compiled = true; - /** - * Expands a mode or clones it if necessary - * - * This is necessary for modes with parental dependenceis (see notes on - * `dependencyOnParent`) and for nodes that have `variants` - which must then be - * exploded into their own individual modes at compile time. - * - * @param {Mode} mode - * @returns {Mode | Mode[]} - * */ - function expand_or_clone_mode(mode) { - if (mode.variants && !mode.cached_variants) { - mode.cached_variants = mode.variants.map(function(variant) { - return inherit(mode, { variants: null }, variant); - }); - } + // __beforeBegin is considered private API, internal use only + mode.__beforeBegin = null; - // 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; - } + mode.keywords = mode.keywords || mode.beginKeywords; - // CLONE - // if we have dependencies on parents then we need a unique - // instance of ourselves, so we can be reused with many - // different parents without issue - if (dependencyOnParent(mode)) { - return inherit(mode, { starts: mode.starts ? inherit(mode.starts) : null }); - } + let keywordPattern = null; + if (typeof mode.keywords === "object") { + keywordPattern = mode.keywords.$pattern; + delete mode.keywords.$pattern; + } - if (Object.isFrozen(mode)) { - return inherit(mode); - } + if (mode.keywords) { + mode.keywords = compileKeywords(mode.keywords, language.case_insensitive); + } - // no special dependency issues, just return ourselves - return mode; - } + // both are not allowed + if (mode.lexemes && keywordPattern) { + throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) "); + } - /*********************************************** - Keywords - ***********************************************/ + // `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); + + 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; + } + } + if (mode.illegal) cmode.illegalRe = langRe(mode.illegal); + // eslint-disable-next-line no-undefined + if (mode.relevance === undefined) mode.relevance = 1; + if (!mode.contains) mode.contains = []; - /** - * Given raw keywords from a language definition, compile them. - * - * @param {string | Record} rawKeywords - * @param {boolean} case_insensitive - */ - function compileKeywords(rawKeywords, case_insensitive) { - /** @type KeywordDict */ - var compiled_keywords = {}; - - if (typeof rawKeywords === 'string') { // string - splitAndCompile('keyword', rawKeywords); - } else { - Object.keys(rawKeywords).forEach(function(className) { - splitAndCompile(className, rawKeywords[className]); - }); - } - return compiled_keywords; + mode.contains = [].concat(...mode.contains.map(function(c) { + return expandOrCloneMode(c === 'self' ? mode : c); + })); + mode.contains.forEach(function(c) { compileMode(/** @type Mode */ (c), cmode); }); - // --- + if (mode.starts) { + compileMode(mode.starts, parent); + } - /** - * Compiles an individual list of keywords - * - * Ex: "for if when while|5" - * - * @param {string} className - * @param {string} keywordList - */ - function splitAndCompile(className, keywordList) { - if (case_insensitive) { - keywordList = keywordList.toLowerCase(); + cmode.matcher = buildModeRegex(cmode); + return cmode; } - keywordList.split(' ').forEach(function(keyword) { - var pair = keyword.split('|'); - compiled_keywords[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.2.0"; - // @ts-nocheck + // 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."); + } - function hasValueOrEmptyAttribute(value) { - return Boolean(value || value === ""); - } + // we need a null object, which inherit will guarantee + language.classNameAliases = inherit(language.classNameAliases || {}); - const Component = { - props: ["language", "code", "autodetect"], - data: function() { - return { - detectedLanguage: "", - unknownLanguage: false - }; - }, - computed: { - className() { - if (this.unknownLanguage) return ""; - - return "hljs " + this.detectedLanguage; - }, - highlighted() { - // no idea what language to use, return raw code - if (!this.autoDetect && !hljs.getLanguage(this.language)) { - console.warn(`The language "${this.language}" you specified could not be found.`); - this.unknownLanguage = true; - return escapeHTML(this.code); - } - - let result; - if (this.autoDetect) { - result = hljs.highlightAuto(this.code); - this.detectedLanguage = result.language; - } else { - result = hljs.highlight(this.language, this.code, this.ignoreIllegals); - this.detectectLanguage = this.language; - } - return result.value; - }, - autoDetect() { - return !this.language || hasValueOrEmptyAttribute(this.autodetect); - }, - ignoreIllegals() { - return true; - } - }, - // this avoids needing to use a whole Vue compilation pipeline just - // to build Highlight.js - render(createElement) { - return createElement("pre", {}, [ - createElement("code", { - class: this.className, - domProps: { innerHTML: this.highlighted }}) - ]); + return compileMode(/** @type Mode */ (language)); } - // template: `
` - }; - const VuePlugin = { - install(Vue) { - Vue.component('highlightjs', Component); + /** + * Determines if a mode has a dependency on it's parent or not + * + * If a mode does have a parent dependency then often we need to clone it if + * it's used in multiple places so that each copy points to the correct parent, + * where-as modes without a parent can often safely be re-used at the bottom of + * a mode chain. + * + * @param {Mode | null} mode + * @returns {boolean} - is there a dependency on the parent? + * */ + function dependencyOnParent(mode) { + if (!mode) return false; + + return mode.endsWithParent || dependencyOnParent(mode.starts); } - }; - - /* - Syntax highlighting with language autodetection. - https://highlightjs.org/ - */ - const escape$1 = escapeHTML; - const inherit$1 = inherit; + /** + * Expands a mode or clones it if necessary + * + * This is necessary for modes with parental dependenceis (see notes on + * `dependencyOnParent`) and for nodes that have `variants` - which must then be + * exploded into their own individual modes at compile time. + * + * @param {Mode} mode + * @returns {Mode | Mode[]} + * */ + function expandOrCloneMode(mode) { + if (mode.variants && !mode.cached_variants) { + mode.cached_variants = mode.variants.map(function(variant) { + return inherit(mode, { variants: null }, variant); + }); + } - const { nodeStream: nodeStream$1, mergeStreams: mergeStreams$1 } = utils; - const NO_MATCH = Symbol("nomatch"); + // 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; + } - /** - * @param {any} hljs - object that is extended (legacy) - * @returns {HLJSApi} - */ - const HLJS = function(hljs) { - // Convenience variables for build-in objects - /** @type {unknown[]} */ - var ArrayProto = []; - - // Global internal variables used within the highlight.js library. - /** @type {Record} */ - var languages = Object.create(null); - /** @type {Record} */ - var aliases = Object.create(null); - /** @type {HLJSPlugin[]} */ - var plugins = []; - - // safe/production mode - swallows more errors, tries to keep running - // even if a single syntax or parse hits a fatal error - var SAFE_MODE = true; - var fixMarkupRe = /(^(<[^>]+>|\t|)+|\n)/gm; - var LANGUAGE_NOT_FOUND = "Could not find the language '{}', did you forget to load/include a language module?"; - /** @type {Language} */ - const PLAINTEXT_LANGUAGE = { disableAutodetect: true, name: 'Plain text', contains: [] }; - - // Global options used when within external APIs. This is modified when - // calling the `hljs.configure` function. - /** @type HLJSOptions */ - var options = { - noHighlightRe: /^(no-?highlight)$/i, - languageDetectRe: /\blang(?:uage)?-([\w-]+)\b/i, - classPrefix: 'hljs-', - tabReplace: null, - useBR: false, - languages: null, - // beta configuration options, subject to change, welcome to discuss - // https://github.com/highlightjs/highlight.js/issues/1086 - __emitter: TokenTreeEmitter - }; + // CLONE + // if we have dependencies on parents then we need a unique + // instance of ourselves, so we can be reused with many + // different parents without issue + if (dependencyOnParent(mode)) { + return inherit(mode, { starts: mode.starts ? inherit(mode.starts) : null }); + } - /* Utility functions */ + if (Object.isFrozen(mode)) { + return inherit(mode); + } - /** - * Tests a language name to see if highlighting should be skipped - * @param {string} languageName - */ - function shouldNotHighlight(languageName) { - return options.noHighlightRe.test(languageName); + // no special dependency issues, just return ourselves + return mode; } + /*********************************************** + Keywords + ***********************************************/ + /** - * @param {HighlightedHTMLElement} block - the HTML element to determine language for + * Given raw keywords from a language definition, compile them. + * + * @param {string | Record} rawKeywords + * @param {boolean} caseInsensitive */ - function blockLanguage(block) { - var classes = block.className + ' '; + 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; - classes += block.parentNode ? block.parentNode.className : ''; + // --- - // language-* takes precedence over non-prefixed class names. - const match = options.languageDetectRe.exec(classes); - if (match) { - var 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); + /** + * 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(); } - return language ? match[1] : 'no-highlight'; + keywordList.split(' ').forEach(function(keyword) { + const pair = keyword.split('|'); + compiledKeywords[pair[0]] = [className, scoreForKeyword(pair[0], pair[1])]; + }); } - - return classes - .split(/\s+/) - .find((_class) => shouldNotHighlight(_class) || getLanguage(_class)); } /** - * Core highlighting function. + * Returns the proper score for a given keyword * - * @param {string} languageName - the language to use for highlighting - * @param {string} code - the code to highlight - * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail - * @param {Mode} [continuation] - current continuation mode, if any - * - * @returns {HighlightResult} Result - an object that represents the result - * @property {string} language - the language name - * @property {number} relevance - the relevance score - * @property {string} value - the highlighted HTML code - * @property {string} code - the original raw code - * @property {Mode} top - top of the current mode stack - * @property {boolean} illegal - indicates whether any illegal matches were found - */ - function highlight(languageName, code, ignoreIllegals, continuation) { - /** @type {{ code: string, language: string, result?: any }} */ - var context = { - code, - language: languageName - }; - // the plugin can change the desired language or the code to be highlighted - // just be changing the object it was passed - fire("before:highlight", context); - - // a before plugin can usurp the result completely by providing it's own - // in which case we don't even need to call highlight - var result = context.result ? - context.result : - _highlight(context.language, context.code, ignoreIllegals, continuation); - - result.code = context.code; - // the plugin can change anything in result to suite it - fire("after:highlight", result); + * 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 result; + return commonKeyword(keyword) ? 0 : 1; } /** - * private highlight that's used internally and does not fire callbacks + * Determines if a given keyword is common or not * - * @param {string} languageName - the language to use for highlighting - * @param {string} code - the code to highlight - * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail - * @param {Mode} [continuation] - current continuation mode, if any - */ - function _highlight(languageName, code, ignoreIllegals, continuation) { - var codeToHighlight = code; - - /** - * Return keyword data if a match is a keyword - * @param {CompiledMode} mode - current mode - * @param {RegExpMatchArray} match - regexp match data - * @returns {KeywordData | false} - */ - function keywordData(mode, match) { - var matchText = language.case_insensitive ? match[0].toLowerCase() : match[0]; - return Object.prototype.hasOwnProperty.call(mode.keywords, matchText) && mode.keywords[matchText]; - } + * @param {string} keyword */ + function commonKeyword(keyword) { + return COMMON_KEYWORDS.includes(keyword.toLowerCase()); + } - function processKeywords() { - if (!top.keywords) { - emitter.addText(mode_buffer); - return; - } + var version = "10.4.0"; - let last_index = 0; - top.keywordPatternRe.lastIndex = 0; - let match = top.keywordPatternRe.exec(mode_buffer); - let buf = ""; + // @ts-nocheck - while (match) { - buf += mode_buffer.substring(last_index, match.index); - const data = keywordData(top, match); - if (data) { - const [kind, keywordRelevance] = data; - emitter.addText(buf); - buf = ""; + function hasValueOrEmptyAttribute(value) { + return Boolean(value || value === ""); + } - relevance += keywordRelevance; - emitter.addKeyword(match[0], kind); - } else { - buf += match[0]; + function BuildVuePlugin(hljs) { + const Component = { + props: ["language", "code", "autodetect"], + data: function() { + return { + detectedLanguage: "", + unknownLanguage: false + }; + }, + computed: { + className() { + if (this.unknownLanguage) return ""; + + return "hljs " + this.detectedLanguage; + }, + highlighted() { + // no idea what language to use, return raw code + if (!this.autoDetect && !hljs.getLanguage(this.language)) { + console.warn(`The language "${this.language}" you specified could not be found.`); + this.unknownLanguage = true; + return escapeHTML(this.code); + } + + let result; + if (this.autoDetect) { + result = hljs.highlightAuto(this.code); + this.detectedLanguage = result.language; + } else { + result = hljs.highlight(this.language, this.code, this.ignoreIllegals); + this.detectedLanguage = this.language; + } + return result.value; + }, + autoDetect() { + return !this.language || hasValueOrEmptyAttribute(this.autodetect); + }, + ignoreIllegals() { + return true; } - last_index = top.keywordPatternRe.lastIndex; - match = top.keywordPatternRe.exec(mode_buffer); + }, + // this avoids needing to use a whole Vue compilation pipeline just + // to build Highlight.js + render(createElement) { + return createElement("pre", {}, [ + createElement("code", { + class: this.className, + domProps: { innerHTML: this.highlighted }}) + ]); } - buf += mode_buffer.substr(last_index); - emitter.addText(buf); - } + // template: `
` + }; + + const VuePlugin = { + install(Vue) { + Vue.component('highlightjs', Component); + } + }; + + return { Component, VuePlugin }; + } - function processSubLanguage() { - if (mode_buffer === "") return; - /** @type HighlightResult */ - var result = null; + /* + Syntax highlighting with language autodetection. + https://highlightjs.org/ + */ - if (typeof top.subLanguage === 'string') { - if (!languages[top.subLanguage]) { - emitter.addText(mode_buffer); - return; - } - result = _highlight(top.subLanguage, mode_buffer, true, continuations[top.subLanguage]); - continuations[top.subLanguage] = result.top; - } else { - result = highlightAuto(mode_buffer, top.subLanguage.length ? top.subLanguage : null); - } + const escape$1 = escapeHTML; + const inherit$1 = inherit; - // Counting embedded language score towards the host language may be disabled - // with zeroing the containing mode relevance. Use case in point is Markdown that - // allows XML everywhere and makes every XML snippet to have a much larger Markdown - // score. - if (top.relevance > 0) { - relevance += result.relevance; - } - emitter.addSublanguage(result.emitter, result.language); - } + const { nodeStream: nodeStream$1, mergeStreams: mergeStreams$1 } = utils; + const NO_MATCH = Symbol("nomatch"); - function processBuffer() { - if (top.subLanguage != null) { - processSubLanguage(); - } else { - processKeywords(); - } - mode_buffer = ''; - } + /** + * @param {any} hljs - object that is extended (legacy) + * @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} */ + const languages = Object.create(null); + /** @type {Record} */ + const aliases = Object.create(null); + /** @type {HLJSPlugin[]} */ + const plugins = []; + + // safe/production mode - swallows more errors, tries to keep running + // even if a single syntax or parse hits a fatal error + let SAFE_MODE = true; + const fixMarkupRe = /(^(<[^>]+>|\t|)+|\n)/gm; + const LANGUAGE_NOT_FOUND = "Could not find the language '{}', did you forget to load/include a language module?"; + /** @type {Language} */ + const PLAINTEXT_LANGUAGE = { disableAutodetect: true, name: 'Plain text', contains: [] }; + + // Global options used when within external APIs. This is modified when + // calling the `hljs.configure` function. + /** @type HLJSOptions */ + let options = { + noHighlightRe: /^(no-?highlight)$/i, + languageDetectRe: /\blang(?:uage)?-([\w-]+)\b/i, + classPrefix: 'hljs-', + tabReplace: null, + useBR: false, + languages: null, + // beta configuration options, subject to change, welcome to discuss + // https://github.com/highlightjs/highlight.js/issues/1086 + __emitter: TokenTreeEmitter + }; + + /* Utility functions */ /** - * @param {Mode} mode - new mode to start + * Tests a language name to see if highlighting should be skipped + * @param {string} languageName */ - function startNewMode(mode) { - if (mode.className) { - emitter.openNode(mode.className); - } - top = Object.create(mode, { parent: { value: top } }); - return top; + function shouldNotHighlight(languageName) { + return options.noHighlightRe.test(languageName); } /** - * @param {CompiledMode } mode - the mode to potentially end - * @param {RegExpMatchArray} match - the latest match - * @param {string} matchPlusRemainder - match plus remainder of content - * @returns {CompiledMode | void} - the next mode, or if void continue on in current mode + * @param {HighlightedHTMLElement} block - the HTML element to determine language for */ - function endOfMode(mode, match, matchPlusRemainder) { - let matched = startsWith(mode.endRe, matchPlusRemainder); - - if (matched) { - if (mode["on:end"]) { - const resp = new Response(mode); - mode["on:end"](match, resp); - if (resp.ignore) matched = false; - } - - if (matched) { - while (mode.endsParent && mode.parent) { - mode = mode.parent; - } - return mode; + function blockLanguage(block) { + let classes = block.className + ' '; + + classes += block.parentNode ? block.parentNode.className : ''; + + // language-* takes precedence over non-prefixed class names. + const match = options.languageDetectRe.exec(classes); + 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); } + return language ? match[1] : 'no-highlight'; } - // even if on:end fires an `ignore` it's still possible - // that we might trigger the end node because of a parent mode - if (mode.endsWithParent) { - return endOfMode(mode.parent, match, matchPlusRemainder); - } + + return classes + .split(/\s+/) + .find((_class) => shouldNotHighlight(_class) || getLanguage(_class)); } /** - * Handle matching but then ignoring a sequence of text + * Core highlighting function. * - * @param {string} lexeme - string containing full match text - */ - function doIgnore(lexeme) { - if (top.matcher.regexIndex === 0) { - // no more regexs to potentially match here, so we move the cursor forward one - // space - mode_buffer += lexeme[0]; - return 1; - } else { - // no need to move the cursor, we still have additional regexes to try and - // match at this very spot - resumeScanAtSamePosition = true; - return 0; - } + * @param {string} languageName - the language to use for highlighting + * @param {string} code - the code to highlight + * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail + * @param {CompiledMode} [continuation] - current continuation mode, if any + * + * @returns {HighlightResult} Result - an object that represents the result + * @property {string} language - the language name + * @property {number} relevance - the relevance score + * @property {string} value - the highlighted HTML code + * @property {string} code - the original raw code + * @property {CompiledMode} top - top of the current mode stack + * @property {boolean} illegal - indicates whether any illegal matches were found + */ + function highlight(languageName, code, ignoreIllegals, continuation) { + /** @type {{ code: string, language: string, result?: any }} */ + const context = { + code, + language: languageName + }; + // the plugin can change the desired language or the code to be highlighted + // just be changing the object it was passed + fire("before:highlight", context); + + // a before plugin can usurp the result completely by providing it's own + // in which case we don't even need to call highlight + const result = context.result ? + context.result : + _highlight(context.language, context.code, ignoreIllegals, continuation); + + result.code = context.code; + // the plugin can change anything in result to suite it + fire("after:highlight", result); + + return result; } /** - * Handle the start of a new potential mode match + * private highlight that's used internally and does not fire callbacks * - * @param {EnhancedMatch} match - the current match - * @returns {number} how far to advance the parse cursor - */ - function doBeginMatch(match) { - var lexeme = match[0]; - var new_mode = match.rule; + * @param {string} languageName - the language to use for highlighting + * @param {string} code - the code to highlight + * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail + * @param {CompiledMode} [continuation] - current continuation mode, if any + * @returns {HighlightResult} - result of the highlight operation + */ + function _highlight(languageName, code, ignoreIllegals, continuation) { + const codeToHighlight = code; + + /** + * Return keyword data if a match is a keyword + * @param {CompiledMode} mode - current mode + * @param {RegExpMatchArray} match - regexp match data + * @returns {KeywordData | false} + */ + function keywordData(mode, match) { + const matchText = language.case_insensitive ? match[0].toLowerCase() : match[0]; + return Object.prototype.hasOwnProperty.call(mode.keywords, matchText) && mode.keywords[matchText]; + } + + function processKeywords() { + if (!top.keywords) { + emitter.addText(modeBuffer); + return; + } - const resp = new Response(new_mode); - // first internal before callbacks, then the public ones - const beforeCallbacks = [new_mode.__beforeBegin, new_mode["on:begin"]]; - for (const cb of beforeCallbacks) { - if (!cb) continue; - cb(match, resp); - if (resp.ignore) return doIgnore(lexeme); + let lastIndex = 0; + top.keywordPatternRe.lastIndex = 0; + let match = top.keywordPatternRe.exec(modeBuffer); + let buf = ""; + + while (match) { + buf += modeBuffer.substring(lastIndex, match.index); + const data = keywordData(top, match); + if (data) { + const [kind, keywordRelevance] = data; + emitter.addText(buf); + buf = ""; + + relevance += keywordRelevance; + const cssClass = language.classNameAliases[kind] || kind; + emitter.addKeyword(match[0], cssClass); + } else { + buf += match[0]; + } + lastIndex = top.keywordPatternRe.lastIndex; + match = top.keywordPatternRe.exec(modeBuffer); + } + buf += modeBuffer.substr(lastIndex); + emitter.addText(buf); } - if (new_mode && new_mode.endSameAsBegin) { - new_mode.endRe = escape(lexeme); + function processSubLanguage() { + if (modeBuffer === "") return; + /** @type HighlightResult */ + let result = null; + + if (typeof top.subLanguage === 'string') { + if (!languages[top.subLanguage]) { + emitter.addText(modeBuffer); + return; + } + result = _highlight(top.subLanguage, modeBuffer, true, continuations[top.subLanguage]); + continuations[top.subLanguage] = /** @type {CompiledMode} */ (result.top); + } else { + result = highlightAuto(modeBuffer, top.subLanguage.length ? top.subLanguage : null); + } + + // Counting embedded language score towards the host language may be disabled + // with zeroing the containing mode relevance. Use case in point is Markdown that + // allows XML everywhere and makes every XML snippet to have a much larger Markdown + // score. + if (top.relevance > 0) { + relevance += result.relevance; + } + emitter.addSublanguage(result.emitter, result.language); } - if (new_mode.skip) { - mode_buffer += lexeme; - } else { - if (new_mode.excludeBegin) { - mode_buffer += lexeme; + function processBuffer() { + if (top.subLanguage != null) { + processSubLanguage(); + } else { + processKeywords(); } - processBuffer(); - if (!new_mode.returnBegin && !new_mode.excludeBegin) { - mode_buffer = lexeme; + modeBuffer = ''; + } + + /** + * @param {Mode} mode - new mode to start + */ + function startNewMode(mode) { + if (mode.className) { + emitter.openNode(language.classNameAliases[mode.className] || mode.className); } + top = Object.create(mode, { parent: { value: top } }); + return top; } - startNewMode(new_mode); - // if (mode["after:begin"]) { - // let resp = new Response(mode); - // mode["after:begin"](match, resp); - // } - return new_mode.returnBegin ? 0 : lexeme.length; - } - /** - * Handle the potential end of mode - * - * @param {RegExpMatchArray} match - the current match - */ - function doEndMatch(match) { - var lexeme = match[0]; - var matchPlusRemainder = codeToHighlight.substr(match.index); + /** + * @param {CompiledMode } mode - the mode to potentially end + * @param {RegExpMatchArray} match - the latest match + * @param {string} matchPlusRemainder - match plus remainder of content + * @returns {CompiledMode | void} - the next mode, or if void continue on in current mode + */ + function endOfMode(mode, match, matchPlusRemainder) { + let matched = startsWith(mode.endRe, matchPlusRemainder); - var end_mode = endOfMode(top, match, matchPlusRemainder); - if (!end_mode) { return NO_MATCH; } + if (matched) { + if (mode["on:end"]) { + const resp = new Response(mode); + mode["on:end"](match, resp); + if (resp.ignore) matched = false; + } - var origin = top; - if (origin.skip) { - mode_buffer += lexeme; - } else { - if (!(origin.returnEnd || origin.excludeEnd)) { - mode_buffer += lexeme; + if (matched) { + while (mode.endsParent && mode.parent) { + mode = mode.parent; + } + return mode; + } + } + // even if on:end fires an `ignore` it's still possible + // that we might trigger the end node because of a parent mode + if (mode.endsWithParent) { + return endOfMode(mode.parent, match, matchPlusRemainder); } - processBuffer(); - if (origin.excludeEnd) { - mode_buffer = lexeme; + } + + /** + * Handle matching but then ignoring a sequence of text + * + * @param {string} lexeme - string containing full match text + */ + function doIgnore(lexeme) { + if (top.matcher.regexIndex === 0) { + // no more regexs to potentially match here, so we move the cursor forward one + // space + modeBuffer += lexeme[0]; + return 1; + } else { + // no need to move the cursor, we still have additional regexes to try and + // match at this very spot + resumeScanAtSamePosition = true; + return 0; } } - do { - if (top.className) { - emitter.closeNode(); + + /** + * Handle the start of a new potential mode match + * + * @param {EnhancedMatch} match - the current match + * @returns {number} how far to advance the parse cursor + */ + function doBeginMatch(match) { + const lexeme = match[0]; + const newMode = match.rule; + + const resp = new Response(newMode); + // first internal before callbacks, then the public ones + const beforeCallbacks = [newMode.__beforeBegin, newMode["on:begin"]]; + for (const cb of beforeCallbacks) { + if (!cb) continue; + cb(match, resp); + if (resp.ignore) return doIgnore(lexeme); } - if (!top.skip && !top.subLanguage) { - relevance += top.relevance; + + if (newMode && newMode.endSameAsBegin) { + newMode.endRe = escape(lexeme); } - top = top.parent; - } while (top !== end_mode.parent); - if (end_mode.starts) { - if (end_mode.endSameAsBegin) { - end_mode.starts.endRe = end_mode.endRe; + + if (newMode.skip) { + modeBuffer += lexeme; + } else { + if (newMode.excludeBegin) { + modeBuffer += lexeme; + } + processBuffer(); + if (!newMode.returnBegin && !newMode.excludeBegin) { + modeBuffer = lexeme; + } } - startNewMode(end_mode.starts); + startNewMode(newMode); + // if (mode["after:begin"]) { + // let resp = new Response(mode); + // mode["after:begin"](match, resp); + // } + return newMode.returnBegin ? 0 : lexeme.length; } - return origin.returnEnd ? 0 : lexeme.length; - } - function processContinuations() { - var list = []; - for (var current = top; current !== language; current = current.parent) { - if (current.className) { - list.unshift(current.className); + /** + * Handle the potential end of mode + * + * @param {RegExpMatchArray} match - the current match + */ + function doEndMatch(match) { + const lexeme = match[0]; + const matchPlusRemainder = codeToHighlight.substr(match.index); + + const endMode = endOfMode(top, match, matchPlusRemainder); + if (!endMode) { return NO_MATCH; } + + const origin = top; + if (origin.skip) { + modeBuffer += lexeme; + } else { + if (!(origin.returnEnd || origin.excludeEnd)) { + modeBuffer += lexeme; + } + processBuffer(); + if (origin.excludeEnd) { + modeBuffer = lexeme; + } + } + do { + if (top.className) { + emitter.closeNode(); + } + if (!top.skip && !top.subLanguage) { + relevance += top.relevance; + } + top = top.parent; + } while (top !== endMode.parent); + if (endMode.starts) { + if (endMode.endSameAsBegin) { + endMode.starts.endRe = endMode.endRe; + } + startNewMode(endMode.starts); } + return origin.returnEnd ? 0 : lexeme.length; } - list.forEach(item => emitter.openNode(item)); - } - /** @type {{type?: MatchType, index?: number, rule?: Mode}}} */ - var lastMatch = {}; + function processContinuations() { + const list = []; + for (let current = top; current !== language; current = current.parent) { + if (current.className) { + list.unshift(current.className); + } + } + list.forEach(item => emitter.openNode(item)); + } - /** - * Process an individual match - * - * @param {string} textBeforeMatch - text preceeding the match (since the last match) - * @param {EnhancedMatch} [match] - the match itself - */ - function processLexeme(textBeforeMatch, match) { - var lexeme = match && match[0]; + /** @type {{type?: MatchType, index?: number, rule?: Mode}}} */ + let lastMatch = {}; - // add non-matched text to the current mode buffer - mode_buffer += textBeforeMatch; + /** + * Process an individual match + * + * @param {string} textBeforeMatch - text preceeding the match (since the last match) + * @param {EnhancedMatch} [match] - the match itself + */ + function processLexeme(textBeforeMatch, match) { + const lexeme = match && match[0]; - if (lexeme == null) { - processBuffer(); - return 0; - } + // add non-matched text to the current mode buffer + modeBuffer += textBeforeMatch; - // we've found a 0 width match and we're stuck, so we need to advance - // this happens when we have badly behaved rules that have optional matchers to the degree that - // sometimes they can end up matching nothing at all - // Ref: https://github.com/highlightjs/highlight.js/issues/2140 - if (lastMatch.type === "begin" && match.type === "end" && lastMatch.index === match.index && lexeme === "") { - // spit the "skipped" character that our regex choked on back into the output sequence - mode_buffer += codeToHighlight.slice(match.index, match.index + 1); - if (!SAFE_MODE) { + if (lexeme == null) { + processBuffer(); + return 0; + } + + // we've found a 0 width match and we're stuck, so we need to advance + // this happens when we have badly behaved rules that have optional matchers to the degree that + // sometimes they can end up matching nothing at all + // Ref: https://github.com/highlightjs/highlight.js/issues/2140 + if (lastMatch.type === "begin" && match.type === "end" && lastMatch.index === match.index && lexeme === "") { + // spit the "skipped" character that our regex choked on back into the output sequence + modeBuffer += codeToHighlight.slice(match.index, match.index + 1); + if (!SAFE_MODE) { + /** @type {AnnotatedError} */ + const err = new Error('0 width match regex'); + err.languageName = languageName; + err.badRule = lastMatch.rule; + throw err; + } + return 1; + } + lastMatch = match; + + if (match.type === "begin") { + return doBeginMatch(match); + } else if (match.type === "illegal" && !ignoreIllegals) { + // illegal match, we do not continue processing /** @type {AnnotatedError} */ - const err = new Error('0 width match regex'); - err.languageName = languageName; - err.badRule = lastMatch.rule; + const err = new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.className || '') + '"'); + err.mode = top; throw err; + } else if (match.type === "end") { + const processed = doEndMatch(match); + if (processed !== NO_MATCH) { + return processed; + } } - return 1; - } - lastMatch = match; - - if (match.type === "begin") { - return doBeginMatch(match); - } else if (match.type === "illegal" && !ignoreIllegals) { - // illegal match, we do not continue processing - /** @type {AnnotatedError} */ - const err = new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.className || '') + '"'); - err.mode = top; - throw err; - } else if (match.type === "end") { - var processed = doEndMatch(match); - if (processed !== NO_MATCH) { - return processed; + + // edge case for when illegal matches $ (end of line) which is technically + // a 0 width match but not a begin/end match so it's not caught by the + // first handler (when ignoreIllegals is true) + if (match.type === "illegal" && lexeme === "") { + // advance so we aren't stuck in an infinite loop + return 1; } - } - // edge case for when illegal matches $ (end of line) which is technically - // a 0 width match but not a begin/end match so it's not caught by the - // first handler (when ignoreIllegals is true) - if (match.type === "illegal" && lexeme === "") { - // advance so we aren't stuck in an infinite loop - return 1; - } + // infinite loops are BAD, this is a last ditch catch all. if we have a + // decent number of iterations yet our index (cursor position in our + // parsing) still 3x behind our index then something is very wrong + // so we bail + if (iterations > 100000 && iterations > match.index * 3) { + const err = new Error('potential infinite loop, way more iterations than matches'); + throw err; + } + + /* + Why might be find ourselves here? Only one occasion now. An end match that was + triggered but could not be completed. When might this happen? When an `endSameasBegin` + rule sets the end rule to a specific match. Since the overall mode termination rule that's + being used to scan the text isn't recompiled that means that any match that LOOKS like + the end (but is not, because it is not an exact match to the beginning) will + end up here. A definite end match, but when `doEndMatch` tries to "reapply" + the end rule and fails to match, we wind up here, and just silently ignore the end. + + This causes no real harm other than stopping a few times too many. + */ - // infinite loops are BAD, this is a last ditch catch all. if we have a - // decent number of iterations yet our index (cursor position in our - // parsing) still 3x behind our index then something is very wrong - // so we bail - if (iterations > 100000 && iterations > match.index * 3) { - const err = new Error('potential infinite loop, way more iterations than matches'); - throw err; + modeBuffer += lexeme; + return lexeme.length; } - /* - Why might be find ourselves here? Only one occasion now. An end match that was - triggered but could not be completed. When might this happen? When an `endSameasBegin` - rule sets the end rule to a specific match. Since the overall mode termination rule that's - being used to scan the text isn't recompiled that means that any match that LOOKS like - the end (but is not, because it is not an exact match to the beginning) will - end up here. A definite end match, but when `doEndMatch` tries to "reapply" - the end rule and fails to match, we wind up here, and just silently ignore the end. - - This causes no real harm other than stopping a few times too many. - */ + const language = getLanguage(languageName); + if (!language) { + console.error(LANGUAGE_NOT_FOUND.replace("{}", languageName)); + throw new Error('Unknown language: "' + languageName + '"'); + } - mode_buffer += lexeme; - return lexeme.length; - } - - var language = getLanguage(languageName); - if (!language) { - console.error(LANGUAGE_NOT_FOUND.replace("{}", languageName)); - throw new Error('Unknown language: "' + languageName + '"'); - } - - var md = compileLanguage(language); - var result = ''; - /** @type {CompiledMode} */ - var top = continuation || md; - /** @type Record */ - var continuations = {}; // keep continuations for sub-languages - var emitter = new options.__emitter(options); - processContinuations(); - var mode_buffer = ''; - var relevance = 0; - var index = 0; - var iterations = 0; - var resumeScanAtSamePosition = false; - - try { - top.matcher.considerAll(); - - for (;;) { - iterations++; - if (resumeScanAtSamePosition) { - // only regexes not matched previously will now be - // considered for a potential match - resumeScanAtSamePosition = false; - } else { - top.matcher.considerAll(); - } - top.matcher.lastIndex = index; + const md = compileLanguage(language); + let result = ''; + /** @type {CompiledMode} */ + let top = continuation || md; + /** @type Record */ + const continuations = {}; // keep continuations for sub-languages + const emitter = new options.__emitter(options); + processContinuations(); + let modeBuffer = ''; + let relevance = 0; + let index = 0; + let iterations = 0; + let resumeScanAtSamePosition = false; + + try { + top.matcher.considerAll(); + + for (;;) { + iterations++; + if (resumeScanAtSamePosition) { + // only regexes not matched previously will now be + // considered for a potential match + resumeScanAtSamePosition = false; + } else { + top.matcher.considerAll(); + } + top.matcher.lastIndex = index; - const match = top.matcher.exec(codeToHighlight); - // console.log("match", match[0], match.rule && match.rule.begin) + const match = top.matcher.exec(codeToHighlight); + // console.log("match", match[0], match.rule && match.rule.begin) - if (!match) break; + if (!match) break; - const beforeMatch = codeToHighlight.substring(index, match.index); - const processedCount = processLexeme(beforeMatch, match); - index = match.index + processedCount; - } - processLexeme(codeToHighlight.substr(index)); - emitter.closeAllNodes(); - emitter.finalize(); - result = emitter.toHTML(); + const beforeMatch = codeToHighlight.substring(index, match.index); + const processedCount = processLexeme(beforeMatch, match); + index = match.index + processedCount; + } + processLexeme(codeToHighlight.substr(index)); + emitter.closeAllNodes(); + emitter.finalize(); + result = emitter.toHTML(); - return { - relevance: relevance, - value: result, - language: languageName, - illegal: false, - emitter: emitter, - top: top - }; - } catch (err) { - if (err.message && err.message.includes('Illegal')) { - return { - illegal: true, - illegalBy: { - msg: err.message, - context: codeToHighlight.slice(index - 100, index + 100), - mode: err.mode - }, - sofar: result, - relevance: 0, - value: escape$1(codeToHighlight), - emitter: emitter - }; - } else if (SAFE_MODE) { return { + relevance: relevance, + value: result, + language: languageName, illegal: false, - relevance: 0, - value: escape$1(codeToHighlight), emitter: emitter, - language: languageName, - top: top, - errorRaised: err + top: top }; - } else { - throw err; + } catch (err) { + if (err.message && err.message.includes('Illegal')) { + return { + illegal: true, + illegalBy: { + msg: err.message, + context: codeToHighlight.slice(index - 100, index + 100), + mode: err.mode + }, + sofar: result, + relevance: 0, + value: escape$1(codeToHighlight), + emitter: emitter + }; + } else if (SAFE_MODE) { + return { + illegal: false, + relevance: 0, + value: escape$1(codeToHighlight), + emitter: emitter, + language: languageName, + top: top, + errorRaised: err + }; + } else { + throw err; + } } } - } - /** - * returns a valid highlight result, without actually doing any actual work, - * auto highlight starts with this and it's possible for small snippets that - * auto-detection may not find a better match - * @param {string} code - * @returns {HighlightResult} - */ - function justTextHighlightResult(code) { - const result = { - relevance: 0, - emitter: new options.__emitter(options), - value: escape$1(code), - illegal: false, - top: PLAINTEXT_LANGUAGE - }; - result.emitter.addText(code); - return result; - } + /** + * returns a valid highlight result, without actually doing any actual work, + * auto highlight starts with this and it's possible for small snippets that + * auto-detection may not find a better match + * @param {string} code + * @returns {HighlightResult} + */ + function justTextHighlightResult(code) { + const result = { + relevance: 0, + emitter: new options.__emitter(options), + value: escape$1(code), + illegal: false, + top: PLAINTEXT_LANGUAGE + }; + result.emitter.addText(code); + return result; + } - /** - Highlighting with language detection. Accepts a string with the code to - highlight. Returns an object with the following properties: - - - language (detected language) - - relevance (int) - - value (an HTML string with highlighting markup) - - second_best (object with the same structure for second-best heuristically - detected language, may be absent) - - @param {string} code - @param {Array} [languageSubset] - @returns {AutoHighlightResult} - */ - function highlightAuto(code, languageSubset) { - languageSubset = languageSubset || options.languages || Object.keys(languages); - var result = justTextHighlightResult(code); - var secondBest = result; - languageSubset.filter(getLanguage).filter(autoDetection).forEach(function(name) { - var current = _highlight(name, code, false); - current.language = name; - if (current.relevance > secondBest.relevance) { - secondBest = current; - } - if (current.relevance > result.relevance) { - secondBest = result; - result = current; - } - }); - if (secondBest.language) { - // second_best (with underscore) is the expected API + /** + Highlighting with language detection. Accepts a string with the code to + highlight. Returns an object with the following properties: + + - language (detected language) + - relevance (int) + - value (an HTML string with highlighting markup) + - second_best (object with the same structure for second-best heuristically + detected language, may be absent) + + @param {string} code + @param {Array} [languageSubset] + @returns {AutoHighlightResult} + */ + function highlightAuto(code, languageSubset) { + languageSubset = languageSubset || options.languages || Object.keys(languages); + const plaintext = justTextHighlightResult(code); + + const results = languageSubset.filter(getLanguage).filter(autoDetection).map(name => + _highlight(name, code, false) + ); + results.unshift(plaintext); // plaintext is always an option + + const sorted = results.sort((a, b) => { + // sort base on relevance + if (a.relevance !== b.relevance) return b.relevance - a.relevance; + + // always award the tie to the base language + // ie if C++ and Arduino are tied, it's more likely to be C++ + if (a.language && b.language) { + if (getLanguage(a.language).supersetOf === b.language) { + return 1; + } else if (getLanguage(b.language).supersetOf === a.language) { + return -1; + } + } + + // otherwise say they are equal, which has the effect of sorting on + // relevance while preserving the original ordering - which is how ties + // have historically been settled, ie the language that comes first always + // wins in the case of a tie + return 0; + }); + + const [best, secondBest] = sorted; + + /** @type {AutoHighlightResult} */ + const result = best; result.second_best = secondBest; + + return result; } - return result; - } - /** - Post-processing of the highlighted markup: + /** + Post-processing of the highlighted markup: - - replace TABs with something more useful - - replace real line-breaks with '
' for non-pre containers + - replace TABs with something more useful + - replace real line-breaks with '
' for non-pre containers - @param {string} html - @returns {string} - */ - function fixMarkup(html) { - if (!(options.tabReplace || options.useBR)) { - return html; + @param {string} html + @returns {string} + */ + function fixMarkup(html) { + if (!(options.tabReplace || options.useBR)) { + return html; + } + + return html.replace(fixMarkupRe, match => { + if (match === '\n') { + return options.useBR ? '
' : match; + } else if (options.tabReplace) { + return match.replace(/\t/g, options.tabReplace); + } + return match; + }); } - return html.replace(fixMarkupRe, match => { - if (match === '\n') { - return options.useBR ? '
' : match; - } else if (options.tabReplace) { - return match.replace(/\t/g, options.tabReplace); + /** + * Builds new class name for block given the language name + * + * @param {string} prevClassName + * @param {string} [currentLang] + * @param {string} [resultLang] + */ + function buildClassName(prevClassName, currentLang, resultLang) { + const language = currentLang ? aliases[currentLang] : resultLang; + const result = [prevClassName.trim()]; + + if (!prevClassName.match(/\bhljs\b/)) { + result.push('hljs'); } - return match; - }); - } - /** - * Builds new class name for block given the language name - * - * @param {string} prevClassName - * @param {string} [currentLang] - * @param {string} [resultLang] - */ - function buildClassName(prevClassName, currentLang, resultLang) { - var language = currentLang ? aliases[currentLang] : resultLang; - var result = [prevClassName.trim()]; + if (!prevClassName.includes(language)) { + result.push(language); + } - if (!prevClassName.match(/\bhljs\b/)) { - result.push('hljs'); + return result.join(' ').trim(); } - if (!prevClassName.includes(language)) { - result.push(language); - } + /** + * Applies highlighting to a DOM node containing code. Accepts a DOM node and + * two optional parameters for fixMarkup. + * + * @param {HighlightedHTMLElement} element - the HTML element to highlight + */ + function highlightBlock(element) { + /** @type HTMLElement */ + let node = null; + const language = blockLanguage(element); - return result.join(' ').trim(); - } + if (shouldNotHighlight(language)) return; - /** - * Applies highlighting to a DOM node containing code. Accepts a DOM node and - * two optional parameters for fixMarkup. - * - * @param {HighlightedHTMLElement} element - the HTML element to highlight - */ - function highlightBlock(element) { - /** @type HTMLElement */ - let node = null; - const language = blockLanguage(element); + fire("before:highlightBlock", + { block: element, language: language }); - if (shouldNotHighlight(language)) return; + if (options.useBR) { + node = document.createElement('div'); + node.innerHTML = element.innerHTML.replace(/\n/g, '').replace(//g, '\n'); + } else { + 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("before:highlightBlock", - { block: element, language: language }); + fire("after:highlightBlock", { block: element, result: result }); - if (options.useBR) { - node = document.createElement('div'); - node.innerHTML = element.innerHTML.replace(/\n/g, '').replace(//g, '\n'); - } else { - node = element; + element.innerHTML = result.value; + element.className = buildClassName(element.className, language, result.language); + element.result = { + language: result.language, + // TODO: remove with version 11.0 + re: result.relevance, + relavance: result.relevance + }; + if (result.second_best) { + element.second_best = { + language: result.second_best.language, + // TODO: remove with version 11.0 + re: result.second_best.relevance, + relavance: result.second_best.relevance + }; + } } - 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); + /** + * Updates highlight.js global options with the passed options + * + * @param {Partial} userOptions + */ + 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"); + } + options = inherit$1(options, userOptions); } - result.value = fixMarkup(result.value); - fire("after:highlightBlock", { block: element, result: result }); + /** + * Highlights to all
 blocks on a page
+       *
+       * @type {Function & {called?: boolean}}
+       */
+      const initHighlighting = () => {
+        if (initHighlighting.called) return;
+        initHighlighting.called = true;
 
-      element.innerHTML = result.value;
-      element.className = buildClassName(element.className, language, result.language);
-      element.result = {
-        language: result.language,
-        // TODO: remove with version 11.0
-        re: result.relevance,
-        relavance: result.relevance
+        const blocks = document.querySelectorAll('pre code');
+        ArrayProto.forEach.call(blocks, highlightBlock);
       };
-      if (result.second_best) {
-        element.second_best = {
-          language: result.second_best.language,
-          // TODO: remove with version 11.0
-          re: result.second_best.relevance,
-          relavance: result.second_best.relevance
-        };
-      }
-    }
-
-    /**
-     * Updates highlight.js global options with the passed options
-     *
-     * @param {{}} userOptions
-     */
-    function configure(userOptions) {
-      options = inherit$1(options, userOptions);
-    }
 
-    /**
-     * Highlights to all 
 blocks on a page
-     *
-     * @type {Function & {called?: boolean}}
-     */
-    const initHighlighting = () => {
-      if (initHighlighting.called) return;
-      initHighlighting.called = true;
+      // Higlights all when DOMContentLoaded fires
+      function initHighlightingOnLoad() {
+        // @ts-ignore
+        window.addEventListener('DOMContentLoaded', initHighlighting, false);
+      }
 
-      var blocks = document.querySelectorAll('pre code');
-      ArrayProto.forEach.call(blocks, highlightBlock);
-    };
+      /**
+       * Register a language grammar module
+       *
+       * @param {string} languageName
+       * @param {LanguageFn} languageDefinition
+       */
+      function registerLanguage(languageName, languageDefinition) {
+        let lang = null;
+        try {
+          lang = languageDefinition(hljs);
+        } catch (error) {
+          console.error("Language definition for '{}' could not be registered.".replace("{}", languageName));
+          // hard or soft error
+          if (!SAFE_MODE) { throw error; } else { console.error(error); }
+          // 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
+          // entire highlighter
+          lang = PLAINTEXT_LANGUAGE;
+        }
+        // give it a temporary name if it doesn't have one in the meta-data
+        if (!lang.name) lang.name = languageName;
+        languages[languageName] = lang;
+        lang.rawDefinition = languageDefinition.bind(null, hljs);
 
-    // Higlights all when DOMContentLoaded fires
-    function initHighlightingOnLoad() {
-      // @ts-ignore
-      window.addEventListener('DOMContentLoaded', initHighlighting, false);
-    }
+        if (lang.aliases) {
+          registerAliases(lang.aliases, { languageName });
+        }
+      }
 
-    /**
-     * Register a language grammar module
-     *
-     * @param {string} languageName
-     * @param {LanguageFn} languageDefinition
-     */
-    function registerLanguage(languageName, languageDefinition) {
-      var lang = null;
-      try {
-        lang = languageDefinition(hljs);
-      } catch (error) {
-        console.error("Language definition for '{}' could not be registered.".replace("{}", languageName));
-        // hard or soft error
-        if (!SAFE_MODE) { throw error; } else { console.error(error); }
-        // 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
-        // entire highlighter
-        lang = PLAINTEXT_LANGUAGE;
-      }
-      // give it a temporary name if it doesn't have one in the meta-data
-      if (!lang.name) lang.name = languageName;
-      languages[languageName] = lang;
-      lang.rawDefinition = languageDefinition.bind(null, hljs);
-
-      if (lang.aliases) {
-        registerAliases(lang.aliases, { languageName });
+      /**
+       * @returns {string[]} List of language internal names
+       */
+      function listLanguages() {
+        return Object.keys(languages);
       }
-    }
 
-    /**
-     * @returns {string[]} List of language internal names
-     */
-    function listLanguages() {
-      return Object.keys(languages);
-    }
+      /**
+        intended usage: When one language truly requires another
 
-    /**
-      intended usage: When one language truly requires another
+        Unlike `getLanguage`, this will throw when the requested language
+        is not available.
 
-      Unlike `getLanguage`, this will throw when the requested language
-      is not available.
+        @param {string} name - name of the language to fetch/require
+        @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");
 
-      @param {string} name - name of the language to fetch/require
-      @returns {Language | never}
-    */
-    function requireLanguage(name) {
-      var lang = getLanguage(name);
-      if (lang) { return lang; }
+        const lang = getLanguage(name);
+        if (lang) { return lang; }
 
-      var err = new Error('The \'{}\' language is required, but not loaded.'.replace('{}', name));
-      throw err;
-    }
+        const err = new Error('The \'{}\' language is required, but not loaded.'.replace('{}', name));
+        throw err;
+      }
 
-    /**
-     * @param {string} name - name of the language to retrieve
-     * @returns {Language | undefined}
-     */
-    function getLanguage(name) {
-      name = (name || '').toLowerCase();
-      return languages[name] || languages[aliases[name]];
-    }
+      /**
+       * @param {string} name - name of the language to retrieve
+       * @returns {Language | undefined}
+       */
+      function getLanguage(name) {
+        name = (name || '').toLowerCase();
+        return languages[name] || languages[aliases[name]];
+      }
 
-    /**
-     *
-     * @param {string|string[]} aliasList - single alias or list of aliases
-     * @param {{languageName: string}} opts
-     */
-    function registerAliases(aliasList, { languageName }) {
-      if (typeof aliasList === 'string') {
-        aliasList = [aliasList];
+      /**
+       *
+       * @param {string|string[]} aliasList - single alias or list of aliases
+       * @param {{languageName: string}} opts
+       */
+      function registerAliases(aliasList, { languageName }) {
+        if (typeof aliasList === 'string') {
+          aliasList = [aliasList];
+        }
+        aliasList.forEach(alias => { aliases[alias] = languageName; });
       }
-      aliasList.forEach(alias => { aliases[alias] = languageName; });
-    }
 
-    /**
-     * Determines if a given language has auto-detection enabled
-     * @param {string} name - name of the language
-     */
-    function autoDetection(name) {
-      var lang = getLanguage(name);
-      return lang && !lang.disableAutodetect;
-    }
+      /**
+       * Determines if a given language has auto-detection enabled
+       * @param {string} name - name of the language
+       */
+      function autoDetection(name) {
+        const lang = getLanguage(name);
+        return lang && !lang.disableAutodetect;
+      }
 
-    /**
-     * @param {HLJSPlugin} plugin
-     */
-    function addPlugin(plugin) {
-      plugins.push(plugin);
-    }
+      /**
+       * @param {HLJSPlugin} plugin
+       */
+      function addPlugin(plugin) {
+        plugins.push(plugin);
+      }
 
-    /**
-     *
-     * @param {PluginEvent} event
-     * @param {any} args
-     */
-    function fire(event, args) {
-      var cb = event;
-      plugins.forEach(function(plugin) {
-        if (plugin[cb]) {
-          plugin[cb](args);
-        }
-      });
-    }
+      /**
+       *
+       * @param {PluginEvent} event
+       * @param {any} args
+       */
+      function fire(event, args) {
+        const cb = event;
+        plugins.forEach(function(plugin) {
+          if (plugin[cb]) {
+            plugin[cb](args);
+          }
+        });
+      }
 
-    /* fixMarkup is deprecated and will be removed entirely in v11 */
-    function deprecate_fixMarkup(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");
+      /**
+      Note: fixMarkup is deprecated and will be removed entirely in v11
 
-      return fixMarkup(arg)
-    }
+      @param {string} arg
+      @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");
 
-    /* Interface definition */
-    Object.assign(hljs, {
-      highlight,
-      highlightAuto,
-      fixMarkup: deprecate_fixMarkup,
-      highlightBlock,
-      configure,
-      initHighlighting,
-      initHighlightingOnLoad,
-      registerLanguage,
-      listLanguages,
-      getLanguage,
-      registerAliases,
-      requireLanguage,
-      autoDetection,
-      inherit: inherit$1,
-      addPlugin,
-      // plugins for frameworks
-      vuePlugin: VuePlugin
-    });
+        return fixMarkup(arg);
+      }
 
-    hljs.debugMode = function() { SAFE_MODE = false; };
-    hljs.safeMode = function() { SAFE_MODE = true; };
-    hljs.versionString = version;
+      /* Interface definition */
+      Object.assign(hljs, {
+        highlight,
+        highlightAuto,
+        fixMarkup: deprecateFixMarkup,
+        highlightBlock,
+        configure,
+        initHighlighting,
+        initHighlightingOnLoad,
+        registerLanguage,
+        listLanguages,
+        getLanguage,
+        registerAliases,
+        requireLanguage,
+        autoDetection,
+        inherit: inherit$1,
+        addPlugin,
+        // plugins for frameworks
+        vuePlugin: BuildVuePlugin(hljs).VuePlugin
+      });
+
+      hljs.debugMode = function() { SAFE_MODE = false; };
+      hljs.safeMode = function() { SAFE_MODE = true; };
+      hljs.versionString = version;
 
-    for (const key in MODES) {
-      // @ts-ignore
-      if (typeof MODES[key] === "object") {
+      for (const key in MODES) {
         // @ts-ignore
-        deepFreeze(MODES[key]);
+        if (typeof MODES[key] === "object") {
+          // @ts-ignore
+          deepFreezeEs6(MODES[key]);
+        }
       }
-    }
 
-    // merge all the modes/regexs into our main object
-    Object.assign(hljs, MODES);
+      // merge all the modes/regexs into our main object
+      Object.assign(hljs, MODES);
 
-    return hljs;
-  };
+      return hljs;
+    };
 
-  // export an "instance" of the highlighter
-  var highlight = HLJS({});
+    // export an "instance" of the highlighter
+    var highlight = HLJS({});
 
-  return highlight;
+    return highlight;
 
 }());
 if (typeof exports === 'object' && typeof module !== 'undefined') { module.exports = hljs; }
@@ -2258,10 +2314,10 @@ hljs.registerLanguage('css', function () {
     };
     var AT_IDENTIFIER = '@[a-z-]+'; // @font-face
     var AT_MODIFIERS = "and or not only";
-    var AT_PROPERTY_RE = /@\-?\w[\w]*(\-\w+)*/; // @-webkit-keyframes
+    var AT_PROPERTY_RE = /@-?\w[\w]*(-\w+)*/; // @-webkit-keyframes
     var IDENT_RE = '[a-zA-Z-][a-zA-Z0-9_-]*';
     var RULE = {
-      begin: /(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/, returnBegin: true, end: ';', endsWithParent: true,
+      begin: /([*]\s?)?(?:[A-Z_.\-\\]+|--[a-zA-Z0-9_-]+)\s*(\/\*\*\/)?:/, returnBegin: true, end: ';', endsWithParent: true,
       contains: [
         ATTRIBUTE
       ]
@@ -2270,14 +2326,14 @@ hljs.registerLanguage('css', function () {
     return {
       name: 'CSS',
       case_insensitive: true,
-      illegal: /[=\/|'\$]/,
+      illegal: /[=|'\$]/,
       contains: [
         hljs.C_BLOCK_COMMENT_MODE,
         {
           className: 'selector-id', begin: /#[A-Za-z0-9_-]+/
         },
         {
-          className: 'selector-class', begin: /\.[A-Za-z0-9_-]+/
+          className: 'selector-class', begin: '\\.' + IDENT_RE
         },
         {
           className: 'selector-attr',
@@ -2290,7 +2346,7 @@ hljs.registerLanguage('css', function () {
         },
         {
           className: 'selector-pseudo',
-          begin: /:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/
+          begin: /:(:)?[a-zA-Z0-9_+()"'.-]+/
         },
         // matching these here allows us to treat them more like regular CSS
         // rules so everything between the {} gets regular rule highlighting,
@@ -2333,10 +2389,11 @@ hljs.registerLanguage('css', function () {
           relevance: 0
         },
         {
-          begin: '{', end: '}',
+          begin: /\{/, end: /\}/,
           illegal: /\S/,
           contains: [
             hljs.C_BLOCK_COMMENT_MODE,
+            { begin: /;/ }, // empty ; rule
             RULE,
           ]
         }
@@ -2531,68 +2588,174 @@ hljs.registerLanguage('javascript', function () {
   Website: https://developer.mozilla.org/en-US/docs/Web/JavaScript
   */
 
+  /** @type LanguageFn */
   function javascript(hljs) {
-    var IDENT_RE$1 = IDENT_RE;
-    var FRAGMENT = {
+    /**
+     * Takes a string like " {
+      const tag = "',
       end: ''
     };
-    var XML_TAG = {
+    const XML_TAG = {
       begin: /<[A-Za-z0-9\\._:-]+/,
-      end: /\/[A-Za-z0-9\\._:-]+>|\/>/
+      end: /\/[A-Za-z0-9\\._:-]+>|\/>/,
+      /**
+       * @param {RegExpMatchArray} match
+       * @param {CallbackResponse} response
+       */
+      isTrulyOpeningTag: (match, response) => {
+        const afterMatchIndex = match[0].length + match.index;
+        const nextChar = match.input[afterMatchIndex];
+        // nested type?
+        // HTML should not include another raw `<` inside a tag
+        // But a type might: `>`, etc.
+        if (nextChar === "<") {
+          response.ignoreMatch();
+          return;
+        }
+        // 
+        // This is now either a tag or a type.
+        if (nextChar === ">") {
+          // if we cannot find a matching closing tag, then we
+          // will ignore it
+          if (!hasClosingTag(match, { after: afterMatchIndex })) {
+            response.ignoreMatch();
+          }
+        }
+      }
     };
-    var KEYWORDS$1 = {
+    const KEYWORDS$1 = {
       $pattern: IDENT_RE,
       keyword: KEYWORDS.join(" "),
       literal: LITERALS.join(" "),
       built_in: BUILT_INS.join(" ")
     };
-    var NUMBER = {
+
+    // https://tc39.es/ecma262/#sec-literals-numeric-literals
+    const decimalDigits = '[0-9](_?[0-9])*';
+    const frac = `\\.(${decimalDigits})`;
+    // DecimalIntegerLiteral, including Annex B NonOctalDecimalIntegerLiteral
+    // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals
+    const decimalInteger = `0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*`;
+    const NUMBER = {
       className: 'number',
       variants: [
-        { begin: '\\b(0[bB][01]+)n?' },
-        { begin: '\\b(0[oO][0-7]+)n?' },
-        { begin: hljs.C_NUMBER_RE + 'n?' }
+        // DecimalLiteral
+        { begin: `(\\b(${decimalInteger})((${frac})|\\.)?|(${frac}))` +
+          `[eE][+-]?(${decimalDigits})\\b` },
+        { begin: `\\b(${decimalInteger})\\b((${frac})\\b|\\.)?|(${frac})\\b` },
+
+        // DecimalBigIntegerLiteral
+        { begin: `\\b(0|[1-9](_?[0-9])*)n\\b` },
+
+        // NonDecimalIntegerLiteral
+        { begin: "\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b" },
+        { begin: "\\b0[bB][0-1](_?[0-1])*n?\\b" },
+        { begin: "\\b0[oO][0-7](_?[0-7])*n?\\b" },
+
+        // LegacyOctalIntegerLiteral (does not include underscore separators)
+        // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals
+        { begin: "\\b0[0-7]+n?\\b" },
       ],
       relevance: 0
     };
-    var SUBST = {
+
+    const SUBST = {
       className: 'subst',
-      begin: '\\$\\{', end: '\\}',
+      begin: '\\$\\{',
+      end: '\\}',
       keywords: KEYWORDS$1,
-      contains: []  // defined later
+      contains: [] // defined later
     };
-    var HTML_TEMPLATE = {
-      begin: 'html`', end: '',
+    const HTML_TEMPLATE = {
+      begin: 'html`',
+      end: '',
       starts: {
-        end: '`', returnEnd: false,
+        end: '`',
+        returnEnd: false,
         contains: [
           hljs.BACKSLASH_ESCAPE,
           SUBST
         ],
-        subLanguage: 'xml',
+        subLanguage: 'xml'
       }
     };
-    var CSS_TEMPLATE = {
-      begin: 'css`', end: '',
+    const CSS_TEMPLATE = {
+      begin: 'css`',
+      end: '',
       starts: {
-        end: '`', returnEnd: false,
+        end: '`',
+        returnEnd: false,
         contains: [
           hljs.BACKSLASH_ESCAPE,
           SUBST
         ],
-        subLanguage: 'css',
+        subLanguage: 'css'
       }
     };
-    var TEMPLATE_STRING = {
+    const TEMPLATE_STRING = {
       className: 'string',
-      begin: '`', end: '`',
+      begin: '`',
+      end: '`',
       contains: [
         hljs.BACKSLASH_ESCAPE,
         SUBST
       ]
     };
-    SUBST.contains = [
+    const JSDOC_COMMENT = hljs.COMMENT(
+      '/\\*\\*',
+      '\\*/',
+      {
+        relevance: 0,
+        contains: [
+          {
+            className: 'doctag',
+            begin: '@[A-Za-z]+',
+            contains: [
+              {
+                className: 'type',
+                begin: '\\{',
+                end: '\\}',
+                relevance: 0
+              },
+              {
+                className: 'variable',
+                begin: IDENT_RE$1 + '(?=\\s*(-)|$)',
+                endsParent: true,
+                relevance: 0
+              },
+              // eat spaces (not newlines) so we can find
+              // types or variables
+              {
+                begin: /(?=[^\n])\s/,
+                relevance: 0
+              }
+            ]
+          }
+        ]
+      }
+    );
+    const COMMENT = {
+      className: "comment",
+      variants: [
+        JSDOC_COMMENT,
+        hljs.C_BLOCK_COMMENT_MODE,
+        hljs.C_LINE_COMMENT_MODE
+      ]
+    };
+    const SUBST_INTERNALS = [
       hljs.APOS_STRING_MODE,
       hljs.QUOTE_STRING_MODE,
       HTML_TEMPLATE,
@@ -2601,32 +2764,52 @@ hljs.registerLanguage('javascript', function () {
       NUMBER,
       hljs.REGEXP_MODE
     ];
-    var PARAMS_CONTAINS = SUBST.contains.concat([
+    SUBST.contains = SUBST_INTERNALS
+      .concat({
+        // we need to pair up {} inside our subst to prevent
+        // it from ending too early by matching another }
+        begin: /\{/,
+        end: /\}/,
+        keywords: KEYWORDS$1,
+        contains: [
+          "self"
+        ].concat(SUBST_INTERNALS)
+      });
+    const SUBST_AND_COMMENTS = [].concat(COMMENT, SUBST.contains);
+    const PARAMS_CONTAINS = SUBST_AND_COMMENTS.concat([
       // eat recursive parens in sub expressions
-      { begin: /\(/, end: /\)/,
-        contains: ["self"].concat(SUBST.contains, [hljs.C_BLOCK_COMMENT_MODE, hljs.C_LINE_COMMENT_MODE])
-      },
-      hljs.C_BLOCK_COMMENT_MODE,
-      hljs.C_LINE_COMMENT_MODE
+      {
+        begin: /\(/,
+        end: /\)/,
+        keywords: KEYWORDS$1,
+        contains: ["self"].concat(SUBST_AND_COMMENTS)
+      }
     ]);
-    var PARAMS = {
+    const PARAMS = {
       className: 'params',
-      begin: /\(/, end: /\)/,
+      begin: /\(/,
+      end: /\)/,
       excludeBegin: true,
       excludeEnd: true,
+      keywords: KEYWORDS$1,
       contains: PARAMS_CONTAINS
     };
 
     return {
-      name: 'JavaScript',
+      name: 'Javascript',
       aliases: ['js', 'jsx', 'mjs', 'cjs'],
       keywords: KEYWORDS$1,
+      // this will be extended by TypeScript
+      exports: { PARAMS_CONTAINS },
+      illegal: /#(?![$_A-z])/,
       contains: [
         hljs.SHEBANG({
+          label: "shebang",
           binary: "node",
           relevance: 5
         }),
         {
+          label: "use_strict",
           className: 'meta',
           relevance: 10,
           begin: /^\s*['"]use (strict|asm)['"]/
@@ -2636,41 +2819,7 @@ hljs.registerLanguage('javascript', function () {
         HTML_TEMPLATE,
         CSS_TEMPLATE,
         TEMPLATE_STRING,
-        hljs.C_LINE_COMMENT_MODE,
-        hljs.COMMENT(
-          '/\\*\\*',
-          '\\*/',
-          {
-            relevance : 0,
-            contains : [
-              {
-                className : 'doctag',
-                begin : '@[A-Za-z]+',
-                contains : [
-                  {
-                    className: 'type',
-                    begin: '\\{',
-                    end: '\\}',
-                    relevance: 0
-                  },
-                  {
-                    className: 'variable',
-                    begin: IDENT_RE$1 + '(?=\\s*(-)|$)',
-                    endsParent: true,
-                    relevance: 0
-                  },
-                  // eat spaces (not newlines) so we can find
-                  // types or variables
-                  {
-                    begin: /(?=[^\n])\s/,
-                    relevance: 0
-                  },
-                ]
-              }
-            ]
-          }
-        ),
-        hljs.C_BLOCK_COMMENT_MODE,
+        COMMENT,
         NUMBER,
         { // object attr container
           begin: concat(/[{,\n]\s*/,
@@ -2687,42 +2836,44 @@ hljs.registerLanguage('javascript', function () {
             lookahead(concat(
               // we also need to allow for multiple possible comments inbetween
               // the first key:value pairing
-              /(((\/\/.*$)|(\/\*(.|\n)*\*\/))\s*)*/,
+              /(((\/\/.*$)|(\/\*(\*[^/]|[^*])*\*\/))\s*)*/,
               IDENT_RE$1 + '\\s*:'))),
           relevance: 0,
           contains: [
             {
               className: 'attr',
               begin: IDENT_RE$1 + lookahead('\\s*:'),
-              relevance: 0,
-            },
+              relevance: 0
+            }
           ]
         },
         { // "value" container
           begin: '(' + hljs.RE_STARTERS_RE + '|\\b(case|return|throw)\\b)\\s*',
           keywords: 'return throw case',
           contains: [
-            hljs.C_LINE_COMMENT_MODE,
-            hljs.C_BLOCK_COMMENT_MODE,
+            COMMENT,
             hljs.REGEXP_MODE,
             {
               className: 'function',
               // we have to count the parens to make sure we actually have the
               // correct bounding ( ) before the =>.  There could be any number of
               // sub-expressions inside also surrounded by parens.
-              begin: '(\\([^(]*' +
-                '(\\([^(]*' +
-                  '(\\([^(]*' +
-                  '\\))?' +
-                '\\))?' +
-              '\\)|' + hljs.UNDERSCORE_IDENT_RE + ')\\s*=>', returnBegin: true,
+              begin: '(\\(' +
+              '[^()]*(\\(' +
+              '[^()]*(\\(' +
+              '[^()]*' +
+              '\\))*[^()]*' +
+              '\\))*[^()]*' +
+              '\\)|' + hljs.UNDERSCORE_IDENT_RE + ')\\s*=>',
+              returnBegin: true,
               end: '\\s*=>',
               contains: [
                 {
                   className: 'params',
                   variants: [
                     {
-                      begin: hljs.UNDERSCORE_IDENT_RE
+                      begin: hljs.UNDERSCORE_IDENT_RE,
+                      relevance: 0
                     },
                     {
                       className: null,
@@ -2730,8 +2881,10 @@ hljs.registerLanguage('javascript', function () {
                       skip: true
                     },
                     {
-                      begin: /\(/, end: /\)/,
-                      excludeBegin: true, excludeEnd: true,
+                      begin: /\(/,
+                      end: /\)/,
+                      excludeBegin: true,
+                      excludeEnd: true,
                       keywords: KEYWORDS$1,
                       contains: PARAMS_CONTAINS
                     }
@@ -2740,69 +2893,120 @@ hljs.registerLanguage('javascript', function () {
               ]
             },
             { // could be a comma delimited list of params to a function call
-              begin: /,/, relevance: 0,
+              begin: /,/, relevance: 0
             },
             {
               className: '',
               begin: /\s/,
               end: /\s*/,
-              skip: true,
+              skip: true
             },
             { // JSX
               variants: [
                 { begin: FRAGMENT.begin, end: FRAGMENT.end },
-                { begin: XML_TAG.begin, end: XML_TAG.end }
+                {
+                  begin: XML_TAG.begin,
+                  // we carefully check the opening tag to see if it truly
+                  // is a tag and not a false positive
+                  'on:begin': XML_TAG.isTrulyOpeningTag,
+                  end: XML_TAG.end
+                }
               ],
               subLanguage: 'xml',
               contains: [
                 {
-                  begin: XML_TAG.begin, end: XML_TAG.end, skip: true,
+                  begin: XML_TAG.begin,
+                  end: XML_TAG.end,
+                  skip: true,
                   contains: ['self']
                 }
               ]
-            },
+            }
           ],
           relevance: 0
         },
         {
           className: 'function',
-          beginKeywords: 'function', end: /\{/, excludeEnd: true,
+          beginKeywords: 'function',
+          end: /[{;]/,
+          excludeEnd: true,
+          keywords: KEYWORDS$1,
           contains: [
-            hljs.inherit(hljs.TITLE_MODE, {begin: IDENT_RE$1}),
+            'self',
+            hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1 }),
             PARAMS
           ],
-          illegal: /\[|%/
+          illegal: /%/
         },
         {
-          begin: /\$[(.]/ // relevance booster for a pattern common to JS libs: `$(something)` and `$.something`
+          // prevent this from getting swallowed up by function
+          // since they appear "function like"
+          beginKeywords: "while if switch catch for"
+        },
+        {
+          className: 'function',
+          // we have to count the parens to make sure we actually have the correct
+          // bounding ( ).  There could be any number of sub-expressions inside
+          // also surrounded by parens.
+          begin: hljs.UNDERSCORE_IDENT_RE +
+            '\\(' + // first parens
+            '[^()]*(\\(' +
+              '[^()]*(\\(' +
+                '[^()]*' +
+              '\\))*[^()]*' +
+            '\\))*[^()]*' +
+            '\\)\\s*\\{', // end parens
+          returnBegin:true,
+          contains: [
+            PARAMS,
+            hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1 }),
+          ]
+        },
+        // hack: prevents detection of keywords in some circumstances
+        // .keyword()
+        // $keyword = x
+        {
+          variants: [
+            { begin: '\\.' + IDENT_RE$1 },
+            { begin: '\\$' + IDENT_RE$1 }
+          ],
+          relevance: 0
         },
-
-        hljs.METHOD_GUARD,
         { // ES6 class
           className: 'class',
-          beginKeywords: 'class', end: /[{;=]/, excludeEnd: true,
-          illegal: /[:"\[\]]/,
+          beginKeywords: 'class',
+          end: /[{;=]/,
+          excludeEnd: true,
+          illegal: /[:"[\]]/,
           contains: [
-            {beginKeywords: 'extends'},
+            { beginKeywords: 'extends' },
             hljs.UNDERSCORE_TITLE_MODE
           ]
         },
         {
-          beginKeywords: 'constructor', end: /\{/, excludeEnd: true
+          begin: /\b(?=constructor)/,
+          end: /[{;]/,
+          excludeEnd: true,
+          contains: [
+            hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1 }),
+            'self',
+            PARAMS
+          ]
         },
         {
           begin: '(get|set)\\s+(?=' + IDENT_RE$1 + '\\()',
-          end: /{/,
+          end: /\{/,
           keywords: "get set",
           contains: [
-            hljs.inherit(hljs.TITLE_MODE, {begin: IDENT_RE$1}),
+            hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1 }),
             { begin: /\(\)/ }, // eat to avoid empty params
             PARAMS
           ]
-
+        },
+        {
+          begin: /\$[(.]/ // relevance booster for a pattern common to JS libs: `$(something)` and `$.something`
         }
-      ],
-      illegal: /#(?!!)/
+      ]
     };
   }
 
@@ -2815,32 +3019,95 @@ hljs.registerLanguage('javascript', function () {
 hljs.registerLanguage('xml', function () {
   'use strict';
 
+  /**
+   * @param {string} value
+   * @returns {RegExp}
+   * */
+
+  /**
+   * @param {RegExp | string } re
+   * @returns {string}
+   */
+  function source(re) {
+    if (!re) return null;
+    if (typeof re === "string") return re;
+
+    return re.source;
+  }
+
+  /**
+   * @param {RegExp | string } re
+   * @returns {string}
+   */
+  function lookahead(re) {
+    return concat('(?=', re, ')');
+  }
+
+  /**
+   * @param {RegExp | string } re
+   * @returns {string}
+   */
+  function optional(re) {
+    return concat('(', re, ')?');
+  }
+
+  /**
+   * @param {...(RegExp | string) } args
+   * @returns {string}
+   */
+  function concat(...args) {
+    const joined = args.map((x) => source(x)).join("");
+    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;
+  }
+
   /*
   Language: HTML, XML
   Website: https://www.w3.org/XML/
   Category: common
   */
 
+  /** @type LanguageFn */
   function xml(hljs) {
-    var XML_IDENT_RE = '[A-Za-z0-9\\._:-]+';
-    var XML_ENTITIES = {
+    // 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_ENTITIES = {
       className: 'symbol',
       begin: '&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;'
     };
-    var XML_META_KEYWORDS = {
-  	  begin: '\\s',
-  	  contains:[
-  	    {
-  	      className: 'meta-keyword',
-  	      begin: '#?[a-z_][a-z1-9_-]+',
-  	      illegal: '\\n',
-        }
-  	  ]
+    const XML_META_KEYWORDS = {
+      begin: '\\s',
+      contains: [
+        {
+          className: 'meta-keyword',
+          begin: '#?[a-z_][a-z1-9_-]+',
+          illegal: '\\n'
+        }
+      ]
     };
-    var XML_META_PAR_KEYWORDS = hljs.inherit(XML_META_KEYWORDS, {begin: '\\(', end: '\\)'});
-    var APOS_META_STRING_MODE = hljs.inherit(hljs.APOS_STRING_MODE, {className: 'meta-string'});
-    var QUOTE_META_STRING_MODE = hljs.inherit(hljs.QUOTE_STRING_MODE, {className: 'meta-string'});
-    var TAG_INTERNALS = {
+    const XML_META_PAR_KEYWORDS = hljs.inherit(XML_META_KEYWORDS, {
+      begin: '\\(',
+      end: '\\)'
+    });
+    const APOS_META_STRING_MODE = hljs.inherit(hljs.APOS_STRING_MODE, {
+      className: 'meta-string'
+    });
+    const QUOTE_META_STRING_MODE = hljs.inherit(hljs.QUOTE_STRING_MODE, {
+      className: 'meta-string'
+    });
+    const TAG_INTERNALS = {
       endsWithParent: true,
       illegal: /`]+/}
+                {
+                  begin: /"/,
+                  end: /"/,
+                  contains: [ XML_ENTITIES ]
+                },
+                {
+                  begin: /'/,
+                  end: /'/,
+                  contains: [ XML_ENTITIES ]
+                },
+                {
+                  begin: /[^\s"'=<>`]+/
+                }
               ]
             }
           ]
@@ -2869,34 +3146,48 @@ hljs.registerLanguage('xml', function () {
     };
     return {
       name: 'HTML, XML',
-      aliases: ['html', 'xhtml', 'rss', 'atom', 'xjb', 'xsd', 'xsl', 'plist', 'wsf', 'svg'],
+      aliases: [
+        'html',
+        'xhtml',
+        'rss',
+        'atom',
+        'xjb',
+        'xsd',
+        'xsl',
+        'plist',
+        'wsf',
+        'svg'
+      ],
       case_insensitive: true,
       contains: [
         {
           className: 'meta',
-          begin: '',
+          begin: '',
           relevance: 10,
           contains: [
-  				  XML_META_KEYWORDS,
-  				  QUOTE_META_STRING_MODE,
-  				  APOS_META_STRING_MODE,
-  					XML_META_PAR_KEYWORDS,
-  					{
-  					  begin: '\\[', end: '\\]',
-  					  contains:[
-  						  {
-  					      className: 'meta',
-  					      begin: '',
-  					      contains: [
-  					        XML_META_KEYWORDS,
-  					        XML_META_PAR_KEYWORDS,
-  					        QUOTE_META_STRING_MODE,
-  					        APOS_META_STRING_MODE
-  						    ]
-  			        }
-  					  ]
-  				  }
-  				]
+            XML_META_KEYWORDS,
+            QUOTE_META_STRING_MODE,
+            APOS_META_STRING_MODE,
+            XML_META_PAR_KEYWORDS,
+            {
+              begin: '\\[',
+              end: '\\]',
+              contains: [
+                {
+                  className: 'meta',
+                  begin: '',
+                  contains: [
+                    XML_META_KEYWORDS,
+                    XML_META_PAR_KEYWORDS,
+                    QUOTE_META_STRING_MODE,
+                    APOS_META_STRING_MODE
+                  ]
+                }
+              ]
+            }
+          ]
         },
         hljs.COMMENT(
           '