use crate::parsing::LanguageBehavior;
use crate::parsing::behavior_state::{BehaviorState, StatefulBehavior};
use crate::parsing::paths::strip_extension;
use crate::parsing::resolution::ResolutionScope;
use crate::symbol::ScopeContext;
use crate::types::FileId;
use crate::{Symbol, Visibility};
use std::path::{Path, PathBuf};
use tree_sitter::Language;
use super::resolution::CSharpResolutionContext;
#[derive(Clone)]
pub struct CSharpBehavior {
state: BehaviorState,
}
impl CSharpBehavior {
pub fn new() -> Self {
Self {
state: BehaviorState::new(),
}
}
pub fn get_containing_class(&self, symbol: &Symbol) -> Option<String> {
if let Some(ScopeContext::ClassMember {
class_name: Some(class),
}) = &symbol.scope_context
{
if let Some(pkg) = &symbol.module_path {
if pkg.is_empty() {
return Some(class.to_string());
}
return Some(format!("{pkg}.{class}"));
}
return Some(class.to_string());
}
None
}
pub fn is_symbol_visible_from_file(&self, symbol: &Symbol, from_file: FileId) -> bool {
if symbol.file_id == from_file {
return true;
}
match symbol.visibility {
Visibility::Private => false,
Visibility::Public => true,
_ => true, }
}
}
impl Default for CSharpBehavior {
fn default() -> Self {
Self::new()
}
}
impl StatefulBehavior for CSharpBehavior {
fn state(&self) -> &BehaviorState {
&self.state
}
}
impl LanguageBehavior for CSharpBehavior {
fn language_id(&self) -> crate::parsing::registry::LanguageId {
crate::parsing::registry::LanguageId::new("csharp")
}
fn configure_symbol(&self, symbol: &mut crate::Symbol, module_path: Option<&str>) {
if let Some(path) = module_path {
let full_path = self.format_module_path(path, &symbol.name);
symbol.module_path = Some(full_path.into());
}
}
fn format_module_path(&self, base_path: &str, _symbol_name: &str) -> String {
base_path.to_string()
}
fn get_language(&self) -> Language {
tree_sitter_c_sharp::LANGUAGE.into()
}
fn format_path_as_module(&self, components: &[&str]) -> Option<String> {
if components.is_empty() {
None
} else {
Some(components.join("."))
}
}
fn module_separator(&self) -> &'static str {
"." }
fn module_path_from_file(
&self,
file_path: &Path,
project_root: &Path,
extensions: &[&str],
) -> Option<String> {
use crate::project_resolver::persist::ResolutionPersistence;
use std::cell::RefCell;
use std::time::{Duration, Instant};
thread_local! {
static RULES_CACHE: RefCell<Option<(Instant, crate::project_resolver::persist::ResolutionIndex)>> = const { RefCell::new(None) };
}
let cached_result = RULES_CACHE.with(|cache| {
let mut cache_ref = cache.borrow_mut();
let needs_reload = cache_ref
.as_ref()
.map(|(ts, _)| ts.elapsed() >= Duration::from_secs(1))
.unwrap_or(true);
if needs_reload {
let persistence =
ResolutionPersistence::new(std::path::Path::new(crate::init::local_dir_name()));
if let Ok(index) = persistence.load("csharp") {
*cache_ref = Some((Instant::now(), index));
} else {
*cache_ref = None;
}
}
if let Some((_, ref index)) = *cache_ref {
if let Ok(canon_file) = file_path.canonicalize() {
if let Some(config_path) = index.get_config_for_file(&canon_file) {
if let Some(rules) = index.rules.get(config_path) {
if let Some(ref base_url) = rules.base_url {
for root_path in rules.paths.keys() {
let root = std::path::Path::new(root_path);
let canon_root =
root.canonicalize().unwrap_or_else(|_| root.to_path_buf());
if let Ok(relative) = canon_file.strip_prefix(&canon_root) {
let relative_str = relative.to_str()?;
let path_without_ext =
strip_extension(relative_str, extensions);
let dir_path = if let Some(parent) =
Path::new(path_without_ext).parent()
{
parent.to_str().unwrap_or("")
} else {
""
};
if dir_path.is_empty() {
return Some(base_url.clone());
} else {
let namespace_suffix =
dir_path.replace(['/', '\\'], ".");
return Some(format!("{base_url}.{namespace_suffix}"));
}
}
}
}
}
}
}
}
None
});
if cached_result.is_some() {
return cached_result;
}
let relative_path = file_path
.strip_prefix(project_root)
.ok()
.or_else(|| file_path.strip_prefix("./").ok())
.unwrap_or(file_path);
let path = relative_path.to_str()?;
let path_without_prefix = path
.trim_start_matches("./")
.trim_start_matches("src/")
.trim_start_matches("lib/");
let module_path = strip_extension(path_without_prefix, extensions);
let namespace_path = module_path.replace(['/', '\\'], ".");
Some(namespace_path)
}
fn parse_visibility(&self, signature: &str) -> Visibility {
if signature.contains("public ") {
Visibility::Public
} else if signature.contains("private ") {
Visibility::Private
} else if signature.contains("protected ") {
Visibility::Module
} else if signature.contains("internal ") {
Visibility::Module
} else {
Visibility::Private
}
}
fn supports_traits(&self) -> bool {
true }
fn supports_inherent_methods(&self) -> bool {
true }
fn create_resolution_context(&self, file_id: FileId) -> Box<dyn ResolutionScope> {
Box::new(CSharpResolutionContext::new(file_id))
}
fn inheritance_relation_name(&self) -> &'static str {
"inherits"
}
fn map_relationship(&self, language_specific: &str) -> crate::relationship::RelationKind {
use crate::relationship::RelationKind;
match language_specific {
"inherits" | "extends" => RelationKind::Extends,
"implements" => RelationKind::Implements,
"uses" => RelationKind::Uses,
"calls" => RelationKind::Calls,
"defines" => RelationKind::Defines,
_ => RelationKind::References,
}
}
fn register_file(&self, path: PathBuf, file_id: FileId, module_path: String) {
self.register_file_with_state(path, file_id, module_path);
}
fn add_import(&self, import: crate::parsing::Import) {
self.add_import_with_state(import);
}
fn get_imports_for_file(&self, file_id: FileId) -> Vec<crate::parsing::Import> {
self.get_imports_from_state(file_id)
}
fn is_resolvable_symbol(&self, symbol: &crate::Symbol) -> bool {
use crate::SymbolKind;
use crate::symbol::ScopeContext;
let always_resolvable = matches!(
symbol.kind,
SymbolKind::Class
| SymbolKind::Interface
| SymbolKind::Struct
| SymbolKind::Enum
| SymbolKind::Method
| SymbolKind::Field
);
if always_resolvable {
return true;
}
if let Some(ref scope_context) = symbol.scope_context {
match scope_context {
ScopeContext::Module | ScopeContext::Global | ScopeContext::Package => true,
ScopeContext::Local { .. } | ScopeContext::Parameter => false,
ScopeContext::ClassMember { .. } => {
matches!(symbol.visibility, Visibility::Public | Visibility::Module)
}
}
} else {
matches!(
symbol.kind,
SymbolKind::TypeAlias | SymbolKind::Constant | SymbolKind::Variable
)
}
}
fn get_module_path_for_file(&self, file_id: FileId) -> Option<String> {
self.state.get_module_path(file_id)
}
fn import_matches_symbol(
&self,
import_path: &str,
symbol_module_path: &str,
_importing_module: Option<&str>,
) -> bool {
import_path == symbol_module_path
|| symbol_module_path.starts_with(&format!("{import_path}."))
}
}