use crate::context::LintContext;
use crate::diagnostic::Severity;
use crate::rule::{Rule, RuleCategory, RuleMeta};
use vize_carton::is_html_tag;
use vize_croquis::builtins::is_builtin_component;
use vize_relief::ast::RootNode;
static META: RuleMeta = RuleMeta {
name: "vue/no-reserved-component-names",
description: "Disallow the use of reserved names as component names",
category: RuleCategory::Essential,
fixable: false,
default_severity: Severity::Error,
};
const RESERVED_NAMES: &[&str] = &[
"annotation-xml",
"color-profile",
"font-face",
"font-face-src",
"font-face-uri",
"font-face-format",
"font-face-name",
"missing-glyph",
];
pub struct NoReservedComponentNames {
pub disallow_html: bool,
pub disallow_vue_builtins: bool,
}
impl Default for NoReservedComponentNames {
fn default() -> Self {
Self {
disallow_html: true,
disallow_vue_builtins: true,
}
}
}
impl NoReservedComponentNames {
fn extract_component_name(filename: &str) -> Option<&str> {
let basename = filename.rsplit('/').next().unwrap_or(filename);
let basename = basename.rsplit('\\').next().unwrap_or(basename);
basename.strip_suffix(".vue")
}
}
impl Rule for NoReservedComponentNames {
fn meta(&self) -> &'static RuleMeta {
&META
}
fn run_on_template<'a>(&self, ctx: &mut LintContext<'a>, root: &RootNode<'a>) {
let filename = ctx.filename;
let Some(component_name) = Self::extract_component_name(filename) else {
return;
};
let name_lower = component_name.to_lowercase();
if RESERVED_NAMES.contains(&name_lower.as_str()) {
ctx.error_with_help(
ctx.t_fmt(
"vue/no-reserved-component-names.message",
&[("name", component_name)],
),
&root.loc,
ctx.t("vue/no-reserved-component-names.help"),
);
return;
}
if self.disallow_html && is_html_tag(&name_lower) {
ctx.error_with_help(
ctx.t_fmt(
"vue/no-reserved-component-names.message",
&[("name", component_name)],
),
&root.loc,
ctx.t("vue/no-reserved-component-names.help"),
);
return;
}
if self.disallow_vue_builtins
&& (is_builtin_component(&name_lower) || is_builtin_component(component_name))
{
ctx.error_with_help(
ctx.t_fmt(
"vue/no-reserved-component-names.message",
&[("name", component_name)],
),
&root.loc,
ctx.t("vue/no-reserved-component-names.help"),
);
}
}
}
#[cfg(test)]
mod tests {
use super::NoReservedComponentNames;
use crate::linter::Linter;
use crate::rule::RuleRegistry;
fn create_linter() -> Linter {
let mut registry = RuleRegistry::new();
registry.register(Box::new(NoReservedComponentNames::default()));
Linter::with_registry(registry)
}
#[test]
fn test_valid_custom_component() {
let linter = create_linter();
let result = linter.lint_template(r#"<div>hello</div>"#, "MyComponent.vue");
assert_eq!(result.error_count, 0);
}
#[test]
fn test_invalid_html_name() {
let linter = create_linter();
let result = linter.lint_template(r#"<div>hello</div>"#, "Div.vue");
assert_eq!(result.error_count, 1);
}
#[test]
fn test_invalid_vue_builtin() {
let linter = create_linter();
let result = linter.lint_template(r#"<div>hello</div>"#, "Transition.vue");
assert_eq!(result.error_count, 1);
}
#[test]
fn test_using_transition_in_template_is_valid() {
let linter = create_linter();
let result = linter.lint_template(
r#"<Transition name="fade"><div>hello</div></Transition>"#,
"MyComponent.vue",
);
assert_eq!(
result.error_count, 0,
"Using Vue built-in <Transition> in template should not be flagged"
);
}
#[test]
fn test_using_keep_alive_in_template_is_valid() {
let linter = create_linter();
let result = linter.lint_template(
r#"<KeepAlive><div>hello</div></KeepAlive>"#,
"MyComponent.vue",
);
assert_eq!(
result.error_count, 0,
"Using Vue built-in <KeepAlive> in template should not be flagged"
);
}
#[test]
fn test_using_teleport_in_template_is_valid() {
let linter = create_linter();
let result = linter.lint_template(
r#"<Teleport to="body"><div>hello</div></Teleport>"#,
"MyComponent.vue",
);
assert_eq!(
result.error_count, 0,
"Using Vue built-in <Teleport> in template should not be flagged"
);
}
#[test]
fn test_using_suspense_in_template_is_valid() {
let linter = create_linter();
let result = linter.lint_template(
r#"<Suspense><div>hello</div></Suspense>"#,
"MyComponent.vue",
);
assert_eq!(
result.error_count, 0,
"Using Vue built-in <Suspense> in template should not be flagged"
);
}
#[test]
fn test_non_vue_file() {
let linter = create_linter();
let result = linter.lint_template(r#"<div>hello</div>"#, "test.html");
assert_eq!(result.error_count, 0);
}
}