Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.6.0 #24

Merged
merged 15 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/github-actions-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
type: string
description: The version of the library
required: true
default: 1.5.0
default: 1.6.0
VersionSuffix:
type: string
description: The version suffix of the library (for example rc.1)
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

<!-- Common NuGet packages -->
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
2 changes: 2 additions & 0 deletions PosInformatique.Moq.Analyzers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Compilation", "Compilation"
docs\Compilation\PosInfoMoq2004.md = docs\Compilation\PosInfoMoq2004.md
docs\Compilation\PosInfoMoq2005.md = docs\Compilation\PosInfoMoq2005.md
docs\Compilation\PosInfoMoq2006.md = docs\Compilation\PosInfoMoq2006.md
docs\Compilation\PosInfoMoq2007.md = docs\Compilation\PosInfoMoq2007.md
docs\Compilation\PosInfoMoq2008.md = docs\Compilation\PosInfoMoq2008.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Analyzers.Sandbox", "tests\Moq.Analyzers.Sandbox\Moq.Analyzers.Sandbox.csproj", "{07F970A1-1477-4D4C-B233-C9B4DA6E3AD6}"
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,8 @@ All the rules of this category should not be disabled (or changed their severity
| [PosInfoMoq2004: Constructor arguments cannot be passed for interface mocks](docs/Compilation/PosInfoMoq2004.md) | No arguments can be passed to a mocked interface. |
| [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. |
| [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. |
| [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. |
| [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. |



4 changes: 2 additions & 2 deletions docs/Compilation/PosInfoMoq2001.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
## Cause

The `Setup()` method must be applied only for overridable members.
An overridable member is a **methode** or **property** which is in:
An overridable member is a **method** or **property** which is in:
- An `interface`.
- A non-`sealed` `class`. In this case, the member must be:
- Defines as `abstract`.
Expand Down Expand Up @@ -58,4 +58,4 @@ To fix a violation of this rule, be sure to mock a member in the `Setup()` metho
## 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 *"Extensions methods may not be used in setup/verification expressions"* message.
thrown with the *"Unsupported expression: m => m.Method(). Non-overridable members (here: Namespace.Class.Method) may not be used in setup / verification expressions."* message.
2 changes: 1 addition & 1 deletion docs/Compilation/PosInfoMoq2006.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ and must be overridable (`virtual`, `abstract` and `override`, but not `sealed`)
[Fact]
public void Test()
{
var service = new Mock<Service>(1, 2, 3);
var service = new Mock<Service>();
service.Protected().Setup("GetData") // The GetData() is public and can be mocked with Protected() feature.
.Returns(10);
service.Protected().Setup("NotExists") // The NotExists() method does not exist.
Expand Down
45 changes: 45 additions & 0 deletions docs/Compilation/PosInfoMoq2007.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# PosInfoMoq2007: The `As<T>()` method can be used only with interfaces.

| Property | Value |
|-------------------------------------|-----------------------------------------------------------------|
| **Rule ID** | PosInfoMoq2007 |
| **Title** | The `As<T>()` method can be used only with interfaces. |
| **Category** | Compilation |
| **Default severity** | Error |

## Cause

The `As<T>()` method is used with a type which is not an interface.

## Rule description

Moq allows to add additional implementations for mocked class (or interface) by adding additional interfaces
with the `As<T>()` method.

```csharp
[Fact]
public void Test()
{
var service = new Mock<Service>();
service.As<IDisposable>() // Add IDisposable implementation for the mocked Service class.
.Setup(s => s.Dispose());
service.As<OtherService>(); // An error will be raised, because we can't mock additional implementation of a class.
}

public abstract class Service
{
}

public abstract class OtherService
{
}
```

## How to fix violations

To fix a violation of this rule, use an interface when using the `As<T>()` method.

## 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 *"Can only add interfaces to the mock."* message.
61 changes: 61 additions & 0 deletions docs/Compilation/PosInfoMoq2008.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# PosInfoMoq2008: The `Verify()` method must be used only on overridable members

| Property | Value |
|-------------------------------------|---------------------------------------------------------------|
| **Rule ID** | PosInfoMoq2008 |
| **Title** | The `Verify()` method must be used only on overridable members |
| **Category** | Compilation |
| **Default severity** | Error |

## Cause

The `Verify()` method must be applied only for overridable members.
An overridable member is a **method** or **property** which is in:
- An `interface`.
- A non-`sealed` `class`. In this case, the member must be:
- Defines as `abstract`.
- Or defined as `virtual`

## Rule description

The `Verify()` method must be applied only for overridable members.

For example, the following methods and properties can be mock and used in the `Verify()` method:
- `IService.MethodCanBeMocked()`
- `IService.PropertyCanBeMocked`
- `Service.VirtualMethodCanBeMocked`
- `Service.VirtualPropertyCanBeMocked`
- `Service.AbstractMethodCanBeMocked`
- `Service.AbstractPropertyCanBeMocked`

```csharp
public interface IService
{
void MethodCanBeMocked();

string PropertyCanBeMocked { get; set; }
}

public abstract class Service
{
public virtual void VirtualMethodCanBeMocked() { ... }

public virtual void VirtualPropertyCanBeMocked() { ... }

public abstract void AbstractMethodCanBeMocked();

public abstract void AbstractPropertyCanBeMocked();
}
```

> **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
static methods which can not be overriden.

## How to fix violations

To fix a violation of this rule, be sure to mock a member in the `Verify()` method which can be overriden.

## 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 *"Unsupported expression: m => m.Method(). Non-overridable members (here: Namespace.Class.Method) may not be used in setup / verification expressions."* message.
11 changes: 10 additions & 1 deletion src/Moq.Analyzers/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
## Release 1.5.0
## Release 1.6.0

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|--------------------
PosInfoMoq2007 | Compilation | Error | The `As<T>()` method can be used only with interfaces.
PosInfoMoq2008 | Compilation | Error | The `Verify()` method can be used only on overridable members.

## Release 1.5.0

### New Rules

Expand Down
67 changes: 67 additions & 0 deletions src/Moq.Analyzers/Analyzers/AsMustBeUsedWithInterfaceAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//-----------------------------------------------------------------------
// <copyright file="AsMustBeUsedWithInterfaceAnalyzer.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;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class AsMustBeUsedWithInterfaceAnalyzer : DiagnosticAnalyzer
{
internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
"PosInfoMoq2007",
"The As<T>() method can be used only with interfaces",
"The As<T>() method can be used only with interfaces",
"Compilation",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "The As<T>() method can be used only with interfaces.",
helpLinkUri: "https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Compilation/PosInfoMoq2007.html");

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();

context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.InvocationExpression);
}

private static void Analyze(SyntaxNodeAnalysisContext context)
{
var invocationExpression = (InvocationExpressionSyntax)context.Node;

var moqSymbols = MoqSymbols.FromCompilation(context.Compilation);

if (moqSymbols is null)
{
return;
}

var moqExpressionAnalyzer = new MoqExpressionAnalyzer(moqSymbols, context.SemanticModel);

var asMethodType = moqExpressionAnalyzer.ExtractAsMethodType(invocationExpression, out var typeSyntax, context.CancellationToken);

if (asMethodType is null)
{
return;
}

if (asMethodType.TypeKind == TypeKind.Interface)
{
return;
}

var diagnostic = Diagnostic.Create(Rule, typeSyntax!.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public class CallBackDelegateMustMatchMockedMethodAnalyzer : DiagnosticAnalyzer
"Compilation",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "The Callback() delegate expression must match the signature of the mocked method.");
description: "The Callback() delegate expression must match the signature of the mocked method.",
helpLinkUri: "https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Compilation/PosInfoMoq2003.html");

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

Expand All @@ -45,8 +46,6 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
return;
}

var moqExpressionAnalyzer = new MoqExpressionAnalyzer(context.SemanticModel);

// Try to determine if the invocation expression is a Callback() expression.
var methodSymbol = context.SemanticModel.GetSymbolInfo(invocationExpression, context.CancellationToken);

Expand All @@ -56,6 +55,8 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
}

// If yes, we extract the lambda expression of it.
var moqExpressionAnalyzer = new MoqExpressionAnalyzer(moqSymbols, context.SemanticModel);

var callBackLambdaExpressionSymbol = moqExpressionAnalyzer.ExtractCallBackLambdaExpressionMethod(invocationExpression, out var lambdaExpression, context.CancellationToken);

if (callBackLambdaExpressionSymbol is null)
Expand All @@ -70,6 +71,11 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
{
// Find the symbol of the mocked method (if not symbol found, it is mean we Setup() method that not currently compile)
// so we skip the analysis.
if (!moqExpressionAnalyzer.IsMockSetupMethod(followingMethod, out var _, context.CancellationToken))
{
continue;
}

var mockedMethod = moqExpressionAnalyzer.ExtractSetupMethod(followingMethod, out var _, context.CancellationToken);

if (mockedMethod is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public class ConstructorArgumentCannotBePassedForInterfaceAnalyzer : DiagnosticA
"Compilation",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "Constructor arguments cannot be passed for interface mocks.");
description: "Constructor arguments cannot be passed for interface mocks.",
helpLinkUri: "https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Compilation/PosInfoMoq2004.html");

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

Expand All @@ -45,10 +46,10 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
return;
}

var moqExpressionAnalyzer = new MoqExpressionAnalyzer(context.SemanticModel);
var moqExpressionAnalyzer = new MoqExpressionAnalyzer(moqSymbols, context.SemanticModel);

// Check there is "new Mock<I>()" statement.
var mockedType = moqExpressionAnalyzer.GetMockedType(moqSymbols, objectCreation, out var typeExpression, context.CancellationToken);
var mockedType = moqExpressionAnalyzer.GetMockedType(objectCreation, out var typeExpression, context.CancellationToken);
if (mockedType is null)
{
return;
Expand Down Expand Up @@ -77,7 +78,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
// Check if the first argument is MockBehavior argument
var argumentCheckStart = 0;

if (moqExpressionAnalyzer.IsStrictBehaviorArgument(moqSymbols, firstArgument, out var _, context.CancellationToken))
if (moqExpressionAnalyzer.IsStrictBehaviorArgument(firstArgument, out var _, context.CancellationToken))
{
argumentCheckStart = 1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ public class ConstructorArgumentsMustMatchAnalyzer : DiagnosticAnalyzer
"Compilation",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "Constructor arguments must match the constructors of the mocked class.");
description: "Constructor arguments must match the constructors of the mocked class.",
helpLinkUri: "https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Compilation/PosInfoMoq2005.html");

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

Expand All @@ -46,10 +47,10 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
return;
}

