fallow_config/
framework.rs1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
10pub struct FrameworkPreset {
11 pub name: String,
13
14 #[serde(default)]
16 pub detection: Option<FrameworkDetection>,
17
18 #[serde(default)]
20 pub entry_points: Vec<FrameworkEntryPattern>,
21
22 #[serde(default)]
24 pub always_used: Vec<String>,
25
26 #[serde(default)]
28 pub used_exports: Vec<FrameworkUsedExport>,
29}
30
31#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
33#[serde(tag = "type", rename_all = "snake_case")]
34pub enum FrameworkDetection {
35 Dependency { package: String },
37 FileExists { pattern: String },
39 All { conditions: Vec<FrameworkDetection> },
41 Any { conditions: Vec<FrameworkDetection> },
43}
44
45#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
47pub struct FrameworkEntryPattern {
48 pub pattern: String,
50}
51
52#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
54pub struct FrameworkUsedExport {
55 pub file_pattern: String,
57 pub exports: Vec<String>,
59}
60
61pub type FrameworkRule = FrameworkPreset;
63
64pub fn resolve_framework_rules(custom: &[FrameworkPreset]) -> Vec<FrameworkRule> {
69 custom.to_vec()
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn resolve_framework_rules_empty() {
78 let rules = resolve_framework_rules(&[]);
79 assert!(rules.is_empty());
80 }
81
82 #[test]
83 fn resolve_framework_rules_with_custom() {
84 let custom = vec![FrameworkPreset {
85 name: "custom".to_string(),
86 detection: None,
87 entry_points: vec![FrameworkEntryPattern {
88 pattern: "src/custom/**/*.ts".to_string(),
89 }],
90 always_used: vec![],
91 used_exports: vec![],
92 }];
93 let rules = resolve_framework_rules(&custom);
94 assert_eq!(rules.len(), 1);
95 assert_eq!(rules[0].name, "custom");
96 }
97
98 #[test]
99 fn framework_preset_is_rule() {
100 let rule: FrameworkRule = FrameworkPreset {
101 name: "test".to_string(),
102 detection: Some(FrameworkDetection::Dependency {
103 package: "test-pkg".to_string(),
104 }),
105 entry_points: vec![FrameworkEntryPattern {
106 pattern: "src/**/*.test.ts".to_string(),
107 }],
108 always_used: vec!["setup.ts".to_string()],
109 used_exports: vec![FrameworkUsedExport {
110 file_pattern: "src/**/*.test.ts".to_string(),
111 exports: vec!["default".to_string()],
112 }],
113 };
114 assert_eq!(rule.name, "test");
115 assert!(rule.detection.is_some());
116 assert_eq!(rule.entry_points.len(), 1);
117 assert_eq!(rule.always_used, vec!["setup.ts"]);
118 assert_eq!(rule.used_exports.len(), 1);
119 }
120
121 #[test]
122 fn framework_detection_deserialize_dependency() {
123 let json = r#"{"type": "dependency", "package": "next"}"#;
124 let detection: FrameworkDetection = serde_json::from_str(json).unwrap();
125 assert!(
126 matches!(detection, FrameworkDetection::Dependency { package } if package == "next")
127 );
128 }
129
130 #[test]
131 fn framework_detection_deserialize_file_exists() {
132 let json = r#"{"type": "file_exists", "pattern": "tsconfig.json"}"#;
133 let detection: FrameworkDetection = serde_json::from_str(json).unwrap();
134 assert!(
135 matches!(detection, FrameworkDetection::FileExists { pattern } if pattern == "tsconfig.json")
136 );
137 }
138
139 #[test]
140 fn framework_detection_deserialize_all() {
141 let json = r#"{"type": "all", "conditions": [{"type": "dependency", "package": "a"}, {"type": "dependency", "package": "b"}]}"#;
142 let detection: FrameworkDetection = serde_json::from_str(json).unwrap();
143 assert!(
144 matches!(detection, FrameworkDetection::All { conditions } if conditions.len() == 2)
145 );
146 }
147
148 #[test]
149 fn framework_detection_deserialize_any() {
150 let json = r#"{"type": "any", "conditions": [{"type": "dependency", "package": "a"}]}"#;
151 let detection: FrameworkDetection = serde_json::from_str(json).unwrap();
152 assert!(
153 matches!(detection, FrameworkDetection::Any { conditions } if conditions.len() == 1)
154 );
155 }
156}