Skip to content

Commit da2f494

Browse files
v1.6.0 (#24)
- Add new rules: - PosInfoMoq2007: The `As<T>()` method can be used only with interfaces (fixes: #19). - PosInfoMoq2008: The `Verify()` method must be used only on overridable members. - Add the support of static methods `VerifyAll()` and `Verify()` for the PosInfoMoq2000 rule (fixes: #20). - Various optimizations to increase speed of analysis. - Various optimizations to reduce memory usage. - Add hyperlink to the documentation of the rules.
1 parent f19dd54 commit da2f494

29 files changed

Lines changed: 1007 additions & 135 deletions

.github/workflows/github-actions-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
type: string
88
description: The version of the library
99
required: true
10-
default: 1.5.0
10+
default: 1.6.0
1111
VersionSuffix:
1212
type: string
1313
description: The version suffix of the library (for example rc.1)

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
<!-- Common NuGet packages -->
3636
<ItemGroup>
37-
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
37+
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
3838
<PrivateAssets>all</PrivateAssets>
3939
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
4040
</PackageReference>

PosInformatique.Moq.Analyzers.sln

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Compilation", "Compilation"
4040
docs\Compilation\PosInfoMoq2004.md = docs\Compilation\PosInfoMoq2004.md
4141
docs\Compilation\PosInfoMoq2005.md = docs\Compilation\PosInfoMoq2005.md
4242
docs\Compilation\PosInfoMoq2006.md = docs\Compilation\PosInfoMoq2006.md
43+
docs\Compilation\PosInfoMoq2007.md = docs\Compilation\PosInfoMoq2007.md
44+
docs\Compilation\PosInfoMoq2008.md = docs\Compilation\PosInfoMoq2008.md
4345
EndProjectSection
4446
EndProject
4547
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Analyzers.Sandbox", "tests\Moq.Analyzers.Sandbox\Moq.Analyzers.Sandbox.csproj", "{07F970A1-1477-4D4C-B233-C9B4DA6E3AD6}"

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,8 @@ All the rules of this category should not be disabled (or changed their severity
4444
| [PosInfoMoq2004: Constructor arguments cannot be passed for interface mocks](docs/Compilation/PosInfoMoq2004.md) | No arguments can be passed to a mocked interface. |
4545
| [PosInfoMoq2005: Constructor arguments must match the constructors of the mocked class](docs/Compilation/PosInfoMoq2005.md) | When instantiating a `Mock<T>`, the parameters must match one of the constructors of the mocked type. |
4646
| [PosInfoMoq2006: The Protected().Setup() method must be use with overridable protected or internal methods](docs/Compilation/PosInfoMoq2006.md) | When using the `Protected().Setup()` configuration, the method mocked must be overridable and protected or internal. |
47+
| [PosInfoMoq2007: The `As<T>()` method can be used only with interfaces.](docs/Compilation/PosInfoMoq2007.md) | The `As<T>()` can only be use with the interfaces. |
48+
| [PosInfoMoq2008: The `Verify()` method must be used only on overridable members](docs/Compilation/PosInfoMoq2008.md)) | The `Verify()` method must be applied only for overridable members. |
49+
50+
4751

docs/Compilation/PosInfoMoq2001.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
## Cause
1111

1212
The `Setup()` method must be applied only for overridable members.
13-
An overridable member is a **methode** or **property** which is in:
13+
An overridable member is a **method** or **property** which is in:
1414
- An `interface`.
1515
- A non-`sealed` `class`. In this case, the member must be:
1616
- Defines as `abstract`.
@@ -58,4 +58,4 @@ To fix a violation of this rule, be sure to mock a member in the `Setup()` metho
5858
## When to suppress warnings
5959

6060
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.
61+
thrown with the *"Unsupported expression: m => m.Method(). Non-overridable members (here: Namespace.Class.Method) may not be used in setup / verification expressions."* message.

docs/Compilation/PosInfoMoq2006.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ and must be overridable (`virtual`, `abstract` and `override`, but not `sealed`)
2323
[Fact]
2424
public void Test()
2525
{
26-
var service = new Mock<Service>(1, 2, 3);
26+
var service = new Mock<Service>();
2727
service.Protected().Setup("GetData") // The GetData() is public and can be mocked with Protected() feature.
2828
.Returns(10);
2929
service.Protected().Setup("NotExists") // The NotExists() method does not exist.

docs/Compilation/PosInfoMoq2007.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# PosInfoMoq2007: The `As<T>()` method can be used only with interfaces.
2+
3+
| Property | Value |
4+
|-------------------------------------|-----------------------------------------------------------------|
5+
| **Rule ID** | PosInfoMoq2007 |
6+
| **Title** | The `As<T>()` method can be used only with interfaces. |
7+
| **Category** | Compilation |
8+
| **Default severity** | Error |
9+
10+
## Cause
11+
12+
The `As<T>()` method is used with a type which is not an interface.
13+
14+
## Rule description
15+
16+
Moq allows to add additional implementations for mocked class (or interface) by adding additional interfaces
17+
with the `As<T>()` method.
18+
19+
```csharp
20+
[Fact]
21+
public void Test()
22+
{
23+
var service = new Mock<Service>();
24+
service.As<IDisposable>() // Add IDisposable implementation for the mocked Service class.
25+
.Setup(s => s.Dispose());
26+
service.As<OtherService>(); // An error will be raised, because we can't mock additional implementation of a class.
27+
}
28+
29+
public abstract class Service
30+
{
31+
}
32+
33+
public abstract class OtherService
34+
{
35+
}
36+
```
37+
38+
## How to fix violations
39+
40+
To fix a violation of this rule, use an interface when using the `As<T>()` method.
41+
42+
## When to suppress warnings
43+
44+
Do not suppress an error from this rule. If bypassed, the execution of the unit test will be failed with a `MoqException`
45+
thrown with the *"Can only add interfaces to the mock."* message.

docs/Compilation/PosInfoMoq2008.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# PosInfoMoq2008: The `Verify()` method must be used only on overridable members
2+
3+
| Property | Value |
4+
|-------------------------------------|---------------------------------------------------------------|
5+
| **Rule ID** | PosInfoMoq2008 |
6+
| **Title** | The `Verify()` method must be used only on overridable members |
7+
| **Category** | Compilation |
8+
| **Default severity** | Error |
9+
10+
## Cause
11+
12+
The `Verify()` method must be applied only for overridable members.
13+
An overridable member is a **method** 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 `Verify()` method must be applied only for overridable members.
22+
23+
For example, the following methods and properties can be mock and used in the `Verify()` 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 `Verify()` 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 *"Unsupported expression: m => m.Method(). Non-overridable members (here: Namespace.Class.Method) may not be used in setup / verification expressions."* message.

src/Moq.Analyzers/AnalyzerReleases.Shipped.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
## Release 1.5.0
1+
## Release 1.6.0
2+
3+
### New Rules
4+
5+
Rule ID | Category | Severity | Notes
6+
--------|----------|----------|--------------------
7+
PosInfoMoq2007 | Compilation | Error | The `As<T>()` method can be used only with interfaces.
8+
PosInfoMoq2008 | Compilation | Error | The `Verify()` method can be used only on overridable members.
9+
10+
## Release 1.5.0
211

312
### New Rules
413

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="AsMustBeUsedWithInterfaceAnalyzer.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 AsMustBeUsedWithInterfaceAnalyzer : DiagnosticAnalyzer
17+
{
18+
internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
19+
"PosInfoMoq2007",
20+
"The As<T>() method can be used only with interfaces",
21+
"The As<T>() method can be used only with interfaces",
22+
"Compilation",
23+
DiagnosticSeverity.Error,
24+
isEnabledByDefault: true,
25+
description: "The As<T>() method can be used only with interfaces.",
26+
helpLinkUri: "https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Compilation/PosInfoMoq2007.html");
27+
28+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
29+
30+
public override void Initialize(AnalysisContext context)
31+
{
32+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
33+
context.EnableConcurrentExecution();
34+
35+
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.InvocationExpression);
36+
}
37+
38+
private static void Analyze(SyntaxNodeAnalysisContext context)
39+
{
40+
var invocationExpression = (InvocationExpressionSyntax)context.Node;
41+
42+
var moqSymbols = MoqSymbols.FromCompilation(context.Compilation);
43+
44+
if (moqSymbols is null)
45+
{
46+
return;
47+
}
48+
49+
var moqExpressionAnalyzer = new MoqExpressionAnalyzer(moqSymbols, context.SemanticModel);
50+
51+
var asMethodType = moqExpressionAnalyzer.ExtractAsMethodType(invocationExpression, out var typeSyntax, context.CancellationToken);
52+
53+
if (asMethodType is null)
54+
{
55+
return;
56+
}
57+
58+
if (asMethodType.TypeKind == TypeKind.Interface)
59+
{
60+
return;
61+
}
62+
63+
var diagnostic = Diagnostic.Create(Rule, typeSyntax!.GetLocation());
64+
context.ReportDiagnostic(diagnostic);
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)