@scalar/code-highlight
Version:
Central methods and themes for code highlighting in Scalar projects
63 lines (62 loc) • 2.61 kB
JavaScript
import rehypeParse from 'rehype-parse';
import rehypeStringify from 'rehype-stringify';
import { unified } from 'unified';
import { visit } from 'unist-util-visit';
import { lowlightLanguageMappings } from '../constants.js';
import { rehypeHighlight } from '../rehype-highlight/index.js';
import { codeBlockLinesPlugin } from './line-numbers.js';
/**
* Syntax highlights a code string using the `rehype-highlight` library.
*/
export function syntaxHighlight(codeString, options) {
// Simple restriction on credentials to prevent unexpected behavior
const credentials = (typeof options?.maskCredentials === 'string' ? [options.maskCredentials] : (options?.maskCredentials ?? [])).filter((c) => {
// Credentials must be at least 3 characters to mask.
if (c.length < 3) {
return false;
}
return true;
});
// Classname is used by lowlight to select the language model
const className = `language-${lowlightLanguageMappings[options.lang] ?? options.lang}`;
// biome-ignore lint/suspicious/noEmptyBlockStatements: empty plugin
const nullPlugin = (() => { });
const html = unified()
// Parses markdown
.use(rehypeParse, { fragment: true })
// Raw code string must be injected after initial hast parsing
// so that HTML code is not parsed into the hast tree
.use(injectRawCodeStringPlugin(codeString))
// Syntax highlighting
.use(rehypeHighlight, {
languages: options.languages,
})
.use(options?.lineNumbers ? codeBlockLinesPlugin : nullPlugin)
// Converts the HTML AST to a string
.use(rehypeStringify)
// Run the pipeline
.processSync(`<pre><code class="${className}"></code></pre>`);
const htmlString = html.toString();
// Replace any credentials with a wrapper element
return credentials.length
? credentials.reduce((acc, credential) => acc
.split(credential)
.join(`<span class="credential"><span class="credential-value">${credential}</span></span>`), htmlString)
: htmlString;
}
/**
* To prevent unified from parsing any content of the code string we inject
* it as a raw text node into the AST tree as a child of the code element
*/
function injectRawCodeStringPlugin(rawCodeString) {
return () => (tree) => {
visit(tree, 'element', (node) => {
if (node.tagName === 'code') {
node.children.push({
type: 'text',
value: rawCodeString,
});
}
});
};
}