pub mod bash;
#[cfg(feature = "semantic-c")]
mod c;
#[cfg(feature = "semantic-clojure")]
mod clojure;
#[cfg(feature = "semantic-cpp")]
mod cpp;
#[cfg(feature = "semantic-elixir")]
mod elixir;
#[cfg(feature = "semantic-go")]
mod go;
#[cfg(feature = "semantic-java")]
mod java;
#[cfg(feature = "semantic-python")]
mod python;
#[cfg(feature = "semantic-ruby")]
mod ruby;
#[cfg(feature = "semantic-rust")]
mod rust;
#[cfg(feature = "semantic-ts")]
mod typescript;
#[cfg(feature = "semantic-c")]
pub use c::CAdapter;
#[cfg(feature = "semantic-clojure")]
pub use clojure::ClojureAdapter;
#[cfg(feature = "semantic-cpp")]
pub use cpp::CppAdapter;
#[cfg(feature = "semantic-elixir")]
pub use elixir::ElixirAdapter;
#[cfg(feature = "semantic-go")]
pub use go::GoAdapter;
#[cfg(feature = "semantic-java")]
pub use java::JavaAdapter;
#[cfg(feature = "semantic-python")]
pub use python::PythonAdapter;
#[cfg(feature = "semantic-ruby")]
pub use ruby::RubyAdapter;
#[cfg(feature = "semantic-rust")]
pub use rust::RustAdapter;
#[cfg(feature = "semantic-ts")]
pub use typescript::TypescriptAdapter;
use std::path::Path;
use crate::semantic::adapter::LanguageAdapter;
pub struct AdapterRegistry {
adapters: Vec<Box<dyn LanguageAdapter>>,
}
impl AdapterRegistry {
pub fn new(adapters: Vec<Box<dyn LanguageAdapter>>) -> Self {
Self { adapters }
}
pub fn find_for_file(&self, file_path: &Path) -> Option<&dyn LanguageAdapter> {
self.find_for_file_with_content(file_path, None)
}
pub fn find_for_file_with_content(
&self,
file_path: &Path,
content: Option<&str>,
) -> Option<&dyn LanguageAdapter> {
let ext = file_path.extension()?.to_str()?.to_lowercase();
if ext == "h"
&& let Some(src) = content
&& self.looks_like_cpp_header(src)
{
if let Some(cpp) = self.adapters.iter().find(|a| {
a.extensions().iter().any(|e| {
e.trim_start_matches('.') == "cpp" || e.trim_start_matches('.') == "hpp"
})
}) {
return Some(cpp.as_ref());
}
}
self.adapters
.iter()
.find(|a| {
a.extensions()
.iter()
.any(|e| e.trim_start_matches('.') == ext)
})
.map(|a| a.as_ref())
}
fn looks_like_cpp_header(&self, src: &str) -> bool {
const SNIFF_BYTES: usize = 32 * 1024;
let head = if src.len() > SNIFF_BYTES {
let mut cut = SNIFF_BYTES;
while cut > 0 && !src.is_char_boundary(cut) {
cut -= 1;
}
&src[..cut]
} else {
src
};
let cleaned = strip_c_comments_and_strings(head);
cleaned.contains("class ")
|| cleaned.contains("namespace ")
|| cleaned.contains("template<")
|| cleaned.contains("template <")
|| cleaned.contains("::")
}
pub fn all_extensions(&self) -> Vec<String> {
self.adapters
.iter()
.flat_map(|a| {
a.extensions()
.iter()
.map(|e| e.trim_start_matches('.').to_string())
})
.collect()
}
}
fn strip_c_comments_and_strings(src: &str) -> String {
let bytes = src.as_bytes();
let mut out = String::with_capacity(src.len());
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b == b'/' && i + 1 < bytes.len() && bytes[i + 1] == b'*' {
i += 2;
while i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') {
i += 1;
}
i = (i + 2).min(bytes.len());
out.push(' ');
continue;
}
if b == b'/' && i + 1 < bytes.len() && bytes[i + 1] == b'/' {
i += 2;
while i < bytes.len() && bytes[i] != b'\n' {
i += 1;
}
out.push(' ');
continue;
}
if b == b'"' {
i += 1;
while i < bytes.len() && bytes[i] != b'"' {
if bytes[i] == b'\\' && i + 1 < bytes.len() {
i += 2;
} else {
i += 1;
}
}
i = (i + 1).min(bytes.len());
out.push(' ');
continue;
}
out.push(b as char);
i += 1;
}
out
}
#[cfg(test)]
mod sniff_tests {
use super::strip_c_comments_and_strings;
#[test]
fn strip_block_comment() {
let stripped = strip_c_comments_and_strings("int x; /* class Foo */ int y;");
assert!(!stripped.contains("class"));
}
#[test]
fn strip_line_comment() {
let stripped = strip_c_comments_and_strings("int x; // namespace foo\nint y;");
assert!(!stripped.contains("namespace"));
}
#[test]
fn strip_string_literal() {
let stripped = strip_c_comments_and_strings(r#"printf("a::b\n");"#);
assert!(!stripped.contains("::"));
}
#[test]
fn keeps_real_cpp_class() {
let stripped = strip_c_comments_and_strings("class Foo { int x; };");
assert!(stripped.contains("class "));
}
}