baobao_codegen/pipeline/phases/
analyze.rs

1//! Analyze phase - computes shared data from IR.
2
3use eyre::Result;
4
5use crate::{
6    pipeline::{CompilationContext, Phase},
7    schema::ComputedData,
8};
9
10/// Phase that computes shared analysis data from the Application IR.
11///
12/// This phase must run after `LowerPhase` as it requires the IR to be populated.
13/// It computes commonly-needed data like context fields, command paths, and
14/// async requirements.
15pub struct AnalyzePhase;
16
17impl Phase for AnalyzePhase {
18    fn name(&self) -> &'static str {
19        "analyze"
20    }
21
22    fn description(&self) -> &'static str {
23        "Compute shared data from IR"
24    }
25
26    fn run(&self, ctx: &mut CompilationContext) -> Result<()> {
27        let ir = ctx
28            .ir
29            .as_ref()
30            .ok_or_else(|| eyre::eyre!("IR not set - AnalyzePhase must run after LowerPhase"))?;
31
32        ctx.computed = Some(ComputedData::from_ir(ir));
33        Ok(())
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use baobao_ir::{AppIR, AppMeta, DatabaseResource, DatabaseType, PoolConfig, Resource};
40    use baobao_manifest::Manifest;
41
42    use super::*;
43
44    fn parse_manifest(content: &str) -> Manifest {
45        toml::from_str(content).expect("Failed to parse test manifest")
46    }
47
48    fn make_test_manifest() -> Manifest {
49        parse_manifest(
50            r#"
51            [cli]
52            name = "test"
53            language = "rust"
54        "#,
55        )
56    }
57
58    fn make_test_ir() -> AppIR {
59        AppIR {
60            meta: AppMeta {
61                name: "test".into(),
62                version: "1.0.0".into(),
63                description: None,
64                author: None,
65            },
66            resources: vec![Resource::Database(DatabaseResource {
67                name: "db".into(),
68                db_type: DatabaseType::Postgres,
69                env_var: "DATABASE_URL".into(),
70                pool: PoolConfig::default(),
71                sqlite: None,
72            })],
73            operations: vec![],
74        }
75    }
76
77    #[test]
78    fn test_analyze_phase() {
79        let manifest = make_test_manifest();
80        let mut ctx = CompilationContext::new(manifest);
81        ctx.ir = Some(make_test_ir());
82
83        assert!(ctx.computed.is_none());
84
85        AnalyzePhase.run(&mut ctx).expect("analyze should succeed");
86
87        assert!(ctx.computed.is_some());
88
89        let computed = ctx.computed.as_ref().unwrap();
90        assert!(computed.is_async);
91        assert!(computed.has_database);
92    }
93
94    #[test]
95    fn test_analyze_phase_requires_ir() {
96        let manifest = make_test_manifest();
97        let mut ctx = CompilationContext::new(manifest);
98        // Don't set IR
99
100        let result = AnalyzePhase.run(&mut ctx);
101        assert!(result.is_err());
102    }
103}