Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
6301376
Analyzer
surgupta-msft Sep 20, 2023
c50dfe9
Add analyzer
surgupta-msft Sep 20, 2023
2d5b70b
Fix test
surgupta-msft Sep 20, 2023
4819a5d
Adding docs for Analyzer
surgupta-msft Sep 20, 2023
047edbb
Added tests for aspnet analyzer
surgupta-msft Sep 20, 2023
d9c1665
Add more tests and code cleanup
surgupta-msft Sep 20, 2023
cb41c98
Addressing PR feedback
surgupta-msft Sep 21, 2023
2bb5684
Adding check for Main method
surgupta-msft Sep 25, 2023
5309d78
Merge branch 'main' into surgupta/aspnet-analyzer
surgupta-msft Sep 25, 2023
bfd7b3a
Updated analyzer check
surgupta-msft Sep 27, 2023
fb93b38
Making Analyzer as part of AspNet package
surgupta-msft Sep 27, 2023
ba7f0b0
Add analyzer as part of AspNet project
surgupta-msft Oct 3, 2023
18edbcf
Update sdk analyzer diagnostics
surgupta-msft Oct 3, 2023
b097472
Updating version
surgupta-msft Oct 3, 2023
23f0aac
Minor updates
surgupta-msft Oct 3, 2023
7d460f3
Minor csproj cleanup
surgupta-msft Oct 3, 2023
1b35d92
Adding a new project for AspNet analyzers
surgupta-msft Oct 4, 2023
6d2b485
Fix formatting in csproj file
surgupta-msft Oct 4, 2023
a2ba972
Removing unecessary check
surgupta-msft Oct 4, 2023
eff85a8
Merge branch 'main' into surgupta/aspnet-analyzer
surgupta-msft Oct 4, 2023
a03e4a2
Test fix
surgupta-msft Oct 4, 2023
90827fa
Revert "Test fix"
surgupta-msft Oct 4, 2023
7868a53
Test fix
surgupta-msft Oct 4, 2023
aa2adfc
Moving aspnet projects in a folder
surgupta-msft Oct 6, 2023
1cbf4f7
Revert "Moving aspnet projects in a folder"
surgupta-msft Oct 6, 2023
b9f2351
Fix typo
surgupta-msft Oct 6, 2023
0fcd2fd
Set diagnostic warning
surgupta-msft Oct 6, 2023
ba6aa87
Adding new tests project for aspnet core
surgupta-msft Oct 6, 2023
55c1d3e
Added release notes
surgupta-msft Oct 6, 2023
c7377e4
Updated analyzer rule doc
surgupta-msft Oct 6, 2023
1f847ca
Removing tools files
surgupta-msft Oct 10, 2023
6b81e33
Rebase with main
surgupta-msft Oct 17, 2023
a0cf107
PR cleanup
surgupta-msft Oct 17, 2023
028bfce
PR cleanup
surgupta-msft Oct 17, 2023
226e3b4
Format csproj
surgupta-msft Oct 17, 2023
7e00672
Revert "Format csproj"
surgupta-msft Oct 17, 2023
b3b2a09
build fix
surgupta-msft Oct 17, 2023
1e40912
Rebase with main
surgupta-msft Oct 18, 2023
2da778f
Update sln with project
surgupta-msft Oct 18, 2023
54ae68d
Update sln
surgupta-msft Oct 18, 2023
7b0e909
Update sln
surgupta-msft Oct 18, 2023
4b0c3ce
Updating name to ASP.Net Core Integration
surgupta-msft Oct 18, 2023
6b84c1d
Merge branch 'main' into surgupta/aspnet-analyzer
surgupta-msft Oct 24, 2023
4d66267
Updated condition to generate diagnostic
surgupta-msft Oct 24, 2023
529c4c8
Minor cleanup
surgupta-msft Oct 24, 2023
03f59bc
Docs update, minor test update
surgupta-msft Oct 24, 2023
20abb2f
Narrowing symbol location
surgupta-msft Oct 24, 2023
24bcd68
address PR comments
surgupta-msft Oct 25, 2023
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
19 changes: 19 additions & 0 deletions docs/analyzer-rules/AZFW0014.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# AZFW0011: Missing Registration for ASP.NET Integration

| | Value |
|-|-|
| **Rule ID** |AZFW00014|
| **Category** |[Usage]|
| **Severity** |Error|

## Cause

This rule is triggered when the ASP.NET function app is missing registration for the appropriate method.

## Rule description

The ASP.NET function app should have registration for `ConfigureFunctionsWebApplication` method. This rule will be violated when the app is missing this registration.

## How to fix violations
Comment thread
surgupta-msft marked this conversation as resolved.

