From 12d72a1f54f349cbf7bc8d591c3acd63b88192ae Mon Sep 17 00:00:00 2001 From: azizur100389 Date: Mon, 15 Jun 2026 21:07:37 +0100 Subject: [PATCH] test(cpp): characterize CUDA parser limitations --- .../group/extractors/include-extractor.ts | 4 +- .../src/core/ingestion/languages/c-cpp.ts | 2 + .../group/include-extractor-sync.test.ts | 35 ++++++++++++++++++ .../integration/tree-sitter-languages.test.ts | 37 +++++++++++++++++++ 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/gitnexus/src/core/group/extractors/include-extractor.ts b/gitnexus/src/core/group/extractors/include-extractor.ts index a4b5bf976..6a181d1d5 100644 --- a/gitnexus/src/core/group/extractors/include-extractor.ts +++ b/gitnexus/src/core/group/extractors/include-extractor.ts @@ -30,7 +30,7 @@ import { logger } from '../../logger.js'; /** * Cross-repo C/C++ `#include` dependency extractor. * - * **Provider side:** registers every `.h/.hpp/.hxx/.hh` file in the repo + * **Provider side:** registers every `.h/.hpp/.hxx/.hh/.cuh` file in the repo * as a provider contract with `include::`. * * **Consumer side:** parses all C/C++ source/header files for `#include "…"` @@ -307,7 +307,7 @@ function getLanguageForFile(filePath: string): unknown | null { function isLocalInclude(cleaned: string, suffixIndex: SuffixIndex): boolean { const candidates = [cleaned]; if (!/\.[a-zA-Z0-9]+$/.test(cleaned)) { - for (const ext of ['.h', '.hpp', '.hxx', '.hh', '.cuh']) candidates.push(cleaned + ext); + for (const ext of HEADER_EXTENSIONS) candidates.push(cleaned + ext); } for (const c of candidates) { if (suffixIndex.get(c) || suffixIndex.getInsensitive(c)) return true; diff --git a/gitnexus/src/core/ingestion/languages/c-cpp.ts b/gitnexus/src/core/ingestion/languages/c-cpp.ts index 024dfa977..f01233348 100644 --- a/gitnexus/src/core/ingestion/languages/c-cpp.ts +++ b/gitnexus/src/core/ingestion/languages/c-cpp.ts @@ -427,6 +427,8 @@ export const cProvider = defineLanguage({ export const cppProvider = defineLanguage({ id: SupportedLanguages.CPlusPlus, + // CUDA files route through tree-sitter-cpp as a conservative C++-subset parser: + // definitions still extract, but CUDA launch syntax (`<<< >>>`) is not modeled as calls. extensions: ['.cpp', '.cc', '.cxx', '.h', '.hpp', '.hxx', '.hh', '.cu', '.cuh'], entryPointPatterns: [ /^main$/, diff --git a/gitnexus/test/integration/group/include-extractor-sync.test.ts b/gitnexus/test/integration/group/include-extractor-sync.test.ts index 908664bdc..86dc9e935 100644 --- a/gitnexus/test/integration/group/include-extractor-sync.test.ts +++ b/gitnexus/test/integration/group/include-extractor-sync.test.ts @@ -192,4 +192,39 @@ describe('IncludeExtractor → syncGroup integration (finding #7)', () => { fs.rmSync(consumerDir, { recursive: true, force: true }); } }); + + it('suppresses extensionless local includes that resolve to .cuh headers', async () => { + const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-include-cuh-')); + try { + fs.mkdirSync(path.join(repoDir, 'include'), { recursive: true }); + fs.mkdirSync(path.join(repoDir, 'src'), { recursive: true }); + fs.writeFileSync( + path.join(repoDir, 'include/kernel.cuh'), + '#pragma once\n__global__ void kernel();', + ); + fs.writeFileSync( + path.join(repoDir, 'src/main.cu'), + '#include "include/kernel"\nvoid host() {}', + ); + + const extractor = new IncludeExtractor(); + const contracts = await extractor.extract(null, repoDir, { + id: 'cuda-repo', + path: 'app/cuda-repo', + repoPath: repoDir, + storagePath: path.join(repoDir, '.gitnexus'), + }); + + expect( + contracts.some( + (c) => c.role === 'provider' && c.contractId === 'include::include/kernel.cuh', + ), + ).toBe(true); + expect( + contracts.some((c) => c.role === 'consumer' && c.contractId === 'include::include/kernel'), + ).toBe(false); + } finally { + fs.rmSync(repoDir, { recursive: true, force: true }); + } + }); }); diff --git a/gitnexus/test/integration/tree-sitter-languages.test.ts b/gitnexus/test/integration/tree-sitter-languages.test.ts index 3fc82b6af..54cb332ca 100644 --- a/gitnexus/test/integration/tree-sitter-languages.test.ts +++ b/gitnexus/test/integration/tree-sitter-languages.test.ts @@ -36,6 +36,16 @@ function extractDefinitions(matches: any[]) { return defs; } +function extractCapturedCallNames(matches: any[]) { + const names: string[] = []; + for (const match of matches) { + if (!match.captures.some((c: any) => c.name === 'call')) continue; + const nameCapture = match.captures.find((c: any) => c.name === 'call.name'); + if (nameCapture) names.push(nameCapture.node.text); + } + return names; +} + describe('Tree-sitter multi-language parsing', () => { let parser: Parser; @@ -303,6 +313,33 @@ describe('Tree-sitter multi-language parsing', () => { expect(names).toContain('launchKernel'); }); + it('characterizes CUDA syntax when routed through the C++ parser', async () => { + await loadLanguage(SupportedLanguages.CPlusPlus, 'src/kernels/force.cu'); + const code = ` + __global__ void axpy(float *x) { x[0] = 1.0f; } + void host() { + axpy<<<1, 32>>>(nullptr); + cudaDeviceSynchronize(); + } + `; + const provider = getProvider(SupportedLanguages.CPlusPlus); + const { tree, matches } = parseAndQuery(parser, code, provider.treeSitterQueries); + const defs = extractDefinitions(matches); + const callNames = extractCapturedCallNames(matches); + const ordinaryCalls = extractCapturedCallNames( + parseAndQuery( + parser, + 'void host() { cudaDeviceSynchronize(); }', + provider.treeSitterQueries, + ).matches, + ); + + expect(tree.rootNode.hasError).toBe(true); + expect(defs.some((d) => d.name === 'axpy')).toBe(true); + expect(ordinaryCalls).toContain('cudaDeviceSynchronize'); + expect(callNames).not.toContain('axpy'); + }); + it('captures C++ typedef anonymous structs, enums, and enumerators', async () => { await loadLanguage(SupportedLanguages.CPlusPlus); const code = `