Skip to content

Commit

Permalink
Merge branch 'dev' into GH-163-expression-in-arg-matchers
Browse files Browse the repository at this point in the history
  • Loading branch information
tpodolak authored Sep 5, 2021
2 parents f90c4a2 + e52798e commit 80e03d4
Show file tree
Hide file tree
Showing 15 changed files with 1,010 additions and 281 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,21 @@ public ConstructorContext CollectConstructorContext(SubstituteContext<TInvocatio

var accessibleConstructors = GetAccessibleConstructors(proxyTypeSymbol);
var invocationParameterTypes = GetInvocationInfo(substituteContext);

bool IsPossibleConstructor(IMethodSymbol methodSymbol)
{
var nonParamsParametersCount = methodSymbol.Parameters.Count(parameter => !parameter.IsParams);

if (nonParamsParametersCount == methodSymbol.Parameters.Length)
{
return methodSymbol.Parameters.Length == invocationParameterTypes.Length;
}

return invocationParameterTypes.Length >= nonParamsParametersCount;
}

var possibleConstructors = invocationParameterTypes != null && accessibleConstructors != null
? accessibleConstructors.Where(ctor => ctor.Parameters.Length == invocationParameterTypes.Length)
? accessibleConstructors.Where(IsPossibleConstructor)
.ToArray()
: null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,36 @@ internal abstract class AbstractSubstituteConstructorMatcher : ISubstituteConstr

public bool MatchesInvocation(Compilation compilation, IMethodSymbol methodSymbol, IList<ITypeSymbol> invocationParameters)
{
if (methodSymbol.Parameters.Length != invocationParameters.Count)
if (methodSymbol.Parameters.Length == 0)
{
return false;
return true;
}

return methodSymbol.Parameters.Length == 0 || methodSymbol.Parameters
.Where((symbol, index) => ClasifyConversion(compilation, invocationParameters[index], symbol.Type))
.Count() == methodSymbol.Parameters.Length;
return methodSymbol.Parameters.All(parameter =>
MatchesInvocation(compilation, parameter, invocationParameters));
}

protected abstract bool IsConvertible(Compilation compilation, ITypeSymbol source, ITypeSymbol destination);

private bool ClasifyConversion(Compilation compilation, ITypeSymbol source, ITypeSymbol destination)
// TODO simplify once https://github.com/nsubstitute/NSubstitute.Analyzers/issues/153 is implemented
private bool MatchesInvocation(Compilation compilation, IParameterSymbol symbol, IList<ITypeSymbol> invocationParameters)
{
if (!symbol.IsParams)
{
return symbol.Ordinal < invocationParameters.Count &&
ClassifyConversion(compilation, invocationParameters[symbol.Ordinal], symbol.Type);
}

if (symbol.Type is IArrayTypeSymbol arrayTypeSymbol)
{
return symbol.Ordinal >= invocationParameters.Count ||
ClassifyConversion(compilation, invocationParameters[symbol.Ordinal], arrayTypeSymbol.ElementType);
}

return false;
}

private bool ClassifyConversion(Compilation compilation, ITypeSymbol source, ITypeSymbol destination)
{
if (source == null || source.Equals(destination))
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using NSubstitute.Analyzers.CSharp;
using NSubstitute.Analyzers.Shared;
using NSubstitute.Analyzers.Tests.Shared;
using NSubstitute.Analyzers.Tests.Shared.DiagnosticAnalyzers;
using NSubstitute.Analyzers.Tests.Shared.Extensions;
using Xunit;

namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.SubstituteAnalyzerTests
{
public class ForAsGenericMethodTests : SubstituteDiagnosticVerifier
{
[Fact]
public async Task ReturnsNoDiagnostic_WhenUsedForInterface()
public async Task ReportsNoDiagnostic_WhenUsedForInterface()
{
var source = @"using NSubstitute;
Expand All @@ -35,7 +29,7 @@ public void Test()
}

[Fact]
public async Task ReturnsDiagnostic_WhenUsedForInterface_AndConstructorParametersUsed()
public async Task ReportsDiagnostic_WhenUsedForInterface_AndConstructorParametersUsed()
{
var source = @"using NSubstitute;
Expand All @@ -57,7 +51,7 @@ public void Test()
}

[Fact]
public async Task ReturnsNoDiagnostic_WhenUsedForDelegate()
public async Task ReportsNoDiagnostic_WhenUsedForDelegate()
{
var source = @"using NSubstitute;
using System;
Expand All @@ -79,7 +73,7 @@ public void Test()
}

[Fact]
public async Task ReturnsDiagnostic_WhenUsedForDelegate_AndConstructorParametersUsed()
public async Task ReportsDiagnostic_WhenUsedForDelegate_AndConstructorParametersUsed()
{
var source = @"using NSubstitute;
using System;
Expand All @@ -97,7 +91,7 @@ public void Test()
}

[Fact]
public async Task ReturnsDiagnostic_WhenMultipleGenericTypeParameters_ContainsMultipleClasses()
public async Task ReportsDiagnostic_WhenMultipleGenericTypeParameters_ContainsMultipleClasses()
{
var source = @"using NSubstitute;
Expand All @@ -123,7 +117,7 @@ public void Test()
}

[Fact]
public async Task ReturnsNoDiagnostic_WhenMultipleGenericTypeParameters_ContainsMultipleSameClasses()
public async Task ReportsNoDiagnostic_WhenMultipleGenericTypeParameters_ContainsMultipleSameClasses()
{
var source = @"using NSubstitute;
Expand All @@ -146,7 +140,7 @@ public void Test()
}

[Fact]
public async Task ReturnsNoDiagnostic_WhenMultipleGenericTypeParameters_ContainsMultipleInterfaces()
public async Task ReportsNoDiagnostic_WhenMultipleGenericTypeParameters_ContainsMultipleInterfaces()
{
var source = @"using NSubstitute;
Expand All @@ -173,7 +167,7 @@ public void Test()
}

[Fact]
public async Task ReturnsNoDiagnostic_WhenMultipleGenericTypeParameters_ContainsInterfaceNotImplementedByClass()
public async Task ReportsNoDiagnostic_WhenMultipleGenericTypeParameters_ContainsInterfaceNotImplementedByClass()
{
var source = @"using NSubstitute;
Expand All @@ -200,7 +194,7 @@ public void Test()
}

[Fact]
public async Task ReturnsDiagnostic_WhenMultipleGenericTypeParameters_ContainsClassWithoutMatchingConstructor()
public async Task ReportsDiagnostic_WhenMultipleGenericTypeParameters_ContainsClassWithoutMatchingConstructor()
{
var source = @"using NSubstitute;
Expand All @@ -226,7 +220,7 @@ public void Test()
}

[Fact]
public override async Task ReturnsDiagnostic_WhenUsedForClassWithoutPublicOrProtectedConstructor()
public override async Task ReportsDiagnostic_WhenUsedForClassWithoutPublicOrProtectedConstructor()
{
var source = @"using NSubstitute;
Expand All @@ -250,7 +244,7 @@ public void Test()
await VerifyDiagnostic(source, SubstituteForWithoutAccessibleConstructorDescriptor, "Could not find accessible constructor. Make sure that type MyNamespace.Foo exposes public or protected constructors.");
}

public override async Task ReturnsDiagnostic_WhenUsedForClassWithInternalConstructor_AndInternalsVisibleToNotApplied()
public override async Task ReportsDiagnostic_WhenUsedForClassWithInternalConstructor_AndInternalsVisibleToNotApplied()
{
var source = @"using NSubstitute;
Expand All @@ -274,7 +268,7 @@ public void Test()
await VerifyDiagnostic(source, SubstituteForWithoutAccessibleConstructorDescriptor, "Could not find accessible constructor. Make sure that type MyNamespace.Foo exposes public or protected constructors.");
}

public override async Task ReturnsDiagnostic_WhenUsedForClassWithProtectedInternalConstructor_AndInternalsVisibleToNotApplied()
public override async Task ReportsDiagnostic_WhenUsedForClassWithProtectedInternalConstructor_AndInternalsVisibleToNotApplied()
{
var source = @"using NSubstitute;
Expand All @@ -298,7 +292,7 @@ public void Test()
await VerifyDiagnostic(source, SubstituteForWithoutAccessibleConstructorDescriptor, "Could not find accessible constructor. Make sure that type MyNamespace.Foo exposes public or protected constructors.");
}

public override async Task ReturnsNoDiagnostic_WhenUsedForClassWithInternalConstructor_AndInternalsVisibleToApplied()
public override async Task ReportsNoDiagnostic_WhenUsedForClassWithInternalConstructor_AndInternalsVisibleToApplied()
{
var source = @"using NSubstitute;
using System.Runtime.CompilerServices;
Expand All @@ -324,7 +318,7 @@ public void Test()
await VerifyNoDiagnostic(source);
}

public override async Task ReturnsNoDiagnostic_WhenUsedForClassWithProtectedInternalConstructor_AndInternalsVisibleToApplied()
public override async Task ReportsNoDiagnostic_WhenUsedForClassWithProtectedInternalConstructor_AndInternalsVisibleToApplied()
{
var source = @"using NSubstitute;
using System.Runtime.CompilerServices;
Expand All @@ -351,7 +345,7 @@ public void Test()
}

[Fact]
public override async Task ReturnsDiagnostic_WhenPassedParametersCount_GreaterThanCtorParametersCount()
public override async Task ReportsDiagnostic_WhenPassedParametersCount_GreaterThanCtorParametersCount()
{
var source = @"using NSubstitute;
Expand All @@ -376,7 +370,7 @@ public void Test()
}

[Fact]
public override async Task ReturnsDiagnostic_WhenPassedParametersCount_LessThanCtorParametersCount()
public override async Task ReportsDiagnostic_WhenPassedParametersCount_LessThanCtorParametersCount()
{
var source = @"using NSubstitute;
Expand All @@ -401,7 +395,7 @@ public void Test()
}

[Fact]
public override async Task ReturnsDiagnostic_WhenUsedWithWithoutProvidingOptionalParameters()
public override async Task ReportsDiagnostic_WhenUsedWithWithoutProvidingOptionalParameters()
{
var source = @"using NSubstitute;
Expand All @@ -426,7 +420,7 @@ public void Test()
}

[Fact]
public override async Task ReturnsDiagnostic_WhenUsedWithInternalClass_AndInternalsVisibleToNotApplied()
public override async Task ReportsDiagnostic_WhenUsedWithInternalClass_AndInternalsVisibleToNotApplied()
{
var source = @"using NSubstitute;
namespace MyNamespace
Expand All @@ -446,7 +440,7 @@ public void Test()
await VerifyDiagnostic(source, SubstituteForInternalMemberDescriptor);
}

public override async Task ReturnsNoDiagnostic_WhenUsedWithInternalClass_AndInternalsVisibleToAppliedToDynamicProxyGenAssembly2(string assemblyAttributes)
public override async Task ReportsNoDiagnostic_WhenUsedWithInternalClass_AndInternalsVisibleToAppliedToDynamicProxyGenAssembly2(string assemblyAttributes)
{
var source = $@"using NSubstitute;
using System.Runtime.CompilerServices;
Expand All @@ -468,7 +462,7 @@ public void Test()
await VerifyNoDiagnostic(source);
}

public override async Task ReturnsDiagnostic_WhenUsedWithInternalClass_AndInternalsVisibleToAppliedToWrongAssembly(string assemblyAttributes)
public override async Task ReportsDiagnostic_WhenUsedWithInternalClass_AndInternalsVisibleToAppliedToWrongAssembly(string assemblyAttributes)
{
var source = $@"using NSubstitute;
using System.Runtime.CompilerServices;
Expand All @@ -495,9 +489,11 @@ public void Test()
[InlineData("int x", "1m")]
[InlineData("int x", "1D")]
[InlineData("List<int> x", "new List<int>().AsReadOnly()")]
[InlineData("params int[] x", "1m")]
[InlineData("int x", "new [] { 1 }")]
[InlineData("int x", "new object()")]
public override async Task ReturnsDiagnostic_WhenConstructorArgumentsRequireExplicitConversion(string ctorValues, string invocationValues)
[InlineData("params int[] x", "new [] { 1m }")]
public override async Task ReportsDiagnostic_WhenConstructorArgumentsRequireExplicitConversion(string ctorValues, string invocationValues)
{
var source = $@"using NSubstitute;
using System.Collections.Generic;
Expand Down Expand Up @@ -529,6 +525,7 @@ public void Test()
[InlineData("IEnumerable<int> x", "new List<int>()")]
[InlineData("IEnumerable<int> x", "new List<int>().AsReadOnly()")]
[InlineData("IEnumerable<char> x", @"""value""")]
[InlineData("int x, params string[] y", "1, \"foo\"")]
[InlineData("int x", @"new object[] { 1 }")]
[InlineData("int[] x", @"new int[] { 1 }")]
[InlineData("object[] x , int y", @"new object[] { 1 }, 1")]
Expand All @@ -538,7 +535,8 @@ public void Test()
[InlineData("int x", "new object[] { null }")] // even though we pass null as first arg, this works fine with NSubstitute
[InlineData("int x, int y", "new object[] { null, null }")] // even though we pass null as first arg, this works fine with NSubstitute
[InlineData("int x, int y", "new object[] { 1, null }")] // even though we pass null as first arg, this works fine with NSubstitute
public override async Task ReturnsNoDiagnostic_WhenConstructorArgumentsDoNotRequireImplicitConversion(string ctorValues, string invocationValues)
[InlineData("int x, params string[] y", "new object[] { 1, \"foo\" }")]
public override async Task ReportsNoDiagnostic_WhenConstructorArgumentsDoNotRequireImplicitConversion(string ctorValues, string invocationValues)
{
var source = $@"using NSubstitute;
using System.Collections.Generic;
Expand All @@ -563,7 +561,7 @@ public void Test()
await VerifyNoDiagnostic(source);
}

public override async Task ReturnsNoDiagnostic_WhenUsedWithGenericArgument()
public override async Task ReportsNoDiagnostic_WhenUsedWithGenericArgument()
{
var source = @"using NSubstitute;
namespace MyNamespace
Expand All @@ -579,5 +577,77 @@ public T Foo<T>() where T : class
}";
await VerifyNoDiagnostic(source);
}

public override async Task ReportsNoDiagnostic_WhenParamsParametersNotProvided()
{
var source = @"using NSubstitute;
namespace MyNamespace
{
public class Foo
{
public Foo(int x, params object[] y)
{
}
}
public class FooTests
{
public void Test()
{
var substitute = NSubstitute.Substitute.For<Foo>(1);
}
}
}";
await VerifyNoDiagnostic(source);
}

public override async Task ReportsNoDiagnostic_WhenParamsParametersProvided()
{
var source = @"using NSubstitute;
namespace MyNamespace
{
public class Foo
{
public Foo(int x, params object[] y)
{
}
}
public class FooTests
{
public void Test()
{
var substitute = NSubstitute.Substitute.For<Foo>(1, 2, 3);
}
}
}";
await VerifyNoDiagnostic(source);
}

public override async Task ReportsDiagnostic_WhenPassedParametersCount_LessThanCtorParametersCount_AndParamsParameterDefined()
{
var source = @"using NSubstitute;
namespace MyNamespace
{
public class Foo
{
public Foo(int x, int y, params object[] z)
{
}
}
public class FooTests
{
public void Test()
{
var substitute = [|NSubstitute.Substitute.For<Foo>(1)|];
}
}
}";
await VerifyDiagnostic(source, SubstituteForConstructorParametersMismatchDescriptor, "The number of arguments passed to NSubstitute.Substitute.For<MyNamespace.Foo> do not match the number of constructor arguments for MyNamespace.Foo. Check the constructors for MyNamespace.Foo and make sure you have passed the required number of arguments.");
}
}
}
Loading

0 comments on commit 80e03d4

Please sign in to comment.