use crate::context::LintContext;
use crate::diagnostic::Severity;
use crate::rule::{Rule, RuleCategory, RuleMeta};
use vize_relief::ast::RootNode;
static META: RuleMeta = RuleMeta {
name: "vue/component-definition-name-casing",
description: "Enforce PascalCase for component definition names",
category: RuleCategory::StronglyRecommended,
fixable: false,
default_severity: Severity::Warning,
};
#[derive(Default)]
pub struct ComponentDefinitionNameCasing;
fn is_pascal_case(s: &str) -> bool {
if s.is_empty() {
return false;
}
let first = s.chars().next().unwrap();
if !first.is_ascii_uppercase() {
return false;
}
if s.contains('-') {
return false;
}
if s.chars()
.all(|c| c.is_ascii_uppercase() || !c.is_alphabetic())
{
return false;
}
true
}
const EXCEPTION_NAMES: &[&str] = &["App"];
impl Rule for ComponentDefinitionNameCasing {
fn meta(&self) -> &'static RuleMeta {
&META
}
fn run_on_template<'a>(&self, ctx: &mut LintContext<'a>, root: &RootNode<'a>) {
let filename = ctx.filename;
if !filename.ends_with(".vue") {
return;
}
let stem = filename
.rsplit('/')
.next()
.unwrap_or(filename)
.rsplit('\\')
.next()
.unwrap_or(filename)
.trim_end_matches(".vue");
if EXCEPTION_NAMES.contains(&stem) {
return;
}
if stem.starts_with('[') {
return;
}
if stem.chars().all(|c| c.is_ascii_lowercase()) {
return;
}
if !is_pascal_case(stem) {
ctx.warn_with_help(
ctx.t_fmt(
"vue/component-definition-name-casing.message",
&[("name", stem)],
),
&root.loc,
ctx.t("vue/component-definition-name-casing.help"),
);
}
}
}
#[cfg(test)]
mod tests {
use super::ComponentDefinitionNameCasing;
use crate::linter::Linter;
use crate::rule::RuleRegistry;
fn create_linter() -> Linter {
let mut registry = RuleRegistry::new();
registry.register(Box::new(ComponentDefinitionNameCasing));
Linter::with_registry(registry)
}
#[test]
fn test_valid_pascal_case() {
let linter = create_linter();
let result = linter.lint_template(r#"<div>Content</div>"#, "MyComponent.vue");
assert_eq!(result.warning_count, 0);
}
#[test]
fn test_valid_index() {
let linter = create_linter();
let result = linter.lint_template(r#"<div>Content</div>"#, "index.vue");
assert_eq!(result.warning_count, 0);
}
#[test]
fn test_valid_app() {
let linter = create_linter();
let result = linter.lint_template(r#"<div>Content</div>"#, "App.vue");
assert_eq!(result.warning_count, 0);
}
#[test]
fn test_invalid_kebab_case() {
let linter = create_linter();
let result = linter.lint_template(r#"<div>Content</div>"#, "my-component.vue");
assert_eq!(result.warning_count, 1);
}
#[test]
fn test_invalid_camel_case() {
let linter = create_linter();
let result = linter.lint_template(r#"<div>Content</div>"#, "myComponent.vue");
assert_eq!(result.warning_count, 1);
}
#[test]
fn test_valid_with_path() {
let linter = create_linter();
let result =
linter.lint_template(r#"<div>Content</div>"#, "src/components/MyComponent.vue");
assert_eq!(result.warning_count, 0);
}
#[test]
fn test_valid_non_vue_file() {
let linter = create_linter();
let result = linter.lint_template(r#"<div>Content</div>"#, "test.html");
assert_eq!(result.warning_count, 0);
}
}