diff --git a/src/Moq.Analyzers/Analyzers/ConstructorArgumentCannotBePassedForInterfaceAnalyzer.cs b/src/Moq.Analyzers/Analyzers/ConstructorArgumentCannotBePassedForInterfaceAnalyzer.cs deleted file mode 100644 index b983433..0000000 --- a/src/Moq.Analyzers/Analyzers/ConstructorArgumentCannotBePassedForInterfaceAnalyzer.cs +++ /dev/null @@ -1,108 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) P.O.S Informatique. All rights reserved. -// -//----------------------------------------------------------------------- - -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 ConstructorArgumentCannotBePassedForInterfaceAnalyzer : DiagnosticAnalyzer - { - internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( - "PosInfoMoq2004", - "Constructor arguments cannot be passed for interface mocks", - "Constructor arguments cannot be passed for interface mocks", - "Compilation", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: "Constructor arguments cannot be passed for interface mocks.", - helpLinkUri: "https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Compilation/PosInfoMoq2004.html"); - - public override ImmutableArray 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; - } - - // Check if the mock instantiation is with a factory. - // In this case, we ignore the matching of the constructor arguments. - var constructorSymbol = context.SemanticModel.GetSymbolInfo(objectCreation, context.CancellationToken); - - if (moqSymbols.IsMockConstructorWithFactory(constructorSymbol.Symbol)) - { - return; - } - - var moqExpressionAnalyzer = new MoqExpressionAnalyzer(moqSymbols, context.SemanticModel); - - // Check there is "new Mock()" statement. - var mockedType = moqExpressionAnalyzer.GetMockedType(objectCreation, out var typeExpression, context.CancellationToken); - if (mockedType is null) - { - return; - } - - // Check the mocked type is an interface - if (mockedType.TypeKind != TypeKind.Interface) - { - return; - } - - // Check that the "new Mock()" statement have at least one argument (else skip analysis). - if (objectCreation.ArgumentList is null) - { - return; - } - - // Gets the first argument (skip analysis if no argument) - var firstArgument = objectCreation.ArgumentList.Arguments.FirstOrDefault(); - - if (firstArgument is null) - { - return; - } - - // Check if the first argument is MockBehavior argument - var argumentCheckStart = 0; - - if (moqExpressionAnalyzer.IsStrictBehaviorArgument(firstArgument, out var _, context.CancellationToken)) - { - argumentCheckStart = 1; - } - - if (objectCreation.ArgumentList.Arguments.Count > argumentCheckStart) - { - var locations = objectCreation.ArgumentList.Arguments - .Skip(argumentCheckStart) - .Select(a => a.Expression.GetLocation()) - .ToArray(); - - context.ReportDiagnostic(Rule, locations); - - return; - } - } - } -} diff --git a/src/Moq.Analyzers/Analyzers/ConstructorArgumentsMustMatchAnalyzer.cs b/src/Moq.Analyzers/Analyzers/ConstructorArgumentsMustMatchAnalyzer.cs index 39b6685..205bbc5 100644 --- a/src/Moq.Analyzers/Analyzers/ConstructorArgumentsMustMatchAnalyzer.cs +++ b/src/Moq.Analyzers/Analyzers/ConstructorArgumentsMustMatchAnalyzer.cs @@ -16,6 +16,16 @@ namespace PosInformatique.Moq.Analyzers [DiagnosticAnalyzer(LanguageNames.CSharp)] public class ConstructorArgumentsMustMatchAnalyzer : DiagnosticAnalyzer { + internal static readonly DiagnosticDescriptor ConstructorArgumentsCanBePassedToInterfaceRule = new DiagnosticDescriptor( + "PosInfoMoq2004", + "Constructor arguments cannot be passed for interface mocks", + "Constructor arguments cannot be passed for interface mocks", + "Compilation", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Constructor arguments cannot be passed for interface mocks.", + helpLinkUri: "https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Compilation/PosInfoMoq2004.html"); + private static readonly DiagnosticDescriptor ConstructorArgumentsMustMatchMockedClassRule = new DiagnosticDescriptor( "PosInfoMoq2005", "Constructor arguments must match the constructors of the mocked class", @@ -47,6 +57,7 @@ public class ConstructorArgumentsMustMatchAnalyzer : DiagnosticAnalyzer helpLinkUri: "https://posinformatique.github.io/PosInformatique.Moq.Analyzers/docs/Compilation/PosInfoMoq2016.html"); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( + ConstructorArgumentsCanBePassedToInterfaceRule, ConstructorArgumentsMustMatchMockedClassRule, ConstructorMockedClassMustBeAccessibleRule, ConstructorWithLambdaExpressionCanBeUseWithClassesOnlyRule); @@ -100,12 +111,6 @@ private static void Analyze(SyntaxNodeAnalysisContext context) return; } - // Check the type is a class (other type are ignored) - if (mockedType.TypeKind != TypeKind.Class) - { - return; - } - // Check the type is a named type if (mockedType is not INamedTypeSymbol namedTypeSymbol) { @@ -131,6 +136,21 @@ private static void Analyze(SyntaxNodeAnalysisContext context) } } + // If the type is an interface and contains arguments, raise an error. + if (mockedType.TypeKind == TypeKind.Interface) + { + if (constructorArguments.Count > 0) + { + context.ReportDiagnostic(ConstructorArgumentsCanBePassedToInterfaceRule, constructorArguments.Select(a => a.GetLocation())); + } + } + + // Check the type is a class (other type are ignored) + if (mockedType.TypeKind != TypeKind.Class) + { + return; + } + var matchedConstructor = default(MatchedConstructor); // Iterate on each constructor and check if the arguments match. diff --git a/tests/Moq.Analyzers.Tests/Analyzers/ConstructorArgumentCannotBePassedForInterfaceAnalyzerTest.cs b/tests/Moq.Analyzers.Tests/Analyzers/ConstructorArgumentCannotBePassedForInterfaceAnalyzerTest.cs deleted file mode 100644 index 6f48b13..0000000 --- a/tests/Moq.Analyzers.Tests/Analyzers/ConstructorArgumentCannotBePassedForInterfaceAnalyzerTest.cs +++ /dev/null @@ -1,204 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) P.O.S Informatique. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace PosInformatique.Moq.Analyzers.Tests -{ - using Microsoft.CodeAnalysis.Testing; - using Verifier = MoqCSharpAnalyzerVerifier; - - public class ConstructorArgumentCannotBePassedForInterfaceAnalyzerTest - { - [Fact] - public async Task Interface_NoMock() - { - var source = @" - namespace ConsoleApplication1 - { - public class TestClass - { - public void TestMethod() - { - var obj = new object(); - } - } - - public interface I - { - } - }"; - - await Verifier.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task Interface_WithNoArgument() - { - var source = @" - namespace ConsoleApplication1 - { - using Moq; - - public class TestClass - { - public void TestMethod() - { - var mock1 = new Mock(); - } - } - - public interface I - { - } - }"; - - await Verifier.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task Interface_WithoutBehaviorStrict() - { - var source = @" - namespace ConsoleApplication1 - { - using Moq; - - public class TestClass - { - public void TestMethod() - { - var mock1 = new Mock({|#0:1|}, {|#1:2|}); - } - } - - public interface I - { - } - }"; - - await Verifier.VerifyAnalyzerAsync( - source, - new DiagnosticResult(ConstructorArgumentCannotBePassedForInterfaceAnalyzer.Rule) - .WithLocation(0).WithArguments("1") - .WithLocation(1).WithArguments("2")); - } - - [Fact] - public async Task Interface_WithBehaviorStrict() - { - var source = @" - namespace ConsoleApplication1 - { - using Moq; - - public class TestClass - { - public void TestMethod() - { - var mock1 = new Mock(MockBehavior.Strict, {|#0:1|}, {|#1:2|}); - } - } - - public interface I - { - } - }"; - - await Verifier.VerifyAnalyzerAsync( - source, - new DiagnosticResult(ConstructorArgumentCannotBePassedForInterfaceAnalyzer.Rule) - .WithLocation(0).WithArguments("1") - .WithLocation(1).WithArguments("2")); - } - - [Theory] - [InlineData("")] - [InlineData(", MockBehavior.Strict")] - public async Task Interface_WithFactoryLambdaExpression(string behavior) - { - var source = @" - namespace ConsoleApplication1 - { - using Moq; - - public class TestClass - { - public void TestMethod() - { - var mock1 = new Mock(() => new C()" + behavior + @"); - } - } - - public interface I - { - } - - public class C : I - { - } - }"; - - await Verifier.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task Class() - { - var source = @" - namespace ConsoleApplication1 - { - using Moq; - - public class TestClass - { - public void TestMethod() - { - var mock1 = new Mock(1, 2, 3); - } - } - - public abstract class C - { - } - }"; - - await Verifier.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task Interface_NoMoqLibrary() - { - var source = @" - namespace ConsoleApplication1 - { - using OtherNamespace; - - public class TestClass - { - public void TestMethod() - { - var mock1 = new Mock(MockBehavior.Strict, 1, 2, 3); - } - } - - public interface I - { - } - } - - namespace OtherNamespace - { - public class Mock - { - public Mock(MockBehavior _, int a, int b, int c) { } - } - - public enum MockBehavior { Strict, Loose } - }"; - - await Verifier.VerifyAnalyzerWithNoMoqLibraryAsync(source); - } - } -} \ No newline at end of file diff --git a/tests/Moq.Analyzers.Tests/Analyzers/ConstructorArgumentsMustMatchAnalyzerTest.cs b/tests/Moq.Analyzers.Tests/Analyzers/ConstructorArgumentsMustMatchAnalyzerTest.cs index 1ea68ea..6a2b0e0 100644 --- a/tests/Moq.Analyzers.Tests/Analyzers/ConstructorArgumentsMustMatchAnalyzerTest.cs +++ b/tests/Moq.Analyzers.Tests/Analyzers/ConstructorArgumentsMustMatchAnalyzerTest.cs @@ -6,6 +6,7 @@ namespace PosInformatique.Moq.Analyzers.Tests { + using Microsoft.CodeAnalysis.Testing; using Verifier = MoqCSharpAnalyzerVerifier; public class ConstructorArgumentsMustMatchAnalyzerTest @@ -54,6 +55,108 @@ public void TestMethod() await Verifier.VerifyAnalyzerAsync(source); } + [Fact] + public async Task Interface_NoMock() + { + var source = @" + namespace ConsoleApplication1 + { + public class TestClass + { + public void TestMethod() + { + var obj = new object(); + } + } + + public interface I + { + } + }"; + + await Verifier.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task Interface_WithNoArgument() + { + var source = @" + namespace ConsoleApplication1 + { + using Moq; + + public class TestClass + { + public void TestMethod() + { + var mock1 = new Mock(); + } + } + + public interface I + { + } + }"; + + await Verifier.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task Interface_WithoutBehaviorStrict() + { + var source = @" + namespace ConsoleApplication1 + { + using Moq; + + public class TestClass + { + public void TestMethod() + { + var mock1 = new Mock({|#0:1|}, {|#1:2|}); + } + } + + public interface I + { + } + }"; + + await Verifier.VerifyAnalyzerAsync( + source, + new DiagnosticResult(ConstructorArgumentsMustMatchAnalyzer.ConstructorArgumentsCanBePassedToInterfaceRule) + .WithLocation(0).WithArguments("1") + .WithLocation(1).WithArguments("2")); + } + + [Fact] + public async Task Interface_WithBehaviorStrict() + { + var source = @" + namespace ConsoleApplication1 + { + using Moq; + + public class TestClass + { + public void TestMethod() + { + var mock1 = new Mock(MockBehavior.Strict, {|#0:1|}, {|#1:2|}); + } + } + + public interface I + { + } + }"; + + await Verifier.VerifyAnalyzerAsync( + source, + new DiagnosticResult(ConstructorArgumentsMustMatchAnalyzer.ConstructorArgumentsCanBePassedToInterfaceRule) + .WithLocation(0).WithArguments("1") + .WithLocation(1).WithArguments("2")); + } + [Fact] public async Task Arguments_Empty() {