Skip to content

Commit 9acd514

Browse files
committed
support local variable usage
1 parent 969d8a6 commit 9acd514

File tree

3 files changed

+83
-6
lines changed

3 files changed

+83
-6
lines changed

src/Framework/AspNetCoreAnalyzers/src/Analyzers/Kestrel/ListenOnIPv6AnyAnalyzer.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ private void KestrelServerOptionsListenInvocation(SyntaxNodeAnalysisContext cont
5252

5353
var args = kestrelOptionsListenExpressionSyntax.ArgumentList;
5454
var ipAddressArgumentSyntax = args.Arguments.FirstOrDefault();
55+
if (ipAddressArgumentSyntax is null)
56+
{
57+
return;
58+
}
5559

5660
// explicit usage like `options.Listen(IPAddress.Any, ...)`
5761
if (ipAddressArgumentSyntax is ArgumentSyntax
@@ -64,6 +68,32 @@ private void KestrelServerOptionsListenInvocation(SyntaxNodeAnalysisContext cont
6468
{
6569
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.KestrelShouldListenOnIPv6AnyInsteadOfIpAny, ipAddressArgumentSyntax.GetLocation()));
6670
}
71+
72+
// usage via local variable like
73+
// ```
74+
// var myIp = IPAddress.Any;
75+
// options.Listen(myIp, ...);
76+
// ```
77+
if (addressArgument!.Value is ILocalReferenceOperation localReferenceOperation)
78+
{
79+
var localVariableDeclaration = localReferenceOperation.Local.DeclaringSyntaxReferences.FirstOrDefault();
80+
if (localVariableDeclaration is null)
81+
{
82+
return;
83+
}
84+
85+
var localVarSyntax = localVariableDeclaration.GetSyntax(context.CancellationToken);
86+
if (localVarSyntax is VariableDeclaratorSyntax
87+
{
88+
Initializer.Value: MemberAccessExpressionSyntax
89+
{
90+
Name.Identifier.ValueText: "Any"
91+
}
92+
})
93+
{
94+
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.KestrelShouldListenOnIPv6AnyInsteadOfIpAny, ipAddressArgumentSyntax.GetLocation()));
95+
}
96+
}
6797
}
6898

6999
private static bool IsIPAddressType(IParameterSymbol? parameter) => parameter is

src/Framework/AspNetCoreAnalyzers/src/CodeFixes/Kestrel/ListenOnIPv6AnyFixer.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,31 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
4343
return context.Document;
4444
}
4545

46-
editor.ReplaceNode(argumentSyntax, argumentSyntax.WithExpression(SyntaxFactory.ParseExpression("IPAddress.IPv6Any")));
46+
// get to the `Listen(IPAddress.Any, ...)` invocation
47+
if (argumentSyntax.Parent?.Parent is not InvocationExpressionSyntax { ArgumentList.Arguments.Count: > 1 } invocationExpressionSyntax)
48+
{
49+
return context.Document;
50+
}
51+
if (invocationExpressionSyntax.Expression is not MemberAccessExpressionSyntax memberAccessExpressionSyntax)
52+
{
53+
return context.Document;
54+
}
55+
56+
var instanceVariableInvoked = memberAccessExpressionSyntax.Expression;
57+
var adjustedArgumentList = invocationExpressionSyntax.ArgumentList.RemoveNode(invocationExpressionSyntax.ArgumentList.Arguments.First(), SyntaxRemoveOptions.KeepLeadingTrivia);
58+
if (adjustedArgumentList is null || adjustedArgumentList.Arguments.Count == 0)
59+
{
60+
return context.Document;
61+
}
62+
63+
// changing invocation from `<variable>.Listen(IPAddress.Any, ...)` to `<variable>.ListenAnyIP(...)`
64+
editor.ReplaceNode(
65+
invocationExpressionSyntax,
66+
invocationExpressionSyntax
67+
.WithExpression(SyntaxFactory.ParseExpression($"{instanceVariableInvoked.ToString()}.ListenAnyIP"))
68+
.WithArgumentList(adjustedArgumentList!)
69+
.WithLeadingTrivia(invocationExpressionSyntax.GetLeadingTrivia())
70+
);
4771
return editor.GetChangedDocument();
4872
},
4973
equivalenceKey: DiagnosticDescriptors.KestrelShouldListenOnIPv6AnyInsteadOfIpAny.Id),

