-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the PosInforMoq2005 rule to check the arguments of the mocked class.
- Loading branch information
1 parent
22acee0
commit 3d87963
Showing
10 changed files
with
602 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# PosInfoMoq2005: The `Callback()` delegate expression must match the signature of the mocked method | ||
|
||
| Property | Value | | ||
|-------------------------------------|-------------------------------------------------------------------------| | ||
| **Rule ID** | PosInfoMoq2005 | | ||
| **Title** | Constructor arguments must match the constructors of the mocked class. | | ||
| **Category** | Compilation | | ||
| **Default severity** | Error | | ||
|
||
## Cause | ||
|
||
Constructor arguments must match the constructors of the mocked class. | ||
|
||
## Rule description | ||
|
||
When configurate mock, all the arguments must match one of the constructor of the mocked type. | ||
|
||
```csharp | ||
[Fact] | ||
public void Test() | ||
{ | ||
var service1 = new Mock<Service>(1, 2, 3); // The arguments does not match one of the Service type constructors. | ||
var service2 = new Mock<Service>("Argument 1", 2); // OK | ||
var service3 = new Mock<Service>(MockBehavior.Strict, "Argument 1", 2); // OK | ||
} | ||
|
||
public abstract class Service | ||
{ | ||
public Service(string a) | ||
{ | ||
} | ||
|
||
public Service(string a, int b) | ||
{ | ||
} | ||
} | ||
``` | ||
|
||
## How to fix violations | ||
|
||
To fix a violation of this rule, be sure to pass right arguments of one of the constructor of the mocked instance. | ||
|
||
## When to suppress warnings | ||
|
||
Do not suppress an error from this rule. If bypassed, the execution of the unit test will be failed with a `MoqException` | ||
thrown with the *"Constructor arguments must match the constructors of the mocked class."* message. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
src/Moq.Analyzers/Analyzers/ConstructorArgumentsMustMatchAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
//----------------------------------------------------------------------- | ||
// <copyright file="ConstructorArgumentsMustMatchAnalyzer.cs" company="P.O.S Informatique"> | ||
// Copyright (c) P.O.S Informatique. All rights reserved. | ||
// </copyright> | ||
//----------------------------------------------------------------------- | ||
|
||
namespace PosInformatique.Moq.Analyzers | ||
{ | ||
using System.Collections.Immutable; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Text; | ||
|
||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class ConstructorArgumentsMustMatchAnalyzer : DiagnosticAnalyzer | ||
{ | ||
internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( | ||
"PosInfoMoq2005", | ||
"Constructor arguments must match the constructors of the mocked class", | ||
"Constructor arguments must match the constructors of the mocked class", | ||
"Compilation", | ||
DiagnosticSeverity.Error, | ||
isEnabledByDefault: true, | ||
description: "Constructor arguments must match the constructors of the mocked class."); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||
context.EnableConcurrentExecution(); | ||
|
||
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.ObjectCreationExpression); | ||
} | ||
|
||
private static void Analyze(SyntaxNodeAnalysisContext context) | ||
{ | ||
var objectCreation = (ObjectCreationExpressionSyntax)context.Node; | ||
|
||
var moqSymbols = MoqSymbols.FromCompilation(context.Compilation); | ||
|
||
if (moqSymbols is null) | ||
{ | ||
return; | ||
} | ||
|
||
var moqExpressionAnalyzer = new MoqExpressionAnalyzer(context.SemanticModel); | ||
|
||
// Check there is "new Mock<I>()" statement. | ||
var mockedType = moqExpressionAnalyzer.GetMockedType(moqSymbols, objectCreation, out var _, context.CancellationToken); | ||
if (mockedType is null) | ||
{ | ||
return; | ||
} | ||
|
||
// Check the type is mockable | ||
if (!moqSymbols.IsMockable(mockedType)) | ||
{ | ||
return; | ||
} | ||
|
||
// Check the type is a named type | ||
if (mockedType is not INamedTypeSymbol namedTypeSymbol) | ||
{ | ||
return; | ||
} | ||
|
||
// Gets the list of the constructor arguments | ||
var constructorArguments = new List<ArgumentSyntax>(); | ||
|
||
if (objectCreation.ArgumentList is not null) | ||
{ | ||
constructorArguments.AddRange(objectCreation.ArgumentList.Arguments); | ||
} | ||
|
||
// Gets the first argument, check if it is MockBehavior argument and skip it. | ||
var firstArgument = constructorArguments.FirstOrDefault(); | ||
|
||
if (firstArgument is not null) | ||
{ | ||
if (moqExpressionAnalyzer.IsStrictBehaviorArgument(moqSymbols, firstArgument, out var _, context.CancellationToken)) | ||
{ | ||
constructorArguments.RemoveAt(0); | ||
} | ||
} | ||
|
||
var matchedConstructor = true; | ||
|
||
// Iterate on each constructor and check if the arguments match. | ||
foreach (var constructor in namedTypeSymbol.Constructors) | ||
{ | ||
matchedConstructor = true; | ||
|
||
// If the number of arguments is different, check the next constructor definition. | ||
if (constructor.Parameters.Length != constructorArguments.Count) | ||
{ | ||
matchedConstructor = false; | ||
continue; | ||
} | ||
|
||
for (var i = 0; i < constructorArguments.Count; i++) | ||
{ | ||
var constructorArgumentSymbol = context.SemanticModel.GetTypeInfo(constructorArguments[i].Expression, context.CancellationToken); | ||
|
||
if (!SymbolEqualityComparer.Default.Equals(constructorArgumentSymbol.Type, constructor.Parameters[i].Type)) | ||
{ | ||
matchedConstructor = false; | ||
break; | ||
} | ||
} | ||
|
||
if (matchedConstructor) | ||
{ | ||
break; | ||
} | ||
} | ||
|
||
if (matchedConstructor) | ||
{ | ||
return; | ||
} | ||
|
||
Location location; | ||
|
||
if (constructorArguments.Count == 0) | ||
{ | ||
location = objectCreation.ArgumentList!.GetLocation(); | ||
} | ||
else | ||
{ | ||
var firstLocation = constructorArguments.First().GetLocation(); | ||
var lastLocation = constructorArguments.Last().GetLocation(); | ||
|
||
location = Location.Create(context.Node.SyntaxTree, new TextSpan(firstLocation.SourceSpan.Start, lastLocation.SourceSpan.End - firstLocation.SourceSpan.Start)); | ||
} | ||
|
||
var diagnostic = Diagnostic.Create(Rule, location); | ||
context.ReportDiagnostic(diagnostic); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Moq" Version="4.20.70" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\..\src\Moq.Analyzers\Moq.Analyzers.csproj" PrivateAssets="all" ReferenceOutputAssembly="false" OutputItemType="Analyzer" Condition="'$(Configuration)' == 'Debug'" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
//----------------------------------------------------------------------- | ||
// <copyright file="Sandbox.cs" company="P.O.S Informatique"> | ||
// Copyright (c) P.O.S Informatique. All rights reserved. | ||
// </copyright> | ||
//----------------------------------------------------------------------- | ||
|
||
namespace PosInformatique.Moq.Analyzers.Sandbox | ||
{ | ||
public class Sandbox | ||
{ | ||
public void TestCodeHere() | ||
{ | ||
} | ||
|
||
public class Test | ||
{ | ||
public Test(int a) | ||
{ | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.