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