use vize_carton::CompactString;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum SetupContextViolationKind {
ModuleLevelState = 0,
ModuleLevelWatch = 1,
ModuleLevelComputed = 2,
ModuleLevelProvide = 3,
ModuleLevelInject = 4,
ModuleLevelLifecycle = 5,
}
impl SetupContextViolationKind {
#[inline]
pub const fn to_display(self) -> &'static str {
match self {
Self::ModuleLevelState => "module-level-state",
Self::ModuleLevelWatch => "module-level-watch",
Self::ModuleLevelComputed => "module-level-computed",
Self::ModuleLevelProvide => "module-level-provide",
Self::ModuleLevelInject => "module-level-inject",
Self::ModuleLevelLifecycle => "module-level-lifecycle",
}
}
#[inline]
pub const fn severity(self) -> ViolationSeverity {
match self {
Self::ModuleLevelProvide | Self::ModuleLevelInject | Self::ModuleLevelLifecycle => {
ViolationSeverity::Error
}
Self::ModuleLevelState => ViolationSeverity::Warning,
Self::ModuleLevelWatch | Self::ModuleLevelComputed => ViolationSeverity::Warning,
}
}
pub fn description(self) -> &'static str {
match self {
Self::ModuleLevelState => {
"Module-level reactive state causes Cross-request State Pollution (CSRP) in SSR"
}
Self::ModuleLevelWatch => {
"Module-level watch is never cleaned up, causing memory leaks"
}
Self::ModuleLevelComputed => {
"Module-level computed is never cleaned up, causing memory leaks"
}
Self::ModuleLevelProvide => "provide() must be called inside setup() or <script setup>",
Self::ModuleLevelInject => "inject() must be called inside setup() or <script setup>",
Self::ModuleLevelLifecycle => {
"Lifecycle hooks must be called inside setup() or <script setup>"
}
}
}
pub fn from_api_name(name: &str) -> Option<Self> {
match name {
"ref" | "shallowRef" | "reactive" | "shallowReactive" | "customRef" | "toRef"
| "toRefs" => Some(Self::ModuleLevelState),
"watch" | "watchEffect" | "watchPostEffect" | "watchSyncEffect" => {
Some(Self::ModuleLevelWatch)
}
"computed" => Some(Self::ModuleLevelComputed),
"provide" => Some(Self::ModuleLevelProvide),
"inject" => Some(Self::ModuleLevelInject),
"onMounted" | "onUnmounted" | "onBeforeMount" | "onBeforeUnmount" | "onUpdated"
| "onBeforeUpdate" | "onActivated" | "onDeactivated" | "onErrorCaptured"
| "onRenderTracked" | "onRenderTriggered" | "onServerPrefetch" => {
Some(Self::ModuleLevelLifecycle)
}
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum ViolationSeverity {
Info = 0,
Warning = 1,
Error = 2,
}
impl ViolationSeverity {
#[inline]
pub const fn to_display(self) -> &'static str {
match self {
Self::Info => "info",
Self::Warning => "warning",
Self::Error => "error",
}
}
}
#[derive(Debug, Clone)]
pub struct SetupContextViolation {
pub kind: SetupContextViolationKind,
pub api_name: CompactString,
pub start: u32,
pub end: u32,
}
#[derive(Debug, Default)]
pub struct SetupContextTracker {
violations: Vec<SetupContextViolation>,
}
impl SetupContextTracker {
#[inline]
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn record_violation(
&mut self,
kind: SetupContextViolationKind,
api_name: CompactString,
start: u32,
end: u32,
) {
self.violations.push(SetupContextViolation {
kind,
api_name,
start,
end,
});
}
#[inline]
pub fn violations(&self) -> &[SetupContextViolation] {
&self.violations
}
#[inline]
pub fn has_violations(&self) -> bool {
!self.violations.is_empty()
}
#[inline]
pub fn count(&self) -> usize {
self.violations.len()
}
}
#[cfg(test)]
mod tests {
use super::{SetupContextTracker, SetupContextViolationKind};
use vize_carton::CompactString;
#[test]
fn test_violation_kind_from_api() {
assert_eq!(
SetupContextViolationKind::from_api_name("ref"),
Some(SetupContextViolationKind::ModuleLevelState)
);
assert_eq!(
SetupContextViolationKind::from_api_name("watch"),
Some(SetupContextViolationKind::ModuleLevelWatch)
);
assert_eq!(
SetupContextViolationKind::from_api_name("computed"),
Some(SetupContextViolationKind::ModuleLevelComputed)
);
assert_eq!(
SetupContextViolationKind::from_api_name("provide"),
Some(SetupContextViolationKind::ModuleLevelProvide)
);
assert_eq!(
SetupContextViolationKind::from_api_name("inject"),
Some(SetupContextViolationKind::ModuleLevelInject)
);
assert_eq!(
SetupContextViolationKind::from_api_name("onMounted"),
Some(SetupContextViolationKind::ModuleLevelLifecycle)
);
assert_eq!(SetupContextViolationKind::from_api_name("unknown"), None);
}
#[test]
fn test_tracker() {
let mut tracker = SetupContextTracker::new();
tracker.record_violation(
SetupContextViolationKind::ModuleLevelState,
CompactString::new("ref"),
0,
10,
);
assert!(tracker.has_violations());
assert_eq!(tracker.count(), 1);
assert_eq!(
tracker.violations()[0].kind,
SetupContextViolationKind::ModuleLevelState
);
}
}