| File: ComponentDisposableSanityAnalyzer.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 System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; #nullable enable namespace Microsoft.AspNetCore.Components.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ComponentDisposableSanityAnalyzer : DiagnosticAnalyzer { private const string IAsyncDisposableTypeName = "System.IAsyncDisposable"; private const string ValueTaskTypeName = "System.Threading.Tasks.ValueTask"; public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create( DiagnosticDescriptors.ComponentHasDisposeWithoutIDisposable, DiagnosticDescriptors.ComponentHasDisposeAsyncWithoutIAsyncDisposable); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); context.RegisterCompilationStartAction(context => { var iComponentType = context.Compilation.GetTypeByMetadataName(ComponentsApi.IComponent.MetadataName); if (iComponentType is null) { return; } var iDisposableType = context.Compilation.GetSpecialType(SpecialType.System_IDisposable); var iAsyncDisposableType = context.Compilation.GetTypeByMetadataName(IAsyncDisposableTypeName); var valueTaskType = context.Compilation.GetTypeByMetadataName(ValueTaskTypeName); context.RegisterSymbolAction(context => { var type = (INamedTypeSymbol)context.Symbol; if (type.TypeKind != TypeKind.Class || !type.AllInterfaces.Any(i => SymbolEqualityComparer.Default.Equals(i, iComponentType))) { return; } var implementsIDisposable = type.AllInterfaces.Any(i => SymbolEqualityComparer.Default.Equals(i, iDisposableType)); var implementsIAsyncDisposable = iAsyncDisposableType is not null && type.AllInterfaces.Any(i => SymbolEqualityComparer.Default.Equals(i, iAsyncDisposableType)); if (implementsIDisposable && implementsIAsyncDisposable) { return; } foreach (var member in type.GetMembers()) { if (member is not IMethodSymbol { MethodKind: MethodKind.Ordinary, DeclaredAccessibility: Accessibility.Public, IsStatic: false, IsGenericMethod: false, ReturnsByRef: false, ReturnsByRefReadonly: false } method || !method.Parameters.IsEmpty) { continue; } if (!implementsIDisposable && method.Name == "Dispose" && method.ReturnType.SpecialType == SpecialType.System_Void) { context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.ComponentHasDisposeWithoutIDisposable, method.Locations[0], type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat))); } else if (iAsyncDisposableType is not null && !implementsIAsyncDisposable && method.Name == "DisposeAsync" && valueTaskType is not null && SymbolEqualityComparer.Default.Equals(method.ReturnType, valueTaskType)) { context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.ComponentHasDisposeAsyncWithoutIAsyncDisposable, method.Locations[0], type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat))); } } }, SymbolKind.NamedType); }); } }