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