/**
* A bare C# name — `TestContext`, `Easing`, `Helper()` — is a member of the
* types around it, of their base types, or of a type a static using brings in
* (in the file, as `global using static`, or as a project file's
* ``). eShop's `TestContext.Current` in one test class
* went to another test class's `TestContext` property; MAUI's `Easing.Linear`
* to an animation class's `Easing` property.
*/
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { CodeGraph } from '../src';
let root = '';
let cg: CodeGraph;
beforeAll(async () => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-csharp-scope-'));
const files: Record = {
'tests/OrderProcessorTests.cs': `namespace App.Tests;
public class OrderProcessorTests
{
public TestContext TestContext { get; set; } = null!;
}
`,
'src/Animations/AnimationBase.cs': `namespace App.Animations;
public abstract class AnimationBase
{
public EasingType Easing { get; set; }
}
`,
'src/Execution/ExpressionBuilder.cs': `namespace App.Execution;
public static class ExpressionBuilder
{
public static object Call(object target) => target;
}
`,
'src/App.csproj': `
`,
'src/Specs/SpecBase.cs': `namespace App.Specs;
public abstract class SpecBase
{
protected void AssertValid() { }
}
`,
'src/Specs/CatalogSpec.cs': `namespace App.Specs;
public class CatalogSpec : SpecBase
{
public void Runs()
{
var token = TestContext.Current.CancellationToken;
var easing = Easing.Linear;
AssertValid();
Call(token);
}
}
`,
};
for (const [rel, content] of Object.entries(files)) {
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
fs.writeFileSync(path.join(root, rel), content);
}
cg = await CodeGraph.init(root, { index: true });
});
afterAll(() => {
cg?.close();
if (root) fs.rmSync(root, { recursive: true, force: true });
});
describe('bare C# names', () => {
it('reach the base class and a project-wide static using, never another class’s property', () => {
const ids = cg.getNodesInFile('src/Specs/CatalogSpec.cs').map((n) => n.id);
const targets = cg
.getOutgoingEdgesFrom(ids)
.filter((e) => e.kind === 'calls' || e.kind === 'references')
.map((e) => cg.getNode(e.target)!.qualifiedName);
expect(targets).not.toContain('App.Tests::OrderProcessorTests::TestContext');
expect(targets).not.toContain('App.Animations::AnimationBase::Easing');
expect(targets).toContain('App.Specs::SpecBase::AssertValid');
expect(targets).toContain('App.Execution::ExpressionBuilder::Call');
});
});