Skip to content

Commit

Permalink
[rel/3.3] Fix MSTEST0014 problems with arrays (#2610)
Browse files Browse the repository at this point in the history
  • Loading branch information
Evangelink authored Mar 20, 2024
1 parent d7b92d0 commit 22bc5fc
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 15 deletions.
48 changes: 33 additions & 15 deletions src/Analyzers/MSTest.Analyzers/DataRowShouldBeValidAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ private static void AnalyzeSymbol(

private static void AnalyzeAttribute(SymbolAnalysisContext context, AttributeData attribute, IMethodSymbol methodSymbol)
{
if (attribute.ApplicationSyntaxReference?.GetSyntax() is not { } syntax)
if (attribute.ApplicationSyntaxReference?.GetSyntax() is not { } dataRowSyntax)
{
return;
}
Expand All @@ -126,21 +126,19 @@ private static void AnalyzeAttribute(SymbolAnalysisContext context, AttributeDat
// constructor argument.
if (methodSymbol.Parameters.Length == 0)
{
context.ReportDiagnostic(syntax.CreateDiagnostic(
context.ReportDiagnostic(dataRowSyntax.CreateDiagnostic(
ArgumentCountMismatchRule,
attribute.ConstructorArguments.Length,
methodSymbol.Parameters.Length));
return;
}

// Possible count mismatch depending on whether last method parameter is an array or not.
IParameterSymbol lastMethodParameter = methodSymbol.Parameters.Last();
bool lastMethodParameterIsArray = lastMethodParameter.Type.Kind == SymbolKind.ArrayType;
if (attribute.ConstructorArguments.Length == 0)
{
if (!lastMethodParameterIsArray)
if (methodSymbol.Parameters[^1].Type.Kind != SymbolKind.ArrayType)
{
context.ReportDiagnostic(syntax.CreateDiagnostic(
context.ReportDiagnostic(dataRowSyntax.CreateDiagnostic(
ArgumentCountMismatchRule,
attribute.ConstructorArguments.Length,
methodSymbol.Parameters.Length));
Expand All @@ -160,7 +158,7 @@ private static void AnalyzeAttribute(SymbolAnalysisContext context, AttributeDat

if (IsArgumentCountMismatch(constructorArguments.Length, methodSymbol.Parameters))
{
context.ReportDiagnostic(syntax.CreateDiagnostic(
context.ReportDiagnostic(dataRowSyntax.CreateDiagnostic(
ArgumentCountMismatchRule,
constructorArguments.Length,
methodSymbol.Parameters.Length));
Expand All @@ -169,34 +167,54 @@ private static void AnalyzeAttribute(SymbolAnalysisContext context, AttributeDat

// Check constructor argument types match method parameter types.
List<(int ConstructorArgumentIndex, int MethodParameterIndex)> typeMismatchIndices = new();
for (int i = 0; i < constructorArguments.Length; ++i)
for (int currentArgumentIndex = 0; currentArgumentIndex < constructorArguments.Length; currentArgumentIndex++)
{
// Null is considered as default for non-nullable types.
if (constructorArguments[i].IsNull)
if (constructorArguments[currentArgumentIndex].IsNull)
{
continue;
}

ITypeSymbol? argumentType = constructorArguments[i].Type;
ITypeSymbol paramType = (lastMethodParameterIsArray && i >= methodSymbol.Parameters.Length - 1)
? ((IArrayTypeSymbol)lastMethodParameter.Type).ElementType
: methodSymbol.Parameters[i].Type;
ITypeSymbol? argumentType = constructorArguments[currentArgumentIndex].Type;
ITypeSymbol paramType = GetParameterType(methodSymbol, currentArgumentIndex, constructorArguments.Length);

if (argumentType is not null && !argumentType.IsAssignableTo(paramType, context.Compilation))
{
typeMismatchIndices.Add((i, Math.Min(i, methodSymbol.Parameters.Length - 1)));
typeMismatchIndices.Add((currentArgumentIndex, Math.Min(currentArgumentIndex, methodSymbol.Parameters.Length - 1)));
}
}

// Report diagnostics if there's any type mismatch.
if (typeMismatchIndices.Count > 0)
{
context.ReportDiagnostic(syntax.CreateDiagnostic(
context.ReportDiagnostic(dataRowSyntax.CreateDiagnostic(
ArgumentTypeMismatchRule,
string.Join(", ", typeMismatchIndices)));
}
}

private static ITypeSymbol GetParameterType(IMethodSymbol methodSymbol, int currentArgumentIndex, int argumentsCount)
{
if (currentArgumentIndex >= methodSymbol.Parameters.Length - 1)
{
IParameterSymbol lastParameter = methodSymbol.Parameters[^1];

// When last parameter is params, we want to check that the extra arguments match the type of the array
if (lastParameter.IsParams)
{
return ((IArrayTypeSymbol)lastParameter.Type).ElementType;
}

// When only parameter is array and we have more than one argument, we want to check the array type
if (argumentsCount > 1 && methodSymbol.Parameters.Length == 1 && lastParameter.Type.Kind == SymbolKind.ArrayType)
{
return ((IArrayTypeSymbol)lastParameter.Type).ElementType;
}
}

return methodSymbol.Parameters[currentArgumentIndex].Type;
}

private static bool IsArgumentCountMismatch(int constructorArgumentsLength, ImmutableArray<IParameterSymbol> methodParameters)
{
int optionalParametersCount = methodParameters.Count(x => x.HasExplicitDefaultValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,29 @@ public void TestMethod1(object[] o)
await VerifyCS.VerifyAnalyzerAsync(code);
}

public async Task WhenDataRowPassesOneItemAndParameterExpectsArray_Diagnostic()
{
var code = """
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class MyTestClass
{
[{|#0:DataRow(1)|}]
[TestMethod]
public void TestMethod1(object[] o)
{
}
}
""";

await VerifyCS.VerifyAnalyzerAsync(
code,
VerifyCS.Diagnostic(DataRowShouldBeValidAnalyzer.ArgumentTypeMismatchRule)
.WithLocation(0)
.WithArguments((0, 0)));
}

public async Task WhenDataRowHasThreeArgumentsAndMethodHasAnIntegerAndAnArrayArgument_Diagnostic()
{
var code = """
Expand Down Expand Up @@ -487,4 +510,36 @@ await VerifyCS.VerifyAnalyzerAsync(
VerifyCS.Diagnostic(DataRowShouldBeValidAnalyzer.ArgumentCountMismatchRule).WithLocation(1).WithArguments(1, 3),
VerifyCS.Diagnostic(DataRowShouldBeValidAnalyzer.ArgumentCountMismatchRule).WithLocation(2).WithArguments(1, 5));
}

public async Task Testfx_2606_NullArgumentForArray()
{
string code = """
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class MyTestClass
{
[DataTestMethod]
[DataRow(
"123",
new string[] { "something" },
null)]
[DataRow(
"123",
null,
new string[] { "something" })]
public void TestSomething(
string x,
string[] y,
string[] z)
{
Assert.AreEqual("123", x);
Assert.IsNotNull(y);
Assert.IsNull(z);
}
}
""";

await VerifyCS.VerifyAnalyzerAsync(code);
}
}
2 changes: 2 additions & 0 deletions test/UnitTests/MSTest.Analyzers.UnitTests/testsbaseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.ClassInitializeShouldBeValidAna
MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.ClassInitializeShouldBeValidAnalyzerTests.WhenClassInitializeReturnTypeIsNotValid_Diagnostic()
MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.ClassInitializeShouldBeValidAnalyzerTests.WhenClassInitializeReturnTypeIsValid_NoDiagnostic()
MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.DataRowShouldBeValidAnalyzerTests.DefaultArguments()
MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.DataRowShouldBeValidAnalyzerTests.Testfx_2606_NullArgumentForArray()
MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.DataRowShouldBeValidAnalyzerTests.WhenDataRowHasArgumentMismatchWithTestMethod_Diagnostic()
MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.DataRowShouldBeValidAnalyzerTests.WhenDataRowHasArgumentMismatchWithTestMethod2_Diagnostic()
MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.DataRowShouldBeValidAnalyzerTests.WhenDataRowHasArgumentMismatchWithTestMethod3_Diagnostic()
Expand All @@ -85,6 +86,7 @@ MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.DataRowShouldBeValidAnalyzerTes
MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.DataRowShouldBeValidAnalyzerTests.WhenDataRowIsCorrectlyDefinedWithThreeArgumentsAndMethodHasArrayArgument_NoDiagnostic()
MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.DataRowShouldBeValidAnalyzerTests.WhenDataRowIsCorrectlyDefinedWithThreeArgumentsAndMethodHasParamsArgument_NoDiagnostic()
MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.DataRowShouldBeValidAnalyzerTests.WhenDataRowIsNotSetOnATestMethod_Diagnostic()
MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.DataRowShouldBeValidAnalyzerTests.WhenDataRowPassesOneItemAndParameterExpectsArray_Diagnostic()
MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.PublicClassShouldBeTestClassAnalyzerTests.WhenTypeIsNotPublicAndNotTestClass_NoDiagnostic()
MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.PublicClassShouldBeTestClassAnalyzerTests.WhenTypeIsPublicAndNotTestClass_Diagnostic()
MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.TestClassShouldBeValidAnalyzerTests.WhenClassIsGeneric_NoDiagnostic()
Expand Down

0 comments on commit 22bc5fc

Please sign in to comment.