#![allow(dead_code)]
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use crate::resolution::ModuleResolver;
use crate::resolution::tsconfig::TsConfig;
use super::FileGraph;
pub struct Project {
pub root: PathBuf,
pub config_path: Option<PathBuf>,
pub config: Option<TsConfig>,
pub resolver: ModuleResolver,
pub files: HashSet<PathBuf>,
pub file_graph: FileGraph,
}
impl Project {
pub fn new(root: PathBuf) -> Self {
Self {
root: root.clone(),
config_path: None,
config: None,
resolver: ModuleResolver::new(root),
files: HashSet::new(),
file_graph: FileGraph::new(),
}
}
pub fn from_tsconfig(config_path: PathBuf) -> Result<Self, String> {
let root = config_path.parent().unwrap_or(Path::new(".")).to_path_buf();
let config = TsConfig::load(&config_path).map_err(|e| e.to_string())?;
let resolver = ModuleResolver::with_tsconfig(root.clone(), &config);
let mut project = Self {
root,
config_path: Some(config_path),
config: Some(config),
resolver,
files: HashSet::new(),
file_graph: FileGraph::new(),
};
project.discover_files()?;
Ok(project)
}
fn discover_files(&mut self) -> Result<(), String> {
let include_patterns: Vec<String> = match &self.config {
Some(c) => c.include.clone().unwrap_or_default(),
None => return Ok(()),
};
if include_patterns.is_empty() {
self.discover_default_files()?;
} else {
for pattern in &include_patterns {
self.discover_files_by_pattern(pattern)?;
}
}
Ok(())
}
fn discover_default_files(&mut self) -> Result<(), String> {
let extensions = ["ts", "tsx", "mts", "cts"];
if let Ok(entries) = std::fs::read_dir(&self.root) {
for entry in entries.filter_map(|e| e.ok()) {
let path = entry.path();
if path.is_file() {
if let Some(ext) = path.extension() {
if extensions.contains(&ext.to_string_lossy().as_ref()) {
self.files.insert(path);
}
}
} else if path.is_dir() {
if let Some(name) = path.file_name() {
let name = name.to_string_lossy();
if !name.starts_with('.') && name != "node_modules" {
self.discover_files_in_dir(&path, &extensions)?;
}
}
}
}
}
Ok(())
}
fn discover_files_in_dir(&mut self, dir: &Path, extensions: &[&str]) -> Result<(), String> {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.filter_map(|e| e.ok()) {
let path = entry.path();
if path.is_file() {
if let Some(ext) = path.extension() {
if extensions.contains(&ext.to_string_lossy().as_ref()) {
self.files.insert(path);
}
}
} else if path.is_dir() {
if let Some(name) = path.file_name() {
let name = name.to_string_lossy();
if !name.starts_with('.') && name != "node_modules" {
self.discover_files_in_dir(&path, extensions)?;
}
}
}
}
}
Ok(())
}
fn discover_files_by_pattern(&mut self, pattern: &str) -> Result<(), String> {
let pattern_path = self.root.join(pattern);
let pattern_str = pattern_path.to_string_lossy();
if pattern_str.contains("**") {
let parts: Vec<&str> = pattern.split("**").collect();
if let Some(base) = parts.first() {
let base_path = self.root.join(base.trim_end_matches('/'));
if base_path.is_dir() {
let extensions = ["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs"];
self.discover_files_in_dir(&base_path, &extensions)?;
}
}
} else if pattern_str.contains('*') {
if let Some(parent) = pattern_path.parent() {
if parent.is_dir() {
if let Ok(entries) = std::fs::read_dir(parent) {
for entry in entries.filter_map(|e| e.ok()) {
let path = entry.path();
if path.is_file() {
self.files.insert(path);
}
}
}
}
}
} else if pattern_path.is_file() {
self.files.insert(pattern_path);
}
Ok(())
}
pub fn add_file(&mut self, path: PathBuf) {
self.files.insert(path);
}
pub fn remove_file(&mut self, path: &Path) {
self.files.remove(path);
self.file_graph.remove_file(path);
}
pub fn contains_file(&self, path: &Path) -> bool {
self.files.contains(path)
}
pub fn get_files(&self) -> impl Iterator<Item = &PathBuf> {
self.files.iter()
}
}