use memchr::memmem;
use crate::diagnostic::{LintDiagnostic, Severity};
use super::{ScriptLintResult, ScriptRule, ScriptRuleMeta};
static META: ScriptRuleMeta = ScriptRuleMeta {
name: "script/prefer-computed",
description: "Prefer computed() for derived reactive state",
default_severity: Severity::Warning,
};
pub struct PreferComputed;
impl ScriptRule for PreferComputed {
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"watch(").is_none() && memmem::find(bytes, b"watch (").is_none() {
return;
}
let watch_finder = memmem::Finder::new(b"watch(");
let mut search_start = 0;
while let Some(pos) = watch_finder.find(&bytes[search_start..]) {
let abs_pos = search_start + pos;
search_start = abs_pos + 6;
let rest = &source[abs_pos..];
if let Some(value_assign_pos) = rest.find(".value =") {
if value_assign_pos < 200 {
result.add_diagnostic(
LintDiagnostic::warn(
META.name,
"Consider using computed() instead of watch() for derived state",
(offset + abs_pos) as u32,
(offset + abs_pos + 5) as u32,
)
.with_help(
"If the watch callback only assigns to a ref based on the watched value, \
use computed() instead: `const derived = computed(() => source.value * 2)`",
),
);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rules::script::ScriptLinter;
fn create_linter() -> ScriptLinter {
let mut linter = ScriptLinter::new();
linter.add_rule(Box::new(PreferComputed));
linter
}
#[test]
fn test_warn_watch_with_value_assignment() {
let linter = create_linter();
let result = linter.lint(
r#"
const count = ref(0)
const doubled = ref(0)
watch(count, (val) => {
doubled.value = val * 2
})
"#,
0,
);
assert_eq!(result.warning_count, 1);
assert!(result.diagnostics[0].message.contains("computed"));
}
#[test]
fn test_valid_watch_without_value_assignment() {
let linter = create_linter();
let result = linter.lint(
r#"
const count = ref(0)
watch(count, (val) => {
console.log('count changed:', val)
})
"#,
0,
);
assert_eq!(result.warning_count, 0);
}
#[test]
fn test_no_watch() {
let linter = create_linter();
let result = linter.lint(
r#"
const count = ref(0)
const doubled = computed(() => count.value * 2)
"#,
0,
);
assert_eq!(result.warning_count, 0);
}
}