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