GitNexus/gitnexus/test/unit/scope-resolution/cpp/cpp-arity.test.ts
azizur100389 2376912ca7
Some checks failed
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Gitleaks / gitleaks (push) Has been cancelled
Publish / Classify release event (push) Has been cancelled
Scorecard / Scorecard analysis (push) Has been cancelled
Trivy Image Scan / Trivy (gitnexus-cli) (push) Has been cancelled
Trivy Image Scan / Trivy (gitnexus-web) (push) Has been cancelled
Publish / RC guard (marker + release-PR skip) (push) Has been cancelled
Publish / ci (push) Has been cancelled
Publish / Publish to npm (push) Has been cancelled
Publish / Build & Push RC Docker images (push) Has been cancelled
feat(ingestion): Add C++ parameter type class sidecar (#1642)
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
2026-05-16 21:44:26 +01:00

203 lines
7.6 KiB
TypeScript

/**
* 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> = {}): 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');
});
});