#![allow(clippy::disallowed_macros)]
use super::{MuseaLintResult, MuseaRule, MuseaRuleMeta};
use crate::diagnostic::{LintDiagnostic, Severity};
static META: MuseaRuleMeta = MuseaRuleMeta {
name: "musea/require-title",
description: "Require title attribute in <art> block",
default_severity: Severity::Error,
};
pub struct RequireTitle;
impl MuseaRule for RequireTitle {
fn meta(&self) -> &'static MuseaRuleMeta {
&META
}
fn check(&self, source: &str, result: &mut MuseaLintResult) {
let Some(art_start) = source.find("<art") else {
return; };
let tag_content = &source[art_start..];
let Some(tag_end) = tag_content.find('>') else {
return;
};
let art_tag = &tag_content[..tag_end];
if !has_attribute(art_tag, "title") {
result.add_diagnostic(
LintDiagnostic::error(
META.name,
"Missing required 'title' attribute in <art> block",
art_start as u32,
(art_start + tag_end) as u32,
)
.with_help("Add a title attribute: <art title=\"Component Name\">"),
);
}
}
}
fn has_attribute(tag: &str, attr_name: &str) -> bool {
let patterns = [format!("{}=", attr_name), format!("{} =", attr_name)];
for pattern in patterns {
if tag.contains(&pattern) {
return true;
}
}
false
}
#[cfg(test)]
mod tests {
use super::{MuseaLintResult, MuseaRule, RequireTitle};
#[test]
fn test_valid_with_title() {
let source = r#"<art title="Button" component="./Button.vue"></art>"#;
let rule = RequireTitle;
let mut result = MuseaLintResult::default();
rule.check(source, &mut result);
assert_eq!(result.error_count, 0);
}
#[test]
fn test_invalid_missing_title() {
let source = r#"<art component="./Button.vue"></art>"#;
let rule = RequireTitle;
let mut result = MuseaLintResult::default();
rule.check(source, &mut result);
assert_eq!(result.error_count, 1);
insta::assert_debug_snapshot!(result.diagnostics);
}
#[test]
fn test_valid_title_with_spaces() {
let source = r#"<art title = "Button" component="./Button.vue"></art>"#;
let rule = RequireTitle;
let mut result = MuseaLintResult::default();
rule.check(source, &mut result);
assert_eq!(result.error_count, 0);
}
}