var moqExpressionAnalyzer = new MoqExpressionAnalyzer(context.SemanticModel);
var moqExpressionAnalyzer = new MoqExpressionAnalyzer(moqSymbols, context.SemanticModel);

// Check there is "new Mock<I>()" statement.
var mockedType = moqExpressionAnalyzer.GetMockedType(moqSymbols, objectCreation, out var _, context.CancellationToken);
var mockedType = moqExpressionAnalyzer.GetMockedType(objectCreation, out var _, context.CancellationToken);
if (mockedType is null)
{
return;
Expand Down Expand Up @@ -80,7 +81,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context)

if (firstArgument is not null)
{
if (moqExpressionAnalyzer.IsStrictBehaviorArgument(moqSymbols, firstArgument, out var _, context.CancellationToken))
if (moqExpressionAnalyzer.IsStrictBehaviorArgument(firstArgument, out var _, context.CancellationToken))
{
constructorArguments.RemoveAt(0);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public class MockInstanceShouldBeStrictBehaviorAnalyzer : DiagnosticAnalyzer
"Design",
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "The Mock<T> instance behavior should be defined to Strict mode.");
description: "The Mock<T> instance behavior should be defined to Strict mode.",
helpLinkUri: "https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Design/PosInfoMoq1001.html");

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

Expand All @@ -47,15 +48,15 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
return;
}

var moqExpressionAnalyzer = new MoqExpressionAnalyzer(context.SemanticModel);
var moqExpressionAnalyzer = new MoqExpressionAnalyzer(moqSymbols, context.SemanticModel);

// Check there is "new Mock<I>()" statement.
if (!moqExpressionAnalyzer.IsMockCreation(moqSymbols, objectCreation, context.CancellationToken))
if (!moqExpressionAnalyzer.IsMockCreation(objectCreation, context.CancellationToken))
{
return;
}

if (!moqExpressionAnalyzer.IsStrictBehavior(moqSymbols, objectCreation, context.CancellationToken))
if (!moqExpressionAnalyzer.IsStrictBehavior(objectCreation, context.CancellationToken))
{
var diagnostic = Diagnostic.Create(Rule, objectCreation.GetLocation());
context.ReportDiagnostic(diagnostic);
Expand Down
Loading