diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs index 2cd1a08c0eeb..457d8daa8e77 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs @@ -2,17 +2,6 @@ namespace Godot.SourceGenerators.Sample { - partial class Generic : GodotObject - { - private int _field; - } - - // Generic again but different generic parameters - partial class Generic : GodotObject - { - private int _field; - } - // Generic again but without generic parameters partial class Generic : GodotObject { diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic1T.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic1T.cs new file mode 100644 index 000000000000..9c4f8ee1e1f3 --- /dev/null +++ b/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 : GodotObject + { + private int _field; + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic2T.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic2T.cs new file mode 100644 index 000000000000..80551a0b429c --- /dev/null +++ b/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 : GodotObject + { + private int _field; + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPathAttributeGeneratorTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPathAttributeGeneratorTests.cs index b7ad4217aabd..4f6b50cf0285 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPathAttributeGeneratorTests.cs +++ b/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.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.MakeVerifier( + Array.Empty(), + 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.MakeVerifier( + Array.Empty(), + 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(); } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Generic_ScriptPath.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Generic(Of T)_ScriptPath.generated.cs similarity index 70% rename from modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Generic_ScriptPath.generated.cs rename to modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Generic(Of T)_ScriptPath.generated.cs index 72c48595a221..355b01f7532b 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/Generic_ScriptPath.generated.cs +++ b/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 { } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/NamespaceA.SameName_ScriptPath.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/NamespaceA.SameName_ScriptPath.generated.cs new file mode 100644 index 000000000000..cad9f2a46bba --- /dev/null +++ b/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 +{ +} + +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.GD0003.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.GD0003.cs new file mode 100644 index 000000000000..15c1e03801a6 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.GD0003.cs @@ -0,0 +1,18 @@ +using Godot; + +partial class Generic : GodotObject +{ + private int _field; +} + +// Generic again but different generic parameters +partial class {|GD0003:Generic|} : GodotObject +{ + private int _field; +} + +// Generic again but without generic parameters +partial class {|GD0003:Generic|} : GodotObject +{ + private int _field; +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.cs index 84d1ede06585..5a83e21e96ee 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Generic.cs @@ -4,15 +4,3 @@ partial class Generic : GodotObject { private int _field; } - -// Generic again but different generic parameters -partial class Generic : GodotObject -{ - private int _field; -} - -// Generic again but without generic parameters -partial class Generic : GodotObject -{ - private int _field; -} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/SameName.GD0003.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/SameName.GD0003.cs new file mode 100644 index 000000000000..3f4f79fc49a1 --- /dev/null +++ b/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; + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs index a47e2e170f51..6cd5ddb42f26 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs +++ b/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", diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs index 625a6f992112..6dc541cc5968 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs +++ b/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, INamedTypeSymbol, IEnumerable>(g => g.Key, g => g.Select(x => x.cds), SymbolEqualityComparer.Default); + var usedPaths = new HashSet(); 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 usedPaths, INamedTypeSymbol symbol, IEnumerable 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(@""")]"); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs index b86a2b8b241c..8aeb19e08b7c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs +++ b/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.