| File: InvokeAsyncOfObjectAnalyzer.cs | Web Access |
| Project: src\aspnetcore\src\Components\Analyzers\src\Microsoft.AspNetCore.Components.Analyzers.csproj (Microsoft.AspNetCore.Components.Analyzers) |
// 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.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; #nullable enable namespace Microsoft.AspNetCore.Components.Analyzers; /// <summary> /// Analyzer that detects usage of InvokeAsync<object> and recommends using InvokeVoidAsync instead. /// </summary> [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class InvokeAsyncOfObjectAnalyzer : DiagnosticAnalyzer { private const string JSRuntimeExtensionsTypeName = "Microsoft.JSInterop.JSRuntimeExtensions"; private const string JSObjectReferenceExtensionsTypeName = "Microsoft.JSInterop.JSObjectReferenceExtensions"; private const string JSInProcessRuntimeExtensionsTypeName = "Microsoft.JSInterop.JSInProcessRuntimeExtensions"; private const string JSInProcessObjectReferenceExtensionsTypeName = "Microsoft.JSInterop.JSInProcessObjectReferenceExtensions"; public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.UseInvokeVoidAsyncForObjectReturn); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); context.RegisterCompilationStartAction(compilationContext => { // Cache type lookups once per compilation var ijsRuntimeType = compilationContext.Compilation.GetTypeByMetadataName("Microsoft.JSInterop.IJSRuntime"); var ijsObjectReferenceType = compilationContext.Compilation.GetTypeByMetadataName("Microsoft.JSInterop.IJSObjectReference"); var ijsInProcessRuntimeType = compilationContext.Compilation.GetTypeByMetadataName("Microsoft.JSInterop.IJSInProcessRuntime"); var ijsInProcessObjectReferenceType = compilationContext.Compilation.GetTypeByMetadataName("Microsoft.JSInterop.IJSInProcessObjectReference"); var jsRuntimeExtensionsType = compilationContext.Compilation.GetTypeByMetadataName(JSRuntimeExtensionsTypeName); var jsObjectReferenceExtensionsType = compilationContext.Compilation.GetTypeByMetadataName(JSObjectReferenceExtensionsTypeName); var jsInProcessRuntimeExtensionsType = compilationContext.Compilation.GetTypeByMetadataName(JSInProcessRuntimeExtensionsTypeName); var jsInProcessObjectReferenceExtensionsType = compilationContext.Compilation.GetTypeByMetadataName(JSInProcessObjectReferenceExtensionsTypeName); var objectType = compilationContext.Compilation.GetSpecialType(SpecialType.System_Object); if (ijsRuntimeType is null && ijsObjectReferenceType is null) { // JSInterop types are not available return; } compilationContext.RegisterOperationAction(operationContext => { var invocation = (IInvocationOperation)operationContext.Operation; var targetMethod = invocation.TargetMethod; // Check if the method is named InvokeAsync and is generic if (targetMethod.Name != "InvokeAsync" || !targetMethod.IsGenericMethod) { return; } // Check if the type argument is object if (targetMethod.TypeArguments.Length != 1 || !SymbolEqualityComparer.Default.Equals(targetMethod.TypeArguments[0], objectType)) { return; } // Check if the method is on IJSRuntime, IJSObjectReference, or their in-process variants // This includes extension methods on these types var containingType = targetMethod.ContainingType; var receiverType = GetReceiverType(invocation); if (!IsJSInteropType(receiverType, ijsRuntimeType, ijsObjectReferenceType, ijsInProcessRuntimeType, ijsInProcessObjectReferenceType) && !IsJSInteropExtensionClass(containingType, jsRuntimeExtensionsType, jsObjectReferenceExtensionsType, jsInProcessRuntimeExtensionsType, jsInProcessObjectReferenceExtensionsType)) { return; } operationContext.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.UseInvokeVoidAsyncForObjectReturn, invocation.Syntax.GetLocation())); }, OperationKind.Invocation); }); } private static ITypeSymbol? GetReceiverType(IInvocationOperation invocation) { // For extension methods, the first argument is the receiver if (invocation.TargetMethod.IsExtensionMethod && invocation.Arguments.Length > 0) { return invocation.Arguments[0].Value.Type; } // For instance methods return invocation.Instance?.Type; } private static bool IsJSInteropType( ITypeSymbol? type, INamedTypeSymbol? ijsRuntimeType, INamedTypeSymbol? ijsObjectReferenceType, INamedTypeSymbol? ijsInProcessRuntimeType, INamedTypeSymbol? ijsInProcessObjectReferenceType) { if (type is null) { return false; } // Check if the type implements any of the JSInterop interfaces if (ijsRuntimeType is not null && ImplementsInterface(type, ijsRuntimeType)) { return true; } if (ijsObjectReferenceType is not null && ImplementsInterface(type, ijsObjectReferenceType)) { return true; } if (ijsInProcessRuntimeType is not null && ImplementsInterface(type, ijsInProcessRuntimeType)) { return true; } if (ijsInProcessObjectReferenceType is not null && ImplementsInterface(type, ijsInProcessObjectReferenceType)) { return true; } return false; } private static bool ImplementsInterface(ITypeSymbol type, INamedTypeSymbol interfaceType) { if (SymbolEqualityComparer.Default.Equals(type, interfaceType)) { return true; } foreach (var iface in type.AllInterfaces) { if (SymbolEqualityComparer.Default.Equals(iface, interfaceType)) { return true; } } return false; } private static bool IsJSInteropExtensionClass( INamedTypeSymbol containingType, INamedTypeSymbol? jsRuntimeExtensionsType, INamedTypeSymbol? jsObjectReferenceExtensionsType, INamedTypeSymbol? jsInProcessRuntimeExtensionsType, INamedTypeSymbol? jsInProcessObjectReferenceExtensionsType) { // Use symbol equality comparison instead of string comparison return SymbolEqualityComparer.Default.Equals(containingType, jsRuntimeExtensionsType) || SymbolEqualityComparer.Default.Equals(containingType, jsObjectReferenceExtensionsType) || SymbolEqualityComparer.Default.Equals(containingType, jsInProcessRuntimeExtensionsType) || SymbolEqualityComparer.Default.Equals(containingType, jsInProcessObjectReferenceExtensionsType); } }