Skip to main content

normalize_languages/
graphql.rs

1//! GraphQL language support.
2
3use crate::{ContainerBody, Language, LanguageSymbols};
4use tree_sitter::Node;
5
6/// GraphQL language support.
7pub struct GraphQL;
8
9impl GraphQL {
10    /// Recursively collect named_type names from implements_interfaces.
11    /// The node is recursive: implements_interfaces > implements_interfaces > named_type > name
12    fn collect_named_types(node: &Node, out: &mut Vec<String>, content: &str) {
13        if node.kind() == "named_type" {
14            // Get the name child (first named child)
15            for i in 0..node.child_count() {
16                if let Some(child) = node.child(i as u32)
17                    && child.is_named()
18                {
19                    out.push(content[child.byte_range()].to_string());
20                    return;
21                }
22            }
23            return;
24        }
25        for i in 0..node.child_count() {
26            if let Some(child) = node.child(i as u32) {
27                Self::collect_named_types(&child, out, content);
28            }
29        }
30    }
31}
32
33impl Language for GraphQL {
34    fn name(&self) -> &'static str {
35        "GraphQL"
36    }
37    fn extensions(&self) -> &'static [&'static str] {
38        &["graphql", "gql"]
39    }
40    fn grammar_name(&self) -> &'static str {
41        "graphql"
42    }
43
44    fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
45        Some(self)
46    }
47
48    fn build_signature(&self, node: &Node, content: &str) -> String {
49        let name = self.node_name(node, content).unwrap_or("");
50        let keyword = match node.kind() {
51            "interface_type_definition" => "interface",
52            "enum_type_definition" => "enum",
53            "union_type_definition" => "union",
54            "input_object_type_definition" => "input",
55            "scalar_type_definition" => "scalar",
56            _ => "type",
57        };
58        format!("{} {}", keyword, name)
59    }
60
61    fn extract_implements(&self, node: &Node, content: &str) -> crate::ImplementsInfo {
62        let mut implements = Vec::new();
63        for i in 0..node.child_count() {
64            if let Some(child) = node.child(i as u32)
65                && child.kind() == "implements_interfaces"
66            {
67                GraphQL::collect_named_types(&child, &mut implements, content);
68            }
69        }
70        crate::ImplementsInfo {
71            is_interface: false,
72            implements,
73        }
74    }
75
76    fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
77        node.child_by_field_name("fields_definition")
78    }
79
80    fn analyze_container_body(
81        &self,
82        body_node: &Node,
83        content: &str,
84        inner_indent: &str,
85    ) -> Option<ContainerBody> {
86        // fields_definition: "{ field1: Type\n  field2: Type\n }"
87        crate::body::analyze_brace_body(body_node, content, inner_indent)
88    }
89
90    fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
91        if let Some(n) = node.child_by_field_name("name") {
92            return Some(&content[n.byte_range()]);
93        }
94        // Fallback: find first child of kind "name"
95        for i in 0..node.child_count() {
96            if let Some(child) = node.child(i as u32)
97                && child.kind() == "name"
98            {
99                return Some(&content[child.byte_range()]);
100            }
101        }
102        None
103    }
104}
105
106impl LanguageSymbols for GraphQL {}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use crate::validate_unused_kinds_audit;
112
113    #[test]
114    fn unused_node_kinds_audit() {
115        // Run cross_check_node_kinds to populate - many kinds already used
116        #[rustfmt::skip]
117        let documented_unused: &[&str] = &[
118            "argument", "directive", "enum_value", "enum_value_definition",
119            "enum_values_definition", "executable_definition", "field",
120            "fields_definition", "fragment_spread",
121            "implements_interfaces", "inline_fragment", "input_fields_definition",
122            "input_value_definition", "named_type", "type", "type_condition",
123            "type_definition", "type_extension", "type_system_definition",
124            "type_system_extension", "union_member_types", "variable_definition",
125            "arguments_definition", "definition", "directive_definition", "list_type",
126            "non_null_type", "object_type_extension", "operation_type",
127            "root_operation_type_definition", "scalar_type_extension", "schema_definition",
128            "enum_type_extension", "input_object_type_extension", "interface_type_extension",
129            "type_system_directive_location", "union_type_extension", "variable_definitions",
130        ];
131        validate_unused_kinds_audit(&GraphQL, documented_unused)
132            .expect("GraphQL unused node kinds audit failed");
133    }
134}