-
Couldn't load subscription status.
- Fork 10.5k
Add codefixer and completion provider to install OpenAPI package from extension methods #55963
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
411d738
ab4bbe6
a3531f0
1a8c286
f92547b
63e2ba0
87be404
a4797f8
b1a4ecc
e9f6fba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Composition; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.AspNetCore.App.Analyzers.Infrastructure; | ||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CodeFixes; | ||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
| using Microsoft.CodeAnalysis.ExternalAccess.AspNetCore.AddPackage; | ||
|
|
||
| namespace Microsoft.AspNetCore.Analyzers.Dependencies; | ||
|
|
||
| [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AddPackageFixer)), Shared] | ||
| public sealed class AddPackageFixer : CodeFixProvider | ||
| { | ||
| public override async Task RegisterCodeFixesAsync(CodeFixContext context) | ||
| { | ||
| var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | ||
| var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); | ||
| if (root == null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (semanticModel == null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| var wellKnownTypes = WellKnownTypes.GetOrCreate(semanticModel.Compilation); | ||
|
|
||
| Dictionary<ThisAndExtensionMethod, PackageSourceAndNamespace> _wellKnownExtensionMethodCache = new() | ||
captainsafia marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| { | ||
| new(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_Extensions_DependencyInjection_IServiceCollection), "AddOpenApi"), | ||
| new("Microsoft.AspNetCore.OpenApi", "Microsoft.Extensions.DependencyInjection") | ||
| }, | ||
| { | ||
| new(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Builder_WebApplication), "MapOpenApi"), | ||
| new("Microsoft.AspNetCore.OpenApi", "Microsoft.AspNetCore.Builder") | ||
| } | ||
| }; | ||
|
|
||
| foreach (var diagnostic in context.Diagnostics) | ||
captainsafia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| var location = diagnostic.Location.SourceSpan; | ||
| var node = root.FindNode(location); | ||
| if (node == null) | ||
| { | ||
| return; | ||
| } | ||
| var methodName = node is IdentifierNameSyntax identifier ? identifier.Identifier.Text : null; | ||
| if (methodName == null) | ||
| { | ||
| return; | ||
| } | ||
| var symbol = semanticModel.GetSymbolInfo(((MemberAccessExpressionSyntax)node.Parent!).Expression).Symbol; | ||
| var symbolType = symbol switch | ||
| { | ||
| IMethodSymbol methodSymbol => methodSymbol.ReturnType, | ||
| IPropertySymbol propertySymbol => propertySymbol.Type, | ||
| ILocalSymbol localSymbol => localSymbol.Type, | ||
| _ => null | ||
| }; | ||
|
|
||
| if (symbolType == null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| var targetThisAndExtensionMethod = new ThisAndExtensionMethod(symbolType, methodName); | ||
| if (_wellKnownExtensionMethodCache.TryGetValue(targetThisAndExtensionMethod, out var packageSourceAndNamespace)) | ||
|
||
| { | ||
| var position = diagnostic.Location.SourceSpan.Start; | ||
| var packageInstallData = new AspNetCoreInstallPackageData( | ||
| packageSource: null, | ||
| packageName: packageSourceAndNamespace.packageName, | ||
| packageVersionOpt: null, | ||
| packageNamespaceName: packageSourceAndNamespace.namespaceName); | ||
| var codeAction = await AspNetCoreAddPackageCodeAction.TryCreateCodeActionAsync( | ||
| context.Document, | ||
| position, | ||
| packageInstallData, | ||
| context.CancellationToken); | ||
|
|
||
| if (codeAction != null) | ||
| { | ||
| context.RegisterCodeFix(codeAction, diagnostic); | ||
| } | ||
| } | ||
|
|
||
| } | ||
| } | ||
|
|
||
| public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; | ||
|
|
||
| public override ImmutableArray<string> FixableDiagnosticIds { get; } = ["CS1061"]; | ||
captainsafia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| namespace Microsoft.AspNetCore.Analyzers; | ||
|
|
||
| internal record struct PackageSourceAndNamespace(string packageName, string namespaceName); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System; | ||
| using Microsoft.CodeAnalysis; | ||
|
|
||
| namespace Microsoft.AspNetCore.Analyzers; | ||
|
|
||
| internal readonly struct ThisAndExtensionMethod(ITypeSymbol thisType, string extensionMethod) | ||
| { | ||
| public ITypeSymbol ThisType { get; } = thisType; | ||
| public string ExtensionMethod { get; } = extensionMethod; | ||
|
|
||
| public override bool Equals(object? obj) | ||
| { | ||
| if (obj is ThisAndExtensionMethod other) | ||
| { | ||
| return SymbolEqualityComparer.Default.Equals(ThisType, other.ThisType) && | ||
| ExtensionMethod.Equals(other.ExtensionMethod, StringComparison.OrdinalIgnoreCase); | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| public override int GetHashCode() => HashCode.Combine(ThisType, ExtensionMethod); | ||
captainsafia marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using VerifyCS = Microsoft.AspNetCore.Analyzers.Verifiers.CSharpCodeFixVerifier< | ||
| Microsoft.AspNetCore.Analyzers.WebApplicationBuilder.WebApplicationBuilderAnalyzer, | ||
| Microsoft.AspNetCore.Analyzers.Dependencies.AddPackageFixer>; | ||
|
|
||
| namespace Microsoft.AspNetCore.Analyzers.Dependencies; | ||
| public class AddPackagesTest | ||
| { | ||
| [Fact] | ||
| public async Task CanFixMissingExtensionMethodForDI() | ||
| { | ||
| // Arrange | ||
| var source = @" | ||
| using Microsoft.AspNetCore.Builder; | ||
| using Microsoft.Extensions.Hosting; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| var builder = WebApplication.CreateBuilder(args); | ||
|
|
||
| builder.Services.{|CS1061:AddOpenApi|}(); | ||
| "; | ||
| var fixedSource = @" | ||
| using Microsoft.AspNetCore.Builder; | ||
| using Microsoft.Extensions.Hosting; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| var builder = WebApplication.CreateBuilder(args); | ||
|
|
||
| builder.Services.AddOpenApi(); | ||
| "; | ||
|
|
||
| // Assert | ||
| await VerifyCS.VerifyCodeFixAsync(source, fixedSource); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task CanFixMissingExtensionMethodForBuilder() | ||
| { | ||
| // Arrange | ||
| var source = @" | ||
| using Microsoft.AspNetCore.Builder; | ||
| using Microsoft.Extensions.Hosting; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| var app = WebApplication.Create(); | ||
|
|
||
| app.{|CS1061:MapOpenApi|}(); | ||
| "; | ||
| var fixedSource = @" | ||
| using Microsoft.AspNetCore.Builder; | ||
| using Microsoft.Extensions.Hosting; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| var app = WebApplication.Create(args); | ||
|
|
||
| app.MapOpenApi(); | ||
| "; | ||
|
|
||
| // Assert | ||
| await VerifyCS.VerifyCodeFixAsync(source, fixedSource); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.