| File: Analyzers\ComponentParameterNullableWarningSuppressor.cs | Web Access |
| Project: src\roslyn\src\Razor\src\Compiler\Microsoft.CodeAnalysis.Razor.Compiler\src\Microsoft.CodeAnalysis.Razor.Compiler.csproj (Microsoft.CodeAnalysis.Razor.Compiler) |
// 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 Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.CodeAnalysis.Razor.Compiler.Analyzers; #pragma warning disable RS1041 // Compiler extensions should be implemented in assemblies targeting netstandard2.0 [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ComponentParameterNullableWarningSuppressor : DiagnosticSuppressor { private static readonly LocalizableString Description = new LocalizableResourceString(nameof(AnalyzerResources.ComponentParameterNullableWarningSuppressorDescription), AnalyzerResources.ResourceManager, typeof(AnalyzerResources)); //Suppress CS8618: "Non-nullable {0} '{1}' must contain a non-null value when exiting constructor. Consider declaring the {0} as nullable." public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => [ new SuppressionDescriptor(AnalyzerIDs.ComponentParameterNullableWarningSuppressionId, "CS8618", Description) ]; public override void ReportSuppressions(SuppressionAnalysisContext context) { var editorRequiredSymbol = context.Compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Components.EditorRequiredAttribute"); var parameterSymbol = context.Compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Components.ParameterAttribute"); var componentSymbol = context.Compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Components.IComponent"); if (parameterSymbol is null || editorRequiredSymbol is null || componentSymbol is null) { return; } foreach (var diagnostic in context.ReportedDiagnostics) { var node = diagnostic.Location.SourceTree?.GetRoot(context.CancellationToken).FindNode(diagnostic.Location.SourceSpan); if (node is PropertyDeclarationSyntax propertySyntax && propertySyntax.AttributeLists.Any()) { var symbol = context.GetSemanticModel(propertySyntax.SyntaxTree).GetDeclaredSymbol(propertySyntax, context.CancellationToken); if (IsValidEditorRequiredParameter(symbol)) { context.ReportSuppression(Suppression.Create(SupportedSuppressions[0], diagnostic)); } } } bool IsValidEditorRequiredParameter(ISymbol? symbol) { // public instance property, with a public setter if (symbol is not IPropertySymbol { DeclaredAccessibility: Accessibility.Public, IsStatic: false, SetMethod.DeclaredAccessibility: Accessibility.Public }) { return false; } // containing type implements IComponent if (!symbol.ContainingType.AllInterfaces.Any(componentSymbol, static (@interface, componentSymbol) => @interface.Equals(componentSymbol, SymbolEqualityComparer.Default))) { return false; } // has both [Parameter] and [EditorRequired] attributes bool hasParameter = false, hasRequired = false; foreach (var attribute in symbol.GetAttributes()) { if (!hasParameter && SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, parameterSymbol)) { hasParameter = true; if (hasRequired) { break; } continue; } if (!hasRequired && SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, editorRequiredSymbol)) { hasRequired = true; if (hasParameter) { break; } continue; } } return hasParameter && hasRequired; } } }