panache 2.52.0

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

pub struct EmojiAliasesRule;

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

    fn node_interests(&self) -> &'static [SyntaxKind] {
        &[SyntaxKind::EMOJI]
    }

    fn check(&self, cx: &LintContext) -> Vec<Diagnostic> {
        if !cx.config.extensions.emoji {
            return Vec::new();
        }
        let input = cx.input;

        let mut diagnostics = Vec::new();

        for node in cx.nodes(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::*;
    use crate::config::Config;

    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(&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());
    }
}