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