use memchr::memmem;
use crate::diagnostic::{LintDiagnostic, Severity};
use super::{ScriptLintResult, ScriptRule, ScriptRuleMeta};
static META: ScriptRuleMeta = ScriptRuleMeta {
name: "script/no-async-in-computed",
description: "Disallow async functions in computed properties",
default_severity: Severity::Error,
};
pub struct NoAsyncInComputed;
impl ScriptRule for NoAsyncInComputed {
fn meta(&self) -> &'static ScriptRuleMeta {
&META
}
fn check(&self, source: &str, offset: usize, result: &mut ScriptLintResult) {
let bytes = source.as_bytes();
if memmem::find(bytes, b"computed").is_none() {
return;
}
let finder = memmem::Finder::new(b"computed(");
let mut search_start = 0;
while let Some(pos) = finder.find(&bytes[search_start..]) {
let abs_pos = search_start + pos;
search_start = abs_pos + 9;
let after = &source[abs_pos + 9..];
let trimmed = after.trim_start();
if let Some(after_async) = trimmed.strip_prefix("async") {
let next_char = after_async.chars().next();
if matches!(next_char, Some(' ') | Some('\t') | Some('\n') | Some('(')) {
result.add_diagnostic(
LintDiagnostic::error(
META.name,
"Computed properties cannot be async. They must return a value synchronously.",
(offset + abs_pos) as u32,
(offset + abs_pos + 9 + trimmed.find("async").unwrap_or(0) + 5) as u32,
)
.with_help(
"Use ref with watchEffect for async operations: \
`const data = ref(null); watchEffect(async () => { data.value = await fetchData() })`",
),
);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::NoAsyncInComputed;
use crate::rules::script::ScriptLinter;
fn create_linter() -> ScriptLinter {
let mut linter = ScriptLinter::new();
linter.add_rule(Box::new(NoAsyncInComputed));
linter
}
#[test]
fn test_valid_sync_computed() {
let linter = create_linter();
let result = linter.lint("const doubled = computed(() => count.value * 2)", 0);
assert_eq!(result.error_count, 0);
}
#[test]
fn test_invalid_async_arrow_computed() {
let linter = create_linter();
let result = linter.lint("const data = computed(async () => await fetch('/api'))", 0);
assert_eq!(result.error_count, 1);
insta::assert_debug_snapshot!(result.diagnostics);
}
#[test]
fn test_invalid_async_function_computed() {
let linter = create_linter();
let result = linter.lint(
"const data = computed(async function() { return await fetch('/api') })",
0,
);
assert_eq!(result.error_count, 1);
}
#[test]
fn test_valid_watcheffect_async() {
let linter = create_linter();
let result = linter.lint(
"watchEffect(async () => { data.value = await fetch('/api') })",
0,
);
assert_eq!(result.error_count, 0);
}
}