* 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)
| 539 | * fallback can run). Returns an array (possibly empty) otherwise. |
| 540 | */ |
| 541 | function loadCppIncludeDirsFromCompileDB(projectRoot: string): string[] | null { |
| 542 | const candidates = [ |
| 543 | path.join(projectRoot, 'compile_commands.json'), |
| 544 | path.join(projectRoot, 'build', 'compile_commands.json'), |
| 545 | path.join(projectRoot, 'cmake-build-debug', 'compile_commands.json'), |
| 546 | path.join(projectRoot, 'cmake-build-release', 'compile_commands.json'), |
| 547 | path.join(projectRoot, 'out', 'compile_commands.json'), |
| 548 | ]; |
| 549 | |
| 550 | let dbPath: string | undefined; |
| 551 | for (const c of candidates) { |
| 552 | try { |
| 553 | if (fs.existsSync(c)) { |
| 554 | dbPath = c; |
| 555 | break; |
| 556 | } |
| 557 | } catch { |
| 558 | // ignore |
| 559 | } |
| 560 | } |
| 561 | if (!dbPath) return null; |
| 562 | |
| 563 | try { |
| 564 | const content = fs.readFileSync(dbPath, 'utf-8'); |
| 565 | const entries = JSON.parse(content) as Array<{ |
| 566 | directory: string; |
| 567 | command?: string; |
| 568 | arguments?: string[]; |
| 569 | }>; |
| 570 | if (!Array.isArray(entries)) return null; |
| 571 | |
| 572 | const dirSet = new Set<string>(); |
| 573 | for (const entry of entries) { |
| 574 | const dir = entry.directory || projectRoot; |
| 575 | const args = entry.arguments || (entry.command ? shlexSplit(entry.command) : []); |
| 576 | for (let i = 0; i < args.length; i++) { |
| 577 | const arg = args[i]!; |
| 578 | let includeDir: string | undefined; |
| 579 | // -I<dir> (no space) |
| 580 | if (arg.startsWith('-I') && arg.length > 2) { |
| 581 | includeDir = arg.substring(2); |
| 582 | } |
| 583 | // -isystem <dir> (space-separated) |
| 584 | else if ((arg === '-isystem' || arg === '-I') && i + 1 < args.length) { |
| 585 | includeDir = args[i + 1]; |
| 586 | i++; // skip next arg |
| 587 | } |
| 588 | if (includeDir) { |
| 589 | // Normalize: resolve relative to the compilation directory |
| 590 | const absPath = path.isAbsolute(includeDir) |
| 591 | ? includeDir |
| 592 | : path.resolve(dir, includeDir); |
| 593 | const relPath = path.relative(projectRoot, absPath).replace(/\\/g, '/'); |
| 594 | // Skip system directories and paths outside the project |
| 595 | // (relative paths starting with .. or absolute paths like |
| 596 | // /usr/include or C:\usr on Windows) |
| 597 | if (!relPath.startsWith('..') && relPath.length > 0 && !path.isAbsolute(relPath)) { |
| 598 | dirSet.add(relPath); |
no test coverage detected