/** * Unit tests for C++ arity compatibility and metadata. */ import { describe, it, expect } from 'vitest'; import { cppArityCompatibility } from '../../../../src/core/ingestion/languages/cpp/arity.js'; import { computeCppDeclarationArity, computeCppCallArity, classifyCppParameterType, } from '../../../../src/core/ingestion/languages/cpp/arity-metadata.js'; import { getCppParser } from '../../../../src/core/ingestion/languages/cpp/query.js'; import type { SyntaxNode } from '../../../../src/core/ingestion/utils/ast-helpers.js'; import type { SymbolDefinition, Callsite } from 'gitnexus-shared'; function parseFuncDef(src: string): SyntaxNode | null { const tree = getCppParser().parse(src); for (let i = 0; i < tree.rootNode.namedChildCount; i++) { const child = tree.rootNode.namedChild(i); if (child?.type === 'function_definition') return child as SyntaxNode; } return null; } function parseCallExpr(src: string): SyntaxNode | null { const tree = getCppParser().parse(src); const walk = (node: SyntaxNode): SyntaxNode | null => { if (node.type === 'call_expression') return node; for (let i = 0; i < node.namedChildCount; i++) { const found = walk(node.namedChild(i) as SyntaxNode); if (found) return found; } return null; }; return walk(tree.rootNode as SyntaxNode); } function mkDef(overrides: Partial = {}): SymbolDefinition { return { nodeId: 'test-def', qualifiedName: 'test', filePath: 'test.cpp', type: 'Function', ...overrides, } as SymbolDefinition; } function mkCallsite(arity: number): Callsite { return { arity } as Callsite; } // ── Declaration arity ─────────────────────────────────────────────────────── describe('computeCppDeclarationArity', () => { it('computes arity for zero-parameter function', () => { const node = parseFuncDef('void foo() {}'); expect(node).not.toBeNull(); const arity = computeCppDeclarationArity(node!); expect(arity.parameterCount).toBe(0); expect(arity.requiredParameterCount).toBe(0); }); it('computes arity for (void) parameter', () => { const node = parseFuncDef('void foo(void) {}'); expect(node).not.toBeNull(); const arity = computeCppDeclarationArity(node!); expect(arity.parameterCount).toBe(0); expect(arity.requiredParameterCount).toBe(0); }); it('computes arity for multiple parameters', () => { const node = parseFuncDef('void foo(int x, int y, int z) {}'); expect(node).not.toBeNull(); const arity = computeCppDeclarationArity(node!); expect(arity.parameterCount).toBe(3); expect(arity.requiredParameterCount).toBe(3); }); it('computes arity with default parameters', () => { const node = parseFuncDef('void foo(int x, int y = 5, int z = 10) {}'); expect(node).not.toBeNull(); const arity = computeCppDeclarationArity(node!); expect(arity.parameterCount).toBe(3); expect(arity.requiredParameterCount).toBe(1); }); it('detects variadic function', () => { const node = parseFuncDef('void foo(int x, ...) {}'); expect(node).not.toBeNull(); const arity = computeCppDeclarationArity(node!); expect(arity.parameterCount).toBeUndefined(); // variadic → undefined max expect(arity.requiredParameterCount).toBe(1); expect(arity.parameterTypes).toContain('...'); }); it('handles pointer return type', () => { const node = parseFuncDef('int* create(int size) {}'); expect(node).not.toBeNull(); const arity = computeCppDeclarationArity(node!); expect(arity.parameterCount).toBe(1); }); it('keeps coarse parameterTypes stable while preserving pointer/reference sidecar classes', () => { const node = parseFuncDef('void f(int value, const int* ptr, int& ref, int&& move) {}'); expect(node).not.toBeNull(); const arity = computeCppDeclarationArity(node!); expect(arity.parameterTypes).toEqual(['int', 'int', 'int', 'int']); expect(arity.parameterTypeClasses).toEqual([ { base: 'int', cv: 'none', indirection: 'value', pointerDepth: 0 }, { base: 'int', cv: 'const', indirection: 'pointer', pointerDepth: 1 }, { base: 'int', cv: 'none', indirection: 'lvalue-ref', pointerDepth: 0 }, { base: 'int', cv: 'none', indirection: 'rvalue-ref', pointerDepth: 0 }, ]); }); it('classifies int, int*, and int& as distinct sidecar shapes for future is_same_v consumers', () => { expect(classifyCppParameterType('int')).toEqual({ base: 'int', cv: 'none', indirection: 'value', pointerDepth: 0, }); expect(classifyCppParameterType('int', '* p')).toEqual({ base: 'int', cv: 'none', indirection: 'pointer', pointerDepth: 1, }); expect(classifyCppParameterType('int', '& r')).toEqual({ base: 'int', cv: 'none', indirection: 'lvalue-ref', pointerDepth: 0, }); }); }); // ── Call-site arity ───────────────────────────────────────────────────────── describe('computeCppCallArity', () => { it('computes arity for no-argument call', () => { const node = parseCallExpr('void f() { foo(); }'); expect(node).not.toBeNull(); expect(computeCppCallArity(node!)).toBe(0); }); it('computes arity for multi-argument call', () => { const node = parseCallExpr('void f() { foo(1, 2, 3); }'); expect(node).not.toBeNull(); expect(computeCppCallArity(node!)).toBe(3); }); it('computes arity for single-argument call', () => { const node = parseCallExpr('void f() { foo(42); }'); expect(node).not.toBeNull(); expect(computeCppCallArity(node!)).toBe(1); }); }); // ── Arity compatibility ───────────────────────────────────────────────────── describe('cppArityCompatibility', () => { it('returns compatible for exact match', () => { const def = mkDef({ parameterCount: 2, requiredParameterCount: 2 }); expect(cppArityCompatibility(def, mkCallsite(2))).toBe('compatible'); }); it('returns compatible when call uses default params', () => { const def = mkDef({ parameterCount: 3, requiredParameterCount: 1 }); expect(cppArityCompatibility(def, mkCallsite(1))).toBe('compatible'); expect(cppArityCompatibility(def, mkCallsite(2))).toBe('compatible'); expect(cppArityCompatibility(def, mkCallsite(3))).toBe('compatible'); }); it('returns incompatible for too few args', () => { const def = mkDef({ parameterCount: 3, requiredParameterCount: 2 }); expect(cppArityCompatibility(def, mkCallsite(1))).toBe('incompatible'); }); it('returns incompatible for too many args (non-variadic)', () => { const def = mkDef({ parameterCount: 2, requiredParameterCount: 2 }); expect(cppArityCompatibility(def, mkCallsite(5))).toBe('incompatible'); }); it('returns compatible for variadic with extra args', () => { const def = mkDef({ parameterCount: undefined, requiredParameterCount: 1, parameterTypes: ['int', '...'], }); expect(cppArityCompatibility(def, mkCallsite(5))).toBe('compatible'); }); it('returns unknown when no metadata', () => { const def = mkDef({}); expect(cppArityCompatibility(def, mkCallsite(2))).toBe('unknown'); }); it('returns unknown for negative arity', () => { const def = mkDef({ parameterCount: 2, requiredParameterCount: 2 }); expect(cppArityCompatibility(def, mkCallsite(-1))).toBe('unknown'); }); });