Skip to main content

reference_query/lang/
mod.rs

1//! Language plugins — the only seam languages plug into.
2//!
3//! A plugin maps source text to the common [`Symbol`](crate::core::Symbol)
4//! model. The core stays language-agnostic; adding a language is a new plugin,
5//! not a core change.
6
7use crate::core::Symbol;
8
9pub mod go;
10pub mod python;
11pub mod ruby;
12pub mod rust;
13
14/// Extracts definitions from a single source file.
15pub trait LanguagePlugin {
16    /// The language tag emitted on every [`Symbol`] (e.g. `"ruby"`). Also the
17    /// canonical name `--lang` matches against.
18    fn language(&self) -> &'static str;
19
20    /// File extensions this plugin handles, without the dot (e.g. `["rb"]`).
21    fn extensions(&self) -> &[&str];
22
23    /// Extract definitions from `source`. `file` is the repo-relative path,
24    /// recorded on each emitted [`Symbol`].
25    fn extract(&self, file: &str, source: &str) -> Vec<Symbol>;
26}
27
28/// The tags of all registered languages — the set `--lang` matches against, so
29/// it can't drift from the registry.
30pub fn languages() -> Vec<&'static str> {
31    registry().iter().map(|p| p.language()).collect()
32}
33
34/// The registered language plugins. Adding a language is one line here.
35pub fn registry() -> Vec<Box<dyn LanguagePlugin>> {
36    vec![
37        Box::new(ruby::Ruby),
38        Box::new(rust::Rust),
39        Box::new(go::Go),
40        Box::new(python::Python),
41    ]
42}
43
44/// The plugin handling files with the given extension (without the dot), if any.
45pub fn plugin_for_extension(ext: &str) -> Option<Box<dyn LanguagePlugin>> {
46    registry()
47        .into_iter()
48        .find(|p| p.extensions().contains(&ext))
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn languages_are_registered_by_extension() {
57        for ext in ["rb", "rs", "go", "py"] {
58            assert!(plugin_for_extension(ext).is_some(), "{ext} should resolve");
59        }
60        assert!(plugin_for_extension("java").is_none());
61    }
62}