Quellcode durchsuchen

C#: Various fixes to generic scripts

- Report a diagnostic when there are multiple classes that match the script file name in the same script since that will result in a duplicate path key in the bimap and it's not allowed.
- Fix InspectorPlugin to handle empty paths in case the project was built with a previous version of Godot that used empty paths for generic scripts.
- Add tests for the new diagnostic GD0003.
Raul Santos vor 1 Jahr
Ursprung
Commit
fe280ef9ae

+ 0 - 11
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs

@@ -2,17 +2,6 @@
 
 namespace Godot.SourceGenerators.Sample
 {
-    partial class Generic<T> : GodotObject
-    {
-        private int _field;
-    }
-
-    // Generic again but different generic parameters
-    partial class Generic<T, R> : GodotObject
-    {
-        private int _field;
-    }
-
     // Generic again but without generic parameters
     partial class Generic : GodotObject
     {

+ 9 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic1T.cs

@@ -0,0 +1,9 @@
+#pragma warning disable CS0169
+
+namespace Godot.SourceGenerators.Sample
+{
+    partial class Generic1T<T> : GodotObject
+    {
+        private int _field;
+    }
+}

+ 10 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic2T.cs

@@ -0,0 +1,10 @@
+#pragma warning disable CS0169
+
+namespace Godot.SourceGenerators.Sample
+{
+    // Generic again but different generic parameters
+    partial class Generic2T<T, R> : GodotObject
+    {
+        private int _field;
+    }
+}

+ 27 - 2
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPathAttributeGeneratorTests.cs

@@ -1,3 +1,4 @@
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
@@ -47,9 +48,33 @@ public class ScriptPathAttributeGeneratorTests
     {
         var verifier = CSharpSourceGeneratorVerifier<ScriptPathAttributeGenerator>.MakeVerifier(
             new string[] { "Generic.cs" },
-            new string[] { "Generic_ScriptPath.generated.cs" }
+            new string[] { "Generic(Of T)_ScriptPath.generated.cs" }
         );
-        verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::Generic" }));
+        verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::Generic<>" }));
+        await verifier.RunAsync();
+    }
+
+    [Fact]
+    public async void GenericMultipleClassesSameName()
+    {
+        var verifier = CSharpSourceGeneratorVerifier<ScriptPathAttributeGenerator>.MakeVerifier(
+            Array.Empty<string>(),
+            new string[] { "Generic(Of T)_ScriptPath.generated.cs" }
+        );
+        verifier.TestState.Sources.Add(("Generic.cs", File.ReadAllText(Path.Combine(Constants.SourceFolderPath, "Generic.GD0003.cs"))));
+        verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::Generic<>", "global::Generic<,>", "global::Generic" }));
+        await verifier.RunAsync();
+    }
+
+    [Fact]
+    public async void NamespaceMultipleClassesSameName()
+    {
+        var verifier = CSharpSourceGeneratorVerifier<ScriptPathAttributeGenerator>.MakeVerifier(
+            Array.Empty<string>(),
+            new string[] { "NamespaceA.SameName_ScriptPath.generated.cs" }
+        );
+        verifier.TestState.Sources.Add(("SameName.cs", File.ReadAllText(Path.Combine(Constants.SourceFolderPath, "SameName.GD0003.cs"))));
+        verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::NamespaceA.SameName", "global::NamespaceB.SameName" }));
         await verifier.RunAsync();
     }
 }

+ 1 - 1
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Generic_ScriptPath.generated.cs → modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Generic(Of T)_ScriptPath.generated.cs

@@ -1,5 +1,5 @@
 using Godot;
 [ScriptPathAttribute("res://Generic.cs")]
-partial class Generic
+partial class Generic<T>
 {
 }

+ 9 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/NamespaceA.SameName_ScriptPath.generated.cs

@@ -0,0 +1,9 @@
+using Godot;
+namespace NamespaceA {
+
+[ScriptPathAttribute("res://SameName.cs")]
+partial class SameName
+{
+}
+
+}

+ 18 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.GD0003.cs

@@ -0,0 +1,18 @@
+using Godot;
+
+partial class Generic<T> : GodotObject
+{
+    private int _field;
+}
+
+// Generic again but different generic parameters
+partial class {|GD0003:Generic|}<T, R> : GodotObject
+{
+    private int _field;
+}
+
+// Generic again but without generic parameters
+partial class {|GD0003:Generic|} : GodotObject
+{
+    private int _field;
+}

+ 0 - 12
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.cs

@@ -4,15 +4,3 @@ partial class Generic<T> : GodotObject
 {
     private int _field;
 }
-
-// Generic again but different generic parameters
-partial class Generic<T, R> : GodotObject
-{
-    private int _field;
-}
-
-// Generic again but without generic parameters
-partial class Generic : GodotObject
-{
-    private int _field;
-}

+ 18 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/SameName.GD0003.cs

@@ -0,0 +1,18 @@
+using Godot;
+
+namespace NamespaceA
+{
+    partial class SameName : GodotObject
+    {
+        private int _field;
+    }
+}
+
+// SameName again but different namespace
+namespace NamespaceB
+{
+    partial class {|GD0003:SameName|} : GodotObject
+    {
+        private int _field;
+    }
+}

+ 10 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs

@@ -65,6 +65,16 @@ namespace Godot.SourceGenerators
                 outerTypeDeclSyntax.SyntaxTree.FilePath));
         }
 
