Skip to content

Commit 2b74a61

Browse files
v1.2.0 (#5)
* Add the PosInfoMoq2001 to check the member is overridable. #4 * Add the PosInfoMoq2002 rule to check the Mock instances is not a sealed class.
1 parent 14f68a6 commit 2b74a61

18 files changed

Lines changed: 733 additions & 36 deletions

PosInformatique.Moq.Analyzers.sln

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ EndProject
4040
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Compilation", "Compilation", "{D9C84D36-7F9C-4EFB-BE6F-9F7A05FE957D}"
4141
ProjectSection(SolutionItems) = preProject
4242
docs\Compilation\PosInfoMoq2000.md = docs\Compilation\PosInfoMoq2000.md
43+
docs\Compilation\PosInfoMoq2001.md = docs\Compilation\PosInfoMoq2001.md
44+
docs\Compilation\PosInfoMoq2002.md = docs\Compilation\PosInfoMoq2002.md
4345
EndProjectSection
4446
EndProject
4547
Global

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,6 @@ All the rules of this category should not be disabled (or changed their severity
3838
| Rule | Description |
3939
| - | - |
4040
| [PosInfoMoq2000: The `Returns()` or `ReturnsAsync()` methods must be call for Strict mocks](docs/Compilation/PosInfoMoq2000.md) | When a `Mock<T>` has been defined with the `Strict` behavior, the `Returns()` or `ReturnsAsync()` method must be called when setup a method to mock which returns a value. |
41+
| [PosInfoMoq2001: The `Setup()` method must be used only on overridable members](docs/Compilation/PosInfoMoq2001.md)) | The `Setup()` method must be applied only for overridable members. |
42+
| [PosInfoMoq2002: `Mock<T>` class can be used only to mock non-sealed class](docs/Compilation/PosInfoMoq2002.md) | The `Mock<T>` can mock only interfaces or non-`sealed` classes. |
43+

build/azure-pipelines-release.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ parameters:
22
- name: VersionPrefix
33
displayName: The version of the library
44
type: string
5-
default: 1.1.0
5+
default: 1.2.0
66
- name: VersionSuffix
77
displayName: The version suffix of the library (rc.1). Use a space ' ' if no suffix.
88
type: string

docs/Compilation/PosInfoMoq2000.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# PosInfoMoq2000: The `Returns()` or `ReturnsAsync()` methods must be call for Strict mocks
22

3-
| Property | Value |
4-
|-------------------------------------|--------------------------------------------------------------------------------------------|
5-
| **Rule ID** | PosInfoMoq2000 |
6-
| **Title** | The `Returns()` or `ReturnsAsync()` methods must be call for Strict mocks |
7-
| **Category** | Compilation |
8-
| **Default severity** | Error |
3+
| Property | Value |
4+
|-------------------------------------|-----------------------------------------------------------------------------|
5+
| **Rule ID** | PosInfoMoq2000 |
6+
| **Title** | The `Returns()` or `ReturnsAsync()` methods must be call for Strict mocks |
7+
| **Category** | Compilation |
8+
| **Default severity** | Error |
99

1010
## Cause
1111

docs/Compilation/PosInfoMoq2001.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# PosInfoMoq2001: The `Setup()` method must be used only on overridable members
2+
3+
| Property | Value |
4+
|-------------------------------------|---------------------------------------------------------------|
5+
| **Rule ID** | PosInfoMoq2001 |
6+
| **Title** | The `Setup()` method must be used only on overridable members |
7+
| **Category** | Compilation |
8+
| **Default severity** | Error |
9+
10+
## Cause
11+
12+
The `Setup()` method must be applied only for overridable members.
13+
An overridable member is a **methode** or **property** which is in:
14+
- An `interface`.
15+
- A non-`sealed` `class`. In this case, the member must be:
16+
- Defines as `abstract`.
17+
- Or defined as `virtual`
18+
19+
## Rule description
20+
21+
The `Setup()` method must be applied only for overridable members.
22+
23+
For example, the following methods and properties can be mock and used in the `Setup()` method:
24+
- `IService.MethodCanBeMocked()`
25+
- `IService.PropertyCanBeMocked`
26+
- `Service.VirtualMethodCanBeMocked`
27+
- `Service.VirtualPropertyCanBeMocked`
28+
- `Service.AbstractMethodCanBeMocked`
29+
- `Service.AbstractPropertyCanBeMocked`
30+
31+
```csharp
32+
public interface IService
33+
{
34+
void MethodCanBeMocked();
35+
36+
string PropertyCanBeMocked { get; set; }
37+
}
38+
39+
public abstract class Service
40+
{
41+
public virtual void VirtualMethodCanBeMocked() { ... }
42+
43+
public virtual void VirtualPropertyCanBeMocked() { ... }
44+
45+
public abstract void AbstractMethodCanBeMocked();
46+
47+
public abstract void AbstractPropertyCanBeMocked();
48+
}
49+
```
50+
51+
> **NOTE**: The extension methods can not be overriden. The C# syntax looks like a member method an interface or class, but the extension method are just simple
52+
static methods which can not be overriden.
53+
54+
## How to fix violations
55+
56+
To fix a violation of this rule, be sure to mock a member in the `Setup()` method which can be overriden.
57+
58+
## When to suppress warnings
59+
60+
Do not suppress an error from this rule. If bypassed, the execution of the unit test will be failed with a `MoqException`
61+
thrown with the *"Extensions methods may not be used in setup/verification expressions"* message.

docs/Compilation/PosInfoMoq2002.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# PosInfoMoq2002: `Mock<T>` class can be used only to mock non-sealed class
2+
3+
| Property | Value |
4+
|-------------------------------------|---------------------------------------------------------------|
5+
| **Rule ID** | PosInfoMoq2002 |
6+
| **Title** | `Mock<T>` class can be used only to mock non-sealed class |
7+
| **Category** | Compilation |
8+
| **Default severity** | Error |
9+
10+
## Cause
11+
12+
The `Mock<T>` can mock only interfaces or non-`sealed` classes.
13+
14+
## Rule description
15+
16+
The `Mock<T>` method must be use only on the interfaces or non-`sealed` classes.
17+
18+
For example, the following code can not mock the `Service` class because it is `sealed`.
19+
20+
```csharp
21+
[Fact]
22+
public void Test()
23+
{
24+
var service = new Mock<Service>(); // The Service can not be mocked, because it is a sealed class.
25+
service.Setup(s => s.GetData())
26+
.Returns(10);
27+
}
28+
29+
public class Service
30+
{
31+
public virtual int GetData() { }
32+
}
33+
```
34+
35+
## How to fix violations
36+
37+
To fix a violation of this rule, be sure to mock interfaces or non-sealed classes.
38+
39+
## When to suppress warnings
40+
41+
Do not suppress an error from this rule. If bypassed, the execution of the unit test will be failed with a `MoqException`
42+
thrown with the *"Type to mock (xxx) must be an interface, a delegate, or a non-selead, non-static class"* message.
Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
1-
## Release 1.1.0
1+
## Release 1.2.0
22

33
### New Rules
44

55
Rule ID | Category | Severity | Notes
66
--------|----------|----------|--------------------
7-
PosInfoMoq2000 | Compilation | Error | The `Returns()` or `ReturnsAsync()` method must be called for Strict mocks
7+
PosInfoMoq2001 | Compilation | Error | The `Setup()` method can be used only on overridable members.
8+
PosInfoMoq2002 | Compilation | Error | `Mock<T>` class can be used only to mock non-sealed class.
9+
10+
## Release 1.1.0
11+
12+
### New Rules
13+
14+
Rule ID | Category | Severity | Notes
15+
--------|----------|----------|--------------------
16+
PosInfoMoq2000 | Compilation | Error | The `Returns()` or `ReturnsAsync()` method must be called for Strict mocks.
817

918
## Release 1.0.0
1019

1120
### New Rules
1221

1322
Rule ID | Category | Severity | Notes
1423
--------|----------|----------|--------------------
15-
PosInfoMoq1000 | Design | Warning | `Verify()` and `VerifyAll()` methods should be called when instantiate a Mock<T> instances
16-
PosInfoMoq1001 | Design | Warning | The `Mock<T>` instance behavior should be defined to Strict mode
24+
PosInfoMoq1000 | Design | Warning | `Verify()` and `VerifyAll()` methods should be called when instantiate a Mock<T> instances.
25+
PosInfoMoq1001 | Design | Warning | The `Mock<T>` instance behavior should be defined to Strict mode.

src/Moq.Analyzers/Analyzers/MockInstanceShouldBeStrictBehaviorAnalyzer.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,15 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
4747
return;
4848
}
4949

