use crate::ir::Symbol;
use crate::ir::reference::UnresolvedRef;
pub trait LanguageAnalyzer {
fn language(&self) -> &'static str;
fn symbols_in_file(&self, path: &str, source: &str) -> Vec<Symbol>;
fn unresolved_refs(&self, path: &str, source: &str) -> Vec<UnresolvedRef>;
fn imports_in_file(
&self,
_path: &str,
_source: &str,
) -> std::collections::HashMap<String, String> {
Default::default()
}
}
pub mod go_spec;
pub mod java_spec;
pub mod js_spec;
pub mod path;
pub mod py_spec;
pub mod ruby_spec;
pub mod rust;
pub mod rust_spec;
pub mod rust_ts;
pub mod ts_spec;
pub mod util;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LanguageKind {
Auto,
Rust,
Ruby,
Python,
Javascript,
Typescript,
Tsx,
Go,
Java,
}
pub fn analyzer_for_path(path: &str, lang: LanguageKind) -> Option<Box<dyn LanguageAnalyzer>> {
let ext = std::path::Path::new(path)
.extension()
.and_then(|s| s.to_str())
.unwrap_or("");
let target = match lang {
LanguageKind::Rust => "rs",
LanguageKind::Ruby => "rb",
LanguageKind::Python => "py",
LanguageKind::Javascript => "js",
LanguageKind::Typescript => "ts",
LanguageKind::Tsx => "tsx",
LanguageKind::Go => "go",
LanguageKind::Java => "java",
LanguageKind::Auto => ext,
};
match target {
"rs" => Some(Box::new(rust_spec::SpecRustAnalyzer::new())),
"rb" => Some(Box::new(ruby_spec::SpecRubyAnalyzer::new())),
"py" => Some(Box::new(py_spec::SpecPyAnalyzer::new())),
"js" => Some(Box::new(js_spec::SpecJsAnalyzer::new())),
"ts" => Some(Box::new(ts_spec::SpecTsAnalyzer::new_ts())),
"tsx" => Some(Box::new(ts_spec::SpecTsAnalyzer::new_tsx())),
"go" => Some(Box::new(go_spec::SpecGoAnalyzer::new())),
"java" => Some(Box::new(java_spec::SpecJavaAnalyzer::new())),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::{LanguageKind, analyzer_for_path};
#[test]
fn analyzer_for_path_recognizes_go_java_python_extensions() {
assert!(analyzer_for_path("main.go", LanguageKind::Auto).is_some());
assert!(analyzer_for_path("Main.java", LanguageKind::Auto).is_some());
assert!(analyzer_for_path("main.py", LanguageKind::Auto).is_some());
assert!(analyzer_for_path("main.any", LanguageKind::Go).is_some());
assert!(analyzer_for_path("main.any", LanguageKind::Java).is_some());
assert!(analyzer_for_path("main.any", LanguageKind::Python).is_some());
}
#[test]
fn analyzer_for_path_existing_languages_unchanged() {
assert!(analyzer_for_path("src/lib.rs", LanguageKind::Auto).is_some());
assert!(analyzer_for_path("app/main.rb", LanguageKind::Auto).is_some());
assert!(analyzer_for_path("web/main.js", LanguageKind::Auto).is_some());
assert!(analyzer_for_path("web/main.ts", LanguageKind::Auto).is_some());
assert!(analyzer_for_path("web/main.tsx", LanguageKind::Auto).is_some());
assert!(analyzer_for_path("pkg/main.py", LanguageKind::Auto).is_some());
assert!(analyzer_for_path("x.any", LanguageKind::Rust).is_some());
assert!(analyzer_for_path("x.any", LanguageKind::Ruby).is_some());
assert!(analyzer_for_path("x.any", LanguageKind::Python).is_some());
assert!(analyzer_for_path("x.any", LanguageKind::Javascript).is_some());
assert!(analyzer_for_path("x.any", LanguageKind::Typescript).is_some());
assert!(analyzer_for_path("x.any", LanguageKind::Tsx).is_some());
}
#[test]
fn analyzer_for_path_uses_go_java_python_implementations_not_fallback() {
let go = analyzer_for_path("main.go", LanguageKind::Auto).expect("go analyzer");
assert_eq!(go.language(), "go");
let go_syms = go.symbols_in_file("main.go", "package main\nfunc main() {}\n");
assert!(go_syms.iter().any(|s| s.name == "main"));
let java = analyzer_for_path("Main.java", LanguageKind::Auto).expect("java analyzer");
assert_eq!(java.language(), "java");
let java_syms = java.symbols_in_file(
"Main.java",
"class Main {\n static void main(String[] args) {\n }\n}\n",
);
assert!(java_syms.iter().any(|s| s.name == "Main"));
assert!(java_syms.iter().any(|s| s.name == "main"));
let py = analyzer_for_path("main.py", LanguageKind::Auto).expect("python analyzer");
assert_eq!(py.language(), "python");
let py_syms = py.symbols_in_file("main.py", "def run():\n return 1\n");
assert!(py_syms.iter().any(|s| s.name == "run"));
}
}