basant307/AI_Governance_Project
045
1/* eslint-disable no-multi-assign */2 3function deepFreeze(obj) {4 if (obj instanceof Map) {5 obj.clear =6 obj.delete =7 obj.set =8 function () {9 throw new Error('map is read-only');10 };11 } else if (obj instanceof Set) {12 obj.add =13 obj.clear =14 obj.delete =15 function () {16 throw new Error('set is read-only');17 };18 }19 20 // Freeze self21 Object.freeze(obj);22 23 Object.getOwnPropertyNames(obj).forEach((name) => {24 const prop = obj[name];25 const type = typeof prop;26 27 // Freeze prop if it is an object or function and also not already frozen28 if ((type === 'object' || type === 'function') && !Object.isFrozen(prop)) {29 deepFreeze(prop);30 }31 });32 33 return obj;34}35 36/** @typedef {import('highlight.js').CallbackResponse} CallbackResponse */37/** @typedef {import('highlight.js').CompiledMode} CompiledMode */38/** @implements CallbackResponse */39 40class Response {41 /**42 * @param {CompiledMode} mode43 */44 constructor(mode) {45 // eslint-disable-next-line no-undefined46 if (mode.data === undefined) mode.data = {};47 48 this.data = mode.data;49 this.isMatchIgnored = false;50 }51 52 ignoreMatch() {53 this.isMatchIgnored = true;54 }55}56 57/**58 * @param {string} value59 * @returns {string}60 */61function escapeHTML(value) {62 return value63 .replace(/&/g, '&')64 .replace(/</g, '<')65 .replace(/>/g, '>')66 .replace(/"/g, '"')67 .replace(/'/g, ''');68}69 70/**71 * performs a shallow merge of multiple objects into one72 *73 * @template T74 * @param {T} original75 * @param {Record<string,any>[]} objects76 * @returns {T} a single new object77 */78function inherit$1(original, ...objects) {79 /** @type Record<string,any> */80 const result = Object.create(null);81 82 for (const key in original) {83 result[key] = original[key];84 }85 objects.forEach(function(obj) {86 for (const key in obj) {87 result[key] = obj[key];88 }89 });90 return /** @type {T} */ (result);91}92 93/**94 * @typedef {object} Renderer95 * @property {(text: string) => void} addText96 * @property {(node: Node) => void} openNode97 * @property {(node: Node) => void} closeNode98 * @property {() => string} value99 */100 101/** @typedef {{scope?: string, language?: string, sublanguage?: boolean}} Node */102/** @typedef {{walk: (r: Renderer) => void}} Tree */103/** */104 105const SPAN_CLOSE = '</span>';106 107/**108 * Determines if a node needs to be wrapped in <span>109 *110 * @param {Node} node */111const emitsWrappingTags = (node) => {112 // rarely we can have a sublanguage where language is undefined113 // TODO: track down why114 return !!node.scope;115};116 117/**118 *119 * @param {string} name120 * @param {{prefix:string}} options121 */122const scopeToCSSClass = (name, { prefix }) => {123 // sub-language124 if (name.startsWith("language:")) {125 return name.replace("language:", "language-");126 }127 // tiered scope: comment.line128 if (name.includes(".")) {129 const pieces = name.split(".");130 return [131 `${prefix}${pieces.shift()}`,132 ...(pieces.map((x, i) => `${x}${"_".repeat(i + 1)}`))133 ].join(" ");134 }135 // simple scope136 return `${prefix}${name}`;137};138 139/** @type {Renderer} */140class HTMLRenderer {141 /**142 * Creates a new HTMLRenderer143 *144 * @param {Tree} parseTree - the parse tree (must support `walk` API)145 * @param {{classPrefix: string}} options146 */147 constructor(parseTree, options) {148 this.buffer = "";149 this.classPrefix = options.classPrefix;150 parseTree.walk(this);151 }152 153 /**154 * Adds texts to the output stream155 *156 * @param {string} text */157 addText(text) {158 this.buffer += escapeHTML(text);159 }160 161 /**162 * Adds a node open to the output stream (if needed)163 *164 * @param {Node} node */165 openNode(node) {166 if (!emitsWrappingTags(node)) return;167 168 const className = scopeToCSSClass(node.scope,169 { prefix: this.classPrefix });170 this.span(className);171 }172 173 /**174 * Adds a node close to the output stream (if needed)175 *176 * @param {Node} node */177 closeNode(node) {178 if (!emitsWrappingTags(node)) return;179 180 this.buffer += SPAN_CLOSE;181 }182 183 /**184 * returns the accumulated buffer185 */186 value() {187 return this.buffer;188 }189 190 // helpers191 192 /**193 * Builds a span element194 *195 * @param {string} className */196 span(className) {197 this.buffer += `<span class="${className}">`;198 }199}200 201/** @typedef {{scope?: string, language?: string, children: Node[]} | string} Node */202/** @typedef {{scope?: string, language?: string, children: Node[]} } DataNode */203/** @typedef {import('highlight.js').Emitter} Emitter */204/** */205 206/** @returns {DataNode} */207const newNode = (opts = {}) => {208 /** @type DataNode */209 const result = { children: [] };210 Object.assign(result, opts);211 return result;212};213 214class TokenTree {215 constructor() {216 /** @type DataNode */217 this.rootNode = newNode();218 this.stack = [this.rootNode];219 }220 221 get top() {222 return this.stack[this.stack.length - 1];223 }224 225 get root() { return this.rootNode; }226 227 /** @param {Node} node */228 add(node) {229 this.top.children.push(node);230 }231 232 /** @param {string} scope */233 openNode(scope) {234 /** @type Node */235 const node = newNode({ scope });236 this.add(node);237 this.stack.push(node);238 }239 240 closeNode() {241 if (this.stack.length > 1) {242 return this.stack.pop();243 }244 // eslint-disable-next-line no-undefined245 return undefined;246 }247 248 closeAllNodes() {249 while (this.closeNode());250 }251 252 toJSON() {253 return JSON.stringify(this.rootNode, null, 4);254 }255 256 /**257 * @typedef { import("./html_renderer").Renderer } Renderer258 * @param {Renderer} builder259 */260 walk(builder) {261 // this does not262 return this.constructor._walk(builder, this.rootNode);263 // this works264 // return TokenTree._walk(builder, this.rootNode);265 }266 267 /**268 * @param {Renderer} builder269 * @param {Node} node270 */271 static _walk(builder, node) {272 if (typeof node === "string") {273 builder.addText(node);274 } else if (node.children) {275 builder.openNode(node);276 node.children.forEach((child) => this._walk(builder, child));277 builder.closeNode(node);278 }279 return builder;280 }281 282 /**283 * @param {Node} node284 */285 static _collapse(node) {286 if (typeof node === "string") return;287 if (!node.children) return;288 289 if (node.children.every(el => typeof el === "string")) {290 // node.text = node.children.join("");291 // delete node.children;292 node.children = [node.children.join("")];293 } else {294 node.children.forEach((child) => {295 TokenTree._collapse(child);296 });297 }298 }299}300 301/**302 Currently this is all private API, but this is the minimal API necessary303 that an Emitter must implement to fully support the parser.304 305 Minimal interface:306 307 - addText(text)308 - __addSublanguage(emitter, subLanguageName)309 - startScope(scope)310 - endScope()311 - finalize()312 - toHTML()313 314*/315 316/**317 * @implements {Emitter}318 */319class TokenTreeEmitter extends TokenTree {320 /**321 * @param {*} options322 */323 constructor(options) {324 super();325 this.options = options;326 }327 328 /**329 * @param {string} text330 */331 addText(text) {332 if (text === "") { return; }333 334 this.add(text);335 }336 337 /** @param {string} scope */338 startScope(scope) {339 this.openNode(scope);340 }341 342 endScope() {343 this.closeNode();344 }345 346 /**347 * @param {Emitter & {root: DataNode}} emitter348 * @param {string} name349 */350 __addSublanguage(emitter, name) {351 /** @type DataNode */352 const node = emitter.root;353 if (name) node.scope = `language:${name}`;354 355 this.add(node);356 }357 358 toHTML() {359 const renderer = new HTMLRenderer(this, this.options);360 return renderer.value();361 }362 363 finalize() {364 this.closeAllNodes();365 return true;366 }367}368 369/**370 * @param {string} value371 * @returns {RegExp}372 * */373 374/**375 * @param {RegExp | string } re376 * @returns {string}377 */378function source(re) {379 if (!re) return null;380 if (typeof re === "string") return re;381 382 return re.source;383}384 385/**386 * @param {RegExp | string } re387 * @returns {string}388 */389function lookahead(re) {390 return concat('(?=', re, ')');391}392 393/**394 * @param {RegExp | string } re395 * @returns {string}396 */397function anyNumberOfTimes(re) {398 return concat('(?:', re, ')*');399}400 401/**402 * @param {RegExp | string } re403 * @returns {string}404 */405function optional(re) {406 return concat('(?:', re, ')?');407}408 409/**410 * @param {...(RegExp | string) } args411 * @returns {string}412 */413function concat(...args) {414 const joined = args.map((x) => source(x)).join("");415 return joined;416}417 418/**419 * @param { Array<string | RegExp | Object> } args420 * @returns {object}421 */422function stripOptionsFromArgs(args) {423 const opts = args[args.length - 1];424 425 if (typeof opts === 'object' && opts.constructor === Object) {426 args.splice(args.length - 1, 1);427 return opts;428 } else {429 return {};430 }431}432 433/** @typedef { {capture?: boolean} } RegexEitherOptions */434 435/**436 * Any of the passed expresssions may match437 *438 * Creates a huge this | this | that | that match439 * @param {(RegExp | string)[] | [...(RegExp | string)[], RegexEitherOptions]} args440 * @returns {string}441 */442function either(...args) {443 /** @type { object & {capture?: boolean} } */444 const opts = stripOptionsFromArgs(args);445 const joined = '('446 + (opts.capture ? "" : "?:")447 + args.map((x) => source(x)).join("|") + ")";448 return joined;449}450 451/**452 * @param {RegExp | string} re453 * @returns {number}454 */455function countMatchGroups(re) {456 return (new RegExp(re.toString() + '|')).exec('').length - 1;457}458 459/**460 * Does lexeme start with a regular expression match at the beginning461 * @param {RegExp} re462 * @param {string} lexeme463 */464function startsWith(re, lexeme) {465 const match = re && re.exec(lexeme);466 return match && match.index === 0;467}468 469// BACKREF_RE matches an open parenthesis or backreference. To avoid470// an incorrect parse, it additionally matches the following:471// - [...] elements, where the meaning of parentheses and escapes change472// - other escape sequences, so we do not misparse escape sequences as473// interesting elements474// - non-matching or lookahead parentheses, which do not capture. These475// follow the '(' with a '?'.476const BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;477 478// **INTERNAL** Not intended for outside usage479// join logically computes regexps.join(separator), but fixes the480// backreferences so they continue to match.481// it also places each individual regular expression into it's own482// match group, keeping track of the sequencing of those match groups483// is currently an exercise for the caller. :-)484/**485 * @param {(string | RegExp)[]} regexps486 * @param {{joinWith: string}} opts487 * @returns {string}488 */489function _rewriteBackreferences(regexps, { joinWith }) {490 let numCaptures = 0;491 492 return regexps.map((regex) => {493 numCaptures += 1;494 const offset = numCaptures;495 let re = source(regex);496 let out = '';497 498 while (re.length > 0) {499 const match = BACKREF_RE.exec(re);500 if (!match) {501 out += re;502 break;503 }504 out += re.substring(0, match.index);505 re = re.substring(match.index + match[0].length);506 if (match[0][0] === '\\' && match[1]) {507 // Adjust the backreference.508 out += '\\' + String(Number(match[1]) + offset);509 } else {510 out += match[0];511 if (match[0] === '(') {512 numCaptures++;513 }514 }515 }516 return out;517 }).map(re => `(${re})`).join(joinWith);518}519 520/** @typedef {import('highlight.js').Mode} Mode */521/** @typedef {import('highlight.js').ModeCallback} ModeCallback */522 523// Common regexps524const MATCH_NOTHING_RE = /\b\B/;525const IDENT_RE = '[a-zA-Z]\\w*';526const UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\w*';527const NUMBER_RE = '\\b\\d+(\\.\\d+)?';528const C_NUMBER_RE = '(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float529const BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b...530const RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~';531 532/**533* @param { Partial<Mode> & {binary?: string | RegExp} } opts534*/535const SHEBANG = (opts = {}) => {536 const beginShebang = /^#![ ]*\//;537 if (opts.binary) {538 opts.begin = concat(539 beginShebang,540 /.*\b/,541 opts.binary,542 /\b.*/);543 }544 return inherit$1({545 scope: 'meta',546 begin: beginShebang,547 end: /$/,548 relevance: 0,549 /** @type {ModeCallback} */550 "on:begin": (m, resp) => {551 if (m.index !== 0) resp.ignoreMatch();552 }553 }, opts);554};555 556// Common modes557const BACKSLASH_ESCAPE = {558 begin: '\\\\[\\s\\S]', relevance: 0559};560const APOS_STRING_MODE = {561 scope: 'string',562 begin: '\'',563 end: '\'',564 illegal: '\\n',565 contains: [BACKSLASH_ESCAPE]566};567const QUOTE_STRING_MODE = {568 scope: 'string',569 begin: '"',570 end: '"',571 illegal: '\\n',572 contains: [BACKSLASH_ESCAPE]573};574const PHRASAL_WORDS_MODE = {575 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/576};577/**578 * Creates a comment mode579 *580 * @param {string | RegExp} begin581 * @param {string | RegExp} end582 * @param {Mode | {}} [modeOptions]583 * @returns {Partial<Mode>}584 */585const COMMENT = function(begin, end, modeOptions = {}) {586 const mode = inherit$1(587 {588 scope: 'comment',589 begin,590 end,591 contains: []592 },593 modeOptions594 );595 mode.contains.push({596 scope: 'doctag',597 // hack to avoid the space from being included. the space is necessary to598 // match here to prevent the plain text rule below from gobbling up doctags599 begin: '[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)',600 end: /(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,601 excludeBegin: true,602 relevance: 0603 });604 const ENGLISH_WORD = either(605 // list of common 1 and 2 letter words in English606 "I",607 "a",608 "is",609 "so",610 "us",611 "to",612 "at",613 "if",614 "in",615 "it",616 "on",617 // note: this is not an exhaustive list of contractions, just popular ones618 /[A-Za-z]+['](d|ve|re|ll|t|s|n)/, // contractions - can't we'd they're let's, etc619 /[A-Za-z]+[-][a-z]+/, // `no-way`, etc.620 /[A-Za-z][a-z]{2,}/ // allow capitalized words at beginning of sentences621 );622 // looking like plain text, more likely to be a comment623 mode.contains.push(624 {625 // TODO: how to include ", (, ) without breaking grammars that use these for626 // comment delimiters?627 // begin: /[ ]+([()"]?([A-Za-z'-]{3,}|is|a|I|so|us|[tT][oO]|at|if|in|it|on)[.]?[()":]?([.][ ]|[ ]|\))){3}/628 // ---629 630 // this tries to find sequences of 3 english words in a row (without any631 // "programming" type syntax) this gives us a strong signal that we've632 // TRULY found a comment - vs perhaps scanning with the wrong language.633 // It's possible to find something that LOOKS like the start of the634 // comment - but then if there is no readable text - good chance it is a635 // false match and not a comment.636 //637 // for a visual example please see:638 // https://github.com/highlightjs/highlight.js/issues/2827639 640 begin: concat(641 /[ ]+/, // necessary to prevent us gobbling up doctags like /* @author Bob Mcgill */642 '(',643 ENGLISH_WORD,644 /[.]?[:]?([.][ ]|[ ])/,645 '){3}') // look for 3 words in a row646 }647 );648 return mode;649};650const C_LINE_COMMENT_MODE = COMMENT('//', '$');651const C_BLOCK_COMMENT_MODE = COMMENT('/\\*', '\\*/');652const HASH_COMMENT_MODE = COMMENT('#', '$');653const NUMBER_MODE = {654 scope: 'number',655 begin: NUMBER_RE,656 relevance: 0657};658const C_NUMBER_MODE = {659 scope: 'number',660 begin: C_NUMBER_RE,661 relevance: 0662};663const BINARY_NUMBER_MODE = {664 scope: 'number',665 begin: BINARY_NUMBER_RE,666 relevance: 0667};668const REGEXP_MODE = {669 scope: "regexp",670 begin: /\/(?=[^/\n]*\/)/,671 end: /\/[gimuy]*/,672 contains: [673 BACKSLASH_ESCAPE,674 {675 begin: /\[/,676 end: /\]/,677 relevance: 0,678 contains: [BACKSLASH_ESCAPE]679 }680 ]681};682const TITLE_MODE = {683 scope: 'title',684 begin: IDENT_RE,685 relevance: 0686};687const UNDERSCORE_TITLE_MODE = {688 scope: 'title',689 begin: UNDERSCORE_IDENT_RE,690 relevance: 0691};692const METHOD_GUARD = {693 // excludes method names from keyword processing694 begin: '\\.\\s*' + UNDERSCORE_IDENT_RE,695 relevance: 0696};697 698/**699 * Adds end same as begin mechanics to a mode700 *701 * Your mode must include at least a single () match group as that first match702 * group is what is used for comparison703 * @param {Partial<Mode>} mode704 */705const END_SAME_AS_BEGIN = function(mode) {706 return Object.assign(mode,707 {708 /** @type {ModeCallback} */709 'on:begin': (m, resp) => { resp.data._beginMatch = m[1]; },710 /** @type {ModeCallback} */711 'on:end': (m, resp) => { if (resp.data._beginMatch !== m[1]) resp.ignoreMatch(); }712 });713};714 715var MODES = /*#__PURE__*/Object.freeze({716 __proto__: null,717 APOS_STRING_MODE: APOS_STRING_MODE,718 BACKSLASH_ESCAPE: BACKSLASH_ESCAPE,719 BINARY_NUMBER_MODE: BINARY_NUMBER_MODE,720 BINARY_NUMBER_RE: BINARY_NUMBER_RE,721 COMMENT: COMMENT,722 C_BLOCK_COMMENT_MODE: C_BLOCK_COMMENT_MODE,723 C_LINE_COMMENT_MODE: C_LINE_COMMENT_MODE,724 C_NUMBER_MODE: C_NUMBER_MODE,725 C_NUMBER_RE: C_NUMBER_RE,726 END_SAME_AS_BEGIN: END_SAME_AS_BEGIN,727 HASH_COMMENT_MODE: HASH_COMMENT_MODE,728 IDENT_RE: IDENT_RE,729 MATCH_NOTHING_RE: MATCH_NOTHING_RE,730 METHOD_GUARD: METHOD_GUARD,731 NUMBER_MODE: NUMBER_MODE,732 NUMBER_RE: NUMBER_RE,733 PHRASAL_WORDS_MODE: PHRASAL_WORDS_MODE,734 QUOTE_STRING_MODE: QUOTE_STRING_MODE,735 REGEXP_MODE: REGEXP_MODE,736 RE_STARTERS_RE: RE_STARTERS_RE,737 SHEBANG: SHEBANG,738 TITLE_MODE: TITLE_MODE,739 UNDERSCORE_IDENT_RE: UNDERSCORE_IDENT_RE,740 UNDERSCORE_TITLE_MODE: UNDERSCORE_TITLE_MODE741});742 743/**744@typedef {import('highlight.js').CallbackResponse} CallbackResponse745@typedef {import('highlight.js').CompilerExt} CompilerExt746*/747 748// Grammar extensions / plugins749// See: https://github.com/highlightjs/highlight.js/issues/2833750 751// Grammar extensions allow "syntactic sugar" to be added to the grammar modes752// without requiring any underlying changes to the compiler internals.753 754// `compileMatch` being the perfect small example of now allowing a grammar755// author to write `match` when they desire to match a single expression rather756// than being forced to use `begin`. The extension then just moves `match` into757// `begin` when it runs. Ie, no features have been added, but we've just made758// the experience of writing (and reading grammars) a little bit nicer.759 760// ------761 762// TODO: We need negative look-behind support to do this properly763/**764 * Skip a match if it has a preceding dot765 *766 * This is used for `beginKeywords` to prevent matching expressions such as767 * `bob.keyword.do()`. The mode compiler automatically wires this up as a768 * special _internal_ 'on:begin' callback for modes with `beginKeywords`769 * @param {RegExpMatchArray} match770 * @param {CallbackResponse} response771 */772function skipIfHasPrecedingDot(match, response) {773 const before = match.input[match.index - 1];774 if (before === ".") {775 response.ignoreMatch();776 }777}778 779/**780 *781 * @type {CompilerExt}782 */783function scopeClassName(mode, _parent) {784 // eslint-disable-next-line no-undefined785 if (mode.className !== undefined) {786 mode.scope = mode.className;787 delete mode.className;788 }789}790 791/**792 * `beginKeywords` syntactic sugar793 * @type {CompilerExt}794 */795function beginKeywords(mode, parent) {796 if (!parent) return;797 if (!mode.beginKeywords) return;798 799 // for languages with keywords that include non-word characters checking for800 // a word boundary is not sufficient, so instead we check for a word boundary801 // or whitespace - this does no harm in any case since our keyword engine802 // doesn't allow spaces in keywords anyways and we still check for the boundary803 // first804 mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')(?!\\.)(?=\\b|\\s)';805 mode.__beforeBegin = skipIfHasPrecedingDot;806 mode.keywords = mode.keywords || mode.beginKeywords;807 delete mode.beginKeywords;808 809 // prevents double relevance, the keywords themselves provide810 // relevance, the mode doesn't need to double it811 // eslint-disable-next-line no-undefined812 if (mode.relevance === undefined) mode.relevance = 0;813}814 815/**816 * Allow `illegal` to contain an array of illegal values817 * @type {CompilerExt}818 */819function compileIllegal(mode, _parent) {820 if (!Array.isArray(mode.illegal)) return;821 822 mode.illegal = either(...mode.illegal);823}824 825/**826 * `match` to match a single expression for readability827 * @type {CompilerExt}828 */829function compileMatch(mode, _parent) {830 if (!mode.match) return;831 if (mode.begin || mode.end) throw new Error("begin & end are not supported with match");832 833 mode.begin = mode.match;834 delete mode.match;835}836 837/**838 * provides the default 1 relevance to all modes839 * @type {CompilerExt}840 */841function compileRelevance(mode, _parent) {842 // eslint-disable-next-line no-undefined843 if (mode.relevance === undefined) mode.relevance = 1;844}845 846// allow beforeMatch to act as a "qualifier" for the match847// the full match begin must be [beforeMatch][begin]848const beforeMatchExt = (mode, parent) => {849 if (!mode.beforeMatch) return;850 // starts conflicts with endsParent which we need to make sure the child851 // rule is not matched multiple times852 if (mode.starts) throw new Error("beforeMatch cannot be used with starts");853 854 const originalMode = Object.assign({}, mode);855 Object.keys(mode).forEach((key) => { delete mode[key]; });856 857 mode.keywords = originalMode.keywords;858 mode.begin = concat(originalMode.beforeMatch, lookahead(originalMode.begin));859 mode.starts = {860 relevance: 0,861 contains: [862 Object.assign(originalMode, { endsParent: true })863 ]864 };865 mode.relevance = 0;866 867 delete originalMode.beforeMatch;868};869 870// keywords that should have no default relevance value871const COMMON_KEYWORDS = [872 'of',873 'and',874 'for',875 'in',876 'not',877 'or',878 'if',879 'then',880 'parent', // common variable name881 'list', // common variable name882 'value' // common variable name883];884 885const DEFAULT_KEYWORD_SCOPE = "keyword";886 887/**888 * Given raw keywords from a language definition, compile them.889 *890 * @param {string | Record<string,string|string[]> | Array<string>} rawKeywords891 * @param {boolean} caseInsensitive892 */893function compileKeywords(rawKeywords, caseInsensitive, scopeName = DEFAULT_KEYWORD_SCOPE) {894 /** @type {import("highlight.js/private").KeywordDict} */895 const compiledKeywords = Object.create(null);896 897 // input can be a string of keywords, an array of keywords, or a object with898 // named keys representing scopeName (which can then point to a string or array)899 if (typeof rawKeywords === 'string') {900 compileList(scopeName, rawKeywords.split(" "));901 } else if (Array.isArray(rawKeywords)) {902 compileList(scopeName, rawKeywords);903 } else {904 Object.keys(rawKeywords).forEach(function(scopeName) {905 // collapse all our objects back into the parent object906 Object.assign(907 compiledKeywords,908 compileKeywords(rawKeywords[scopeName], caseInsensitive, scopeName)909 );910 });911 }912 return compiledKeywords;913 914 // ---915 916 /**917 * Compiles an individual list of keywords918 *919 * Ex: "for if when while|5"920 *921 * @param {string} scopeName922 * @param {Array<string>} keywordList923 */924 function compileList(scopeName, keywordList) {925 if (caseInsensitive) {926 keywordList = keywordList.map(x => x.toLowerCase());927 }928 keywordList.forEach(function(keyword) {929 const pair = keyword.split('|');930 compiledKeywords[pair[0]] = [scopeName, scoreForKeyword(pair[0], pair[1])];931 });932 }933}934 935/**936 * Returns the proper score for a given keyword937 *938 * Also takes into account comment keywords, which will be scored 0 UNLESS939 * another score has been manually assigned.940 * @param {string} keyword941 * @param {string} [providedScore]942 */943function scoreForKeyword(keyword, providedScore) {944 // manual scores always win over common keywords945 // so you can force a score of 1 if you really insist946 if (providedScore) {947 return Number(providedScore);948 }949 950 return commonKeyword(keyword) ? 0 : 1;951}952 953/**954 * Determines if a given keyword is common or not955 *956 * @param {string} keyword */957function commonKeyword(keyword) {958 return COMMON_KEYWORDS.includes(keyword.toLowerCase());959}960 961/*962 963For the reasoning behind this please see:964https://github.com/highlightjs/highlight.js/issues/2880#issuecomment-747275419965 966*/967 968/**969 * @type {Record<string, boolean>}970 */971const seenDeprecations = {};972 973/**974 * @param {string} message975 */976const error = (message) => {977 console.error(message);978};979 980/**981 * @param {string} message982 * @param {any} args983 */984const warn = (message, ...args) => {985 console.log(`WARN: ${message}`, ...args);986};987 988/**989 * @param {string} version990 * @param {string} message991 */992const deprecated = (version, message) => {993 if (seenDeprecations[`${version}/${message}`]) return;994 995 console.log(`Deprecated as of ${version}. ${message}`);996 seenDeprecations[`${version}/${message}`] = true;997};998 999/* eslint-disable no-throw-literal */1000 1001/**1002@typedef {import('highlight.js').CompiledMode} CompiledMode1003*/1004 1005const MultiClassError = new Error();1006 1007/**1008 * Renumbers labeled scope names to account for additional inner match1009 * groups that otherwise would break everything.1010 *1011 * Lets say we 3 match scopes:1012 *1013 * { 1 => ..., 2 => ..., 3 => ... }1014 *1015 * So what we need is a clean match like this:1016 *1017 * (a)(b)(c) => [ "a", "b", "c" ]1018 *1019 * But this falls apart with inner match groups:1020 *1021 * (a)(((b)))(c) => ["a", "b", "b", "b", "c" ]1022 *1023 * Our scopes are now "out of alignment" and we're repeating `b` 3 times.1024 * What needs to happen is the numbers are remapped:1025 *1026 * { 1 => ..., 2 => ..., 5 => ... }1027 *1028 * We also need to know that the ONLY groups that should be output1029 * are 1, 2, and 5. This function handles this behavior.1030 *1031 * @param {CompiledMode} mode1032 * @param {Array<RegExp | string>} regexes1033 * @param {{key: "beginScope"|"endScope"}} opts1034 */1035function remapScopeNames(mode, regexes, { key }) {1036 let offset = 0;1037 const scopeNames = mode[key];1038 /** @type Record<number,boolean> */1039 const emit = {};1040 /** @type Record<number,string> */1041 const positions = {};1042 1043 for (let i = 1; i <= regexes.length; i++) {1044 positions[i + offset] = scopeNames[i];1045 emit[i + offset] = true;1046 offset += countMatchGroups(regexes[i - 1]);1047 }1048 // we use _emit to keep track of which match groups are "top-level" to avoid double1049 // output from inside match groups1050 mode[key] = positions;1051 mode[key]._emit = emit;1052 mode[key]._multi = true;1053}1054 1055/**1056 * @param {CompiledMode} mode1057 */1058function beginMultiClass(mode) {1059 if (!Array.isArray(mode.begin)) return;1060 1061 if (mode.skip || mode.excludeBegin || mode.returnBegin) {1062 error("skip, excludeBegin, returnBegin not compatible with beginScope: {}");1063 throw MultiClassError;1064 }1065 1066 if (typeof mode.beginScope !== "object" || mode.beginScope === null) {1067 error("beginScope must be object");1068 throw MultiClassError;1069 }1070 1071 remapScopeNames(mode, mode.begin, { key: "beginScope" });1072 mode.begin = _rewriteBackreferences(mode.begin, { joinWith: "" });1073}1074 1075/**1076 * @param {CompiledMode} mode1077 */1078function endMultiClass(mode) {1079 if (!Array.isArray(mode.end)) return;1080 1081 if (mode.skip || mode.excludeEnd || mode.returnEnd) {1082 error("skip, excludeEnd, returnEnd not compatible with endScope: {}");1083 throw MultiClassError;1084 }1085 1086 if (typeof mode.endScope !== "object" || mode.endScope === null) {1087 error("endScope must be object");1088 throw MultiClassError;1089 }1090 1091 remapScopeNames(mode, mode.end, { key: "endScope" });1092 mode.end = _rewriteBackreferences(mode.end, { joinWith: "" });1093}1094 1095/**1096 * this exists only to allow `scope: {}` to be used beside `match:`1097 * Otherwise `beginScope` would necessary and that would look weird1098 1099 {1100 match: [ /def/, /\w+/ ]1101 scope: { 1: "keyword" , 2: "title" }1102 }1103 1104 * @param {CompiledMode} mode1105 */1106function scopeSugar(mode) {1107 if (mode.scope && typeof mode.scope === "object" && mode.scope !== null) {1108 mode.beginScope = mode.scope;1109 delete mode.scope;1110 }1111}1112 1113/**1114 * @param {CompiledMode} mode1115 */1116function MultiClass(mode) {1117 scopeSugar(mode);1118 1119 if (typeof mode.beginScope === "string") {1120 mode.beginScope = { _wrap: mode.beginScope };1121 }1122 if (typeof mode.endScope === "string") {1123 mode.endScope = { _wrap: mode.endScope };1124 }1125 1126 beginMultiClass(mode);1127 endMultiClass(mode);1128}1129 1130/**1131@typedef {import('highlight.js').Mode} Mode1132@typedef {import('highlight.js').CompiledMode} CompiledMode1133@typedef {import('highlight.js').Language} Language1134@typedef {import('highlight.js').HLJSPlugin} HLJSPlugin1135@typedef {import('highlight.js').CompiledLanguage} CompiledLanguage1136*/1137 1138// compilation1139 1140/**1141 * Compiles a language definition result1142 *1143 * Given the raw result of a language definition (Language), compiles this so1144 * that it is ready for highlighting code.1145 * @param {Language} language1146 * @returns {CompiledLanguage}1147 */1148function compileLanguage(language) {1149 /**1150 * Builds a regex with the case sensitivity of the current language1151 *1152 * @param {RegExp | string} value1153 * @param {boolean} [global]1154 */1155 function langRe(value, global) {1156 return new RegExp(1157 source(value),1158 'm'1159 + (language.case_insensitive ? 'i' : '')1160 + (language.unicodeRegex ? 'u' : '')1161 + (global ? 'g' : '')1162 );1163 }1164 1165 /**1166 Stores multiple regular expressions and allows you to quickly search for1167 them all in a string simultaneously - returning the first match. It does1168 this by creating a huge (a|b|c) regex - each individual item wrapped with ()1169 and joined by `|` - using match groups to track position. When a match is1170 found checking which position in the array has content allows us to figure1171 out which of the original regexes / match groups triggered the match.1172 1173 The match object itself (the result of `Regex.exec`) is returned but also1174 enhanced by merging in any meta-data that was registered with the regex.1175 This is how we keep track of which mode matched, and what type of rule1176 (`illegal`, `begin`, end, etc).1177 */1178 class MultiRegex {1179 constructor() {1180 this.matchIndexes = {};1181 // @ts-ignore1182 this.regexes = [];1183 this.matchAt = 1;1184 this.position = 0;1185 }1186 1187 // @ts-ignore1188 addRule(re, opts) {1189 opts.position = this.position++;1190 // @ts-ignore1191 this.matchIndexes[this.matchAt] = opts;1192 this.regexes.push([opts, re]);1193 this.matchAt += countMatchGroups(re) + 1;1194 }1195 1196 compile() {1197 if (this.regexes.length === 0) {1198 // avoids the need to check length every time exec is called1199 // @ts-ignore1200 this.exec = () => null;