fallow_config/
framework.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Deserialize, Serialize)]
9pub struct FrameworkPreset {
10 pub name: String,
12
13 #[serde(default)]
15 pub detection: Option<FrameworkDetection>,
16
17 #[serde(default)]
19 pub entry_points: Vec<FrameworkEntryPattern>,
20
21 #[serde(default)]
23 pub always_used: Vec<String>,
24
25 #[serde(default)]
27 pub used_exports: Vec<FrameworkUsedExport>,
28}
29
30#[derive(Debug, Clone, Deserialize, Serialize)]
32#[serde(tag = "type", rename_all = "snake_case")]
33pub enum FrameworkDetection {
34 Dependency { package: String },
36 FileExists { pattern: String },
38 All { conditions: Vec<FrameworkDetection> },
40 Any { conditions: Vec<FrameworkDetection> },
42}
43
44#[derive(Debug, Clone, Deserialize, Serialize)]
46pub struct FrameworkEntryPattern {
47 pub pattern: String,
49}
50
51#[derive(Debug, Clone, Deserialize, Serialize)]
53pub struct FrameworkUsedExport {
54 pub file_pattern: String,
56 pub exports: Vec<String>,
58}
59
60#[derive(Debug, Clone)]
62pub struct FrameworkRule {
63 pub name: String,
64 pub detection: Option<FrameworkDetection>,
65 pub entry_points: Vec<FrameworkEntryPattern>,
66 pub always_used: Vec<String>,
67 pub used_exports: Vec<FrameworkUsedExport>,
68}
69
70impl From<FrameworkPreset> for FrameworkRule {
71 fn from(preset: FrameworkPreset) -> Self {
72 Self {
73 name: preset.name,
74 detection: preset.detection,
75 entry_points: preset.entry_points,
76 always_used: preset.always_used,
77 used_exports: preset.used_exports,
78 }
79 }
80}
81
82pub fn resolve_framework_rules(custom: &[FrameworkPreset]) -> Vec<FrameworkRule> {
87 custom
88 .iter()
89 .map(|p| FrameworkRule::from(p.clone()))
90 .collect()
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn resolve_framework_rules_empty() {
99 let rules = resolve_framework_rules(&[]);
100 assert!(rules.is_empty());
101 }
102
103 #[test]
104 fn resolve_framework_rules_with_custom() {
105 let custom = vec![FrameworkPreset {
106 name: "custom".to_string(),
107 detection: None,
108 entry_points: vec![FrameworkEntryPattern {
109 pattern: "src/custom/**/*.ts".to_string(),
110 }],
111 always_used: vec![],
112 used_exports: vec![],
113 }];
114 let rules = resolve_framework_rules(&custom);
115 assert_eq!(rules.len(), 1);
116 assert_eq!(rules[0].name, "custom");
117 }
118
119 #[test]
120 fn framework_preset_to_rule() {
121 let preset = FrameworkPreset {
122 name: "test".to_string(),
123 detection: Some(FrameworkDetection::Dependency {
124 package: "test-pkg".to_string(),
125 }),
126 entry_points: vec![FrameworkEntryPattern {
127 pattern: "src/**/*.test.ts".to_string(),
128 }],
129 always_used: vec!["setup.ts".to_string()],
130 used_exports: vec![FrameworkUsedExport {
131 file_pattern: "src/**/*.test.ts".to_string(),
132 exports: vec!["default".to_string()],
133 }],
134 };
135 let rule: FrameworkRule = preset.into();
136 assert_eq!(rule.name, "test");
137 assert!(rule.detection.is_some());
138 assert_eq!(rule.entry_points.len(), 1);
139 assert_eq!(rule.always_used, vec!["setup.ts"]);
140 assert_eq!(rule.used_exports.len(), 1);
141 }
142
143 #[test]
144 fn framework_detection_deserialize_dependency() {
145 let json = r#"{"type": "dependency", "package": "next"}"#;
146 let detection: FrameworkDetection = serde_json::from_str(json).unwrap();
147 assert!(
148 matches!(detection, FrameworkDetection::Dependency { package } if package == "next")
149 );
150 }
151
152 #[test]
153 fn framework_detection_deserialize_file_exists() {
154 let json = r#"{"type": "file_exists", "pattern": "tsconfig.json"}"#;
155 let detection: FrameworkDetection = serde_json::from_str(json).unwrap();
156 assert!(
157 matches!(detection, FrameworkDetection::FileExists { pattern } if pattern == "tsconfig.json")
158 );
159 }
160
161 #[test]
162 fn framework_detection_deserialize_all() {
163 let json = r#"{"type": "all", "conditions": [{"type": "dependency", "package": "a"}, {"type": "dependency", "package": "b"}]}"#;
164 let detection: FrameworkDetection = serde_json::from_str(json).unwrap();
165 assert!(
166 matches!(detection, FrameworkDetection::All { conditions } if conditions.len() == 2)
167 );
168 }
169
170 #[test]
171 fn framework_detection_deserialize_any() {
172 let json = r#"{"type": "any", "conditions": [{"type": "dependency", "package": "a"}]}"#;
173 let detection: FrameworkDetection = serde_json::from_str(json).unwrap();
174 assert!(
175 matches!(detection, FrameworkDetection::Any { conditions } if conditions.len() == 1)
176 );
177 }
178}