use crate::ir::{Api, Module, TypeRef};
const MAX_NESTING: usize = 3;
const MAX_ENUM_VARIANTS: usize = 100;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ValidationWarning {
LargeEnumVariantCount {
enum_name: String,
count: usize,
},
DeepNesting {
location: String,
depth: usize,
},
EmptyModuleDoc {
module: String,
},
AsyncVoidFunction {
module: String,
function: String,
},
DeprecatedFunction {
module: String,
function: String,
message: String,
},
}
impl std::fmt::Display for ValidationWarning {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::LargeEnumVariantCount { enum_name, count } => {
write!(
f,
"enum '{enum_name}' has {count} variants (>{MAX_ENUM_VARIANTS})"
)
}
Self::DeepNesting { location, depth } => write!(
f,
"deep type nesting at {location} (depth {depth}, max recommended {MAX_NESTING})"
),
Self::EmptyModuleDoc { module } => {
write!(
f,
"module '{module}' has no doc comment on itself or any function"
)
}
Self::AsyncVoidFunction { module, function } => write!(
f,
"async function {module}::{function} has no return type; async void is unusual"
),
Self::DeprecatedFunction {
module,
function,
message,
} => write!(f, "function {module}::{function} is deprecated: {message}"),
}
}
}
pub fn collect_warnings(api: &Api) -> Vec<ValidationWarning> {
let mut warnings = Vec::new();
for module in &api.modules {
collect_module(module, &mut warnings);
}
warnings
}
fn collect_module(module: &Module, warnings: &mut Vec<ValidationWarning>) {
let name = &module.name;
for e in &module.enums {
if e.variants.len() > MAX_ENUM_VARIANTS {
warnings.push(ValidationWarning::LargeEnumVariantCount {
enum_name: e.name.clone(),
count: e.variants.len(),
});
}
}
let mut deep = |ty: &TypeRef, location: String| {
let depth = nesting_depth(ty);
if depth > MAX_NESTING {
warnings.push(ValidationWarning::DeepNesting { location, depth });
}
};
for s in &module.structs {
for field in &s.fields {
deep(&field.ty, format!("{name}::{}::{}", s.name, field.name));
}
}
for f in &module.functions {
for p in &f.params {
deep(&p.ty, format!("{name}::{}::{}", f.name, p.name));
}
if let Some(ret) = &f.returns {
deep(ret, format!("{name}::{}::return", f.name));
}
}
for f in &module.functions {
if f.r#async && f.returns.is_none() {
warnings.push(ValidationWarning::AsyncVoidFunction {
module: name.clone(),
function: f.name.clone(),
});
}
if let Some(msg) = &f.deprecated {
warnings.push(ValidationWarning::DeprecatedFunction {
module: name.clone(),
function: f.name.clone(),
message: msg.clone(),
});
}
}
if module.doc.is_none()
&& !module.functions.is_empty()
&& module.functions.iter().all(|f| f.doc.is_none())
{
warnings.push(ValidationWarning::EmptyModuleDoc {
module: name.clone(),
});
}
for child in &module.modules {
collect_module(child, warnings);
}
}
fn nesting_depth(ty: &TypeRef) -> usize {
match ty {
TypeRef::Optional(inner) | TypeRef::List(inner) | TypeRef::Iterator(inner) => {
1 + nesting_depth(inner)
}
TypeRef::Map(k, v) => 1 + nesting_depth(k).max(nesting_depth(v)),
_ => 0,
}
}