@@ -122,6 +122,7 @@ pub fn detect_languages(root: &Path) -> Vec<Language> {
122122 . workspace_markers ( )
123123 . iter ( )
124124 . any ( |m| root. join ( m) . exists ( ) )
125+ || has_c_files ( root)
125126 {
126127 languages. push ( Language :: Cpp ) ;
127128 }
@@ -178,6 +179,35 @@ fn has_ts_files(root: &Path) -> bool {
178179 false
179180}
180181
182+ /// Returns true if the project root (or its `src/` subdirectory) contains C/C++ source files.
183+ /// Handles Makefile-based and legacy C/C++ projects that lack `CMakeLists.txt` or
184+ /// `compile_commands.json`.
185+ fn has_c_files ( root : & Path ) -> bool {
186+ // C source extensions (not headers — headers alone don't indicate a buildable project)
187+ const C_SRC_EXTS : & [ & str ] = & [ "c" , "cpp" , "cc" , "cxx" ] ;
188+
189+ let mut dirs = vec ! [ root. to_path_buf( ) ] ;
190+ let src = root. join ( "src" ) ;
191+ if src. is_dir ( ) {
192+ dirs. push ( src) ;
193+ }
194+
195+ for dir in & dirs {
196+ let Ok ( entries) = std:: fs:: read_dir ( dir) else {
197+ continue ;
198+ } ;
199+ if entries. filter_map ( Result :: ok) . any ( |e| {
200+ e. path ( )
201+ . extension ( )
202+ . and_then ( |x| x. to_str ( ) )
203+ . is_some_and ( |x| C_SRC_EXTS . contains ( & x) )
204+ } ) {
205+ return true ;
206+ }
207+ }
208+ false
209+ }
210+
181211#[ cfg( test) ]
182212mod tests {
183213 use super :: * ;
@@ -269,4 +299,52 @@ mod tests {
269299 let langs = detect_languages ( dir. path ( ) ) ;
270300 assert ! ( langs. is_empty( ) ) ;
271301 }
302+
303+ #[ test]
304+ fn detects_cpp_from_cmake ( ) {
305+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
306+ std:: fs:: write ( dir. path ( ) . join ( "CMakeLists.txt" ) , "" ) . unwrap ( ) ;
307+
308+ let langs = detect_languages ( dir. path ( ) ) ;
309+ assert_eq ! ( langs, vec![ Language :: Cpp ] ) ;
310+ }
311+
312+ #[ test]
313+ fn detects_c_project_from_root_c_files ( ) {
314+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
315+ std:: fs:: write ( dir. path ( ) . join ( "main.c" ) , "int main() {}" ) . unwrap ( ) ;
316+
317+ let langs = detect_languages ( dir. path ( ) ) ;
318+ assert_eq ! ( langs, vec![ Language :: Cpp ] ) ;
319+ }
320+
321+ #[ test]
322+ fn detects_c_project_from_src_c_files ( ) {
323+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
324+ std:: fs:: create_dir ( dir. path ( ) . join ( "src" ) ) . unwrap ( ) ;
325+ std:: fs:: write ( dir. path ( ) . join ( "src/app.c" ) , "" ) . unwrap ( ) ;
326+
327+ let langs = detect_languages ( dir. path ( ) ) ;
328+ assert_eq ! ( langs, vec![ Language :: Cpp ] ) ;
329+ }
330+
331+ #[ test]
332+ fn detects_cpp_project_from_src_cpp_files ( ) {
333+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
334+ std:: fs:: create_dir ( dir. path ( ) . join ( "src" ) ) . unwrap ( ) ;
335+ std:: fs:: write ( dir. path ( ) . join ( "src/main.cpp" ) , "" ) . unwrap ( ) ;
336+
337+ let langs = detect_languages ( dir. path ( ) ) ;
338+ assert_eq ! ( langs, vec![ Language :: Cpp ] ) ;
339+ }
340+
341+ #[ test]
342+ fn headers_only_not_detected_as_c ( ) {
343+ // .h files alone shouldn't trigger C detection (could be headers for another language)
344+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
345+ std:: fs:: write ( dir. path ( ) . join ( "config.h" ) , "" ) . unwrap ( ) ;
346+
347+ let langs = detect_languages ( dir. path ( ) ) ;
348+ assert ! ( langs. is_empty( ) ) ;
349+ }
272350}
0 commit comments