src/Framework/AspNetCoreAnalyzers/test/Kestrel/ListenOnIPv6AnyAnalyzerAndFixerTests.cs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,17 @@ namespace Microsoft.AspNetCore.Analyzers.Kestrel;
1313

1414
public class ListenOnIPv6AnyAnalyzerAndFixerTests
1515
{
16-
[Fact] // do we need any other scenarios except the direct usage one?
16+
[Fact]
17+
public async Task ReportsDiagnostic_IPAddressAsLocalVariable_OuterScope()
18+
{
19+
var source = GetKestrelSetupSource("myIp", extraOuterCode: "var myIp = IPAddress.Any;");
20+
await VerifyCS.VerifyAnalyzerAsync(source, codeSampleDiagnosticResult);
21+
}
22+
23+
[Fact]
1724
public async Task ReportsDiagnostic_IPAddressAsLocalVariable()
1825
{
19-
var source = GetKestrelSetupSource("myIp", "var myIp = IPAddress.Any;");
26+
var source = GetKestrelSetupSource("myIp", extraInlineCode: "var myIp = IPAddress.Any;");
2027
await VerifyCS.VerifyAnalyzerAsync(source, codeSampleDiagnosticResult);
2128
}
2229

@@ -31,19 +38,35 @@ public async Task ReportsDiagnostic_ExplicitUsage()
3138
public async Task CodeFix_ExplicitUsage()
3239
{
3340
var source = GetKestrelSetupSource("IPAddress.Any");
34-
var fixedSource = GetKestrelSetupSource("IPAddress.IPv6Any");
41+
var fixedSource = GetCorrectedKestrelSetup();
42+
await VerifyCS.VerifyCodeFixAsync(source, codeSampleDiagnosticResult, fixedSource);
43+
}
44+
45+
[Fact]
46+
public async Task CodeFix_IPAddressAsLocalVariable()
47+
{
48+
var source = GetKestrelSetupSource("IPAddress.Any", extraInlineCode: "var myIp = IPAddress.Any;");
49+
var fixedSource = GetCorrectedKestrelSetup(extraInlineCode: "var myIp = IPAddress.Any;");
3550
await VerifyCS.VerifyCodeFixAsync(source, codeSampleDiagnosticResult, fixedSource);
3651
}
3752

3853
private static DiagnosticResult codeSampleDiagnosticResult
3954
= new DiagnosticResult(DiagnosticDescriptors.KestrelShouldListenOnIPv6AnyInsteadOfIpAny).WithLocation(0);
4055

41-
static string GetKestrelSetupSource(string ipAddressArgument, string extraInlineCode = null) => $$"""
56+
static string GetKestrelSetupSource(string ipAddressArgument, string extraInlineCode = null, string extraOuterCode = null)
57+
=> GetCodeSample($$"""Listen({|#0:{{ipAddressArgument}}|}, """, extraInlineCode, extraOuterCode);
58+
59+
static string GetCorrectedKestrelSetup(string extraInlineCode = null, string extraOuterCode = null)
60+
=> GetCodeSample("ListenAnyIP(", extraInlineCode, extraOuterCode);
61+
62+
static string GetCodeSample(string invocation, string extraInlineCode = null, string extraOuterCode = null) => $$"""
4263
using Microsoft.Extensions.Hosting;
4364
using Microsoft.AspNetCore.Hosting;
4465
using Microsoft.AspNetCore.Server.Kestrel.Core;
4566
using System.Net;
4667
68+
{{extraOuterCode}}
69+
4770
var hostBuilder = new HostBuilder()
4871
.ConfigureWebHost(webHost =>
4972
{
@@ -53,7 +76,7 @@ static string GetKestrelSetupSource(string ipAddressArgument, string extraInline
5376
5477
options.ListenLocalhost(5000);
5578
options.ListenAnyIP(5000);
56-
options.Listen({|#0:{{ipAddressArgument}}|}, 5000, listenOptions =>
79+
options.{{invocation}}5000, listenOptions =>
5780
{
5881
listenOptions.UseHttps();
5982
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;

0 commit comments

Comments
 (0)