File: Analyzers\ComponentParameterNullableWarningSuppressor.cs
Web Access
Project: src\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;
        }
    }
}