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