Add registration for `ConfigureFunctionsWebApplication` method in Function App. Refer to [public documentation](https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide#aspnet-core-integration) for guidance on ASP.NET Core integration.
3 changes: 3 additions & 0 deletions sdk/Sdk.Analyzers/DiagnosticDescriptors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,8 @@ private static DiagnosticDescriptor Create(string id, string title,string messag
public static DiagnosticDescriptor IterableBindingTypeExpectedForBlobContainer { get; }
= Create(id: "AZFW0011", title: "Invalid binding type", messageFormat: "The binding type '{0}' must be iterable for container path.",
category: Constants.DiagnosticsCategories.Usage, severity: DiagnosticSeverity.Error);
public static DiagnosticDescriptor CorrectRegistrationExpectedInAspNetIntegration { get; }
= Create(id: "AZFW0014", title: "Missing expected registration in ASPNet Integration", messageFormat: "The registration for method '{0}' is expected for ASP.Net Integration.",
category: Constants.DiagnosticsCategories.Usage, severity: DiagnosticSeverity.Error);
}
}
18 changes: 18 additions & 0 deletions sdk/Sdk.Analyzers/Extensions/MethodSymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,23 @@ public static bool IsFunction(this IMethodSymbol symbol, SymbolAnalysisContext a

return attributes.Any(a => attributeType.IsAssignableFrom(a.AttributeClass, true));
}

/// <summary>
/// Checks if a method symbol is a Main method. This also checks for implicit main in top-level statements
Comment thread
surgupta-msft marked this conversation as resolved.
Outdated
/// </summary>
/// <param name="symbol">The method symbol to check.</param>
/// <returns>A boolean value indicating whether the method symbol is a Main method.</returns>
public static bool IsMainMethod(this IMethodSymbol symbol)
{
bool isMainMethod = symbol?.IsStatic == true && symbol.Name switch
{
"Main" => true,
"$Main" => true,
"<Main>$" => true,
_ => false
};

return isMainMethod;
}
}
}
48 changes: 48 additions & 0 deletions sdk/Sdk.Analyzers/RegistrationExpectedInASPNetIntegration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Microsoft.Azure.Functions.Worker.Sdk.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class RegistrationExpectedInASPNetIntegration : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.CorrectRegistrationExpectedInAspNetIntegration);

private const string ExpectedRegistrationMethod = "ConfigureFunctionsWebApplication";
private const string AspNetExtensionAssemblyName = "Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore";

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method);
}

