use std::collections::HashMap;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::ts_syn::declarative::MacroDef;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ProjectDeclarativeRegistry {
by_file: HashMap<String, HashMap<String, MacroDef>>,
}
impl ProjectDeclarativeRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn insert_file(&mut self, file_path: impl Into<String>, macros: Vec<MacroDef>) {
if macros.is_empty() {
return;
}
let mut entry: HashMap<String, MacroDef> = HashMap::with_capacity(macros.len());
for def in macros {
entry.insert(def.name.clone(), def);
}
self.by_file.insert(file_path.into(), entry);
}
pub fn file_macros(&self, file_path: &Path) -> Option<&HashMap<String, MacroDef>> {
self.by_file.get(file_path.to_string_lossy().as_ref())
}
pub fn lookup(&self, file_path: &Path, name: &str) -> Option<&MacroDef> {
self.file_macros(file_path).and_then(|m| m.get(name))
}
pub fn is_empty(&self) -> bool {
self.by_file.is_empty()
}
pub fn file_count(&self) -> usize {
self.by_file.len()
}
pub fn macro_count(&self) -> usize {
self.by_file.values().map(|m| m.len()).sum()
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &HashMap<String, MacroDef>)> {
self.by_file.iter()
}
pub fn resolve_specifier(&self, importer: &Path, specifier: &str) -> Option<PathBuf> {
let parent = importer.parent()?;
let base = normalize_path(&parent.join(specifier));
let mut candidates: Vec<PathBuf> = Vec::with_capacity(5);
candidates.push(base.clone());
if base.extension().is_none() {
let mut with_ts = base.clone();
with_ts.set_extension("ts");
candidates.push(with_ts);
let mut with_tsx = base.clone();
with_tsx.set_extension("tsx");
candidates.push(with_tsx);
}
candidates.push(base.join("index.ts"));
candidates.push(base.join("index.tsx"));
for candidate in candidates {
let key = candidate.to_string_lossy();
if self.by_file.contains_key(key.as_ref()) {
return Some(candidate);
}
}
None
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
pub fn from_json(s: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(s)
}
}
fn normalize_path(path: &Path) -> PathBuf {
use std::path::Component;
let mut out = PathBuf::new();
for component in path.components() {
match component {
Component::CurDir => {}
Component::ParentDir => {
if !out.as_os_str().is_empty()
&& out
.components()
.next_back()
.is_some_and(|c| matches!(c, Component::Normal(_)))
{
out.pop();
} else {
out.push(component.as_os_str());
}
}
other => out.push(other.as_os_str()),
}
}
out
}