#![allow(clippy::disallowed_macros)]
use crate::context::LintContext;
use crate::diagnostic::Severity;
use crate::rule::{Rule, RuleCategory, RuleMeta};
use vize_relief::ast::{DirectiveNode, ElementNode, ExpressionNode};
static META: RuleMeta = RuleMeta {
name: "vue/scoped-event-names",
description: "Recommend scoped event names using context:event format",
category: RuleCategory::Recommended,
fixable: false,
default_severity: Severity::Warning,
};
const COMMON_SUFFIXES: &[&str] = &[
"Audio", "Video", "Form", "Dialog", "Modal", "Menu", "List", "Item", "User", "Data", "File",
"Image", "Input", "Select", "Button",
];
pub struct ScopedEventNames;
impl ScopedEventNames {
fn could_benefit_from_scope(event_name: &str) -> Option<&'static str> {
for suffix in COMMON_SUFFIXES {
let lower_suffix = suffix.to_lowercase();
let lower_name = event_name.to_lowercase();
if lower_name.ends_with(&lower_suffix) && lower_name.len() > lower_suffix.len() {
return Some(suffix);
}
if lower_name.ends_with(&format!("-{}", lower_suffix)) {
return Some(suffix);
}
}
None
}
fn is_scoped(event_name: &str) -> bool {
event_name.contains(':')
}
}
impl Rule for ScopedEventNames {
fn meta(&self) -> &'static RuleMeta {
&META
}
fn check_directive<'a>(
&self,
ctx: &mut LintContext<'a>,
_element: &ElementNode<'a>,
directive: &DirectiveNode<'a>,
) {
if directive.name.as_str() != "on" {
return;
}
let event_name = match &directive.arg {
Some(ExpressionNode::Simple(s)) => s.content.as_str(),
_ => return,
};
if Self::is_scoped(event_name) {
return;
}
if Self::could_benefit_from_scope(event_name).is_some() {
ctx.warn_with_help(
ctx.t("vue/scoped-event-names.message"),
&directive.loc,
ctx.t("vue/scoped-event-names.help"),
);
}
}
}
#[cfg(test)]
mod tests {
use super::ScopedEventNames;
use crate::linter::Linter;
use crate::rule::RuleRegistry;
fn create_linter() -> Linter {
let mut registry = RuleRegistry::new();
registry.register(Box::new(ScopedEventNames));
Linter::with_registry(registry)
}
#[test]
fn test_valid_scoped_event() {
let linter = create_linter();
let result =
linter.lint_template(r#"<button @audio:play="handlePlay"></button>"#, "test.vue");
assert_eq!(result.warning_count, 0);
}
#[test]
fn test_warn_unscoped_event_with_suffix() {
let linter = create_linter();
let result =
linter.lint_template(r#"<button @playAudio="handlePlay"></button>"#, "test.vue");
assert_eq!(result.warning_count, 1);
assert!(result.diagnostics[0].message.contains("scoped"));
}
#[test]
fn test_valid_simple_event() {
let linter = create_linter();
let result = linter.lint_template(r#"<button @click="handleClick"></button>"#, "test.vue");
assert_eq!(result.warning_count, 0);
}
#[test]
fn test_valid_update_event() {
let linter = create_linter();
let result =
linter.lint_template(r#"<input @update:modelValue="handleUpdate" />"#, "test.vue");
assert_eq!(result.warning_count, 0);
}
}