1#![warn(missing_docs)]
8
9mod asset_url;
10pub mod astro;
11pub mod cache;
12pub(crate) mod complexity;
13pub mod css;
14pub mod flags;
15pub mod html;
16pub mod inventory;
17pub mod mdx;
18mod parse;
19pub mod sfc;
20mod sfc_template;
21pub mod suppress;
22pub(crate) mod template_complexity;
23mod template_usage;
24pub mod visitor;
25
26use std::path::Path;
27
28use rayon::prelude::*;
29
30use cache::CacheStore;
31use fallow_types::discover::{DiscoveredFile, FileId};
32
33pub use fallow_types::extract::{
35 ClassHeritageInfo, DynamicImportInfo, DynamicImportPattern, ExportInfo, ExportName, ImportInfo,
36 ImportedName, MemberAccess, MemberInfo, MemberKind, ModuleInfo, ParseResult, ReExportInfo,
37 RequireCallInfo, VisibilityTag, compute_line_offsets,
38};
39
40pub use astro::extract_astro_frontmatter;
42pub use css::extract_css_module_exports;
43pub use mdx::extract_mdx_statements;
44pub use sfc::{extract_sfc_scripts, is_sfc_file};
45pub use sfc_template::angular::ANGULAR_TPL_SENTINEL;
46
47pub const INSTANCE_EXPORT_SENTINEL: &str = "__fallow_instance_export__:";
53
54use parse::parse_source_to_module;
55
56pub fn parse_all_files(
63 files: &[DiscoveredFile],
64 cache: Option<&CacheStore>,
65 need_complexity: bool,
66) -> ParseResult {
67 use std::sync::atomic::{AtomicUsize, Ordering};
68 let cache_hits = AtomicUsize::new(0);
69 let cache_misses = AtomicUsize::new(0);
70
71 let modules: Vec<ModuleInfo> = files
72 .par_iter()
73 .filter_map(|file| {
74 parse_single_file_cached(file, cache, &cache_hits, &cache_misses, need_complexity)
75 })
76 .collect();
77
78 let hits = cache_hits.load(Ordering::Relaxed);
79 let misses = cache_misses.load(Ordering::Relaxed);
80 if hits > 0 || misses > 0 {
81 tracing::info!(
82 cache_hits = hits,
83 cache_misses = misses,
84 "incremental cache stats"
85 );
86 }
87
88 ParseResult {
89 modules,
90 cache_hits: hits,
91 cache_misses: misses,
92 }
93}
94
95fn mtime_secs(metadata: &std::fs::Metadata) -> u64 {
98 metadata
99 .modified()
100 .ok()
101 .and_then(|t| t.duration_since(std::time::SystemTime::UNIX_EPOCH).ok())
102 .map_or(0, |d| d.as_secs())
103}
104
105fn parse_single_file_cached(
114 file: &DiscoveredFile,
115 cache: Option<&CacheStore>,
116 cache_hits: &std::sync::atomic::AtomicUsize,
117 cache_misses: &std::sync::atomic::AtomicUsize,
118 need_complexity: bool,
119) -> Option<ModuleInfo> {
120 use std::sync::atomic::Ordering;
121
122 if let Some(store) = cache
125 && let Ok(metadata) = std::fs::metadata(&file.path)
126 {
127 let mt = mtime_secs(&metadata);
128 let sz = metadata.len();
129 if let Some(cached) = store.get_by_metadata(&file.path, mt, sz) {
130 if !need_complexity || !cached.complexity.is_empty() {
133 cache_hits.fetch_add(1, Ordering::Relaxed);
134 return Some(cache::cached_to_module(cached, file.id));
135 }
136 }
137 }
138
139 let source = std::fs::read_to_string(&file.path).ok()?;
141 let content_hash = xxhash_rust::xxh3::xxh3_64(source.as_bytes());
142
143 if let Some(store) = cache
145 && let Some(cached) = store.get(&file.path, content_hash)
146 && (!need_complexity || !cached.complexity.is_empty())
147 {
148 cache_hits.fetch_add(1, Ordering::Relaxed);
149 return Some(cache::cached_to_module(cached, file.id));
150 }
151 cache_misses.fetch_add(1, Ordering::Relaxed);
152
153 Some(parse_source_to_module(
155 file.id,
156 &file.path,
157 &source,
158 content_hash,
159 need_complexity,
160 ))
161}
162
163#[must_use]
165pub fn parse_single_file(file: &DiscoveredFile) -> Option<ModuleInfo> {
166 let source = std::fs::read_to_string(&file.path).ok()?;
167 let content_hash = xxhash_rust::xxh3::xxh3_64(source.as_bytes());
168 Some(parse_source_to_module(
169 file.id,
170 &file.path,
171 &source,
172 content_hash,
173 false,
174 ))
175}
176
177#[must_use]
179pub fn parse_from_content(file_id: FileId, path: &Path, content: &str) -> ModuleInfo {
180 let content_hash = xxhash_rust::xxh3::xxh3_64(content.as_bytes());
181 parse_source_to_module(file_id, path, content, content_hash, true)
182}
183
184#[cfg(all(test, not(miri)))]
187mod tests;