normalize-languages 0.3.2

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

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

/// PowerShell language support.
pub struct PowerShell;

impl Language for PowerShell {
    fn name(&self) -> &'static str {
        "PowerShell"
    }
    fn extensions(&self) -> &'static [&'static str] {
        &["ps1", "psm1", "psd1"]
    }
    fn grammar_name(&self) -> &'static str {
        "powershell"
    }

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

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

        let text = &content[node.byte_range()];
        let line = node.start_position().row + 1;

        // Import-Module ModuleName
        if let Some(rest) = text.strip_prefix("Import-Module ") {
            let module = rest.split_whitespace().next().map(|s| s.to_string());
            if let Some(module) = module {
                return vec![Import {
                    module,
                    names: Vec::new(),
                    alias: None,
                    is_wildcard: true,
                    is_relative: false,
                    line,
                }];
            }
        }

        Vec::new()
    }

    fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
        // PowerShell: Import-Module or using module
        format!("Import-Module {}", import.module)
    }

    fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
        let name = symbol.name.as_str();
        match symbol.kind {
            crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
            crate::SymbolKind::Module => name == "tests" || name == "test",
            _ => false,
        }
    }

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

    fn analyze_container_body(
        &self,
        body_node: &Node,
        content: &str,
        inner_indent: &str,
    ) -> Option<ContainerBody> {
        crate::body::analyze_brace_body(body_node, content, inner_indent)
    }
}

impl LanguageSymbols for PowerShell {}

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

    #[test]
    fn unused_node_kinds_audit() {
        #[rustfmt::skip]
        let documented_unused: &[&str] = &[
            "additive_argument_expression", "additive_expression", "argument_expression",
            "argument_expression_list", "array_expression", "array_literal_expression",
            "array_type_name", "assignement_operator", "assignment_expression",
            "bitwise_argument_expression", "bitwise_expression", "block_name", "cast_expression",
            "catch_clauses", "catch_type_list", "class_attribute", "class_method_definition",
            "class_method_parameter", "class_method_parameter_list", "class_property_definition",
            "command_invokation_operator", "comparison_argument_expression",
            "comparison_expression", "comparison_operator", "data_statement", "do_statement",
            "else_clause", "elseif_clauses", "empty_statement", "enum_member",
            "expression_with_unary_operator", "file_redirection_operator", "finally_clause",
            "flow_control_statement", "for_condition", "for_initializer", "for_iterator",
            "foreach_command", "foreach_parameter", "format_argument_expression",
            "format_expression", "format_operator",
            "function_parameter_declaration", "generic_type_arguments", "generic_type_name",
            "hash_entry", "hash_literal_body", "hash_literal_expression",
            "inlinescript_statement", "invokation_expression", "invokation_foreach_expression",
            "key_expression", "label_expression", "left_assignment_expression",
            "logical_argument_expression", "logical_expression", "merging_redirection_operator",
            "multiplicative_argument_expression", "multiplicative_expression", "named_block",
            "named_block_list", "parallel_statement", "param_block", "parenthesized_expression",
            "post_decrement_expression", "post_increment_expression", "pre_decrement_expression",
            "pre_increment_expression", "range_argument_expression", "range_expression",
            "script_block_body", "script_block_expression", "sequence_statement",
            "statement_block", "statement_list", "sub_expression", "switch_body",
            "switch_clause", "switch_clause_condition", "switch_clauses", "trap_statement",
            "type_identifier", "type_literal", "type_name", "type_spec", "unary_expression",
            "while_condition",
            // control flow — not extracted as symbols
            "if_statement",
            "try_statement",
            "catch_clause",
            "while_statement",
            "switch_statement",
            "for_statement",
            "elseif_clause",
            "foreach_statement",
            "script_block",
        ];
        validate_unused_kinds_audit(&PowerShell, documented_unused)
            .expect("PowerShell unused node kinds audit failed");
    }
}