Commit: b4088c5
Parent: d58523a

Follow constructors

Mårten Åsberg committed on 2026-10-27 at 21:07
Assisted-by: GitHub Copilot:gpt-6.1-sol
core/src/CalleBuilder.cs +6 -1
diff --git a/core/src/CalleBuilder.cs b/core/src/CalleBuilder.cs
index f4dd2ef..71e73f5 100644
@@ -57,7 +57,12 @@ internal sealed class CalleeBuilder
var accesses = syntax
.DescendantNodesAndSelf()
.Where(node =>
node is SimpleNameSyntax or ElementAccessExpressionSyntax or ElementBindingExpressionSyntax
node
is SimpleNameSyntax
or ElementAccessExpressionSyntax
or ElementBindingExpressionSyntax
or BaseObjectCreationExpressionSyntax
or ConstructorInitializerSyntax
);
foreach (var access in accesses)
{
core/tests/CalleeBuilderTests/FindCalleesShould.cs +83 -0
diff --git a/core/tests/CalleeBuilderTests/FindCalleesShould.cs b/core/tests/CalleeBuilderTests/FindCalleesShould.cs
index b3b7ced..4baf023 100644
@@ -405,6 +405,89 @@ public sealed class FindCalleesShould
call.LinkKind.ShouldBe(LinkKind.Direct);
}
[Theory]
[InlineData("new Service()")]
[InlineData("new()")]
public void FollowConstructorsAndTheirMethodCalls(string creation)
{
// Arrange
var source = $$"""
class Service
{
public Service() { Initialize(); }
void Initialize() { }
}
class Example
{
void Start() { Service service = {{creation}}; }
}
""";
// Act
var (start, calls) = FindCallees(source);
// Assert
var constructor = calls[start].ShouldHaveSingleItem();
constructor.Target.ShouldBeAssignableTo<IMethodSymbol>().MethodKind.ShouldBe(MethodKind.Constructor);
constructor.LinkKind.ShouldBe(LinkKind.Direct);
calls[constructor.Target].ShouldHaveSingleItem().Target.Name.ShouldBe("Initialize");
}
[Theory]
[InlineData("this")]
[InlineData("base")]
public void FollowChainedConstructors(string initializer)
{
// Arrange
var source = $$"""
class Parent
{
public Parent() { }
public Parent(int value) { }
}
class Service : Parent
{
public Service() : {{initializer}}(1) { }
public Service(int value) { }
}
class Example
{
void Start() { _ = new Service(); }
}
""";
// Act
var (start, calls) = FindCallees(source);
// Assert
var constructor = calls[start].ShouldHaveSingleItem();
var chained = calls[constructor.Target].ShouldHaveSingleItem();
var method = chained.Target.ShouldBeAssignableTo<IMethodSymbol>();
method.MethodKind.ShouldBe(MethodKind.Constructor);
method.Parameters.ShouldHaveSingleItem().Type.SpecialType.ShouldBe(SpecialType.System_Int32);
method.ContainingType.Name.ShouldBe(initializer is "this" ? "Service" : "Parent");
chained.LinkKind.ShouldBe(LinkKind.Direct);
}
[Fact]
public void IgnoreConstructorsWithoutSourceDeclarations()
{
// Arrange
const string source = """
class Service { }
class Example
{
void Start() { _ = new Service(); _ = new object(); }
}
""";
// Act
var (start, calls) = FindCallees(source);
// Assert
calls[start].ShouldBeEmpty();
}
private static (ISymbol, IImmutableDictionary<ISymbol, IImmutableList<Call>>) FindCallees(string source)
{
var compilation = CSharpCompilation.Create(