weaveffi_model/validate/
warnings.rs1use crate::ir::{Api, Module, TypeRef};
10
11const MAX_NESTING: usize = 3;
13
14const MAX_ENUM_VARIANTS: usize = 100;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum ValidationWarning {
20 LargeEnumVariantCount {
22 enum_name: String,
24 count: usize,
26 },
27 DeepNesting {
29 location: String,
31 depth: usize,
33 },
34 EmptyModuleDoc {
37 module: String,
39 },
40 AsyncVoidFunction {
42 module: String,
44 function: String,
46 },
47 DeprecatedFunction {
49 module: String,
51 function: String,
53 message: String,
55 },
56}
57
58impl std::fmt::Display for ValidationWarning {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 match self {
61 Self::LargeEnumVariantCount { enum_name, count } => {
62 write!(
63 f,
64 "enum '{enum_name}' has {count} variants (>{MAX_ENUM_VARIANTS})"
65 )
66 }
67 Self::DeepNesting { location, depth } => write!(
68 f,
69 "deep type nesting at {location} (depth {depth}, max recommended {MAX_NESTING})"
70 ),
71 Self::EmptyModuleDoc { module } => {
72 write!(
73 f,
74 "module '{module}' has no doc comment on itself or any function"
75 )
76 }
77 Self::AsyncVoidFunction { module, function } => write!(
78 f,
79 "async function {module}::{function} has no return type; async void is unusual"
80 ),
81 Self::DeprecatedFunction {
82 module,
83 function,
84 message,
85 } => write!(f, "function {module}::{function} is deprecated: {message}"),
86 }
87 }
88}
89
90pub fn collect_warnings(api: &Api) -> Vec<ValidationWarning> {
96 let mut warnings = Vec::new();
97 for module in &api.modules {
98 collect_module(module, &mut warnings);
99 }
100 warnings
101}
102
103fn collect_module(module: &Module, warnings: &mut Vec<ValidationWarning>) {
104 let name = &module.name;
105 for e in &module.enums {
106 if e.variants.len() > MAX_ENUM_VARIANTS {
107 warnings.push(ValidationWarning::LargeEnumVariantCount {
108 enum_name: e.name.clone(),
109 count: e.variants.len(),
110 });
111 }
112 }
113 let mut deep = |ty: &TypeRef, location: String| {
114 let depth = nesting_depth(ty);
115 if depth > MAX_NESTING {
116 warnings.push(ValidationWarning::DeepNesting { location, depth });
117 }
118 };
119 for s in &module.structs {
120 for field in &s.fields {
121 deep(&field.ty, format!("{name}::{}::{}", s.name, field.name));
122 }
123 }
124 for f in &module.functions {
125 for p in &f.params {
126 deep(&p.ty, format!("{name}::{}::{}", f.name, p.name));
127 }
128 if let Some(ret) = &f.returns {
129 deep(ret, format!("{name}::{}::return", f.name));
130 }
131 }
132 for f in &module.functions {
133 if f.r#async && f.returns.is_none() {
134 warnings.push(ValidationWarning::AsyncVoidFunction {
135 module: name.clone(),
136 function: f.name.clone(),
137 });
138 }
139 if let Some(msg) = &f.deprecated {
140 warnings.push(ValidationWarning::DeprecatedFunction {
141 module: name.clone(),
142 function: f.name.clone(),
143 message: msg.clone(),
144 });
145 }
146 }
147 if module.doc.is_none()
148 && !module.functions.is_empty()
149 && module.functions.iter().all(|f| f.doc.is_none())
150 {
151 warnings.push(ValidationWarning::EmptyModuleDoc {
152 module: name.clone(),
153 });
154 }
155 for child in &module.modules {
156 collect_module(child, warnings);
157 }
158}
159
160fn nesting_depth(ty: &TypeRef) -> usize {
161 match ty {
162 TypeRef::Optional(inner) | TypeRef::List(inner) | TypeRef::Iterator(inner) => {
163 1 + nesting_depth(inner)
164 }
165 TypeRef::Map(k, v) => 1 + nesting_depth(k).max(nesting_depth(v)),
166 _ => 0,
167 }
168}