Skip to content

Commit

Permalink
Make all code functional but not yet readable/maintainable
Browse files Browse the repository at this point in the history
  • Loading branch information
Bartleby2718 committed Apr 5, 2024
1 parent e6e63c8 commit fe925c0
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ public void TestMethod()
[Test]
public void VerifyAreEqualFixWhenToleranceExistsWithNamedArgumentButInNonstandardOrder()
{
var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@"
var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@"
public void TestMethod()
{{
{
↓ClassicAssert.AreEqual(delta: 0.0000001d, actual: 3d, expected: 2d);
}}");
}");
var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@"
public void TestMethod()
{
Expand All @@ -59,11 +59,11 @@ public void TestMethod()
[Test]
public void VerifyAreEqualFixWithNamedParametersInNonstandardOrder()
{
var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@"
var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@"
public void TestMethod()
{{
{
↓ClassicAssert.AreEqual(actual: 3d, expected: 2d);
}}");
}");
var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@"
public void TestMethod()
{
Expand Down Expand Up @@ -120,6 +120,24 @@ public void TestMethod()
RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription);
}

[Test]
public void VerifyAreEqualFixWithMessageAnd2ParamsInNonstandardOrder()
{
var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@"
public void TestMethod()
{
var actual = 3d;
↓ClassicAssert.AreEqual(delta: 0.0000001d, expected: 2d, actual: actual, args: new[] { ""Guid.NewGuid()"", Guid.NewGuid().ToString() }, message: ""message-id: {0}, {1}"");
}");
var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@"
public void TestMethod()
{
var actual = 3d;
Assert.That(actual: actual, Is.EqualTo(expected: 2d).Within(0.0000001d), $""message-id: {""Guid.NewGuid()""}, {Guid.NewGuid().ToString() }"");
}"); // TODO: Fix whitespace
RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription);
}

[Test]
public void VerifyAreEqualFixWhenToleranceExists()
{
Expand Down Expand Up @@ -200,6 +218,22 @@ public void TestMethod()
RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription);
}

[Test]
public void VerifyAreEqualFixWhenToleranceExistsWithMessageAndParamsLiteralExpressionSyntax()
{
var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@"
public void TestMethod()
{
↓ClassicAssert.AreEqual(2d, 3d, 0.0000001d, ""message-id: {0}"", ""mystring"");
}");
var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@"
public void TestMethod()
{
Assert.That(3d, Is.EqualTo(2d).Within(0.0000001d), $""message-id: {""mystring""}"");
}");
RoslynAssert.CodeFix(analyzer, fix, expectedDiagnostic, code, fixedCode, fixTitle: ClassicModelAssertUsageCodeFix.TransformToConstraintModelDescription);
}

[Test]
public void CodeFixPreservesLineBreakBeforeMessage()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
Expand All @@ -17,7 +16,9 @@ public sealed class AreEqualClassicModelAssertUsageCodeFix
{
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(AnalyzerIdentifiers.AreEqualUsage);

protected override List<ArgumentSyntax> UpdateArguments(Diagnostic diagnostic, IReadOnlyDictionary<string, ArgumentSyntax> argumentNamesToArguments)
protected override (ArgumentSyntax ActualArgument, ArgumentSyntax ConstraintArgument) UpdateArguments(
Diagnostic diagnostic,
IReadOnlyDictionary<string, ArgumentSyntax> argumentNamesToArguments)
{
var expectedArgument = argumentNamesToArguments[NUnitFrameworkConstants.NameOfExpectedParameter];
var actualArgument = argumentNamesToArguments[NUnitFrameworkConstants.NameOfActualParameter];
Expand Down Expand Up @@ -50,18 +51,7 @@ protected override List<ArgumentSyntax> UpdateArguments(Diagnostic diagnostic, I
SyntaxFactory.SingletonSeparatedList(toleranceArgumentNoColon)));
}

var arguments = new List<ArgumentSyntax>() { actualArgument, SyntaxFactory.Argument(equalToInvocationNode) };
var handledParameterNames = new[]
{
NUnitFrameworkConstants.NameOfExpectedParameter,
NUnitFrameworkConstants.NameOfActualParameter,
NameOfDeltaParameter,
};
return arguments
.Concat(argumentNamesToArguments
.Where(kvp => !handledParameterNames.Contains(kvp.Key))
.Select(kvp => kvp.Value))
.ToList();
return (actualArgument, SyntaxFactory.Argument(equalToInvocationNode));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NUnit.Analyzers.Constants;
using NUnit.Analyzers.Extensions;
using NUnit.Analyzers.Helpers;

