Skip to content

Commit 875e9ec

Browse files
authored
[GH-157] - correct handling of CallInfo<T> usages (#158)
1 parent 254cee4 commit 875e9ec

File tree

25 files changed

+403
-65
lines changed

25 files changed

+403
-65
lines changed

benchmarks/NSubstitute.Analyzers.Benchmarks.Source.CSharp/NSubstitute.Analyzers.Benchmarks.Source.CSharp.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
</PropertyGroup>
66

77
<ItemGroup>
8-
<PackageReference Include="NSubstitute" Version="4.0.0" />
8+
<PackageReference Include="NSubstitute" Version="4.2.2" />
99
</ItemGroup>
1010

1111
<ItemGroup>

benchmarks/NSubstitute.Analyzers.Benchmarks.Source.VisualBasic/NSubstitute.Analyzers.Benchmarks.Source.VisualBasic.vbproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</ItemGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="NSubstitute" Version="4.0.0" />
12+
<PackageReference Include="NSubstitute" Version="4.2.2" />
1313
</ItemGroup>
1414

1515
</Project>
146 KB
Binary file not shown.
151 KB
Binary file not shown.

src/NSubstitute.Analyzers.CSharp/CodeFixProviders/ReEntrantSetupCodeFixProvider.cs

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -96,24 +96,10 @@ private static SeparatedSyntaxList<ExpressionSyntax> CreateSimpleLambdaExpressio
9696
private static ArrayTypeSyntax CreateArrayTypeNode(SyntaxGenerator syntaxGenerator, ITypeSymbol type)
9797
{
9898
var typeSyntax = (TypeSyntax)syntaxGenerator.TypeExpression(type);
99-
var typeArgumentListSyntax = TypeArgumentList(
100-
SeparatedList<TypeSyntax>(
101-
new SyntaxNodeOrToken[]
102-
{
103-
QualifiedName(
104-
QualifiedName(
105-
IdentifierName("NSubstitute"),
106-
IdentifierName("Core")),
107-
IdentifierName("CallInfo")),
108-
Token(SyntaxKind.CommaToken),
109-
typeSyntax
110-
}));
111-
112-
var qualifiedNameSyntax = QualifiedName(IdentifierName("System"), GenericName(Identifier("Func"), typeArgumentListSyntax));
11399

114100
var arrayRankSpecifierSyntaxes = SingletonList(ArrayRankSpecifier(SingletonSeparatedList<ExpressionSyntax>(OmittedArraySizeExpression())));
115101

116-
return ArrayType(qualifiedNameSyntax, arrayRankSpecifierSyntaxes).WithAdditionalAnnotations(Simplifier.Annotation);
102+
return ArrayType(typeSyntax, arrayRankSpecifierSyntaxes).WithAdditionalAnnotations(Simplifier.Annotation);
117103
}
118104
}
119105
}

src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/CallInfoCallFinder.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.CodeAnalysis.CSharp.Syntax;
55
using NSubstitute.Analyzers.Shared;
66
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;
7+
using NSubstitute.Analyzers.Shared.Extensions;
78

