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