Skip to main content

normalize_languages/
objc.rs

1//! Objective-C language support.
2
3use crate::external_packages::ResolvedPackage;
4use crate::{Export, Import, Language, Symbol, SymbolKind, Visibility, VisibilityMechanism};
5use std::path::{Path, PathBuf};
6use tree_sitter::Node;
7
8/// Objective-C language support.
9pub struct ObjC;
10
11impl Language for ObjC {
12    fn name(&self) -> &'static str {
13        "Objective-C"
14    }
15    fn extensions(&self) -> &'static [&'static str] {
16        &["m", "mm"]
17    }
18    fn grammar_name(&self) -> &'static str {
19        "objc"
20    }
21
22    fn has_symbols(&self) -> bool {
23        true
24    }
25
26    fn container_kinds(&self) -> &'static [&'static str] {
27        &[
28            "class_interface",
29            "class_implementation",
30            "protocol_declaration",
31        ]
32    }
33
34    fn function_kinds(&self) -> &'static [&'static str] {
35        &["method_declaration", "function_definition"]
36    }
37
38    fn type_kinds(&self) -> &'static [&'static str] {
39        &["struct_specifier", "enum_specifier", "type_definition"]
40    }
41
42    fn import_kinds(&self) -> &'static [&'static str] {
43        &["preproc_include"]
44    }
45
46    fn public_symbol_kinds(&self) -> &'static [&'static str] {
47        &[
48            "class_interface",
49            "protocol_declaration",
50            "method_declaration",
51        ]
52    }
53
54    fn visibility_mechanism(&self) -> VisibilityMechanism {
55        VisibilityMechanism::AllPublic
56    }
57
58    fn extract_public_symbols(&self, node: &Node, content: &str) -> Vec<Export> {
59        match node.kind() {
60            "class_interface" | "class_implementation" | "protocol_declaration" => {
61                if let Some(name) = self.node_name(node, content) {
62                    return vec![Export {
63                        name: name.to_string(),
64                        kind: SymbolKind::Class,
65                        line: node.start_position().row + 1,
66                    }];
67                }
68            }
69            "method_declaration" => {
70                if let Some(name) = self.node_name(node, content) {
71                    return vec![Export {
72                        name: name.to_string(),
73                        kind: SymbolKind::Function,
74                        line: node.start_position().row + 1,
75                    }];
76                }
77            }
78            _ => {}
79        }
80        Vec::new()
81    }
82
83    fn scope_creating_kinds(&self) -> &'static [&'static str] {
84        &[
85            "class_implementation",
86            "method_declaration",
87            "function_definition",
88            "compound_statement",
89        ]
90    }
91
92    fn control_flow_kinds(&self) -> &'static [&'static str] {
93        &[
94            "if_statement",
95            "switch_statement",
96            "while_statement",
97            "for_statement",
98        ]
99    }
100
101    fn complexity_nodes(&self) -> &'static [&'static str] {
102        &[
103            "if_statement",
104            "switch_statement",
105            "while_statement",
106            "for_statement",
107        ]
108    }
109
110    fn nesting_nodes(&self) -> &'static [&'static str] {
111        &[
112            "if_statement",
113            "switch_statement",
114            "while_statement",
115            "for_statement",
116            "compound_statement",
117        ]
118    }
119
120    fn signature_suffix(&self) -> &'static str {
121        ""
122    }
123
124    fn extract_function(&self, node: &Node, content: &str, _in_container: bool) -> Option<Symbol> {
125        match node.kind() {
126            "method_declaration" | "function_definition" => {
127                let name = self.node_name(node, content)?;
128                let text = &content[node.byte_range()];
129                let first_line = text.lines().next().unwrap_or(text);
130
131                Some(Symbol {
132                    name: name.to_string(),
133                    kind: SymbolKind::Function,
134                    signature: first_line.trim().to_string(),
135                    docstring: None,
136                    attributes: Vec::new(),
137                    start_line: node.start_position().row + 1,
138                    end_line: node.end_position().row + 1,
139                    visibility: Visibility::Public,
140                    children: Vec::new(),
141                    is_interface_impl: false,
142                    implements: Vec::new(),
143                })
144            }
145            _ => None,
146        }
147    }
148
149    fn extract_container(&self, node: &Node, content: &str) -> Option<Symbol> {
150        match node.kind() {
151            "class_interface" | "class_implementation" | "protocol_declaration" => {
152                let name = self.node_name(node, content)?;
153                let text = &content[node.byte_range()];
154                let first_line = text.lines().next().unwrap_or(text);
155
156                Some(Symbol {
157                    name: name.to_string(),
158                    kind: SymbolKind::Class,
159                    signature: first_line.trim().to_string(),
160                    docstring: None,
161                    attributes: Vec::new(),
162                    start_line: node.start_position().row + 1,
163                    end_line: node.end_position().row + 1,
164                    visibility: Visibility::Public,
165                    children: Vec::new(),
166                    is_interface_impl: false,
167                    implements: Vec::new(),
168                })
169            }
170            _ => None,
171        }
172    }
173
174    fn extract_type(&self, node: &Node, content: &str) -> Option<Symbol> {
175        match node.kind() {
176            "struct_specifier" | "enum_specifier" | "type_definition" => {
177                let name = self.node_name(node, content)?;
178                let text = &content[node.byte_range()];
179                let first_line = text.lines().next().unwrap_or(text);
180
181                Some(Symbol {
182                    name: name.to_string(),
183                    kind: SymbolKind::Type,
184                    signature: first_line.trim().to_string(),
185                    docstring: None,
186                    attributes: Vec::new(),
187                    start_line: node.start_position().row + 1,
188                    end_line: node.end_position().row + 1,
189                    visibility: Visibility::Public,
190                    children: Vec::new(),
191                    is_interface_impl: false,
192                    implements: Vec::new(),
193                })
194            }
195            _ => None,
196        }
197    }
198
199    fn extract_docstring(&self, _node: &Node, _content: &str) -> Option<String> {
200        None
201    }
202
203    fn extract_attributes(&self, _node: &Node, _content: &str) -> Vec<String> {
204        Vec::new()
205    }
206
207    fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
208        match node.kind() {
209            "preproc_include" => {
210                let text = &content[node.byte_range()];
211                vec![Import {
212                    module: text.trim().to_string(),
213                    names: Vec::new(),
214                    alias: None,
215                    is_wildcard: false,
216                    is_relative: text.contains('"'),
217                    line: node.start_position().row + 1,
218                }]
219            }
220            _ => Vec::new(),
221        }
222    }
223
224    fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
225        // Objective-C: #import <Header.h> or #import "header.h"
226        if import.is_relative {
227            format!("#import \"{}\"", import.module)
228        } else {
229            format!("#import <{}>", import.module)
230        }
231    }
232
233    fn is_public(&self, _node: &Node, _content: &str) -> bool {
234        true
235    }
236    fn get_visibility(&self, _node: &Node, _content: &str) -> Visibility {
237        Visibility::Public
238    }
239
240    fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
241        let name = symbol.name.as_str();
242        match symbol.kind {
243            crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
244            crate::SymbolKind::Module => name == "tests" || name == "test",
245            _ => false,
246        }
247    }
248
249    fn embedded_content(&self, _node: &Node, _content: &str) -> Option<crate::EmbeddedBlock> {
250        None
251    }
252
253    fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
254        node.child_by_field_name("body")
255    }
256
257    fn body_has_docstring(&self, _body: &Node, _content: &str) -> bool {
258        false
259    }
260
261    fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
262        node.child_by_field_name("name")
263            .or_else(|| node.child_by_field_name("declarator"))
264            .map(|n| &content[n.byte_range()])
265    }
266
267    fn file_path_to_module_name(&self, path: &Path) -> Option<String> {
268        let ext = path.extension()?.to_str()?;
269        if !["m", "mm"].contains(&ext) {
270            return None;
271        }
272        let stem = path.file_stem()?.to_str()?;
273        Some(stem.to_string())
274    }
275
276    fn module_name_to_paths(&self, module: &str) -> Vec<String> {
277        vec![
278            format!("{}.m", module),
279            format!("{}.mm", module),
280            format!("{}.h", module),
281        ]
282    }
283
284    fn lang_key(&self) -> &'static str {
285        "objc"
286    }
287
288    fn is_stdlib_import(&self, import_name: &str, _project_root: &Path) -> bool {
289        import_name.starts_with("<Foundation/")
290            || import_name.starts_with("<UIKit/")
291            || import_name.starts_with("<AppKit/")
292    }
293
294    fn find_stdlib(&self, _project_root: &Path) -> Option<PathBuf> {
295        None
296    }
297    fn resolve_local_import(&self, _: &str, _: &Path, _: &Path) -> Option<PathBuf> {
298        None
299    }
300    fn resolve_external_import(&self, _: &str, _: &Path) -> Option<ResolvedPackage> {
301        None
302    }
303    fn get_version(&self, _: &Path) -> Option<String> {
304        None
305    }
306    fn find_package_cache(&self, _: &Path) -> Option<PathBuf> {
307        None
308    }
309    fn indexable_extensions(&self) -> &'static [&'static str] {
310        &["m", "mm", "h"]
311    }
312    fn package_sources(&self, _: &Path) -> Vec<crate::PackageSource> {
313        Vec::new()
314    }
315
316    fn should_skip_package_entry(&self, name: &str, is_dir: bool) -> bool {
317        use crate::traits::{has_extension, skip_dotfiles};
318        if skip_dotfiles(name) {
319            return true;
320        }
321        !is_dir && !has_extension(name, self.indexable_extensions())
322    }
323
324    fn discover_packages(&self, _: &crate::PackageSource) -> Vec<(String, PathBuf)> {
325        Vec::new()
326    }
327
328    fn package_module_name(&self, entry_name: &str) -> String {
329        entry_name
330            .strip_suffix(".m")
331            .or_else(|| entry_name.strip_suffix(".mm"))
332            .or_else(|| entry_name.strip_suffix(".h"))
333            .unwrap_or(entry_name)
334            .to_string()
335    }
336
337    fn find_package_entry(&self, path: &Path) -> Option<PathBuf> {
338        if path.is_file() {
339            Some(path.to_path_buf())
340        } else {
341            None
342        }
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349    use crate::validate_unused_kinds_audit;
350
351    #[test]
352    fn unused_node_kinds_audit() {
353        #[rustfmt::skip]
354        let documented_unused: &[&str] = &[
355            // Preprocessor
356            "preproc_if", "preproc_elif", "preproc_elifdef", "preproc_function_def",
357            // Statement types
358            "expression_statement", "return_statement", "break_statement", "continue_statement",
359            "goto_statement", "case_statement", "labeled_statement", "attributed_statement",
360            // Control flow
361            "try_statement", "catch_clause", "throw_statement",
362            // Expression types
363            "binary_expression", "unary_expression", "conditional_expression",
364            "call_expression", "subscript_expression", "cast_expression",
365            "comma_expression", "assignment_expression", "update_expression",
366            "compound_literal_expression", "generic_expression",
367            // ObjC specific expressions
368            "message_expression", "selector_expression", "encode_expression",
369            "at_expression", "available_expression",
370            // Declaration types
371            "declaration", "declaration_list", "field_declaration_list",
372            "property_declaration", "class_declaration", "atomic_declaration",
373            "protocol_forward_declaration", "qualified_protocol_interface_declaration",
374            "compatibility_alias_declaration",
375            // Type system
376            "type_name", "type_identifier", "type_qualifier",
377            "sized_type_specifier", "array_type_specifier", "macro_type_specifier",
378            "typedefed_specifier", "union_specifier", "generic_specifier",
379            // Method-related
380            "method_definition", "method_identifier", "method_type",
381            // Identifiers
382            "field_identifier", "statement_identifier",
383            // Attributes and specifiers
384            "attribute_specifier", "attribute_declaration", "storage_class_specifier",
385            "visibility_specification", "property_attributes_declaration",
386            "protocol_qualifier", "alignas_qualifier", "alignof_expression",
387            "availability_attribute_specifier", "platform",
388            // MS extensions
389            "ms_restrict_modifier", "ms_unaligned_ptr_modifier", "ms_based_modifier",
390            "ms_signed_ptr_modifier", "ms_pointer_modifier", "ms_call_modifier",
391            "ms_declspec_modifier", "ms_unsigned_ptr_modifier", "ms_asm_block",
392            // GNU extensions
393            "gnu_asm_expression", "va_arg_expression", "offsetof_expression",
394            // Other
395            "function_declarator", "enumerator", "enumerator_list", "else_clause",
396            "module_import", "abstract_block_pointer_declarator",
397            // Additional expression types
398            "extension_expression", "pointer_expression", "parenthesized_expression",
399            "sizeof_expression", "range_expression", "field_expression", "block_literal",
400            // Declaration and statements
401            "implementation_definition", "struct_declaration", "field_declaration",
402            "parameter_declaration", "linkage_specification",
403            "do_statement", "synchronized_statement", "finally_clause",
404            // Type-related
405            "typeof_specifier", "type_descriptor", "primitive_type",
406            // Preprocessor
407            "preproc_else", "preproc_ifdef",
408            // Other
409            "method_parameter", "block_pointer_declarator", "abstract_function_declarator",
410            "bitfield_clause", "identifier", "struct_declarator", "gnu_asm_qualifier",
411        ];
412        validate_unused_kinds_audit(&ObjC, documented_unused)
413            .expect("Objective-C unused node kinds audit failed");
414    }
415}