Skip to main content

weaveffi_model/validate/
warnings.rs

1//! Non-fatal, lint-style checks over a validated [`Api`].
2//!
3//! These are distinct from the hard validation errors in the parent
4//! [`crate::validate`] module: errors *reject* an IDL, whereas warnings
5//! merely flag stylistic or ergonomic concerns (deep nesting, undocumented
6//! modules, async functions with no result) that the caller can surface and
7//! the user can choose to ignore.
8
9use crate::ir::{Api, Module, TypeRef};
10
11/// Nesting deeper than this (`[[[[i32]]]]`) is flagged.
12const MAX_NESTING: usize = 3;
13
14/// Enums with more variants than this are flagged.
15const MAX_ENUM_VARIANTS: usize = 100;
16
17/// A non-fatal advisory emitted by [`collect_warnings`].
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum ValidationWarning {
20    /// An enum has an unusually large number of variants (more than 100).
21    LargeEnumVariantCount {
22        /// Enum that tripped the threshold.
23        enum_name: String,
24        /// Number of variants the enum declares.
25        count: usize,
26    },
27    /// A type is nested more deeply than recommended (more than 3 levels).
28    DeepNesting {
29        /// Where the deeply nested type appears (a `module::fn::param` path).
30        location: String,
31        /// Measured nesting depth.
32        depth: usize,
33    },
34    /// A module declares functions but carries no doc comment on itself or
35    /// on any of them.
36    EmptyModuleDoc {
37        /// Module with no documentation.
38        module: String,
39    },
40    /// An async function declares no return type, which is unusual.
41    AsyncVoidFunction {
42        /// Module that contains the function.
43        module: String,
44        /// Async function with no return type.
45        function: String,
46    },
47    /// A function is marked deprecated; the message is surfaced to consumers.
48    DeprecatedFunction {
49        /// Module that contains the function.
50        module: String,
51        /// Deprecated function.
52        function: String,
53        /// Deprecation message declared in the IDL.
54        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
90/// Walk every module (including nested submodules) and collect all advisory
91/// warnings for `api`.
92///
93/// Assumes `api` has already passed hard validation; it does not re-check
94/// structural invariants.
95pub 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}