89
namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers
910
{
@@ -48,7 +49,7 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node)
4849
{
4950
var symbolInfo = _semanticModel.GetSymbolInfo(node);
5051

51-
if (symbolInfo.Symbol != null && symbolInfo.Symbol.ContainingType.ToString().Equals(MetadataNames.NSubstituteCallInfoFullTypeName))
52+
if (symbolInfo.Symbol != null && symbolInfo.Symbol.ContainingType.IsCallInfoSymbol())
5253
{
5354
switch (symbolInfo.Symbol.Name)
5455
{
@@ -67,7 +68,7 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node)
6768
public override void VisitElementAccessExpression(ElementAccessExpressionSyntax node)
6869
{
6970
var symbolInfo = ModelExtensions.GetSymbolInfo(_semanticModel, node).Symbol ?? ModelExtensions.GetSymbolInfo(_semanticModel, node.Expression).Symbol;
70-
if (symbolInfo != null && symbolInfo.ContainingType.ToString().Equals(MetadataNames.NSubstituteCallInfoFullTypeName))
71+
if (symbolInfo != null && symbolInfo.ContainingType.IsCallInfoSymbol())
7172
{
7273
DirectIndexerAccesses.Add(node);
7374
}

src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractReEntrantSetupCodeFixProvider.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.CodeAnalysis.Editing;
1010
using Microsoft.CodeAnalysis.Operations;
1111
using Microsoft.CodeAnalysis.Simplification;
12+
using NSubstitute.Analyzers.Shared.Extensions;
1213

1314
namespace NSubstitute.Analyzers.Shared.CodeFixProviders
1415
{
@@ -78,13 +79,15 @@ private async Task<Document> CreateChangedDocument(
7879
? 1
7980
: 0;
8081

82+
ITypeSymbol lambdaType = null;
8183
foreach (var argumentSyntax in argumentSyntaxes.Skip(skip))
8284
{
8385
if (IsArrayParamsArgument(semanticModel, argumentSyntax))
8486
{
87+
lambdaType = lambdaType ?? ConstructCallInfoLambdaType(methodSymbol, semanticModel);
8588
var updatedParamsArgumentSyntaxNode = CreateUpdatedParamsArgumentSyntaxNode(
8689
SyntaxGenerator.GetGenerator(context.Document),
87-
methodSymbol.TypeArguments.FirstOrDefault() ?? methodSymbol.ReceiverType,
90+
lambdaType,
8891
argumentSyntax);
8992

9093
documentEditor.ReplaceNode(argumentSyntax, updatedParamsArgumentSyntaxNode);
@@ -100,6 +103,21 @@ private async Task<Document> CreateChangedDocument(
100103
return await Simplifier.ReduceAsync(documentEditor.GetChangedDocument(), cancellationToken: ct);
101104
}
102105

106+
private static ITypeSymbol ConstructCallInfoLambdaType(IMethodSymbol methodSymbol, SemanticModel semanticModel)
107+
{
108+
var callInfoOverloadMethodSymbol = methodSymbol.ContainingType.GetMembers(methodSymbol.Name)
109+
.Where(symbol => !symbol.Equals(methodSymbol.ConstructedFrom))
110+
.OfType<IMethodSymbol>()
111+
.First(method => method.Parameters.Any(param => param.Type.IsCallInfoDelegate(semanticModel)));
112+
113+
var typeArgument = methodSymbol.TypeArguments.FirstOrDefault() ?? methodSymbol.ReceiverType;
114+
var constructedOverloadSymbol = callInfoOverloadMethodSymbol.Construct(typeArgument);
115+
var lambdaType = constructedOverloadSymbol.Parameters
116+
.First(param => param.Type.IsCallInfoDelegate(semanticModel)).Type;
117+
118+
return lambdaType;
119+
}
120+
103121
private bool IsFixSupported(SemanticModel semanticModel, IEnumerable<TArgumentSyntax> arguments)
104122
{
105123
return arguments.All(arg =>

src/NSubstitute.Analyzers.Shared/Extensions/TypeInfoExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ typeSymbol is INamedTypeSymbol namedTypeSymbol &&
2525
}
2626

2727
public static bool IsCallInfoSymbol(this ITypeSymbol symbol)
28+
{
29+
return IsCallInfoSymbolInternal(symbol) || IsCallInfoSymbolInternal(symbol.BaseType);
30+
}
31+
32+
private static bool IsCallInfoSymbolInternal(ISymbol symbol)
2833
{
2934
return symbol != null &&
3035
symbol.ContainingAssembly?.Name.Equals(MetadataNames.NSubstituteAssemblyName, StringComparison.OrdinalIgnoreCase) == true &&

src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/ReEntrantSetupCodeFixProvider.cs

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -116,25 +116,10 @@ private static SingleLineLambdaExpressionSyntax CreateSingleLineLambdaExpression
116116
return lambdaExpression;
117117
}
118118

119-
private static QualifiedNameSyntax CreateTypeNode(SyntaxGenerator syntaxGenerator, ITypeSymbol type)
119+
private static TypeSyntax CreateTypeNode(SyntaxGenerator syntaxGenerator, ITypeSymbol type)
120120
{
121121
var typeSyntax = (TypeSyntax)syntaxGenerator.TypeExpression(type);
122-
var typeArgumentListSyntax = TypeArgumentList(
123-
SeparatedList<TypeSyntax>(
124-
new SyntaxNodeOrToken[]
125-
{
126-
QualifiedName(
127-
QualifiedName(
128-
IdentifierName("NSubstitute"),
129-
IdentifierName("Core")),
130-
IdentifierName("CallInfo")),
131-
Token(SyntaxKind.CommaToken),
132-
typeSyntax
133-
}));
134-
135-
var qualifiedNameSyntax = QualifiedName(IdentifierName("System"), GenericName(Identifier("Func"), typeArgumentListSyntax));
136-
137-
return qualifiedNameSyntax.WithAdditionalAnnotations(Simplifier.Annotation);
122+
return typeSyntax.WithAdditionalAnnotations(Simplifier.Annotation);
138123
}
139124
}
140125
}

src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/CallInfoCallFinder.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
55
using NSubstitute.Analyzers.Shared;
66
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;
7+
using NSubstitute.Analyzers.Shared.Extensions;
78

89
namespace NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers
910
{
@@ -48,7 +49,7 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node)
4849
{
4950
var symbol = _semanticModel.GetSymbolInfo(node).Symbol;
5051

51-
if (symbol != null && symbol.ContainingType.ToString().Equals(MetadataNames.NSubstituteCallInfoFullTypeName))
52+
if (symbol != null && symbol.ContainingType.IsCallInfoSymbol())
5253
{
5354
switch (symbol.Name)
5455
{
@@ -68,7 +69,7 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node)
6869
{
6970
var expressionSymbol = _semanticModel.GetSymbolInfo(node.Expression).Symbol;
7071

71-
if (expressionSymbol != null && expressionSymbol.ContainingType.ToString().Equals(MetadataNames.NSubstituteCallInfoFullTypeName))
72+
if (expressionSymbol != null && expressionSymbol.ContainingType.IsCallInfoSymbol())
7273
{
7374
DirectIndexerAccesses.Add(node);
7475
}

tests/NSubstitute.Analyzers.Tests.Benchmarks/NSubstitute.Analyzers.Tests.Benchmarks.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
<PackageReference Include="coverlet.msbuild" Version="2.6.0" />
77
<PackageReference Include="FluentAssertions" Version="5.0.0" />
88
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
9-
<PackageReference Include="NSubstitute" Version="4.0.0" />
109
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.2" />
1110
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
1211
<PackageReference Include="xunit" Version="2.4.1" />

tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReEntrantSetupCodeFixVerifier.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,17 @@ protected ReEntrantSetupCodeFixVerifier()
2121

2222
[Theory]
2323
[InlineData("CreateReEntrantSubstitute(), CreateDefaultValue(), 1", "_ => CreateReEntrantSubstitute(), _ => CreateDefaultValue(), _ => 1")]
24-
[InlineData("CreateReEntrantSubstitute(), new [] { CreateDefaultValue(), 1 }", "_ => CreateReEntrantSubstitute(), new System.Func<NSubstitute.Core.CallInfo, int>[] { _ => CreateDefaultValue(), _ => 1 }")]
25-
[InlineData("CreateReEntrantSubstitute(), new int[] { CreateDefaultValue(), 1 }", "_ => CreateReEntrantSubstitute(), new System.Func<NSubstitute.Core.CallInfo, int>[] { _ => CreateDefaultValue(), _ => 1 }")]
24+
[InlineData("CreateReEntrantSubstitute(), new [] { CreateDefaultValue(), 1 }", "_ => CreateReEntrantSubstitute(), new System.Func<NSubstitute.Core.CallInfo<int>, int>[] { _ => CreateDefaultValue(), _ => 1 }")]
25+
[InlineData("CreateReEntrantSubstitute(), new int[] { CreateDefaultValue(), 1 }", "_ => CreateReEntrantSubstitute(), new System.Func<NSubstitute.Core.CallInfo<int>, int>[] { _ => CreateDefaultValue(), _ => 1 }")]
2626
[InlineData("returnThis: CreateReEntrantSubstitute()", "returnThis: _ => CreateReEntrantSubstitute()")]
27-
[InlineData("returnThis: CreateReEntrantSubstitute(), returnThese: new [] { CreateDefaultValue(), 1 }", "returnThis: _ => CreateReEntrantSubstitute(), returnThese: new System.Func<NSubstitute.Core.CallInfo, int>[] { _ => CreateDefaultValue(), _ => 1 }")]
28-
[InlineData("returnThis: CreateReEntrantSubstitute(), returnThese: new int[] { CreateDefaultValue(), 1 }", "returnThis: _ => CreateReEntrantSubstitute(), returnThese: new System.Func<NSubstitute.Core.CallInfo, int>[] { _ => CreateDefaultValue(), _ => 1 }")]
27+
[InlineData("returnThis: CreateReEntrantSubstitute(), returnThese: new [] { CreateDefaultValue(), 1 }", "returnThis: _ => CreateReEntrantSubstitute(), returnThese: new System.Func<NSubstitute.Core.CallInfo<int>, int>[] { _ => CreateDefaultValue(), _ => 1 }")]
28+
[InlineData("returnThis: CreateReEntrantSubstitute(), returnThese: new int[] { CreateDefaultValue(), 1 }", "returnThis: _ => CreateReEntrantSubstitute(), returnThese: new System.Func<NSubstitute.Core.CallInfo<int>, int>[] { _ => CreateDefaultValue(), _ => 1 }")]
2929
public abstract Task ReplacesArgumentExpression_WithLambda(string arguments, string rewrittenArguments);
3030

3131
[Fact]
3232
public abstract Task ReplacesArgumentExpression_WithLambdaWithReducedTypes_WhenGeneratingArrayParamsArgument();
33+
34+
[Fact]
35+
public abstract Task ReplacesArgumentExpression_WithLambdaWithNonGenericCallInfo_WhenGeneratingArrayParamsArgument();
3336
}
3437
}

tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsExtensionMethodTests.cs

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using System;
12
using System.Threading.Tasks;
3+
using NSubstitute.Analyzers.Tests.Shared;
24

35
namespace NSubstitute.Analyzers.Tests.CSharp.CodeFixProviderTests.ReEntrantSetupCodeFixProviderTests
46
{
@@ -148,7 +150,7 @@ public interface IFoo
148150
public void Test()
149151
{
150152
var secondSubstitute = Substitute.For<IFoo>();
151-
secondSubstitute.Id.Returns(_ => CreateReEntrantSubstitute(), new Func<CallInfo, int>[] { _ => MyNamespace.FooTests.Value });
153+
secondSubstitute.Id.Returns(_ => CreateReEntrantSubstitute(), new Func<CallInfo<int>, int>[] { _ => MyNamespace.FooTests.Value });
152154
}
153155
154156
private int CreateReEntrantSubstitute()
@@ -161,5 +163,83 @@ private int CreateReEntrantSubstitute()
161163
}";
162164
await VerifyFix(oldSource, newSource);
163165
}
166+
167+
public override async Task ReplacesArgumentExpression_WithLambdaWithNonGenericCallInfo_WhenGeneratingArrayParamsArgument()
168+
{
169+
var oldSource = @"using NSubstitute;
170+
using NSubstitute.Core;
171+
using System;
172+
173+
namespace MyNamespace
174+
{
175+
public class FooTests
176+
{
177+
private IFoo firstSubstitute = Substitute.For<IFoo>();
178+
179+
public static int Value { get; set; }
180+
181+
public FooTests()
182+
{
183+
firstSubstitute.Id.Returns(45);
184+
}
185+
186+
public interface IFoo
187+
{
188+
int Id { get; }
189+
}
190+
191+
public void Test()
192+
{
193+
var secondSubstitute = Substitute.For<IFoo>();
194+
secondSubstitute.Id.Returns(CreateReEntrantSubstitute(), new[] { MyNamespace.FooTests.Value });
195+
}
196+
197+
private int CreateReEntrantSubstitute()
198+
{
199+
var substitute = Substitute.For<IFoo>();
200+
substitute.Id.Returns(1);
201+
return 1;
202+
}
203+
}
204+
}";
205+
206+
var newSource = @"using NSubstitute;
207+
using NSubstitute.Core;
208+
using System;
209+
210+
namespace MyNamespace
211+
{
212+
public class FooTests
213+
{
214+
private IFoo firstSubstitute = Substitute.For<IFoo>();
215+
216+
public static int Value { get; set; }
217+
218+
public FooTests()
219+
{
220+
firstSubstitute.Id.Returns(45);
221+
}
222+
223+
public interface IFoo
224+
{
225+
int Id { get; }
226+
}
227+
228+
public void Test()
229+
{
230+
var secondSubstitute = Substitute.For<IFoo>();
231+
secondSubstitute.Id.Returns(_ => CreateReEntrantSubstitute(), new Func<CallInfo, int>[] { _ => MyNamespace.FooTests.Value });
232+
}
233+
234+
private int CreateReEntrantSubstitute()
235+
{
236+
var substitute = Substitute.For<IFoo>();
237+
substitute.Id.Returns(1);
238+
return 1;
239+
}
240+
}
241+
}";
242+
await VerifyFix(oldSource, newSource, version: NSubstituteVersion.NSubstitute4_2_2);
243+
}
164244
}
165245
}

0 commit comments

Comments
 (0)