use clankerdiff_syntax::{
Fingerprint, LanguageHint, SyntaxError, SyntaxHighlighter, SyntaxTheme, resolve_language,
};
use tree_sitter::QueryErrorKind;
#[test]
fn resolves_aliases_paths_special_files_and_shebangs() {
for (hint, source, expected) in [
("RUST", "", Some("rust")),
(".rs", "", Some("rust")),
("src/lib.rs", "", Some("rust")),
("src\\lib.rs", "", Some("rust")),
("view.tsx", "", Some("tsx")),
("x.d.ts", "", Some("typescript")),
("x.d.mts", "", Some("typescript")),
("x.d.cts", "", Some("typescript")),
("foo.jsx", "", Some("javascript")),
("foo.jsonc", "", Some("json")),
("foo.yml", "", Some("yaml")),
("Dockerfile", "", Some("dockerfile")),
("Containerfile", "", Some("dockerfile")),
("Program.cs", "", Some("c-sharp")),
("Main.java", "", Some("java")),
("Main.kt", "", Some("kotlin")),
("script.rb", "", Some("ruby")),
("query.sql", "", Some("sql")),
(".bashrc", "", Some("bash")),
(".zshrc", "", Some("zsh")),
("go.mod", "", Some("go")),
("", "#!/usr/bin/env python3\n", Some("python")),
("", "#!/usr/bin/env node\n", Some("javascript")),
("unknown.bin", "bytes", None),
("markdown-inline", "", None),
] {
assert_eq!(resolve_language(hint, source), expected, "{hint}");
}
}
#[test]
fn preserves_detector_fallbacks_and_local_precedence() {
for (alias, expected) in [
("conf", "ini"),
("docker", "dockerfile"),
("mdx", "markdown"),
("mm", "objc"),
("mysql", "sql"),
("postgres", "sql"),
("postgresql", "sql"),
("sqlite", "sql"),
("opa", "rego"),
("py3", "python"),
("rkt", "scheme"),
("rlang", "r"),
("sass", "scss"),
("x86", "x86asm"),
("xsl", "xml"),
("xslt", "xml"),
] {
for hint in [
alias.to_owned(),
format!("src/file.{alias}"),
format!("SRC\\\\FILE.{}", alias.to_ascii_uppercase()),
] {
assert_eq!(
resolve_language(hint.as_str(), "#!/bin/bash"),
Some(expected),
"{hint}"
);
}
}
for (hint, expected) in [
("main.m", Some("objc")),
("main.mm", Some("objc")),
("main.scm", Some("scheme")),
("m", None),
("s", None),
("dir/conf", None),
("main.nginx", None),
] {
assert_eq!(resolve_language(hint, ""), expected, "{hint}");
}
}
#[test]
fn bundled_grammars_preserve_query_support() -> Result<(), SyntaxError> {
let mut highlighter = SyntaxHighlighter::default();
let theme = SyntaxTheme::default();
for language in [
"asm",
"bash",
"batch",
"c",
"c-sharp",
"clojure",
"cmake",
"commonlisp",
"cpp",
"css",
"dart",
"diff",
"dockerfile",
"elixir",
"erlang",
"fish",
"go",
"graphql",
"haskell",
"hcl",
"html",
"ini",
"java",
"javascript",
"json",
"just",
"kotlin",
"lua",
"make",
"markdown",
"meson",
"ninja",
"nix",
"objc",
"ocaml",
"perl",
"php",
"powershell",
"proto",
"python",
"r",
"rego",
"ruby",
"rust",
"scala",
"scheme",
"scss",
"solidity",
"sql",
"starlark",
"svelte",
"swift",
"toml",
"tsx",
"typescript",
"vue",
"x86asm",
"xml",
"yaml",
"zig",
"zsh",
] {
assert_eq!(resolve_language(language, ""), Some(language));
let result = highlighter.with_theme(&theme).highlight_document(
Fingerprint::of(["x\n"]),
language,
|| "x\n",
);
if language == "perl" {
assert!(
matches!(result, Err(SyntaxError::Query { source, .. })
if source.kind == QueryErrorKind::Structure && source.row == 136),
"Arborium 2.18.2 Perl query has an invalid postfix_deref pattern"
);
} else {
result?;
}
}
Ok(())
}
#[test]
fn bundled_languages_resolve_representative_families_and_exclude_nginx() -> Result<(), SyntaxError>
{
let fixtures = [
("main.zig", "const x: u8 = 1;", "zig"),
("flake.nix", "{ pkgs, ... }: { }", "nix"),
("Main.hs", "main = putStrLn \"ok\"", "haskell"),
("app.exs", "IO.puts(\"ok\")", "elixir"),
("build.sbt", "val x = 1", "scala"),
("deps.edn", "{:deps {}}", "clojure"),
("main.ml", "let x = 1", "ocaml"),
("script.ps1", "Write-Host 'ok'", "powershell"),
("script.fish", "echo ok", "fish"),
("Makefile", "all:\n\techo ok", "make"),
("CMakeLists.txt", "project(foo)", "cmake"),
("build.ninja", "rule cc", "ninja"),
("main.tf", "resource \"x\" \"y\" {}", "hcl"),
("schema.graphql", "type Query { x: Int }", "graphql"),
("message.proto", "message X {}", "proto"),
("icon.svg", "<svg></svg>", "xml"),
("App.vue", "<template></template>", "vue"),
("App.svelte", "<script>let x = 1;</script>", "svelte"),
("style.scss", "$x: red;", "scss"),
("main.m", "@interface X @end", "objc"),
("lib.rs.patch", "+fn main() {}", "diff"),
];
let theme = SyntaxTheme::default();
let mut highlighter = SyntaxHighlighter::default();
let mut styled_fixture_count = 0;
for (path, source, expected) in fixtures {
assert_eq!(
resolve_language(LanguageHint::Path(path), source),
Some(expected),
"{path}"
);
let highlights = highlighter.with_theme(&theme).highlight_document(
Fingerprint::of([source]),
LanguageHint::Path(path),
|| source,
)?;
styled_fixture_count += usize::from((0..highlights.line_count()).any(|index| {
highlights
.line(index)
.is_some_and(|spans| !spans.is_empty())
}));
}
assert!(
styled_fixture_count >= 15,
"only {styled_fixture_count} fixtures highlighted"
);
assert_eq!(resolve_language("nginx", "server {}"), None);
Ok(())
}