mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
* touching publish.yml * Moved Examples into ./Examples * Moved Benchmarks into ./Tests * Moved Benchmarks into ./Tests * Moved UICatalog into ./Examples * Moved UICatalog into ./Examples 2 * Moved tests into ./Tests * Updated nuget
334 lines
17 KiB
C#
334 lines
17 KiB
C#
using System.Collections.Immutable;
|
|
using System.Text;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
|
|
namespace TerminalGuiFluentTestingXunit.Generator;
|
|
|
|
[Generator]
|
|
public class TheGenerator : IIncrementalGenerator
|
|
{
|
|
/// <inheritdoc/>
|
|
public void Initialize (IncrementalGeneratorInitializationContext context)
|
|
{
|
|
IncrementalValuesProvider<ClassDeclarationSyntax> provider = context.SyntaxProvider.CreateSyntaxProvider (
|
|
static (node, _) => IsClass (node, "XunitContextExtensions"),
|
|
static (ctx, _) =>
|
|
(ClassDeclarationSyntax)ctx.Node)
|
|
.Where (m => m is { });
|
|
|
|
IncrementalValueProvider<(Compilation Left, ImmutableArray<ClassDeclarationSyntax> Right)> compilation =
|
|
context.CompilationProvider.Combine (provider.Collect ());
|
|
context.RegisterSourceOutput (compilation, Execute);
|
|
}
|
|
|
|
private static bool IsClass (SyntaxNode node, string named) { return node is ClassDeclarationSyntax c && c.Identifier.Text == named; }
|
|
|
|
private void Execute (SourceProductionContext context, (Compilation Left, ImmutableArray<ClassDeclarationSyntax> Right) arg2)
|
|
{
|
|
INamedTypeSymbol assertType = arg2.Left.GetTypeByMetadataName ("Xunit.Assert")
|
|
?? throw new NotSupportedException("Referencing codebase does not include Xunit, could not find Xunit.Assert");
|
|
|
|
GenerateMethods (assertType, context, "Equal", false);
|
|
|
|
GenerateMethods (assertType, context, "All", true);
|
|
GenerateMethods (assertType, context, "Collection", true);
|
|
GenerateMethods (assertType, context, "Contains", true);
|
|
GenerateMethods (assertType, context, "Distinct", true);
|
|
GenerateMethods (assertType, context, "DoesNotContain", true);
|
|
GenerateMethods (assertType, context, "DoesNotMatch", true);
|
|
GenerateMethods (assertType, context, "Empty", true);
|
|
GenerateMethods (assertType, context, "EndsWith", false);
|
|
GenerateMethods (assertType, context, "Equivalent", true);
|
|
GenerateMethods (assertType, context, "Fail", true);
|
|
GenerateMethods (assertType, context, "False", true);
|
|
GenerateMethods (assertType, context, "InRange", true);
|
|
GenerateMethods (assertType, context, "IsAssignableFrom", true);
|
|
GenerateMethods (assertType, context, "IsNotAssignableFrom", true);
|
|
GenerateMethods (assertType, context, "IsType", true);
|
|
GenerateMethods (assertType, context, "IsNotType", true);
|
|
|
|
GenerateMethods (assertType, context, "Matches", true);
|
|
GenerateMethods (assertType, context, "Multiple", true);
|
|
GenerateMethods (assertType, context, "NotEmpty", true);
|
|
GenerateMethods (assertType, context, "NotEqual", true);
|
|
GenerateMethods (assertType, context, "NotInRange", true);
|
|
GenerateMethods (assertType, context, "NotNull", false);
|
|
GenerateMethods (assertType, context, "NotSame", true);
|
|
GenerateMethods (assertType, context, "NotStrictEqual", true);
|
|
GenerateMethods (assertType, context, "Null", false);
|
|
GenerateMethods (assertType, context, "ProperSubset", true);
|
|
GenerateMethods (assertType, context, "ProperSuperset", true);
|
|
GenerateMethods (assertType, context, "Raises", true);
|
|
GenerateMethods (assertType, context, "RaisesAny", true);
|
|
GenerateMethods (assertType, context, "Same", true);
|
|
GenerateMethods (assertType, context, "Single", true);
|
|
GenerateMethods (assertType, context, "StartsWith", false);
|
|
|
|
GenerateMethods (assertType, context, "StrictEqual", true);
|
|
GenerateMethods (assertType, context, "Subset", true);
|
|
GenerateMethods (assertType, context, "Superset", true);
|
|
|
|
// GenerateMethods (assertType, context, "Throws", true);
|
|
// GenerateMethods (assertType, context, "ThrowsAny", true);
|
|
GenerateMethods (assertType, context, "True", false);
|
|
}
|
|
|
|
private void GenerateMethods (INamedTypeSymbol assertType, SourceProductionContext context, string methodName, bool invokeTExplicitly)
|
|
{
|
|
var sb = new StringBuilder ();
|
|
|
|
// Create a HashSet to track unique method signatures
|
|
HashSet<string> signaturesDone = new ();
|
|
|
|
List<IMethodSymbol> methods = assertType
|
|
.GetMembers (methodName)
|
|
.OfType<IMethodSymbol> ()
|
|
.ToList ();
|
|
|
|
var header = """"
|
|
#nullable enable
|
|
using TerminalGuiFluentTesting;
|
|
using Xunit;
|
|
|
|
namespace TerminalGuiFluentTestingXunit;
|
|
|
|
public static partial class XunitContextExtensions
|
|
{
|
|
|
|
|
|
"""";
|
|
|
|
var tail = """
|
|
|
|
}
|
|
""";
|
|
|
|
sb.AppendLine (header);
|
|
|
|
foreach (IMethodSymbol? m in methods)
|
|
{
|
|
string signature = GetModifiedMethodSignature (m, methodName, invokeTExplicitly, out string [] paramNames, out string typeParams);
|
|
|
|
if (!signaturesDone.Add (signature))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var method = $$"""
|
|
{{signature}}
|
|
{
|
|
try
|
|
{
|
|
Assert.{{methodName}}{{typeParams}} ({{string.Join (",", paramNames)}});
|
|
}
|
|
catch(Exception)
|
|
{
|
|
context.HardStop ();
|
|
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
return context;
|
|
}
|
|
""";
|
|
|
|
sb.AppendLine (method);
|
|
}
|
|
|
|
sb.AppendLine (tail);
|
|
|
|
context.AddSource ($"XunitContextExtensions{methodName}.g.cs", sb.ToString ());
|
|
}
|
|
|
|
private string GetModifiedMethodSignature (
|
|
IMethodSymbol methodSymbol,
|
|
string methodName,
|
|
bool invokeTExplicitly,
|
|
out string [] paramNames,
|
|
out string typeParams
|
|
)
|
|
{
|
|
typeParams = string.Empty;
|
|
|
|
// Create the "this GuiTestContext context" parameter
|
|
ParameterSyntax contextParam = SyntaxFactory.Parameter (SyntaxFactory.Identifier ("context"))
|
|
.WithType (SyntaxFactory.ParseTypeName ("GuiTestContext"))
|
|
.AddModifiers (SyntaxFactory.Token (SyntaxKind.ThisKeyword)); // Add the "this" keyword
|
|
|
|
// Extract the parameter names (expected and actual)
|
|
paramNames = new string [methodSymbol.Parameters.Length];
|
|
|
|
for (var i = 0; i < methodSymbol.Parameters.Length; i++)
|
|
{
|
|
paramNames [i] = methodSymbol.Parameters.ElementAt (i).Name;
|
|
|
|
// Check if the parameter name is a reserved keyword and prepend "@" if it is
|
|
if (IsReservedKeyword (paramNames [i]))
|
|
{
|
|
paramNames [i] = "@" + paramNames [i];
|
|
}
|
|
else
|
|
{
|
|
paramNames [i] = paramNames [i];
|
|
}
|
|
}
|
|
|
|
// Get the current method parameters and add the context parameter at the start
|
|
List<ParameterSyntax> parameters = methodSymbol.Parameters.Select (p => CreateParameter (p)).ToList ();
|
|
|
|
parameters.Insert (0, contextParam); // Insert 'context' as the first parameter
|
|
|
|
// Change the return type to GuiTestContext
|
|
TypeSyntax returnType = SyntaxFactory.ParseTypeName ("GuiTestContext");
|
|
|
|
// Change the method name to AssertEqual
|
|
SyntaxToken newMethodName = SyntaxFactory.Identifier ($"Assert{methodName}");
|
|
|
|
// Handle generic type parameters if the method is generic
|
|
TypeParameterSyntax [] typeParameters = methodSymbol.TypeParameters.Select (
|
|
tp =>
|
|
SyntaxFactory.TypeParameter (SyntaxFactory.Identifier (tp.Name))
|
|
)
|
|
.ToArray ();
|
|
|
|
MethodDeclarationSyntax dec = SyntaxFactory.MethodDeclaration (returnType, newMethodName)
|
|
.WithModifiers (
|
|
SyntaxFactory.TokenList (
|
|
SyntaxFactory.Token (SyntaxKind.PublicKeyword),
|
|
SyntaxFactory.Token (SyntaxKind.StaticKeyword)))
|
|
.WithParameterList (SyntaxFactory.ParameterList (SyntaxFactory.SeparatedList (parameters)));
|
|
|
|
if (typeParameters.Any ())
|
|
{
|
|
// Add the <T> here
|
|
dec = dec.WithTypeParameterList (SyntaxFactory.TypeParameterList (SyntaxFactory.SeparatedList (typeParameters)));
|
|
|
|
// Handle type parameter constraints
|
|
List<TypeParameterConstraintClauseSyntax> constraintClauses = methodSymbol.TypeParameters
|
|
.Where (tp => tp.ConstraintTypes.Length > 0)
|
|
.Select (
|
|
tp =>
|
|
SyntaxFactory.TypeParameterConstraintClause (tp.Name)
|
|
.WithConstraints (
|
|
SyntaxFactory
|
|
.SeparatedList<TypeParameterConstraintSyntax> (
|
|
tp.ConstraintTypes.Select (
|
|
constraintType =>
|
|
SyntaxFactory.TypeConstraint (
|
|
SyntaxFactory.ParseTypeName (
|
|
constraintType
|
|
.ToDisplayString ()))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
.ToList ();
|
|
|
|
if (constraintClauses.Any ())
|
|
{
|
|
dec = dec.WithConstraintClauses (SyntaxFactory.List (constraintClauses));
|
|
}
|
|
|
|
// Add the <T> here
|
|
if (invokeTExplicitly)
|
|
{
|
|
typeParams = "<" + string.Join (", ", typeParameters.Select (tp => tp.Identifier.ValueText)) + ">";
|
|
}
|
|
}
|
|
|
|
// Build the method signature syntax tree
|
|
MethodDeclarationSyntax methodSyntax = dec.NormalizeWhitespace ();
|
|
|
|
// Convert the method syntax to a string
|
|
var methodString = methodSyntax.ToString ();
|
|
|
|
return methodString;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a <see cref="ParameterSyntax"/> from a discovered parameter on real xunit method parameter
|
|
/// <paramref name="p"/>
|
|
/// </summary>
|
|
/// <param name="p"></param>
|
|
/// <returns></returns>
|
|
private ParameterSyntax CreateParameter (IParameterSymbol p)
|
|
{
|
|
string paramName = p.Name;
|
|
|
|
// Check if the parameter name is a reserved keyword and prepend "@" if it is
|
|
if (IsReservedKeyword (paramName))
|
|
{
|
|
paramName = "@" + paramName;
|
|
}
|
|
|
|
// Create the basic parameter syntax with the modified name and type
|
|
ParameterSyntax parameterSyntax = SyntaxFactory.Parameter (SyntaxFactory.Identifier (paramName))
|
|
.WithType (SyntaxFactory.ParseTypeName (p.Type.ToDisplayString ()));
|
|
|
|
// Add 'params' keyword if the parameter has the Params modifier
|
|
var modifiers = new List<SyntaxToken> ();
|
|
|
|
if (p.IsParams)
|
|
{
|
|
modifiers.Add (SyntaxFactory.Token (SyntaxKind.ParamsKeyword));
|
|
}
|
|
|
|
// Handle ref/out/in modifiers
|
|
if (p.RefKind != RefKind.None)
|
|
{
|
|
SyntaxKind modifierKind = p.RefKind switch
|
|
{
|
|
RefKind.Ref => SyntaxKind.RefKeyword,
|
|
RefKind.Out => SyntaxKind.OutKeyword,
|
|
RefKind.In => SyntaxKind.InKeyword,
|
|
_ => throw new NotSupportedException ($"Unsupported RefKind: {p.RefKind}")
|
|
};
|
|
|
|
|
|
modifiers.Add (SyntaxFactory.Token (modifierKind));
|
|
}
|
|
|
|
|
|
if (modifiers.Any ())
|
|
{
|
|
parameterSyntax = parameterSyntax.WithModifiers (SyntaxFactory.TokenList (modifiers));
|
|
}
|
|
|
|
// Add default value if one is present
|
|
if (p.HasExplicitDefaultValue)
|
|
{
|
|
ExpressionSyntax defaultValueExpression = p.ExplicitDefaultValue switch
|
|
{
|
|
null => SyntaxFactory.LiteralExpression (SyntaxKind.NullLiteralExpression),
|
|
bool b => SyntaxFactory.LiteralExpression (
|
|
b
|
|
? SyntaxKind.TrueLiteralExpression
|
|
: SyntaxKind.FalseLiteralExpression),
|
|
int i => SyntaxFactory.LiteralExpression (
|
|
SyntaxKind.NumericLiteralExpression,
|
|
SyntaxFactory.Literal (i)),
|
|
double d => SyntaxFactory.LiteralExpression (
|
|
SyntaxKind.NumericLiteralExpression,
|
|
SyntaxFactory.Literal (d)),
|
|
string s => SyntaxFactory.LiteralExpression (
|
|
SyntaxKind.StringLiteralExpression,
|
|
SyntaxFactory.Literal (s)),
|
|
_ => SyntaxFactory.ParseExpression (p.ExplicitDefaultValue.ToString ()) // Fallback
|
|
};
|
|
|
|
parameterSyntax = parameterSyntax.WithDefault (
|
|
SyntaxFactory.EqualsValueClause (defaultValueExpression)
|
|
);
|
|
}
|
|
|
|
return parameterSyntax;
|
|
}
|
|
|
|
// Helper method to check if a parameter name is a reserved keyword
|
|
private bool IsReservedKeyword (string name) { return string.Equals (name, "object"); }
|
|
}
|