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.
This commit is contained in:
parent
ae51db75e7
commit
fe280ef9ae
|
@ -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
|
||||
{
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
#pragma warning disable CS0169
|
||||
|
||||
namespace Godot.SourceGenerators.Sample
|
||||
{
|
||||
partial class Generic1T<T> : GodotObject
|
||||
{
|
||||
private int _field;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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,5 +1,5 @@
|
|||
using Godot;
|
||||
[ScriptPathAttribute("res://Generic.cs")]
|
||||
partial class Generic
|
||||
partial class Generic<T>
|
||||
{
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using Godot;
|
||||
namespace NamespaceA {
|
||||
|
||||
[ScriptPathAttribute("res://SameName.cs")]
|
||||
partial class SameName
|
||||
{
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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(@""")]");
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue