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}