baobao_codegen/pipeline/phases/validate/
mod.rs1mod lint;
4pub mod lints;
5
6use eyre::{Result, bail};
7pub use lint::{Lint, LintInfo};
8pub use lints::{CommandNamingLint, DuplicateCommandLint, EmptyDescriptionLint};
9
10use crate::pipeline::{CompilationContext, Phase};
11
12pub struct ValidatePhase {
14 lints: Vec<Box<dyn Lint>>,
15}
16
17impl ValidatePhase {
18 pub fn new() -> Self {
20 Self {
21 lints: vec![
22 Box::new(CommandNamingLint),
23 Box::new(DuplicateCommandLint),
24 Box::new(EmptyDescriptionLint),
25 ],
26 }
27 }
28
29 pub fn empty() -> Self {
31 Self { lints: Vec::new() }
32 }
33
34 pub fn with_lint(mut self, lint: impl Lint + 'static) -> Self {
36 self.lints.push(Box::new(lint));
37 self
38 }
39}
40
41impl Default for ValidatePhase {
42 fn default() -> Self {
43 Self::new()
44 }
45}
46
47impl ValidatePhase {
48 pub fn lint_names(&self) -> Vec<&'static str> {
50 self.lints.iter().map(|l| l.name()).collect()
51 }
52
53 pub fn lint_info(&self) -> Vec<LintInfo> {
55 self.lints.iter().map(|l| l.info()).collect()
56 }
57}
58
59impl Phase for ValidatePhase {
60 fn name(&self) -> &'static str {
61 "validate"
62 }
63
64 fn description(&self) -> &'static str {
65 "Check manifest integrity and collect diagnostics"
66 }
67
68 fn run(&self, ctx: &mut CompilationContext) -> Result<()> {
69 for lint in &self.lints {
71 lint.check(&ctx.manifest, &mut ctx.diagnostics);
72 }
73
74 if ctx.has_errors() {
76 bail!("Validation failed with {} error(s)", ctx.error_count());
77 }
78
79 Ok(())
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use baobao_manifest::Manifest;
86
87 use super::*;
88 use crate::pipeline::Diagnostic;
89
90 fn parse_manifest(content: &str) -> Manifest {
91 toml::from_str(content).expect("Failed to parse test manifest")
92 }
93
94 fn make_test_manifest() -> Manifest {
95 parse_manifest(
96 r#"
97 [cli]
98 name = "test"
99 language = "rust"
100 "#,
101 )
102 }
103
104 #[test]
105 fn test_with_errors() {
106 struct AlwaysErrorLint;
107 impl Lint for AlwaysErrorLint {
108 fn name(&self) -> &'static str {
109 "always-error"
110 }
111 fn description(&self) -> &'static str {
112 "Always produces an error"
113 }
114 fn check(&self, _manifest: &Manifest, diagnostics: &mut Vec<Diagnostic>) {
115 diagnostics.push(Diagnostic::error("test", "forced error"));
116 }
117 }
118
119 let manifest = make_test_manifest();
120 let mut ctx = CompilationContext::new(manifest);
121
122 let phase = ValidatePhase::empty().with_lint(AlwaysErrorLint);
123 let result = phase.run(&mut ctx);
124
125 assert!(result.is_err());
126 assert!(ctx.has_errors());
127 }
128
129 #[test]
130 fn test_warnings_allowed() {
131 let manifest = parse_manifest(
132 r#"
133 [cli]
134 name = "test"
135 language = "rust"
136
137 [commands.deploy]
138 description = ""
139 "#,
140 );
141
142 let mut ctx = CompilationContext::new(manifest);
143
144 let phase = ValidatePhase::empty().with_lint(EmptyDescriptionLint);
146 let result = phase.run(&mut ctx);
147
148 assert!(result.is_ok());
150 assert!(ctx.has_warnings());
151 assert!(!ctx.has_errors());
152 }
153}