1use crate::c_cpp;
4use crate::external_packages::ResolvedPackage;
5use crate::{Export, Import, Language, Symbol, SymbolKind, Visibility, VisibilityMechanism};
6use std::path::{Path, PathBuf};
7use tree_sitter::Node;
8
9pub struct C;
11
12impl Language for C {
13 fn name(&self) -> &'static str {
14 "C"
15 }
16 fn extensions(&self) -> &'static [&'static str] {
17 &["c", "h"]
18 }
19 fn grammar_name(&self) -> &'static str {
20 "c"
21 }
22
23 fn has_symbols(&self) -> bool {
24 true
25 }
26
27 fn container_kinds(&self) -> &'static [&'static str] {
28 &[]
29 } fn function_kinds(&self) -> &'static [&'static str] {
31 &["function_definition"]
32 }
33 fn type_kinds(&self) -> &'static [&'static str] {
34 &["struct_specifier", "enum_specifier", "type_definition"]
35 }
36 fn import_kinds(&self) -> &'static [&'static str] {
37 &["preproc_include"]
38 }
39
40 fn public_symbol_kinds(&self) -> &'static [&'static str] {
41 &["function_definition"]
42 }
43
44 fn visibility_mechanism(&self) -> VisibilityMechanism {
45 VisibilityMechanism::HeaderBased
46 }
47 fn scope_creating_kinds(&self) -> &'static [&'static str] {
48 &["for_statement", "while_statement", "compound_statement"]
49 }
50
51 fn control_flow_kinds(&self) -> &'static [&'static str] {
52 &[
53 "if_statement",
54 "for_statement",
55 "while_statement",
56 "do_statement",
57 "switch_statement",
58 "return_statement",
59 "break_statement",
60 "continue_statement",
61 "goto_statement",
62 ]
63 }
64
65 fn complexity_nodes(&self) -> &'static [&'static str] {
66 &[
67 "if_statement",
68 "for_statement",
69 "while_statement",
70 "do_statement",
71 "switch_statement",
72 "case_statement",
73 "&&",
74 "||",
75 "conditional_expression",
76 ]
77 }
78
79 fn nesting_nodes(&self) -> &'static [&'static str] {
80 &[
81 "if_statement",
82 "for_statement",
83 "while_statement",
84 "do_statement",
85 "switch_statement",
86 "function_definition",
87 ]
88 }
89
90 fn signature_suffix(&self) -> &'static str {
91 " {}"
92 }
93
94 fn extract_function(&self, node: &Node, content: &str, _in_container: bool) -> Option<Symbol> {
95 let declarator = node.child_by_field_name("declarator")?;
96 let name = self.find_identifier(&declarator, content)?;
97
98 Some(Symbol {
99 name: name.to_string(),
100 kind: SymbolKind::Function,
101 signature: name.to_string(),
102 docstring: None,
103 attributes: Vec::new(),
104 start_line: node.start_position().row + 1,
105 end_line: node.end_position().row + 1,
106 visibility: Visibility::Public,
107 children: Vec::new(),
108 is_interface_impl: false,
109 implements: Vec::new(),
110 })
111 }
112
113 fn extract_container(&self, _node: &Node, _content: &str) -> Option<Symbol> {
114 None }
116
117 fn extract_type(&self, node: &Node, content: &str) -> Option<Symbol> {
118 let name = self.node_name(node, content)?;
119 let kind = match node.kind() {
120 "struct_specifier" => SymbolKind::Struct,
121 "enum_specifier" => SymbolKind::Enum,
122 _ => SymbolKind::Type,
123 };
124
125 Some(Symbol {
126 name: name.to_string(),
127 kind,
128 signature: format!("{} {}", kind.as_str(), name),
129 docstring: None,
130 attributes: Vec::new(),
131 start_line: node.start_position().row + 1,
132 end_line: node.end_position().row + 1,
133 visibility: Visibility::Public,
134 children: Vec::new(),
135 is_interface_impl: false,
136 implements: Vec::new(),
137 })
138 }
139
140 fn extract_docstring(&self, _node: &Node, _content: &str) -> Option<String> {
141 None
142 }
143
144 fn extract_attributes(&self, _node: &Node, _content: &str) -> Vec<String> {
145 Vec::new()
146 }
147
148 fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
149 if node.kind() != "preproc_include" {
150 return Vec::new();
151 }
152
153 let line = node.start_position().row + 1;
154 let mut cursor = node.walk();
155 for child in node.children(&mut cursor) {
156 if child.kind() == "string_literal" || child.kind() == "system_lib_string" {
157 let text = &content[child.byte_range()];
158 let module = text
159 .trim_matches(|c| c == '"' || c == '<' || c == '>')
160 .to_string();
161 let is_relative = text.starts_with('"');
162 return vec![Import {
163 module,
164 names: Vec::new(),
165 alias: None,
166 is_wildcard: false,
167 is_relative,
168 line,
169 }];
170 }
171 }
172 Vec::new()
173 }
174
175 fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
176 if import.module.starts_with('<') || import.module.ends_with('>') {
178 format!("#include {}", import.module)
179 } else {
180 format!("#include \"{}\"", import.module)
181 }
182 }
183
184 fn extract_public_symbols(&self, node: &Node, content: &str) -> Vec<Export> {
185 if node.kind() != "function_definition" {
186 return Vec::new();
187 }
188
189 if let Some(name) = self.node_name(node, content) {
190 vec![Export {
191 name: name.to_string(),
192 kind: SymbolKind::Function,
193 line: node.start_position().row + 1,
194 }]
195 } else {
196 Vec::new()
197 }
198 }
199
200 fn is_public(&self, _node: &Node, _content: &str) -> bool {
201 true }
203
204 fn get_visibility(&self, _node: &Node, _content: &str) -> Visibility {
205 Visibility::Public
206 }
207
208 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
209 let name = symbol.name.as_str();
210 match symbol.kind {
211 crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
212 crate::SymbolKind::Module => name == "tests" || name == "test",
213 _ => false,
214 }
215 }
216
217 fn embedded_content(&self, _node: &Node, _content: &str) -> Option<crate::EmbeddedBlock> {
218 None
219 }
220
221 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
222 node.child_by_field_name("body")
223 }
224
225 fn body_has_docstring(&self, _body: &Node, _content: &str) -> bool {
226 false
227 }
228
229 fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
230 if let Some(name_node) = node.child_by_field_name("name") {
232 return Some(&content[name_node.byte_range()]);
233 }
234 if let Some(declarator) = node.child_by_field_name("declarator") {
236 return self.find_identifier(&declarator, content);
237 }
238 None
239 }
240
241 fn file_path_to_module_name(&self, path: &Path) -> Option<String> {
242 let ext = path.extension()?.to_str()?;
243 if !["c", "h"].contains(&ext) {
244 return None;
245 }
246 Some(path.to_string_lossy().to_string())
247 }
248
249 fn module_name_to_paths(&self, module: &str) -> Vec<String> {
250 vec![module.to_string()]
251 }
252
253 fn is_stdlib_import(&self, include: &str, _project_root: &Path) -> bool {
254 let stdlib = [
256 "stdio.h", "stdlib.h", "string.h", "math.h", "time.h", "ctype.h", "errno.h", "float.h",
257 "limits.h", "locale.h", "setjmp.h", "signal.h", "stdarg.h", "stddef.h", "assert.h",
258 ];
259 stdlib.contains(&include)
260 }
261
262 fn find_package_cache(&self, _project_root: &Path) -> Option<PathBuf> {
263 None }
265
266 fn find_stdlib(&self, _project_root: &Path) -> Option<PathBuf> {
267 c_cpp::find_cpp_include_paths().into_iter().next()
269 }
270
271 fn package_sources(&self, _project_root: &Path) -> Vec<crate::PackageSource> {
272 use crate::{PackageSource, PackageSourceKind};
273 c_cpp::find_cpp_include_paths()
274 .into_iter()
275 .map(|path| PackageSource {
276 name: "includes",
277 path,
278 kind: PackageSourceKind::Recursive,
279 version_specific: false,
280 })
281 .collect()
282 }
283
284 fn package_module_name(&self, entry_name: &str) -> String {
285 entry_name.to_string()
286 }
287
288 fn discover_packages(&self, source: &crate::PackageSource) -> Vec<(String, PathBuf)> {
289 self.discover_recursive_packages(&source.path, &source.path)
290 }
291
292 fn find_package_entry(&self, path: &Path) -> Option<PathBuf> {
293 if path.is_file() {
294 Some(path.to_path_buf())
295 } else {
296 None
297 }
298 }
299
300 fn lang_key(&self) -> &'static str {
303 "c"
304 }
305
306 fn resolve_local_import(
307 &self,
308 include: &str,
309 current_file: &Path,
310 _project_root: &Path,
311 ) -> Option<PathBuf> {
312 let header = include
314 .trim_start_matches('"')
315 .trim_end_matches('"')
316 .trim_start_matches('<')
317 .trim_end_matches('>');
318
319 let current_dir = current_file.parent()?;
320
321 let relative = current_dir.join(header);
323 if relative.is_file() {
324 return Some(relative);
325 }
326
327 if !header.contains('.') {
329 for ext in &[".h", ".c"] {
330 let with_ext = current_dir.join(format!("{}{}", header, ext));
331 if with_ext.is_file() {
332 return Some(with_ext);
333 }
334 }
335 }
336
337 None
338 }
339
340 fn resolve_external_import(
341 &self,
342 include: &str,
343 _project_root: &Path,
344 ) -> Option<ResolvedPackage> {
345 let include_paths = c_cpp::find_cpp_include_paths();
346 c_cpp::resolve_cpp_include(include, &include_paths)
347 }
348
349 fn get_version(&self, _project_root: &Path) -> Option<String> {
350 c_cpp::get_gcc_version()
351 }
352
353 fn indexable_extensions(&self) -> &'static [&'static str] {
354 &["c", "h"]
355 }
356
357 fn should_skip_package_entry(&self, name: &str, is_dir: bool) -> bool {
358 use crate::traits::{has_extension, skip_dotfiles};
359 if skip_dotfiles(name) {
360 return true;
361 }
362 !is_dir && !has_extension(name, self.indexable_extensions())
363 }
364}
365
366impl C {
367 fn find_identifier<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
368 if node.kind() == "identifier" {
369 return Some(&content[node.byte_range()]);
370 }
371 let mut cursor = node.walk();
372 for child in node.children(&mut cursor) {
373 if let Some(id) = self.find_identifier(&child, content) {
374 return Some(id);
375 }
376 }
377 None
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384 use crate::validate_unused_kinds_audit;
385
386 #[test]
389 fn unused_node_kinds_audit() {
390 #[rustfmt::skip]
391 let documented_unused: &[&str] = &[
392 "bitfield_clause", "declaration", "declaration_list", "enumerator", "enumerator_list", "field_declaration", "field_declaration_list", "field_expression", "field_identifier", "identifier", "linkage_specification", "parameter_declaration", "primitive_type", "sized_type_specifier", "statement_identifier", "storage_class_specifier", "type_descriptor", "type_identifier", "type_qualifier", "union_specifier", "else_clause", "alignof_expression", "assignment_expression", "binary_expression", "call_expression", "cast_expression", "comma_expression", "compound_literal_expression", "extension_expression", "generic_expression", "gnu_asm_expression", "offsetof_expression", "parenthesized_expression","pointer_expression", "sizeof_expression", "subscript_expression", "unary_expression", "update_expression", "abstract_function_declarator", "function_declarator", "preproc_elif", "preproc_elifdef", "preproc_else", "preproc_function_def", "preproc_if", "preproc_ifdef", "alignas_qualifier", "attribute_declaration", "attribute_specifier", "attributed_statement", "expression_statement", "gnu_asm_qualifier", "labeled_statement", "macro_type_specifier", "ms_based_modifier", "ms_call_modifier", "ms_declspec_modifier", "ms_pointer_modifier", "ms_restrict_modifier", "ms_signed_ptr_modifier", "ms_unaligned_ptr_modifier", "ms_unsigned_ptr_modifier", "seh_except_clause", "seh_finally_clause", "seh_leave_statement", "seh_try_statement", ];
474
475 validate_unused_kinds_audit(&C, documented_unused)
476 .expect("C unused node kinds audit failed");
477 }
478}