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
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>Latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
333
Tests/TerminalGuiFluentTestingXunit.Generator/TheGenerator.cs
Normal file
333
Tests/TerminalGuiFluentTestingXunit.Generator/TheGenerator.cs
Normal file
@@ -0,0 +1,333 @@
|
||||
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"); }
|
||||
}
|
||||
Reference in New Issue
Block a user