From 7a90c56c00e7d63199bd3abe409622c3c24e0ec5 Mon Sep 17 00:00:00 2001 From: Alberto Vilches <194074+avilches@users.noreply.github.com> Date: Mon, 25 Dec 2023 23:21:09 +0100 Subject: [PATCH] C# Add test suite for Diagnostic Analyzers: GlobalClass and MustBeVariant --- .../editor/Godot.NET.Sdk/Godot.NET.Sdk.sln | 6 + .../GlobalClass.cs | 14 ++ .../Godot.SourceGenerators.Sample.csproj | 1 + .../MustBeVariantSamples.cs | 164 ++++++++++++++++++ .../CSharpAnalyzerVerifier.cs | 56 ++++++ .../GlobalClassAnalyzerTests.cs | 20 +++ .../Godot.SourceGenerators.Tests.csproj | 2 + .../MustBeVariantAnalyzerTests.cs | 20 +++ .../TestData/Sources/GlobalClass.GD0401.cs | 22 +++ .../TestData/Sources/GlobalClass.GD0402.cs | 15 ++ .../TestData/Sources/MustBeVariant.GD0301.cs | 71 ++++++++ .../TestData/Sources/MustBeVariant.GD0302.cs | 27 +++ 12 files changed, 418 insertions(+) create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/GlobalClass.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/GlobalClassAnalyzerTests.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/MustBeVariantAnalyzerTests.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0302.cs diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln index 03a7dc453c2..9674626183a 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln @@ -6,6 +6,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators", "G EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Sample", "Godot.SourceGenerators.Sample\Godot.SourceGenerators.Sample.csproj", "{7297A614-8DF5-43DE-9EAD-99671B26BD1F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Tests", "Godot.SourceGenerators.Tests\Godot.SourceGenerators.Tests.csproj", "{07E6D201-35C9-4463-9B29-D16621EA733D}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharp", "..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj", "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}" EndProject Global @@ -26,6 +28,10 @@ Global {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.Build.0 = Debug|Any CPU {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.ActiveCfg = Release|Any CPU {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.Build.0 = Release|Any CPU + {07E6D201-35C9-4463-9B29-D16621EA733D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07E6D201-35C9-4463-9B29-D16621EA733D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07E6D201-35C9-4463-9B29-D16621EA733D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07E6D201-35C9-4463-9B29-D16621EA733D}.Release|Any CPU.Build.0 = Release|Any CPU {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/GlobalClass.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/GlobalClass.cs new file mode 100644 index 00000000000..b9f11908e11 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/GlobalClass.cs @@ -0,0 +1,14 @@ +namespace Godot.SourceGenerators.Sample; + +[GlobalClass] +public partial class CustomGlobalClass : GodotObject +{ +} + +// This doesn't works because global classes can't have any generic type parameter. +/* +[GlobalClass] +public partial class CustomGlobalClass : Node +{ +} +*/ diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj index 3f569ebac3f..d0907c1cd4b 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj @@ -2,6 +2,7 @@ net6.0 + 11 diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs new file mode 100644 index 00000000000..1e06091e809 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs @@ -0,0 +1,164 @@ +using System; +using Godot.Collections; +using Array = Godot.Collections.Array; + +namespace Godot.SourceGenerators.Sample; + +public class MustBeVariantMethods +{ + public void MustBeVariantMethodCalls() + { + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + + // This call fails because generic type is not Variant-compatible. + //Method(); + } + + public void Method<[MustBeVariant] T>() + { + } + + public void MustBeVariantClasses() + { + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + new ClassWithGenericVariant(); + + // This class fails because generic type is not Variant-compatible. + //new ClassWithGenericVariant(); + } +} + +public class ClassWithGenericVariant<[MustBeVariant] T> +{ +} + +public class MustBeVariantAnnotatedMethods +{ + [GenericTypeAttribute()] + public void MethodWithAttributeOk() + { + } + + // This method definition fails because generic type is not Variant-compatible. + /* + [GenericTypeAttribute()] + public void MethodWithWrongAttribute() + { + } + */ +} + +[GenericTypeAttribute()] +public class ClassVariantAnnotated +{ +} + +// This class definition fails because generic type is not Variant-compatible. +/* +[GenericTypeAttribute()] +public class ClassNonVariantAnnotated +{ +} +*/ + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] +public class GenericTypeAttribute<[MustBeVariant] T> : Attribute +{ +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs new file mode 100644 index 00000000000..e3e7373b2e1 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.Text; + +namespace Godot.SourceGenerators.Tests; + +public static class CSharpAnalyzerVerifier +where TAnalyzer : DiagnosticAnalyzer, new() +{ + public class Test : CSharpAnalyzerTest + { + public Test() + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net60; + + SolutionTransforms.Add((Solution solution, ProjectId projectId) => + { + Project project = + solution.GetProject(projectId)!.AddMetadataReference(Constants.GodotSharpAssembly + .CreateMetadataReference()); + + return project.Solution; + }); + } + } + + public static Task Verify(string sources, params DiagnosticResult[] expected) + { + return MakeVerifier(new string[] { sources }, expected).RunAsync(); + } + + public static Test MakeVerifier(ICollection sources, params DiagnosticResult[] expected) + { + var verifier = new Test(); + + verifier.TestState.AnalyzerConfigFiles.Add(("/.globalconfig", $""" + is_global = true + build_property.GodotProjectDir = {Constants.ExecutingAssemblyPath} + """)); + + verifier.TestState.Sources.AddRange(sources.Select(source => + { + return (source, SourceText.From(File.ReadAllText(Path.Combine(Constants.SourceFolderPath, source)))); + })); + + verifier.ExpectedDiagnostics.AddRange(expected); + return verifier; + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/GlobalClassAnalyzerTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/GlobalClassAnalyzerTests.cs new file mode 100644 index 00000000000..74d6afceb39 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/GlobalClassAnalyzerTests.cs @@ -0,0 +1,20 @@ +using Xunit; + +namespace Godot.SourceGenerators.Tests; + +public class GlobalClassAnalyzerTests +{ + [Fact] + public async void GlobalClassMustDeriveFromGodotObjectTest() + { + const string GlobalClassGD0401 = "GlobalClass.GD0401.cs"; + await CSharpAnalyzerVerifier.Verify(GlobalClassGD0401); + } + + [Fact] + public async void GlobalClassMustNotBeGenericTest() + { + const string GlobalClassGD0402 = "GlobalClass.GD0402.cs"; + await CSharpAnalyzerVerifier.Verify(GlobalClassGD0402); + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj index e39c14f0492..13e54a543f1 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj @@ -17,6 +17,8 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/MustBeVariantAnalyzerTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/MustBeVariantAnalyzerTests.cs new file mode 100644 index 00000000000..62c602efbb3 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/MustBeVariantAnalyzerTests.cs @@ -0,0 +1,20 @@ +using Xunit; + +namespace Godot.SourceGenerators.Tests; + +public class MustBeVariantAnalyzerTests +{ + [Fact] + public async void GenericTypeArgumentMustBeVariantTest() + { + const string MustBeVariantGD0301 = "MustBeVariant.GD0301.cs"; + await CSharpAnalyzerVerifier.Verify(MustBeVariantGD0301); + } + + [Fact] + public async void GenericTypeParameterMustBeVariantAnnotatedTest() + { + const string MustBeVariantGD0302 = "MustBeVariant.GD0302.cs"; + await CSharpAnalyzerVerifier.Verify(MustBeVariantGD0302); + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs new file mode 100644 index 00000000000..6e6d3a6f39d --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs @@ -0,0 +1,22 @@ +using Godot; + +// This works because it inherits from GodotObject. +[GlobalClass] +public partial class CustomGlobalClass1 : GodotObject +{ + +} + +// This works because it inherits from an object that inherits from GodotObject +[GlobalClass] +public partial class CustomGlobalClass2 : Node +{ + +} + +// This raises a GD0401 diagnostic error: global classes must inherit from GodotObject +{|GD0401:[GlobalClass] +public partial class CustomGlobalClass3 +{ + +}|} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs new file mode 100644 index 00000000000..1c0a169841d --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs @@ -0,0 +1,15 @@ +using Godot; + +// This works because it inherits from GodotObject and it doesn't have any generic type parameter. +[GlobalClass] +public partial class CustomGlobalClass : GodotObject +{ + +} + +// This raises a GD0402 diagnostic error: global classes can't have any generic type parameter +{|GD0402:[GlobalClass] +public partial class CustomGlobalClass : GodotObject +{ + +}|} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs new file mode 100644 index 00000000000..031039cba1f --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs @@ -0,0 +1,71 @@ +using System; +using Godot; +using Godot.Collections; +using Array = Godot.Collections.Array; + +public class MustBeVariantGD0301 +{ + public void MethodCallsError() + { + // This raises a GD0301 diagnostic error: object is not Variant (and Method requires a variant generic type). + Method<{|GD0301:object|}>(); + } + public void MethodCallsOk() + { + // All these calls are valid because they are Variant types. + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + Method(); + } + + public void Method<[MustBeVariant] T>() + { + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0302.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0302.cs new file mode 100644 index 00000000000..ce182e8c620 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0302.cs @@ -0,0 +1,27 @@ +using Godot; + +public class MustBeVariantGD0302 +{ + public void MethodOk<[MustBeVariant] T>() + { + // T is guaranteed to be a Variant-compatible type because it's annotated with the [MustBeVariant] attribute, so it can be used here. + new ExampleClass(); + Method(); + } + + public void MethodFail() + { + // These two calls raise a GD0302 diagnostic error: T is not valid here because it may not a Variant type and method call and class require it. + new ExampleClass<{|GD0302:T|}>(); + Method<{|GD0302:T|}>(); + } + + public void Method<[MustBeVariant] T>() + { + } +} + +public class ExampleClass<[MustBeVariant] T> +{ + +}