Expand Down Expand Up @@ -52,34 +54,65 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
if (newInvocationNode is null)
return;

// Now, replace the arguments.
List<ArgumentSyntax> arguments = invocationNode.ArgumentList.Arguments.ToList();
var methodSymbol = (IMethodSymbol)semanticModel.GetSymbolInfo(invocationNode).Symbol!;

// See if we need to cast the arguments when they were using a specific classic overload.
arguments[0] = CastIfNecessary(arguments[0]);
if (arguments.Count > 1)
arguments[1] = CastIfNecessary(arguments[1]);
Dictionary<string, ArgumentSyntax> argumentNamesToArguments = new();
const string NameOfArgsParameter = "args";

var argumentNamesToArguments = arguments.ToDictionary(
argument => GetParameterName(argument),
argument => argument);
// There can be 0 to any number of message and parameters.
List<ArgumentSyntax> messageAndParams = new();
var arguments = invocationNode.ArgumentList.Arguments.ToList();
for (var i = 0; i < arguments.Count; ++i)
{
var argument = arguments[i];
if (i < methodSymbol.Parameters.Length
&& (argument.NameColon?.Name.Identifier.Text ?? methodSymbol.Parameters[i].Name) is { } argumentName
&& argumentName is not (NUnitFrameworkConstants.NameOfMessageParameter or NameOfArgsParameter))
{
// See if we need to cast the arguments when they were using a specific classic overload.
argumentNamesToArguments[argumentName] =
argumentName is NUnitFrameworkConstants.NameOfExpectedParameter or NUnitFrameworkConstants.NameOfActualParameter
? CastIfNecessary(argument)
: argument;
}
else
{
messageAndParams.Add(argument);
}
}

// Remove null message to avoid ambiguous calls.
if (argumentNamesToArguments.TryGetValue(NUnitFrameworkConstants.NameOfMessageParameter, out var messageArgument)
&& messageArgument.Expression.IsKind(SyntaxKind.NullLiteralExpression))
{
argumentNamesToArguments.Remove(NUnitFrameworkConstants.NameOfMessageParameter);
}

// Now, replace the arguments.
List<ArgumentSyntax> newArguments = new();
ArgumentSyntax actualArgument, constraintArgument;

// Do the rule specific conversion
if (typeArguments is null)
arguments = this.UpdateArguments(diagnostic, argumentNamesToArguments);
{
(actualArgument, constraintArgument) = this.UpdateArguments(diagnostic, argumentNamesToArguments);
newArguments.Add(actualArgument);
newArguments.Add(constraintArgument);
if (CodeFixHelper.GetInterpolatedMessageArgumentOrDefault(messageAndParams) is { } interpolatedMessageArgument)
{
newArguments.Add(interpolatedMessageArgument);
}
}
else
{
this.UpdateArguments(diagnostic, arguments, typeArguments);

// Remove null message to avoid ambiguous calls.
if (arguments.Count == 3 && arguments[2].Expression.IsKind(SyntaxKind.NullLiteralExpression))
{
arguments.RemoveAt(2);
// Do the format spec, params to formattable string conversion
CodeFixHelper.UpdateStringFormatToFormattableString(arguments, this.MinimumNumberOfParameters);
newArguments = arguments;
}

// Do the format spec, params to formattable string conversion
CodeFixHelper.UpdateStringFormatToFormattableString(arguments, this.MinimumNumberOfParameters);

var newArgumentsList = invocationNode.ArgumentList.WithArguments(arguments);
var newArgumentsList = invocationNode.ArgumentList.WithArguments(newArguments);
newInvocationNode = newInvocationNode.WithArgumentList(newArgumentsList);

context.CancellationToken.ThrowIfCancellationRequested();
Expand All @@ -105,16 +138,6 @@ ArgumentSyntax CastIfNecessary(ArgumentSyntax argument)
argument.Expression));
}

