/* Syntax highlighting with language autodetection. https://highlightjs.org/ */ // @ts-ignore 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"; /** @typedef {import('highlight.js').Mode} Mode @typedef {import('highlight.js').CompiledMode} CompiledMode @typedef {import('highlight.js').CompiledScope} CompiledScope @typedef {import('highlight.js').Language} Language @typedef {import('highlight.js').HLJSApi} HLJSApi @typedef {import('highlight.js').HLJSPlugin} HLJSPlugin @typedef {import('highlight.js').PluginEvent} PluginEvent @typedef {import('highlight.js').HLJSOptions} HLJSOptions @typedef {import('highlight.js').LanguageFn} LanguageFn @typedef {import('highlight.js').HighlightedHTMLElement} HighlightedHTMLElement @typedef {import('highlight.js').BeforeHighlightContext} BeforeHighlightContext @typedef {import('highlight.js/private').MatchType} MatchType @typedef {import('highlight.js/private').KeywordData} KeywordData @typedef {import('highlight.js/private').EnhancedMatch} EnhancedMatch @typedef {import('highlight.js/private').AnnotatedError} AnnotatedError @typedef {import('highlight.js').AutoHighlightResult} AutoHighlightResult @typedef {import('highlight.js').HighlightOptions} HighlightOptions @typedef {import('highlight.js').HighlightResult} HighlightResult */ const escape = utils.escapeHTML; const inherit = utils.inherit; const NO_MATCH = Symbol("nomatch"); const MAX_KEYWORD_HITS = 7; /** * @param {any} hljs - object that is extended (legacy) * @returns {HLJSApi} */ const HLJS = function(hljs) { // 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 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 = { ignoreUnescapedHTML: false, throwUnescapedHTML: false, noHighlightRe: /^(no-?highlight)$/i, languageDetectRe: /\blang(?:uage)?-([\w-]+)\b/i, classPrefix: 'hljs-', cssSelector: 'pre code', languages: null, // beta configuration options, subject to change, welcome to discuss // https://github.com/highlightjs/highlight.js/issues/1086 __emitter: TokenTreeEmitter }; /* Utility functions */ /** * 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) { 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) { 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)); } /** * Core highlighting function. * * OLD API * highlight(lang, code, ignoreIllegals, continuation) * * NEW API * highlight(code, {lang, ignoreIllegals}) * * @param {string} codeOrLanguageName - the language to use for highlighting * @param {string | HighlightOptions} optionsOrCode - the code to highlight * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail * * @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(codeOrLanguageName, optionsOrCode, ignoreIllegals) { let code = ""; let languageName = ""; if (typeof optionsOrCode === "object") { code = codeOrLanguageName; ignoreIllegals = optionsOrCode.ignoreIllegals; languageName = optionsOrCode.language; } else { // old API 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; } // https://github.com/highlightjs/highlight.js/issues/3149 // eslint-disable-next-line no-undefined if (ignoreIllegals === undefined) { ignoreIllegals = true; } /** @type {BeforeHighlightContext} */ 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); result.code = context.code; // the plugin can change anything in result to suite it fire("after:highlight", result); return result; } /** * private highlight that's used internally and does not fire callbacks * * @param {string} languageName - the language to use for highlighting * @param {string} codeToHighlight - 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, codeToHighlight, ignoreIllegals, continuation) { const keywordHits = Object.create(null); /** * Return keyword data if a match is a keyword * @param {CompiledMode} mode - current mode * @param {string} matchText - the textual match * @returns {KeywordData | false} */ 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("_")) { // _ implied for relevance only, do not highlight // by applying a class name buf += match[0]; } else { const cssClass = language.classNameAliases[kind] || kind; emitKeyword(match[0], cssClass); } } else { buf += match[0]; } lastIndex = top.keywordPatternRe.lastIndex; match = top.keywordPatternRe.exec(modeBuffer); } buf += modeBuffer.substring(lastIndex); emitter.addText(buf); } 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); } function processBuffer() { if (top.subLanguage != null) { processSubLanguage(); } else { processKeywords(); } modeBuffer = ''; } /** * @param {string} text * @param {string} scope */ function emitKeyword(keyword, scope) { if (keyword === "") return; emitter.startScope(scope); emitter.addText(keyword); emitter.endScope(); } /** * @param {CompiledScope} scope * @param {RegExpMatchArray} match */ 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) { emitKeyword(text, klass); } else { modeBuffer = text; processKeywords(); modeBuffer = ""; } i++; } } /** * @param {CompiledMode} mode - new mode to start * @param {RegExpMatchArray} match */ function startNewMode(mode, match) { if (mode.scope && typeof mode.scope === "string") { emitter.openNode(language.classNameAliases[mode.scope] || mode.scope); } if (mode.beginScope) { // beginScope just wraps the begin match itself in a scope if (mode.beginScope._wrap) { emitKeyword(modeBuffer, language.classNameAliases[mode.beginScope._wrap] || mode.beginScope._wrap); modeBuffer = ""; } else if (mode.beginScope._multi) { // at this point modeBuffer should just be the match emitMultiClass(mode.beginScope, match); modeBuffer = ""; } } 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 = 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; } } // 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 regexes 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; } } /** * 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.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; } /** * Handle the potential end of mode * * @param {RegExpMatchArray} match - the current match */ function doEndMatch(match) { const lexeme = match[0]; const matchPlusRemainder = codeToHighlight.substring(match.index); const endMode = endOfMode(top, match, matchPlusRemainder); if (!endMode) { return NO_MATCH; } const origin = top; if (top.endScope && top.endScope._wrap) { processBuffer(); emitKeyword(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)); } /** @type {{type?: MatchType, index?: number, rule?: Mode}}} */ let lastMatch = {}; /** * Process an individual match * * @param {string} textBeforeMatch - text preceding the match (since the last match) * @param {EnhancedMatch} [match] - the match itself */ function processLexeme(textBeforeMatch, match) { const lexeme = match && match[0]; // add non-matched text to the current mode buffer modeBuffer += 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 === "") { // 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 (${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) { // illegal match, we do not continue processing /** @type {AnnotatedError} */ const err = new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.scope || '') + '"'); err.mode = top; throw err; } else if (match.type === "end") { const 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; } // 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? An potential end match that was triggered but could not be completed. IE, `doEndMatch` returned NO_MATCH. (this could be because a callback requests the match be ignored, etc) This causes no real harm other than stopping a few times too many. */ 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 = ''; /** @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 { if (!language.__emitTokens) { 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) if (!match) break; const beforeMatch = codeToHighlight.substring(index, match.index); const processedCount = processLexeme(beforeMatch, match); index = match.index + processedCount; } processLexeme(codeToHighlight.substring(index)); } else { language.__emitTokens(codeToHighlight, emitter); } emitter.finalize(); result = emitter.toHTML(); return { language: languageName, value: result, 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, 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; } } } /** * 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 = { value: escape(code), illegal: false, relevance: 0, _top: PLAINTEXT_LANGUAGE, _emitter: new options.__emitter(options) }; 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) - secondBest (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.secondBest = secondBest; return result; } /** * Builds new class name for block given the language name * * @param {HTMLElement} element * @param {string} [currentLang] * @param {string} [resultLang] */ function updateClassName(element, currentLang, resultLang) { const language = (currentLang && aliases[currentLang]) || resultLang; element.classList.add("hljs"); element.classList.add(`language-${language}`); } /** * Applies highlighting to a DOM node containing code. * * @param {HighlightedHTMLElement} element - the HTML element to highlight */ function highlightElement(element) { /** @type HTMLElement */ let node = null; const language = blockLanguage(element); if (shouldNotHighlight(language)) return; fire("before:highlightElement", { el: element, language }); if (element.dataset.highlighted) { console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.", element); return; } // we should be all text, no child nodes (unescaped HTML) - this is possibly // an HTML injection attack - it's likely too late if this is already in // production (the code has likely already done its damage by the time // we're seeing it)... but we yell loudly about this so that hopefully it's // more likely to be caught in development before making it to production 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; element.dataset.highlighted = "yes"; updateClassName(element, language, result.language); element.result = { language: result.language, // TODO: remove with version 11.0 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 }); } /** * Updates highlight.js global options with the passed options * * @param {Partial} userOptions */ function configure(userOptions) { options = inherit(options, userOptions); } // TODO: remove v12, deprecated const initHighlighting = () => { highlightAll(); logger.deprecated("10.6.0", "initHighlighting() deprecated. Use highlightAll() now."); }; // TODO: remove v12, deprecated function initHighlightingOnLoad() { highlightAll(); logger.deprecated("10.6.0", "initHighlightingOnLoad() deprecated. Use highlightAll() now."); } let wantsHighlight = false; /** * auto-highlights all pre>code elements on the page */ function highlightAll() { // if we are called too early in the loading process if (document.readyState === "loading") { wantsHighlight = true; return; } const blocks = document.querySelectorAll(options.cssSelector); blocks.forEach(highlightElement); } function boot() { // if a highlight was requested before DOM was loaded, do now if (wantsHighlight) highlightAll(); } // make sure we are in the browser environment if (typeof window !== 'undefined' && window.addEventListener) { window.addEventListener('DOMContentLoaded', boot, false); } /** * Register a language grammar module * * @param {string} languageName * @param {LanguageFn} languageDefinition */ function registerLanguage(languageName, languageDefinition) { let lang = null; try { lang = languageDefinition(hljs); } catch (error) { logger.error("Language definition for '{}' could not be registered.".replace("{}", languageName)); // hard or soft error if (!SAFE_MODE) { throw error; } else { logger.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 }); } } /** * Remove a language grammar module * * @param {string} languageName */ function unregisterLanguage(languageName) { delete languages[languageName]; for (const alias of Object.keys(aliases)) { if (aliases[alias] === languageName) { delete aliases[alias]; } } } /** * @returns {string[]} List of language internal names */ function listLanguages() { return Object.keys(languages); } /** * @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.toLowerCase()] = languageName; }); } /** * 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; } /** * Upgrades the old highlightBlock plugins to the new * highlightElement API * @param {HLJSPlugin} plugin */ function upgradePluginAPI(plugin) { // TODO: remove with v12 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) ); }; } } /** * @param {HLJSPlugin} plugin */ function addPlugin(plugin) { upgradePluginAPI(plugin); plugins.push(plugin); } /** * @param {HLJSPlugin} plugin */ function removePlugin(plugin) { const index = plugins.indexOf(plugin); if (index !== -1) { plugins.splice(index, 1); } } /** * * @param {PluginEvent} event * @param {any} args */ function fire(event, args) { const cb = event; plugins.forEach(function(plugin) { if (plugin[cb]) { plugin[cb](args); } }); } /** * DEPRECATED * @param {HighlightedHTMLElement} el */ 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); } /* Interface definition */ Object.assign(hljs, { highlight, highlightAuto, highlightAll, highlightElement, // TODO: Remove with v12 API highlightBlock: deprecateHighlightBlock, configure, initHighlighting, initHighlightingOnLoad, registerLanguage, unregisterLanguage, listLanguages, getLanguage, registerAliases, autoDetection, inherit, addPlugin, removePlugin }); 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) { // @ts-ignore if (typeof MODES[key] === "object") { // @ts-ignore deepFreeze(MODES[key]); } } // merge all the modes/regexes into our main object Object.assign(hljs, MODES); return hljs; }; // Other names for the variable may break build script const highlight = HLJS({}); // returns a new instance of the highlighter to be used for extensions // check https://github.com/wooorm/lowlight/issues/47 highlight.newInstance = () => HLJS({}); // export an "instance" of the highlighter export default highlight;