normalize-languages 0.3.1

Tree-sitter language support and dynamic grammar loading
Documentation
//! AsciiDoc language support.

use crate::{Import, Language, LanguageSymbols};
use tree_sitter::Node;

/// AsciiDoc language support.
pub struct AsciiDoc;

impl Language for AsciiDoc {
    fn name(&self) -> &'static str {
        "AsciiDoc"
    }
    fn extensions(&self) -> &'static [&'static str] {
        &["adoc", "asciidoc", "asc"]
    }
    fn grammar_name(&self) -> &'static str {
        "asciidoc"
    }

    fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
        Some(self)
    }

    fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
        if node.kind() != "block_macro" {
            return Vec::new();
        }

        let text = &content[node.byte_range()];
        // Only include macros are imports
        if !text.starts_with("include::") {
            return Vec::new();
        }

        vec![Import {
            module: text.trim().to_string(),
            names: Vec::new(),
            alias: None,
            is_wildcard: false,
            is_relative: false,
            line: node.start_position().row + 1,
        }]
    }

    fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
        import.module.clone()
    }

    fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
        node.child_by_field_name("content")
    }

    fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
        // For sections, the title is typically the first line
        let text = &content[node.byte_range()];
        let first_line = text.lines().next()?;
        // Strip section markers (=, ==, etc.)
        let name = first_line.trim().trim_start_matches('=').trim();
        if !name.is_empty() { Some(name) } else { None }
    }
}

impl LanguageSymbols for AsciiDoc {}

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

    #[test]
    fn unused_node_kinds_audit() {
        #[rustfmt::skip]
        let documented_unused: &[&str] = &[
            // Block types - not symbols
            "literal_block", "listing_block", "open_block", "quoted_block",
            "passthrough_block", "delimited_block", "table_block", "ntable_block",
            "ident_block", "quoted_md_block",
            // Block markers and bodies
            "block_comment", "block_comment_start_marker", "block_comment_end_marker",
            "quoted_block_marker", "quoted_block_md_marker", "passthrough_block_marker",
            "open_block_marker", "table_block_marker", "ntable_block_marker",
            "literal_block_marker", "literal_block_body",
            "listing_block_start_marker", "listing_block_end_marker", "listing_block_body",
            "delimited_block_start_marker", "delimited_block_end_marker",
            // Block elements and titles
            "block_title", "block_title_marker", "block_element",
            "block_macro_name", "block_macro_attr",
            // Other content
            "body", "ident_block_line", "admonition_important",
            // structural node, not extracted as symbols
            "section_block",
            "block_macro",
        ];
        validate_unused_kinds_audit(&AsciiDoc, documented_unused)
            .expect("AsciiDoc unused node kinds audit failed");
    }
}