string GetParameterName(ArgumentSyntax argument)
{
if (argument.NameColon?.Name.Identifier.Text is { } argumentName)
return argumentName;

var methodSymbol = (IMethodSymbol)semanticModel.GetSymbolInfo(invocationNode).Symbol!;
var index = invocationNode.ArgumentList.Arguments.IndexOf(argument);
return methodSymbol!.Parameters[index].Name;
}

string? GetUserDefinedImplicitTypeConversion(ExpressionSyntax expression)
{
var typeInfo = semanticModel.GetTypeInfo(expression, context.CancellationToken);
Expand All @@ -135,7 +158,7 @@ string GetParameterName(ArgumentSyntax argument)
}
}

protected virtual List<ArgumentSyntax> UpdateArguments(
protected virtual (ArgumentSyntax ActualArgument, ArgumentSyntax ConstraintArgument) UpdateArguments(
Diagnostic diagnostic,
IReadOnlyDictionary<string, ArgumentSyntax> argumentNamesToArguments)
{
Expand Down
1 change: 1 addition & 0 deletions src/nunit.analyzers/Constants/NUnitFrameworkConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ public static class NUnitFrameworkConstants
public const string NameOfConditionParameter = "condition";
public const string NameOfExpectedParameter = "expected";
public const string NameOfExpressionParameter = "expression";
public const string NameOfMessageParameter = "message";

public const string NameOfConstraintExpressionAnd = "And";
public const string NameOfConstraintExpressionOr = "Or";
Expand Down
36 changes: 36 additions & 0 deletions src/nunit.analyzers/Helpers/CodeFixHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,42 @@ internal static class CodeFixHelper
return invocationExpression.ReplaceNode(invocationTargetNode, newInvocationTargetNode);
}

/// <summary>
/// This is assumed to be arguments for an 'Assert.That(actual, constraint, "...: {0} - {1}", param0, param1)`
/// which needs converting into 'Assert.That(actual, constraint, $"...: {param0} - {param1}").
/// </summary>
/// <param name="arguments">The arguments passed to the 'Assert' method. </param>
/// <param name="minimumNumberOfArguments">The argument needed for the actual method, any more are assumed messages.</param>
public static ArgumentSyntax? GetInterpolatedMessageArgumentOrDefault(List<ArgumentSyntax> messageAndParams)
{
if (messageAndParams.Count == 0)
return null;

var messageArgument = messageAndParams.SingleOrDefault(a => a.NameColon?.Name.Identifier.Text == NUnitFrameworkConstants.NameOfMessageParameter)
?? messageAndParams.First();
var formatSpecificationArgument = messageArgument.Expression;
if (formatSpecificationArgument.IsKind(SyntaxKind.NullLiteralExpression))
return null;

// We only support converting if the format specification is a constant string.
if (messageAndParams.Count == 1 || formatSpecificationArgument is not LiteralExpressionSyntax literalExpression)
return messageAndParams[0];

var formatSpecification = literalExpression.Token.ValueText;

// var formatArgumentExpressions = new List<ExpressionSyntax>(capacity: messageAndParams.Count - 1);
var argsExpression = messageAndParams.Single(a => a != messageArgument).Expression;
var formatArgumentExpressions = argsExpression is ImplicitArrayCreationExpressionSyntax args
? args.Initializer.Expressions.ToArray()
: new[] { argsExpression };

var interpolatedStringContent = UpdateStringFormatToFormattableString(formatSpecification, formatArgumentExpressions);
var interpolatedString = SyntaxFactory.InterpolatedStringExpression(
SyntaxFactory.Token(SyntaxKind.InterpolatedStringStartToken),
SyntaxFactory.List(interpolatedStringContent));
return SyntaxFactory.Argument(interpolatedString);
}

/// <summary>
/// This is assumed to be arguments for an 'Assert.That(actual, constraint, "...: {0} - {1}", param0, param1)`
/// which needs converting into 'Assert.That(actual, constraint, $"...: {param0} - {param1}").
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private static void AnalyzeNUnit4AssertInvocation(OperationAnalysisContext conte
IParameterSymbol parameter = parameters[formatParameterIndex];
if (parameter.IsOptional)
{
if (parameter.Name == "message")
if (parameter.Name == NUnitFrameworkConstants.NameOfMessageParameter)
break;

// Overload with FormattableString or Func<string> overload
Expand Down

0 comments on commit fe925c0

Please sign in to comment.