Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions Source/aweXpect.SourceGenerators/ExpectationGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,23 @@ private static void Execute(ExpectationToGenerate expectationToGenerate, SourceP
}

string? outcomeMethod = null;
string? name = null;
string? positiveName = null;
string? negativeName = null;
if (attributeData.ConstructorArguments.Length == 2)
{
name = attributeData.ConstructorArguments[0].Value?.ToString();
var name = attributeData.ConstructorArguments[0].Value?.ToString();
positiveName = name?.Replace("{Not}", "");
negativeName = name?.Replace("{Not}", "Not");
outcomeMethod = attributeData.ConstructorArguments[1].Value?.ToString();
}
else if (attributeData.ConstructorArguments.Length == 3)
{
positiveName = attributeData.ConstructorArguments[0].Value?.ToString();
negativeName = attributeData.ConstructorArguments[1].Value?.ToString();
outcomeMethod = attributeData.ConstructorArguments[2].Value?.ToString();
}

if (outcomeMethod == null || name == null)
if (outcomeMethod == null || positiveName == null)
{
return null;
}
Expand All @@ -125,7 +134,8 @@ private static void Execute(ExpectationToGenerate expectationToGenerate, SourceP
containingNamespace,
classSymbol.Name,
targetType,
name,
positiveName,
negativeName,
outcomeMethod,
attributeData);
}
Expand Down
28 changes: 19 additions & 9 deletions Source/aweXpect.SourceGenerators/Helpers/ExpectationToGenerate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,35 @@ internal readonly record struct ExpectationToGenerate
public ExpectationToGenerate(string @namespace,
string className,
INamedTypeSymbol targetType,
string name,
string positiveName,
string? negativeName,
string outcomeMethod,
AttributeData attributeData)
{
Namespace = @namespace;
ClassName = className;
TargetType = targetType.ToDisplayString();
Name = name.Replace("{Not}", "");
NegatedName = name.Replace("{Not}", "Not");
IncludeNegated = name.Contains("{Not}");
Name = positiveName;
NegatedName = negativeName;
IncludeNegated = negativeName is not null;
OutcomeMethod = outcomeMethod;

string expectationText = outcomeMethod;
string? positiveExpectationText = null;
string? negativeExpectationText = null;
foreach (KeyValuePair<string, TypedConstant> namedArgument in attributeData.NamedArguments)
{
switch (namedArgument.Key)
{
case "ExpectationText":
expectationText = namedArgument.Value.Value?.ToString() ?? expectationText;
string? expectationText = namedArgument.Value.Value?.ToString();
positiveExpectationText = expectationText?.Replace("{not}", "").Replace(" ", " ");
negativeExpectationText = expectationText?.Replace("{not}", " not ").Replace(" ", " ");
break;
case "PositiveExpectationText":
positiveExpectationText = namedArgument.Value.Value?.ToString();
break;
case "NegativeExpectationText":
negativeExpectationText = namedArgument.Value.Value?.ToString();
break;
case "Remarks":
Remarks = namedArgument.Value.Value?.ToString();
Expand All @@ -44,16 +54,16 @@ public ExpectationToGenerate(string @namespace,
TargetType += "?";
}

ExpectationText = expectationText.Replace("{not}", "").Replace(" ", " ");
NegatedExpectationText = expectationText.Replace("{not}", " not ").Replace(" ", " ");
ExpectationText = positiveExpectationText ?? positiveName;
NegatedExpectationText = negativeExpectationText ?? $"not {positiveName}";
FileName = $"{ClassName}.{Name}.g.cs";
}

public string[] Usings { get; } = [];
public string FileName { get; }
public bool IncludeNegated { get; }
public bool IsNullable { get; }
public string NegatedName { get; }
public string? NegatedName { get; }
public string Namespace { get; }
public string ClassName { get; }
public string TargetType { get; }
Expand Down
112 changes: 71 additions & 41 deletions Source/aweXpect.SourceGenerators/Helpers/SourceGenerationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,34 @@ namespace aweXpect.SourceGenerators;
[System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true)]
internal class CreateExpectationOnAttribute<TTarget> : System.Attribute
{
public CreateExpectationOnAttribute(string methodName, string name)
{
TargetType = typeof(TTarget);
MethodName = methodName;
Name = name;
}

public Type TargetType { get; }
public string MethodName { get; }
public string Name { get; set; }
public string? ExpectationText { get; set; }
public string? Remarks { get; set; }
public string[] Using { get; set; } = [];
public CreateExpectationOnAttribute(string name, string outcomeMethod)
{
TargetType = typeof(TTarget);
PositiveName = name.Replace("{Not}", "");
if (name.Contains("{Not}"))
{
NegativeName = name.Replace("{Not}", "Not");
}
OutcomeMethod = outcomeMethod;
}

public CreateExpectationOnAttribute(string positiveName, string negativeName, string outcomeMethod)
{
TargetType = typeof(TTarget);
PositiveName = positiveName;
NegativeName = negativeName;
OutcomeMethod = outcomeMethod;
}

public Type TargetType { get; }
public string PositiveName { get; }
public string? NegativeName { get; }
public string OutcomeMethod { get; set; }
public string? ExpectationText { get; set; }
public string? PositiveExpectationText { get; set; }
public string? NegativeExpectationText { get; set; }
public string? Remarks { get; set; }
public string[] Using { get; set; } = [];
}
#nullable disable
""";
Expand All @@ -47,19 +62,34 @@ namespace aweXpect.SourceGenerators;
[System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true)]
internal class CreateExpectationOnNullableAttribute<TTarget> : System.Attribute
{
public CreateExpectationOnNullableAttribute(string methodName, string name)
{
TargetType = typeof(TTarget);
MethodName = methodName;
Name = name;
}

public Type TargetType { get; }
public string MethodName { get; }
public string Name { get; set; }
public string? ExpectationText { get; set; }
public string? Remarks { get; set; }
public string[] Using { get; set; } = [];
public CreateExpectationOnNullableAttribute(string name, string outcomeMethod)
{
TargetType = typeof(TTarget);
PositiveName = name.Replace("{Not}", "");
if (name.Contains("{Not}"))
{
NegativeName = name.Replace("{Not}", "Not");
}
OutcomeMethod = outcomeMethod;
}

public CreateExpectationOnNullableAttribute(string positiveName, string negativeName, string outcomeMethod)
{
TargetType = typeof(TTarget);
PositiveName = positiveName;
NegativeName = negativeName;
OutcomeMethod = outcomeMethod;
}

public Type TargetType { get; }
public string PositiveName { get; }
public string? NegativeName { get; }
public string OutcomeMethod { get; set; }
public string? ExpectationText { get; set; }
public string? PositiveExpectationText { get; set; }
public string? NegativeExpectationText { get; set; }
public string? Remarks { get; set; }
public string[] Using { get; set; } = [];
}
#nullable disable
""";
Expand All @@ -78,26 +108,26 @@ namespace {{expectationToGenerate.Namespace}};
#nullable enable
public static partial class {{expectationToGenerate.ClassName}}
{
/// <summary>
/// Verifies that the subject {{expectationToGenerate.ExpectationText}}.
/// </summary>{{expectationToGenerate.AppendRemarks()}}
public static AndOrResult<{{expectationToGenerate.TargetType}}, IThat<{{expectationToGenerate.TargetType}}>> {{expectationToGenerate.Name}}(this IThat<{{expectationToGenerate.TargetType}}> source)
=> new(source.Get().ExpectationBuilder.AddConstraint((it, grammars) =>
new {{expectationToGenerate.Name}}Constraint(it, grammars)),
source);
/// <summary>
/// Verifies that the subject {{expectationToGenerate.ExpectationText}}.
/// </summary>{{expectationToGenerate.AppendRemarks()}}
public static AndOrResult<{{expectationToGenerate.TargetType}}, IThat<{{expectationToGenerate.TargetType}}>> {{expectationToGenerate.Name}}(this IThat<{{expectationToGenerate.TargetType}}> source)
=> new(source.Get().ExpectationBuilder.AddConstraint((it, grammars) =>
new {{expectationToGenerate.Name}}Constraint(it, grammars)),
source);


""";
if (expectationToGenerate.IncludeNegated)
{
result += $$"""
/// <summary>
/// Verifies that the subject {{expectationToGenerate.NegatedExpectationText}}.
/// </summary>{{expectationToGenerate.AppendRemarks()}}
public static AndOrResult<{{expectationToGenerate.TargetType}}, IThat<{{expectationToGenerate.TargetType}}>> {{expectationToGenerate.NegatedName}}(this IThat<{{expectationToGenerate.TargetType}}> source)
=> new(source.Get().ExpectationBuilder.AddConstraint((it, grammars) =>
new {{expectationToGenerate.Name}}Constraint(it, grammars).Invert()),
source);
/// <summary>
/// Verifies that the subject {{expectationToGenerate.NegatedExpectationText}}.
/// </summary>{{expectationToGenerate.AppendRemarks()}}
public static AndOrResult<{{expectationToGenerate.TargetType}}, IThat<{{expectationToGenerate.TargetType}}>> {{expectationToGenerate.NegatedName}}(this IThat<{{expectationToGenerate.TargetType}}> source)
=> new(source.Get().ExpectationBuilder.AddConstraint((it, grammars) =>
new {{expectationToGenerate.Name}}Constraint(it, grammars).Invert()),
source);


""";
Expand All @@ -113,7 +143,7 @@ private sealed class {{expectationToGenerate.Name}}Constraint(string it, Expecta
public ConstraintResult IsMetBy({{expectationToGenerate.TargetType}} actual)
{
Actual = actual;
Outcome = actual is not null && {{expectationToGenerate.OutcomeMethod.Replace("{value}", "actual.Value")}} ? Outcome.Success : Outcome.Failure;
Outcome = actual is not null && {{expectationToGenerate.OutcomeMethod.Replace("{value}", "actual")}} ? Outcome.Success : Outcome.Failure;
return this;
}

Expand Down
2 changes: 1 addition & 1 deletion Source/aweXpect/That/Chars/ThatNullableChar.IsALetter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace aweXpect;

[CreateExpectationOnNullable<char>("Is{Not}ALetter", "char.IsLetter({value})",
[CreateExpectationOnNullable<char>("Is{Not}ALetter", "char.IsLetter({value}.Value)",
ExpectationText = "is {not} a letter",
Remarks = """
This means, that the specified Unicode character is categorized as a Unicode letter.<br />
Expand Down
2 changes: 1 addition & 1 deletion Source/aweXpect/That/Chars/ThatNullableChar.IsANumber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace aweXpect;

[CreateExpectationOnNullable<char>("Is{Not}ANumber", "char.IsNumber({value})",
[CreateExpectationOnNullable<char>("Is{Not}ANumber", "char.IsNumber({value}.Value)",
ExpectationText = "is {not} a number",
Remarks = """
This means, that the specified Unicode character is categorized as a number.<br />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
namespace aweXpect;

#if NET8_0_OR_GREATER
[CreateExpectationOnNullable<char>("Is{Not}AnAsciiLetter", "char.IsAsciiLetter({value})",
[CreateExpectationOnNullable<char>("Is{Not}AnAsciiLetter", "char.IsAsciiLetter({value}.Value)",
ExpectationText = "is {not} an ASCII letter",
Remarks = """
This means, that the specified Unicode character is categorized as an ASCII letter.<br />
<seealso cref="char.IsAsciiLetter(char)" />
"""
)]
#else
[CreateExpectationOnNullable<char>("Is{Not}AnAsciiLetter", "{value} is >= 'a' and <= 'z' or >= 'A' and <= 'Z'",
[CreateExpectationOnNullable<char>("Is{Not}AnAsciiLetter", "{value}.Value is >= 'a' and <= 'z' or >= 'A' and <= 'Z'",
ExpectationText = "is {not} an ASCII letter"
)]
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace aweXpect;

[CreateExpectationOnNullable<char>("Is{Not}WhiteSpace", "char.IsWhiteSpace({value})",
[CreateExpectationOnNullable<char>("Is{Not}WhiteSpace", "char.IsWhiteSpace({value}.Value)",
ExpectationText = "is {not} white-space",
Remarks = """
This means, that the specified Unicode character is categorized as white-space.<br />
Expand Down
14 changes: 14 additions & 0 deletions Source/aweXpect/That/Uris/ThatUri.HasDefaultPort.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using aweXpect.SourceGenerators;

namespace aweXpect;

[CreateExpectationOn<Uri>("HasDefaultPort", "DoesNotHaveDefaultPort", "{value}.IsDefaultPort",
PositiveExpectationText = "has the default port for the used scheme",
NegativeExpectationText = "does not have the default port for the used scheme",
Using = ["System",],
Remarks = """
<seealso cref="Uri.IsDefaultPort" />
"""
)]
public static partial class ThatUri;
13 changes: 13 additions & 0 deletions Source/aweXpect/That/Uris/ThatUri.IsAbsolute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using aweXpect.SourceGenerators;

namespace aweXpect;

[CreateExpectationOn<Uri>("Is{Not}Absolute", "{value}.IsAbsoluteUri",
ExpectationText = "is {not} an absolute URI",
Using = ["System",],
Remarks = """
<seealso cref="Uri.IsAbsoluteUri" />
"""
)]
public static partial class ThatUri;
13 changes: 13 additions & 0 deletions Source/aweXpect/That/Uris/ThatUri.IsFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using aweXpect.SourceGenerators;

namespace aweXpect;

[CreateExpectationOn<Uri>("Is{Not}File", "{value}.IsFile",
ExpectationText = "is {not} a file URI",
Using = ["System",],
Remarks = """
<seealso cref="Uri.IsFile" />
"""
)]
public static partial class ThatUri;
14 changes: 14 additions & 0 deletions Source/aweXpect/That/Uris/ThatUri.IsLoopback.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using aweXpect.SourceGenerators;

namespace aweXpect;

[CreateExpectationOn<Uri>("Is{Not}Loopback", "{value}.IsLoopback",
PositiveExpectationText = "references the local host",
NegativeExpectationText = "does not reference the local host",
Using = ["System",],
Remarks = """
<seealso cref="Uri.IsLoopback" />
"""
)]
public static partial class ThatUri;
13 changes: 13 additions & 0 deletions Source/aweXpect/That/Uris/ThatUri.IsUnc.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using aweXpect.SourceGenerators;

namespace aweXpect;

[CreateExpectationOn<Uri>("Is{Not}Unc", "{value}.IsUnc",
ExpectationText = "is {not} an UNC path",
Using = ["System",],
Remarks = """
<seealso cref="Uri.IsUnc" />
"""
)]
public static partial class ThatUri;
8 changes: 8 additions & 0 deletions Source/aweXpect/That/Uris/ThatUri.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace aweXpect;

/// <summary>
/// Expectations on <see cref="Uri" /> values.
/// </summary>
public static partial class ThatUri;
3 changes: 2 additions & 1 deletion Source/aweXpect/aweXpect.csproj.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=that_005Cstreams/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=that_005Cstrings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=that_005Ctimeonlys/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=that_005Ctimespans/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=that_005Ctimespans/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=that_005Curis/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
13 changes: 13 additions & 0 deletions Tests/aweXpect.Api.Tests/Expected/aweXpect_net8.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,19 @@ namespace aweXpect
public static aweXpect.Results.TimeToleranceResult<System.TimeSpan, aweXpect.Core.IThat<System.TimeSpan>> IsOneOf(this aweXpect.Core.IThat<System.TimeSpan> source, params System.TimeSpan?[] expected) { }
public static aweXpect.Results.AndOrResult<System.TimeSpan, aweXpect.Core.IThat<System.TimeSpan>> IsPositive(this aweXpect.Core.IThat<System.TimeSpan> source) { }
}
public static class ThatUri
{
public static aweXpect.Results.AndOrResult<System.Uri, aweXpect.Core.IThat<System.Uri>> DoesNotHaveDefaultPort(this aweXpect.Core.IThat<System.Uri> source) { }
public static aweXpect.Results.AndOrResult<System.Uri, aweXpect.Core.IThat<System.Uri>> HasDefaultPort(this aweXpect.Core.IThat<System.Uri> source) { }
public static aweXpect.Results.AndOrResult<System.Uri, aweXpect.Core.IThat<System.Uri>> IsAbsolute(this aweXpect.Core.IThat<System.Uri> source) { }
public static aweXpect.Results.AndOrResult<System.Uri, aweXpect.Core.IThat<System.Uri>> IsFile(this aweXpect.Core.IThat<System.Uri> source) { }
public static aweXpect.Results.AndOrResult<System.Uri, aweXpect.Core.IThat<System.Uri>> IsLoopback(this aweXpect.Core.IThat<System.Uri> source) { }
public static aweXpect.Results.AndOrResult<System.Uri, aweXpect.Core.IThat<System.Uri>> IsNotAbsolute(this aweXpect.Core.IThat<System.Uri> source) { }
public static aweXpect.Results.AndOrResult<System.Uri, aweXpect.Core.IThat<System.Uri>> IsNotFile(this aweXpect.Core.IThat<System.Uri> source) { }
public static aweXpect.Results.AndOrResult<System.Uri, aweXpect.Core.IThat<System.Uri>> IsNotLoopback(this aweXpect.Core.IThat<System.Uri> source) { }
public static aweXpect.Results.AndOrResult<System.Uri, aweXpect.Core.IThat<System.Uri>> IsNotUnc(this aweXpect.Core.IThat<System.Uri> source) { }
public static aweXpect.Results.AndOrResult<System.Uri, aweXpect.Core.IThat<System.Uri>> IsUnc(this aweXpect.Core.IThat<System.Uri> source) { }
}
}
namespace aweXpect.Equivalency
{
Expand Down
Loading
Loading