Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 29 additions & 14 deletions src/TUnit.Core.SourceGenerator/Generators/AotConverterGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
private void ScanTestParameters(Compilation compilation, List<ConversionInfo> conversionInfos, CancellationToken cancellationToken)
{
var typesToScan = new HashSet<ITypeSymbol>(SymbolEqualityComparer.Default);
var baseTestAttribute = compilation.GetTypeByMetadataName(WellKnownFullyQualifiedClassNames.BaseTestAttribute.WithoutGlobalPrefix);

foreach (var tree in compilation.SyntaxTrees)
{
Expand All @@ -115,19 +116,26 @@ private void ScanTestParameters(Compilation compilation, List<ConversionInfo> co
var semanticModel = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();

foreach(var nodes in root.DescendantNodes())
// Conversions depend on declarations and their attributes, never executable bodies.
foreach (var nodes in root.DescendantNodes(static node => node is not
(BaseMethodDeclarationSyntax or BasePropertyDeclarationSyntax or BaseFieldDeclarationSyntax)))
{
cancellationToken.ThrowIfCancellationRequested();

if(nodes is MethodDeclarationSyntax method)
if (nodes is MethodDeclarationSyntax method)
{
if (method.AttributeLists.Count == 0)
{
continue;
}

var methodSymbol = semanticModel.GetDeclaredSymbol(method);
if (methodSymbol == null)
{
continue;
}

if (!IsTestMethod(methodSymbol))
if (!IsTestMethod(methodSymbol, baseTestAttribute))
{
continue;
}
Expand All @@ -148,7 +156,7 @@ private void ScanTestParameters(Compilation compilation, List<ConversionInfo> co
continue;
}

if (!IsTestClass(classSymbol))
if (!IsTestClass(classSymbol, baseTestAttribute))
{
continue;
}
Expand Down Expand Up @@ -179,35 +187,42 @@ private void ScanTestParameters(Compilation compilation, List<ConversionInfo> co
}
}

private static bool IsTestMethod(IMethodSymbol method)
private static bool IsTestMethod(IMethodSymbol method, INamedTypeSymbol? baseTestAttribute)
{
return method.GetAttributes().Any(attr =>
foreach (var attr in method.GetAttributes())
{
var attrClass = attr.AttributeClass;
if (attrClass == null)
{
return false;
continue;
}

var baseType = attrClass;
while (baseType != null)
{
if (baseType.ToDisplayString() == WellKnownFullyQualifiedClassNames.BaseTestAttribute.WithoutGlobalPrefix)
if (SymbolEqualityComparer.Default.Equals(baseType, baseTestAttribute))
{
return true;
}
baseType = baseType.BaseType;
}

return false;
});
}

return false;
}

private bool IsTestClass(INamedTypeSymbol classSymbol)
private static bool IsTestClass(INamedTypeSymbol classSymbol, INamedTypeSymbol? baseTestAttribute)
{
return classSymbol.GetMembers()
.OfType<IMethodSymbol>()
.Any(IsTestMethod);
foreach (var member in classSymbol.GetMembers())
{
if (member is IMethodSymbol method && IsTestMethod(method, baseTestAttribute))
{
return true;
}
}

return false;
}

private void ScanAttributesForTypes(ImmutableArray<AttributeData> attributes, HashSet<ITypeSymbol> typesToScan)
Expand Down
71 changes: 71 additions & 0 deletions tests/TUnit.Core.SourceGenerator.Tests/AotConverterScanTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using TUnit.Core.SourceGenerator.Generators;

namespace TUnit.Core.SourceGenerator.Tests;

public class AotConverterScanTests
{
[Test]
public async Task FindsConversionsInNestedAndPartialDeclarations()
{
const string source = """
using TUnit.Core;
public struct MethodValue { public static implicit operator MethodValue(int value) => new(); }
public struct ConstructorValue { public static implicit operator ConstructorValue(int value) => new(); }
public struct NestedValue { public static implicit operator NestedValue(int value) => new(); }
public struct PartialValue { public static implicit operator PartialValue(int value) => new(); }
public struct BodyOnlyValue { public static implicit operator BodyOnlyValue(int value) => new(); }
public partial class Tests
{
[Test]
public partial void Partial(System.Type value);

[Test, Arguments(1)]
public void Test(MethodValue value)
{
BodyOnlyValue local = 1;
void Helper() { BodyOnlyValue inner = 2; }
Helper();
}
public class Nested
{
[Test, Arguments(1)]
public void Test(NestedValue value) { }
}
}
""";
const string secondPart = """
using TUnit.Core;
[Arguments(1)]
public partial class Tests
{
public partial void Partial([Arguments(typeof(PartialValue))] System.Type value) { }
public Tests(ConstructorValue value) { BodyOnlyValue local = 1; }
public BodyOnlyValue Property => 1;
private BodyOnlyValue field = 1;
}
""";

var parseOptions = new CSharpParseOptions(LanguageVersion.Preview);
var compilation = CSharpCompilation.Create(
"ConverterDeclarations",
[CSharpSyntaxTree.ParseText(source, parseOptions), CSharpSyntaxTree.ParseText(secondPart, parseOptions)],
ReferencesHelper.References,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

GeneratorDriver driver = CSharpGeneratorDriver.Create(
[new AotConverterGenerator().AsSourceGenerator()], parseOptions: parseOptions);
driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var output, out var diagnostics);

await Assert.That(diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error)).IsEmpty();
await Assert.That(output.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error)).IsEmpty();

var generated = string.Join("\n", driver.GetRunResult().GeneratedTrees.Select(t => t.ToString()));
await Assert.That(generated).Contains("global::MethodValue");
await Assert.That(generated).Contains("global::ConstructorValue");
await Assert.That(generated).Contains("global::NestedValue");
await Assert.That(generated).Contains("global::PartialValue");
await Assert.That(generated).DoesNotContain("global::BodyOnlyValue");
}
}
Loading