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 std::sync::Arc;
11use std::time::{SystemTime, UNIX_EPOCH};
12
13use oxc_span::Span;
14
15use crate::ExportName;
16use fallow_types::extract::{NamespaceObjectAlias, VisibilityTag};
17use fallow_types::suppress::{PolicyRuleSuppression, SuppressionTarget};
18
19/// Seconds-since-Unix-epoch from the wall clock, saturating to 0 if the
20/// system clock is set before the epoch. Used as the LRU bookkeeping
21/// timestamp on `CachedModule.last_access_secs`. Wall-clock (not monotonic)
22/// is the right source here because the value persists across process
23/// invocations.
24#[must_use]
25pub fn current_unix_seconds() -> u64 {
26    SystemTime::now()
27        .duration_since(UNIX_EPOCH)
28        .map_or(0, |d| d.as_secs())
29}
30
31use super::types::{
32    CachedDynamicImport, CachedDynamicImportPattern, CachedExport, CachedImport,
33    CachedLocalTypeDeclaration, CachedMember, CachedModule, CachedNamespaceObjectAlias,
34    CachedPublicSignatureTypeReference, CachedReExport, CachedRequireCall, CachedSuppression,
35    CachedUnknownSuppressionKind, IMPORT_KIND_DEFAULT, IMPORT_KIND_NAMED, IMPORT_KIND_NAMESPACE,
36    IMPORT_KIND_SIDE_EFFECT,
37};
38
39/// Reconstruct a [`ModuleInfo`](crate::ModuleInfo) from a [`CachedModule`].
40#[must_use]
41pub fn cached_to_module(
42    cached: &CachedModule,
43    file_id: fallow_types::discover::FileId,
44) -> crate::ModuleInfo {
45    cached_to_module_opts(cached, file_id, true)
46}
47
48fn cached_exports_to_module(exports: &[CachedExport]) -> Vec<crate::ExportInfo> {
49    exports
50        .iter()
51        .map(|export| crate::ExportInfo {
52            name: if export.is_default {
53                ExportName::Default
54            } else {
55                ExportName::Named(export.name.clone())
56            },
57            local_name: export.local_name.clone(),
58            is_type_only: export.is_type_only,
59            is_side_effect_used: export.is_side_effect_used,
60            visibility: match export.visibility {
61                1 => VisibilityTag::Public,
62                2 => VisibilityTag::Internal,
63                3 => VisibilityTag::Beta,
64                4 => VisibilityTag::Alpha,
65                5 => VisibilityTag::ExpectedUnused,
66                _ => VisibilityTag::None,
67            },
68            expected_unused_reason: export.expected_unused_reason.clone(),
69            span: Span::new(export.span_start, export.span_end),
70            members: export
71                .members
72                .iter()
73                .map(|member| crate::MemberInfo {
74                    name: member.name.clone(),
75                    kind: member.kind,
76                    span: Span::new(member.span_start, member.span_end),
77                    has_decorator: member.has_decorator,
78                    decorator_names: member.decorator_names.clone(),
79                    is_instance_returning_static: member.is_instance_returning_static,
80                    is_self_returning: member.is_self_returning,
81                })
82                .collect(),
83            super_class: export.super_class.clone(),
84        })
85        .collect()
86}
87
88fn cached_imports_to_module(imports: &[CachedImport]) -> Vec<crate::ImportInfo> {
89    imports
90        .iter()
91        .map(|import| crate::ImportInfo {
92            source: import.source.clone(),
93            imported_name: match import.kind {
94                IMPORT_KIND_DEFAULT => crate::ImportedName::Default,
95                IMPORT_KIND_NAMESPACE => crate::ImportedName::Namespace,
96                IMPORT_KIND_SIDE_EFFECT => crate::ImportedName::SideEffect,
97                _ => crate::ImportedName::Named(import.imported_name.clone()),
98            },
99            local_name: import.local_name.clone(),
100            is_type_only: import.is_type_only,
101            from_style: import.from_style,
102            span: Span::new(import.span_start, import.span_end),
103            source_span: Span::new(import.source_span_start, import.source_span_end),
104        })
105        .collect()
106}
107
108fn cached_re_exports_to_module(re_exports: &[CachedReExport]) -> Vec<crate::ReExportInfo> {
109    re_exports
110        .iter()
111        .map(|re_export| crate::ReExportInfo {
112            source: re_export.source.clone(),
113            imported_name: re_export.imported_name.clone(),
114            exported_name: re_export.exported_name.clone(),
115            is_type_only: re_export.is_type_only,
116            span: Span::new(re_export.span_start, re_export.span_end),
117            statement_span: Span::new(re_export.statement_span_start, re_export.statement_span_end),
118            source_span: Span::new(re_export.source_span_start, re_export.source_span_end),
119        })
120        .collect()
121}
122
123fn cached_dynamic_imports_to_module(
124    dynamic_imports: &[CachedDynamicImport],
125) -> Vec<crate::DynamicImportInfo> {
126    dynamic_imports
127        .iter()
128        .map(|dynamic_import| crate::DynamicImportInfo {
129            source: dynamic_import.source.clone(),
130            span: Span::new(dynamic_import.span_start, dynamic_import.span_end),
131            destructured_names: dynamic_import.destructured_names.clone(),
132            local_name: dynamic_import.local_name.clone(),
133            is_speculative: dynamic_import.is_speculative,
134        })
135        .collect()
136}
137
138fn cached_require_calls_to_module(
139    require_calls: &[CachedRequireCall],
140) -> Vec<crate::RequireCallInfo> {
141    require_calls
142        .iter()
143        .map(|require_call| crate::RequireCallInfo {
144            source: require_call.source.clone(),
145            span: Span::new(require_call.span_start, require_call.span_end),
146            source_span: Span::new(require_call.source_span_start, require_call.source_span_end),
147            destructured_names: require_call.destructured_names.clone(),
148            local_name: require_call.local_name.clone(),
149        })
150        .collect()
151}
152
153fn cached_dynamic_patterns_to_module(
154    dynamic_import_patterns: &[CachedDynamicImportPattern],
155) -> Vec<crate::DynamicImportPattern> {
156    dynamic_import_patterns
157        .iter()
158        .map(|pattern| crate::DynamicImportPattern {
159            prefix: pattern.prefix.clone(),
160            suffix: pattern.suffix.clone(),
161            span: Span::new(pattern.span_start, pattern.span_end),
162            mechanism: pattern.mechanism,
163        })
164        .collect()
165}
166
167fn cached_suppressions_to_module(
168    suppressions: &[CachedSuppression],
169) -> Vec<crate::suppress::Suppression> {
170    suppressions
171        .iter()
172        .map(|suppression| {
173            let target = if suppression.kind == 0 {
174                None
175            } else if suppression.kind
176                == crate::suppress::IssueKind::PolicyViolation.to_discriminant()
177                && !suppression.policy_pack.is_empty()
178                && !suppression.policy_rule_id.is_empty()
179            {
180                Some(SuppressionTarget::PolicyRule(PolicyRuleSuppression::new(
181                    suppression.policy_pack.clone(),
182                    suppression.policy_rule_id.clone(),
183                )))
184            } else {
185                crate::suppress::IssueKind::from_discriminant(suppression.kind)
186                    .map(SuppressionTarget::Issue)
187            };
188            crate::suppress::Suppression {
189                line: suppression.line,
190                comment_line: suppression.comment_line,
191                target,
192                reason: suppression.reason.clone(),
193            }
194        })
195        .collect()
196}
197
198fn cached_unknown_suppressions_to_module(
199    unknown_suppression_kinds: &[CachedUnknownSuppressionKind],
200) -> Vec<fallow_types::suppress::UnknownSuppressionKind> {
201    unknown_suppression_kinds
202        .iter()
203        .map(|unknown| fallow_types::suppress::UnknownSuppressionKind {
204            comment_line: unknown.comment_line,
205            is_file_level: unknown.is_file_level,
206            token: unknown.token.clone(),
207            reason: unknown.reason.clone(),
208        })
209        .collect()
210}
211
212fn cached_local_types_to_module(
213    local_type_declarations: &[CachedLocalTypeDeclaration],
214) -> Vec<crate::LocalTypeDeclaration> {
215    local_type_declarations
216        .iter()
217        .map(|declaration| crate::LocalTypeDeclaration {
218            name: declaration.name.clone(),
219            span: Span::new(declaration.span_start, declaration.span_end),
220        })
221        .collect()
222}
223
224fn cached_signature_refs_to_module(
225    public_signature_type_references: &[CachedPublicSignatureTypeReference],
226) -> Vec<crate::PublicSignatureTypeReference> {
227    public_signature_type_references
228        .iter()
229        .map(|reference| crate::PublicSignatureTypeReference {
230            export_name: reference.export_name.clone(),
231            type_name: reference.type_name.clone(),
232            span: Span::new(reference.span_start, reference.span_end),
233        })
234        .collect()
235}
236
237fn cached_namespace_aliases_to_module(
238    namespace_object_aliases: &[CachedNamespaceObjectAlias],
239) -> Vec<NamespaceObjectAlias> {
240    namespace_object_aliases
241        .iter()
242        .map(|alias| NamespaceObjectAlias {
243            via_export_name: alias.via_export_name.clone(),
244            suffix: alias.suffix.clone(),
245            namespace_local: alias.namespace_local.clone(),
246        })
247        .collect()
248}
249
250fn module_exports_to_cached(exports: &[crate::ExportInfo]) -> Vec<CachedExport> {
251    exports
252        .iter()
253        .map(|export| CachedExport {
254            name: match &export.name {
255                ExportName::Named(name) => name.clone(),
256                ExportName::Default => String::new(),
257            },
258            is_default: matches!(export.name, ExportName::Default),
259            is_type_only: export.is_type_only,
260            is_side_effect_used: export.is_side_effect_used,
261            visibility: export.visibility as u8,
262            expected_unused_reason: export.expected_unused_reason.clone(),
263            local_name: export.local_name.clone(),
264            span_start: export.span.start,
265            span_end: export.span.end,
266            members: export
267                .members
268                .iter()
269                .map(|member| CachedMember {
270                    name: member.name.clone(),
271                    kind: member.kind,
272                    span_start: member.span.start,
273                    span_end: member.span.end,
274                    has_decorator: member.has_decorator,
275                    decorator_names: member.decorator_names.clone(),
276                    is_instance_returning_static: member.is_instance_returning_static,
277                    is_self_returning: member.is_self_returning,
278                })
279                .collect(),
280            super_class: export.super_class.clone(),
281        })
282        .collect()
283}
284
285fn module_imports_to_cached(imports: &[crate::ImportInfo]) -> Vec<CachedImport> {
286    imports
287        .iter()
288        .map(|import| {
289            let (kind, imported_name) = match &import.imported_name {
290                crate::ImportedName::Named(name) => (IMPORT_KIND_NAMED, name.clone()),
291                crate::ImportedName::Default => (IMPORT_KIND_DEFAULT, String::new()),
292                crate::ImportedName::Namespace => (IMPORT_KIND_NAMESPACE, String::new()),
293                crate::ImportedName::SideEffect => (IMPORT_KIND_SIDE_EFFECT, String::new()),
294            };
295            CachedImport {
296                source: import.source.clone(),
297                imported_name,
298                local_name: import.local_name.clone(),
299                is_type_only: import.is_type_only,
300                from_style: import.from_style,
301                kind,
302                span_start: import.span.start,
303                span_end: import.span.end,
304                source_span_start: import.source_span.start,
305                source_span_end: import.source_span.end,
306            }
307        })
308        .collect()
309}
310
311fn module_re_exports_to_cached(re_exports: &[crate::ReExportInfo]) -> Vec<CachedReExport> {
312    re_exports
313        .iter()
314        .map(|re_export| CachedReExport {
315            source: re_export.source.clone(),
316            imported_name: re_export.imported_name.clone(),
317            exported_name: re_export.exported_name.clone(),
318            is_type_only: re_export.is_type_only,
319            span_start: re_export.span.start,
320            span_end: re_export.span.end,
321            statement_span_start: re_export.statement_span.start,
322            statement_span_end: re_export.statement_span.end,
323            source_span_start: re_export.source_span.start,
324            source_span_end: re_export.source_span.end,
325        })
326        .collect()
327}
328
329fn module_dynamic_imports_to_cached(
330    dynamic_imports: &[crate::DynamicImportInfo],
331) -> Vec<CachedDynamicImport> {
332    dynamic_imports
333        .iter()
334        .map(|dynamic_import| CachedDynamicImport {
335            source: dynamic_import.source.clone(),
336            span_start: dynamic_import.span.start,
337            span_end: dynamic_import.span.end,
338            destructured_names: dynamic_import.destructured_names.clone(),
339            local_name: dynamic_import.local_name.clone(),
340            is_speculative: dynamic_import.is_speculative,
341        })
342        .collect()
343}
344
345fn module_require_calls_to_cached(
346    require_calls: &[crate::RequireCallInfo],
347) -> Vec<CachedRequireCall> {
348    require_calls
349        .iter()
350        .map(|require_call| CachedRequireCall {
351            source: require_call.source.clone(),
352            span_start: require_call.span.start,
353            span_end: require_call.span.end,
354            source_span_start: require_call.source_span.start,
355            source_span_end: require_call.source_span.end,
356            destructured_names: require_call.destructured_names.clone(),
357            local_name: require_call.local_name.clone(),
358        })
359        .collect()
360}
361
362fn module_dynamic_patterns_to_cached(
363    dynamic_import_patterns: &[crate::DynamicImportPattern],
364) -> Vec<CachedDynamicImportPattern> {
365    dynamic_import_patterns
366        .iter()
367        .map(|pattern| CachedDynamicImportPattern {
368            prefix: pattern.prefix.clone(),
369            suffix: pattern.suffix.clone(),
370            span_start: pattern.span.start,
371            span_end: pattern.span.end,
372            mechanism: pattern.mechanism,
373        })
374        .collect()
375}
376
377fn module_suppressions_to_cached(
378    suppressions: &[crate::suppress::Suppression],
379) -> Vec<CachedSuppression> {
380    suppressions
381        .iter()
382        .map(|suppression| {
383            let (kind, policy_pack, policy_rule_id) = match &suppression.target {
384                None => (0, String::new(), String::new()),
385                Some(SuppressionTarget::Issue(kind)) => {
386                    (kind.to_discriminant(), String::new(), String::new())
387                }
388                Some(SuppressionTarget::PolicyRule(target)) => (
389                    crate::suppress::IssueKind::PolicyViolation.to_discriminant(),
390                    target.pack.clone(),
391                    target.rule_id.clone(),
392                ),
393            };
394            CachedSuppression {
395                line: suppression.line,
396                comment_line: suppression.comment_line,
397                kind,
398                policy_pack,
399                policy_rule_id,
400                reason: suppression.reason.clone(),
401            }
402        })
403        .collect()
404}
405
406fn module_unknown_suppressions_to_cached(
407    unknown_suppression_kinds: &[fallow_types::suppress::UnknownSuppressionKind],
408) -> Vec<CachedUnknownSuppressionKind> {
409    unknown_suppression_kinds
410        .iter()
411        .map(|unknown| CachedUnknownSuppressionKind {
412            comment_line: unknown.comment_line,
413            is_file_level: unknown.is_file_level,
414            token: unknown.token.clone(),
415            reason: unknown.reason.clone(),
416        })
417        .collect()
418}
419
420fn module_local_types_to_cached(
421    local_type_declarations: &[crate::LocalTypeDeclaration],
422) -> Vec<CachedLocalTypeDeclaration> {
423    local_type_declarations
424        .iter()
425        .map(|declaration| CachedLocalTypeDeclaration {
426            name: declaration.name.clone(),
427            span_start: declaration.span.start,
428            span_end: declaration.span.end,
429        })
430        .collect()
431}
432
433fn module_signature_refs_to_cached(
434    public_signature_type_references: &[crate::PublicSignatureTypeReference],
435) -> Vec<CachedPublicSignatureTypeReference> {
436    public_signature_type_references
437        .iter()
438        .map(|reference| CachedPublicSignatureTypeReference {
439            export_name: reference.export_name.clone(),
440            type_name: reference.type_name.clone(),
441            span_start: reference.span.start,
442            span_end: reference.span.end,
443        })
444        .collect()
445}
446
447fn module_namespace_aliases_to_cached(
448    namespace_object_aliases: &[NamespaceObjectAlias],
449) -> Vec<CachedNamespaceObjectAlias> {
450    namespace_object_aliases
451        .iter()
452        .map(|alias| CachedNamespaceObjectAlias {
453            via_export_name: alias.via_export_name.clone(),
454            suffix: alias.suffix.clone(),
455            namespace_local: alias.namespace_local.clone(),
456        })
457        .collect()
458}
459
460/// Rehydrate an optional cached slice into the shared `Arc<[T]>` module form.
461fn cached_opt_to_arc<T: Clone>(cached: Option<&[T]>) -> Arc<[T]> {
462    cached.map(Arc::from).unwrap_or_default()
463}
464
465/// Reconstruct a [`ModuleInfo`](crate::ModuleInfo) from a [`CachedModule`], skipping
466/// the per-function complexity vec when `need_complexity` is `false`. Avoids the
467/// `Vec<FunctionComplexity>` clone on warm runs of commands (e.g. `fallow dead-code`)
468/// that don't consume complexity, which adds up across tens of thousands of files.
469#[must_use]
470pub fn cached_to_module_opts(
471    cached: &CachedModule,
472    file_id: fallow_types::discover::FileId,
473    need_complexity: bool,
474) -> crate::ModuleInfo {
475    crate::ModuleInfo {
476        file_id,
477        exports: cached_exports_to_module(&cached.exports).into(),
478        imports: cached_imports_to_module(&cached.imports),
479        re_exports: cached_re_exports_to_module(&cached.re_exports),
480        dynamic_imports: cached_dynamic_imports_to_module(&cached.dynamic_imports),
481        dynamic_import_patterns: cached_dynamic_patterns_to_module(&cached.dynamic_import_patterns),
482        require_calls: cached_require_calls_to_module(&cached.require_calls),
483        package_path_references: cached.package_path_references.clone(),
484        member_accesses: cached.member_accesses.clone().into(),
485        semantic_facts: cached_opt_to_arc(cached.semantic_facts.as_deref()),
486        whole_object_uses: Arc::from(&*cached.whole_object_uses),
487        has_cjs_exports: cached.has_cjs_exports,
488        has_angular_component_template_url: cached.has_angular_component_template_url,
489        content_hash: cached.content_hash,
490        suppressions: cached_suppressions_to_module(&cached.suppressions),
491        unknown_suppression_kinds: cached_unknown_suppressions_to_module(
492            &cached.unknown_suppression_kinds,
493        ),
494        unused_import_bindings: cached.unused_import_bindings.clone(),
495        type_referenced_import_bindings: cached.type_referenced_import_bindings.clone(),
496        value_referenced_import_bindings: cached.value_referenced_import_bindings.clone(),
497        line_offsets: cached.line_offsets.clone(),
498        complexity: if need_complexity {
499            cached.complexity.clone()
500        } else {
501            Vec::new()
502        },
503        flag_uses: cached.flag_uses.clone(),
504        class_heritage: cached.class_heritage.clone(),
505        exported_factory_returns: cached_opt_to_arc(cached.exported_factory_returns.as_deref()),
506        exported_factory_return_object_shapes: cached_opt_to_arc(
507            cached.exported_factory_return_object_shapes.as_deref(),
508        ),
509        type_member_types: cached_opt_to_arc(cached.type_member_types.as_deref()),
510        injection_tokens: cached.injection_tokens.clone(),
511        local_type_declarations: cached_local_types_to_module(&cached.local_type_declarations),
512        public_signature_type_references: cached_signature_refs_to_module(
513            &cached.public_signature_type_references,
514        ),
515        namespace_object_aliases: cached_namespace_aliases_to_module(
516            &cached.namespace_object_aliases,
517        ),
518        iconify_prefixes: cached.iconify_prefixes.clone(),
519        iconify_icon_names: cached.iconify_icon_names.clone(),
520        auto_import_candidates: cached.auto_import_candidates.clone(),
521        directives: cached.directives.clone(),
522        client_only_dynamic_import_spans: cached.client_only_dynamic_import_spans.clone(),
523        security_sinks: cached.security_sinks.clone(),
524        security_sinks_skipped: cached.security_sinks_skipped,
525        security_unresolved_callee_sites: cached.security_unresolved_callee_sites.clone(),
526        tainted_bindings: cached.tainted_bindings.clone(),
527        sanitized_sink_args: cached.sanitized_sink_args.clone(),
528        security_control_sites: cached.security_control_sites.clone(),
529        callee_uses: cached.callee_uses.clone(),
530        misplaced_directives: cached.misplaced_directives.clone(),
531        inline_server_action_exports: cached.inline_server_action_exports.clone(),
532        di_key_sites: cached.di_key_sites.clone(),
533        has_dynamic_provide: cached.has_dynamic_provide,
534        // Derived in `release_resolution_payload` from `imports` + `unused_import_bindings`
535        // (both cached); never persisted, so the cache-load path leaves it empty.
536        referenced_import_bindings: Vec::new(),
537        component_props: cached.component_props.clone(),
538        has_props_attrs_fallthrough: cached.has_props_attrs_fallthrough,
539        has_define_expose: cached.has_define_expose,
540        has_define_model: cached.has_define_model,
541        has_unharvestable_props: cached.has_unharvestable_props,
542        component_emits: cached.component_emits.clone(),
543        angular_inputs: cached.angular_inputs.clone(),
544        angular_outputs: cached.angular_outputs.clone(),
545        angular_component_selectors: cached.angular_component_selectors.clone(),
546        registered_custom_elements: cached.registered_custom_elements.clone(),
547        used_custom_element_tags: cached.used_custom_element_tags.clone(),
548        angular_used_selectors: cached.angular_used_selectors.clone(),
549        angular_entry_component_refs: cached.angular_entry_component_refs.clone(),
550        has_dynamic_component_render: cached.has_dynamic_component_render,
551        has_unharvestable_emits: cached.has_unharvestable_emits,
552        has_dynamic_emit: cached.has_dynamic_emit,
553        has_emit_whole_object_use: cached.has_emit_whole_object_use,
554        load_return_keys: cached.load_return_keys.clone(),
555        has_unharvestable_load: cached.has_unharvestable_load,
556        has_load_data_whole_use: cached.has_load_data_whole_use,
557        // Derived in `release_resolution_payload` from `whole_object_uses`.
558        has_page_data_store_whole_use: false,
559        // Derived in `release_resolution_payload` from `whole_object_uses`.
560        has_route_loader_data_whole_use: false,
561        component_functions: cached.component_functions.clone(),
562        react_props: cached.react_props.clone(),
563        hook_uses: cached.hook_uses.clone(),
564        render_edges: cached.render_edges.clone(),
565        svelte_dispatched_events: cached.svelte_dispatched_events.clone(),
566        svelte_listened_events: cached.svelte_listened_events.clone(),
567        has_dynamic_dispatch: cached.has_dynamic_dispatch,
568    }
569}
570
571/// Convert a [`ModuleInfo`](crate::ModuleInfo) to a [`CachedModule`] for storage.
572///
573/// The [`SourceFingerprint`](fallow_types::source_fingerprint::SourceFingerprint)
574/// comes from `std::fs::metadata()` at parse time and enables fast cache
575/// validation on subsequent runs.
576#[must_use]
577pub fn module_to_cached(
578    module: &crate::ModuleInfo,
579    fingerprint: fallow_types::source_fingerprint::SourceFingerprint,
580) -> CachedModule {
581    CachedModule {
582        content_hash: module.content_hash,
583        mtime_ns: fingerprint.mtime_ns,
584        file_size: fingerprint.file_size,
585        last_access_secs: current_unix_seconds(),
586        exports: module_exports_to_cached(&module.exports),
587        imports: module_imports_to_cached(&module.imports),
588        re_exports: module_re_exports_to_cached(&module.re_exports),
589        dynamic_imports: module_dynamic_imports_to_cached(&module.dynamic_imports),
590        require_calls: module_require_calls_to_cached(&module.require_calls),
591        package_path_references: module.package_path_references.clone(),
592        member_accesses: module.member_accesses.to_vec(),
593        semantic_facts: (!module.semantic_facts.is_empty())
594            .then(|| Box::from(&*module.semantic_facts)),
595        whole_object_uses: Box::from(&*module.whole_object_uses),
596        dynamic_import_patterns: module_dynamic_patterns_to_cached(&module.dynamic_import_patterns),
597        has_cjs_exports: module.has_cjs_exports,
598        has_angular_component_template_url: module.has_angular_component_template_url,
599        unused_import_bindings: module.unused_import_bindings.clone(),
600        type_referenced_import_bindings: module.type_referenced_import_bindings.clone(),
601        value_referenced_import_bindings: module.value_referenced_import_bindings.clone(),
602        suppressions: module_suppressions_to_cached(&module.suppressions),
603        unknown_suppression_kinds: module_unknown_suppressions_to_cached(
604            &module.unknown_suppression_kinds,
605        ),
606        line_offsets: module.line_offsets.clone(),
607        complexity: module.complexity.clone(),
608        flag_uses: module.flag_uses.clone(),
609        class_heritage: module.class_heritage.clone(),
610        exported_factory_returns: (!module.exported_factory_returns.is_empty())
611            .then(|| Box::from(&*module.exported_factory_returns)),
612        exported_factory_return_object_shapes: (!module
613            .exported_factory_return_object_shapes
614            .is_empty())
615        .then(|| Box::from(&*module.exported_factory_return_object_shapes)),
616        type_member_types: (!module.type_member_types.is_empty())
617            .then(|| Box::from(&*module.type_member_types)),
618        injection_tokens: module.injection_tokens.clone(),
619        local_type_declarations: module_local_types_to_cached(&module.local_type_declarations),
620        public_signature_type_references: module_signature_refs_to_cached(
621            &module.public_signature_type_references,
622        ),
623        namespace_object_aliases: module_namespace_aliases_to_cached(
624            &module.namespace_object_aliases,
625        ),
626        iconify_prefixes: module.iconify_prefixes.clone(),
627        iconify_icon_names: module.iconify_icon_names.clone(),
628        auto_import_candidates: module.auto_import_candidates.clone(),
629        directives: module.directives.clone(),
630        client_only_dynamic_import_spans: module.client_only_dynamic_import_spans.clone(),
631        security_sinks: module.security_sinks.clone(),
632        security_sinks_skipped: module.security_sinks_skipped,
633        security_unresolved_callee_sites: module.security_unresolved_callee_sites.clone(),
634        tainted_bindings: module.tainted_bindings.clone(),
635        sanitized_sink_args: module.sanitized_sink_args.clone(),
636        security_control_sites: module.security_control_sites.clone(),
637        callee_uses: module.callee_uses.clone(),
638        misplaced_directives: module.misplaced_directives.clone(),
639        inline_server_action_exports: module.inline_server_action_exports.clone(),
640        di_key_sites: module.di_key_sites.clone(),
641        has_dynamic_provide: module.has_dynamic_provide,
642        component_props: module.component_props.clone(),
643        has_props_attrs_fallthrough: module.has_props_attrs_fallthrough,
644        has_define_expose: module.has_define_expose,
645        has_define_model: module.has_define_model,
646        has_unharvestable_props: module.has_unharvestable_props,
647        component_emits: module.component_emits.clone(),
648        angular_inputs: module.angular_inputs.clone(),
649        angular_outputs: module.angular_outputs.clone(),
650        angular_component_selectors: module.angular_component_selectors.clone(),
651        registered_custom_elements: module.registered_custom_elements.clone(),
652        used_custom_element_tags: module.used_custom_element_tags.clone(),
653        angular_used_selectors: module.angular_used_selectors.clone(),
654        angular_entry_component_refs: module.angular_entry_component_refs.clone(),
655        has_dynamic_component_render: module.has_dynamic_component_render,
656        has_unharvestable_emits: module.has_unharvestable_emits,
657        has_dynamic_emit: module.has_dynamic_emit,
658        has_emit_whole_object_use: module.has_emit_whole_object_use,
659        load_return_keys: module.load_return_keys.clone(),
660        has_unharvestable_load: module.has_unharvestable_load,
661        has_load_data_whole_use: module.has_load_data_whole_use,
662        component_functions: module.component_functions.clone(),
663        react_props: module.react_props.clone(),
664        hook_uses: module.hook_uses.clone(),
665        render_edges: module.render_edges.clone(),
666        svelte_dispatched_events: module.svelte_dispatched_events.clone(),
667        svelte_listened_events: module.svelte_listened_events.clone(),
668        has_dynamic_dispatch: module.has_dynamic_dispatch,
669    }
670}
671
672/// Convert a module to a cache entry from explicit source-fingerprint parts.
673///
674/// Kept for tests that need literal invalidation inputs without filesystem
675/// metadata.
676#[cfg(test)]
677#[must_use]
678pub fn module_to_cached_from_parts(
679    module: &crate::ModuleInfo,
680    mtime_ns: u64,
681    file_size: u64,
682) -> CachedModule {
683    module_to_cached(
684        module,
685        fallow_types::source_fingerprint::SourceFingerprint::new(mtime_ns, file_size),
686    )
687}