private static void AnalyzeMethod(SymbolAnalysisContext context)
{
var symbol = (IMethodSymbol)context.Symbol;

bool isAspNetAssembly = context.Compilation.ReferencedAssemblyNames.Any(assembly => assembly.Name.Equals(AspNetExtensionAssemblyName));

if (!isAspNetAssembly || !symbol.IsMainMethod())
{
return;
}

SyntaxReference syntaxReference = symbol.DeclaringSyntaxReferences.FirstOrDefault();
string code = syntaxReference.SyntaxTree.GetText().ToString();

Comment thread
surgupta-msft marked this conversation as resolved.
Outdated
if (!code.Contains(ExpectedRegistrationMethod))
Comment thread
surgupta-msft marked this conversation as resolved.
Outdated
{
var diagnostic = Diagnostic.Create(DiagnosticDescriptors.CorrectRegistrationExpectedInAspNetIntegration, symbol.Locations.First(), ExpectedRegistrationMethod);
context.ReportDiagnostic(diagnostic);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
using Xunit;
using AnalyzerTest = Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerTest<Microsoft.Azure.Functions.Worker.Sdk.Analyzers.RegistrationExpectedInASPNetIntegration, Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier>;
using Verify = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier<Microsoft.Azure.Functions.Worker.Sdk.Analyzers.RegistrationExpectedInASPNetIntegration>;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Testing;
using System.Collections.Immutable;

namespace Sdk.Analyzers.Tests
{
public class RegistrationExpectedInAspNetIntegrationTests
{
private const string ExpectedRegistrationMethod = "ConfigureFunctionsWebApplication";

[Fact]
public async Task AspNetIntegration_MissingRegistration_Diagnostics_Expected()
{
string testCode = @"
namespace AspNetIntegration
{
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

class Program
{
static void Main(string[] args)
{
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.Build();

host.Run();
}
}
}";

var test = new AnalyzerTest
{
ReferenceAssemblies = ReferenceAssemblies.Net.Net60.WithPackages(ImmutableArray.Create(
new PackageIdentity("Microsoft.Azure.Functions.Worker", "1.19.0"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Sdk", "1.14.1"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs", "6.0.0"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore", "1.0.0"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Abstractions", "5.0.0"),
new PackageIdentity("Microsoft.Extensions.Hosting.Abstractions", "6.0.0")
)),

TestCode = testCode
};

test.ExpectedDiagnostics.Add(Verify.Diagnostic()
.WithSeverity(Microsoft.CodeAnalysis.DiagnosticSeverity.Error)
.WithSpan(13, 37, 13, 41)
.WithArguments(ExpectedRegistrationMethod));

await test.RunAsync();
}


[Fact]
public async Task AspNetIntegrationWithTrigger_MissingRegistration_Diagnostics_Expected()
{
string testCode = @"
namespace AspNetIntegration
{
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

class Program
{
static void Main(string[] args)
{
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.Build();

host.Run();
}
}
}

namespace AspNetIntegration
{
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;

public class FunctionHttpTrigger
{
[Function(nameof(FunctionHttpTrigger))]
public void Run([HttpTrigger(AuthorizationLevel.Anonymous, ""post"")] HttpRequestData req)
{
}
}
}";

var test = new AnalyzerTest
{
ReferenceAssemblies = ReferenceAssemblies.Net.Net60.WithPackages(ImmutableArray.Create(
new PackageIdentity("Microsoft.Azure.Functions.Worker", "1.19.0"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Sdk", "1.14.1"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs", "6.0.0"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore", "1.0.0"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Abstractions", "5.0.0"),
new PackageIdentity("Microsoft.Extensions.Hosting.Abstractions", "6.0.0"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Http", "3.1.0")
)),

TestCode = testCode
};

test.ExpectedDiagnostics.Add(Verify.Diagnostic()
.WithSeverity(Microsoft.CodeAnalysis.DiagnosticSeverity.Error)
.WithSpan(13, 37, 13, 41)
.WithArguments(ExpectedRegistrationMethod));

await test.RunAsync();
}

[Fact]
public async Task NotAspNetIntegration_Diagnostics_NotExpected()
{
string testCode = @"
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace SampleApp
{
public class Program
{
public static void Main()
Comment thread
surgupta-msft marked this conversation as resolved.
Outdated
{
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.Build();

host.Run();
}

public static void Method1()
{
}

private static void Method2()
{
}
}
}";

var test = new AnalyzerTest
{
ReferenceAssemblies = ReferenceAssemblies.Net.Net50.WithPackages(ImmutableArray.Create(
new PackageIdentity("Microsoft.Azure.Functions.Worker", "1.18.0"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Sdk", "1.13.0"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs", "6.0.0"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Abstractions", "1.3.0"))),

TestCode = testCode
};

// test.ExpectedDiagnostics is an empty collection.

await test.RunAsync();
}

[Fact]
public async Task AspNetIntegration_WithRegistration_Diagnostics_NotExpected()
{
string testCode = @"
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace AspNetIntegration
{
class Program
{
static void Main(string[] args)
{

//<docsnippet_aspnet_registration>
var host = new HostBuilder()
.ConfigureFunctionsWebApplication()
.Build();

host.Run();
//</docsnippet_aspnet_registration>
}

public static void Method1()
{
}

private static void Method2()
{
}
}
}";

var test = new AnalyzerTest
{
ReferenceAssemblies = ReferenceAssemblies.Net.Net60.WithPackages(ImmutableArray.Create(
new PackageIdentity("Microsoft.Azure.Functions.Worker", "1.19.0"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Sdk", "1.14.1"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs", "6.0.0"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore", "1.0.0"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Abstractions", "5.0.0"),
new PackageIdentity("Microsoft.Extensions.Hosting.Abstractions", "6.0.0")
)),

TestCode = testCode
};

// test.ExpectedDiagnostics is an empty collection.

await test.RunAsync();
}

[Fact]
public async Task AspNetIntegration_WithMiddleWare_WithRegistration_Diagnostics_NotExpected()
{
string testCode = @"
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace AspNetIntegration
{
class Program
{
static void Main(string[] args)
{
#if ENABLE_MIDDLEWARE
var host = new HostBuilder()
.ConfigureFunctionsWebApplication(builder =>
{
// can still register middleware and use this extension method the same way
// .ConfigureFunctionsWorkerDefaults() is used
builder.UseWhen<RoutingMiddleware>((context)=>
{
// We want to use this middleware only for http trigger invocations.
return context.FunctionDefinition.InputBindings.Values
.First(a => a.Type.EndsWith(""Trigger"")).Type == ""httpTrigger"";
});
})
.Build();
host.Run();
#else
//<docsnippet_aspnet_registration>
var host = new HostBuilder()
.ConfigureFunctionsWebApplication()
.Build();

host.Run();
//</docsnippet_aspnet_registration>
#endif
}
}
}";

var test = new AnalyzerTest
{
ReferenceAssemblies = ReferenceAssemblies.Net.Net60.WithPackages(ImmutableArray.Create(
new PackageIdentity("Microsoft.Azure.Functions.Worker", "1.19.0"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Sdk", "1.14.1"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs", "6.0.0"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore", "1.0.0"),
new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Abstractions", "5.0.0"),
new PackageIdentity("Microsoft.Extensions.Hosting.Abstractions", "6.0.0")
)),

TestCode = testCode
};

// test.ExpectedDiagnostics is an empty collection.

await test.RunAsync();
}
}
}