diff --git a/.github/workflows/github-actions-release.yml b/.github/workflows/github-actions-release.yml
index 1c0a449..03c1a56 100644
--- a/.github/workflows/github-actions-release.yml
+++ b/.github/workflows/github-actions-release.yml
@@ -7,7 +7,7 @@ on:
type: string
description: The version of the library
required: true
- default: 2.0.0
+ default: 2.0.1
VersionSuffix:
type: string
description: The version suffix of the library (for example rc.1)
diff --git a/README.md b/README.md
index caeb6d7..9507ef5 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
# PosInformatique.Moq.Analyzers
-
[](https://www.nuget.org/packages/PosInformatique.Moq.Analyzers/)
[](https://www.nuget.org/packages/PosInformatique.Moq.Analyzers/)
@@ -7,8 +6,6 @@
[](https://github.com/PosInformatique/PosInformatique.Moq.Analyzers/actions)
[](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0)
-
-
PosInformatique.Moq.Analyzers is a set of analyzers to verify syntax and code design when writing the unit tests using the [Moq](https://github.com/devlooped/moq) library.
The analyzers are compiled against [.NET Standard 2.0](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0),
diff --git a/src/Moq.Analyzers/Analyzers/ConstructorArgumentsAnalyzer.cs b/src/Moq.Analyzers/Analyzers/ConstructorArgumentsAnalyzer.cs
index 11eb390..a853052 100644
--- a/src/Moq.Analyzers/Analyzers/ConstructorArgumentsAnalyzer.cs
+++ b/src/Moq.Analyzers/Analyzers/ConstructorArgumentsAnalyzer.cs
@@ -118,11 +118,11 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
}
// Gets the list of the constructor arguments
- var constructorArguments = new List();
+ var constructorArguments = new List();
if (objectCreation.ArgumentList is not null)
{
- constructorArguments.AddRange(objectCreation.ArgumentList.Arguments);
+ constructorArguments.AddRange(objectCreation.ArgumentList.Arguments.Select(a => a.Expression));
}
// Gets the first argument, check if it is MockBehavior argument and skip it.
@@ -158,6 +158,18 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
{
matchedConstructor.Try(constructor);
+ // Special case, if we have only one argument and it is an array of object.
+ // Use the array of object as the arguments
+ if (constructorArguments.Count == 1)
+ {
+ var objectArrayElements = ExpandObjectArrayElements(constructorArguments[0], context);
+
+ if (objectArrayElements is not null)
+ {
+ constructorArguments = objectArrayElements;
+ }
+ }
+
// If the number of arguments is different, check the next constructor definition.
if (constructor.Parameters.Length != constructorArguments.Count)
{
@@ -167,7 +179,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
for (var i = 0; i < constructorArguments.Count; i++)
{
- if (constructorArguments[i].Expression.IsKind(SyntaxKind.NullLiteralExpression))
+ if (constructorArguments[i].IsKind(SyntaxKind.NullLiteralExpression))
{
// Null parameter, just check the parameter type is a reference type.
if (!constructor.Parameters[i].Type.IsReferenceType)
@@ -179,13 +191,13 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
continue;
}
- if (constructorArguments[i].Expression.IsKind(SyntaxKind.DefaultLiteralExpression))
+ if (constructorArguments[i].IsKind(SyntaxKind.DefaultLiteralExpression))
{
// Default parameter, skip the parameter.
continue;
}
- var constructorArgumentSymbol = context.SemanticModel.GetTypeInfo(constructorArguments[i].Expression, context.CancellationToken);
+ var constructorArgumentSymbol = context.SemanticModel.GetTypeInfo(constructorArguments[i], context.CancellationToken);
if (!constructorArgumentSymbol.Type.IsOrInheritFrom(constructor.Parameters[i].Type))
{
@@ -226,6 +238,55 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
}
}
+ private static List? ExpandObjectArrayElements(ExpressionSyntax argument, SyntaxNodeAnalysisContext context)
+ {
+ var argumentType = context.SemanticModel.GetTypeInfo(argument, context.CancellationToken);
+
+ var type = argumentType.Type;
+
+ if (argumentType.Type is null)
+ {
+ type = argumentType.ConvertedType;
+ }
+
+ if (type is not IArrayTypeSymbol arrayTypeSymbol)
+ {
+ return null;
+ }
+
+ if (arrayTypeSymbol.ElementType.SpecialType != SpecialType.System_Object)
+ {
+ return null;
+ }
+
+ // It is an object[] array, try to extract the arguments of the array creation
+ if (argument is ArrayCreationExpressionSyntax arrayCreationExpressionSyntax)
+ {
+ if (arrayCreationExpressionSyntax.Initializer is not null)
+ {
+ return arrayCreationExpressionSyntax.Initializer.Expressions.ToList();
+ }
+ }
+ else if (argument is CollectionExpressionSyntax collectionExpressionSyntax)
+ {
+ var expressions = new List(collectionExpressionSyntax.Elements.Count);
+
+ foreach (var element in collectionExpressionSyntax.Elements)
+ {
+ if (element is not ExpressionElementSyntax elementExpression)
+ {
+ return null;
+ }
+
+ expressions.Add(elementExpression.Expression);
+ }
+
+ return expressions;
+ }
+
+ return null;
+ }
+
private struct MatchedConstructor
{
public MatchedConstructor()
diff --git a/src/Moq.Analyzers/Moq.Analyzers.csproj b/src/Moq.Analyzers/Moq.Analyzers.csproj
index 82d371c..68efed3 100644
--- a/src/Moq.Analyzers/Moq.Analyzers.csproj
+++ b/src/Moq.Analyzers/Moq.Analyzers.csproj
@@ -127,8 +127,8 @@
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/Moq.Analyzers/MoqExpressionAnalyzer.cs b/src/Moq.Analyzers/MoqExpressionAnalyzer.cs
index 3ce9631..5cebcbd 100644
--- a/src/Moq.Analyzers/MoqExpressionAnalyzer.cs
+++ b/src/Moq.Analyzers/MoqExpressionAnalyzer.cs
@@ -226,7 +226,7 @@ public bool IsMockCreationStrictBehavior(ObjectCreationExpressionSyntax mockCrea
argument = mockCreation.ArgumentList.Arguments[1];
}
- return this.IsStrictBehaviorArgument(argument, cancellationToken);
+ return this.IsStrictBehaviorArgument(argument.Expression, cancellationToken);
}
public bool IsMockOfStrictBehavior(ArgumentListSyntax? argumentList, CancellationToken cancellationToken)
@@ -244,15 +244,15 @@ public bool IsMockOfStrictBehavior(ArgumentListSyntax? argumentList, Cancellatio
return false;
}
- return this.IsStrictBehaviorArgument(lastArgument, cancellationToken);
+ return this.IsStrictBehaviorArgument(lastArgument.Expression, cancellationToken);
}
- public bool IsStrictBehaviorArgument(ArgumentSyntax argument, out MemberAccessExpressionSyntax? memberAccessExpression, CancellationToken cancellationToken)
+ public bool IsStrictBehaviorArgument(ExpressionSyntax argument, out MemberAccessExpressionSyntax? memberAccessExpression, CancellationToken cancellationToken)
{
memberAccessExpression = null;
// Check it is a MemberAccessExpressionSyntax (because we searching for MockBehavior.XXXXX).
- if (argument.Expression is not MemberAccessExpressionSyntax expression)
+ if (argument is not MemberAccessExpressionSyntax expression)
{
return false;
}
@@ -621,7 +621,7 @@ private bool IsMockSetupMethod(InvocationExpressionSyntax invocationExpression,
return true;
}
- private bool IsStrictBehaviorArgument(ArgumentSyntax argument, CancellationToken cancellationToken)
+ private bool IsStrictBehaviorArgument(ExpressionSyntax argument, CancellationToken cancellationToken)
{
if (!this.IsStrictBehaviorArgument(argument, out var memberAccessExpression, cancellationToken))
{
diff --git a/tests/Moq.Analyzers.Tests/Analyzers/ConstructorArgumentsAnalyzerTest.cs b/tests/Moq.Analyzers.Tests/Analyzers/ConstructorArgumentsAnalyzerTest.cs
index 447bdfc..628618f 100644
--- a/tests/Moq.Analyzers.Tests/Analyzers/ConstructorArgumentsAnalyzerTest.cs
+++ b/tests/Moq.Analyzers.Tests/Analyzers/ConstructorArgumentsAnalyzerTest.cs
@@ -212,6 +212,7 @@ private C()
}
[Theory]
+ [InlineData("1")]
[InlineData("1, \"B\"")]
[InlineData("1, null")]
[InlineData("default, \"B\"")]
@@ -219,6 +220,10 @@ private C()
[InlineData("default, null, 1234")]
[InlineData("1, \"An object\", 3, null")]
[InlineData("1, \"An object\", 3, new System.IO.MemoryStream()")]
+ [InlineData("[new string[] { \"A\", \"B\" }]")]
+ [InlineData("new object[] { new string[] { \"A\", \"B\" } }")]
+ [InlineData("[1]")]
+ [InlineData("[1, \"A\"]")]
public async Task Arguments_Match(string parameters)
{
var source = @"
@@ -255,6 +260,10 @@ public C(int a, int[] b, int c)
public C(int a, object b, int c, System.IDisposable d)
{
}
+
+ public C(string[] array)
+ {
+ }
}
}";
@@ -340,6 +349,8 @@ public void TestMethod()
[InlineData("1, \"B\"")]
[InlineData("1, \"An object\", 3, null")]
[InlineData("1, \"An object\", 3, new System.IO.MemoryStream()")]
+ [InlineData("[1]")]
+ [InlineData("[1, \"A\"]")]
public async Task Arguments_Match_WithMockBehavior(string parameters)
{
var source = @"
@@ -372,6 +383,10 @@ public C(int a, object c)
public C(int a, object b, int c, System.IDisposable d)
{
}
+
+ public C(string[] array)
+ {
+ }
}
}";
@@ -408,6 +423,8 @@ public void TestMethod()
[InlineData("null")]
[InlineData("\"The string\", 2")]
[InlineData("1, 2, 3, \"The string\"")]
+ [InlineData("new int[] { 1, 2 }, 1000")]
+ [InlineData("new object[] { \"A\", \"B\" }, 1000")]
public async Task Arguments_NotMatch(string parameters)
{
var source = @"
@@ -440,12 +457,56 @@ public C(int a, object c)
public C(int a, object b, int c, System.IDisposable d)
{
}
+
+ public C(string[] array, int b)
+ {
+ }
}
}";
await Verifier.VerifyAnalyzerAsync(source);
}
+ [Theory]
+ [InlineData("[]")]
+ public async Task Arguments_NotMatch_EmptyArray(string parameters)
+ {
+ var source = @"
+ namespace ConsoleApplication1
+ {
+ using Moq;
+
+ public class TestClass
+ {
+ public void TestMethod()
+ {
+ var mock = new Mock{|PosInfoMoq2005:(" + parameters + @")|};
+ }
+ }
+
+ public class C
+ {
+ public C(int a)
+ {
+ }
+
+ public C(int a, string b)
+ {
+ }
+
+ public C(int a, object c)
+ {
+ }
+
+ public C(string[] array, int b)
+ {
+ }
+ }
+ }";
+
+ await Verifier.VerifyAnalyzerAsync(source);
+ }
+
[Fact]
public async Task Arguments_WithDefaultParameters()
{
@@ -474,7 +535,7 @@ public C(int a = 0, int b = 1, int c = 2, int d = 3)
}
[Fact]
- public async Task Arguments_NotMatch_WithNoContructor()
+ public async Task Arguments_NotMatch_WithNoConstructor()
{
var source = @"
namespace ConsoleApplication1
@@ -500,6 +561,7 @@ public class ClassWithNoConstructor { }
[InlineData("null")]
[InlineData("\"The string\", 2")]
[InlineData("1, 2, 3, \"The string\"")]
+ [InlineData("new int[] { 1, 2 }, 1000")]
public async Task Arguments_NotMatch_WithMockBehavior(string parameters)
{
var source = @"
@@ -528,6 +590,50 @@ public C(int a, string b)
public C(int a, object c)
{
}
+
+ public C(string[] array, int b)
+ {
+ }
+ }
+ }";
+
+ await Verifier.VerifyAnalyzerAsync(source);
+ }
+
+ [Theory]
+ [InlineData("[]")]
+ public async Task Arguments_NotMatch_WithMockBehavior_EmptyArray(string parameters)
+ {
+ var source = @"
+ namespace ConsoleApplication1
+ {
+ using Moq;
+
+ public class TestClass
+ {
+ public void TestMethod()
+ {
+ var mock = new Mock{|PosInfoMoq2005:(MockBehavior.Strict, " + parameters + @")|};
+ }
+ }
+
+ public class C
+ {
+ public C(int a)
+ {
+ }
+
+ public C(int a, string b)
+ {
+ }
+
+ public C(int a, object c)
+ {
+ }
+
+ public C(string[] array, int b)
+ {
+ }
}
}";