Skip to main content

normalize_languages/
prolog.rs

1//! Prolog language support.
2
3use crate::{Import, Language, LanguageSymbols};
4use tree_sitter::Node;
5
6/// Prolog language support.
7pub struct Prolog;
8
9impl Language for Prolog {
10    fn name(&self) -> &'static str {
11        "Prolog"
12    }
13    fn extensions(&self) -> &'static [&'static str] {
14        &["pl", "pro", "prolog"]
15    }
16    fn grammar_name(&self) -> &'static str {
17        "prolog"
18    }
19
20    fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
21        Some(self)
22    }
23
24    fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
25        if node.kind() != "directive_term" {
26            return Vec::new();
27        }
28
29        let text = &content[node.byte_range()];
30        if text.contains("use_module(") {
31            return vec![Import {
32                module: text.trim().to_string(),
33                names: Vec::new(),
34                alias: None,
35                is_wildcard: false,
36                is_relative: false,
37                line: node.start_position().row + 1,
38            }];
39        }
40
41        Vec::new()
42    }
43
44    fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
45        // Prolog: :- use_module(module) or :- use_module(module, [pred/arity])
46        let names_to_use: Vec<&str> = names
47            .map(|n| n.to_vec())
48            .unwrap_or_else(|| import.names.iter().map(|s| s.as_str()).collect());
49        if names_to_use.is_empty() {
50            format!(":- use_module({}).", import.module)
51        } else {
52            format!(
53                ":- use_module({}, [{}]).",
54                import.module,
55                names_to_use.join(", ")
56            )
57        }
58    }
59
60    fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
61        let name = symbol.name.as_str();
62        match symbol.kind {
63            crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
64            crate::SymbolKind::Module => name == "tests" || name == "test",
65            _ => false,
66        }
67    }
68
69    fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
70        // clause_term has no field names — children are atom, functional_notation,
71        // or operator_notation.
72        //
73        // For fact/rule:    clause_term → functional_notation { function: atom @name }
74        // For :- rule head: clause_term → operator_notation → functional_notation { function: atom }
75        // For simple fact:  clause_term → atom @name
76        let mut cursor = node.walk();
77        for child in node.children(&mut cursor) {
78            match child.kind() {
79                "atom" => return Some(&content[child.byte_range()]),
80                "functional_notation" => {
81                    if let Some(name_node) = child.child_by_field_name("function") {
82                        return Some(&content[name_node.byte_range()]);
83                    }
84                }
85                "operator_notation" => {
86                    // Head is the first functional_notation inside operator_notation
87                    let mut inner = child.walk();
88                    for inner_child in child.children(&mut inner) {
89                        if inner_child.kind() == "functional_notation"
90                            && let Some(name_node) = inner_child.child_by_field_name("function")
91                        {
92                            return Some(&content[name_node.byte_range()]);
93                        }
94                    }
95                }
96                _ => {}
97            }
98        }
99        None
100    }
101}
102
103impl LanguageSymbols for Prolog {}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::validate_unused_kinds_audit;
109
110    #[test]
111    fn unused_node_kinds_audit() {
112        #[rustfmt::skip]
113        let documented_unused: &[&str] = &[
114            "binary_operator", "prefix_operator", "prexif_operator",
115        ];
116        validate_unused_kinds_audit(&Prolog, documented_unused)
117            .expect("Prolog unused node kinds audit failed");
118    }
119}