import deepFreeze from 'deep-freeze-es6';
import Response from './lib/response.js';
import TokenTreeEmitter from './lib/token_tree.js';
import * as regex from './lib/regex.js';
import * as utils from './lib/utils.js';
import * as MODES from './lib/modes.js';
import { compileLanguage } from './lib/mode_compiler.js';
import * as packageJSON from '../package.json';
import * as logger from "./lib/logger.js";
import HTMLInjectionError from "./lib/html_injection_error.js";
const escape = utils.escapeHTML;
const inherit = utils.inherit;
const NO_MATCH = Symbol("nomatch");
const MAX_KEYWORD_HITS = 7;
const HLJS = function(hljs) {
const languages = Object.create(null);
const aliases = Object.create(null);
const plugins = [];
let SAFE_MODE = true;
const LANGUAGE_NOT_FOUND = "Could not find the language '{}', did you forget to load/include a language module?";
const PLAINTEXT_LANGUAGE = { disableAutodetect: true, name: 'Plain text', contains: [] };
let options = {
ignoreUnescapedHTML: false,
throwUnescapedHTML: false,
noHighlightRe: /^(no-?highlight)$/i,
languageDetectRe: /\blang(?:uage)?-([\w-]+)\b/i,
classPrefix: 'hljs-',
cssSelector: 'pre code',
languages: null,
__emitter: TokenTreeEmitter
};
function shouldNotHighlight(languageName) {
return options.noHighlightRe.test(languageName);
}
function blockLanguage(block) {
let classes = block.className + ' ';
classes += block.parentNode ? block.parentNode.className : '';
const match = options.languageDetectRe.exec(classes);
if (match) {
const language = getLanguage(match[1]);
if (!language) {
logger.warn(LANGUAGE_NOT_FOUND.replace("{}", match[1]));
logger.warn("Falling back to no-highlight mode for this block.", block);
}
return language ? match[1] : 'no-highlight';
}
return classes
.split(/\s+/)
.find((_class) => shouldNotHighlight(_class) || getLanguage(_class));
}
function highlight(codeOrLanguageName, optionsOrCode, ignoreIllegals) {
let code = "";
let languageName = "";
if (typeof optionsOrCode === "object") {
code = codeOrLanguageName;
ignoreIllegals = optionsOrCode.ignoreIllegals;
languageName = optionsOrCode.language;
} else {
logger.deprecated("10.7.0", "highlight(lang, code, ...args) has been deprecated.");
logger.deprecated("10.7.0", "Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277");
languageName = codeOrLanguageName;
code = optionsOrCode;
}
if (ignoreIllegals === undefined) { ignoreIllegals = true; }
const context = {
code,
language: languageName
};
fire("before:highlight", context);
const result = context.result
? context.result
: _highlight(context.language, context.code, ignoreIllegals);
result.code = context.code;
fire("after:highlight", result);
return result;
}
function _highlight(languageName, codeToHighlight, ignoreIllegals, continuation) {
const keywordHits = Object.create(null);
function keywordData(mode, matchText) {
return mode.keywords[matchText];
}
function processKeywords() {
if (!top.keywords) {
emitter.addText(modeBuffer);
return;
}
let lastIndex = 0;
top.keywordPatternRe.lastIndex = 0;
let match = top.keywordPatternRe.exec(modeBuffer);
let buf = "";
while (match) {
buf += modeBuffer.substring(lastIndex, match.index);
const word = language.case_insensitive ? match[0].toLowerCase() : match[0];
const data = keywordData(top, word);
if (data) {
const [kind, keywordRelevance] = data;
emitter.addText(buf);
buf = "";
keywordHits[word] = (keywordHits[word] || 0) + 1;
if (keywordHits[word] <= MAX_KEYWORD_HITS) relevance += keywordRelevance;
if (kind.startsWith("_")) {
buf += match[0];
} else {
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);
}
function processSubLanguage() {
if (modeBuffer === "") return;
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] = (result._top);
} else {
result = highlightAuto(modeBuffer, top.subLanguage.length ? top.subLanguage : null);
}
if (top.relevance > 0) {
relevance += result.relevance;
}
emitter.addSublanguage(result._emitter, result.language);
}
function processBuffer() {
if (top.subLanguage != null) {
processSubLanguage();
} else {
processKeywords();
}
modeBuffer = '';
}
function emitMultiClass(scope, match) {
let i = 1;
const max = match.length - 1;
while (i <= max) {
if (!scope._emit[i]) { i++; continue; }
const klass = language.classNameAliases[scope[i]] || scope[i];
const text = match[i];
if (klass) {
emitter.addKeyword(text, klass);
} else {
modeBuffer = text;
processKeywords();
modeBuffer = "";
}
i++;
}
}
function startNewMode(mode, match) {
if (mode.scope && typeof mode.scope === "string") {
emitter.openNode(language.classNameAliases[mode.scope] || mode.scope);
}
if (mode.beginScope) {
if (mode.beginScope._wrap) {
emitter.addKeyword(modeBuffer, language.classNameAliases[mode.beginScope._wrap] || mode.beginScope._wrap);
modeBuffer = "";
} else if (mode.beginScope._multi) {
emitMultiClass(mode.beginScope, match);
modeBuffer = "";
}
}
top = Object.create(mode, { parent: { value: top } });
return top;
}
function endOfMode(mode, match, matchPlusRemainder) {
let matched = regex.startsWith(mode.endRe, matchPlusRemainder);
if (matched) {
if (mode["on:end"]) {
const resp = new Response(mode);
mode["on:end"](match, resp);
if (resp.isMatchIgnored) matched = false;
}
if (matched) {
while (mode.endsParent && mode.parent) {
mode = mode.parent;
}
return mode;
}
}
if (mode.endsWithParent) {
return endOfMode(mode.parent, match, matchPlusRemainder);
}
}
function doIgnore(lexeme) {
if (top.matcher.regexIndex === 0) {
modeBuffer += lexeme[0];
return 1;
} else {
resumeScanAtSamePosition = true;
return 0;
}
}
function doBeginMatch(match) {
const lexeme = match[0];
const newMode = match.rule;
const resp = new Response(newMode);
const beforeCallbacks = [newMode.__beforeBegin, newMode["on:begin"]];
for (const cb of beforeCallbacks) {
if (!cb) continue;
cb(match, resp);
if (resp.isMatchIgnored) return doIgnore(lexeme);
}
if (newMode.skip) {
modeBuffer += lexeme;
} else {
if (newMode.excludeBegin) {
modeBuffer += lexeme;
}
processBuffer();
if (!newMode.returnBegin && !newMode.excludeBegin) {
modeBuffer = lexeme;
}
}
startNewMode(newMode, match);
return newMode.returnBegin ? 0 : lexeme.length;
}
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 (top.endScope && top.endScope._wrap) {
processBuffer();
emitter.addKeyword(lexeme, top.endScope._wrap);
} else if (top.endScope && top.endScope._multi) {
processBuffer();
emitMultiClass(top.endScope, match);
} else if (origin.skip) {
modeBuffer += lexeme;
} else {
if (!(origin.returnEnd || origin.excludeEnd)) {
modeBuffer += lexeme;
}
processBuffer();
if (origin.excludeEnd) {
modeBuffer = lexeme;
}
}
do {
if (top.scope) {
emitter.closeNode();
}
if (!top.skip && !top.subLanguage) {
relevance += top.relevance;
}
top = top.parent;
} while (top !== endMode.parent);
if (endMode.starts) {
startNewMode(endMode.starts, match);
}
return origin.returnEnd ? 0 : lexeme.length;
}
function processContinuations() {
const list = [];
for (let current = top; current !== language; current = current.parent) {
if (current.scope) {
list.unshift(current.scope);
}
}
list.forEach(item => emitter.openNode(item));
}
let lastMatch = {};
function processLexeme(textBeforeMatch, match) {
const lexeme = match && match[0];
modeBuffer += textBeforeMatch;
if (lexeme == null) {
processBuffer();
return 0;
}
if (lastMatch.type === "begin" && match.type === "end" && lastMatch.index === match.index && lexeme === "") {
modeBuffer += codeToHighlight.slice(match.index, match.index + 1);
if (!SAFE_MODE) {
const err = new Error(`0 width match regex (${languageName})`);
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) {
const err = new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.scope || '<unnamed>') + '"');
err.mode = top;
throw err;
} else if (match.type === "end") {
const processed = doEndMatch(match);
if (processed !== NO_MATCH) {
return processed;
}
}
if (match.type === "illegal" && lexeme === "") {
return 1;
}
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;
}
const language = getLanguage(languageName);
if (!language) {
logger.error(LANGUAGE_NOT_FOUND.replace("{}", languageName));
throw new Error('Unknown language: "' + languageName + '"');
}
const md = compileLanguage(language);
let result = '';
let top = continuation || md;
const continuations = {};
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) {
resumeScanAtSamePosition = false;
} else {
top.matcher.considerAll();
}
top.matcher.lastIndex = index;
const match = top.matcher.exec(codeToHighlight);
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();
return {
language: languageName,
value: result,
relevance: relevance,
illegal: false,
_emitter: emitter,
_top: top
};
} catch (err) {
if (err.message && err.message.includes('Illegal')) {
return {
language: languageName,
value: escape(codeToHighlight),
illegal: true,
relevance: 0,
_illegalBy: {
message: err.message,
index: index,
context: codeToHighlight.slice(index - 100, index + 100),
mode: err.mode,
resultSoFar: result
},
_emitter: emitter
};
} else if (SAFE_MODE) {
return {
language: languageName,
value: escape(codeToHighlight),
illegal: false,
relevance: 0,
errorRaised: err,
_emitter: emitter,
_top: top
};
} else {
throw err;
}
}
}
function justTextHighlightResult(code) {
const result = {
value: escape(code),
illegal: false,
relevance: 0,
_top: PLAINTEXT_LANGUAGE,
_emitter: new options.__emitter(options)
};
result._emitter.addText(code);
return result;
}
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);
const sorted = results.sort((a, b) => {
if (a.relevance !== b.relevance) return b.relevance - a.relevance;
if (a.language && b.language) {
if (getLanguage(a.language).supersetOf === b.language) {
return 1;
} else if (getLanguage(b.language).supersetOf === a.language) {
return -1;
}
}
return 0;
});
const [best, secondBest] = sorted;
const result = best;
result.secondBest = secondBest;
return result;
}
function updateClassName(element, currentLang, resultLang) {
const language = (currentLang && aliases[currentLang]) || resultLang;
element.classList.add("hljs");
element.classList.add(`language-${language}`);
}
function highlightElement(element) {
let node = null;
const language = blockLanguage(element);
if (shouldNotHighlight(language)) return;
fire("before:highlightElement",
{ el: element, language: language });
if (element.children.length > 0) {
if (!options.ignoreUnescapedHTML) {
console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk.");
console.warn("https://github.com/highlightjs/highlight.js/wiki/security");
console.warn("The element with unescaped HTML:");
console.warn(element);
}
if (options.throwUnescapedHTML) {
const err = new HTMLInjectionError(
"One of your code blocks includes unescaped HTML.",
element.innerHTML
);
throw err;
}
}
node = element;
const text = node.textContent;
const result = language ? highlight(text, { language, ignoreIllegals: true }) : highlightAuto(text);
element.innerHTML = result.value;
updateClassName(element, language, result.language);
element.result = {
language: result.language,
re: result.relevance,
relevance: result.relevance
};
if (result.secondBest) {
element.secondBest = {
language: result.secondBest.language,
relevance: result.secondBest.relevance
};
}
fire("after:highlightElement", { el: element, result, text });
}
function configure(userOptions) {
options = inherit(options, userOptions);
}
const initHighlighting = () => {
highlightAll();
logger.deprecated("10.6.0", "initHighlighting() deprecated. Use highlightAll() now.");
};
function initHighlightingOnLoad() {
highlightAll();
logger.deprecated("10.6.0", "initHighlightingOnLoad() deprecated. Use highlightAll() now.");
}
let wantsHighlight = false;
function highlightAll() {
if (document.readyState === "loading") {
wantsHighlight = true;
return;
}
const blocks = document.querySelectorAll(options.cssSelector);
blocks.forEach(highlightElement);
}
function boot() {
if (wantsHighlight) highlightAll();
}
if (typeof window !== 'undefined' && window.addEventListener) {
window.addEventListener('DOMContentLoaded', boot, false);
}
function registerLanguage(languageName, languageDefinition) {
let lang = null;
try {
lang = languageDefinition(hljs);
} catch (error) {
logger.error("Language definition for '{}' could not be registered.".replace("{}", languageName));
if (!SAFE_MODE) { throw error; } else { logger.error(error); }
lang = PLAINTEXT_LANGUAGE;
}
if (!lang.name) lang.name = languageName;
languages[languageName] = lang;
lang.rawDefinition = languageDefinition.bind(null, hljs);
if (lang.aliases) {
registerAliases(lang.aliases, { languageName });
}
}
function unregisterLanguage(languageName) {
delete languages[languageName];
for (const alias of Object.keys(aliases)) {
if (aliases[alias] === languageName) {
delete aliases[alias];
}
}
}
function listLanguages() {
return Object.keys(languages);
}
function getLanguage(name) {
name = (name || '').toLowerCase();
return languages[name] || languages[aliases[name]];
}
function registerAliases(aliasList, { languageName }) {
if (typeof aliasList === 'string') {
aliasList = [aliasList];
}
aliasList.forEach(alias => { aliases[alias.toLowerCase()] = languageName; });
}
function autoDetection(name) {
const lang = getLanguage(name);
return lang && !lang.disableAutodetect;
}
function upgradePluginAPI(plugin) {
if (plugin["before:highlightBlock"] && !plugin["before:highlightElement"]) {
plugin["before:highlightElement"] = (data) => {
plugin["before:highlightBlock"](
Object.assign({ block: data.el }, data)
);
};
}
if (plugin["after:highlightBlock"] && !plugin["after:highlightElement"]) {
plugin["after:highlightElement"] = (data) => {
plugin["after:highlightBlock"](
Object.assign({ block: data.el }, data)
);
};
}
}
function addPlugin(plugin) {
upgradePluginAPI(plugin);
plugins.push(plugin);
}
function fire(event, args) {
const cb = event;
plugins.forEach(function(plugin) {
if (plugin[cb]) {
plugin[cb](args);
}
});
}
function deprecateHighlightBlock(el) {
logger.deprecated("10.7.0", "highlightBlock will be removed entirely in v12.0");
logger.deprecated("10.7.0", "Please use highlightElement now.");
return highlightElement(el);
}
Object.assign(hljs, {
highlight,
highlightAuto,
highlightAll,
highlightElement,
highlightBlock: deprecateHighlightBlock,
configure,
initHighlighting,
initHighlightingOnLoad,
registerLanguage,
unregisterLanguage,
listLanguages,
getLanguage,
registerAliases,
autoDetection,
inherit,
addPlugin
});
hljs.debugMode = function() { SAFE_MODE = false; };
hljs.safeMode = function() { SAFE_MODE = true; };
hljs.versionString = packageJSON.version;
hljs.regex = {
concat: regex.concat,
lookahead: regex.lookahead,
either: regex.either,
optional: regex.optional,
anyNumberOfTimes: regex.anyNumberOfTimes
};
for (const key in MODES) {
if (typeof MODES[key] === "object") {
deepFreeze(MODES[key]);
}
}
Object.assign(hljs, MODES);
return hljs;
};
export default HLJS({});