Skip to main content

sem_core/parser/
registry.rs

1use std::collections::HashMap;
2use std::path::Path;
3
4use super::plugin::SemanticParserPlugin;
5
6pub struct ParserRegistry {
7    plugins: Vec<Box<dyn SemanticParserPlugin>>,
8    extension_map: HashMap<String, usize>, // ext → index into plugins
9}
10
11impl ParserRegistry {
12    pub fn new() -> Self {
13        Self {
14            plugins: Vec::new(),
15            extension_map: HashMap::new(),
16        }
17    }
18
19    pub fn register(&mut self, plugin: Box<dyn SemanticParserPlugin>) {
20        let idx = self.plugins.len();
21        for ext in plugin.extensions() {
22            self.extension_map.insert(ext.to_string(), idx);
23        }
24        self.plugins.push(plugin);
25    }
26
27    pub fn get_plugin(&self, file_path: &str) -> Option<&dyn SemanticParserPlugin> {
28        for ext in get_extensions(file_path) {
29            if let Some(&idx) = self.extension_map.get(&ext) {
30                return Some(self.plugins[idx].as_ref());
31            }
32        }
33        // Fallback plugin
34        self.get_plugin_by_id("fallback")
35    }
36
37    pub fn get_plugin_by_id(&self, id: &str) -> Option<&dyn SemanticParserPlugin> {
38        self.plugins
39            .iter()
40            .find(|p| p.id() == id)
41            .map(|p| p.as_ref())
42    }
43}
44
45fn get_extensions(file_path: &str) -> Vec<String> {
46    let Some(file_name) = Path::new(file_path)
47        .file_name()
48        .and_then(|name| name.to_str())
49    else {
50        return Vec::new();
51    };
52
53    let file_name = file_name.to_lowercase();
54    let mut extensions = Vec::new();
55
56    for (idx, ch) in file_name.char_indices() {
57        if ch == '.' {
58            extensions.push(file_name[idx..].to_string());
59        }
60    }
61
62    extensions
63}
64
65#[cfg(test)]
66mod tests {
67    use crate::parser::plugins::create_default_registry;
68
69    #[test]
70    fn test_registry_matches_compound_svelte_typescript_suffix() {
71        let registry = create_default_registry();
72        let plugin = registry
73            .get_plugin("src/routes/+page.svelte.ts")
74            .expect("plugin should exist");
75
76        assert_eq!(plugin.id(), "svelte");
77    }
78
79    #[test]
80    fn test_registry_matches_compound_svelte_javascript_suffix() {
81        let registry = create_default_registry();
82        let plugin = registry
83            .get_plugin("src/routes/+layout.svelte.js")
84            .expect("plugin should exist");
85
86        assert_eq!(plugin.id(), "svelte");
87    }
88
89    #[test]
90    fn test_registry_matches_svelte_test_suffix() {
91        let registry = create_default_registry();
92        let plugin = registry
93            .get_plugin("src/lib/multiplier.svelte.test.js")
94            .expect("plugin should exist");
95
96        assert_eq!(plugin.id(), "svelte");
97    }
98
99    #[test]
100    fn test_registry_prefers_svelte_plugin_for_component_files() {
101        let registry = create_default_registry();
102        let plugin = registry
103            .get_plugin("src/lib/Component.svelte")
104            .expect("plugin should exist");
105
106        assert_eq!(plugin.id(), "svelte");
107    }
108
109    #[test]
110    fn test_registry_matches_typescript_module_suffix() {
111        let registry = create_default_registry();
112        let plugin = registry
113            .get_plugin("src/lib/index.mts")
114            .expect("plugin should exist");
115
116        assert_eq!(plugin.id(), "code");
117    }
118
119    #[test]
120    fn test_registry_matches_typescript_commonjs_suffix() {
121        let registry = create_default_registry();
122        let plugin = registry
123            .get_plugin("src/lib/index.cts")
124            .expect("plugin should exist");
125
126        assert_eq!(plugin.id(), "code");
127    }
128}