Skip to main content

normalize_languages/
objc.rs

1//! Objective-C language support.
2
3use crate::{ContainerBody, Import, Language, LanguageSymbols};
4use tree_sitter::Node;
5
6/// Objective-C language support.
7pub struct ObjC;
8
9impl Language for ObjC {
10    fn name(&self) -> &'static str {
11        "Objective-C"
12    }
13    fn extensions(&self) -> &'static [&'static str] {
14        &["m", "mm"]
15    }
16    fn grammar_name(&self) -> &'static str {
17        "objc"
18    }
19
20    fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
21        Some(self)
22    }
23
24    fn build_signature(&self, node: &Node, content: &str) -> String {
25        let text = &content[node.byte_range()];
26        text.lines().next().unwrap_or(text).trim().to_string()
27    }
28
29    fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
30        match node.kind() {
31            "preproc_include" => {
32                let text = &content[node.byte_range()];
33                vec![Import {
34                    module: text.trim().to_string(),
35                    names: Vec::new(),
36                    alias: None,
37                    is_wildcard: false,
38                    is_relative: text.contains('"'),
39                    line: node.start_position().row + 1,
40                }]
41            }
42            _ => Vec::new(),
43        }
44    }
45
46    fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
47        // Objective-C: #import <Header.h> or #import "header.h"
48        if import.is_relative {
49            format!("#import \"{}\"", import.module)
50        } else {
51            format!("#import <{}>", import.module)
52        }
53    }
54
55    fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
56        let name = symbol.name.as_str();
57        match symbol.kind {
58            crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
59            crate::SymbolKind::Module => name == "tests" || name == "test",
60            _ => false,
61        }
62    }
63
64    fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
65        // ObjC has no body field; @interface/@implementation span the whole node
66        Some(*node)
67    }
68
69    fn analyze_container_body(
70        &self,
71        body_node: &Node,
72        content: &str,
73        inner_indent: &str,
74    ) -> Option<ContainerBody> {
75        // Structure: "@interface Foo : Bar\n  methods\n@end"
76        // Content starts after the first newline, ends before @end child
77        let start = body_node.start_byte();
78        let end = body_node.end_byte();
79        let bytes = content.as_bytes();
80
81        // Skip past the header line (first \n)
82        let mut content_start = start;
83        while content_start < end && bytes[content_start] != b'\n' {
84            content_start += 1;
85        }
86        if content_start < end {
87            content_start += 1; // skip the \n
88        }
89
90        // Find @end child — content ends just before it
91        let mut content_end = end;
92        let mut c = body_node.walk();
93        for child in body_node.children(&mut c) {
94            if child.kind() == "@end" {
95                content_end = child.start_byte();
96                // trim trailing whitespace before @end
97                while content_end > content_start
98                    && matches!(bytes[content_end - 1], b' ' | b'\t' | b'\n')
99                {
100                    content_end -= 1;
101                }
102                break;
103            }
104        }
105
106        let is_empty = content[content_start..content_end].trim().is_empty();
107        Some(ContainerBody {
108            content_start,
109            content_end,
110            inner_indent: inner_indent.to_string(),
111            is_empty,
112        })
113    }
114
115    fn extract_implements(&self, node: &Node, content: &str) -> crate::ImplementsInfo {
116        let mut implements = Vec::new();
117        // Superclass is a named field inlined from _class_interface_inheritance
118        if let Some(superclass) = node.child_by_field_name("superclass") {
119            implements.push(content[superclass.byte_range()].to_string());
120        }
121        // Protocols are in a parameterized_arguments child: <Proto1, Proto2>
122        // Each protocol appears as type_name > type_identifier inside parameterized_arguments.
123        let mut cursor = node.walk();
124        for child in node.children(&mut cursor) {
125            if child.kind() == "parameterized_arguments" {
126                let mut pc = child.walk();
127                for item in child.children(&mut pc) {
128                    if item.kind() == "identifier" || item.kind() == "type_identifier" {
129                        implements.push(content[item.byte_range()].to_string());
130                    } else if item.kind() == "type_name" {
131                        // type_name wraps type_identifier: extract the inner type
132                        let mut tc = item.walk();
133                        for inner in item.children(&mut tc) {
134                            if inner.kind() == "type_identifier" || inner.kind() == "identifier" {
135                                implements.push(content[inner.byte_range()].to_string());
136                            }
137                        }
138                    }
139                }
140            }
141        }
142        crate::ImplementsInfo {
143            is_interface: false,
144            implements,
145        }
146    }
147
148    fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
149        if let Some(n) = node
150            .child_by_field_name("name")
151            .or_else(|| node.child_by_field_name("declarator"))
152        {
153            return Some(&content[n.byte_range()]);
154        }
155        // ObjC class_interface/class_implementation: first identifier child is the name
156        for i in 0..node.child_count() {
157            if let Some(child) = node.child(i as u32)
158                && child.kind() == "identifier"
159            {
160                return Some(&content[child.byte_range()]);
161            }
162        }
163        None
164    }
165}
166
167impl LanguageSymbols for ObjC {}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172    use crate::validate_unused_kinds_audit;
173
174    #[test]
175    fn unused_node_kinds_audit() {
176        #[rustfmt::skip]
177        let documented_unused: &[&str] = &[
178            // Preprocessor
179            "preproc_if", "preproc_elif", "preproc_elifdef", "preproc_function_def",
180            // Statement types
181            "expression_statement", "return_statement", "break_statement", "continue_statement",
182            "goto_statement", "case_statement", "labeled_statement", "attributed_statement",
183            // Control flow
184            "try_statement", "catch_clause", "throw_statement",
185            // Expression types
186            "binary_expression", "unary_expression", "conditional_expression",
187            "call_expression", "subscript_expression", "cast_expression",
188            "comma_expression", "assignment_expression", "update_expression",
189            "compound_literal_expression", "generic_expression",
190            // ObjC specific expressions
191            "message_expression", "selector_expression", "encode_expression",
192            "at_expression", "available_expression",
193            // Declaration types
194            "declaration", "declaration_list", "field_declaration_list",
195            "property_declaration", "class_declaration", "atomic_declaration",
196            "protocol_forward_declaration", "qualified_protocol_interface_declaration",
197            "compatibility_alias_declaration",
198            // Type system
199            "type_name", "type_identifier", "type_qualifier",
200            "sized_type_specifier", "array_type_specifier", "macro_type_specifier",
201            "typedefed_specifier", "union_specifier", "generic_specifier",
202            // Method-related
203            "method_definition", "method_identifier", "method_type",
204            // Identifiers
205            "field_identifier", "statement_identifier",
206            // Attributes and specifiers
207            "attribute_specifier", "attribute_declaration", "storage_class_specifier",
208            "visibility_specification", "property_attributes_declaration",
209            "protocol_qualifier", "alignas_qualifier", "alignof_expression",
210            "availability_attribute_specifier", "platform",
211            // MS extensions
212            "ms_restrict_modifier", "ms_unaligned_ptr_modifier", "ms_based_modifier",
213            "ms_signed_ptr_modifier", "ms_pointer_modifier", "ms_call_modifier",
214            "ms_declspec_modifier", "ms_unsigned_ptr_modifier", "ms_asm_block",
215            // GNU extensions
216            "gnu_asm_expression", "va_arg_expression", "offsetof_expression",
217            // Other
218            "function_declarator", "enumerator", "enumerator_list", "else_clause",
219            "module_import", "abstract_block_pointer_declarator",
220            // Additional expression types
221            "extension_expression", "pointer_expression", "parenthesized_expression",
222            "sizeof_expression", "range_expression", "field_expression", "block_literal",
223            // Declaration and statements
224            "implementation_definition", "struct_declaration", "field_declaration",
225            "parameter_declaration", "linkage_specification",
226            "do_statement", "synchronized_statement", "finally_clause",
227            // Type-related
228            "typeof_specifier", "type_descriptor", "primitive_type",
229            // Preprocessor
230            "preproc_else", "preproc_ifdef",
231            // Other
232            "method_parameter", "block_pointer_declarator", "abstract_function_declarator",
233            "bitfield_clause", "struct_declarator", "gnu_asm_qualifier",
234            // covered by tags.scm
235            "method_declaration",
236            "while_statement",
237            "for_statement",
238            "switch_statement",
239            "if_statement",
240            "compound_statement",
241        ];
242        validate_unused_kinds_audit(&ObjC, documented_unused)
243            .expect("Objective-C unused node kinds audit failed");
244    }
245}