nodejs_resolver/
fs.rs

1use crate::{
2    description::{DescriptionData, PkgJSON},
3    entry::EntryStat,
4    tsconfig::TsConfig,
5    RResult,
6};
7use rustc_hash::FxHasher;
8use std::{
9    fmt::Debug,
10    fs,
11    hash::BuildHasherDefault,
12    path::{Path, PathBuf},
13    sync::Arc,
14    time::SystemTime,
15};
16
17use dashmap::DashMap;
18
19use std::time::Duration;
20
21#[derive(Debug, Default)]
22pub struct CachedFS {
23    /// Caches raw files
24    entries: CachedMap<String>,
25
26    /// Caches parsed package.json
27    descriptions: CachedMap<DescriptionData>,
28
29    /// Caches tsconfig.json
30    tsconfigs: CachedMap<serde_json::Value>,
31}
32
33pub type CachedMap<T> = DashMap<PathBuf, CachedEntry<T>, BuildHasherDefault<FxHasher>>;
34
35#[derive(Debug)]
36pub struct CachedEntry<T: Sized> {
37    content: Arc<T>,
38    stat: EntryStat,
39}
40
41impl<T> Clone for CachedEntry<T> {
42    fn clone(&self) -> Self {
43        Self {
44            content: Arc::clone(&self.content),
45            stat: self.stat,
46        }
47    }
48}
49
50impl<T: Sized> CachedEntry<T> {
51    fn new(content: T, stat: EntryStat) -> Self {
52        Self {
53            content: content.into(),
54            stat,
55        }
56    }
57
58    fn content(&self) -> Arc<T> {
59        self.content.clone()
60    }
61}
62
63const DEBOUNCE_INTERVAL: Duration = Duration::from_millis(300);
64
65impl CachedFS {
66    pub fn read_file(&self, path: &Path, file_stat: EntryStat) -> RResult<Arc<String>> {
67        if let Some(cached) = self.entries.get(path) {
68            if self.is_modified(file_stat.modified(), cached.stat.modified()) {
69                return Ok(cached.value().content());
70            }
71        }
72        let string = fs::read_to_string(path)?;
73        let entry = CachedEntry::new(string, file_stat);
74        self.entries.insert(path.to_path_buf(), entry.clone());
75        Ok(entry.content())
76    }
77
78    pub fn read_description_file(
79        &self,
80        path: &Path,
81        file_stat: EntryStat,
82    ) -> RResult<Arc<DescriptionData>> {
83        if let Some(cached) = self.descriptions.get(path) {
84            if self.is_modified(file_stat.modified(), cached.stat.modified()) {
85                return Ok(cached.value().content());
86            }
87        }
88        let string = fs::read_to_string(path)?;
89        let json = PkgJSON::parse(&string, path)?;
90        let dir = path.parent().unwrap().to_path_buf();
91        let info = DescriptionData::new(json, dir);
92        let entry = CachedEntry::new(info, file_stat);
93        self.descriptions.insert(path.to_path_buf(), entry.clone());
94        Ok(entry.content())
95    }
96
97    pub fn read_tsconfig(
98        &self,
99        path: &Path,
100        file_stat: EntryStat,
101    ) -> RResult<Arc<serde_json::Value>> {
102        if let Some(cached) = self.tsconfigs.get(path) {
103            if self.is_modified(file_stat.modified(), cached.stat.modified()) {
104                return Ok(cached.value().content());
105            }
106        }
107        let string = fs::read_to_string(path)?;
108        let serde_json = TsConfig::parse(&string, path)?;
109        let entry = CachedEntry::new(serde_json, file_stat);
110        self.tsconfigs.insert(path.to_path_buf(), entry.clone());
111        Ok(entry.content())
112    }
113
114    fn is_modified(&self, before: Option<SystemTime>, after: Option<SystemTime>) -> bool {
115        if let (Some(before), Some(after)) = (before, after) {
116            if before.duration_since(after).expect("after > before") < DEBOUNCE_INTERVAL {
117                return true;
118            }
119        }
120        false
121    }
122}