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}