50+
var moqExpressionAnalyzer = new MoqExpressionAnalyzer(context.SemanticModel);
51+
5052
// Check there is "new Mock<I>()" statement.
51-
if (!MockExpressionHelper.IsMockCreation(moqSymbols, context.SemanticModel, objectCreation))
53+
if (!moqExpressionAnalyzer.IsMockCreation(moqSymbols, objectCreation))
5254
{
5355
return;
5456
}
5557

56-
if (!MockExpressionHelper.IsStrictBehavior(moqSymbols, context.SemanticModel, objectCreation))
58+
if (!moqExpressionAnalyzer.IsStrictBehavior(moqSymbols, objectCreation))
5759
{
5860
var diagnostic = Diagnostic.Create(Rule, objectCreation.GetLocation());
5961
context.ReportDiagnostic(diagnostic);
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="NoSealedClassAnalyzer.cs" company="P.O.S Informatique">
3+
// Copyright (c) P.O.S Informatique. All rights reserved.
4+
// </copyright>
5+
//-----------------------------------------------------------------------
6+
7+
namespace PosInformatique.Moq.Analyzers
8+
{
9+
using System.Collections.Immutable;
10+
using Microsoft.CodeAnalysis;
11+
using Microsoft.CodeAnalysis.CSharp;
12+
using Microsoft.CodeAnalysis.CSharp.Syntax;
13+
using Microsoft.CodeAnalysis.Diagnostics;
14+
15+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
16+
public class NoSealedClassAnalyzer : DiagnosticAnalyzer
17+
{
18+
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
19+
"PosInfoMoq2002",
20+
"Mock<T> class can be used only to mock non-sealed class",
21+
"Mock<T> class can be used only to mock non-sealed class",
22+
"Compilation",
23+
DiagnosticSeverity.Error,
24+
isEnabledByDefault: true,
25+
description: "Mock<T> class can be used only to mock non-sealed class.");
26+
27+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
28+
29+
public override void Initialize(AnalysisContext context)
30+
{
31+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
32+
context.EnableConcurrentExecution();
33+
34+
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.ObjectCreationExpression);
35+
}
36+
37+
private static void Analyze(SyntaxNodeAnalysisContext context)
38+
{
39+
var objectCreationExpression = (ObjectCreationExpressionSyntax)context.Node;
40+
41+
var moqSymbols = MoqSymbols.FromCompilation(context.Compilation);
42+
43+
if (moqSymbols is null)
44+
{
45+
return;
46+
}
47+
48+
var moqExpressionAnalyzer = new MoqExpressionAnalyzer(context.SemanticModel);
49+
50+
// Check the expression is a Mock<T> instance creation.
51+
var mockedType = moqExpressionAnalyzer.GetMockedType(moqSymbols, objectCreationExpression, out var typeExpression);
52+
53+
if (mockedType is null)
54+
{
55+
return;
56+
}
57+
58+
if (mockedType.TypeKind == TypeKind.Interface)
59+
{
60+
return;
61+
}
62+
63+
if (mockedType.IsAbstract)
64+
{
65+
return;
66+
}
67+
68+
if (!mockedType.IsSealed)
69+
{
70+
return;
71+
}
72+
73+
// No returns method has been specified with Strict mode. Report the diagnostic issue.
74+
var diagnostic = Diagnostic.Create(Rule, typeExpression!.GetLocation());
75+
context.ReportDiagnostic(diagnostic);
76+
}
77+
}
78+
}

