1#![warn(missing_docs)]
8
9pub mod astro;
10pub mod cache;
11pub mod css;
12pub mod mdx;
13mod parse;
14pub mod sfc;
15pub mod suppress;
16pub mod visitor;
17
18use std::path::Path;
19
20use rayon::prelude::*;
21
22use cache::CacheStore;
23use fallow_types::discover::{DiscoveredFile, FileId};
24
25pub use fallow_types::extract::{
27 DynamicImportInfo, DynamicImportPattern, ExportInfo, ExportName, ImportInfo, ImportedName,
28 MemberAccess, MemberInfo, MemberKind, ModuleInfo, ParseResult, ReExportInfo, RequireCallInfo,
29 compute_line_offsets,
30};
31
32pub use astro::extract_astro_frontmatter;
34pub use css::extract_css_module_exports;
35pub use mdx::extract_mdx_statements;
36pub use sfc::{extract_sfc_scripts, is_sfc_file};
37
38use parse::parse_source_to_module;
39
40pub fn parse_all_files(files: &[DiscoveredFile], cache: Option<&CacheStore>) -> ParseResult {
43 use std::sync::atomic::{AtomicUsize, Ordering};
44 let cache_hits = AtomicUsize::new(0);
45 let cache_misses = AtomicUsize::new(0);
46
47 let modules: Vec<ModuleInfo> = files
48 .par_iter()
49 .filter_map(|file| parse_single_file_cached(file, cache, &cache_hits, &cache_misses))
50 .collect();
51
52 let hits = cache_hits.load(Ordering::Relaxed);
53 let misses = cache_misses.load(Ordering::Relaxed);
54 if hits > 0 || misses > 0 {
55 tracing::info!(
56 cache_hits = hits,
57 cache_misses = misses,
58 "incremental cache stats"
59 );
60 }
61
62 ParseResult {
63 modules,
64 cache_hits: hits,
65 cache_misses: misses,
66 }
67}
68
69fn mtime_secs(metadata: &std::fs::Metadata) -> u64 {
72 metadata
73 .modified()
74 .ok()
75 .and_then(|t| t.duration_since(std::time::SystemTime::UNIX_EPOCH).ok())
76 .map_or(0, |d| d.as_secs())
77}
78
79fn parse_single_file_cached(
88 file: &DiscoveredFile,
89 cache: Option<&CacheStore>,
90 cache_hits: &std::sync::atomic::AtomicUsize,
91 cache_misses: &std::sync::atomic::AtomicUsize,
92) -> Option<ModuleInfo> {
93 use std::sync::atomic::Ordering;
94
95 if let Some(store) = cache
98 && let Ok(metadata) = std::fs::metadata(&file.path)
99 {
100 let mt = mtime_secs(&metadata);
101 let sz = metadata.len();
102 if let Some(cached) = store.get_by_metadata(&file.path, mt, sz) {
103 cache_hits.fetch_add(1, Ordering::Relaxed);
104 return Some(cache::cached_to_module(cached, file.id));
105 }
106 }
107
108 let source = std::fs::read_to_string(&file.path).ok()?;
110 let content_hash = xxhash_rust::xxh3::xxh3_64(source.as_bytes());
111
112 if let Some(store) = cache
114 && let Some(cached) = store.get(&file.path, content_hash)
115 {
116 cache_hits.fetch_add(1, Ordering::Relaxed);
117 return Some(cache::cached_to_module(cached, file.id));
118 }
119 cache_misses.fetch_add(1, Ordering::Relaxed);
120
121 Some(parse_source_to_module(
123 file.id,
124 &file.path,
125 &source,
126 content_hash,
127 ))
128}
129
130pub fn parse_single_file(file: &DiscoveredFile) -> Option<ModuleInfo> {
132 let source = std::fs::read_to_string(&file.path).ok()?;
133 let content_hash = xxhash_rust::xxh3::xxh3_64(source.as_bytes());
134 Some(parse_source_to_module(
135 file.id,
136 &file.path,
137 &source,
138 content_hash,
139 ))
140}
141
142pub fn parse_from_content(file_id: FileId, path: &Path, content: &str) -> ModuleInfo {
144 let content_hash = xxhash_rust::xxh3::xxh3_64(content.as_bytes());
145 parse_source_to_module(file_id, path, content, content_hash)
146}
147
148#[cfg(all(test, not(miri)))]
151mod tests;