summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJim Mason <jmason@ibinx.com>2020-06-12 16:47:37 +0100
committerJim Mason <jmason@ibinx.com>2020-06-14 10:57:58 +0100
commit9cb2748867137b1e1fccc16fabca32427df075f1 (patch)
tree35a2b7638017f40c22b35582094235d72e9536d6
parentb584710b9e8af9f33ac1a7f993c90fb290b63dfd (diff)
downloadepiphany-9cb2748867137b1e1fccc16fabca32427df075f1.tar.gz
highlight.js: update to version 10.1.0
fixes #1230
-rw-r--r--third-party/highlightjs/highlight.js1403
1 files changed, 1043 insertions, 360 deletions
diff --git a/third-party/highlightjs/highlight.js b/third-party/highlightjs/highlight.js
index c62a3082e..5d3214cb6 100644
--- a/third-party/highlightjs/highlight.js
+++ b/third-party/highlightjs/highlight.js
@@ -1,5 +1,5 @@
/*
- Highlight.js 10.0.2 (e29f8f7d)
+ Highlight.js 10.1.0 (74de6eaa)
License: BSD-3-Clause
Copyright (c) 2006-2020, Ivan Sagalaev
*/
@@ -7,61 +7,106 @@ var hljs = (function () {
'use strict';
// https://github.com/substack/deep-freeze/blob/master/index.js
- function deepFreeze (o) {
- Object.freeze(o);
+
+ function deepFreeze(obj) {
+ Object.freeze(obj);
- var objIsFunction = typeof o === 'function';
+ var objIsFunction = typeof obj === 'function';
- Object.getOwnPropertyNames(o).forEach(function (prop) {
- if (o.hasOwnProperty(prop)
- && o[prop] !== null
- && (typeof o[prop] === "object" || typeof o[prop] === "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(o[prop])) {
- deepFreeze(o[prop]);
+ && !Object.isFrozen(obj[prop])) {
+ deepFreeze(obj[prop]);
}
});
- return o;
+ return obj;
}
- function escapeHTML(value) {
- return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+ class Response {
+ /**
+ * @param {CompiledMode} mode
+ */
+ constructor(mode) {
+ // eslint-disable-next-line no-undefined
+ if (mode.data === undefined) mode.data = {};
+
+ this.data = mode.data;
+ }
+
+ ignoreMatch() {
+ this.ignore = true;
+ }
}
+ /**
+ * @param {string} value
+ * @returns {string}
+ */
+ function escapeHTML(value) {
+ return value
+ .replace(/&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&#x27;');
+ }
-
- function inherit(parent) { // inherit(parent, override_obj, override_obj, ...)
- var key;
+ /**
+ * performs a shallow merge of multiple objects into one
+ *
+ * @template T
+ * @param {T} original
+ * @param {Record<string,any>[]} objects
+ * @returns {T} a single new object
+ */
+ function inherit(original, ...objects) {
+ /** @type Record<string,any> */
var result = {};
- var objects = Array.prototype.slice.call(arguments, 1);
- for (key in parent)
- result[key] = parent[key];
+ for (const key in original) {
+ result[key] = original[key];
+ }
objects.forEach(function(obj) {
- for (key in obj)
+ for (const key in obj) {
result[key] = obj[key];
+ }
});
- return result;
+ return /** @type {T} */ (result);
}
/* Stream merging */
+ /**
+ * @typedef Event
+ * @property {'start'|'stop'} event
+ * @property {number} offset
+ * @property {Node} node
+ */
+ /**
+ * @param {Node} node
+ */
function tag(node) {
return node.nodeName.toLowerCase();
}
-
+ /**
+ * @param {Node} node
+ */
function nodeStream(node) {
+ /** @type Event[] */
var result = [];
(function _nodeStream(node, offset) {
for (var child = node.firstChild; child; child = child.nextSibling) {
- if (child.nodeType === 3)
+ if (child.nodeType === 3) {
offset += child.nodeValue.length;
- else if (child.nodeType === 1) {
+ } else if (child.nodeType === 1) {
result.push({
event: 'start',
offset: offset,
@@ -85,6 +130,11 @@ var hljs = (function () {
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 = '';
@@ -116,17 +166,28 @@ var hljs = (function () {
return highlighted[0].event === 'start' ? original : highlighted;
}
+ /**
+ * @param {Node} node
+ */
function open(node) {
- function attr_str(a) {
- return ' ' + a.nodeName + '="' + escapeHTML(a.value).replace(/"/g, '&quot;') + '"';
+ /** @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('') + '>';
}
+ /**
+ * @param {Node} node
+ */
function close(node) {
result += '</' + tag(node) + '>';
}
+ /**
+ * @param {Event} event
+ */
function render(event) {
(event.event === 'start' ? open : close)(event.node);
}
@@ -168,76 +229,128 @@ var hljs = (function () {
mergeStreams: mergeStreams
});
+ /**
+ * @typedef {object} Renderer
+ * @property {(text: string) => void} addText
+ * @property {(node: Node) => void} openNode
+ * @property {(node: Node) => void} closeNode
+ * @property {() => string} value
+ */
+
+ /** @typedef {{kind?: string, sublanguage?: boolean}} Node */
+ /** @typedef {{walk: (r: Renderer) => void}} Tree */
+ /** */
+
const SPAN_CLOSE = '</span>';
+ /**
+ * Determines if a node needs to be wrapped in <span>
+ *
+ * @param {Node} node */
const emitsWrappingTags = (node) => {
return !!node.kind;
};
+ /** @type {Renderer} */
class HTMLRenderer {
- constructor(tree, options) {
+ /**
+ * 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;
- tree.walk(this);
+ parseTree.walk(this);
}
- // renderer API
-
+ /**
+ * Adds texts to the output stream
+ *
+ * @param {string} text */
addText(text) {
this.buffer += escapeHTML(text);
}
+ /**
+ * Adds a node open to the output stream (if needed)
+ *
+ * @param {Node} node */
openNode(node) {
if (!emitsWrappingTags(node)) return;
let className = node.kind;
- if (!node.sublanguage)
+ if (!node.sublanguage) {
className = `${this.classPrefix}${className}`;
+ }
this.span(className);
}
+ /**
+ * Adds a node close to the output stream (if needed)
+ *
+ * @param {Node} node */
closeNode(node) {
if (!emitsWrappingTags(node)) return;
this.buffer += SPAN_CLOSE;
}
+ /**
+ * returns the accumulated buffer
+ */
+ value() {
+ return this.buffer;
+ }
+
// helpers
+ /**
+ * Builds a span element
+ *
+ * @param {string} className */
span(className) {
this.buffer += `<span class="${className}">`;
}
-
- value() {
- return this.buffer;
- }
}
+ /** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} | string} Node */
+ /** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} } DataNode */
+ /** */
+
class TokenTree {
constructor() {
+ /** @type DataNode */
this.rootNode = { children: [] };
- this.stack = [ this.rootNode ];
+ this.stack = [this.rootNode];
}
get top() {
return this.stack[this.stack.length - 1];
}
- get root() { return this.rootNode };
+ get root() { return this.rootNode; }
+ /** @param {Node} node */
add(node) {
this.top.children.push(node);
}
+ /** @param {string} kind */
openNode(kind) {
- let node = { kind, children: [] };
+ /** @type Node */
+ const node = { kind, children: [] };
this.add(node);
this.stack.push(node);
}
closeNode() {
- if (this.stack.length > 1)
+ if (this.stack.length > 1) {
return this.stack.pop();
+ }
+ // eslint-disable-next-line no-undefined
+ return undefined;
}
closeAllNodes() {
@@ -248,10 +361,21 @@ var hljs = (function () {
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);
@@ -263,16 +387,19 @@ var hljs = (function () {
return builder;
}
+ /**
+ * @param {Node} node
+ */
static _collapse(node) {
- if (!node.children) {
- return;
- }
+ 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.text = node.children.join("");
+ // delete node.children;
+ node.children = [node.children.join("")];
} else {
node.children.forEach((child) => {
- if (typeof child === "string") return;
TokenTree._collapse(child);
});
}
@@ -287,7 +414,7 @@ var hljs = (function () {
- addKeyword(text, kind)
- addText(text)
- - addSublanguage(emitter, subLangaugeName)
+ - addSublanguage(emitter, subLanguageName)
- finalize()
- openNode(kind)
- closeNode()
@@ -295,12 +422,23 @@ var hljs = (function () {
- toHTML()
*/
+
+ /**
+ * @implements {Emitter}
+ */
class TokenTreeEmitter extends TokenTree {
+ /**
+ * @param {*} options
+ */
constructor(options) {
super();
this.options = options;
}
+ /**
+ * @param {string} text
+ * @param {string} kind
+ */
addKeyword(text, kind) {
if (text === "") { return; }
@@ -309,44 +447,78 @@ var hljs = (function () {
this.closeNode();
}
+ /**
+ * @param {string} text
+ */
addText(text) {
if (text === "") { return; }
this.add(text);
}
+ /**
+ * @param {Emitter & {root: DataNode}} emitter
+ * @param {string} name
+ */
addSublanguage(emitter, name) {
- let node = emitter.root;
+ /** @type DataNode */
+ const node = emitter.root;
node.kind = name;
node.sublanguage = true;
this.add(node);
}
toHTML() {
- let renderer = new HTMLRenderer(this, this.options);
+ const renderer = new HTMLRenderer(this, this.options);
return renderer.value();
}
finalize() {
- return;
+ return true;
}
-
}
+ /**
+ * @param {string} value
+ * @returns {RegExp}
+ * */
function escape(value) {
- return new RegExp(value.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'm');
+ return new RegExp(value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'm');
}
+ /**
+ * @param {RegExp | string } re
+ * @returns {string}
+ */
function source(re) {
- // if it's a regex get it's source,
- // otherwise it's a string already so just return it
- return (re && re.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;
@@ -357,7 +529,12 @@ var hljs = (function () {
// 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. :-)
- function join(regexps, separator) {
+ /**
+ * @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
@@ -384,12 +561,12 @@ var hljs = (function () {
}
ret += re.substring(0, match.index);
re = re.substring(match.index + match[0].length);
- if (match[0][0] == '\\' && match[1]) {
+ if (match[0][0] === '\\' && match[1]) {
// Adjust the backreference.
ret += '\\' + String(Number(match[1]) + offset);
} else {
ret += match[0];
- if (match[0] == '(') {
+ if (match[0] === '(') {
numCaptures++;
}
}
@@ -407,38 +584,73 @@ var hljs = (function () {
const BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b...
const RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~';
+ /**
+ * @param { Partial<Mode> & {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: '\'',
+ begin: '\'',
+ end: '\'',
illegal: '\\n',
contains: [BACKSLASH_ESCAPE]
};
const QUOTE_STRING_MODE = {
className: 'string',
- begin: '"', end: '"',
+ 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/
};
- const COMMENT = function (begin, end, inherits) {
+ /**
+ * Creates a comment mode
+ *
+ * @param {string | RegExp} begin
+ * @param {string | RegExp} end
+ * @param {Mode | {}} [modeOptions]
+ * @returns {Partial<Mode>}
+ */
+ const COMMENT = function(begin, end, modeOptions = {}) {
var mode = inherit(
{
className: 'comment',
- begin: begin, end: end,
+ begin,
+ end,
contains: []
},
- inherits || {}
+ modeOptions
);
mode.contains.push(PHRASAL_WORDS_MODE);
mode.contains.push({
className: 'doctag',
- begin: '(?:TODO|FIXME|NOTE|BUG|XXX):',
+ begin: '(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):',
relevance: 0
});
return mode;
@@ -464,7 +676,7 @@ var hljs = (function () {
const CSS_NUMBER_MODE = {
className: 'number',
begin: NUMBER_RE + '(' +
- '%|em|ex|ch|rem' +
+ '%|em|ex|ch|rem' +
'|vw|vh|vmin|vmax' +
'|cm|mm|in|pt|pc|px' +
'|deg|grad|rad|turn' +
@@ -481,15 +693,17 @@ var hljs = (function () {
// 3 / something
//
// (which will then blow up when regex's `illegal` sees the newline)
- begin: /(?=\/[^\/\n]*\/)/,
+ begin: /(?=\/[^/\n]*\/)/,
contains: [{
className: 'regexp',
- begin: /\//, end: /\/[gimuy]*/,
+ begin: /\//,
+ end: /\/[gimuy]*/,
illegal: /\n/,
contains: [
BACKSLASH_ESCAPE,
{
- begin: /\[/, end: /\]/,
+ begin: /\[/,
+ end: /\]/,
relevance: 0,
contains: [BACKSLASH_ESCAPE]
}
@@ -512,6 +726,23 @@ var hljs = (function () {
relevance: 0
};
+ /**
+ * 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>} 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(); }
+ });
+ };
+
var MODES = /*#__PURE__*/Object.freeze({
__proto__: null,
IDENT_RE: IDENT_RE,
@@ -520,6 +751,7 @@ var hljs = (function () {
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,
@@ -535,7 +767,8 @@ var hljs = (function () {
REGEXP_MODE: REGEXP_MODE,
TITLE_MODE: TITLE_MODE,
UNDERSCORE_TITLE_MODE: UNDERSCORE_TITLE_MODE,
- METHOD_GUARD: METHOD_GUARD
+ METHOD_GUARD: METHOD_GUARD,
+ END_SAME_AS_BEGIN: END_SAME_AS_BEGIN
});
// keywords that should have no default relevance value
@@ -543,8 +776,21 @@ var hljs = (function () {
// compilation
+ /**
+ * 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),
@@ -568,13 +814,16 @@ var hljs = (function () {
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;
@@ -583,20 +832,27 @@ var hljs = (function () {
compile() {
if (this.regexes.length === 0) {
// avoids the need to check length every time exec is called
+ // @ts-ignore
this.exec = () => null;
}
- let terminators = this.regexes.map(el => el[1]);
- this.matcherRe = langRe(join(terminators, '|'), true);
+ const terminators = this.regexes.map(el => el[1]);
+ this.matcherRe = langRe(join(terminators), true);
this.lastIndex = 0;
}
+ /** @param {string} s */
exec(s) {
this.matcherRe.lastIndex = this.lastIndex;
- let match = this.matcherRe.exec(s);
+ const match = this.matcherRe.exec(s);
if (!match) { return null; }
- let i = match.findIndex((el, i) => i>0 && el!=undefined);
- let matchData = this.matchIndexes[i];
+ // 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);
return Object.assign(match, matchData);
}
@@ -635,7 +891,9 @@ var hljs = (function () {
*/
class ResumableMultiRegex {
constructor() {
+ // @ts-ignore
this.rules = [];
+ // @ts-ignore
this.multiRegexes = [];
this.count = 0;
@@ -643,11 +901,12 @@ var hljs = (function () {
this.regexIndex = 0;
}
+ // @ts-ignore
getMatcher(index) {
if (this.multiRegexes[index]) return this.multiRegexes[index];
- let matcher = new MultiRegex();
- this.rules.slice(index).forEach(([re, opts])=> matcher.addRule(re,opts));
+ const matcher = new MultiRegex();
+ this.rules.slice(index).forEach(([re, opts]) => matcher.addRule(re, opts));
matcher.compile();
this.multiRegexes[index] = matcher;
return matcher;
@@ -657,19 +916,22 @@ var hljs = (function () {
this.regexIndex = 0;
}
+ // @ts-ignore
addRule(re, opts) {
this.rules.push([re, opts]);
- if (opts.type==="begin") this.count++;
+ if (opts.type === "begin") this.count++;
}
+ /** @param {string} s */
exec(s) {
- let m = this.getMatcher(this.regexIndex);
+ const m = this.getMatcher(this.regexIndex);
m.lastIndex = this.lastIndex;
- let result = m.exec(s);
+ const result = m.exec(s);
if (result) {
this.regexIndex += result.position + 1;
- if (this.regexIndex === this.count) // wrap-around
+ if (this.regexIndex === this.count) { // wrap-around
this.regexIndex = 0;
+ }
}
// this.regexIndex = 0;
@@ -677,26 +939,43 @@ var hljs = (function () {
}
}
+ /**
+ * 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();
- let mm = new ResumableMultiRegex();
-
- mode.contains.forEach(term => mm.addRule(term.begin, {rule: term, type: "begin" }));
+ 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"} );
+ 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
- function skipIfhasPrecedingOrTrailingDot(match) {
- let before = match.input[match.index-1];
- let after = match.input[match.index + match[0].length];
+ /**
+ * 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 === ".") {
- return {ignoreMatch: true };
+ response.ignoreMatch();
}
}
@@ -730,19 +1009,43 @@ var hljs = (function () {
* - The parser cursor is not moved forward.
*/
+ /**
+ * 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) {
- if (mode.compiled)
- return;
+ const cmode = /** @type CompiledMode */ (mode);
+ if (mode.compiled) return cmode;
mode.compiled = true;
- // __onBegin is considered private API, internal use only
- mode.__onBegin = null;
+ // __beforeBegin is considered private API, internal use only
+ mode.__beforeBegin = null;
mode.keywords = mode.keywords || mode.beginKeywords;
- if (mode.keywords)
+
+ let kw_pattern = null;
+ if (typeof mode.keywords === "object") {
+ kw_pattern = mode.keywords.$pattern;
+ delete mode.keywords.$pattern;
+ }
+
+ if (mode.keywords) {
mode.keywords = compileKeywords(mode.keywords, language.case_insensitive);
+ }
+
+ // 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) ");
+ }
- mode.lexemesRe = langRe(mode.lexemes || /\w+/, true);
+ // `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);
if (parent) {
if (mode.beginKeywords) {
@@ -752,122 +1055,171 @@ var hljs = (function () {
// 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.__onBegin = skipIfhasPrecedingOrTrailingDot;
+ mode.__beforeBegin = skipIfhasPrecedingOrTrailingDot;
+ }
+ 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.begin)
- mode.begin = /\B|\b/;
- mode.beginRe = langRe(mode.begin);
- if (mode.endSameAsBegin)
- mode.end = mode.begin;
- if (!mode.end && !mode.endsWithParent)
- mode.end = /\B|\b/;
- if (mode.end)
- mode.endRe = langRe(mode.end);
- mode.terminator_end = source(mode.end) || '';
- if (mode.endsWithParent && parent.terminator_end)
- mode.terminator_end += (mode.end ? '|' : '') + parent.terminator_end;
- }
- if (mode.illegal)
- mode.illegalRe = langRe(mode.illegal);
- if (mode.relevance == null)
- mode.relevance = 1;
- if (!mode.contains) {
- mode.contains = [];
}
+ 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(c, mode);});
+ mode.contains.forEach(function(c) { compileMode(/** @type Mode */ (c), cmode); });
if (mode.starts) {
compileMode(mode.starts, parent);
}
- mode.matcher = buildModeRegex(mode);
+ 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.")
+ throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");
}
- compileMode(language);
+ return compileMode(/** @type Mode */ (language));
}
+ /**
+ * 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);
}
+ /**
+ * 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);
+ return inherit(mode, { variants: null }, variant);
});
}
// 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)
+ if (mode.cached_variants) {
return mode.cached_variants;
+ }
// 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))
+ if (dependencyOnParent(mode)) {
return inherit(mode, { starts: mode.starts ? inherit(mode.starts) : null });
+ }
- if (Object.isFrozen(mode))
+ if (Object.isFrozen(mode)) {
return inherit(mode);
+ }
// no special dependency issues, just return ourselves
return mode;
}
+ /***********************************************
+ Keywords
+ ***********************************************/
- // keywords
-
+ /**
+ * Given raw keywords from a language definition, compile them.
+ *
+ * @param {string | Record<string,string>} 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) {
+ Object.keys(rawKeywords).forEach(function(className) {
splitAndCompile(className, rawKeywords[className]);
});
}
- return compiled_keywords;
+ return compiled_keywords;
- // ---
+ // ---
- function splitAndCompile(className, str) {
- if (case_insensitive) {
- str = str.toLowerCase();
+ /**
+ * 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();
+ }
+ keywordList.split(' ').forEach(function(keyword) {
+ var pair = keyword.split('|');
+ compiled_keywords[pair[0]] = [className, scoreForKeyword(pair[0], pair[1])];
+ });
}
- str.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);
+ // 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;
+ return commonKeyword(keyword) ? 0 : 1;
}
- function commonKeyword(word) {
- return COMMON_KEYWORDS.includes(word.toLowerCase());
+ /**
+ * Determines if a given keyword is common or not
+ *
+ * @param {string} keyword */
+ function commonKeyword(keyword) {
+ return COMMON_KEYWORDS.includes(keyword.toLowerCase());
}
- var version = "10.0.2";
+ var version = "10.1.0";
/*
Syntax highlighting with language autodetection.
@@ -878,36 +1230,42 @@ var hljs = (function () {
const inherit$1 = inherit;
const { nodeStream: nodeStream$1, mergeStreams: mergeStreams$1 } = utils;
+ const NO_MATCH = Symbol("nomatch");
-
+ /**
+ * @param {any} hljs - object that is extended (legacy)
+ */
const HLJS = function(hljs) {
-
// Convenience variables for build-in objects
+ /** @type {unknown[]} */
var ArrayProto = [];
// Global internal variables used within the highlight.js library.
- var languages = {},
- aliases = {},
- plugins = [];
+ /** @type {Record<string, Language>} */
+ var languages = {};
+ /** @type {Record<string, string>} */
+ var aliases = {};
+ /** @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;
-
- // Regular expressions used throughout the highlight.js library.
- var fixMarkupRe = /((^(<[^>]+>|\t|)+|(?:\n)))/gm;
-
+ 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: undefined,
+ languages: null,
// beta configuration options, subject to change, welcome to discuss
// https://github.com/highlightjs/highlight.js/issues/1086
__emitter: TokenTreeEmitter
@@ -915,18 +1273,24 @@ var hljs = (function () {
/* Utility functions */
- function shouldNotHighlight(language) {
- return options.noHighlightRe.test(language);
+ /**
+ * Tests a language name to see if highlighting should be skipped
+ * @param {string} languageName
+ */
+ function shouldNotHighlight(languageName) {
+ return options.noHighlightRe.test(languageName);
}
+ /**
+ * @param {HighlightedHTMLElement} block - the HTML element to determine language for
+ */
function blockLanguage(block) {
- var match;
var classes = block.className + ' ';
classes += block.parentNode ? block.parentNode.className : '';
// language-* takes precedence over non-prefixed class names.
- match = options.languageDetectRe.exec(classes);
+ const match = options.languageDetectRe.exec(classes);
if (match) {
var language = getLanguage(match[1]);
if (!language) {
@@ -946,18 +1310,19 @@ var hljs = (function () {
*
* @param {string} languageName - the language to use for highlighting
* @param {string} code - the code to highlight
- * @param {boolean} ignore_illegals - whether to ignore illegal matches, default is to bail
- * @param {array<mode>} continuation - array of continuation modes
+ * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail
+ * @param {Mode} [continuation] - current continuation mode, if any
*
- * @returns an object that represents the result
+ * @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 {Mode} top - top of the current mode stack
* @property {boolean} illegal - indicates whether any illegal matches were found
*/
- function highlight(languageName, code, ignore_illegals, continuation) {
+ function highlight(languageName, code, ignoreIllegals, continuation) {
+ /** @type {{ code: string, language: string, result?: any }} */
var context = {
code,
language: languageName
@@ -970,7 +1335,7 @@ var hljs = (function () {
// in which case we don't even need to call highlight
var result = context.result ?
context.result :
- _highlight(context.language, context.code, ignore_illegals, continuation);
+ _highlight(context.language, context.code, ignoreIllegals, continuation);
result.code = context.code;
// the plugin can change anything in result to suite it
@@ -979,56 +1344,54 @@ var hljs = (function () {
return result;
}
- // private highlight that's used internally and does not fire callbacks
- function _highlight(languageName, code, ignore_illegals, continuation) {
+ /**
+ * private highlight that's used internally and does not fire callbacks
+ *
+ * @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;
- function endOfMode(mode, lexeme) {
- if (startsWith(mode.endRe, lexeme)) {
- while (mode.endsParent && mode.parent) {
- mode = mode.parent;
- }
- return mode;
- }
- if (mode.endsWithParent) {
- return endOfMode(mode.parent, lexeme);
- }
- }
-
- function keywordMatch(mode, match) {
- var match_str = language.case_insensitive ? match[0].toLowerCase() : match[0];
- return mode.keywords.hasOwnProperty(match_str) && mode.keywords[match_str];
+ /**
+ * 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];
}
function processKeywords() {
- var keyword_match, last_index, match, buf;
-
if (!top.keywords) {
emitter.addText(mode_buffer);
return;
}
- last_index = 0;
- top.lexemesRe.lastIndex = 0;
- match = top.lexemesRe.exec(mode_buffer);
- buf = "";
+ let last_index = 0;
+ top.keywordPatternRe.lastIndex = 0;
+ let match = top.keywordPatternRe.exec(mode_buffer);
+ let buf = "";
while (match) {
buf += mode_buffer.substring(last_index, match.index);
- keyword_match = keywordMatch(top, match);
- var kind = null;
- if (keyword_match) {
+ const data = keywordData(top, match);
+ if (data) {
+ const [kind, keywordRelevance] = data;
emitter.addText(buf);
buf = "";
- relevance += keyword_match[1];
- kind = keyword_match[0];
+ relevance += keywordRelevance;
emitter.addKeyword(match[0], kind);
} else {
buf += match[0];
}
- last_index = top.lexemesRe.lastIndex;
- match = top.lexemesRe.exec(mode_buffer);
+ last_index = top.keywordPatternRe.lastIndex;
+ match = top.keywordPatternRe.exec(mode_buffer);
}
buf += mode_buffer.substr(last_index);
emitter.addText(buf);
@@ -1036,18 +1399,20 @@ var hljs = (function () {
function processSubLanguage() {
if (mode_buffer === "") return;
+ /** @type HighlightResult */
+ var result = null;
- var explicit = typeof top.subLanguage === 'string';
-
- if (explicit && !languages[top.subLanguage]) {
- emitter.addText(mode_buffer);
- return;
+ 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);
}
- var result = explicit ?
- _highlight(top.subLanguage, mode_buffer, true, continuations[top.subLanguage]) :
- highlightAuto(mode_buffer, top.subLanguage.length ? top.subLanguage : undefined);
-
// 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
@@ -1055,27 +1420,64 @@ var hljs = (function () {
if (top.relevance > 0) {
relevance += result.relevance;
}
- if (explicit) {
- continuations[top.subLanguage] = result.top;
- }
emitter.addSublanguage(result.emitter, result.language);
}
function processBuffer() {
- if (top.subLanguage != null)
+ if (top.subLanguage != null) {
processSubLanguage();
- else
+ } else {
processKeywords();
+ }
mode_buffer = '';
}
+ /**
+ * @param {Mode} mode - new mode to start
+ */
function startNewMode(mode) {
if (mode.className) {
emitter.openNode(mode.className);
}
- top = Object.create(mode, {parent: {value: top}});
+ top = Object.create(mode, { parent: { value: top } });
+ return top;
}
+ /**
+ * @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);
+
+ 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;
+ }
+ }
+ // 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);
+ }
+ }
+
+ /**
+ * 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
@@ -1090,18 +1492,27 @@ var hljs = (function () {
}
}
+ /**
+ * 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) {
var lexeme = match[0];
var new_mode = match.rule;
- if (new_mode.__onBegin) {
- let res = new_mode.__onBegin(match) || {};
- if (res.ignoreMatch)
- return doIgnore(lexeme);
+ 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);
}
if (new_mode && new_mode.endSameAsBegin) {
- new_mode.endRe = escape( lexeme );
+ new_mode.endRe = escape(lexeme);
}
if (new_mode.skip) {
@@ -1116,14 +1527,24 @@ var hljs = (function () {
}
}
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);
- var end_mode = endOfMode(top, matchPlusRemainder);
- if (!end_mode) { return; }
+
+ var end_mode = endOfMode(top, match, matchPlusRemainder);
+ if (!end_mode) { return NO_MATCH; }
var origin = top;
if (origin.skip) {
@@ -1157,7 +1578,7 @@ var hljs = (function () {
function processContinuations() {
var list = [];
- for(var current = top; current !== language; current = current.parent) {
+ for (var current = top; current !== language; current = current.parent) {
if (current.className) {
list.unshift(current.className);
}
@@ -1165,50 +1586,57 @@ var hljs = (function () {
list.forEach(item => emitter.openNode(item));
}
+ /** @type {{type?: MatchType, index?: number, rule?: Mode}}} */
var lastMatch = {};
- function processLexeme(text_before_match, match) {
- var err;
+ /**
+ * 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];
// add non-matched text to the current mode buffer
- mode_buffer += text_before_match;
+ mode_buffer += textBeforeMatch;
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 === "") {
+ 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) {
- err = new Error('0 width match regex');
+ /** @type {AnnotatedError} */
+ const err = new Error('0 width match regex');
err.languageName = languageName;
err.badRule = lastMatch.rule;
- throw(err);
+ throw err;
}
return 1;
}
lastMatch = match;
- if (match.type==="begin") {
+ if (match.type === "begin") {
return doBeginMatch(match);
- } else if (match.type==="illegal" && !ignore_illegals) {
+ } else if (match.type === "illegal" && !ignoreIllegals) {
// illegal match, we do not continue processing
- err = new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.className || '<unnamed>') + '"');
+ /** @type {AnnotatedError} */
+ const err = new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.className || '<unnamed>') + '"');
err.mode = top;
throw err;
- } else if (match.type==="end") {
+ } else if (match.type === "end") {
var processed = doEndMatch(match);
- if (processed != undefined)
+ if (processed !== NO_MATCH) {
return processed;
+ }
}
// edge case for when illegal matches $ (end of line) which is technically
@@ -1250,16 +1678,16 @@ var hljs = (function () {
throw new Error('Unknown language: "' + languageName + '"');
}
- compileLanguage(language);
- var top = continuation || language;
+ var md = compileLanguage(language);
+ var result = '';
+ /** @type {CompiledMode} */
+ var top = continuation || md;
+ /** @type Record<string,Mode> */
var continuations = {}; // keep continuations for sub-languages
- var result;
var emitter = new options.__emitter(options);
processContinuations();
var mode_buffer = '';
var relevance = 0;
- var match;
- var processedCount;
var index = 0;
var iterations = 0;
var continueScanAtSamePosition = false;
@@ -1270,19 +1698,19 @@ var hljs = (function () {
for (;;) {
iterations++;
if (continueScanAtSamePosition) {
- continueScanAtSamePosition = false;
// only regexes not matched previously will now be
// considered for a potential match
+ continueScanAtSamePosition = false;
} else {
top.matcher.lastIndex = index;
top.matcher.considerAll();
}
- match = top.matcher.exec(codeToHighlight);
+ const match = top.matcher.exec(codeToHighlight);
// console.log("match", match[0], match.rule && match.rule.begin)
- if (!match)
- break;
- let beforeMatch = codeToHighlight.substring(index, match.index);
- processedCount = processLexeme(beforeMatch, match);
+ if (!match) break;
+
+ const beforeMatch = codeToHighlight.substring(index, match.index);
+ const processedCount = processLexeme(beforeMatch, match);
index = match.index + processedCount;
}
processLexeme(codeToHighlight.substr(index));
@@ -1304,16 +1732,17 @@ var hljs = (function () {
illegal: true,
illegalBy: {
msg: err.message,
- context: codeToHighlight.slice(index-100,index+100),
+ context: codeToHighlight.slice(index - 100, index + 100),
mode: err.mode
},
sofar: result,
relevance: 0,
value: escape$1(codeToHighlight),
- emitter: emitter,
+ emitter: emitter
};
} else if (SAFE_MODE) {
return {
+ illegal: false,
relevance: 0,
value: escape$1(codeToHighlight),
emitter: emitter,
@@ -1327,10 +1756,13 @@ var hljs = (function () {
}
}
- // 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
+ /**
+ * 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,
@@ -1343,7 +1775,7 @@ var hljs = (function () {
return result;
}
- /*
+ /**
Highlighting with language detection. Accepts a string with the code to
highlight. Returns an object with the following properties:
@@ -1353,53 +1785,66 @@ var hljs = (function () {
- second_best (object with the same structure for second-best heuristically
detected language, may be absent)
+ @param {string} code
+ @param {Array<string>} [languageSubset]
+ @returns {AutoHighlightResult}
*/
function highlightAuto(code, languageSubset) {
languageSubset = languageSubset || options.languages || Object.keys(languages);
var result = justTextHighlightResult(code);
- var second_best = result;
+ var secondBest = result;
languageSubset.filter(getLanguage).filter(autoDetection).forEach(function(name) {
var current = _highlight(name, code, false);
current.language = name;
- if (current.relevance > second_best.relevance) {
- second_best = current;
+ if (current.relevance > secondBest.relevance) {
+ secondBest = current;
}
if (current.relevance > result.relevance) {
- second_best = result;
+ secondBest = result;
result = current;
}
});
- if (second_best.language) {
- result.second_best = second_best;
+ if (secondBest.language) {
+ // second_best (with underscore) is the expected API
+ result.second_best = secondBest;
}
return result;
}
- /*
+ /**
Post-processing of the highlighted markup:
- replace TABs with something more useful
- replace real line-breaks with '<br>' for non-pre containers
+ @param {string} html
+ @returns {string}
*/
- function fixMarkup(value) {
+ function fixMarkup(html) {
if (!(options.tabReplace || options.useBR)) {
- return value;
+ return html;
}
- return value.replace(fixMarkupRe, function(match, p1) {
- if (options.useBR && match === '\n') {
- return '<br>';
- } else if (options.tabReplace) {
- return p1.replace(/\t/g, options.tabReplace);
- }
- return '';
+ return html.replace(fixMarkupRe, match => {
+ if (match === '\n') {
+ return options.useBR ? '<br>' : match;
+ } else if (options.tabReplace) {
+ return match.replace(/\t/g, options.tabReplace);
+ }
+ 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,
- result = [prevClassName.trim()];
+ var language = currentLang ? aliases[currentLang] : resultLang;
+ var result = [prevClassName.trim()];
if (!prevClassName.match(/\bhljs\b/)) {
result.push('hljs');
@@ -1412,86 +1857,99 @@ var hljs = (function () {
return result.join(' ').trim();
}
- /*
- Applies highlighting to a DOM node containing code. Accepts a DOM node and
- two optional parameters for fixMarkup.
+ /**
+ * 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(block) {
- var node, originalStream, result, resultNode, text;
- var language = blockLanguage(block);
+ function highlightBlock(element) {
+ /** @type HTMLElement */
+ let node = null;
+ const language = blockLanguage(element);
- if (shouldNotHighlight(language))
- return;
+ if (shouldNotHighlight(language)) return;
fire("before:highlightBlock",
- { block: block, language: language});
+ { block: element, language: language });
if (options.useBR) {
node = document.createElement('div');
- node.innerHTML = block.innerHTML.replace(/\n/g, '').replace(/<br[ \/]*>/g, '\n');
+ node.innerHTML = element.innerHTML.replace(/\n/g, '').replace(/<br[ /]*>/g, '\n');
} else {
- node = block;
+ node = element;
}
- text = node.textContent;
- result = language ? highlight(language, text, true) : highlightAuto(text);
+ const text = node.textContent;
+ const result = language ? highlight(language, text, true) : highlightAuto(text);
- originalStream = nodeStream$1(node);
+ const originalStream = nodeStream$1(node);
if (originalStream.length) {
- resultNode = document.createElement('div');
+ const resultNode = document.createElement('div');
resultNode.innerHTML = result.value;
result.value = mergeStreams$1(originalStream, nodeStream$1(resultNode), text);
}
result.value = fixMarkup(result.value);
- fire("after:highlightBlock", { block: block, result: result});
+ fire("after:highlightBlock", { block: element, result: result });
- block.innerHTML = result.value;
- block.className = buildClassName(block.className, language, result.language);
- block.result = {
+ element.innerHTML = result.value;
+ element.className = buildClassName(element.className, language, result.language);
+ element.result = {
language: result.language,
- re: result.relevance
+ // TODO: remove with version 11.0
+ re: result.relevance,
+ relavance: result.relevance
};
if (result.second_best) {
- block.second_best = {
+ element.second_best = {
language: result.second_best.language,
- re: result.second_best.relevance
+ // TODO: remove with version 11.0
+ re: result.second_best.relevance,
+ relavance: result.second_best.relevance
};
}
}
- /*
- Updates highlight.js global options with values passed in the form of an object.
- */
- function configure(user_options) {
- options = inherit$1(options, user_options);
+ /**
+ * Updates highlight.js global options with the passed options
+ *
+ * @param {{}} userOptions
+ */
+ function configure(userOptions) {
+ options = inherit$1(options, userOptions);
}
- /*
- Applies highlighting to all <pre><code>..</code></pre> blocks on a page.
- */
- function initHighlighting() {
- if (initHighlighting.called)
- return;
+ /**
+ * Highlights to all <pre><code> blocks on a page
+ *
+ * @type {Function & {called?: boolean}}
+ */
+ const initHighlighting = () => {
+ if (initHighlighting.called) return;
initHighlighting.called = true;
var blocks = document.querySelectorAll('pre code');
ArrayProto.forEach.call(blocks, highlightBlock);
- }
+ };
- /*
- Attaches highlighting to the page load event.
- */
+ // Higlights all when DOMContentLoaded fires
function initHighlightingOnLoad() {
+ // @ts-ignore
window.addEventListener('DOMContentLoaded', initHighlighting, false);
}
- const PLAINTEXT_LANGUAGE = { disableAutodetect: true, name: 'Plain text' };
-
- function registerLanguage(name, language) {
- var lang;
- try { lang = language(hljs); }
- catch (error) {
- console.error("Language definition for '{}' could not be registered.".replace("{}", name));
+ /**
+ * 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
@@ -1501,51 +1959,84 @@ var hljs = (function () {
lang = PLAINTEXT_LANGUAGE;
}
// give it a temporary name if it doesn't have one in the meta-data
- if (!lang.name)
- lang.name = name;
- languages[name] = lang;
- lang.rawDefinition = language.bind(null,hljs);
+ if (!lang.name) lang.name = languageName;
+ languages[languageName] = lang;
+ lang.rawDefinition = languageDefinition.bind(null, hljs);
if (lang.aliases) {
- lang.aliases.forEach(function(alias) {aliases[alias] = name;});
+ registerAliases(lang.aliases, { languageName });
}
}
+ /**
+ * @returns {string[]} List of language internal names
+ */
function listLanguages() {
return Object.keys(languages);
}
- /*
+ /**
intended usage: When one language truly requires another
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) {
var lang = getLanguage(name);
if (lang) { return lang; }
- var err = new Error('The \'{}\' language is required, but not loaded.'.replace('{}',name));
+ var 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|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; });
+ }
+
+ /**
+ * 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;
}
- function addPlugin(plugin, options) {
+ /**
+ * @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) {
+ plugins.forEach(function(plugin) {
if (plugin[cb]) {
plugin[cb](args);
}
@@ -1554,7 +2045,7 @@ var hljs = (function () {
/* Interface definition */
- Object.assign(hljs,{
+ Object.assign(hljs, {
highlight,
highlightAuto,
fixMarkup,
@@ -1565,6 +2056,7 @@ var hljs = (function () {
registerLanguage,
listLanguages,
getLanguage,
+ registerAliases,
requireLanguage,
autoDetection,
inherit: inherit$1,
@@ -1576,8 +2068,11 @@ var hljs = (function () {
hljs.versionString = version;
for (const key in MODES) {
- if (typeof MODES[key] === "object")
+ // @ts-ignore
+ if (typeof MODES[key] === "object") {
+ // @ts-ignore
deepFreeze(MODES[key]);
+ }
}
// merge all the modes/regexs into our main object
@@ -1603,6 +2098,7 @@ hljs.registerLanguage('css', function () {
Website: https://developer.mozilla.org/en-US/docs/Web/CSS
*/
+ /** @type LanguageFn */
function css(hljs) {
var FUNCTION_LIKE = {
begin: /[\w-]+\(/, returnBegin: true,
@@ -1738,6 +2234,177 @@ hljs.registerLanguage('css', function () {
hljs.registerLanguage('javascript', function () {
'use strict';
+ const IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*';
+ const KEYWORDS = [
+ "as", // for exports
+ "in",
+ "of",
+ "if",
+ "for",
+ "while",
+ "finally",
+ "var",
+ "new",
+ "function",
+ "do",
+ "return",
+ "void",
+ "else",
+ "break",
+ "catch",
+ "instanceof",
+ "with",
+ "throw",
+ "case",
+ "default",
+ "try",
+ "switch",
+ "continue",
+ "typeof",
+ "delete",
+ "let",
+ "yield",
+ "const",
+ "class",
+ // JS handles these with a special rule
+ // "get",
+ // "set",
+ "debugger",
+ "async",
+ "await",
+ "static",
+ "import",
+ "from",
+ "export",
+ "extends"
+ ];
+ const LITERALS = [
+ "true",
+ "false",
+ "null",
+ "undefined",
+ "NaN",
+ "Infinity"
+ ];
+
+ const TYPES = [
+ "Intl",
+ "DataView",
+ "Number",
+ "Math",
+ "Date",
+ "String",
+ "RegExp",
+ "Object",
+ "Function",
+ "Boolean",
+ "Error",
+ "Symbol",
+ "Set",
+ "Map",
+ "WeakSet",
+ "WeakMap",
+ "Proxy",
+ "Reflect",
+ "JSON",
+ "Promise",
+ "Float64Array",
+ "Int16Array",
+ "Int32Array",
+ "Int8Array",
+ "Uint16Array",
+ "Uint32Array",
+ "Float32Array",
+ "Array",
+ "Uint8Array",
+ "Uint8ClampedArray",
+ "ArrayBuffer"
+ ];
+
+ const ERROR_TYPES = [
+ "EvalError",
+ "InternalError",
+ "RangeError",
+ "ReferenceError",
+ "SyntaxError",
+ "TypeError",
+ "URIError"
+ ];
+
+ const BUILT_IN_GLOBALS = [
+ "setInterval",
+ "setTimeout",
+ "clearInterval",
+ "clearTimeout",
+
+ "require",
+ "exports",
+
+ "eval",
+ "isFinite",
+ "isNaN",
+ "parseFloat",
+ "parseInt",
+ "decodeURI",
+ "decodeURIComponent",
+ "encodeURI",
+ "encodeURIComponent",
+ "escape",
+ "unescape"
+ ];
+
+ const BUILT_IN_VARIABLES = [
+ "arguments",
+ "this",
+ "super",
+ "console",
+ "window",
+ "document",
+ "localStorage",
+ "module",
+ "global" // Node.js
+ ];
+
+ const BUILT_INS = [].concat(
+ BUILT_IN_GLOBALS,
+ BUILT_IN_VARIABLES,
+ TYPES,
+ ERROR_TYPES
+ );
+
+ /**
+ * @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) } args
+ * @returns {string}
+ */
+ function concat(...args) {
+ const joined = args.map((x) => source(x)).join("");
+ return joined;
+ }
+
/*
Language: JavaScript
Description: JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions.
@@ -1746,6 +2413,7 @@ hljs.registerLanguage('javascript', function () {
*/
function javascript(hljs) {
+ var IDENT_RE$1 = IDENT_RE;
var FRAGMENT = {
begin: '<>',
end: '</>'
@@ -1754,26 +2422,11 @@ hljs.registerLanguage('javascript', function () {
begin: /<[A-Za-z0-9\\._:-]+/,
end: /\/[A-Za-z0-9\\._:-]+>|\/>/
};
- var IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*';
- var KEYWORDS = {
- keyword:
- 'in of if for while finally var new function do return void else break catch ' +
- 'instanceof with throw case default try this switch continue typeof delete ' +
- 'let yield const export super debugger as async await static ' +
- // ECMAScript 6 modules import
- 'import from as'
- ,
- literal:
- 'true false null undefined NaN Infinity',
- built_in:
- 'eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent ' +
- 'encodeURI encodeURIComponent escape unescape Object Function Boolean Error ' +
- 'EvalError InternalError RangeError ReferenceError StopIteration SyntaxError ' +
- 'TypeError URIError Number Math Date String RegExp Array Float32Array ' +
- 'Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array ' +
- 'Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require ' +
- 'module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect ' +
- 'Promise'
+ var KEYWORDS$1 = {
+ $pattern: IDENT_RE,
+ keyword: KEYWORDS.join(" "),
+ literal: LITERALS.join(" "),
+ built_in: BUILT_INS.join(" ")
};
var NUMBER = {
className: 'number',
@@ -1787,7 +2440,7 @@ hljs.registerLanguage('javascript', function () {
var SUBST = {
className: 'subst',
begin: '\\$\\{', end: '\\}',
- keywords: KEYWORDS,
+ keywords: KEYWORDS$1,
contains: [] // defined later
};
var HTML_TEMPLATE = {
@@ -1830,6 +2483,10 @@ hljs.registerLanguage('javascript', function () {
hljs.REGEXP_MODE
];
var PARAMS_CONTAINS = SUBST.contains.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
]);
@@ -1844,17 +2501,17 @@ hljs.registerLanguage('javascript', function () {
return {
name: 'JavaScript',
aliases: ['js', 'jsx', 'mjs', 'cjs'],
- keywords: KEYWORDS,
+ keywords: KEYWORDS$1,
contains: [
+ hljs.SHEBANG({
+ binary: "node",
+ relevance: 5
+ }),
{
className: 'meta',
relevance: 10,
begin: /^\s*['"]use (strict|asm)['"]/
},
- {
- className: 'meta',
- begin: /^#!/, end: /$/
- },
hljs.APOS_STRING_MODE,
hljs.QUOTE_STRING_MODE,
HTML_TEMPLATE,
@@ -1879,7 +2536,7 @@ hljs.registerLanguage('javascript', function () {
},
{
className: 'variable',
- begin: IDENT_RE + '(?=\\s*(-)|$)',
+ begin: IDENT_RE$1 + '(?=\\s*(-)|$)',
endsParent: true,
relevance: 0
},
@@ -1897,13 +2554,29 @@ hljs.registerLanguage('javascript', function () {
hljs.C_BLOCK_COMMENT_MODE,
NUMBER,
{ // object attr container
- begin: /[{,\n]\s*/, relevance: 0,
+ begin: concat(/[{,\n]\s*/,
+ // we need to look ahead to make sure that we actually have an
+ // attribute coming up so we don't steal a comma from a potential
+ // "value" container
+ //
+ // NOTE: this might not work how you think. We don't actually always
+ // enter this mode and stay. Instead it might merely match `,
+ // <comments up next>` and then immediately end after the , because it
+ // fails to find any actual attrs. But this still does the job because
+ // it prevents the value contain rule from grabbing this instead and
+ // prevening this rule from firing when we actually DO have keys.
+ lookahead(concat(
+ // we also need to allow for multiple possible comments inbetween
+ // the first key:value pairing
+ /(((\/\/.*)|(\/\*(.|\n)*\*\/))\s*)*/,
+ IDENT_RE$1 + '\\s*:'))),
+ relevance: 0,
contains: [
{
- begin: IDENT_RE + '\\s*:', returnBegin: true,
+ className: 'attr',
+ begin: IDENT_RE$1 + lookahead('\\s*:'),
relevance: 0,
- contains: [{className: 'attr', begin: IDENT_RE, relevance: 0}]
- }
+ },
]
},
{ // "value" container
@@ -1915,22 +2588,32 @@ hljs.registerLanguage('javascript', function () {
hljs.REGEXP_MODE,
{
className: 'function',
- begin: '(\\(.*?\\)|' + IDENT_RE + ')\\s*=>', returnBegin: true,
+ // 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,
end: '\\s*=>',
contains: [
{
className: 'params',
variants: [
{
- begin: IDENT_RE
+ begin: hljs.UNDERSCORE_IDENT_RE
},
{
+ className: null,
begin: /\(\s*\)/,
+ skip: true
},
{
begin: /\(/, end: /\)/,
excludeBegin: true, excludeEnd: true,
- keywords: KEYWORDS,
+ keywords: KEYWORDS$1,
contains: PARAMS_CONTAINS
}
]
@@ -1966,7 +2649,7 @@ hljs.registerLanguage('javascript', function () {
className: 'function',
beginKeywords: 'function', end: /\{/, excludeEnd: true,
contains: [
- hljs.inherit(hljs.TITLE_MODE, {begin: IDENT_RE}),
+ hljs.inherit(hljs.TITLE_MODE, {begin: IDENT_RE$1}),
PARAMS
],
illegal: /\[|%/
@@ -1989,11 +2672,11 @@ hljs.registerLanguage('javascript', function () {
beginKeywords: 'constructor', end: /\{/, excludeEnd: true
},
{
- begin:'(get|set)\\s*(?=' + IDENT_RE+ '\\()',
+ begin: '(get|set)\\s+(?=' + IDENT_RE$1 + '\\()',
end: /{/,
keywords: "get set",
contains: [
- hljs.inherit(hljs.TITLE_MODE, {begin: IDENT_RE}),
+ hljs.inherit(hljs.TITLE_MODE, {begin: IDENT_RE$1}),
{ begin: /\(\)/ }, // eat to avoid empty params
PARAMS
]