src/Moq.Analyzers/Analyzers/SetupMethodMustReturnValueWithStrictBehaviorAnalyzer.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,16 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
4545
return;
4646
}
4747

48+
var moqExpressionAnalyzer = new MoqExpressionAnalyzer(context.SemanticModel);
49+
4850
// Check is Setup() method.
49-
if (!MockExpressionHelper.IsMockSetupMethod(moqSymbols, context.SemanticModel, invocationExpression, out var localVariableExpression))
51+
if (!moqExpressionAnalyzer.IsMockSetupMethod(moqSymbols, invocationExpression, out var localVariableExpression))
5052
{
5153
return;
5254
}
5355

5456
// Check the mocked method return type (if "void", we skip the analysis, because no Returns() is required).
55-
var mockedMethodReturnTypeSymbol = MockExpressionHelper.GetSetupMethodReturnSymbol(moqSymbols, context.SemanticModel, invocationExpression);
57+
var mockedMethodReturnTypeSymbol = moqExpressionAnalyzer.GetSetupMethodReturnSymbol(moqSymbols, invocationExpression);
5658
if (mockedMethodReturnTypeSymbol is null)
5759
{
5860
return;
@@ -64,7 +66,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
6466
}
6567

6668
// Check the behavior of the mock instance is Strict.
67-
if (!MockExpressionHelper.IsStrictBehavior(moqSymbols, context.SemanticModel, localVariableExpression!))
69+
if (!moqExpressionAnalyzer.IsStrictBehavior(moqSymbols, localVariableExpression!))
6870
{
6971
return;
7072
}

0 commit comments

Comments
 (0)