Skip to main content

fallow_extract/cache/
conversion.rs

1//! Conversion between [`ModuleInfo`](crate::ModuleInfo) and [`CachedModule`].
2//!
3//! Both functions convert between borrowed source structs and owned target structs
4//! (`&CachedModule -> ModuleInfo`, `&ModuleInfo -> CachedModule`). All `String` clones
5//! are structurally necessary: the cache store retains ownership of `CachedModule`
6//! entries (for persistence), and `ModuleInfo` must outlive the cache for the
7//! analysis pipeline. Eliminating these clones would require shared ownership
8//! (`Arc<str>`) across the entire extraction + analysis pipeline.
9
10use oxc_span::Span;
11
12use crate::ExportName;
13
14use super::types::{
15    CachedDynamicImport, CachedDynamicImportPattern, CachedExport, CachedImport, CachedMember,
16    CachedModule, CachedReExport, CachedRequireCall, CachedSuppression, IMPORT_KIND_DEFAULT,
17    IMPORT_KIND_NAMED, IMPORT_KIND_NAMESPACE, IMPORT_KIND_SIDE_EFFECT,
18};
19
20/// Reconstruct a [`ModuleInfo`](crate::ModuleInfo) from a [`CachedModule`].
21#[must_use]
22pub fn cached_to_module(
23    cached: &CachedModule,
24    file_id: fallow_types::discover::FileId,
25) -> crate::ModuleInfo {
26    use crate::{
27        DynamicImportInfo, ExportInfo, ImportInfo, ImportedName, MemberInfo, ModuleInfo,
28        ReExportInfo, RequireCallInfo,
29    };
30
31    let exports = cached
32        .exports
33        .iter()
34        .map(|e| ExportInfo {
35            name: if e.is_default {
36                ExportName::Default
37            } else {
38                ExportName::Named(e.name.clone())
39            },
40            local_name: e.local_name.clone(),
41            is_type_only: e.is_type_only,
42            is_public: e.is_public,
43            span: Span::new(e.span_start, e.span_end),
44            members: e
45                .members
46                .iter()
47                .map(|m| MemberInfo {
48                    name: m.name.clone(),
49                    kind: m.kind,
50                    span: Span::new(m.span_start, m.span_end),
51                    has_decorator: m.has_decorator,
52                })
53                .collect(),
54        })
55        .collect();
56
57    let imports = cached
58        .imports
59        .iter()
60        .map(|i| ImportInfo {
61            source: i.source.clone(),
62            imported_name: match i.kind {
63                IMPORT_KIND_DEFAULT => ImportedName::Default,
64                IMPORT_KIND_NAMESPACE => ImportedName::Namespace,
65                IMPORT_KIND_SIDE_EFFECT => ImportedName::SideEffect,
66                // IMPORT_KIND_NAMED (0) and any unknown value default to Named
67                _ => ImportedName::Named(i.imported_name.clone()),
68            },
69            local_name: i.local_name.clone(),
70            is_type_only: i.is_type_only,
71            span: Span::new(i.span_start, i.span_end),
72            source_span: Span::new(i.source_span_start, i.source_span_end),
73        })
74        .collect();
75
76    let re_exports = cached
77        .re_exports
78        .iter()
79        .map(|r| ReExportInfo {
80            source: r.source.clone(),
81            imported_name: r.imported_name.clone(),
82            exported_name: r.exported_name.clone(),
83            is_type_only: r.is_type_only,
84        })
85        .collect();
86
87    let dynamic_imports = cached
88        .dynamic_imports
89        .iter()
90        .map(|d| DynamicImportInfo {
91            source: d.source.clone(),
92            span: Span::new(d.span_start, d.span_end),
93            destructured_names: d.destructured_names.clone(),
94            local_name: d.local_name.clone(),
95        })
96        .collect();
97
98    let require_calls = cached
99        .require_calls
100        .iter()
101        .map(|r| RequireCallInfo {
102            source: r.source.clone(),
103            span: Span::new(r.span_start, r.span_end),
104            destructured_names: r.destructured_names.clone(),
105            local_name: r.local_name.clone(),
106        })
107        .collect();
108
109    let dynamic_import_patterns = cached
110        .dynamic_import_patterns
111        .iter()
112        .map(|p| crate::DynamicImportPattern {
113            prefix: p.prefix.clone(),
114            suffix: p.suffix.clone(),
115            span: Span::new(p.span_start, p.span_end),
116        })
117        .collect();
118
119    let suppressions = cached
120        .suppressions
121        .iter()
122        .map(|s| crate::suppress::Suppression {
123            line: s.line,
124            kind: if s.kind == 0 {
125                None
126            } else {
127                crate::suppress::IssueKind::from_discriminant(s.kind)
128            },
129        })
130        .collect();
131
132    ModuleInfo {
133        file_id,
134        exports,
135        imports,
136        re_exports,
137        dynamic_imports,
138        dynamic_import_patterns,
139        require_calls,
140        member_accesses: cached.member_accesses.clone(),
141        whole_object_uses: cached.whole_object_uses.clone(),
142        has_cjs_exports: cached.has_cjs_exports,
143        content_hash: cached.content_hash,
144        suppressions,
145        unused_import_bindings: cached.unused_import_bindings.clone(),
146        line_offsets: cached.line_offsets.clone(),
147        complexity: cached.complexity.clone(),
148        flag_uses: cached.flag_uses.clone(),
149    }
150}
151
152/// Convert a [`ModuleInfo`](crate::ModuleInfo) to a [`CachedModule`] for storage.
153///
154/// `mtime_secs` and `file_size` come from `std::fs::metadata()` at parse time
155/// and enable fast cache validation on subsequent runs (skip file read when
156/// mtime+size match).
157#[must_use]
158pub fn module_to_cached(
159    module: &crate::ModuleInfo,
160    mtime_secs: u64,
161    file_size: u64,
162) -> CachedModule {
163    CachedModule {
164        content_hash: module.content_hash,
165        mtime_secs,
166        file_size,
167        exports: module
168            .exports
169            .iter()
170            .map(|e| CachedExport {
171                name: match &e.name {
172                    ExportName::Named(n) => n.clone(),
173                    ExportName::Default => String::new(),
174                },
175                is_default: matches!(e.name, ExportName::Default),
176                is_type_only: e.is_type_only,
177                is_public: e.is_public,
178                local_name: e.local_name.clone(),
179                span_start: e.span.start,
180                span_end: e.span.end,
181                members: e
182                    .members
183                    .iter()
184                    .map(|m| CachedMember {
185                        name: m.name.clone(),
186                        kind: m.kind,
187                        span_start: m.span.start,
188                        span_end: m.span.end,
189                        has_decorator: m.has_decorator,
190                    })
191                    .collect(),
192            })
193            .collect(),
194        imports: module
195            .imports
196            .iter()
197            .map(|i| {
198                let (kind, imported_name) = match &i.imported_name {
199                    crate::ImportedName::Named(n) => (IMPORT_KIND_NAMED, n.clone()),
200                    crate::ImportedName::Default => (IMPORT_KIND_DEFAULT, String::new()),
201                    crate::ImportedName::Namespace => (IMPORT_KIND_NAMESPACE, String::new()),
202                    crate::ImportedName::SideEffect => (IMPORT_KIND_SIDE_EFFECT, String::new()),
203                };
204                CachedImport {
205                    source: i.source.clone(),
206                    imported_name,
207                    local_name: i.local_name.clone(),
208                    is_type_only: i.is_type_only,
209                    kind,
210                    span_start: i.span.start,
211                    span_end: i.span.end,
212                    source_span_start: i.source_span.start,
213                    source_span_end: i.source_span.end,
214                }
215            })
216            .collect(),
217        re_exports: module
218            .re_exports
219            .iter()
220            .map(|r| CachedReExport {
221                source: r.source.clone(),
222                imported_name: r.imported_name.clone(),
223                exported_name: r.exported_name.clone(),
224                is_type_only: r.is_type_only,
225            })
226            .collect(),
227        dynamic_imports: module
228            .dynamic_imports
229            .iter()
230            .map(|d| CachedDynamicImport {
231                source: d.source.clone(),
232                span_start: d.span.start,
233                span_end: d.span.end,
234                destructured_names: d.destructured_names.clone(),
235                local_name: d.local_name.clone(),
236            })
237            .collect(),
238        require_calls: module
239            .require_calls
240            .iter()
241            .map(|r| CachedRequireCall {
242                source: r.source.clone(),
243                span_start: r.span.start,
244                span_end: r.span.end,
245                destructured_names: r.destructured_names.clone(),
246                local_name: r.local_name.clone(),
247            })
248            .collect(),
249        member_accesses: module.member_accesses.clone(),
250        whole_object_uses: module.whole_object_uses.clone(),
251        dynamic_import_patterns: module
252            .dynamic_import_patterns
253            .iter()
254            .map(|p| CachedDynamicImportPattern {
255                prefix: p.prefix.clone(),
256                suffix: p.suffix.clone(),
257                span_start: p.span.start,
258                span_end: p.span.end,
259            })
260            .collect(),
261        has_cjs_exports: module.has_cjs_exports,
262        unused_import_bindings: module.unused_import_bindings.clone(),
263        suppressions: module
264            .suppressions
265            .iter()
266            .map(|s| CachedSuppression {
267                line: s.line,
268                kind: s
269                    .kind
270                    .map_or(0, crate::suppress::IssueKind::to_discriminant),
271            })
272            .collect(),
273        line_offsets: module.line_offsets.clone(),
274        complexity: module.complexity.clone(),
275        flag_uses: module.flag_uses.clone(),
276    }
277}