GitNexus/gitnexus/test/integration/resolvers/cpp-local-using.test.ts

173 lines
6.8 KiB
TypeScript

import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import {
getRelationships,
runPipelineFromRepo,
writeFixtureRepo,
type PipelineResult,
} from './helpers.js';
describe('C++ lexical using declarations and directives', () => {
let repoDir: string;
let result: PipelineResult;
beforeAll(async () => {
repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-cpp-local-using-'));
writeFixtureRepo(repoDir, {
'alpha.hpp': `namespace alpha {
int work() { return 1; }
int collision() { return 1; }
struct Worker { int run() { return 1; } };
namespace detail { int nested_work() { return 1; } }
}
int included_helper() { return 1; }
`,
'beta.hpp': `namespace beta {
int work() { return 2; }
struct Worker { int run() { return 2; } };
namespace detail { int nested_work() { return 2; } }
}
`,
'decoy.cpp': 'int work() { return 9; }\n',
'main.cpp': `#include "alpha.hpp"
#include "beta.hpp"
int collision() { return 0; }
int named_alpha() { using alpha::work; return work(); }
int named_beta() { using beta::work; return work(); }
int before_named() { return work(); using alpha::work; }
int unresolved_named() { using missing::work; return work(); }
int missing_member() { using alpha::absent; return absent(); }
int directive_alpha() { using namespace alpha; return work(); }
int directive_beta() { using namespace beta; return work(); }
int before_directive() { return work(); using namespace alpha; }
int sibling() { return work(); }
int nested_block() { { using alpha::work; work(); } return 0; }
int outside_block() { { using alpha::work; } return work(); }
int ambiguous_directives() { using namespace alpha; using namespace beta; return work(); }
int type_alpha() { using alpha::Worker; Worker worker; return worker.run(); }
int type_beta() { using beta::Worker; Worker worker; return worker.run(); }
int directive_type() { using namespace alpha; Worker worker; return worker.run(); }
int before_type() { Worker worker; using alpha::Worker; return worker.run(); }
int value_shadow() { using namespace alpha; { int work = 0; return work(); } }
int ancestor_value_shadow() { int work = 0; { using namespace alpha; return work(); } }
int global_collision() { using namespace alpha; return collision(); }
int directive_before_value() { using namespace alpha; work(); int work = 0; return 0; }
int prototype_parameter() { using namespace alpha; void unused(int work); return work(); }
int function_pointer_parameter(void (*callback)(int work)) { using namespace alpha; return work(); }
int actual_parameter(int work) { using namespace alpha; return work(); }
int nested_alpha() { using alpha::detail::nested_work; return nested_work(); }
int nested_beta() { using beta::detail::nested_work; return nested_work(); }
struct InlineCaller { int inline_method() { using alpha::work; return work(); } };
int header_control() { return included_helper(); }
`,
'overloads.hpp': 'namespace imported { int choose(int value) { return value; } }\n',
'overloads.cpp': `#include "overloads.hpp"
int choose(double value) { return 1; }
int before_using() { return choose(1.0); }
using imported::choose;
int local_overload() { return choose(1.0); }
int imported_overload() { return choose(1); }
int block_hides_outer() { using imported::choose; return choose(1.0); }
`,
'nested-overloads.cpp': `#include "overloads.hpp"
namespace nested {
int choose(double value) { return 2; }
using imported::choose;
int nested_local() { return choose(1.0); }
int nested_imported() { return choose(1); }
}
`,
'absent.cpp': 'int absent() { return 0; }\n',
});
result = await runPipelineFromRepo(repoDir, () => {});
}, 60_000);
afterAll(() => {
if (repoDir !== undefined) fs.rmSync(repoDir, { recursive: true, force: true });
});
it.each([
['named_alpha', 'alpha.hpp'],
['named_beta', 'beta.hpp'],
['directive_alpha', 'alpha.hpp'],
['directive_beta', 'beta.hpp'],
['nested_block', 'alpha.hpp'],
['inline_method', 'alpha.hpp'],
['directive_before_value', 'alpha.hpp'],
['prototype_parameter', 'alpha.hpp'],
['function_pointer_parameter', 'alpha.hpp'],
])('resolves %s to its exact namespace member', (caller, targetFile) => {
const calls = getRelationships(result, 'CALLS').filter(
(edge) => edge.source === caller && edge.target === 'work',
);
expect(calls.map((edge) => edge.targetFilePath)).toEqual([targetFile]);
});
it.each([
['nested_alpha', 'alpha.hpp'],
['nested_beta', 'beta.hpp'],
])('preserves the full namespace path in %s', (caller, targetFile) => {
const calls = getRelationships(result, 'CALLS').filter(
(edge) => edge.source === caller && edge.target === 'nested_work',
);
expect(calls.map((edge) => edge.targetFilePath)).toEqual([targetFile]);
});
it.each(['before_named', 'before_directive', 'sibling', 'outside_block'])(
'does not leak a namespace using into %s',
(caller) => {
const calls = getRelationships(result, 'CALLS').filter(
(edge) => edge.source === caller && edge.target === 'work',
);
expect(calls.some((edge) => edge.targetFilePath === 'alpha.hpp')).toBe(false);
expect(calls.some((edge) => edge.targetFilePath === 'beta.hpp')).toBe(false);
},
);
it.each([
'unresolved_named',
'missing_member',
'ambiguous_directives',
'value_shadow',
'ancestor_value_shadow',
'actual_parameter',
'global_collision',
'before_type',
])('does not guess an unrelated target for %s', (caller) => {
expect(getRelationships(result, 'CALLS').filter((edge) => edge.source === caller)).toEqual([]);
});
it.each([
['type_alpha', 'alpha.hpp'],
['type_beta', 'beta.hpp'],
['directive_type', 'alpha.hpp'],
])('uses the namespace type identity in %s', (caller, targetFile) => {
const calls = getRelationships(result, 'CALLS').filter(
(edge) => edge.source === caller && edge.target === 'run',
);
expect(calls.map((edge) => edge.targetFilePath)).toEqual([targetFile]);
});
it('preserves compilation-unit include bindings', () => {
const calls = getRelationships(result, 'CALLS').filter(
(edge) => edge.source === 'header_control' && edge.target === 'included_helper',
);
expect(calls.map((edge) => edge.targetFilePath)).toEqual(['alpha.hpp']);
});
it.each([
['before_using', 'overloads.cpp'],
['local_overload', 'overloads.cpp'],
['imported_overload', 'overloads.hpp'],
['nested_local', 'nested-overloads.cpp'],
['nested_imported', 'overloads.hpp'],
['block_hides_outer', 'overloads.hpp'],
])('preserves the exact overload selected by %s', (caller, targetFile) => {
const calls = getRelationships(result, 'CALLS').filter(
(edge) => edge.source === caller && edge.target === 'choose',
);
expect(calls.map((edge) => edge.targetFilePath)).toEqual([targetFile]);
});
});