fob_graph/
export.rs

1use serde::{Deserialize, Serialize};
2
3use super::SourceSpan;
4
5/// Export declaration kind.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub enum ExportKind {
8    Named,
9    Default,
10    ReExport,
11    /// Star re-export: `export * from './module'`
12    ///
13    /// This re-exports all named exports from the source module.
14    /// Unlike `ReExport`, this doesn't specify individual export names.
15    StarReExport,
16    TypeOnly,
17}
18
19/// Complete metadata describing a module export.
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
21pub struct Export {
22    pub name: String,
23    pub kind: ExportKind,
24    pub is_used: bool,
25    pub is_type_only: bool,
26    pub re_exported_from: Option<String>,
27    pub is_framework_used: bool,
28    /// True if this export came from a CommonJS module.
29    ///
30    /// Important for CJS/ESM interop detection.
31    pub came_from_commonjs: bool,
32    pub span: SourceSpan,
33    /// Number of times this export is imported across the entire module graph.
34    ///
35    /// - `None` means usage count hasn't been computed yet
36    /// - `Some(0)` means the export is confirmed unused
37    /// - `Some(n)` where n > 0 means the export is used n times
38    ///
39    /// This is populated by `ModuleGraph::compute_export_usage_counts()`.
40    pub usage_count: Option<usize>,
41}
42
43impl Export {
44    /// Construct a new export metadata record.
45    #[allow(clippy::too_many_arguments)]
46    pub fn new(
47        name: impl Into<String>,
48        kind: ExportKind,
49        is_used: bool,
50        is_type_only: bool,
51        re_exported_from: Option<String>,
52        is_framework_used: bool,
53        came_from_commonjs: bool,
54        span: SourceSpan,
55    ) -> Self {
56        Self {
57            name: name.into(),
58            kind,
59            is_used,
60            is_type_only,
61            re_exported_from,
62            is_framework_used,
63            came_from_commonjs,
64            span,
65            usage_count: None,
66        }
67    }
68
69    /// Marks the export as used by another module.
70    pub fn mark_used(&mut self) {
71        self.is_used = true;
72    }
73
74    /// Marks the export as unused.
75    pub fn mark_unused(&mut self) {
76        self.is_used = false;
77    }
78
79    /// Marks the export as used by framework conventions (React hooks, etc.).
80    pub fn mark_framework_used(&mut self) {
81        self.is_framework_used = true;
82        self.is_used = true;
83    }
84
85    /// Convenience check for default exports.
86    pub fn is_default(&self) -> bool {
87        matches!(self.kind, ExportKind::Default)
88    }
89
90    /// Returns true if the export re-exports from another module.
91    pub fn is_re_export(&self) -> bool {
92        matches!(self.kind, ExportKind::ReExport | ExportKind::StarReExport)
93    }
94
95    /// Returns true if this is a star re-export (`export * from './module'`).
96    pub fn is_star_re_export(&self) -> bool {
97        matches!(self.kind, ExportKind::StarReExport)
98    }
99
100    /// Returns true if this export is marked as used by framework conventions.
101    ///
102    /// Framework-used exports include React hooks, Next.js data fetching functions,
103    /// Vue composables, etc. These exports appear unused in static analysis but are
104    /// consumed by framework magic.
105    pub fn is_framework_used(&self) -> bool {
106        self.is_framework_used
107    }
108
109    /// Sets the usage count for this export.
110    ///
111    /// This should be called by `ModuleGraph::compute_export_usage_counts()`.
112    pub fn set_usage_count(&mut self, count: usize) {
113        self.usage_count = Some(count);
114    }
115
116    /// Increments the usage count by 1.
117    ///
118    /// If the count hasn't been initialized yet (is None), sets it to 1.
119    pub fn increment_usage_count(&mut self) {
120        self.usage_count = Some(self.usage_count.unwrap_or(0) + 1);
121    }
122
123    /// Returns the usage count for this export.
124    ///
125    /// - `None` means the count hasn't been computed yet
126    /// - `Some(0)` means the export is confirmed unused
127    /// - `Some(n)` where n > 0 means the export is used n times
128    pub fn usage_count(&self) -> Option<usize> {
129        self.usage_count
130    }
131
132    /// Resets the usage count to None.
133    ///
134    /// Used when the module graph is modified and counts need to be recomputed.
135    pub fn reset_usage_count(&mut self) {
136        self.usage_count = None;
137    }
138}