+        public static readonly DiagnosticDescriptor MultipleClassesInGodotScriptRule =
+            new DiagnosticDescriptor(id: "GD0003",
+                title: "Found multiple classes with the same name in the same script file",
+                messageFormat: "Found multiple classes with the name '{0}' in the same script file",
+                category: "Usage",
+                DiagnosticSeverity.Error,
+                isEnabledByDefault: true,
+                "Found multiple classes with the same name in the same script file. A script file must only contain one class with a name that matches the file name.",
+                helpLinkUri: string.Format(_helpLinkFormat, "GD0003"));
+
         public static readonly DiagnosticDescriptor ExportedMemberIsStaticRule =
             new DiagnosticDescriptor(id: "GD0101",
                 title: "The exported member is static",

+ 15 - 2
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs

@@ -58,9 +58,10 @@ namespace Godot.SourceGenerators
                 .GroupBy<(ClassDeclarationSyntax cds, INamedTypeSymbol symbol), INamedTypeSymbol>(x => x.symbol, SymbolEqualityComparer.Default)
                 .ToDictionary<IGrouping<INamedTypeSymbol, (ClassDeclarationSyntax cds, INamedTypeSymbol symbol)>, INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>>(g => g.Key, g => g.Select(x => x.cds), SymbolEqualityComparer.Default);
 
+            var usedPaths = new HashSet<string>();
             foreach (var godotClass in godotClasses)
             {
-                VisitGodotScriptClass(context, godotProjectDir,
+                VisitGodotScriptClass(context, godotProjectDir, usedPaths,
                     symbol: godotClass.Key,
                     classDeclarations: godotClass.Value);
             }
@@ -74,6 +75,7 @@ namespace Godot.SourceGenerators
         private static void VisitGodotScriptClass(
             GeneratorExecutionContext context,
             string godotProjectDir,
+            HashSet<string> usedPaths,
             INamedTypeSymbol symbol,
             IEnumerable<ClassDeclarationSyntax> classDeclarations
         )
@@ -93,8 +95,19 @@ namespace Godot.SourceGenerators
                 if (attributes.Length != 0)
                     attributes.Append("\n");
 
+                string scriptPath = RelativeToDir(cds.SyntaxTree.FilePath, godotProjectDir);
+                if (!usedPaths.Add(scriptPath))
+                {
+                    context.ReportDiagnostic(Diagnostic.Create(
+                        Common.MultipleClassesInGodotScriptRule,
+                        cds.Identifier.GetLocation(),
+                        symbol.Name
+                    ));
+                    return;
+                }
+
                 attributes.Append(@"[ScriptPathAttribute(""res://");
-                attributes.Append(RelativeToDir(cds.SyntaxTree.FilePath, godotProjectDir));
+                attributes.Append(scriptPath);
                 attributes.Append(@""")]");
             }
 

+ 9 - 0
modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs

@@ -28,6 +28,15 @@ namespace GodotTools.Inspector
                     continue;
 
                 string scriptPath = script.ResourcePath;
+
+                if (string.IsNullOrEmpty(scriptPath))
+                {
+                    // Generic types used empty paths in older versions of Godot
+                    // so we assume your project is out of sync.
+                    AddCustomControl(new InspectorOutOfSyncWarning());
+                    break;
+                }
+
                 if (scriptPath.StartsWith("csharp://"))
                 {
                     // This is a virtual path used by generic types, extract the real path.