Skip to main content

diffkit/language/
mod.rs

1use std::error::Error;
2use std::path::Path;
3
4use crate::model::{LanguageId, SemanticCallGraph};
5
6pub mod ocaml;
7pub mod rust;
8
9pub type FrontendResult<T> = Result<T, Box<dyn Error + Send + Sync>>;
10
11pub struct FileContext<'a> {
12    pub path: &'a Path,
13    pub module: &'a [String],
14}
15
16pub struct ProjectContext<'a> {
17    pub root: &'a Path,
18    /// Executable used when a compiler requires DiffKit as a driver/wrapper.
19    pub driver_executable: Option<&'a Path>,
20    pub entries: &'a [String],
21    pub cache: Option<ProjectCache<'a>>,
22}
23
24#[derive(Clone, Copy)]
25pub struct ProjectCache<'a> {
26    pub project_root: &'a Path,
27    pub endpoint: &'a str,
28    pub verbose: bool,
29}
30
31/// A language frontend owns its parser/compiler representation and emits the
32/// small semantic call-graph result consumed by DiffKit. This is not a shared
33/// compiler AST: language-native AST, Typedtree, and MIR types never leak into
34/// the common engine.
35pub trait LanguageBackend: Send + Sync {
36    fn language(&self) -> LanguageId;
37    fn extensions(&self) -> &'static [&'static str];
38    fn analyze_file(
39        &self,
40        context: &FileContext<'_>,
41        source: &str,
42    ) -> FrontendResult<SemanticCallGraph>;
43    fn analyze_project(&self, context: &ProjectContext<'_>) -> FrontendResult<SemanticCallGraph>;
44}
45
46pub fn backend_for_extension(path: &Path) -> Option<&'static dyn LanguageBackend> {
47    let extension = path.extension()?.to_str()?;
48    builtin_backends().into_iter().find(|backend| {
49        backend
50            .extensions()
51            .iter()
52            .any(|candidate| candidate.eq_ignore_ascii_case(extension))
53    })
54}
55
56pub fn backend_for_language(language: &str) -> Option<&'static dyn LanguageBackend> {
57    builtin_backends()
58        .into_iter()
59        .find(|backend| backend.language().0.eq_ignore_ascii_case(language))
60}
61
62pub fn builtin_backends() -> [&'static dyn LanguageBackend; 2] {
63    [&rust::RUST_BACKEND, &ocaml::OCAML_BACKEND]
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn registry_routes_extensions_without_engine_language_branches() {
72        assert_eq!(
73            backend_for_extension(Path::new("service.RS"))
74                .unwrap()
75                .language()
76                .0,
77            "rust"
78        );
79        assert_eq!(
80            backend_for_extension(Path::new("service.mli"))
81                .unwrap()
82                .language()
83                .0,
84            "ocaml"
85        );
86    }
87}