pub mod escape;
mod lang;
pub mod scanner;
pub mod token;
use token::render;
pub fn scan(code: &str, language: &str) -> Vec<token::Token> {
lang::scan(code, language)
}
pub fn highlight(code: &str, lang: &str) -> String {
if code.is_empty() {
return String::new();
}
let tokens = lang::scan(code, lang);
render(code, &tokens)
}
pub fn supported_languages() -> Vec<&'static str> {
lang::supported()
}
pub fn is_supported(lang: &str) -> bool {
lang::get_scanner(lang).is_some()
}
#[cfg(feature = "wasm")]
mod wasm {
use wasm_bindgen::prelude::*;
#[wasm_bindgen(js_name = "highlight")]
pub fn highlight_wasm(code: &str, lang: &str) -> String {
crate::highlight(code, lang)
}
#[wasm_bindgen(js_name = "supportedLanguages")]
pub fn supported_languages_wasm() -> Vec<String> {
crate::supported_languages()
.into_iter()
.map(String::from)
.collect()
}
#[wasm_bindgen(js_name = "isSupported")]
pub fn is_supported_wasm(lang: &str) -> bool {
crate::is_supported(lang)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_input() {
assert_eq!(highlight("", "rust"), "");
}
#[test]
fn unknown_language_escapes_html() {
assert_eq!(highlight("<b>hi</b>", "unknown"), "<b>hi</b>");
}
#[test]
fn round_trip_strip_spans() {
let code = "let x = 42; // test\nfn foo() {}";
let html = highlight(code, "rust");
let stripped = html
.replace(|_: char| false, "") .split("<span")
.map(|s| {
if let Some(pos) = s.find('>') {
&s[pos + 1..]
} else {
s
}
})
.collect::<Vec<_>>()
.join("")
.replace("</span>", "");
let mut expected = String::new();
crate::escape::escape_html(code, &mut expected);
assert_eq!(stripped, expected);
}
#[test]
fn supported_languages_nonempty() {
assert!(supported_languages().len() > 10);
}
#[test]
fn is_supported_works() {
assert!(is_supported("rust"));
assert!(is_supported("rs"));
assert!(is_supported("javascript"));
assert!(!is_supported("brainfuck"));
}
#[test]
fn unicode_no_panic() {
let _ = highlight("let 变量 = \"你好世界\";", "rust");
let _ = highlight("const 🎉 = true;", "javascript");
}
#[test]
fn crlf_handling() {
let code = "let x = 1;\r\nlet y = 2;\r\n";
let html = highlight(code, "rust");
assert!(html.contains("tok-keyword"));
assert!(html.contains("\r\n"));
}
#[test]
fn spec_example() {
let out = highlight("let x = 42;", "rust");
assert!(out.contains("<span class=\"tok-keyword\">let</span>"));
assert!(out.contains("<span class=\"tok-operator\">=</span>"));
assert!(out.contains("<span class=\"tok-number\">42</span>"));
assert!(out.contains("<span class=\"tok-punctuation\">;</span>"));
}
}