Skip to main content

strop_syntax/
languages.rs

1//! The curated language registry (0002 §2.2: statically linked, always).
2//! Extension → (grammar, highlights query). Adding a language is one
3//! row here plus one crate in Cargo.toml — never a download, never a
4//! dlopen. C++ included specifically: its scanner is C++, the musl
5//! static-libstdc++ path the release gate guards (0002 §5).
6
7use tree_sitter::Language;
8
9pub struct LanguageSpec {
10    pub name: &'static str,
11    pub language: Language,
12    pub highlights: &'static str,
13}
14
15// tree-sitter-toml 0.20 targets an old ABI (a second tree-sitter in the
16// tree — not worth it); TOML joins when a 0.24-ABI grammar crate exists.
17macro_rules! lang_fn {
18    ($name:literal, $f:expr, $q:expr) => {
19        LanguageSpec {
20            name: $name,
21            language: $f.into(),
22            highlights: $q,
23        }
24    };
25}
26
27/// Extension (with dot) → spec. First match wins.
28pub fn for_extension(ext: &str) -> Option<LanguageSpec> {
29    Some(match ext {
30        ".rs" => lang_fn!(
31            "rust",
32            tree_sitter_rust::LANGUAGE,
33            tree_sitter_rust::HIGHLIGHTS_QUERY
34        ),
35        ".py" | ".pyi" => {
36            lang_fn!(
37                "python",
38                tree_sitter_python::LANGUAGE,
39                tree_sitter_python::HIGHLIGHTS_QUERY
40            )
41        }
42        ".js" | ".jsx" | ".mjs" | ".cjs" => {
43            lang_fn!(
44                "javascript",
45                tree_sitter_javascript::LANGUAGE,
46                tree_sitter_javascript::HIGHLIGHT_QUERY
47            )
48        }
49        ".ts" => lang_fn!(
50            "typescript",
51            tree_sitter_typescript::LANGUAGE_TYPESCRIPT,
52            tree_sitter_typescript::HIGHLIGHTS_QUERY
53        ),
54        ".tsx" => lang_fn!(
55            "tsx",
56            tree_sitter_typescript::LANGUAGE_TSX,
57            tree_sitter_typescript::HIGHLIGHTS_QUERY
58        ),
59        ".go" => lang_fn!(
60            "go",
61            tree_sitter_go::LANGUAGE,
62            tree_sitter_go::HIGHLIGHTS_QUERY
63        ),
64        ".c" | ".h" => lang_fn!("c", tree_sitter_c::LANGUAGE, tree_sitter_c::HIGHLIGHT_QUERY),
65        ".cpp" | ".cc" | ".cxx" | ".hpp" | ".hh" => {
66            lang_fn!(
67                "cpp",
68                tree_sitter_cpp::LANGUAGE,
69                tree_sitter_cpp::HIGHLIGHT_QUERY
70            )
71        }
72        ".json" => lang_fn!(
73            "json",
74            tree_sitter_json::LANGUAGE,
75            tree_sitter_json::HIGHLIGHTS_QUERY
76        ),
77        ".sh" | ".bash" => lang_fn!(
78            "bash",
79            tree_sitter_bash::LANGUAGE,
80            tree_sitter_bash::HIGHLIGHT_QUERY
81        ),
82        _ => return None,
83    })
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn covers_the_curated_set() {
92        for ext in [
93            ".rs", ".py", ".js", ".ts", ".tsx", ".go", ".c", ".cpp", ".json", ".sh",
94        ] {
95            assert!(for_extension(ext).is_some(), "missing {ext}");
96        }
97        assert!(for_extension(".xyz").is_none());
98    }
99}