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