From 071868b8df526b88c08a359db39db37e309bd1f2 Mon Sep 17 00:00:00 2001 From: koy Date: Thu, 15 Aug 2024 19:20:22 +0800 Subject: [PATCH] update: support prism validation As per the Prism for highlight requires the strict dependencies import order for languages. When user add multi highlight support for much langs, it may put in wrong order. A validation to make user aware the order for each langs' dependencies. --- src/core/render/compiler/code.js | 2 + src/core/util/prism.js | 299 +++++++++++++++++++++++++++++++ 2 files changed, 301 insertions(+) create mode 100644 src/core/util/prism.js diff --git a/src/core/render/compiler/code.js b/src/core/render/compiler/code.js index 92f0ab55f..b63b85aa1 100644 --- a/src/core/render/compiler/code.js +++ b/src/core/render/compiler/code.js @@ -1,9 +1,11 @@ import Prism from 'prismjs'; // See https://github.com/PrismJS/prism/pull/1367 import 'prismjs/components/prism-markup-templating.js'; +import checkLangDependenciesAllLoaded from '../../util/prism.js'; export const highlightCodeCompiler = ({ renderer }) => (renderer.code = function ({ text, lang = 'markup' }) { + checkLangDependenciesAllLoaded(lang); const langOrMarkup = Prism.languages[lang] || Prism.languages.markup; const code = Prism.highlight( text.replace(/@DOCSIFY_QM@/g, '`'), diff --git a/src/core/util/prism.js b/src/core/util/prism.js new file mode 100644 index 000000000..86ae9ab58 --- /dev/null +++ b/src/core/util/prism.js @@ -0,0 +1,299 @@ +import Prism from 'prismjs'; +/** + * + * The dependencies map which syncs from + * https://github.com/PrismJS/prism/blob/master/plugins/autoloader/prism-autoloader.js + * + */ +const lang_dependencies = { + javascript: 'clike', + actionscript: 'javascript', + apex: ['clike', 'sql'], + arduino: 'cpp', + aspnet: ['markup', 'csharp'], + birb: 'clike', + bison: 'c', + c: 'clike', + csharp: 'clike', + cpp: 'c', + cfscript: 'clike', + chaiscript: ['clike', 'cpp'], + cilkc: 'c', + cilkcpp: 'cpp', + coffeescript: 'javascript', + crystal: 'ruby', + 'css-extras': 'css', + d: 'clike', + dart: 'clike', + django: 'markup-templating', + ejs: ['javascript', 'markup-templating'], + etlua: ['lua', 'markup-templating'], + erb: ['ruby', 'markup-templating'], + fsharp: 'clike', + 'firestore-security-rules': 'clike', + flow: 'javascript', + ftl: 'markup-templating', + gml: 'clike', + glsl: 'c', + go: 'clike', + gradle: 'clike', + groovy: 'clike', + haml: 'ruby', + handlebars: 'markup-templating', + haxe: 'clike', + hlsl: 'c', + idris: 'haskell', + java: 'clike', + javadoc: ['markup', 'java', 'javadoclike'], + jolie: 'clike', + jsdoc: ['javascript', 'javadoclike', 'typescript'], + 'js-extras': 'javascript', + json5: 'json', + jsonp: 'json', + 'js-templates': 'javascript', + kotlin: 'clike', + latte: ['clike', 'markup-templating', 'php'], + less: 'css', + lilypond: 'scheme', + liquid: 'markup-templating', + markdown: 'markup', + 'markup-templating': 'markup', + mongodb: 'javascript', + n4js: 'javascript', + objectivec: 'c', + opencl: 'c', + parser: 'markup', + php: 'markup-templating', + phpdoc: ['php', 'javadoclike'], + 'php-extras': 'php', + plsql: 'sql', + processing: 'clike', + protobuf: 'clike', + pug: ['markup', 'javascript'], + purebasic: 'clike', + purescript: 'haskell', + qsharp: 'clike', + qml: 'javascript', + qore: 'clike', + racket: 'scheme', + cshtml: ['markup', 'csharp'], + jsx: ['markup', 'javascript'], + tsx: ['jsx', 'typescript'], + reason: 'clike', + ruby: 'clike', + sass: 'css', + scss: 'css', + scala: 'java', + 'shell-session': 'bash', + smarty: 'markup-templating', + solidity: 'clike', + soy: 'markup-templating', + sparql: 'turtle', + sqf: 'clike', + squirrel: 'clike', + stata: ['mata', 'java', 'python'], + 't4-cs': ['t4-templating', 'csharp'], + 't4-vb': ['t4-templating', 'vbnet'], + tap: 'yaml', + tt2: ['clike', 'markup-templating'], + textile: 'markup', + twig: 'markup-templating', + typescript: 'javascript', + v: 'clike', + vala: 'clike', + vbnet: 'basic', + velocity: 'markup', + wiki: 'markup', + xeora: 'markup', + 'xml-doc': 'markup', + xquery: 'markup', +}; + +const lang_aliases = { + html: 'markup', + xml: 'markup', + svg: 'markup', + mathml: 'markup', + ssml: 'markup', + atom: 'markup', + rss: 'markup', + js: 'javascript', + g4: 'antlr4', + ino: 'arduino', + 'arm-asm': 'armasm', + art: 'arturo', + adoc: 'asciidoc', + avs: 'avisynth', + avdl: 'avro-idl', + gawk: 'awk', + sh: 'bash', + shell: 'bash', + shortcode: 'bbcode', + rbnf: 'bnf', + oscript: 'bsl', + cs: 'csharp', + dotnet: 'csharp', + cfc: 'cfscript', + 'cilk-c': 'cilkc', + 'cilk-cpp': 'cilkcpp', + cilk: 'cilkcpp', + coffee: 'coffeescript', + conc: 'concurnas', + jinja2: 'django', + 'dns-zone': 'dns-zone-file', + dockerfile: 'docker', + gv: 'dot', + eta: 'ejs', + xlsx: 'excel-formula', + xls: 'excel-formula', + gamemakerlanguage: 'gml', + po: 'gettext', + gni: 'gn', + ld: 'linker-script', + 'go-mod': 'go-module', + hbs: 'handlebars', + mustache: 'handlebars', + hs: 'haskell', + idr: 'idris', + gitignore: 'ignore', + hgignore: 'ignore', + npmignore: 'ignore', + webmanifest: 'json', + kt: 'kotlin', + kts: 'kotlin', + kum: 'kumir', + tex: 'latex', + context: 'latex', + ly: 'lilypond', + emacs: 'lisp', + elisp: 'lisp', + 'emacs-lisp': 'lisp', + md: 'markdown', + moon: 'moonscript', + n4jsd: 'n4js', + nani: 'naniscript', + objc: 'objectivec', + qasm: 'openqasm', + objectpascal: 'pascal', + px: 'pcaxis', + pcode: 'peoplecode', + plantuml: 'plant-uml', + pq: 'powerquery', + mscript: 'powerquery', + pbfasm: 'purebasic', + purs: 'purescript', + py: 'python', + qs: 'qsharp', + rkt: 'racket', + razor: 'cshtml', + rpy: 'renpy', + res: 'rescript', + robot: 'robotframework', + rb: 'ruby', + 'sh-session': 'shell-session', + shellsession: 'shell-session', + smlnj: 'sml', + sol: 'solidity', + sln: 'solution-file', + rq: 'sparql', + sclang: 'supercollider', + t4: 't4-cs', + trickle: 'tremor', + troy: 'tremor', + trig: 'turtle', + ts: 'typescript', + tsconfig: 'typoscript', + uscript: 'unrealscript', + uc: 'unrealscript', + url: 'uri', + vb: 'visual-basic', + vba: 'visual-basic', + webidl: 'web-idl', + mathematica: 'wolfram', + nb: 'wolfram', + wl: 'wolfram', + xeoracube: 'xeora', + yml: 'yaml', +}; + +// The `depTreeCache` is used to cache the dependency tree for each language, +// preventing duplicate calculations and avoiding repeated warning messages. +const depTreeCache = {}; + +/** + * PrismJs language dependencies required a specific order to load. + * Try to check and print a warning message if some dependencies missing or in wrong order. + * @param {*} lang current lang to check dependencies + */ +export default function checkLangDependenciesAllLoaded(lang) { + if (!lang) { + return; + } + + lang = lang_aliases[lang] || lang; + + const validLang = lang_dependencies[lang]; + if (!validLang) { + return; + } + + if (!depTreeCache[lang]) { + /** + * The dummy node constructs the dependency tree as the root + * and maintains the final global loading status (dummy.loaded) for current lang. + */ + const dummy = { + cur: '', + loaded: true, + dependencies: [], + }; + + buildAndCheckDepTree(lang, dummy, dummy); + + const depTree = dummy.dependencies[0]; + depTreeCache[lang] = depTree; + + if (!dummy.loaded) { + const prettyOutput = prettryPrint(depTree, 1); + // eslint-disable-next-line no-console + console.warn( + `The language '${lang}' required dependencies for code block highlighting are not satisfied.`, + `Priority dependencies from low to high, consider to place all the necessary dependencie by priority (higher first): \n`, + prettyOutput, + ); + } + } +} + +const buildAndCheckDepTree = (lang, parent, dummy) => { + if (!lang) { + return; + } + const cur = { cur: lang, loaded: true, dependencies: [] }; + let deps = lang_dependencies[lang] || []; + + if (!(lang in Prism.languages)) { + dummy.loaded = false; + cur.loaded = false; + } + + if (typeof deps === 'string') { + deps = [deps]; + } + + deps.forEach(dep => { + buildAndCheckDepTree(dep, cur, dummy); + }); + + parent.dependencies.push(cur); +}; + +const prettryPrint = (depTree, level) => { + let cur = `${' '.repeat(level * 3)} ${depTree.cur} ${depTree.loaded ? '(+)' : '(-)'}`; + if (depTree.dependencies.length) { + depTree.dependencies.forEach(dep => { + cur += prettryPrint(dep, level + 1, cur); + }); + } + return '\n' + cur; +};