* Try to load include directories from compile_commands.json. * Returns null if no compilation database is found (so the heuristic * fallback can run). Returns an array (possibly empty) otherwise.
(projectRoot: string)
| 365 | * fallback can run). Returns an array (possibly empty) otherwise. |
| 366 | */ |
| 367 | function loadCppIncludeDirsFromCompileDB(projectRoot: string): string[] | null { |
| 368 | const candidates = [ |
| 369 | path.join(projectRoot, 'compile_commands.json'), |
| 370 | path.join(projectRoot, 'build', 'compile_commands.json'), |
| 371 | path.join(projectRoot, 'cmake-build-debug', 'compile_commands.json'), |
| 372 | path.join(projectRoot, 'cmake-build-release', 'compile_commands.json'), |
| 373 | path.join(projectRoot, 'out', 'compile_commands.json'), |
| 374 | ]; |
| 375 | |
| 376 | let dbPath: string | undefined; |
| 377 | for (const c of candidates) { |
| 378 | try { |
| 379 | if (fs.existsSync(c)) { |
| 380 | dbPath = c; |
| 381 | break; |
| 382 | } |
| 383 | } catch { |
| 384 | // ignore |
| 385 | } |
| 386 | } |
| 387 | if (!dbPath) return null; |
| 388 | |
| 389 | try { |
| 390 | const content = fs.readFileSync(dbPath, 'utf-8'); |
| 391 | const entries = JSON.parse(content) as Array<{ |
| 392 | directory: string; |
| 393 | command?: string; |
| 394 | arguments?: string[]; |
| 395 | }>; |
| 396 | if (!Array.isArray(entries)) return null; |
| 397 | |
| 398 | const dirSet = new Set<string>(); |
| 399 | for (const entry of entries) { |
| 400 | const dir = entry.directory || projectRoot; |
| 401 | const args = entry.arguments || (entry.command ? shlexSplit(entry.command) : []); |
| 402 | for (let i = 0; i < args.length; i++) { |
| 403 | const arg = args[i]!; |
| 404 | let includeDir: string | undefined; |
| 405 | // -I<dir> (no space) |
| 406 | if (arg.startsWith('-I') && arg.length > 2) { |
| 407 | includeDir = arg.substring(2); |
| 408 | } |
| 409 | // -isystem <dir> (space-separated) |
| 410 | else if ((arg === '-isystem' || arg === '-I') && i + 1 < args.length) { |
| 411 | includeDir = args[i + 1]; |
| 412 | i++; // skip next arg |
| 413 | } |
| 414 | if (includeDir) { |
| 415 | // Normalize: resolve relative to the compilation directory |
| 416 | const absPath = path.isAbsolute(includeDir) |
| 417 | ? includeDir |
| 418 | : path.resolve(dir, includeDir); |
| 419 | const relPath = path.relative(projectRoot, absPath).replace(/\\/g, '/'); |
| 420 | // Skip system directories and paths outside the project |
| 421 | // (relative paths starting with .. or absolute paths like |
| 422 | // /usr/include or C:\usr on Windows) |
| 423 | if (!relPath.startsWith('..') && relPath.length > 0 && !path.isAbsolute(relPath)) { |
| 424 | dirSet.add(relPath); |
no test coverage detected