blast_radius/language/
mod.rs1use std::path::Path;
12use std::sync::OnceLock;
13
14use anyhow::Result;
15
16use crate::parse::ModuleFacts;
17use crate::resolve::{Resolution, ResolveCtx};
18
19mod javascript;
20use javascript::JavaScriptAdapter;
21
22#[cfg(any(feature = "vue", feature = "svelte"))]
23mod component;
24#[cfg(feature = "svelte")]
25use component::SvelteAdapter;
26#[cfg(feature = "vue")]
27use component::VueAdapter;
28
29#[cfg(feature = "python")]
30mod python;
31#[cfg(feature = "python")]
32use python::PythonAdapter;
33
34#[cfg(feature = "rust")]
35mod rust_lang;
36#[cfg(feature = "rust")]
37use rust_lang::RustAdapter;
38
39pub(crate) trait LanguageAdapter: Send + Sync {
42 fn extensions(&self) -> &'static [&'static str];
45
46 fn handles(&self, path: &Path) -> bool {
48 path.extension()
49 .and_then(|ext| ext.to_str())
50 .is_some_and(|ext| self.extensions().contains(&ext))
51 }
52
53 fn parse(&self, path: &Path, source: &str) -> Result<ModuleFacts>;
54
55 fn resolve(&self, ctx: &ResolveCtx, importer: &Path, specifier: &str) -> Resolution;
56
57 fn is_internal(&self, ctx: &ResolveCtx, importer: &Path, specifier: &str) -> bool;
58}
59
60fn registry() -> &'static [Box<dyn LanguageAdapter>] {
61 static REGISTRY: OnceLock<Vec<Box<dyn LanguageAdapter>>> = OnceLock::new();
62 REGISTRY.get_or_init(|| {
63 #[allow(unused_mut)]
67 let mut adapters: Vec<Box<dyn LanguageAdapter>> = vec![Box::new(JavaScriptAdapter)];
68 #[cfg(feature = "python")]
69 adapters.push(Box::new(PythonAdapter));
70 #[cfg(feature = "rust")]
71 adapters.push(Box::new(RustAdapter));
72 #[cfg(feature = "vue")]
73 adapters.push(Box::new(VueAdapter));
74 #[cfg(feature = "svelte")]
75 adapters.push(Box::new(SvelteAdapter));
76 adapters
77 })
78}
79
80pub(crate) fn adapter_for(path: &Path) -> &'static dyn LanguageAdapter {
83 let registry = registry();
84 registry
85 .iter()
86 .find(|adapter| adapter.handles(path))
87 .unwrap_or(®istry[0])
88 .as_ref()
89}
90
91fn source_extensions() -> &'static [&'static str] {
97 static EXTENSIONS: OnceLock<Vec<&'static str>> = OnceLock::new();
98 EXTENSIONS.get_or_init(|| {
99 registry()
100 .iter()
101 .flat_map(|adapter| adapter.extensions().iter().copied())
102 .collect()
103 })
104}
105
106pub(crate) fn is_source_extension(ext: &str) -> bool {
108 source_extensions().contains(&ext)
109}