panache 2.43.0

An LSP, formatter, and linter for Markdown, Quarto, and R Markdown
use crate::config::Config;
use crate::linter::diagnostics::{Diagnostic, Location};
use crate::linter::rules::Rule;
use crate::syntax::{SyntaxKind, SyntaxNode};

pub struct EmojiAliasesRule;

impl Rule for EmojiAliasesRule {
    fn name(&self) -> &str {
        "unknown-emoji-alias"
    }

    fn check(
        &self,
        tree: &SyntaxNode,
        input: &str,
        config: &Config,
        _metadata: Option<&crate::metadata::DocumentMetadata>,
    ) -> Vec<Diagnostic> {
        if !config.extensions.emoji {
            return Vec::new();
        }

        let mut diagnostics = Vec::new();

        for node in tree
            .descendants()
            .filter(|node| node.kind() == SyntaxKind::EMOJI)
        {
            let raw = node.to_string();
            let Some(alias) = raw.strip_prefix(':').and_then(|s| s.strip_suffix(':')) else {
                continue;
            };

            if emojis::get_by_shortcode(alias).is_none() {
                diagnostics.push(Diagnostic::warning(
                    Location::from_node(&node, input),
                    "unknown-emoji-alias",
                    format!("Unknown emoji alias '{}'", raw),
                ));
            }
        }

        diagnostics
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn parse_and_lint(input: &str, emoji_enabled: bool) -> Vec<Diagnostic> {
        let mut config = Config::default();
        config.extensions.emoji = emoji_enabled;
        let tree = crate::parser::parse(input, Some(config.clone()));
        let rule = EmojiAliasesRule;
        rule.check(&tree, input, &config, None)
    }

    #[test]
    fn accepts_known_alias() {
        let diagnostics = parse_and_lint("Hello :smile:", true);
        assert!(diagnostics.is_empty());
    }

    #[test]
    fn warns_on_unknown_alias() {
        let diagnostics = parse_and_lint("Hello :not-a-real-emoji:", true);
        assert_eq!(diagnostics.len(), 1);
        assert_eq!(diagnostics[0].code, "unknown-emoji-alias");
        assert_eq!(diagnostics[0].location.line, 1);
    }

    #[test]
    fn does_not_run_when_emoji_extension_disabled() {
        let diagnostics = parse_and_lint("Hello :not-a-real-emoji:", false);
        assert!(diagnostics.is_empty());
    }
}